可配置永久生效的Table组件的封装过程

前言

公司OV系统的Table有时列表项很多,并且公司除了研发的电脑屏幕比较小,一个table有时只能展示2、3行,所以封装了可配置的组件TXTable

功能点

  • 可手动配置columns列展示隐藏哪些字段
  • 可手动配置columns列哪些固定在left或者right
  • 可手动配置Table的大小
  • 根据用户ID,永久存储相关table配置 (使用IndexDB实现)

因为代码量太多了,所以就记录下思路。IndexDB相关的这里就不贴了,没什么说的。就是根据用户ID为key,table配置项为值存起来就是了。

Table整体结构和页面展示

就三个部分,一个就是Table本身,一个是Table右上角给了个setting按钮,点击setting按钮就弹出弹窗,弹窗内容包含当前Table的columns的title,以及尺寸、固定列的操作。页面展示如下

点击右上角setting按钮,弹窗如下

弹窗里面可以勾选要展示的columns列,以及columns列哪些固定在左侧或者右侧,以及table的size

Table的思路逻辑

因为有很多页面,然后每个页面的Table都要记录配置项数据,所以在使用Table的时候,需要传入一个props tableKey,用来标识是哪个页面的table

tsx 复制代码
// xxx.tsx
return <TXTable<T> 
      tableKey="CUSTOMER_POOL_MANAGEMENT_LIST_TABLE"
      {...rest}
/>

然后Table初始化时,根据 IndexDB(也就是下面代码的 await getData("TABLE") 这步) 获取 当前用户 下的 当前页面的 table 配置数据,然后存储到localSetting这个state中

ts 复制代码
async getLocalSetting(tableKey?: TTableKey) {
    this.haveInit = false;
    if (!tableKey) {
      this.haveInit = true;
      return;
    }
    const data = (await getData("TABLE")) || {};
    const oldData: ILocalData | undefined = data[tableKey];
    if (!oldData) {
      runInAction(() => {
        this.haveInit = true;
      });
      return;
    }
    runInAction(() => {
      this.localSetting = oldData;
      this.haveInit = true;
    });
  }

拿到配置项数据后,去计算columns

tsx 复制代码
  get columns() {
    const res: ITXColumnType[] = [];
    const { propsStore, logic } = this.rootStore;

    // 逻辑1
    if (!Object.keys(logic.localSetting.columns).length) {
      for (let cell of propsStore.props.columns) {
        res.push(this.calcCell(cell as ITXColumnType));
      }

      return res;
    }

    const left: ITXColumnType[] = [];
    const right: ITXColumnType[] = [];

    // 逻辑2
    for (let cell_ of propsStore.props.columns) {
      const cell = { ...cell_ } as ITXColumnType;

      if (typeof cell.key !== "string") {
        res.push(this.calcCell(cell as ITXColumnType));
        continue;
      }
      const history = logic.localSetting.columns[cell.key];
      const newCell = { ...cell, index: -1 } as ITXColumnType;
      if (history) {
        newCell.fixed = history.fixed;
        newCell.hidden = history.hidden;
        newCell.index = history.index || -1;
      }
      
      // 逻辑3
      if (newCell.fixed === "left") {
        left.push(this.calcCell(newCell));
        continue;
      }

      if (newCell.fixed === "right") {
        right.push(this.calcCell(newCell));
        continue;
      }

      res.push(this.calcCell(newCell));
    }

    left.sort((a, b) => (a.index || 0) - (b.index || 0));
    res.sort((a, b) => (a.index || 0) - (b.index || 0));
    right.sort((a, b) => (a.index || 0) - (b.index || 0));

    return [...left, ...res, ...right];
  }

计算columns的思路是

  • 逻辑1:如果没有配置过,则通过 props.columns 走默认的 columns 处理(这里是之前的组件逻辑,不影响)
  • 逻辑2:如果有配置过,根据配置项数据,改一下columns列的 fixed(是否固定,固定在左还是右)、hidden(是否展示)、index(当前列的位置)的属性
  • 逻辑3:根据最新的columns列数据,向左侧固定、右侧固定、常态里面添加列数据即可

最后返回计算后的 columns

弹窗的逻辑

首先打开弹窗时,会传入初始值,初始值只需要业调用 TXTable 时的props

ts 复制代码
openSetting() {
    const { refs, propsStore } = this.rootStore;
    if (!propsStore.props.tableKey || !propsStore.props.columns) {
      return;
    }
    refs.settingRef.current?.openModal({
      // 标时当前table的唯一key
      tableKey: propsStore.props.tableKey,
      // table的columns
      columns: propsStore.props.columns as ITXColumnType[],
      // table的size
      tableSize: propsStore.props.size,
    });
  }

初始化的逻辑

tsx 复制代码
  async toInit(isReset?: boolean) {
    // 逻辑1
    if (!this.initData?.columns || !this.initData.tableKey) {
      return;
    }
    
    // 逻辑2
    let data: Partial<TTableData> = {};
    if (!isReset) {
      data = (await getData("TABLE")) || {};
    }
    
    // 逻辑3
    const history: ILocalData | undefined = data[this.initData.tableKey];
    runInAction(() => {
      this.tableSize =
        history?.tableSize || this.initData?.tableSize || "middle";

      const renderList: ITepItem[] = [];
      const leftList: ITepItem[] = [];
      const rightList: ITepItem[] = [];

      for (let c_ of this.initData!.columns) {
        let c = { ...c_, index: -1 };

        if (typeof c.key !== "string") {
          continue;
        }

        const oldItem = history?.columns[c.key];
        if (oldItem) {
          c.hidden = oldItem.hidden;
          c.fixed = oldItem.fixed;
          c.index = oldItem.index || -1;
        }

        this.columnMap.set(c.key, c);
        if (c.hidden) {
          continue;
        }

        if (c.fixed === "left") {
          leftList.push({
            key: c.key,
            index: c.index,
          });
          continue;
        }
        if (c.fixed === "right") {
          rightList.push({
            key: c.key,
            index: c.index,
          });
          continue;
        }

        renderList.push({
          key: c.key,
          index: c.index,
        });
      }
      renderList.sort((a, b) => a.index - b.index);
      this.renderList = renderList.map((t) => t.key);
      leftList.sort((a, b) => a.index - b.index);
      this.leftList = leftList.map((t) => t.key);
      rightList.sort((a, b) => a.index - b.index);
      this.rightList = rightList.map((t) => t.key);
    });
  }
  • 逻辑1:没有columns或者没配置唯一key时,直接return
  • 逻辑2:去 IndexDB 里面拿配置项数据
  • 逻辑3:然后就是计算 table size、columns,这里计算的size、columns是回填到弹窗里

然后可选字段的地方,勾选或者取消勾选。然后处理一下如果是显示的时候,是固定在左侧、右侧、还是不固定

tsx 复制代码
  changeHidden(key: string) {
    const record = this.columnMap.get(key);

    if (!record) {
      return;
    }
    record.hidden = !record.hidden;

    this.columnMap.set(key, record);
    if (record.hidden && record.fixed === "left") {
      this.leftList = this.leftList.filter((item) => item !== key);
      return;
    }
    if (record.hidden && record.fixed === "right") {
      this.rightList = this.rightList.filter((item) => item !== key);
      return;
    }
    if (record.hidden) {
      this.renderList = this.renderList.filter((item) => item !== key);
      return;
    }
    if (record.fixed === "left") {
      this.leftList.push(key);
      return;
    }
    if (record.fixed === "right") {
      this.rightList.push(key);
      return;
    }
    this.renderList.push(key);
  }

鼠标hover上去时,可以选择固定在左侧还是右侧。如果hover到固定列上,显示是否取消固定

然后当设置固定或者不固定时,也是计算下 fixed

tsx 复制代码
  cancelFixed(key: string) {
    const record = this.columnMap.get(key);
    if (!record) {
      return;
    }

    const oldFix = record.fixed;

    record.fixed = undefined;
    this.columnMap.set(key, record);

    if (oldFix === "left") {
      this.leftList = this.leftList.filter((item) => item !== key);
      this.renderList.unshift(key);
      return;
    }
    if (oldFix === "right") {
      this.rightList = this.rightList.filter((item) => item !== key);
      this.renderList.push(key);
      return;
    }
  }

  fixedToLeft(key: string) {
    const record = this.columnMap.get(key);
    if (!record) {
      return;
    }
    record.fixed = "left";
    this.columnMap.set(key, record);
    this.leftList.push(key);
    this.renderList = this.renderList.filter((item) => item !== key);
  }

  fixedToRight(key: string) {
    const record = this.columnMap.get(key);
    if (!record) {
      return;
    }
    record.fixed = "right";
    this.columnMap.set(key, record);
    this.rightList.push(key);
    this.renderList = this.renderList.filter((item) => item !== key);
  }

弹窗确定时、重置、以及搜索时没什么说的,常规逻辑,这里就不多赘述了。主要就是确定、重置时,IndexDB 更新下对应的key就行了

结尾

如果有相关业务逻辑的伙伴可以参考下。说难也不是很难,说不难也不算不难。主要是代码量多。

然后就是因为是纯前端,后端也没必要存。所以 IndexDB 确实方便

相关推荐
jlspcsdn31 分钟前
20251222项目练习
前端·javascript·html
行走的陀螺仪1 小时前
Sass 详细指南
前端·css·rust·sass
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
React 怎么区分导入的是组件还是函数,或者是对象
前端·react.js·前端框架
LYFlied1 小时前
【每日算法】LeetCode 136. 只出现一次的数字
前端·算法·leetcode·面试·职场和发展
子春一21 小时前
Flutter 2025 国际化与本地化工程体系:从多语言支持到文化适配,打造真正全球化的应用
前端·flutter
前端无涯1 小时前
React/Vue 代理配置全攻略:Vite 与 Webpack 实战指南
vue.js·react.js
QT 小鲜肉1 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记
羽沢312 小时前
ECharts 学习
前端·学习·echarts
LYFlied2 小时前
WebAssembly (Wasm) 跨端方案深度解析
前端·职场和发展·wasm·跨端