20260902223822
This commit is contained in:
1 parent
db7813a2d2
commit
c8940f6e03
11 files changed
+327
-922
No files matched your search
@@ -10,6 +10,8 @@ defineOptions({ inheritAttrs: false })
|
||||
const props = defineProps({
|
||||
virtual: { type: Boolean, default: true },
|
||||
virtualX: { type: Boolean, default: true },
|
||||
// 是否在非横向虚拟滚动时自动铺满容器;默认按列配置宽度展示。
|
||||
autoFitWidth: { type: Boolean, default: false },
|
||||
// 列宽拖拽默认开启;false=关闭;{ disabled: (col) => boolean }=按列禁用
|
||||
// (铺满容器的弹性列应禁用拖拽,见库文档「列宽铺满容器hack方式」)
|
||||
colResizable: { type: [Boolean, Object], default: true },
|
||||
@@ -73,7 +75,7 @@ const tableTheme = computed(() => props.theme ?? (appStore.darkMode ? 'dark' : '
|
||||
// 需从 vnode.props 探测;绑定关系在模板编写时确定,运行期不会变化,无需响应式。
|
||||
const columnsBound =
|
||||
'onUpdate:columns' in (getCurrentInstance()?.vnode.props ?? {})
|
||||
const effectiveColResizable = columnsBound ? props.colResizable : false
|
||||
const effectiveColResizable = columnsBound && !props.autoFitWidth ? props.colResizable : false
|
||||
|
||||
// Tab 面板从隐藏恢复时,等到容器拥有有效尺寸再刷新虚拟滚动布局。
|
||||
// rAF 将同一帧内的多次 ResizeObserver 通知合并,避免重复重排。
|
||||
@@ -276,6 +278,14 @@ const seqConfig = computed(() => {
|
||||
width: Number(cfg.width ?? 50),
|
||||
}
|
||||
})
|
||||
const seqWidth = ref(seqConfig.value.width)
|
||||
watch(
|
||||
() => seqConfig.value.width,
|
||||
(width) => {
|
||||
seqWidth.value = width
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
const columnDragConfig = computed(() => {
|
||||
if (!columnsBound || !props.columnDraggable) return false
|
||||
const configured = props.columnDraggable === true ? {} : props.columnDraggable
|
||||
@@ -298,16 +308,18 @@ const columnDragConfig = computed(() => {
|
||||
}
|
||||
})
|
||||
const hasRowSelection = computed(() => Boolean(props.rowSelection))
|
||||
const selectionWidth = ref(44)
|
||||
const dragRowColumn = {
|
||||
type: 'dragRow',
|
||||
dataIndex: '__drag_row__',
|
||||
title: '',
|
||||
width: 40,
|
||||
resizable: false,
|
||||
}
|
||||
const dragRowWidth = ref(dragRowColumn.width)
|
||||
|
||||
function getHeaderAlign(column) {
|
||||
if (column.headerAlign) return column.headerAlign
|
||||
if (column.type === 'seq') return 'center'
|
||||
if (isSelectionColumn(column)) return 'center'
|
||||
if (column.dataIndex === 'actions') return 'center'
|
||||
if (column.actions && column.actions.length) return 'center'
|
||||
@@ -322,14 +334,13 @@ const displayColumns = computed(() => {
|
||||
if (seq.enabled) {
|
||||
cols.unshift({
|
||||
type: 'seq',
|
||||
width: seq.width,
|
||||
width: seqWidth.value,
|
||||
dataIndex: '',
|
||||
title: seq.title,
|
||||
resizable: false,
|
||||
})
|
||||
}
|
||||
if (props.rowDraggable) cols.unshift(dragRowColumn)
|
||||
if (hasRowSelection.value) cols.unshift(selectionColumn)
|
||||
if (props.rowDraggable) cols.unshift({ ...dragRowColumn, width: dragRowWidth.value })
|
||||
if (hasRowSelection.value) cols.unshift({ ...selectionColumn, width: selectionWidth.value })
|
||||
return cols.map((column) => ({
|
||||
...column,
|
||||
headerAlign: getHeaderAlign(column),
|
||||
@@ -338,6 +349,15 @@ const displayColumns = computed(() => {
|
||||
|
||||
// 剥离内部列(选择列、序号列、拖拽列)后回传,避免污染父组件 columns
|
||||
function onColumnsUpdate(columns) {
|
||||
for (const column of columns || []) {
|
||||
if (isSelectionColumn(column) && column.width != null) {
|
||||
selectionWidth.value = Number.parseInt(column.width, 10) || selectionWidth.value
|
||||
} else if (column.type === 'seq' && column.width != null) {
|
||||
seqWidth.value = Number.parseInt(column.width, 10) || seqWidth.value
|
||||
} else if (column.type === 'dragRow' && column.width != null) {
|
||||
dragRowWidth.value = Number.parseInt(column.width, 10) || dragRowWidth.value
|
||||
}
|
||||
}
|
||||
const originalByKey = new Map(
|
||||
props.columns.map((col) => [String(col.key ?? col.dataIndex ?? ''), col]),
|
||||
)
|
||||
@@ -592,7 +612,7 @@ defineExpose({
|
||||
<div
|
||||
ref="rootRef"
|
||||
class="fms-table"
|
||||
:class="[bordered ? 'fms-table--bordered' : '']"
|
||||
:class="[bordered ? 'fms-table--bordered' : '', autoFitWidth ? 'fms-table--auto-fit-width' : '']"
|
||||
@keydown.capture="handleKeydownCapture"
|
||||
>
|
||||
<StkTable
|
||||
@@ -660,6 +680,11 @@ defineExpose({
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
/* 不自适应时按列配置宽度展示;自适应模式不启用列宽拖拽,使用库默认布局。 */
|
||||
.fms-table:not(.fms-table--auto-fit-width) :deep(.stk-table-main) {
|
||||
flex: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 非 scoped 全局样式:选择列原生 checkbox 外观模拟 antdv-next Checkbox,
|
||||
@@ -684,15 +709,6 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
/* 列宽铺满容器(库文档「列宽铺满容器hack方式」):
|
||||
开启 col-resizable 后库默认把 .stk-table-main 设为 flex:none + fit-content,
|
||||
表格收缩到内容宽。非 virtual-x(列数 ≤ 阈值、未设宽列走原生自适应)时覆盖回
|
||||
flex:1 使表格铺满容器;配合「固定列设 width、弹性列设 minWidth 且禁用其拖拽」
|
||||
的列配置约定使用。宽表(virtual-x,横向滚动)保持库的收缩行为。 */
|
||||
.fms-table__inner.stk-table:not(.virtual-x) .stk-table-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fms-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* 菜单管理:管理 s_menu 导航树。
|
||||
* 布局:左侧菜单树 + 右侧属性编辑面板。
|
||||
* 菜单类型:directory(目录)、page(固定页面)、data(数据页面)、external(外链)。
|
||||
* 菜单类型:directory(目录)、page(页面)、external(外链)。
|
||||
*/
|
||||
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
||||
import { useHotkeys } from '@/composables/useHotkeys'
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Folder,
|
||||
FileText,
|
||||
ExternalLink,
|
||||
Database,
|
||||
RefreshCw,
|
||||
Languages,
|
||||
} from '@lucide/vue'
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
Checkbox,
|
||||
Input,
|
||||
Select,
|
||||
Switch,
|
||||
Splitter,
|
||||
SplitterPanel,
|
||||
Tree,
|
||||
@@ -63,22 +61,19 @@ const nonDefaultLanguages = I18N_LANGUAGES.filter((l) => !l.default)
|
||||
/* ================= 类型映射 ================= */
|
||||
const menuTypeOptions = [
|
||||
{ label: '目录', value: 'directory' },
|
||||
{ label: '固定页面', value: 'page' },
|
||||
{ label: '数据页面', value: 'data' },
|
||||
{ label: '页面', value: 'page' },
|
||||
{ label: '外链', value: 'external' },
|
||||
]
|
||||
|
||||
const menuTypeLabel = {
|
||||
directory: '目录',
|
||||
page: '页面',
|
||||
data: '数据',
|
||||
external: '外链',
|
||||
}
|
||||
|
||||
const menuTypeIconMap = {
|
||||
directory: Folder,
|
||||
page: FileText,
|
||||
data: Database,
|
||||
external: ExternalLink,
|
||||
}
|
||||
|
||||
@@ -119,8 +114,10 @@ const parentMenuName = computed(() => {
|
||||
const menuType = computed(() => selectedMenu.value?.b_menu_type || null)
|
||||
const isDirectory = computed(() => menuType.value === 'directory')
|
||||
const isPage = computed(() => menuType.value === 'page')
|
||||
const isData = computed(() => menuType.value === 'data')
|
||||
const isExternal = computed(() => menuType.value === 'external')
|
||||
const menuTypeLocked = computed(
|
||||
() => !!selectedMenu.value && !unsavedMenuIds.has(String(selectedMenu.value.b_id)),
|
||||
)
|
||||
|
||||
/* ================= 树加载 ================= */
|
||||
watch(menuKeyword, (kw) => {
|
||||
@@ -175,7 +172,6 @@ function buildMenuContextMenu(node) {
|
||||
const items = [
|
||||
{ key: 'add-directory', label: '新增目录', icon: Folder, disabled: isUnsaved },
|
||||
{ key: 'add-page', label: '新增页面', icon: FileText, disabled: isUnsaved },
|
||||
{ key: 'add-data', label: '新增数据页面', icon: Database, disabled: isUnsaved },
|
||||
{ key: 'add-external', label: '新增外链', icon: ExternalLink, disabled: isUnsaved },
|
||||
]
|
||||
if (node) {
|
||||
@@ -189,7 +185,6 @@ function onMenuContextMenuClick({ item, node }) {
|
||||
const parentId = node ? String(node.b_id) : null
|
||||
if (item.key === 'add-directory') void addMenu(parentId, 'directory')
|
||||
else if (item.key === 'add-page') void addMenu(parentId, 'page')
|
||||
else if (item.key === 'add-data') void addMenu(parentId, 'data')
|
||||
else if (item.key === 'add-external') void addMenu(parentId, 'external')
|
||||
else if (item.key === 'delete') void deleteMenu(parentId)
|
||||
}
|
||||
@@ -198,7 +193,6 @@ function onMenuContextMenuClick({ item, node }) {
|
||||
const toolbarAddOptions = [
|
||||
{ key: 'directory', label: '新增目录', icon: Folder },
|
||||
{ key: 'page', label: '新增页面', icon: FileText },
|
||||
{ key: 'data', label: '新增数据页面', icon: Database },
|
||||
{ key: 'external', label: '新增外链', icon: ExternalLink },
|
||||
]
|
||||
|
||||
@@ -316,7 +310,6 @@ function menuRowOf(node) {
|
||||
b_menu_type: node.b_menu_type,
|
||||
b_route: node.b_route ?? null,
|
||||
b_module_id: node.b_module_id ?? null,
|
||||
b_power_code: node.b_power_code ?? null,
|
||||
b_icon: node.b_icon ?? null,
|
||||
b_xh: node.b_xh,
|
||||
b_canuse: node.b_canuse,
|
||||
@@ -335,7 +328,6 @@ async function addMenu(parentId, menuTypeValue = 'page') {
|
||||
b_menu_type: menuTypeValue,
|
||||
b_route: null,
|
||||
b_module_id: null,
|
||||
b_power_code: null,
|
||||
b_icon: null,
|
||||
b_xh: 10,
|
||||
b_canuse: 1,
|
||||
@@ -385,7 +377,7 @@ async function deleteMenu(id) {
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const menuPowerCode = menu.b_power_code
|
||||
const menuPowerCode = menu.b_code ? `menu.${String(menu.b_code).trim()}` : ''
|
||||
const reqs = [{ table: 's_i18n', key_field: 'b_id', deletes: [{ b_module_id: menuId }] }]
|
||||
// 清理权限点:访问(type=menu 按 b_resource 关联)+ 操作/按钮(type=action 按 b_module_id 关联)
|
||||
if (menuPowerCode) {
|
||||
@@ -433,7 +425,7 @@ async function loadMenuConfig(menuId) {
|
||||
}
|
||||
i18nValues.value = i18nValuesInit
|
||||
originalI18nValues.value = { ...i18nValuesInit }
|
||||
// 操作/按钮权限点:仅 page/data/external 才有(目录是导航容器,无按钮权限)
|
||||
// 操作/按钮权限点:仅页面/外链才有(目录是导航容器,无按钮权限)
|
||||
powersLoading.value = true
|
||||
try {
|
||||
const powerRes =
|
||||
@@ -587,22 +579,56 @@ async function saveMenu() {
|
||||
const menuData = drafts.value?.menu
|
||||
if (!menuData) return false
|
||||
|
||||
const isNew = unsavedMenuIds.has(menuId)
|
||||
const allowedMenuTypes = new Set(['directory', 'page', 'external'])
|
||||
if (!allowedMenuTypes.has(menuData.b_menu_type)) {
|
||||
Message.warning('菜单类型只能是目录、页面或外链')
|
||||
return false
|
||||
}
|
||||
if (!isNew && menuData.b_menu_type !== originals.value?.menu?.b_menu_type) {
|
||||
Message.warning('已保存菜单不允许修改菜单类型')
|
||||
return false
|
||||
}
|
||||
if (menuData.b_parent_id && String(menuData.b_parent_id) === menuId) {
|
||||
Message.warning('上级菜单不能选择当前菜单')
|
||||
return false
|
||||
}
|
||||
const menuTypeRules = {
|
||||
directory: { route: false, module: false },
|
||||
page: { route: true, module: true },
|
||||
external: { route: true, module: false },
|
||||
}
|
||||
const typeRule = menuTypeRules[menuData.b_menu_type]
|
||||
if (typeRule.route && !String(menuData.b_route || '').trim()) {
|
||||
Message.warning(
|
||||
menuData.b_menu_type === 'external'
|
||||
? '外链菜单必须填写外链地址'
|
||||
: '页面菜单必须填写路由地址',
|
||||
)
|
||||
return false
|
||||
}
|
||||
if (typeRule.module && !menuData.b_module_id) {
|
||||
Message.warning('页面菜单必须绑定模块')
|
||||
return false
|
||||
}
|
||||
if (!typeRule.route && menuData.b_route) menuData.b_route = null
|
||||
if (!typeRule.module && menuData.b_module_id) menuData.b_module_id = null
|
||||
|
||||
// 简体中文名称 = 表单 b_name(默认语言,直接存菜单表)
|
||||
|
||||
// 菜单编码由用户填写,但保存时统一去除首尾空格,作为稳定标识使用。
|
||||
const menuCode = String(menuData.b_code || '').trim()
|
||||
menuData.b_code = menuCode
|
||||
|
||||
// 受管 i18n 键:菜单统一 menu. 前缀
|
||||
const menuI18nKey = `menu.${menuData.b_code}`
|
||||
const menuI18nKey = `menu.${menuCode}`
|
||||
menuData.b_i18n = menuI18nKey
|
||||
|
||||
// 目录是纯导航容器:可见性由子菜单决定,自身不需要访问权限编码,也不生成权限点。
|
||||
// 仅 page/data/external 需要访问权限编码;留空时按菜单编码自动生成(权限跟着菜单走)
|
||||
// 目录是纯导航容器:可见性由子菜单决定,自身不生成菜单访问权限点。
|
||||
// 页面类型菜单的访问权限统一由菜单编码派生。
|
||||
const menuIsDirectory = menuData.b_menu_type === 'directory'
|
||||
if (menuIsDirectory) {
|
||||
menuData.b_power_code = null
|
||||
} else if (!menuData.b_power_code) {
|
||||
menuData.b_power_code = `menu.${menuData.b_code}`
|
||||
}
|
||||
const powerCode = menuCode ? `menu.${menuCode}` : ''
|
||||
|
||||
const isNew = unsavedMenuIds.has(menuId)
|
||||
const req = { table: 's_menu', key_field: 'b_id' }
|
||||
if (isNew) {
|
||||
req.inserts = [menuData]
|
||||
@@ -632,12 +658,13 @@ async function saveMenu() {
|
||||
reqs.push(i18nReq)
|
||||
}
|
||||
|
||||
// 目录是纯导航容器:自身不需要访问权限编码与权限点。
|
||||
// 目录是纯导航容器:自身不需要菜单访问权限与按钮权限点。
|
||||
// 若该菜单此前(或改为目录前)生成过权限记录,保存时一并清理,避免孤儿数据:
|
||||
// - 访问权限(s_power.b_power_type='menu',按原权限编码关联)
|
||||
// - 访问权限(s_power.b_power_type='menu',按菜单编码派生的权限字符关联)
|
||||
// - 操作/按钮权限(s_power.b_power_type='action',按 b_module_id=菜单 id 关联)
|
||||
if (menuIsDirectory) {
|
||||
const prevPowerCode = originals.value?.menu?.b_power_code
|
||||
const previousCode = String(originals.value?.menu?.b_code || '').trim()
|
||||
const prevPowerCode = previousCode ? `menu.${previousCode}` : ''
|
||||
if (prevPowerCode) {
|
||||
reqs.push({
|
||||
table: 's_power',
|
||||
@@ -652,9 +679,17 @@ async function saveMenu() {
|
||||
})
|
||||
}
|
||||
|
||||
// 访问权限点(s_power.b_power_type='menu',自动同步)
|
||||
const powerCode = (menuData.b_power_code || '').trim()
|
||||
// 访问权限点(s_power.b_power_type='menu',由菜单编码自动同步)
|
||||
if (powerCode) {
|
||||
const previousCode = String(originals.value?.menu?.b_code || '').trim()
|
||||
const previousPowerCode = previousCode ? `menu.${previousCode}` : ''
|
||||
if (previousPowerCode && previousPowerCode !== powerCode) {
|
||||
reqs.push({
|
||||
table: 's_power',
|
||||
key_field: 'b_id',
|
||||
deletes: [{ b_power_type: 'menu', b_resource: previousPowerCode }],
|
||||
})
|
||||
}
|
||||
// 查是否有已存在的访问权限(按 b_resource = 菜单权限编码)
|
||||
const accessPowerRes = menuData.b_id
|
||||
? await loadDataApi('s_power', `b_power_type = 'menu' AND b_resource = '${powerCode}'`)
|
||||
@@ -814,16 +849,12 @@ function onI18nModalChange(values) {
|
||||
|
||||
function onMenuTypeChange(v) {
|
||||
if (!drafts.value?.menu) return
|
||||
if (menuTypeLocked.value) return
|
||||
drafts.value.menu.b_menu_type = v
|
||||
// Clear type-specific fields when switching
|
||||
// 目录和外链不绑定业务模块;页面同时使用路由和模块。
|
||||
if (v === 'directory') {
|
||||
drafts.value.menu.b_route = null
|
||||
drafts.value.menu.b_module_id = null
|
||||
drafts.value.menu.b_power_code = null
|
||||
} else if (v === 'page') {
|
||||
drafts.value.menu.b_module_id = null
|
||||
} else if (v === 'data') {
|
||||
drafts.value.menu.b_route = null
|
||||
} else if (v === 'external') {
|
||||
drafts.value.menu.b_module_id = null
|
||||
}
|
||||
@@ -863,9 +894,7 @@ const powers = ref([])
|
||||
const powersBaseline = ref('')
|
||||
const powersLoading = ref(false)
|
||||
|
||||
/** 权限点表格列配置
|
||||
* 列宽铺满约定(库文档「列宽铺满容器hack方式」):固定列设 width,
|
||||
* 弹性列(权限名称)设 minWidth 占满剩余宽度,并在 colResizable 中禁用其拖拽。 */
|
||||
/** 权限点表格列配置 */
|
||||
const powerColumns = shallowRef([
|
||||
{
|
||||
dataIndex: 'b_code',
|
||||
@@ -876,7 +905,7 @@ const powerColumns = shallowRef([
|
||||
{
|
||||
dataIndex: 'b_name',
|
||||
title: '权限名称',
|
||||
minWidth: 150,
|
||||
width: 180,
|
||||
editor: { type: 'input' },
|
||||
},
|
||||
{
|
||||
@@ -911,24 +940,6 @@ const rowSelection = computed(() => ({
|
||||
},
|
||||
}))
|
||||
|
||||
/** 访问权限展示(派生自菜单,跟随 b_power_code / b_name / b_canuse,不可编辑) */
|
||||
const accessPowerDesc = computed(() => {
|
||||
const menu = drafts.value?.menu
|
||||
if (!menu) return ''
|
||||
return menu.b_power_code || `menu.${menu.b_code || ''}`
|
||||
})
|
||||
|
||||
/** 根据菜单编码自动生成访问权限编码 */
|
||||
function autoPowerCode() {
|
||||
if (!drafts.value?.menu) return
|
||||
const code = (drafts.value.menu.b_code || '').trim()
|
||||
if (!code) {
|
||||
Message.warning('请先填写菜单编码')
|
||||
return
|
||||
}
|
||||
setField('b_power_code', `menu.${code}`)
|
||||
}
|
||||
|
||||
/** 新增操作/按钮权限点 */
|
||||
async function addPowerRow() {
|
||||
if (powersLoading.value) return
|
||||
@@ -972,9 +983,9 @@ const selectedLibCodes = ref([])
|
||||
const libTableRef = ref(null)
|
||||
|
||||
const powerLibraryColumns = shallowRef([
|
||||
// 列宽铺满约定同 powerColumns:编码固定宽,名称弹性占满
|
||||
{ dataIndex: 'b_code', title: '权限编码', width: 100 },
|
||||
{ dataIndex: 'b_name', title: '权限名称', minWidth: 80 },
|
||||
// 权限库按容器自然分配列宽,不提供固定宽度,也不启用列宽拖拽
|
||||
{ dataIndex: 'b_code', title: '权限编码' },
|
||||
{ dataIndex: 'b_name', title: '权限名称' },
|
||||
])
|
||||
|
||||
const libRowSelection = computed(() => ({
|
||||
@@ -1037,7 +1048,7 @@ function importSelectedPowers() {
|
||||
Message.success(`已导入 ${added.length} 条权限`)
|
||||
}
|
||||
|
||||
/* ================= 加载模块列表(供 data 类型下拉选择) ================= */
|
||||
/* ================= 加载模块列表(供页面类型下拉选择) ================= */
|
||||
const moduleOptions = ref([])
|
||||
let moduleOptionsLoaded = false
|
||||
|
||||
@@ -1198,112 +1209,96 @@ loadPowerLibrary()
|
||||
<div class="mm-pane__title">菜单配置</div>
|
||||
</div>
|
||||
<Form
|
||||
ref="formRef"
|
||||
:model="drafts.menu"
|
||||
:rules="{
|
||||
b_code: [{ required: true, message: '请填写菜单编码' }],
|
||||
b_name: [{ required: true, message: '请填写菜单名称' }],
|
||||
b_menu_type: [{ required: true, message: '请选择菜单类型' }],
|
||||
b_route:
|
||||
isPage || isExternal
|
||||
? [{ required: true, message: '请填写路由地址' }]
|
||||
ref="formRef"
|
||||
:model="drafts.menu"
|
||||
:rules="{
|
||||
b_code: [{ required: true, message: '请填写菜单编码' }],
|
||||
b_name: [{ required: true, message: '请填写菜单名称' }],
|
||||
b_menu_type: [{ required: true, message: '请选择菜单类型' }],
|
||||
b_route:
|
||||
isPage || isExternal
|
||||
? [{ required: true, message: '请填写路由地址' }]
|
||||
: [],
|
||||
b_module_id: isPage
|
||||
? [{ required: true, message: '请选择绑定模块' }]
|
||||
: [],
|
||||
b_module_id: isData ? [{ required: true, message: '请选择绑定模块' }] : [],
|
||||
}"
|
||||
layout="vertical"
|
||||
:gap="20"
|
||||
>
|
||||
<FormItem label="菜单名称" name="b_name" required>
|
||||
<div class="mm-name-field">
|
||||
}"
|
||||
layout="vertical"
|
||||
:gap="20"
|
||||
>
|
||||
<FormItem label="菜单名称" name="b_name" required>
|
||||
<div class="mm-name-field">
|
||||
<Input
|
||||
class="mm-name-field__input"
|
||||
:model-value="drafts.menu.b_name"
|
||||
@update:model-value="(v) => setField('b_name', v)"
|
||||
/>
|
||||
<Button
|
||||
type="ghost"
|
||||
class="mm-name-field__i18n"
|
||||
title="多语言名称"
|
||||
@click="i18nModalVisible = true"
|
||||
>
|
||||
<Languages :size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem label="菜单编码" name="b_code" required>
|
||||
<Input
|
||||
class="mm-name-field__input"
|
||||
:model-value="drafts.menu.b_name"
|
||||
@update:model-value="(v) => setField('b_name', v)"
|
||||
:model-value="drafts.menu.b_code"
|
||||
@update:model-value="(v) => setField('b_code', v)"
|
||||
/>
|
||||
<Button
|
||||
type="ghost"
|
||||
class="mm-name-field__i18n"
|
||||
title="多语言名称"
|
||||
@click="i18nModalVisible = true"
|
||||
>
|
||||
<Languages :size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem label="菜单编码" name="b_code" required>
|
||||
<Input
|
||||
:model-value="drafts.menu.b_code"
|
||||
@update:model-value="(v) => setField('b_code', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="菜单类型" name="b_menu_type" required>
|
||||
<Select
|
||||
:model-value="drafts.menu.b_menu_type"
|
||||
:options="menuTypeOptions"
|
||||
@update:model-value="onMenuTypeChange"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="上级菜单" name="b_parent_id">
|
||||
<Input :model-value="parentMenuName || '根节点(顶级菜单)'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="图标" name="b_icon">
|
||||
<IconPicker
|
||||
:model-value="drafts.menu.b_icon"
|
||||
@update:model-value="(v) => setField('b_icon', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="排序号" name="b_xh">
|
||||
<Input
|
||||
:model-value="drafts.menu.b_xh"
|
||||
type="number"
|
||||
@update:model-value="(v) => setField('b_xh', Number(v) || 0)"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- page: 路由地址 -->
|
||||
<FormItem v-if="isPage" label="路由地址" name="b_route" required>
|
||||
<Input
|
||||
:model-value="drafts.menu.b_route"
|
||||
@update:model-value="(v) => setField('b_route', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- data: 绑定模块 -->
|
||||
<FormItem v-if="isData" label="绑定模块" name="b_module_id" required>
|
||||
<Select
|
||||
:model-value="drafts.menu.b_module_id"
|
||||
:options="moduleOptions"
|
||||
@update:model-value="(v) => setField('b_module_id', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- external: 外链地址 -->
|
||||
<FormItem v-if="isExternal" label="外链地址" name="b_route" required>
|
||||
<Input
|
||||
:model-value="drafts.menu.b_route"
|
||||
@update:model-value="(v) => setField('b_route', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="启用" name="b_canuse">
|
||||
<Switch
|
||||
:model-value="Number(drafts.menu.b_canuse) === 1"
|
||||
@update:model-value="(v) => setField('b_canuse', v ? 1 : 0)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem v-if="!isDirectory" label="访问权限编码" name="b_power_code">
|
||||
<div class="mm-power-code-field">
|
||||
</FormItem>
|
||||
<FormItem label="菜单类型" name="b_menu_type" required>
|
||||
<Select
|
||||
:model-value="drafts.menu.b_menu_type"
|
||||
:options="menuTypeOptions"
|
||||
:disabled="menuTypeLocked || busy"
|
||||
@update:model-value="onMenuTypeChange"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="上级菜单" name="b_parent_id">
|
||||
<Input :model-value="parentMenuName || '根节点(顶级菜单)'" readonly />
|
||||
</FormItem>
|
||||
<!-- page/external: 路由地址 -->
|
||||
<FormItem v-if="isPage" label="路由地址" name="b_route" required>
|
||||
<Input
|
||||
class="mm-power-code-field__input"
|
||||
:model-value="drafts.menu.b_power_code"
|
||||
@update:model-value="(v) => setField('b_power_code', v)"
|
||||
:model-value="drafts.menu.b_route"
|
||||
@update:model-value="(v) => setField('b_route', v)"
|
||||
/>
|
||||
<Button type="outline" size="small" @click="autoPowerCode">
|
||||
<RefreshCw :size="12" /> 自动生成
|
||||
</Button>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</FormItem>
|
||||
<FormItem v-if="isExternal" label="外链地址" name="b_route" required>
|
||||
<Input
|
||||
:model-value="drafts.menu.b_route"
|
||||
@update:model-value="(v) => setField('b_route', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- page: 绑定模块 -->
|
||||
<FormItem v-if="isPage" label="绑定模块" name="b_module_id" required>
|
||||
<Select
|
||||
:model-value="drafts.menu.b_module_id"
|
||||
:options="moduleOptions"
|
||||
@update:model-value="(v) => setField('b_module_id', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="图标" name="b_icon">
|
||||
<IconPicker
|
||||
:model-value="drafts.menu.b_icon"
|
||||
@update:model-value="(v) => setField('b_icon', v)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="排序号" name="b_xh">
|
||||
<Input
|
||||
:model-value="drafts.menu.b_xh"
|
||||
type="number"
|
||||
@update:model-value="(v) => setField('b_xh', Number(v) || 0)"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</SplitterPanel>
|
||||
|
||||
<!-- 权限面板:仅 page/data/external(目录无权限) -->
|
||||
<!-- 权限面板:仅页面/外链(目录无权限) -->
|
||||
<SplitterPanel v-if="!isDirectory">
|
||||
<div class="mm-power-card">
|
||||
<div class="mm-power-card__head">
|
||||
@@ -1338,7 +1333,7 @@ loadPowerLibrary()
|
||||
:editable="true"
|
||||
row-key="b_id"
|
||||
:row-selection="rowSelection"
|
||||
:col-resizable="{ disabled: (col) => col.dataIndex === 'b_name' }"
|
||||
:col-resizable="true"
|
||||
@change="() => {}"
|
||||
/>
|
||||
</div>
|
||||
@@ -1380,7 +1375,7 @@ loadPowerLibrary()
|
||||
:seq="false"
|
||||
row-key="b_code"
|
||||
:row-selection="libRowSelection"
|
||||
:col-resizable="{ disabled: (col) => col.dataIndex === 'b_name' }"
|
||||
:auto-fit-width="true"
|
||||
@change="() => {}"
|
||||
/>
|
||||
</div>
|
||||
@@ -1624,18 +1619,6 @@ loadPowerLibrary()
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 访问权限编码行 */
|
||||
.mm-power-code-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mm-power-code-field__input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ---- 右侧主区 ---- */
|
||||
.mm-main {
|
||||
display: flex;
|
||||
|
||||
Reference in new issue
Block a user