feat: 表单保存交互规范P0 — 全局未保存离开守卫(useFormGuard)+MapCanvas接入
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { watch, isRef, onBeforeUnmount, type Ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
/**
|
||||
* 统一"未保存离开守卫"(CMA表单保存交互规范 P0)
|
||||
*
|
||||
* 用法(页面 setup 中):
|
||||
* const isDirty = ref(false)
|
||||
* const { markDirty, markClean } = useFormGuard(isDirty)
|
||||
* 表单数据变化时 markDirty();保存成功后 markClean()
|
||||
*
|
||||
* 行为:
|
||||
* 1. 路由切换前检查:从 meta.editable 页面离开且 isDirty 时弹确认框
|
||||
* 2. 浏览器关闭/刷新前检查:beforeunload 原生提示
|
||||
* 组件卸载时自动注销守卫与事件监听,避免全局钩子累积。
|
||||
*/
|
||||
export function useFormGuard(isDirty: Ref<boolean>) {
|
||||
const router = useRouter()
|
||||
|
||||
// 路由切换前检查(组件卸载时自动注销)
|
||||
const removeGuard = router.beforeEach((to, from, next) => {
|
||||
// 同页 query/hash 变化不算离开(如筛选条件更新URL),不触发守卫
|
||||
if (to.path === from.path) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
if (isDirty.value && from.meta.editable) {
|
||||
ElMessageBox.confirm('有未保存的修改,确定离开吗?', '提示', {
|
||||
confirmButtonText: '离开',
|
||||
cancelButtonText: '继续编辑',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
isDirty.value = false
|
||||
next()
|
||||
}).catch(() => {
|
||||
next(false)
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// 浏览器关闭/刷新前检查
|
||||
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (isDirty.value) {
|
||||
e.preventDefault()
|
||||
e.returnValue = ''
|
||||
}
|
||||
}
|
||||
window.addEventListener('beforeunload', onBeforeUnload)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeGuard()
|
||||
window.removeEventListener('beforeunload', onBeforeUnload)
|
||||
})
|
||||
|
||||
return {
|
||||
markDirty: () => { isDirty.value = true },
|
||||
markClean: () => { isDirty.value = false },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗表单脏追踪:弹窗打开时记录表单快照,内容变化后触发 onDirty,关闭时重置。
|
||||
* 适用于"弹窗编辑"类页面(KPIList/OrgManage/UserManage/ExpenseManage/TaxCompliance/CashPlan/AlertList 等)。
|
||||
* form 可为 ref 或 reactive 对象。
|
||||
*
|
||||
* 用法:
|
||||
* trackDialogForm(showForm, form, markDirty)
|
||||
*/
|
||||
export function trackDialogForm(open: Ref<boolean>, form: any, onDirty: () => void) {
|
||||
let snap = ''
|
||||
const get = () => JSON.stringify(isRef(form) ? form.value : form)
|
||||
watch(open, (v) => { if (!v) snap = '' })
|
||||
watch(get, (v) => {
|
||||
if (!open.value) return
|
||||
if (!snap) snap = v
|
||||
else if (v !== snap) onDirty()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user