fix(gerp): 修复"空草稿"误判,只看用户可填列

草稿判定原来比对整个表单对象,隐藏列/父级注入列/自动时间戳(CreateTime 每次打开都不同)与多选列空数组规范化都会产生差异,导致明明是空草稿仍弹恢复提示。改为写入与恢复共用 hasUserInput:仅比对表单可填列(editableFields)且值非空、与基线不同;内容清空时同步清掉旧草稿。
This commit is contained in:
TermiNexus committed 2026-10-01 16:16:36 +08:00
1 parent 0e45175bc8
commit 78866514e8
2 files changed
+44 -28

No files matched your search

+5 -1
View File
@@ -209,11 +209,15 @@ const draft = useDraftAutosave({
editingRow: editingRow as Record<string, unknown>,
dialogVisible: () => dialogVisible.value,
isEdit: () => isEdit.value,
// 只有"表单里用户可填的列"变化才算草稿内容:
// 隐藏列/父级注入列/自动时间戳(CreateTime)的变化一律忽略,避免生成"看起来是空的"草稿
editableFields: () => formColumns.value.map((c) => c.field),
})
// 新增:先走 useCrudTable 初始化,再注入父级上下文关联字段;
// 自动时间戳字段(CreateTime 等)预填当前 UTC 时间——不要求用户输入(后端仍以服务器时间为准);
// 若存在未提交草稿,提示恢复或放弃
// 若存在未提交草稿(且草稿里有用户真正录入的内容),提示恢复或删除;
// 空草稿(可填列没有有效录入)直接丢弃,不弹窗
async function onHandleCreate(): Promise<void> {
// 新增前拦截:父页面可通过 beforeCreate 前置校验(返回 false 阻止打开弹窗)
if (props.beforeCreate && props.beforeCreate() === false) return
+39 -27
View File
@@ -27,9 +27,15 @@ export function useDraftAutosave(options: {
dialogVisible: () => boolean
/** 是否编辑态(编辑态不存草稿) */
isEdit: () => boolean
/**
* 用户真正可填的列(表单可见列 field 列表):**只有这些列的有效值才算"录入内容"**。
* 隐藏列/上下文注入列/自动时间戳的变化(如 CreateTime 每次打开都不同、
* 多选列由 '' 规范化为 [])一律忽略 —— 否则会生成"用户视角下明明是空的"草稿。
*/
editableFields: () => string[]
}) {
const draftKey = `gerp-draft:add:${options.table}`
/** 打开新增弹窗时的表单基线(JSON),内容未改动时不写草稿,避免"空草稿"打扰 */
/** 打开新增弹窗时的表单基线(JSON),用于判断某个可填列是否真的被改过 */
let draftBaseline: string | null = null
let draftTimer: ReturnType<typeof setTimeout> | null = null
@@ -46,11 +52,38 @@ export function useDraftAutosave(options: {
localStorage.removeItem(draftKey)
}
/**
* 判断表单数据里是否存在"用户真正录入"的内容(写入与恢复共用同一判定,保证对称)
*
* 判定规则:只看可填列(editableFields),且值非空、与本次打开表单的基线不同。
* 基线为 null(草稿刚恢复、允许增量保存)时以"非空即可"为准。
*/
function hasUserInput(data: Record<string, unknown>): boolean {
const fields = options.editableFields()
let base: Record<string, unknown> = {}
if (draftBaseline) {
try {
base = JSON.parse(draftBaseline) as Record<string, unknown>
} catch {
base = {}
}
}
return fields.some((field) => {
const value = data[field]
if (value === null || value === undefined || value === '') return false
if (Array.isArray(value) && value.length === 0) return false
return JSON.stringify(value) !== JSON.stringify(base[field])
})
}
function writeDraft(): void {
if (!options.dialogVisible() || options.isEdit()) return
if (!hasUserInput(options.editingRow)) {
// 用户已把内容清空 → 一并清掉旧草稿,不在 localStorage 里留"空草稿"
clearDraft()
return
}
try {
const cur = JSON.stringify({ ...options.editingRow })
if (draftBaseline !== null && cur === draftBaseline) return // 未录入任何内容,不存
localStorage.setItem(
draftKey,
JSON.stringify({ data: { ...options.editingRow }, updatedAt: Date.now() } satisfies FormDraft),
@@ -80,36 +113,15 @@ export function useDraftAutosave(options: {
draftBaseline = JSON.stringify({ ...options.editingRow })
}
/**
* 草稿是否"为空":没有任何"用户真正录入"的内容 —— 全部是空值,
* 或与本次打开表单的基线完全一致(只有 CreateTime 等系统预填字段,用户没动过)
*/
function isDraftEmpty(draft: FormDraft): boolean {
const data = draft.data || {}
let base: Record<string, unknown> = {}
if (draftBaseline) {
try {
base = JSON.parse(draftBaseline) as Record<string, unknown>
} catch {
base = {}
}
}
return !Object.entries(data).some(([key, value]) => {
if (value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)) {
return false
}
return JSON.stringify(value) !== JSON.stringify(base[key])
})
}
/**
* 检测并提示恢复/删除草稿;恢复则写入表单(在新增弹窗初始化后调用)。
* 空草稿(无有效内容/与基线一致)直接丢弃,不弹窗打扰用户。
* 空草稿(可填列没有任何有效录入)直接丢弃,不弹窗打扰用户 ——
* 历史遗留的"只有隐藏列/时间戳变化"的草稿也在这里被清掉。
* 弹窗:确定=恢复草稿,取消=删除草稿,右上角关闭=稍后再看(保留草稿)
*/
async function restoreDraftIfAny(): Promise<void> {
const draft = readDraft()
if (!draft || isDraftEmpty(draft)) {
if (!draft || !hasUserInput(draft.data || {})) {
clearDraft() // 空草稿:直接丢弃
return
}