Files
dukang/apps/admin-web/src/lib/useAdminListColumns.tsx
T
jacy fed8ff3d3a fix(admin): 列设置靠右并支持订单状态多选
HQ 列表主操作居右、列设置贴最右侧;订单筛选状态可多选,导出同步过滤。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 14:19:49 +08:00

176 lines
5.5 KiB
TypeScript

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';
import type { HqListColumnKey } from '@dukang/shared-types';
import { AdminListColumnsModal } from '../components/AdminListColumnsModal';
import { useListColumnPrefs } from './ListColumnPrefsContext';
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;
pageSize?: number;
};
export function useAdminListColumns<T>(
listKey: HqListColumnKey,
allColumns: ColumnsType<T>,
options: Options = {},
) {
const { page = 1, pageSize = 20 } = options;
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),
[allColumns, pref],
);
const items = useMemo(
() => settingItems(allColumns as ColumnType<T>[], pref),
[allColumns, pref],
);
itemsRef.current = items;
const serialCol: ColumnType<T> = useMemo(
() => ({
key: SERIAL_COLUMN_KEY,
title: '序号',
width: 64,
fixed: 'left' as const,
render: (_: unknown, __: T, index: number) => (page - 1) * pageSize + index + 1,
}),
[page, pageSize],
);
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,
),
];
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 = (
<span className="admin-list-settings-slot">
<Button type="text" icon={<SettingOutlined />} className="admin-list-settings-btn" onClick={() => setOpen(true)}>
列设置
</Button>
</span>
);
const settingsModal = (
<AdminListColumnsModal
open={open}
items={items}
saving={saving}
onCancel={() => setOpen(false)}
onReset={() => void persist(null)}
onSave={(draft) => void persist(draft)}
/>
);
return { columns, settingsButton, settingsModal };
}