@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { Button, message } from 'antd';
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType, ColumnType } from 'antd/es/table';
|
||||
@@ -9,9 +9,11 @@ import {
|
||||
ACTIONS_COLUMN_KEY,
|
||||
SERIAL_COLUMN_KEY,
|
||||
applyColumnPrefs,
|
||||
columnKey,
|
||||
settingItems,
|
||||
type ListColumnSettingItem,
|
||||
} from './list-column-prefs';
|
||||
import { beginColumnResize, withResizeTitle } from './column-resize';
|
||||
|
||||
type Options = {
|
||||
page?: number;
|
||||
@@ -27,7 +29,13 @@ export function useAdminListColumns<T>(
|
||||
const { prefs, save } = useListColumnPrefs();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [localWidths, setLocalWidths] = useState<Record<string, number>>({});
|
||||
const pref = prefs[listKey];
|
||||
const prefRef = useRef(pref);
|
||||
prefRef.current = pref;
|
||||
const localWidthsRef = useRef(localWidths);
|
||||
localWidthsRef.current = localWidths;
|
||||
const itemsRef = useRef<ListColumnSettingItem[]>([]);
|
||||
|
||||
const configured = useMemo(
|
||||
() => applyColumnPrefs(allColumns as ColumnType<T>[], pref),
|
||||
@@ -38,6 +46,7 @@ export function useAdminListColumns<T>(
|
||||
() => settingItems(allColumns as ColumnType<T>[], pref),
|
||||
[allColumns, pref],
|
||||
);
|
||||
itemsRef.current = items;
|
||||
|
||||
const serialCol: ColumnType<T> = useMemo(
|
||||
() => ({
|
||||
@@ -50,37 +59,98 @@ export function useAdminListColumns<T>(
|
||||
[page, pageSize],
|
||||
);
|
||||
|
||||
const columns = useMemo<ColumnsType<T>>(
|
||||
() => [
|
||||
const persistPref = useCallback(
|
||||
async (next: { order: string[]; hidden: string[]; widths?: Record<string, number> } | null) => {
|
||||
if (!next) {
|
||||
setLocalWidths({});
|
||||
await save(listKey, null);
|
||||
return;
|
||||
}
|
||||
await save(listKey, next);
|
||||
},
|
||||
[listKey, save],
|
||||
);
|
||||
|
||||
const persist = useCallback(
|
||||
async (next: ListColumnSettingItem[] | null) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (!next) {
|
||||
await persistPref(null);
|
||||
} else {
|
||||
const widths = { ...(prefRef.current?.widths ?? {}), ...localWidthsRef.current };
|
||||
await persistPref({
|
||||
order: next.map((i) => i.key),
|
||||
hidden: next.filter((i) => !i.visible).map((i) => i.key),
|
||||
...(Object.keys(widths).length ? { widths } : {}),
|
||||
});
|
||||
}
|
||||
setOpen(false);
|
||||
message.success(next ? '列设置已保存' : '已恢复默认列');
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
},
|
||||
[persistPref],
|
||||
);
|
||||
|
||||
const persistWidth = useCallback(
|
||||
async (key: string, width: number) => {
|
||||
const current = prefRef.current;
|
||||
const prevW = current?.widths?.[key];
|
||||
if (prevW === width) return;
|
||||
const order = current?.order?.length ? current.order : itemsRef.current.map((i) => i.key);
|
||||
const hidden = current?.hidden ?? [];
|
||||
const widths = { ...(current?.widths ?? {}), ...localWidthsRef.current, [key]: width };
|
||||
try {
|
||||
await persistPref({ order, hidden, widths });
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '列宽保存失败');
|
||||
}
|
||||
},
|
||||
[persistPref],
|
||||
);
|
||||
|
||||
const columns = useMemo<ColumnsType<T>>(() => {
|
||||
const merged: ColumnType<T>[] = [
|
||||
serialCol,
|
||||
...configured.map((col) =>
|
||||
col.title === '操作' || col.key === ACTIONS_COLUMN_KEY || col.key === 'actions'
|
||||
? { ...col, key: col.key ?? ACTIONS_COLUMN_KEY }
|
||||
: col,
|
||||
),
|
||||
],
|
||||
[configured, serialCol],
|
||||
);
|
||||
|
||||
async function persist(next: ListColumnSettingItem[] | null) {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (!next) {
|
||||
await save(listKey, null);
|
||||
} else {
|
||||
await save(listKey, {
|
||||
order: next.map((i) => i.key),
|
||||
hidden: next.filter((i) => !i.visible).map((i) => i.key),
|
||||
});
|
||||
}
|
||||
setOpen(false);
|
||||
message.success(next ? '列设置已保存' : '已恢复默认列');
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
];
|
||||
const widths = { ...(pref?.widths ?? {}), ...localWidths };
|
||||
return merged.map((col, i) => {
|
||||
const key = columnKey(col, i);
|
||||
const width = widths[key] ?? (typeof col.width === 'number' ? col.width : undefined);
|
||||
const prevHeader = col.onHeaderCell;
|
||||
return {
|
||||
...col,
|
||||
key,
|
||||
width,
|
||||
title: withResizeTitle(col.title, (e) => {
|
||||
const th = (e.currentTarget as HTMLElement).closest('th');
|
||||
const startWidth = width ?? th?.getBoundingClientRect().width ?? 120;
|
||||
beginColumnResize(
|
||||
e,
|
||||
startWidth,
|
||||
(next) => setLocalWidths((prev) => ({ ...prev, [key]: next })),
|
||||
(next) => void persistWidth(key, next),
|
||||
);
|
||||
}),
|
||||
onHeaderCell: (column) => {
|
||||
const extra = typeof prevHeader === 'function' ? prevHeader(column) : {};
|
||||
return {
|
||||
...extra,
|
||||
className: [extra.className, 'admin-th-resizable'].filter(Boolean).join(' '),
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [configured, serialCol, pref?.widths, localWidths, persistWidth]);
|
||||
|
||||
const settingsButton = (
|
||||
<Button icon={<SettingOutlined />} onClick={() => setOpen(true)}>
|
||||
|
||||
Reference in New Issue
Block a user