refactor(admin): EditableCell 跨单元格协调状态迁入 zustand,替代模块级单例并补单测

This commit is contained in:
2026-08-08 17:04:42 +08:00
parent 260a7517d2
commit 9cc9ed09df
3 changed files with 117 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ import { DatePicker, Input, InputNumber, Select, Spin, Tooltip } from 'antd';
import dayjs, { type Dayjs } from 'dayjs';
import equal from 'fast-deep-equal';
import { usePermission } from '../../hooks/usePermission';
import { useEditableCellStore } from '../../store/editableCell/editableCellStore';
import { message } from '../../ui/app-message';
import './style.css';
import { getErrorMessage } from '../../utils/error';
@@ -39,9 +40,6 @@ export interface EditableCellProps<Value = unknown> {
onSave: (value: Value) => Promise<void>;
}
let activeCell: { id: string; save: () => Promise<boolean> } | null = null;
let replayingOutsideAction = false;
export function normalizeEditableValue(value: unknown, editor: EditableCellEditor) {
if (editor === 'date') return value ? dayjs(value as string) : null;
if (editor === 'date-range')
@@ -125,7 +123,7 @@ const EditableCell = <Value,>({
const cancel = useCallback(() => {
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
if (activeCell?.id === idRef.current) activeCell = null;
useEditableCellStore.getState().clearIfActive(idRef.current);
setEditing(false);
}, [editor, formatValue, value]);
@@ -138,7 +136,7 @@ const EditableCell = <Value,>({
return false;
}
if (editableValuesEqual(serialized, original)) {
if (activeCell?.id === idRef.current) activeCell = null;
useEditableCellStore.getState().clearIfActive(idRef.current);
setEditing(false);
return true;
}
@@ -146,7 +144,7 @@ const EditableCell = <Value,>({
const previousValue = original;
try {
await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
if (activeCell?.id === idRef.current) activeCell = null;
useEditableCellStore.getState().clearIfActive(idRef.current);
setEditing(false);
// 提供 6 秒内的撤销入口(把旧值再保存一次)
setUndoMeta({ serializedPrevious: previousValue });
@@ -167,16 +165,14 @@ const EditableCell = <Value,>({
useEffect(() => {
const cellId = idRef.current;
if (editing && activeCell?.id === cellId) activeCell.save = save;
return () => {
if (activeCell?.id === cellId) activeCell = null;
};
if (editing) useEditableCellStore.getState().updateActiveSave(cellId, save);
return () => useEditableCellStore.getState().clearIfActive(cellId);
}, [editing, save]);
useEffect(() => {
if (!editing) return;
const onPointerDown = (event: PointerEvent) => {
if (replayingOutsideAction) return;
if (useEditableCellStore.getState().replayingOutsideAction) return;
if (rootRef.current?.contains(event.target as Node) || isEditorOverlay(event.target)) return;
const actionTarget =
event.target instanceof Element
@@ -192,10 +188,10 @@ const EditableCell = <Value,>({
event.stopPropagation();
void save().then((saved) => {
if (!saved) return;
replayingOutsideAction = true;
useEditableCellStore.getState().setReplayingOutsideAction(true);
actionTarget.click();
queueMicrotask(() => {
replayingOutsideAction = false;
useEditableCellStore.getState().setReplayingOutsideAction(false);
});
});
};
@@ -207,11 +203,12 @@ const EditableCell = <Value,>({
const beginEdit = async () => {
if (!enabled || saving) return;
const { activeCell } = useEditableCellStore.getState();
if (activeCell && activeCell.id !== idRef.current) {
const saved = await activeCell.save();
if (!saved) return;
}
activeCell = { id: idRef.current, save };
useEditableCellStore.getState().setActiveCell({ id: idRef.current, save });
// 重新进入编辑时清掉上一次的撤销入口
window.clearTimeout(undoTimerRef.current);
setUndoMeta(null);