ソースを参照

feat: 新增列配置远端同步功能,支持动态列更新

xieyong 2 日 前
コミット
41de22d0f6

+ 224 - 0
src/mixins/tableColumnsMixin.js

@@ -0,0 +1,224 @@
+import request from '@/utils/request';
+
+export default {
+  data() {
+    return {
+      newColumns: [],
+      tabMixinsInit: true //进入页面是否默认请求列配置
+    };
+  },
+  created() {
+    //从服务器获取缓存列表配置
+    if (this.tabMixinsInit) {
+      this.getTabColumns();
+    }
+    // 创建防抖函数并绑定this
+    this.debouncedHandleColumnChange = this.debounce(
+      this.handleColumnChangeImpl,
+      1000
+    );
+  },
+  methods: {
+    // 实际的列变更处理逻辑
+    handleColumnChangeImpl() {
+      try {
+        const list = this.getStorage(this.cacheKeyUrl + 'Cols');
+        if (list) {
+          this.saveColumns(list);
+        }
+      } catch (error) {
+        console.error('处理列配置出错:', error);
+      }
+    },
+
+    // 列表变化回调
+    handleColumnChange() {
+      this.debouncedHandleColumnChange();
+    },
+
+    // 获取table-column配置
+    async getTabColumns() {
+      const res = await this.getByTableId(this.cacheKeyUrl);
+      if (res?.columnConfig?.length > 0) {
+        //对比接口返回和本地columns
+        let { nlist, type } = this.columnsContrast(res.columnConfig);
+        //有更新则更新服务缓存配置
+        if (type) {
+          this.saveColumns(nlist);
+        }
+        this.setStorage(this.cacheKeyUrl + 'Cols', nlist);
+        // 更新列
+        if (this._computedWatchers && this._computedWatchers.columns) {
+          // console.log('columns 是计算属性');
+          this.columnsVersion++;
+        } else {
+          // console.log('columns 是 data 属性');
+          this.columns = [...this.columns];
+          this.newColumns = [...this.newColumns];
+        }
+      }
+    },
+    getColumns() {
+      if (typeof this.columns == 'function') {
+        return this.columns();
+      } else {
+        return this.columns;
+      }
+    },
+    //服务器和本地配置columns对比
+    columnsContrast(list) {
+      const key = 'label';
+      var updateType = 0;
+      let sList = list.filter((d, i, r) => {
+        return d[key];
+      });
+      let devColumns = this.newColumns?.length
+        ? this.newColumns
+        : this.getColumns();
+
+      let dList = devColumns.filter((d, i, r) => {
+        return d[key] && d[key] !== '序号';
+      });
+      const keysA = new Set(sList.map((item) => item[key]));
+      const keysB = new Set(dList.map((item) => item[key]));
+      // 本地 比 缓存服务端 多的对象(新增)
+      const added = dList.filter((item) => {
+        return !keysA.has(item[key]) && (item.prop || item.label === '操作');
+      });
+      // 本地 比 缓存服务端 少的对象(删除)
+      const removed = sList.filter((item) => !keysB.has(item[key]));
+      const removedPropSet = new Set(removed.map((item) => item[key]));
+      // 删除 缓存中 中被移除的对象
+      const keptA = list.filter((item) => !removedPropSet.has(item[key]));
+      added.forEach((item) => {
+        //新增columns字段prop参数为必填
+        if (item.prop) {
+          item.id = item.prop;
+        } else if (item.columnKey) {
+          item.id = item.columnKey;
+        }
+        item.checked = true;
+      });
+
+      if (added.length > 0 || removed.length > 0) {
+        updateType = 1;
+      }
+
+      // 更新项:key 存在但内容变化
+      const dMap = new Map(dList.map((item) => [item[key], item]));
+      const updated = keptA.map((sItem) => {
+        const dItem = dMap.get(sItem[key]);
+        if (dItem && dItem.prop && sItem.prop !== dItem.prop) {
+          updateType = 1;
+          // 记录旧值和新值
+          const oldValue = sItem.prop;
+          const newValue = dItem.prop;
+          // 遍历所有属性,动态替换匹配旧值的字段
+          const updatedItem = { ...sItem };
+          Object.keys(updatedItem).forEach((k) => {
+            if (updatedItem[k] === oldValue) {
+              updatedItem[k] = newValue;
+            }
+          });
+          return updatedItem;
+        }
+        return sItem;
+      });
+
+      // 合并保留的对象和新增的对象
+      return { nlist: [...updated, ...added], type: updateType };
+    },
+
+    // 提交columns配置
+    async saveColumns(e) {
+      const data = {
+        tableId: this.cacheKeyUrl,
+        columnConfig: e
+      };
+      const msg = await this.saveTableConfig(data);
+      // console.log('列配置保存成功:', msg);
+      return msg;
+    },
+
+    //获取localstorage缓存
+    setStorage(key, value) {
+      try {
+        localStorage.setItem(key, JSON.stringify(value));
+      } catch (e) {
+        console.log('LocalStorage 存储错误:', e);
+        if (e.name === 'QuotaExceededError') {
+          this.clearCacheByPrefix(); //缓存不足,清除
+          localStorage.setItem(key, JSON.stringify(value));
+        }
+      }
+    },
+
+    //缓存不足清除缓存
+    clearCacheByPrefix() {
+      const prefix = 'Cols'; // 标识后缀
+      Object.keys(localStorage).forEach((key) => {
+        if (key.endsWith(prefix)) {
+          localStorage.removeItem(key);
+          // console.log(`已清除缓存: ${key}`);
+        }
+      });
+      // console.log('缓存清除完成');
+    },
+
+    //设置localstorage缓存
+    getStorage(key) {
+      try {
+        const value = localStorage.getItem(key);
+        return value ? JSON.parse(value) : null;
+      } catch (e) {
+        console.error('LocalStorage 解析错误:', e);
+        return null;
+      }
+    },
+
+    //防抖函数
+    debounce(fn, delay) {
+      let timer = null;
+      return (...args) => {
+        clearTimeout(timer);
+        timer = setTimeout(() => {
+          fn.apply(this, args);
+        }, delay);
+      };
+    },
+
+    //获取column记录接口
+    async getByTableId(key) {
+      try {
+        const res = await request.get(
+          `/sys/table-config/getByTableId/${key}`,
+          {}
+        );
+        if (res.data.code == 0) {
+          return res.data.data;
+        }
+      } catch (error) {
+        console.error('获取列配置失败:', error);
+      }
+    },
+
+    // 添加column记录接口
+    async saveTableConfig(data) {
+      if (!data?.columnConfig?.length) {
+        return;
+      }
+      try {
+        const res = await request({
+          url: '/sys/table-config/save',
+          method: 'post',
+          data
+        });
+        if (res.data.code == 0) {
+          return res.data.data;
+        }
+      } catch (error) {
+        console.error('保存列配置失败:', error);
+      }
+    }
+  }
+};

+ 70 - 1
src/views/doc/components/file-table-list.vue

@@ -13,6 +13,8 @@
       :current.sync="current"
       :selection.sync="selection"
       highlight-current-row
+      :cache-key="cacheKeyUrl"
+      @columns-change="onColumnsChange"
       @row-click="rowClick"
       @selection-change="selectionClick"
       @done="done"
@@ -434,6 +436,7 @@
 
   import { isPower, isCheckOut, fileStatus, isCreateUserId } from '../util.js';
   import { getFile } from '@/api/system/file/index.js';
+  import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import {
     fileDeleteAPI,
     filePageAPI,
@@ -445,6 +448,7 @@
     fileUpdateAllAPI
   } from '@/api/doc-manage';
   export default {
+    mixins: [tableColumnsMixin],
     components: {
       fileSearch,
       fileEdit,
@@ -484,12 +488,21 @@
       disabledTableList: {
         //已选择列表
         default: () => []
+      },
+      // 列配置远端同步 key:仅在调用方显式传入时启用同步,其他路由不传则完全关闭
+      cacheKeyUrl: {
+        type: String,
+        default: ''
       }
     },
     //add:新建 fileEdit:修改  filePigeonhole:归档 fileUnPigeonhole:取消归档  fileIssue:发布 fileChange:变更 fileDel:删除
 
     data() {
       return {
+        // 关闭 mixin 默认的 getTabColumns(与 ele-pro-table 工具栏共享同一 localStorage key,
+        // 混用会破坏缓存结构,由本组件自行接管"远端拉取 + 应用"流程)。
+        // 仅在 cacheKeyUrl 非空时才会真正发起同步(见 loadColumnsFromServer / onColumnsChange 的守卫)
+        tabMixinsInit: false,
         isPower,
         isCheckOut,
         isCreateUserId,
@@ -506,6 +519,7 @@
             type: 'selection',
             columnKey: 'selection',
             align: 'center',
+            fixed: 'left',
             selectable: (row, index) => {
               return (
                 !this.disabledTableList
@@ -658,9 +672,64 @@
         showEditFlag: false
       };
     },
-    created() {},
+    created() {
+      this.loadColumnsFromServer();
+    },
 
     methods: {
+      // ele-pro-table 列变化 → 转换为 slim 格式后同步到后端
+      // slim 格式与 ele-pro-table 工具栏写入 localStorage 的结构一致,避免缓存互相覆盖
+      // 注意:type 为 selection/index/expand 的列被 ele-pro-table 排除在 ⚙️ 工具栏外(见 HIDE_SETTING_TYPES),
+      // 不应写入后端和 localStorage,否则下次加载会被 getSettingCols 当作缓存返回,污染工具栏列表
+      onColumnsChange(visibleColumns) {
+        if (!this.cacheKeyUrl) return;
+        // 用户在 ⚙️ 工具栏里改最新 fixed 值(按 columnKey / prop 索引)
+        // 源 columns 里的 fixed 是页面初始值,必须以这里为准,否则刷新后丢失
+        const fixedMap = new Map();
+        visibleColumns.forEach((c) => {
+          const key = c.prop || c.columnKey;
+          if (key) fixedMap.set(key, c.fixed);
+        });
+        const visibleIds = new Set(
+          visibleColumns.map((c) => c.prop || c.columnKey)
+        );
+        const merged = this.columns
+          .filter(
+            (c) =>
+              (c.prop || c.columnKey || c.label === '操作') &&
+              !['selection', 'index', 'expand'].includes(c.type)
+          )
+          .map((c) => {
+            const key = c.prop || c.columnKey;
+            return {
+              id: key,
+              prop: c.prop,
+              columnKey: c.columnKey,
+              label: c.label,
+              checked: visibleIds.has(key),
+              fixed: fixedMap.has(key) ? fixedMap.get(key) : c.fixed
+            };
+          });
+        this.saveColumns(merged);
+      },
+      // 拉取后端列配置,写入与 ele-pro-table 共享的 localStorage,触发表格重渲染
+      // 过滤掉 HIDE_SETTING_TYPES 的列(selection/index/expand),避免污染 ⚙️ 工具栏列表
+      async loadColumnsFromServer() {
+        if (!this.cacheKeyUrl) return;
+        try {
+          const res = await this.getByTableId(this.cacheKeyUrl);
+          if (res?.columnConfig?.length > 0) {
+            const sanitized = res.columnConfig.filter(
+              (c) => !['selection', 'index', 'expand'].includes(c.type)
+            );
+            this.setStorage(this.cacheKeyUrl + 'Cols', sanitized);
+            // 触发 ele-pro-table 的 columns watcher 重读缓存
+            this.columns = [...this.columns];
+          }
+        } catch (e) {
+          console.error('加载列配置失败:', e);
+        }
+      },
       /* 表格数据源 */
       datasource({ page, limit, where, order }) {
         if (this.lcyStatus) {

+ 6 - 0
src/views/doc/components/main.vue

@@ -128,6 +128,7 @@
             :lcyStatus="lcyStatus"
             :isPop="isPop"
             :disabledTableList="disabledTableList"
+            :cacheKeyUrl="cacheKeyUrl"
           />
         </template>
       </ele-split-layout>
@@ -179,6 +180,11 @@
       disabledTableList: {
         //已选择列表
         default: () => []
+      },
+      // 列配置远端同步 key(透传给 FileTableList):留空表示不启用列配置远端同步
+      cacheKeyUrl: {
+        type: String,
+        default: ''
       }
     },
     data() {

+ 1 - 1
src/views/doc/public_doc/index.vue

@@ -1,5 +1,5 @@
 <template>
-<Main :fileType="0" lcyStatus="1"></Main>
+<Main :fileType="0" lcyStatus="1" cacheKeyUrl="fm-2611241029-doc-public-0-1"></Main>
 </template>
 
 <script>