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

相关推荐
小蜜蜂嗡嗡6 分钟前
flutter封装vlcplayer的控制器
前端·javascript·flutter
一tiao咸鱼9 分钟前
如何简单使用 prompt
前端·aigc
cdbqss114 分钟前
VB.net编写的身份证类
前端·.net
骑自行车的码农32 分钟前
React短文系列 遍历fiber树 App的创建
前端·react.js
AskSky35 分钟前
为了搞一个完美的健身APP,我真是费尽心机
前端
斯~内克41 分钟前
基于Vue.js和PDF-Lib的条形码生成与批量打印方案
前端·vue.js·pdf
阴阳怪气乌托邦42 分钟前
别再啃OA代码了!低代码"搭积木"式搞数智化,我直接少写500行
前端·低代码
beelan1 小时前
v-on的思考
前端
山河木马1 小时前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++
用户9272472502191 小时前
PHP + CSS + JS + JSON 数据采集与展示系统,支持伪静态
前端