可配置永久生效的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 确实方便

相关推荐
故事与九5 分钟前
vue3使用vue-pdf-embed实现前端PDF在线预览
前端·vue.js·pdf
Mintopia1 小时前
🚀 顶点-面碰撞检测之诗:用牛顿法追寻命运的交点
前端·javascript·计算机图形学
wb1891 小时前
企业WEB应用服务器TOMCAT
运维·前端·笔记·tomcat·云计算
烛阴1 小时前
解锁 Gulp 的潜力:高级技巧与工作流优化
前端·javascript
Entropy-Lee2 小时前
JavaScript 语句和函数
开发语言·前端·javascript
Wcowin2 小时前
MkDocs文档日期插件【推荐】
前端·mkdocs
xw53 小时前
免费的个人网站托管-Cloudflare
服务器·前端
网安Ruler3 小时前
Web开发-PHP应用&Cookie脆弱&Session固定&Token唯一&身份验证&数据库通讯
前端·数据库·网络安全·php·渗透·红队
!win !4 小时前
免费的个人网站托管-Cloudflare
服务器·前端·开发工具
饺子不放糖4 小时前
基于BroadcastChannel的前端多标签页同步方案:让用户体验更一致
前端