diff --git a/admin/src/views/config/center/ContactFormPane.vue b/admin/src/views/config/center/ContactFormPane.vue index e691e7b..4f9a228 100644 --- a/admin/src/views/config/center/ContactFormPane.vue +++ b/admin/src/views/config/center/ContactFormPane.vue @@ -98,6 +98,8 @@ ): void /** footer 回滚入口:请求父层切到版本历史 */ (e: 'navigate', group: string): void + /** 未保存修改上报(Task 14:父层三处离开拦截消费) */ + (e: 'dirty', value: boolean): void }>() // ---- 编辑态(group 引用变化 = 保存回写/重置/快照重载 → 重置表单) ---------------------- @@ -107,17 +109,25 @@ const saving = ref(false) const resetting = ref(false) const actionError = ref('') + /** dirty 基线(与表单重建同 watch 刷新——保存/重置回写经父层快照组 + * 引用变化走同一条链,无需手动清) */ + const baseline = ref(JSON.stringify(contactToForm(undefined))) watch( () => props.group, (group) => { - Object.assign(form, contactToForm(group)) + const next = contactToForm(group) + Object.assign(form, next) + baseline.value = JSON.stringify(next) saveComment.value = '' actionError.value = '' }, { immediate: true } ) + const dirty = computed(() => JSON.stringify(form) !== baseline.value) + watch(dirty, (v) => emit('dirty', v), { immediate: true }) + // ---- 保存(contactToPayload → PUT 整组;类型校验后端兜底) ---------------------------- async function onSave(): Promise { diff --git a/admin/src/views/config/center/CredsPane.vue b/admin/src/views/config/center/CredsPane.vue index 74a5733..fb02187 100644 --- a/admin/src/views/config/center/CredsPane.vue +++ b/admin/src/views/config/center/CredsPane.vue @@ -407,6 +407,8 @@ ): void /** footer 回滚入口:请求父层切到对应组('versions' = 版本历史) */ (e: 'navigate', group: string): void + /** 未保存修改上报(Task 14:三卡合一,父层三处离开拦截消费) */ + (e: 'dirty', value: boolean): void }>() // ---- 编辑态(组引用变化 = 保存回写/租户切换/重载 → 重置表单与密钥态) ---------------- @@ -432,6 +434,12 @@ const emailError = ref('') const pushError = ref('') const aiError = ref('') + /** dirty 基线(三表单各自 normalize*Group().form 的 JSON 快照——与 + * resetAll 同 watch 刷新;密钥输入不进基线:掩码 GET 本就无原值可比, + * 由 dirty 计算里的三态判定补足) */ + const emailBaseline = ref(JSON.stringify(normalizeEmailCredsGroup(undefined).form)) + const pushBaseline = ref(JSON.stringify(normalizePushCredsGroup(undefined).form)) + const aiBaseline = ref(JSON.stringify(normalizeAiCredsGroup(undefined).form)) // 测试态(声明先于下方 immediate watch 的 resetAll——避免 TDZ 引用) const emailTestTo = ref('') @@ -454,14 +462,17 @@ Object.assign(emailForm, es.form) emailPasswordMasked.value = es.passwordMasked emailPasswordCleared.value = false + emailBaseline.value = JSON.stringify(es.form) const ps = normalizePushCredsGroup(pushGroup) Object.assign(pushForm, ps.form) pushApiKeyMasked.value = ps.apiKeyMasked pushApiKeyCleared.value = false + pushBaseline.value = JSON.stringify(ps.form) const as = normalizeAiCredsGroup(aiGroup) Object.assign(aiForm, as.form) aiApiKeyMasked.value = as.apiKeyMasked aiApiKeyCleared.value = false + aiBaseline.value = JSON.stringify(as.form) emailComment.value = '' pushComment.value = '' aiComment.value = '' @@ -481,6 +492,22 @@ { immediate: true } ) + /** 未保存判定:三表单任一偏离基线,或密钥三态(输入非空 = 覆盖新值 / + * cleared = 显式清除)——密钥输入框基线恒空串,只能独立判定 */ + const dirty = computed( + () => + JSON.stringify(emailForm) !== emailBaseline.value || + emailForm.smtpPassword !== '' || + emailPasswordCleared.value || + JSON.stringify(pushForm) !== pushBaseline.value || + pushForm.apiKey !== '' || + pushApiKeyCleared.value || + JSON.stringify(aiForm) !== aiBaseline.value || + aiForm.apiKey !== '' || + aiApiKeyCleared.value + ) + watch(dirty, (v) => emit('dirty', v), { immediate: true }) + // ---- 保存(前端预检 → 整组 PUT;密钥不动回哨兵保持库值) ------------------------------ async function onSaveEmail(): Promise { diff --git a/admin/src/views/config/center/EngineFormPane.vue b/admin/src/views/config/center/EngineFormPane.vue index 2528239..274a10a 100644 --- a/admin/src/views/config/center/EngineFormPane.vue +++ b/admin/src/views/config/center/EngineFormPane.vue @@ -42,7 +42,7 @@ - +

产品字段 → 会话上下文种子键的映射;空行保存时自动剔除,清空全部行 = @@ -86,7 +86,6 @@