fed8ff3d3a
HQ 列表主操作居右、列设置贴最右侧;订单筛选状态可多选,导出同步过滤。 Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
5.5 KiB
TypeScript
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 };
|
|
}
|