feat(module-management): 导航状态化阶段一——分 section 脏检查 + tab 脏点
- useModuleConfig 新增 dirtySections(逐段 JSON.stringify 对比),hasModuleChanges 改为其派生;先以整树对比语义为基准写等价断言(含「加载补全默认行即脏」的 既有怪癖),新 spec module-dirty-sections 7 用例全绿 - index.vue #label 插槽给脏 tab 渲染 6px 圆点(title/aria-label,不只靠颜色), ui/Tabs 组件零改动;groups 由列表/表单两面板共享编辑,两 tab 联动标脏 - 计划文档 docs/module-management-tab-status-plan.md(横排保留、不做分组为已决策)
This commit is contained in:
1 parent
05d0c7f304
commit
4f89f373e6
4 files changed
+297
-4
No files matched your search
@@ -114,12 +114,32 @@ export function useModuleConfig(deps) {
|
||||
})
|
||||
|
||||
/* ================= 脏检查 ================= */
|
||||
// 是否有未保存的修改(用于「重载」按钮的确认提示)
|
||||
const hasModuleChanges = computed(() => {
|
||||
if (!drafts.value || !originals.value) return false
|
||||
return JSON.stringify(drafts.value) !== JSON.stringify(originals.value)
|
||||
// 分 section 脏检查:逐段 JSON.stringify 对比(与整树对比判定结果等价——
|
||||
// draft/original 均由 loadModuleConfig 以同一组键构造,整树不同 ⟺ 存在段不同)。
|
||||
// 已知怪癖(既有行为,保留):加载时补全的默认列表行只写 drafts(见 loadModuleConfig),
|
||||
// 此时 listConfig 打开即「脏」——与原整树对比一致,不得顺手修复。
|
||||
const SECTION_KEYS = [
|
||||
'module',
|
||||
'fields',
|
||||
'groups',
|
||||
'listConfig',
|
||||
'editConfig',
|
||||
'queryConfig',
|
||||
'autoCode',
|
||||
'powers',
|
||||
]
|
||||
const dirtySections = computed(() => {
|
||||
const dirty = new Set()
|
||||
if (!drafts.value || !originals.value) return dirty
|
||||
for (const key of SECTION_KEYS) {
|
||||
if (JSON.stringify(drafts.value[key]) !== JSON.stringify(originals.value[key])) dirty.add(key)
|
||||
}
|
||||
return dirty
|
||||
})
|
||||
|
||||
// 是否有未保存的修改(用于切换保护与「重载」按钮的确认提示)
|
||||
const hasModuleChanges = computed(() => dirtySections.value.size > 0)
|
||||
|
||||
function onModuleSelect(keys) {
|
||||
if (!keys.length) return
|
||||
const nextId = String(keys[0])
|
||||
@@ -369,6 +389,8 @@ export function useModuleConfig(deps) {
|
||||
const missingQueryCount = missingConfigCount('queryConfig')
|
||||
|
||||
return {
|
||||
hasModuleChanges,
|
||||
dirtySections,
|
||||
reloadModule,
|
||||
onModuleSelect,
|
||||
onFieldsUpdate,
|
||||
|
||||
@@ -189,6 +189,26 @@ const configNavItems = computed(() =>
|
||||
/* 编辑设计器:form/table 两面板 props/emits 契约一致,用动态组件消除模板重复 */
|
||||
const activeEditPanel = computed(() => (editMode.value === 'table' ? ModuleTableEditPanel : ModuleFormEditPanel))
|
||||
|
||||
/* tab 脏点:tab key → drafts section 映射。groups 在列表/表单两个面板都可编辑,
|
||||
分组变化时两个 tab 都标脏;多语言 tab 的数据独立保存(不走 drafts),不参与脏检查 */
|
||||
const TAB_SECTION_MAP = {
|
||||
basic: ['module'],
|
||||
fields: ['fields'],
|
||||
list: ['listConfig', 'groups'],
|
||||
edit: ['editConfig', 'groups'],
|
||||
query: ['queryConfig'],
|
||||
'auto-code': ['autoCode'],
|
||||
powers: ['powers'],
|
||||
}
|
||||
|
||||
const dirtyTabKeys = computed(() => {
|
||||
const keys = new Set()
|
||||
for (const [tabKey, sections] of Object.entries(TAB_SECTION_MAP)) {
|
||||
if (sections.some((section) => dirtySections.value.has(section))) keys.add(tabKey)
|
||||
}
|
||||
return keys
|
||||
})
|
||||
|
||||
/* ================= composables ================= */
|
||||
const { saveModule } = useModuleSave({
|
||||
selectedModule,
|
||||
@@ -296,6 +316,7 @@ const {
|
||||
onFieldsUpdate,
|
||||
onAutoCodeUpdate,
|
||||
syncFields,
|
||||
dirtySections,
|
||||
missingQueryCount,
|
||||
fillMissingConfigs,
|
||||
} = useModuleConfig({
|
||||
@@ -447,6 +468,14 @@ loadModuleTree()
|
||||
>
|
||||
<template #label="{ item }">
|
||||
<component :is="item.icon" :size="14" class="mm-config-nav__icon" />
|
||||
<!-- 脏点:有未保存修改的 tab 在图标后显示实心圆点(形状+title,不只靠颜色) -->
|
||||
<span
|
||||
v-if="dirtyTabKeys.has(item.key)"
|
||||
class="mm-config-nav__dirty"
|
||||
role="img"
|
||||
title="有未保存修改"
|
||||
aria-label="有未保存修改"
|
||||
></span>
|
||||
<span class="mm-config-nav__label">{{ item.label }}</span>
|
||||
<span v-if="item.badge" class="mm-config-nav__badge">{{ item.badge }}</span>
|
||||
</template>
|
||||
@@ -826,6 +855,15 @@ loadModuleTree()
|
||||
color: var(--fms-text-secondary);
|
||||
}
|
||||
|
||||
/* 脏点:作为 label 插槽的 flex 子元素排在图标后,间距由 .tabs-tab 的 gap 统一管理 */
|
||||
.mm-config-nav__dirty {
|
||||
flex: none;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--fms-primary);
|
||||
}
|
||||
|
||||
.mm-config-nav .tabs-tab-active .mm-config-nav__icon {
|
||||
color: var(--fms-primary);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user