refactor(module-management): R1 清场——删除无引用的 MenuDetailPanel/IconSelect 与过期 spec,useConfirm 合回 index.vue
This commit is contained in:
1 parent
91237c4a47
commit
2dba857880
5 files changed
+64
-3509
No files matched your search
@@ -1,60 +0,0 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 图标选择器:基于 Select(内置搜索过滤),选项为常用 lucide 图标子集。
|
||||
* 图标名首字母大写,如 "Settings"、"Users"。
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { Select } from '@/components/ui'
|
||||
import { getIcon } from '@/utils/lucideIcons'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const ICON_NAMES = [
|
||||
'LayoutGrid', 'Home', 'Folder', 'FolderOpen', 'FolderTree', 'Boxes', 'Database',
|
||||
'Table2', 'List', 'ListChecks', 'ClipboardList', 'FileText', 'FilePlus2', 'Files',
|
||||
'Settings', 'Settings2', 'SlidersHorizontal', 'Cog', 'Wrench', 'Shield', 'ShieldCheck',
|
||||
'Users', 'User', 'UserPlus', 'UserCog', 'Building2', 'Building', 'Contact',
|
||||
'Briefcase', 'Banknote', 'ShoppingCart', 'Truck', 'Package', 'Box', 'Warehouse',
|
||||
'CalendarDays', 'CalendarClock', 'Clock', 'Timer', 'History', 'AlarmClock',
|
||||
'Search', 'Filter', 'Eye', 'EyeOff', 'Lock', 'Unlock', 'Key', 'KeyRound',
|
||||
'Plus', 'Minus', 'Pencil', 'Trash2', 'Copy', 'Save', 'Upload', 'Download',
|
||||
'RefreshCw', 'RotateCw', 'ExternalLink', 'Link', 'Share2',
|
||||
'Printer', 'Mail', 'Phone', 'MapPin', 'Globe', 'Globe2', 'Languages', 'Translate',
|
||||
'BookOpen', 'BookMarked', 'GraduationCap', 'Award', 'Star', 'Heart', 'Flag',
|
||||
'Check', 'CheckCircle2', 'XCircle', 'AlertCircle', 'AlertTriangle', 'Info',
|
||||
'ChevronRight', 'ChevronDown', 'ChevronUp', 'ChevronsUpDown', 'ArrowRight',
|
||||
'ArrowLeft', 'ArrowUpDown', 'ArrowDownUp', 'MoveUp', 'MoveDown', 'GripVertical',
|
||||
'BarChart3', 'PieChart', 'TrendingUp', 'TrendingDown', 'Activity', 'Gauge',
|
||||
'CircleDot', 'Radio', 'Play', 'Pause', 'Video', 'Image', 'Camera', 'Palette',
|
||||
'Music', 'MessageSquare', 'MessageCircle', 'Bell', 'BellRing', 'Inbox',
|
||||
'Send', 'Paperclip', 'AttachFile', 'Cloud', 'CloudUpload', 'CloudDownload',
|
||||
'Cpu', 'HardDrive', 'Monitor', 'Laptop', 'Smartphone', 'Tablet', 'Mouse',
|
||||
'Network', 'Server', 'Plug', 'Wifi', 'Zap', 'Battery', 'Power', 'LogIn', 'LogOut',
|
||||
]
|
||||
|
||||
const iconOptions = ref(
|
||||
ICON_NAMES.map((name) => ({ label: name, value: name })),
|
||||
)
|
||||
|
||||
function iconOf(name) {
|
||||
return getIcon(name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:model-value="modelValue"
|
||||
:options="iconOptions"
|
||||
:disabled="disabled"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="搜索图标名称"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,347 +0,0 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 菜单配置面板(s_menu):基础信息 + 权限 + 多语言。
|
||||
* 权限点 b_power_type=menu、b_resource=菜单编码;i18n 键 menu.{code} / menu.{code}.{action}。
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { Languages } from '@lucide/vue'
|
||||
import { Input, Select, Switch, Form, FormItem } from '@/components/ui'
|
||||
import { buildModuleI18nKey, validateModuleCode } from '@/utils/i18n'
|
||||
import IconSelect from './IconSelect.vue'
|
||||
import ModulePowersPanel from './ModulePowersPanel.vue'
|
||||
import ModuleI18nPanel from './ModuleI18nPanel.vue'
|
||||
import I18nQuickSetModal from './I18nQuickSetModal.vue'
|
||||
|
||||
const props = defineProps({
|
||||
/** 当前菜单草稿对象(s_menu 行) */
|
||||
menu: { type: Object, default: null },
|
||||
parentName: { type: String, default: '' },
|
||||
/** data/query 模块选项 [{label, value}](b_module_id 关联) */
|
||||
moduleOptions: { type: Array, default: () => [] },
|
||||
/** 菜单权限点草稿(s_power,b_power_type=menu) */
|
||||
powers: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
/** 当前激活的配置分区(由父级导航栏驱动) */
|
||||
activeSection: { type: String, default: 'basic' },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update', 'updatePowers', 'i18nSaved', 'update:activeSection'])
|
||||
|
||||
const formData = ref(null)
|
||||
|
||||
// 权限点数量(导航项徽标)
|
||||
const powersCount = computed(() => props.powers?.length ?? 0)
|
||||
|
||||
watch(
|
||||
() => props.menu,
|
||||
(val) => {
|
||||
if (val === formData.value) return
|
||||
formData.value = val ? { ...val } : null
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const menuTypeOptions = [
|
||||
{ label: '目录', value: 'directory' },
|
||||
{ label: '页面', value: 'page' },
|
||||
{ label: '数据页面', value: 'data' },
|
||||
{ label: '外链', value: 'external' },
|
||||
]
|
||||
|
||||
const isDirectory = computed(() => formData.value?.b_menu_type === 'directory')
|
||||
const isPage = computed(() => formData.value?.b_menu_type === 'page')
|
||||
const isData = computed(() => formData.value?.b_menu_type === 'data')
|
||||
const isExternal = computed(() => formData.value?.b_menu_type === 'external')
|
||||
|
||||
// 类型变为目录时,若停留在「权限配置」分区(目录不展示该分区)则切回基本信息
|
||||
watch(
|
||||
() => formData.value?.b_menu_type,
|
||||
(type) => {
|
||||
if (type === 'directory' && props.activeSection === 'powers') {
|
||||
emit('update:activeSection', 'basic')
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/* ---------- 校验规则:错误直接显示在对应 FormItem 下方 ---------- */
|
||||
const formRules = computed(() => ({
|
||||
b_code: [
|
||||
{ required: true, message: '请填写菜单编码' },
|
||||
{ validator: (value) => validateModuleCode(value) || true },
|
||||
],
|
||||
b_name: [{ required: true, message: '请填写菜单名称' }],
|
||||
b_menu_type: [{ required: true, message: '请选择菜单类型' }],
|
||||
b_route: [{ required: true, message: '请填写页面路由' }],
|
||||
b_module_id: [{ required: true, message: '请选择关联模块' }],
|
||||
}))
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
// 保存前强制校验:返回带错误的字段列表 [{ name, errors }]
|
||||
function validate() {
|
||||
// 先切回基本信息分区,确保用户能看到标红字段
|
||||
emit('update:activeSection', 'basic')
|
||||
return formRef.value?.validateAll?.() ?? Promise.resolve([])
|
||||
}
|
||||
|
||||
function emitUpdate() {
|
||||
if (formData.value) emit('update', formData.value)
|
||||
}
|
||||
|
||||
function setField(field, value) {
|
||||
if (!formData.value) return
|
||||
formData.value[field] = value === '' ? null : value
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
// 编码变化时自动派生菜单多语言键
|
||||
function onCodeInput(v) {
|
||||
setField('b_code', v)
|
||||
if (formData.value) {
|
||||
formData.value.b_i18n = buildModuleI18nKey('menu', v)
|
||||
emitUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
const i18nModalRef = ref(null)
|
||||
|
||||
function openI18nModal() {
|
||||
if (!formData.value || !i18nModalRef.value) return
|
||||
const key = formData.value.b_i18n || buildModuleI18nKey('menu', formData.value.b_code)
|
||||
i18nModalRef.value.open(key, formData.value.b_name || '')
|
||||
}
|
||||
|
||||
function onI18nSaved(key) {
|
||||
setField('b_i18n', key)
|
||||
i18nPanelRef.value?.refreshKey?.(key)
|
||||
emit('i18nSaved', key)
|
||||
}
|
||||
|
||||
const i18nPanelRef = ref(null)
|
||||
|
||||
/** 供父组件保存时调用:按最终草稿态生成 s_i18n 请求 */
|
||||
function saveI18n() {
|
||||
const panel = i18nPanelRef.value
|
||||
if (!panel) return null
|
||||
if (typeof panel.reconcileManagedRows === 'function') panel.reconcileManagedRows(i18nItems.value)
|
||||
return typeof panel.buildSaveRequest === 'function' ? panel.buildSaveRequest() : null
|
||||
}
|
||||
|
||||
function markI18nSaved() {
|
||||
i18nPanelRef.value?.markSaved?.()
|
||||
}
|
||||
|
||||
/** 父组件为 i18n inserts 分配真实主键后调用,回写行内 ID 避免二次保存重复插入 */
|
||||
function applyI18nInsertedIds(inserts) {
|
||||
i18nPanelRef.value?.applyInsertedIds?.(inserts)
|
||||
}
|
||||
|
||||
defineExpose({ validate, saveI18n, markI18nSaved, applyI18nInsertedIds })
|
||||
|
||||
/* ---- i18n 受管键组装(菜单本身 + 权限点) ---- */
|
||||
const i18nItems = computed(() => {
|
||||
const items = []
|
||||
if (formData.value) {
|
||||
const key = formData.value.b_i18n || buildModuleI18nKey('menu', formData.value.b_code)
|
||||
if (key) {
|
||||
items.push({
|
||||
key,
|
||||
managed: true,
|
||||
defaultValue: formData.value.b_name || '',
|
||||
id: `menu:${key}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const power of props.powers || []) {
|
||||
const key = String(power.b_i18n || '')
|
||||
if (key) {
|
||||
items.push({
|
||||
key,
|
||||
managed: true,
|
||||
defaultValue: power.b_name || '',
|
||||
id: `power:${key}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mm-menu-detail" v-if="formData">
|
||||
<!-- 配置导航已上提至页面级中栏(index.vue),本组件只负责内容分区 -->
|
||||
<div class="mm-config-content">
|
||||
<div v-show="activeSection === 'basic'" class="mm-config-pane">
|
||||
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical" :columns="2" max-width="720px">
|
||||
<FormItem label="菜单编码" name="b_code" required>
|
||||
<Input :model-value="formData.b_code" :disabled="loading" @update:model-value="onCodeInput" />
|
||||
</FormItem>
|
||||
<FormItem label="菜单名称" name="b_name" required>
|
||||
<Input
|
||||
:model-value="formData.b_name"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_name', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="菜单类型" name="b_menu_type" required>
|
||||
<Select
|
||||
:model-value="formData.b_menu_type"
|
||||
:options="menuTypeOptions"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_menu_type', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="上级菜单" name="b_parent_id">
|
||||
<Input :model-value="parentName" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="多语言键" name="b_i18n">
|
||||
<div class="mm-basic__i18n">
|
||||
<Input :model-value="formData.b_i18n" readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="mm-basic__i18n-btn"
|
||||
title="快速设置多语言"
|
||||
@click="openI18nModal"
|
||||
>
|
||||
<Languages :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem label="排序号" name="b_xh">
|
||||
<Input
|
||||
:model-value="formData.b_xh"
|
||||
type="number"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_xh', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="菜单图标" name="b_icon">
|
||||
<IconSelect
|
||||
:model-value="formData.b_icon || ''"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_icon', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="启用" name="b_canuse">
|
||||
<Switch
|
||||
:model-value="Number(formData.b_canuse) === 1"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_canuse', v ? 1 : 0)"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem v-if="isPage || isExternal" label="页面路由" name="b_route" required>
|
||||
<Input
|
||||
:model-value="formData.b_route"
|
||||
:disabled="loading"
|
||||
placeholder="固定页面路由或外链地址"
|
||||
@update:model-value="(v) => setField('b_route', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem v-if="isData" label="关联模块" name="b_module_id" required>
|
||||
<Select
|
||||
:model-value="String(formData.b_module_id || '')"
|
||||
:options="moduleOptions"
|
||||
:disabled="loading"
|
||||
placeholder="选择数据/查询模块"
|
||||
clearable
|
||||
@update:model-value="(v) => setField('b_module_id', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem v-if="isPage || isExternal || isData" label="访问权限编码" name="b_power_code">
|
||||
<Input
|
||||
:model-value="formData.b_power_code"
|
||||
:disabled="loading"
|
||||
placeholder="对应 s_power.b_code"
|
||||
@update:model-value="(v) => setField('b_power_code', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div v-if="!isDirectory" v-show="activeSection === 'powers'" class="mm-config-pane">
|
||||
<ModulePowersPanel
|
||||
:module-id="String(formData.b_id || '')"
|
||||
:module-code="formData.b_code || ''"
|
||||
:powers="powers"
|
||||
:loading="loading"
|
||||
power-type="menu"
|
||||
:resource="formData.b_code || ''"
|
||||
i18n-prefix="menu"
|
||||
@update="(v) => emit('updatePowers', v)"
|
||||
@i18n-saved="(key) => emit('i18nSaved', key)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeSection === 'i18n'" class="mm-config-pane">
|
||||
<ModuleI18nPanel
|
||||
:module-id="String(formData.b_id || '')"
|
||||
:i18n-items="i18nItems"
|
||||
:loading="loading"
|
||||
ref="i18nPanelRef"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<I18nQuickSetModal ref="i18nModalRef" @saved="onI18nSaved" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mm-menu-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ---- 内容分区(配置导航已上提至页面级中栏) ---- */
|
||||
.mm-config-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
.mm-config-pane {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.mm-basic__i18n {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
:deep(.input-wrapper) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-basic__i18n-btn {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: var(--fms-control-radius);
|
||||
background: transparent;
|
||||
color: var(--fms-text-secondary);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--fms-secondary);
|
||||
color: var(--fms-text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* 两套确认弹窗:
|
||||
* - 通用确认(ui/modal 为组件式 API):showConfirm/onConfirmOk/confirmState
|
||||
* - 切换对象未保存保护:switchConfirm/onSwitch*(保存并切换 / 放弃修改 / 取消)
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { Message } from '@/components/ui'
|
||||
|
||||
export function useConfirm({ saveModule, moduleSelectedId }) {
|
||||
/* ================= 通用确认弹窗 ================= */
|
||||
const confirmVisible = ref(false)
|
||||
const confirmState = ref(null)
|
||||
// 确认操作进行中:按钮转圈、弹窗锁定,等待 onOk 完成后再关闭
|
||||
const confirmSaving = ref(false)
|
||||
|
||||
function showConfirm({ title, content, okText, okType, onOk }) {
|
||||
confirmState.value = { title, content, okText: okText || '确定', okType, onOk }
|
||||
confirmVisible.value = true
|
||||
}
|
||||
|
||||
async function onConfirmOk() {
|
||||
const state = confirmState.value
|
||||
if (!state?.onOk || confirmSaving.value) return
|
||||
confirmSaving.value = true
|
||||
try {
|
||||
await state.onOk()
|
||||
} catch (e) {
|
||||
// onOk 内部已处理错误提示,这里兜底避免未捕获异常
|
||||
Message.error('操作失败:' + (e?.message || e))
|
||||
} finally {
|
||||
confirmSaving.value = false
|
||||
confirmVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= 切换模块未保存保护(保存并切换 / 放弃修改 / 取消) ================= */
|
||||
const switchConfirm = ref(null) // { pendingId, name }
|
||||
const switchSaving = ref(false)
|
||||
|
||||
function onSwitchCancel() {
|
||||
switchConfirm.value = null
|
||||
switchSaving.value = false
|
||||
}
|
||||
|
||||
function onSwitchDiscard() {
|
||||
const state = switchConfirm.value
|
||||
if (!state) return
|
||||
moduleSelectedId.value = state.pendingId
|
||||
switchConfirm.value = null
|
||||
}
|
||||
|
||||
async function onSwitchSave() {
|
||||
const state = switchConfirm.value
|
||||
if (!state || switchSaving.value) return
|
||||
switchSaving.value = true
|
||||
try {
|
||||
const ok = await saveModule()
|
||||
if (!ok) {
|
||||
// 保存失败(原因已由 saveModule 内部提示):留在当前对象,弹窗不关闭,可重试或放弃
|
||||
Message.warning('保存未完成,已留在当前对象,请处理后再切换')
|
||||
return
|
||||
}
|
||||
moduleSelectedId.value = state.pendingId
|
||||
switchConfirm.value = null
|
||||
} finally {
|
||||
switchSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
confirmVisible,
|
||||
confirmState,
|
||||
confirmSaving,
|
||||
showConfirm,
|
||||
onConfirmOk,
|
||||
switchConfirm,
|
||||
switchSaving,
|
||||
onSwitchCancel,
|
||||
onSwitchDiscard,
|
||||
onSwitchSave,
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
* 模块管理:模块配置(单分区)。
|
||||
* 模块配置:左侧模块树 + 右侧分节面板(基础/字段/列表/编辑/查询/自动编码/权限/多语言)。
|
||||
* 数据直接经通用 API(loadDataApi / saveObjectApi / nextIdApi / describeApi)读写元数据表。
|
||||
* 树/草稿/保存/确认弹窗逻辑分别抽到 composables/useModuleTree、useModuleConfig、
|
||||
* useModuleSave、useConfirm;本文件只保留模板、状态接线与面板编排。
|
||||
* 树/草稿/保存逻辑分别抽到 composables/useModuleTree、useModuleConfig、
|
||||
* useModuleSave;确认弹窗状态机仅本页使用,直接内联在下方。
|
||||
* 本文件保留模板、状态接线与面板编排。
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { useHotkeys } from '@/composables/useHotkeys'
|
||||
@@ -33,12 +34,12 @@ import {
|
||||
SplitterPanel,
|
||||
Tabs,
|
||||
Modal,
|
||||
Message,
|
||||
} from '@/components/ui'
|
||||
import { buildModuleI18nKey, buildModuleItemI18nKey } from '@/utils/i18n'
|
||||
import { useModuleTree, findModuleNode } from './composables/useModuleTree'
|
||||
import { useModuleConfig } from './composables/useModuleConfig'
|
||||
import { useModuleSave } from './composables/useModuleSave'
|
||||
import { useConfirm } from './composables/useConfirm'
|
||||
import ModuleBasicPanel from './components/ModuleBasicPanel.vue'
|
||||
import ModuleFieldsPanel from './components/ModuleFieldsPanel.vue'
|
||||
import ModuleListConfigPanel from './components/ModuleListConfigPanel.vue'
|
||||
@@ -213,18 +214,66 @@ const { saveModule } = useModuleSave({
|
||||
getFinalI18nItems: () => finalI18nItems.value,
|
||||
})
|
||||
|
||||
const {
|
||||
confirmVisible,
|
||||
confirmState,
|
||||
confirmSaving,
|
||||
showConfirm,
|
||||
onConfirmOk,
|
||||
switchConfirm,
|
||||
switchSaving,
|
||||
onSwitchCancel,
|
||||
onSwitchDiscard,
|
||||
onSwitchSave,
|
||||
} = useConfirm({ saveModule, moduleSelectedId })
|
||||
/* ================= 确认弹窗状态机(仅本页使用,不抽 composable) ================= */
|
||||
/* 通用确认弹窗(ui/modal 为组件式 API) */
|
||||
const confirmVisible = ref(false)
|
||||
const confirmState = ref(null)
|
||||
// 确认操作进行中:按钮转圈、弹窗锁定,等待 onOk 完成后再关闭
|
||||
const confirmSaving = ref(false)
|
||||
|
||||
function showConfirm({ title, content, okText, okType, onOk }) {
|
||||
confirmState.value = { title, content, okText: okText || '确定', okType, onOk }
|
||||
confirmVisible.value = true
|
||||
}
|
||||
|
||||
async function onConfirmOk() {
|
||||
const state = confirmState.value
|
||||
if (!state?.onOk || confirmSaving.value) return
|
||||
confirmSaving.value = true
|
||||
try {
|
||||
await state.onOk()
|
||||
} catch (e) {
|
||||
// onOk 内部已处理错误提示,这里兜底避免未捕获异常
|
||||
Message.error('操作失败:' + (e?.message || e))
|
||||
} finally {
|
||||
confirmSaving.value = false
|
||||
confirmVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/* 切换模块未保存保护(保存并切换 / 放弃修改 / 取消) */
|
||||
const switchConfirm = ref(null) // { pendingId, name }
|
||||
const switchSaving = ref(false)
|
||||
|
||||
function onSwitchCancel() {
|
||||
switchConfirm.value = null
|
||||
switchSaving.value = false
|
||||
}
|
||||
|
||||
function onSwitchDiscard() {
|
||||
const state = switchConfirm.value
|
||||
if (!state) return
|
||||
moduleSelectedId.value = state.pendingId
|
||||
switchConfirm.value = null
|
||||
}
|
||||
|
||||
async function onSwitchSave() {
|
||||
const state = switchConfirm.value
|
||||
if (!state || switchSaving.value) return
|
||||
switchSaving.value = true
|
||||
try {
|
||||
const ok = await saveModule()
|
||||
if (!ok) {
|
||||
// 保存失败(原因已由 saveModule 内部提示):留在当前对象,弹窗不关闭,可重试或放弃
|
||||
Message.warning('保存未完成,已留在当前对象,请处理后再切换')
|
||||
return
|
||||
}
|
||||
moduleSelectedId.value = state.pendingId
|
||||
switchConfirm.value = null
|
||||
} finally {
|
||||
switchSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
filteredModuleTree,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user