20260822171825
This commit is contained in:
1 parent
c43b4c8364
commit
27a32b313d
187 files changed
+34869
-4741
No files matched your search
+12
-25
@@ -96,14 +96,14 @@ defineExpose({ open })
|
||||
@ok="save"
|
||||
>
|
||||
<div class="mm-i18n-quick">
|
||||
<div class="mm-i18n-quick__key">
|
||||
<div class="mm-i18n-quick__field">
|
||||
<label>多语言键</label>
|
||||
<Input v-model="i18nKey" disabled />
|
||||
</div>
|
||||
<div class="mm-i18n-quick__rows">
|
||||
<div v-for="(item, idx) in entries" :key="idx" class="mm-i18n-quick__row">
|
||||
<span class="mm-i18n-quick__locale">{{ item.b_name }} ({{ item.b_locale }})</span>
|
||||
<Input v-model="item.b_value" class="mm-i18n-quick__value" />
|
||||
<div v-for="(item, idx) in entries" :key="idx" class="mm-i18n-quick__field">
|
||||
<label>{{ item.b_name }} ({{ item.b_locale }})</label>
|
||||
<Input v-model="item.b_value" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,35 +116,22 @@ defineExpose({ open })
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
&__key {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
/* 上下结构:label 在上、输入框在下 */
|
||||
&__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
label {
|
||||
font-size: 13px;
|
||||
color: var(--fms-text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
&__rows {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__locale {
|
||||
flex: none;
|
||||
width: 140px;
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+248
-119
@@ -10,8 +10,6 @@ import Select from '@/components/ui/select/select.vue'
|
||||
import Switch from '@/components/ui/switch/switch.vue'
|
||||
import Form from '@/components/ui/form/form.vue'
|
||||
import FormItem from '@/components/ui/form/form-item.vue'
|
||||
import Tabs from '@/components/ui/tabs/tabs.vue'
|
||||
import TabPane from '@/components/ui/tabs/tab-pane.vue'
|
||||
import { buildModuleI18nKey, validateModuleCode } from '@/utils/i18n'
|
||||
import IconSelect from './IconSelect.vue'
|
||||
import ModulePowersPanel from './ModulePowersPanel.vue'
|
||||
@@ -34,6 +32,9 @@ const emit = defineEmits(['update', 'updatePowers', 'i18nSaved'])
|
||||
const activeSection = ref('basic')
|
||||
const formData = ref(null)
|
||||
|
||||
// 权限点数量(导航项徽标)
|
||||
const powersCount = computed(() => props.powers?.length ?? 0)
|
||||
|
||||
watch(
|
||||
() => props.menu,
|
||||
(val) => {
|
||||
@@ -55,6 +56,16 @@ 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' && activeSection.value === 'powers') {
|
||||
activeSection.value = 'basic'
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/* ---------- 校验规则:错误直接显示在对应 FormItem 下方 ---------- */
|
||||
const formRules = computed(() => ({
|
||||
b_code: [
|
||||
@@ -156,123 +167,162 @@ const i18nItems = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="mm-menu-detail" v-if="formData">
|
||||
<Tabs v-model="activeSection" class="mm-menu-tabs">
|
||||
<TabPane tab-key="basic">
|
||||
<template #label><Info :size="14" /> 基本信息</template>
|
||||
<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>
|
||||
<div class="mm-config">
|
||||
<nav class="mm-config-nav">
|
||||
<div class="mm-config-nav__group">
|
||||
<div class="mm-config-nav__group-title">概览</div>
|
||||
<button
|
||||
type="button"
|
||||
class="mm-config-nav__item"
|
||||
:class="{ 'is-active': activeSection === 'basic' }"
|
||||
@click="activeSection = 'basic'"
|
||||
>
|
||||
<Info :size="14" class="mm-config-nav__icon" />
|
||||
<span class="mm-config-nav__label">基本信息</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!isDirectory" class="mm-config-nav__group">
|
||||
<div class="mm-config-nav__group-title">业务能力</div>
|
||||
<button
|
||||
type="button"
|
||||
class="mm-config-nav__item"
|
||||
:class="{ 'is-active': activeSection === 'powers' }"
|
||||
@click="activeSection = 'powers'"
|
||||
>
|
||||
<Shield :size="14" class="mm-config-nav__icon" />
|
||||
<span class="mm-config-nav__label">权限配置</span>
|
||||
<span v-if="powersCount" class="mm-config-nav__badge">{{ powersCount }} 个权限</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mm-config-nav__group">
|
||||
<div class="mm-config-nav__group-title">高级设置</div>
|
||||
<button
|
||||
type="button"
|
||||
class="mm-config-nav__item"
|
||||
:class="{ 'is-active': activeSection === 'i18n' }"
|
||||
@click="activeSection = 'i18n'"
|
||||
>
|
||||
<Globe :size="14" class="mm-config-nav__icon" />
|
||||
<span class="mm-config-nav__label">多语言</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
<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="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" 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="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>
|
||||
</TabPane>
|
||||
<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>
|
||||
|
||||
<TabPane tab-key="powers">
|
||||
<template #label><Shield :size="14" /> 权限配置</template>
|
||||
<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)"
|
||||
/>
|
||||
</TabPane>
|
||||
<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>
|
||||
|
||||
<TabPane tab-key="i18n">
|
||||
<template #label><Globe :size="14" /> 多语言</template>
|
||||
<ModuleI18nPanel
|
||||
:module-id="String(formData.b_id || '')"
|
||||
:i18n-items="i18nItems"
|
||||
:loading="loading"
|
||||
ref="i18nPanelRef"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<I18nQuickSetModal ref="i18nModalRef" @saved="onI18nSaved" />
|
||||
</div>
|
||||
@@ -286,15 +336,94 @@ const i18nItems = computed(() => {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mm-menu-tabs {
|
||||
/* ---- 分组配置导航 + 内容(与模块配置 mm-config 系列一致) ---- */
|
||||
.mm-config {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
:deep(.tabs-content) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
.mm-config-nav {
|
||||
flex: none;
|
||||
width: 168px;
|
||||
padding: 10px 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mm-config-nav__group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mm-config-nav__group-title {
|
||||
padding: 2px 8px 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--fms-text-secondary);
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.mm-config-nav__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 5px 8px;
|
||||
border: none;
|
||||
border-radius: var(--fms-control-radius);
|
||||
background: transparent;
|
||||
color: var(--fms-text);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--fms-secondary);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: color-mix(in srgb, var(--fms-primary) 10%, transparent);
|
||||
color: var(--fms-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-config-nav__icon {
|
||||
flex: none;
|
||||
color: var(--fms-text-secondary);
|
||||
}
|
||||
|
||||
.mm-config-nav__item.is-active .mm-config-nav__icon {
|
||||
color: var(--fms-primary);
|
||||
}
|
||||
|
||||
.mm-config-nav__label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mm-config-nav__badge {
|
||||
flex: none;
|
||||
padding: 0 5px;
|
||||
border-radius: 999px;
|
||||
background: var(--fms-secondary);
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.mm-config-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
.mm-config-pane {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mm-basic__i18n {
|
||||
|
||||
+38
-11
@@ -8,6 +8,7 @@ import { computed, ref, watch } from 'vue'
|
||||
import { Languages } from '@lucide/vue'
|
||||
import Input from '@/components/ui/input/input.vue'
|
||||
import Select from '@/components/ui/select/select.vue'
|
||||
import Switch from '@/components/ui/switch/switch.vue'
|
||||
import Form from '@/components/ui/form/form.vue'
|
||||
import FormItem from '@/components/ui/form/form-item.vue'
|
||||
import { buildModuleI18nKey, validateModuleCode } from '@/utils/i18n'
|
||||
@@ -118,7 +119,8 @@ function onI18nSaved(key) {
|
||||
|
||||
<template>
|
||||
<div v-if="formData" class="mm-basic">
|
||||
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical" :columns="2" max-width="720px">
|
||||
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical" :columns="3" max-width="720px">
|
||||
<div class="mm-basic__section-title">标识与归属</div>
|
||||
<FormItem :label="isCategory ? '分类编码' : '模块编码'" name="b_code" required>
|
||||
<Input :model-value="formData.b_code" :disabled="loading" @update:model-value="onCodeInput" />
|
||||
</FormItem>
|
||||
@@ -140,7 +142,7 @@ function onI18nSaved(key) {
|
||||
<FormItem label="上级模块" name="b_parent_id">
|
||||
<Input :model-value="parentName" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="多语言键" name="b_i18n">
|
||||
<FormItem label="多语言键" name="b_i18n" :span="2">
|
||||
<div class="mm-basic__i18n">
|
||||
<Input :model-value="formData.b_i18n" readonly />
|
||||
<button
|
||||
@@ -153,16 +155,9 @@ function onI18nSaved(key) {
|
||||
</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>
|
||||
|
||||
<template v-if="isData || isQuery">
|
||||
<div class="mm-basic__section-title">数据来源</div>
|
||||
<FormItem label="查询视图" name="b_viewtable" required>
|
||||
<Input
|
||||
:model-value="formData.b_viewtable"
|
||||
@@ -206,7 +201,25 @@ function onI18nSaved(key) {
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<FormItem label="描述" name="b_bz" :span="2">
|
||||
<div class="mm-basic__section-title">行为设置</div>
|
||||
<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_canuse">
|
||||
<Switch
|
||||
:model-value="Number(formData.b_canuse) === 1"
|
||||
:disabled="loading"
|
||||
@update:model-value="(v) => setField('b_canuse', v ? 1 : 0)"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<div class="mm-basic__section-title">描述</div>
|
||||
<FormItem label="描述" name="b_bz" :span="3">
|
||||
<Input
|
||||
:model-value="formData.b_bz"
|
||||
type="textarea"
|
||||
@@ -227,6 +240,20 @@ function onI18nSaved(key) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 表单内部分组区块标题:占满整行,纯文字小标题(无分隔线,更轻) */
|
||||
.mm-basic__section-title {
|
||||
grid-column: 1 / -1;
|
||||
padding: 10px 0 6px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--fms-text-secondary);
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-basic__i18n {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user