效果

image.png
image.png
要实现的功能如下:

  1. 可以拖动排序字段
  2. 可以设置显示/隐藏某个字段
  3. 可以恢复默认值

    思路

    首先 element ui 没有提供上述功能,要实现上面的功能,有一个关键点要解决:table-column 有自己的定制逻辑,如何渲染这些逻辑?
    1. <el-table-column
    2. label="最新跟进摘要"
    3. prop="latestFollowContent"
    4. width="300"
    5. show-overflow-tooltip>
    6. <template slot="header">
    7. 最新跟进摘要
    8. <base-tooltip placement="top">未超过一天按小时/分钟/秒计算,超过一天按自然日计算</base-tooltip>
    9. </template>
    10. <template slot-scope="scope">
    11. <el-button @click="toFollowContent(scope.row)" type="text">
    12. <span style="color: #343a40">{{ scope.row.latestFollowDate | latestFollowDateFormat }}</span>
    13. {{ scope.row.latestFollowContent }}
    14. </el-button>
    15. </template>
    16. </el-table-column>
    我使用了一个粗暴的方法,但是对于原来的写法影响不大,大概如下
    1. <template v-for="colum in tableColumns">
    2. <el-table-column
    3. :key="colum.prop"
    4. v-if="colum.prop === 'createdTime' && !colum.hidden"
    5. label="添加时间"
    6. prop="createdTime"
    7. width="135"
    8. show-overflow-tooltip>
    9. </el-table-column>
    10. <el-table-column
    11. :key="colum.prop"
    12. v-if="colum.prop === 'latestFollowContent' && !colum.hidden"
    13. label="最新跟进摘要"
    14. prop="latestFollowContent"
    15. width="300"
    16. show-overflow-tooltip>
    17. <template slot="header">
    18. 最新跟进摘要
    19. <base-tooltip placement="top">未超过一天按小时/分钟/秒计算,超过一天按自然日计算</base-tooltip>
    20. </template>
    21. <template slot-scope="scope">
    22. <el-button @click="toFollowContent(scope.row)" type="text">
    23. <span style="color: #343a40">{{ scope.row.latestFollowDate | latestFollowDateFormat }}</span>
    24. {{ scope.row.latestFollowContent }}
    25. </el-button>
    26. </template>
    27. </el-table-column>
    28. </template>
    对于 tableColumns 的数据结构就很简单了
    1. [
    2. { prop: 'customerNo', hidden: false, label: '客户编号' },
    3. { prop: 'createdTime', hidden: false, label: '添加时间' },
    4. { prop: 'latestFollowContent', hidden: false, label: '最新跟进摘要' },
    5. { prop: 'followCount', hidden: false, label: '跟进次数' },
    6. { prop: 'notFollowDayOfNewAdd', hidden: false, label: '新客未跟进' }
    7. ]

    代码实现

    核心逻辑组件实现

    TableColumnConfig.vue ```html
  1. 拖动组件使用的是[ Vue.Draggable](https://github.com/SortableJS/Vue.Draggable),这里有一个 [中文文档](https://www.itxst.com/vue-draggable/tutorial.html)
  2. <a name="YeBLN"></a>
  3. ### 使用方如何使用?
  4. 通过按钮触发配置面板,使用 el-popover 组件
  5. ```html
  6. <el-popover
  7. placement="left"
  8. width="400"
  9. trigger="click">
  10. <table-column-config v-model="tableConfigColumns"
  11. :default-table-columns="defaultTableColumns"
  12. @on-success="tableColumnsSave"></table-column-config>
  13. <el-button slot="reference" icon="el-icon-setting">列表字段配置</el-button>
  14. </el-popover>
  1. data () {
  2. /**
  3. * 默认配置
  4. */
  5. defaultTableColumns: [
  6. { prop: 'customerNo', hidden: false, label: '客户编号' },
  7. { prop: 'createdTime', hidden: false, label: '添加时间' },
  8. { prop: 'latestFollowContent', hidden: false, label: '最新跟进摘要' }
  9. ],
  10. // 表格中使用的配置
  11. tableColumns: [],
  12. // 传给组件的 配置
  13. // 为什么要和 tableColumns 分开?是因为数据实时变化的话,会导致表格渲染抖动,仅排序的情况下,表格还检测不到变更
  14. tableConfigColumns: []
  15. }
  16. created () {
  17. // 进入该页面的时候,就初始化配置
  18. this.initTableColumns()
  19. }
  20. methods:{
  21. /**
  22. * 初始化列表字段配置
  23. * 从 local 读取,如果没有,则使用默认值
  24. */
  25. initTableColumns () {
  26. // 配置从 localStorage 获取,保存的时候也保存在 localStorage 中
  27. const tableColumnsJson = window.localStorage.getItem('xx-customer/list-tableColumns')
  28. let finalTableColumnsJson
  29. if (tableColumnsJson) {
  30. finalTableColumnsJson = tableColumnsJson
  31. } else {
  32. finalTableColumnsJson = JSON.stringify(this.defaultTableColumns)
  33. }
  34. this.tableColumns = JSON.parse(finalTableColumnsJson)
  35. this.tableConfigColumns = JSON.parse(finalTableColumnsJson)
  36. },
  37. /**
  38. * 保存列表字段配置
  39. * @param tableColumns
  40. */
  41. tableColumnsSave (tableColumns) {
  42. const finalTableColumnsJson = JSON.stringify(tableColumns)
  43. window.localStorage.setItem('xx-customer/list-tableColumns', finalTableColumnsJson)
  44. // 解决赋值新的数组后,页面不变化(如果修改了显示隐藏,页面能检测到变化,仅排序检测不到)
  45. // 先赋值空数组
  46. this.tableColumns = []
  47. this.$nextTick(() => {
  48. // 等渲染后,再赋值保存的最新配置
  49. this.tableColumns = JSON.parse(finalTableColumnsJson)
  50. this.$nextTick(() => {
  51. // 等渲染后,调用表格的 重新布局函数,否则可能会导致表格错位
  52. this.$refs.table.doLayout()
  53. })
  54. })
  55. }
  56. }

那么表格里面你就可以将需要控制的字段循环出来了

  1. <el-table-column
  2. fixed
  3. label="客户名称"
  4. prop="customerName"
  5. width="150"
  6. show-overflow-tooltip>
  7. <template slot-scope="scope">
  8. <el-button @click="toDetails(scope.row)" type="text">{{ scope.row.customerName }}</el-button>
  9. </template>
  10. </el-table-column>
  11. // 这里是需要控制字段的循环,只添加了 if 和 key
  12. <template v-for="colum in tableColumns">
  13. <el-table-column
  14. :key="colum.prop"
  15. v-if="colum.prop === 'customerNo' && !colum.hidden"
  16. label="客户编号"
  17. prop="customerNo"
  18. width="120"
  19. show-overflow-tooltip>
  20. </el-table-column>
  21. <el-table-column
  22. :key="colum.prop"
  23. v-if="colum.prop === 'latestFollowContent' && !colum.hidden"
  24. label="最新跟进摘要"
  25. prop="latestFollowContent"
  26. width="300"
  27. show-overflow-tooltip>
  28. <template slot="header">
  29. 最新跟进摘要
  30. <base-tooltip placement="top">未超过一天按小时/分钟/秒计算,超过一天按自然日计算</base-tooltip>
  31. </template>
  32. <template slot-scope="scope">
  33. <el-button @click="toFollowContent(scope.row)" type="text">
  34. <span style="color: #343a40">{{ scope.row.latestFollowDate | latestFollowDateFormat }}</span>
  35. {{ scope.row.latestFollowContent }}
  36. </el-button>
  37. </template>
  38. </el-table-column>
  39. </template>

进一步包装:初始化和存储也包装成组件

上面的代码,在使用的时候还需要使用方自己去实现 初始化存储 的逻辑,那么完全可以将这两个也包装成组件,默认就使用 **_window_**.**localStorage** 进行存储

组件实现

基于 TableColumnConfig.vue 再包装一层 TableColumnConfigOfStore.vue

  1. <template>
  2. <table-column-config v-model="tableColumns"
  3. :default-table-columns="defaultTableColumns"
  4. @on-success="tableColumnsSave">
  5. </table-column-config>
  6. </template>
  7. <script>
  8. // 将配置存储在 本地的 window.localStorage 中
  9. import TableColumnConfig from './TableColumnConfig'
  10. export default {
  11. name: 'TableColumnConfigStore',
  12. components: {
  13. TableColumnConfig
  14. },
  15. props: {
  16. /**
  17. * 由于要存储在 localStorage 中,但是 localStorage 中是按域名来划分的
  18. * 所以需要外部传入一个唯一的 存储 key,以支持多个 表格使用该组件
  19. */
  20. storeKey: {
  21. type: String,
  22. required: true
  23. },
  24. /**
  25. * 默认配置
  26. */
  27. defaultTableColumns: {
  28. type: Array,
  29. required: true
  30. }
  31. },
  32. data () {
  33. return {
  34. tableColumns: undefined
  35. }
  36. },
  37. created () {
  38. this.initTableColumns()
  39. },
  40. methods: {
  41. /**
  42. * 初始化列表字段配置
  43. * 从 local 读取,如果没有,则使用默认值
  44. */
  45. initTableColumns () {
  46. const tableColumnsJson = window.localStorage.getItem(this.storeKey)
  47. let finalTableColumnsJson
  48. if (tableColumnsJson) {
  49. finalTableColumnsJson = tableColumnsJson
  50. } else {
  51. finalTableColumnsJson = JSON.stringify(this.defaultTableColumns)
  52. }
  53. this.tableColumns = JSON.parse(finalTableColumnsJson)
  54. this.tableConfigColumns = JSON.parse(finalTableColumnsJson)
  55. // 不要将应用属性传递给外部,否则会导致外部能事实的接受到数据的变更
  56. this.emitSuccess(JSON.parse(finalTableColumnsJson))
  57. },
  58. /**
  59. * 保存列表字段配置
  60. * @param tableColumns
  61. */
  62. tableColumnsSave (tableColumns) {
  63. const finalTableColumnsJson = JSON.stringify(tableColumns)
  64. window.localStorage.setItem(this.storeKey, finalTableColumnsJson)
  65. this.emitSuccess(JSON.parse(finalTableColumnsJson))
  66. },
  67. /**
  68. * 保存配置的时候将配置给出;
  69. * 原因:实时反馈的话,表格会抖动,配置完成后,再统一替换掉配置数组 体验较好
  70. */
  71. emitSuccess (tableColumns) {
  72. this.$emit('on-success', tableColumns)
  73. }
  74. }
  75. }
  76. </script>
  77. <style scoped>
  78. </style>

这里说下关键思路:这里简化了外部传入的逻辑,外部只需要传入 storeKey 和 defaultTableColumns 属性:

  1. storeKey :放在 localStorage 中的 key,一个项目中有多个表格,那么就有多个 key,防止他们数据覆盖
  2. defaultTableColumns :默认顺序的配置,由于在实践中,不推荐使用实时改变配置的方式,那么也就不需要外部通过 v-model 双向绑定数据了,首次渲染直接使用这个默认顺序,当用户修改配置之后,就会使用配置的顺序了

    使用方如何使用?

    1. <el-popover
    2. placement="left"
    3. width="400"
    4. trigger="click">
    5. <table-column-config-of-store
    6. store-key="xxx-list-tableColumns"
    7. :default-table-columns="defaultTableColumns"
    8. @on-success="tableColumnsSave"></table-column-config-of-store>
    9. <el-button slot="reference" icon="el-icon-setting" style="margin-left: 10px">列表字段配置</el-button>
    10. </el-popover>
    定义的数据和方法
    1. data () {
    2. /**
    3. * 默认配置
    4. */
    5. defaultTableColumns: [
    6. { prop: 'customerNo', hidden: false, label: '客户编号' },
    7. { prop: 'createdTime', hidden: false, label: '添加时间' },
    8. { prop: 'latestFollowContent', hidden: false, label: '最新跟进摘要' }
    9. ],
    10. // 表格中使用的配置
    11. tableColumns: []
    12. }
    13. created () {
    14. }
    15. methods:{
    16. /**
    17. * 将最新的用户配置给我们的表格使用
    18. * @param tableColumns
    19. */
    20. tableColumnsSave (tableColumns) {
    21. this.tableColumns = []
    22. this.$nextTick(() => {
    23. this.tableColumns = tableColumns
    24. this.$nextTick(() => {
    25. this.$refs.table.doLayout()
    26. })
    27. })
    28. }
    29. }
    对于表格中的循环渲染,并没有变化。 可以看到上述代码,在使用的时候大大简化了。

解决默认配置改变后,用户的配置无法加载新的字段

上面使用 localStorage 存储方式有一个缺点:当 localStorage 中有数据的时候,你项目更新了表格字段,那么就会导致新的字段不能显示在用户的配置中,要解决这个问题的话,稍微有点麻烦,笔者想到一种解决方式:

  1. 新增一个配置的版本属性
  2. 在初始化配置的时候,检查从 localStorage 中读取的版本号:如果匹配这继续使用用户的配置,如果不匹配则清空用户的配置,使用默认配置

功能实现

新增 prop 属性

  1. /**
  2. * 版本号:当用户配置的版本号与此版本号不匹配时,这清空用户的配置,使用默认配置
  3. */
  4. version: {
  5. type: String,
  6. required: true
  7. }

在初始化的时候执行版本检查

  1. if (tableColumnsJson) {
  2. finalTableColumnsJson = tableColumnsJson
  3. // 检查配置版本号
  4. const version = window.localStorage.getItem(this.storeKey + '_version')
  5. if (this.version !== version) {
  6. this.$message.warning('此表格字段配置以升级,已恢复成默认配置,如有需要请重新配置')
  7. finalTableColumnsJson = JSON.stringify(this.defaultTableColumns)
  8. window.localStorage.setItem(this.storeKey + '_version', this.version)
  9. }
  10. }

修改之后的完整组件代码

  1. <template>
  2. <table-column-config v-model="tableColumns"
  3. :default-table-columns="defaultTableColumns"
  4. @on-success="tableColumnsSave">
  5. </table-column-config>
  6. </template>
  7. <script>
  8. // 将配置存储在 本地的 window.localStorage 中
  9. import TableColumnConfig from './TableColumnConfig'
  10. export default {
  11. name: 'TableColumnConfigStore',
  12. components: {
  13. TableColumnConfig
  14. },
  15. props: {
  16. /**
  17. * 由于要存储在 localStorage 中,但是 localStorage 中是按域名来划分的
  18. * 所以需要外部传入一个唯一的 存储 key,以支持多个 表格使用该组件
  19. */
  20. storeKey: {
  21. type: String,
  22. required: true
  23. },
  24. /**
  25. * 默认配置
  26. */
  27. defaultTableColumns: {
  28. type: Array,
  29. required: true
  30. },
  31. /**
  32. * 版本号:当用户配置的版本号与此版本号不匹配时,这清空用户的配置,使用默认配置
  33. */
  34. version: {
  35. type: String,
  36. required: true
  37. }
  38. },
  39. data () {
  40. return {
  41. tableColumns: undefined
  42. }
  43. },
  44. created () {
  45. this.initTableColumns()
  46. },
  47. methods: {
  48. /**
  49. * 初始化列表字段配置
  50. * 从 local 读取,如果没有,则使用默认值
  51. */
  52. initTableColumns () {
  53. const tableColumnsJson = window.localStorage.getItem(this.storeKey)
  54. let finalTableColumnsJson
  55. if (tableColumnsJson) {
  56. finalTableColumnsJson = tableColumnsJson
  57. // 检查配置版本号
  58. const version = window.localStorage.getItem(this.storeKey + '_version')
  59. if (this.version !== version) {
  60. this.$message.warning('此表格字段配置以升级,已恢复成默认配置,如有需要请重新配置')
  61. finalTableColumnsJson = JSON.stringify(this.defaultTableColumns)
  62. window.localStorage.setItem(this.storeKey + '_version', this.version)
  63. }
  64. } else {
  65. finalTableColumnsJson = JSON.stringify(this.defaultTableColumns)
  66. }
  67. this.tableColumns = JSON.parse(finalTableColumnsJson)
  68. this.tableConfigColumns = JSON.parse(finalTableColumnsJson)
  69. // 不要将应用属性传递给外部,否则会导致外部能事实的接受到数据的变更
  70. this.emitSuccess(JSON.parse(finalTableColumnsJson))
  71. },
  72. /**
  73. * 保存列表字段配置
  74. * @param tableColumns
  75. */
  76. tableColumnsSave (tableColumns) {
  77. const finalTableColumnsJson = JSON.stringify(tableColumns)
  78. window.localStorage.setItem(this.storeKey, finalTableColumnsJson)
  79. this.emitSuccess(JSON.parse(finalTableColumnsJson))
  80. },
  81. /**
  82. * 保存配置的时候将配置给出;
  83. * 原因:实时反馈的话,表格会抖动,配置完成后,再统一替换掉配置数组 体验较好
  84. */
  85. emitSuccess (tableColumns) {
  86. this.$emit('on-success', tableColumns)
  87. }
  88. }
  89. }
  90. </script>
  91. <style scoped>
  92. </style>

使用方如何使用?

这里其他的都没有变化,唯一的使用方需要传入一个 version

  1. <el-popover
  2. placement="left"
  3. width="400"
  4. trigger="click">
  5. <table-column-config-of-store
  6. version="1"
  7. store-key="/clue/customer/list-tableColumns"
  8. :default-table-columns="defaultTableColumns"
  9. @on-success="tableColumnsSave"></table-column-config-of-store>
  10. <el-button slot="reference" icon="el-icon-setting" style="margin-left: 10px">列表字段配置</el-button>
  11. </el-popover>

每次有默认配置更新的话(比如新增/删除了字段,字段名称变更等),就可以更改该版本号,以达到用户的配置与最新的配置同步