前言
公司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 确实方便