fix(admin): 编辑消息/切换会话保护、行内编辑撤销与上传进度反馈
- 编辑旧消息会删除其后全部消息时,先弹确认告知影响范围 - 有待发送附件时切换会话,先确认再丢弃(避免静默删除已上传文件) - EditableCell 保存成功后提供 6 秒内"撤销"入口(把旧值重新保存), 重新编辑时自动清除 - 导入向导/重新上传显示上传进度条与百分比,不再"假死" - AI 附件上传透传进度给附件列表 - 租赁合同上传显示按钮 loading 与上传百分比
This commit is contained in:
@@ -102,8 +102,18 @@ const EditableCell = <Value,>({
|
||||
const [draft, setDraft] = useState<unknown>(() =>
|
||||
normalizeEditableValue(formatValue ? formatValue(value) : value, editor),
|
||||
);
|
||||
// 保存成功后短暂显示「撤销」入口:记录保存前的序列化旧值
|
||||
const [undoMeta, setUndoMeta] = useState<{ serializedPrevious: unknown } | null>(null);
|
||||
const undoTimerRef = useRef<number | undefined>(undefined);
|
||||
const enabled = !disabled && (!permission || hasPermission(permission));
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
window.clearTimeout(undoTimerRef.current);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const original = useMemo(
|
||||
() =>
|
||||
serializeEditableValue(
|
||||
@@ -133,10 +143,15 @@ const EditableCell = <Value,>({
|
||||
return true;
|
||||
}
|
||||
setSaving(true);
|
||||
const previousValue = original;
|
||||
try {
|
||||
await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
|
||||
if (activeCell?.id === idRef.current) activeCell = null;
|
||||
setEditing(false);
|
||||
// 提供 6 秒内的撤销入口(把旧值再保存一次)
|
||||
setUndoMeta({ serializedPrevious: previousValue });
|
||||
window.clearTimeout(undoTimerRef.current);
|
||||
undoTimerRef.current = window.setTimeout(() => setUndoMeta(null), 6_000);
|
||||
return true;
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error, '保存失败'));
|
||||
@@ -197,10 +212,29 @@ const EditableCell = <Value,>({
|
||||
if (!saved) return;
|
||||
}
|
||||
activeCell = { id: idRef.current, save };
|
||||
// 重新进入编辑时清掉上一次的撤销入口
|
||||
window.clearTimeout(undoTimerRef.current);
|
||||
setUndoMeta(null);
|
||||
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
|
||||
setEditing(true);
|
||||
};
|
||||
|
||||
const handleUndo = async () => {
|
||||
if (!undoMeta) return;
|
||||
window.clearTimeout(undoTimerRef.current);
|
||||
setUndoMeta(null);
|
||||
try {
|
||||
await onSave(
|
||||
parseValue
|
||||
? parseValue(undoMeta.serializedPrevious)
|
||||
: (undoMeta.serializedPrevious as Value),
|
||||
);
|
||||
message.success('已撤销修改');
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error, '撤销失败'));
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.pointerType !== 'touch' || editing) return;
|
||||
touchStartRef.current = {
|
||||
@@ -308,7 +342,23 @@ const EditableCell = <Value,>({
|
||||
{editing ? (
|
||||
<Spin spinning={saving}>{control}</Spin>
|
||||
) : (
|
||||
<Tooltip title={enabled ? '双击编辑,触屏双击编辑' : undefined}>{children}</Tooltip>
|
||||
<Tooltip title={enabled ? '双击编辑,触屏双击编辑' : undefined}>
|
||||
<span className="editable-cell-display">
|
||||
{children}
|
||||
{undoMeta ? (
|
||||
<button
|
||||
type="button"
|
||||
className="editable-cell-undo"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
void handleUndo();
|
||||
}}
|
||||
>
|
||||
撤销
|
||||
</button>
|
||||
) : null}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user