快速搞定,查询 + 表格,这种页面

2,810 阅读4分钟

做后台系统的时候,估计接触最多的就是表单 + 表格,以此进行增删改查的操作。

本篇,努力将增删改查的通用逻辑分析清除,使用的时候,能通过简单的配置,就搞定一个复杂的页面。

可以的话,可以先看下准备篇:

本篇代码,涉及的文件比较多,想要看细节,可以参照github

TL;DR

  • 基础的查询和表格显示
  • 表格增加分页和排序
  • 更改查询条件,进行查询需要重置页码和排序
  • 重置查询条件
  • 新加表格项
  • 编辑表格项

1. 基础的查询和表格

第一步,先将表单显示出来,表格的数据通过请求拿到数据,也显示出来。

之前封装过表格和表单插件。

一个页面,先看下,查询的条件,将其变成一个表单的model,表格就更简单,将进行配置化。

当然查询条件,除了表单,还外加下分页和排序数据,pageIndex pageSize sortBy isAsc

第一版展示效果:

table-form2

第一版核心代码,详细代码见文末: table-form1 table-form6

github上可以切换c1分支

2. 排序和分页

排序的话:

  • 不分页,直接配置colConfigsortable:true,就在该列的表头显示排序符号
  • 分页,需要请求数据的话,配置colConfigsortable:custom,在组件上写上@sort-change="sortChange",点击排序符号的时候,就会触发sort-change事件。

页码变化的话: 分页组件有事件current-change,相应的设置pageIndex

监测pageIndex sortBy isAsc发生变化的时候,请求表格数据

table-form7 table-form8

github上可以切换c2分支

3. 修改查询条件

点击查询的时候,按照查询条件,请求数据。

!!!注意,当前页数需要重置为1。

table-form9 table-form10

github上可以切换c3分支

4. 重置

重置其实有三个方面:

  • 重置查询表单:将所有字段值重置为初始值并移除校验结果
  • 重置当前页:设置为1
  • 重置排序:将排序重置为默认排序

!!!注意,排序的设置,除了data里的isAsc和sortBy,还需要将表格里的排序图标保持正确。

设置排序的图标,有两个方法:

  • clearSort:用于清空排序条件,数据会恢复成未排序的状态
  • sort:手动对 Table 进行排序。参数prop属性指定排序列,order指定排序顺序。

所以,需要watch下data里的isAsc和sortBy,发生变化的时候,显示相应的排序图标。

为了监测方便,将isAsc和sortBy放在一个对象里,这样只需要监听一次

clickResetBtn() {
  // 重置查询表单,将所有字段值重置为初始值并移除校验结果
  this.$refs.queryForm.resetFields();
  // 重置页数
  this.pageIndex = 1;
  // 重置排序
  this.sortConfig = { ...this.sortConfigDefault };
},

效果图: table-form11.gif 代码: table-form11

github上可以切换c4分支

5. 新建

新建功能一般会弹框显示,填完表单,确定之后,会在列表上显示。

用代码表达:

  • DOM提前准备好弹框和表单,但不显示
  • 点击新建之后,显示弹框
  • 弹框里点击提交,有错提示错误,没错请求新建的接口
  • 此时看需求,但是多数需要,重置页面,再次请求列表数据,以显示刚刚创建的信息
  • 细节:考虑到第二次点击新建的时候,同一个表单可能会有残留的验证提示,所以需要重置弹框表单

效果图: table-form12.gif

代码:

DOM的核心代码:

  el-dialog(:title="dialogFormTitle" :visible.sync="isShowDialogForm" center width="340px")
    enhanced-el-form(ref="dialogForm" :model="dialogFormModel" :schema="dialogFormSchema"  label-width="70px" label-position= "right")
      template(#footer)
        el-form-item
          el-button.btn(type='primary', @click='clickCancelOfDialogForm') 取消
          el-button.btn(plain, @click='clickConfirmOfDialogForm') 确定

table-form12

table-form14

github上可以切换c5分支

6. 编辑

编辑功能通常和新建用的同一个表单框。 但是和新建不一样的是,编辑表单是有数据的。

用代码表达:

  • DOM提前准备好弹框和表单,但不显示
  • 表格配置加上编辑
  • 点击编辑之后,填充表单数据,显示弹框
  • 弹框里点击提交,有错提示错误,没错请求编辑的接口
  • 此时看需求,但是多数需要,将当前行的数据及时更新

效果图: table-form16.gif

代码:

colConfigs.js增加操作列:

  { prop: "score", label: "分数", sortable: "custom" },
  { slotName: "action", label: "操作" }

table-form16

github上可以切换c6分支

7. 待续

时间原因,先写到这里。
后期追加下 删除、导出
终极目标:封装成组件,高效使用~

代码

代码:1. 显示查询的表单和表格

这边使用vue create创建了项目。

整体的代码略微复杂,加了mock数据、加了请求、全局用$api。

相应的文件有很多:但是除了App.vue文件,其余文件后期不动。

目录结构这样,红框的文件就是需要动的文件

table-form3

App.vue

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(ref="queryForm" :model="model" :schema="schema"  :inline="true" label-width="100px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
  //- 表格区域
  enhanced-el-table(:data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeTablePage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      model: {},
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      isAsc: "",
      sortBy: "",
      // 数据总长度,基本只给分页组件用的
      dataCount: 0
    };
  },

  mounted() {
    this.getTableData();
  },
  methods: {
    async getTableData() {
      const { pageIndex, pageSize, sortBy, isAsc } = this;
      let params = { ...this.model, pageIndex, pageSize, sortBy, isAsc };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },

    clickName() {},
    clickSearchBtn() {},
    changeTablePage(curPageIndex) {
      this.pageIndex = curPageIndex;
    }
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>

表单和表格的配置:colConfig.js、 schema.js

colConfig.js

export default [
  { prop: "orderNumber", label: "序号" },
  {
    slotName: "name",
    label: "姓名"
  },
  {
    prop: "year",
    label: "年份"
  },
  {
    prop: "quarter",
    label: "季度"
  },

  { prop: "score", label: "分数", sortable: "custom" }
];

schema.js

export default [
  {
    modelKey: "year",
    label: "年份",
    type: "date-picker",
    props: {
      type: "year",
      format: "yyyy",
      valueFormat: "yyyy",
      style: { width: "150px" }
    }
  },
  {
    modelKey: "quarter",
    label: "季度",
    type: "select",
    props: {
      options: [
        { value: "春", label: "春" },
        { value: "夏", label: "夏" },
        { value: "秋", label: "秋" },
        { value: "冬", label: "冬" }
      ],
      style: { width: "150px" }
    }
  },

  { modelKey: "name", label: "姓名", type: "input", style: { width: "150px" } }
];


两个组件:EnhancedElTable.vue和EnhancedElForm.vue

EnhancedElTable.vue

<template lang="pug">
  el-table(ref="elTable" :data="data" v-bind="$attrs" v-on="$listeners")
    template(v-for="colConfig in colConfigs")
      slot(v-if="colConfig.slotName" :name="colConfig.slotName" v-bind="colConfig")
      el-table-column(v-else v-bind="colConfig" :key="colConfig.prop")
</template>

<script>
export default {
  name: "enhanced-el-table",
  props: {
    data: {
      type: Array,
      default() {
        return [];
      }
    },
    colConfigs: {
      type: Array,
      default() {
        return [];
      }
    }
  },
  mounted() {
    console.log(444, this.tableData);
    const methods = [
      "clearSelection",
      "toggleRowSelection",
      "toggleAllSelection",
      "toggleRowExpansion",
      "setCurrentRow",
      "clearSort",
      "clearFilter",
      "doLayout",
      "sort"
    ];
    methods.forEach(method => (this[method] = this.$refs.elTable[method]));
  }
};
</script>

EnhancedElForm.vue

<template lang="pug">
el-form(ref="elForm" :model="model" :rules="rules" v-bind="$attrs" v-on="$listeners")
  slot(name="header")
  
  template(v-for="config in schema" )
    slot(v-if="config.slotName" :name="config.slotName" v-bind="config")
 
    el-form-item(v-else :label="config.label" :prop="config.modelKey" :key="config.modelKey")
      el-radio-group(v-if="config.type==='radio-group'"   v-model="model[config.modelKey]" v-bind="config.props")
        el-radio(v-for="(item,index) in config.props.options" :key="index" :label="typeof item==='object'?item.value:item") {{ typeof item==='object'?item.label:item }}
      el-checkbox-group(v-else-if="config.type==='checkbox-group'"   v-model="model[config.modelKey]" v-bind="config.props")
        el-checkbox(v-for="(item,index) in config.props.options" :key="index" :label="typeof item==='object'?item.value:item") {{ typeof item==='object'?item.label:item }}
      el-select(v-else-if="config.type==='select'"   v-model="model[config.modelKey]" v-bind="config.props")
        el-option(v-for="(item,index) in config.props.options" :key="index" :value="typeof item==='object'?item.value:item" :label="typeof item==='object'?item.label:item")

      component(v-else :is="'el-'+config.type" v-model="model[config.modelKey]" v-bind="config.props") {{config.text}}

  slot(name="footer")
</template>
<script>
export default {
  name: "enhanced-el-form",
  props: {
    model: {
      type: Object,
      default() {
        return {};
      }
    },
    schema: {
      type: Array,
      default() {
        return {};
      }
    }
  },
  computed: {
    rules() {
      return this.schema.reduce((acc, cur) => {
        acc[cur.modelKey] = cur.rules;
        // 日期组件可能有children
        const hasChildren = cur.children && cur.children.length;
        hasChildren &&
          cur.children.forEach(child => (acc[child.modelKey] = child.rules));
        return acc;
      }, {});
    }
  },
  mounted() {
    // el-form上面的方法继承过来
    const methods = [
      "validate",
      "validateField",
      "resetFields",
      "clearValidate"
    ];
    methods.forEach(method => (this[method] = this.$refs.elForm[method]));
  }
};
</script>

mock数据部分:.env.mock、mock.js、vue.config.js

.env.mock

NODE_ENV = 'mock'

mock.js

module.exports = Array.from({ length: 28 }, (v, i) => ({
  orderNumber: i + 1,
  year: "199" + i - 0 + 1 + "",
  quarter: i % 4 === 1 ? "春" : i % 4 === 2 ? "夏" : i % 4 === 3 ? "秋" : "冬",
  name: `李三${i + 1}`,
  score: `8${i}`
}));

vue.config.js

const nativeData = require("./mock");
module.exports = {
  devServer: {
    before(app) {
      app.get("/api/list", (req, res) => {
        console.log(req.query);
        const { pageIndex, pageSize, sortBy, isAsc, year, quarter, name } = req.query;
        let data = [...nativeData];
        // 拿到查询条件
        const conditions = { year, quarter, name };
        // 看查询条件有没有值
        const keysOfHasValue = Object.keys(conditions).filter(
          key => conditions[key]
        );
        const isHasCondition = keysOfHasValue.length > 0;
        // 如果有查询条件的话就过滤下
        if (isHasCondition) {
          data = data.filter(item =>
            keysOfHasValue.every(key => item[key] === conditions[key])
          );
        }

        // 如果有排序的话
        if (sortBy && isAsc) {
          isAsc === "true"
            ? data.sort((x, y) => x[sortBy] - y[sortBy])
            : data.sort((x, y) => y[sortBy] - x[sortBy]);
        }

        // 如果有分页的话
        if (pageSize) {
          data = data.slice((pageIndex - 1) * pageSize, pageSize * pageIndex);
        }
        return res.json({
          state: 1,
          data,
          dataCount: data.length
        });
      });
    }
  }
};


加了请求:api/index.js

api/index.js

import axios from "axios";

// 所有请求统一配置
const instance = axios.create({ timeout: 5000 });

// 请求拦截器,统一加些appid、sign之类的
instance.interceptors.request.use(
  config => {
    config.method === "get" && (config.params.appid = "颜酱");
    return config;
  },
  error => Promise.error(error)
);

// 响应拦截器,错误的统一处理
instance.interceptors.response.use(
  // 请求成功
  res => {
    //   200的状态就是请求成功了
    const isSuccess = res.status === 200;
    if (!isSuccess) {
      Promise.reject(res);
      return;
    }
    return Promise.resolve(res.data);
  },
  // 请求失败
  error => {
    console.log(error);
    if (!window.navigator.onLine) {
      this.$message.error("网不好");
      return;
    }
    const { response } = error;
    if (response) {
      this.$message.error(response.message);
    }
  }
);

export const ApiGetList = ({ pageIndex, pageSize, sortBy, isAsc, year, quarter, name }) =>
  instance.get("/api/list", {
    params: { pageIndex, pageSize, sortBy, isAsc, year, quarter, name }
  });

全局api:main.js

main.js

import Vue from "vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
import App from "./App.vue";
import * as api from "@/api";
// 让全局方便访问api
Vue.prototype.$api = api;

Vue.use(ElementUI);

new Vue({
  el: "#app",
  render: h => h(App)
});

代码:2. 增加排序和页码变化

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(:model="model" :schema="schema"  :inline="true" label-width="100px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
  //- 表格区域
  enhanced-el-table(@sort-change="sortChange" :data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeCurrentPage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      // 表单数据
      model: {},
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      isAsc: "",
      sortBy: "",
      // 数据总长度,基本只给分页组件用的
      dataCount: 0
    };
  },
  mounted() {
    this.getTableData();
  },
  watch: {
    isAsc() {
      this.getTableData();
    },
    sortBy() {
      this.getTableData();
    },
    pageIndex() {
      this.getTableData();
    }
  },
  methods: {
    changeCurrentPage(curPageIndex) {
      this.pageIndex = curPageIndex;
    },
    async getTableData() {
      const { pageIndex, pageSize, sortBy, isAsc } = this;
      let params = { ...this.model, pageIndex, pageSize, sortBy, isAsc };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },
    sortChange({ column, prop, order }) {
      console.log(column, prop, order);
      this.isAsc = order === "ascending";
      this.sortBy = prop;
    },

    clickName() {},
    clickSearchBtn() {}
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>

代码:3. 增加查询条件变化

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(:model="model" :schema="schema"  :inline="true" label-width="70px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
  //- 表格区域
  enhanced-el-table(@sort-change="sortChange" :data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeCurrentPage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      // 表单数据
      model: {},
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      isAsc: "",
      sortBy: "",
      // 数据总长度,基本只给分页组件用的
      dataCount: 0
    };
  },
  mounted() {
    this.sortConditionDefault = { isAsc: this.isAsc, sortBy: this.sortBy };
    this.getTableData();
  },
  watch: {
    isAsc() {
      this.getTableData();
    },
    sortBy() {
      this.getTableData();
    },
    pageIndex() {
      this.getTableData();
    }
  },
  methods: {
    clickSearchBtn() {
      this.pageIndex = 1;
      this.sortBy = this.sortConditionDefault.sortBy;
      this.isAsc = this.sortConditionDefault.isAsc;
      this.getTableData();
    },
    changeCurrentPage(curPageIndex) {
      this.pageIndex = curPageIndex;
    },
    async getTableData() {
      const { pageIndex, pageSize, sortBy, isAsc } = this;
      let params = { ...this.model, pageIndex, pageSize, sortBy, isAsc };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },
    sortChange({ column, prop, order }) {
      console.log(column, prop, order);
      this.isAsc = order === "ascending";
      this.sortBy = prop;
    },

    clickName() {}
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>

代码:4. 增加重置

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(ref="queryForm" :model="model" :schema="schema"  :inline="true" label-width="70px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
        el-button.btn(plain, @click='clickResetBtn') 重置
  //- 表格区域
  enhanced-el-table(ref="mainTable" @sort-change="sortChange" :data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeCurrentPage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      // 表单数据
      model: { quarter: "" },
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      sortConfig: { isAsc: "", sortBy: "" },
      // 数据总长度,基本只给分页组件用的
      dataCount: 0
    };
  },
  mounted() {
    // 存下默认,这里需要复制,引用类型你懂的
    this.sortConfigDefault = { ...this.sortConfig };
    this.getTableData();
  },
  watch: {
    sortConfig: {
      handler(newValue) {
        console.log(newValue);
        const order =
          newValue.isAsc === ""
            ? null
            : newValue.isAsc === false
            ? "descending"
            : "ascending";
        const hasOrder = order !== null;
        const mainTable = this.$refs.mainTable;
        // 有排序的时候,需要设置,没有排序的时候直接清除掉
        hasOrder
          ? mainTable.sort(newValue.sortBy, order)
          : mainTable.clearSort();
        this.getTableData();
      },
      deep: true
    },

    pageIndex() {
      this.getTableData();
    }
  },
  methods: {
    clickResetBtn() {
      // 重置查询表单,将所有字段值重置为初始值并移除校验结果
      this.$refs.queryForm.resetFields();
      // 重置页数
      this.pageIndex = 1;
      // 重置排序
      this.sortConfig = { ...this.sortConfigDefault };
    },
    clickSearchBtn() {
      this.pageIndex = 1;
      this.getTableData();
    },
    changeCurrentPage(curPageIndex) {
      this.pageIndex = curPageIndex;
    },
    async getTableData() {
      const { pageIndex, pageSize } = this;
      let params = { ...this.model, ...this.sortConfig, pageIndex, pageSize };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },
    sortChange({ column, prop, order }) {
      console.log(1, column, prop, order);
      this.sortConfig.isAsc = order === null ? "" : order === "ascending";
      this.sortConfig.sortBy = prop;
    },

    clickName() {}
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>

5.代码:新建

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(ref="queryForm" :model="model" :schema="schema"  :inline="true" label-width="70px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
        el-button.btn(plain, @click='clickResetBtn') 重置
        el-button.btn(plain, @click='clickCreateBtn') 新建
  //- 表格区域
  enhanced-el-table(ref="mainTable" @sort-change="sortChange" :data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeCurrentPage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
  el-dialog(:title="dialogFormTitle" :visible.sync="isShowDialogForm" center width="340px")
    enhanced-el-form(ref="dialogForm" :model="dialogFormModel" :schema="dialogFormSchema"  label-width="70px" label-position= "right")
      template(#footer)
        el-form-item
          el-button.btn(type='primary', @click='clickCancelOfDialogForm') 取消
          el-button.btn(plain, @click='clickConfirmOfDialogForm') 确定
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      // 表单数据
      model: {},
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      sortConfig: { isAsc: "", sortBy: "" },
      // 数据总长度,基本只给分页组件用的
      dataCount: 0,
      // 弹框 新建的各种参数
      isShowDialogForm: false,
      dialogFormTitle: "新建学生",
      dialogFormModel: {},
      dialogFormSchema: schema.map(item => {
        let res = { ...item };
        res.rules = [{ required: true, trigger: "blur", message: "不能为空" }];
        return res;
      })
    };
  },
  mounted() {
    // 存下默认,这里需要复制,引用类型你懂的
    this.sortConfigDefault = { ...this.sortConfig };
    this.getTableData();
  },
  watch: {
    sortConfig: {
      handler(newValue) {
        const order =
          newValue.isAsc === ""
            ? null
            : newValue.isAsc === false
            ? "descending"
            : "ascending";
        const hasOrder = order !== null;
        const mainTable = this.$refs.mainTable;
        // 有排序的时候,需要设置,没有排序的时候直接清除掉
        hasOrder
          ? mainTable.sort(newValue.sortBy, order)
          : mainTable.clearSort();
        this.getTableData();
      },
      deep: true
    },

    pageIndex() {
      this.getTableData();
    }
  },
  methods: {
    clickCreateBtn() {
      this.$refs.dialogForm && this.$refs.dialogForm.resetFields();
      this.isShowDialogForm = true;
    },
    clickCancelOfDialogForm() {
      this.isShowDialogForm = false;
    },
    async clickConfirmOfDialogForm() {
      const isValid = await this.$refs.dialogForm.validate();
      if (!isValid) {
        return;
      }
      // 鉴于时间不多,不在多写接口,表达意思就行
      // await this.$api.createData(this.dialogFormModel)
      // this.$message.success('新建成功~')
      this.isShowDialogForm = false;
      // 重置查询条件和排序
      this.resetQueryAndSort();
    },
    resetQueryAndSort() {
      // 重置查询表单,将所有字段值重置为初始值并移除校验结果
      this.$refs.queryForm.resetFields();
      // 重置页数
      this.pageIndex = 1;
      // 重置排序
      this.sortConfig = { ...this.sortConfigDefault };
    },
    clickResetBtn() {
      this.resetQueryAndSort();
    },
    clickSearchBtn() {
      this.pageIndex = 1;
      this.getTableData();
    },
    changeCurrentPage(curPageIndex) {
      this.pageIndex = curPageIndex;
    },
    async getTableData() {
      const { pageIndex, pageSize } = this;
      let params = { ...this.model, ...this.sortConfig, pageIndex, pageSize };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },
    sortChange({ column, prop, order }) {
      console.log(1, column, prop, order);
      this.sortConfig.isAsc = order === null ? "" : order === "ascending";
      this.sortConfig.sortBy = prop;
    },

    clickName() {}
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>

6.代码:编辑

App.vue

<template lang="pug">
div#app
  //- 表单区域
  enhanced-el-form(ref="queryForm" :model="model" :schema="schema"  :inline="true" label-width="70px" label-position= "right")
    template(#footer)
      el-form-item.app-btns-box
        el-button.btn(type='primary', @click='clickSearchBtn') 查询
        el-button.btn(plain, @click='clickResetBtn') 重置
        el-button.btn(plain, @click='clickCreateBtn') 新建
  //- 表格区域
  enhanced-el-table(ref="mainTable" @sort-change="sortChange" :data='tableData', :col-configs='colConfigs')
    template(#name="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          a.link(href="javascript:;" @click="clickName(row)") {{row.name}}
    template(#action="colConfig")
      el-table-column(v-bind="colConfig")
        template(#default="{row}")
          div(style="color:#409eff;cursor:pointer" @click="clickEdit(row)") 编辑
  //- 分页
  .pagination-box
    el-pagination(@current-change='changeCurrentPage', :current-page.sync='pageIndex', :page-size='pageSize', layout='prev, pager, next, jumper', :total='dataCount')
  el-dialog(:title="dialogFormTitle" :visible.sync="isShowDialogForm" center width="340px")
    enhanced-el-form(ref="dialogForm" :model="dialogFormModel" :schema="dialogFormSchema"  label-width="70px" label-position= "right")
      template(#footer)
        el-form-item
          el-button.btn( plain,@click='clickCancelOfDialogForm') 取消
          el-button.btn(type='primary', @click='clickConfirmOfDialogForm') 确定
</template>
<script>
import EnhancedElTable from "@/components/EnhancedElTable";
import EnhancedElForm from "@/components/EnhancedElForm";
import schema from "./schema";
import colConfigs from "./colConfigs";
export default {
  name: "app",
  components: { EnhancedElTable, EnhancedElForm },

  data() {
    return {
      // 表单数据
      model: {},
      // 表单配置
      schema,
      // 表格配置
      colConfigs,
      // 表格请求的原始数据
      tableData: [],
      // 有数据就意味着可能分页
      pageIndex: 0,
      pageSize: 10,
      sortConfig: { isAsc: "", sortBy: "" },
      // 数据总长度,基本只给分页组件用的
      dataCount: 0,
      // 弹框 新建的各种参数
      isShowDialogForm: false,
      dialogFormTitle: "新建学生",
      dialogFormModel: {},
      dialogFormSchema: schema.map(item => {
        let res = { ...item };
        res.rules = [{ required: true, trigger: "blur", message: "不能为空" }];
        return res;
      })
    };
  },
  mounted() {
    // 存下默认,这里需要复制,引用类型你懂的
    this.sortConfigDefault = { ...this.sortConfig };
    this.getTableData();
  },
  watch: {
    sortConfig: {
      handler(newValue) {
        const order =
          newValue.isAsc === ""
            ? null
            : newValue.isAsc === false
            ? "descending"
            : "ascending";
        const hasOrder = order !== null;
        const mainTable = this.$refs.mainTable;
        // 有排序的时候,需要设置,没有排序的时候直接清除掉
        hasOrder
          ? mainTable.sort(newValue.sortBy, order)
          : mainTable.clearSort();
        this.getTableData();
      },
      deep: true
    },

    pageIndex() {
      this.getTableData();
    }
  },
  methods: {
    clickEdit(row) {
      // 这里后期确定是编辑还是添加,以此做一些不同的操作
      this.isEdit = true;
      // 注意这里必须是这行在下行的前面,为了后期的resetFields因为页面首次点击编辑的话,表单初始值是空的。如果下面那行在上面的话,页面先点击编辑后点击新建的话,resetFields就会失效。nextTick也是保证这个功能
      this.isShowDialogForm = true;
      this.$nextTick(() => {
        this.dialogFormModel = { ...row };
      });
      // 保留当前行的信息,修改成功之后,将更新的信息赋值
      this.curRow = row;
      // 可能需要修改标题
      this.dialogFormTitle = "修改";
    },
    async clickConfirmOfDialogForm() {
      const isValid = await this.$refs.dialogForm.validate();
      if (!isValid) {
        return;
      }
      // 鉴于时间不多,不在多写接口,表达意思就行
      if (this.isEdit) {
        // await this.$api.editData(this.dialogFormModel)
        this.$message.success("修改成功~");
        // 这里需要用遍历的办法更新当前row的信息,必须等接口成功
        Object.keys(this.dialogFormModel).forEach(key => {
          this.curRow[key] = this.dialogFormModel[key];
        });
      } else {
        // await this.$api.createData(this.dialogFormModel)
        this.$message.success("新建成功~");
        // 重置查询条件和排序
        this.resetQueryAndSort();
      }
      this.isShowDialogForm = false;
    },
    clickCreateBtn() {
      // 这里后期确定是编辑还是添加,以此做一些不同的操作
      this.isEdit = false;
      this.dialogFormTitle = "新建";
      this.$refs.dialogForm && this.$refs.dialogForm.resetFields();
      this.isShowDialogForm = true;
    },
    clickCancelOfDialogForm() {
      this.isShowDialogForm = false;
    },
    resetQueryAndSort() {
      // 重置查询表单,将所有字段值重置为初始值并移除校验结果
      this.$refs.queryForm.resetFields();
      // 重置页数
      this.pageIndex = 1;
      // 重置排序
      this.sortConfig = { ...this.sortConfigDefault };
    },
    clickResetBtn() {
      this.resetQueryAndSort();
    },
    clickSearchBtn() {
      this.pageIndex = 1;
      this.getTableData();
    },
    changeCurrentPage(curPageIndex) {
      this.pageIndex = curPageIndex;
    },
    async getTableData() {
      const { pageIndex, pageSize } = this;
      let params = { ...this.model, ...this.sortConfig, pageIndex, pageSize };
      const res = await this.$api.ApiGetList(params);
      this.tableData = res.data;
      this.dataCount = res.dataCount;
    },
    sortChange({ column, prop, order }) {
      console.log(1, column, prop, order);
      this.sortConfig.isAsc = order === null ? "" : order === "ascending";
      this.sortConfig.sortBy = prop;
    },

    clickName() {}
  }
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

.el-table {
  border: 1px solid #e8e8e8;
  width: 90%;
  margin: auto;
}
.pagination-box {
  margin-top: 20px;
  text-align: center;
}
</style>