20260822171825
This commit is contained in:
1 parent
c43b4c8364
commit
27a32b313d
187 files changed
+34869
-4741
No files matched your search
@@ -94,6 +94,45 @@ const barStyles = computed(() =>
|
||||
}),
|
||||
)
|
||||
|
||||
/* ---------- 拖拽移动(固定浮层可能遮挡顶栏操作,允许用户拖开) ---------- */
|
||||
const rootEl = ref(null)
|
||||
// 拖动后的 left/top 坐标;null 表示未拖过,使用默认定位(右上角)
|
||||
const pos = ref(null)
|
||||
let dragStart = null // { pointerX, pointerY, originX, originY, width, height }
|
||||
|
||||
function onPointerDown(e) {
|
||||
// 重置按钮的点击不触发拖拽
|
||||
if (e.target.closest('.fps-monitor__reset')) return
|
||||
const rect = rootEl.value.getBoundingClientRect()
|
||||
dragStart = {
|
||||
pointerX: e.clientX,
|
||||
pointerY: e.clientY,
|
||||
originX: pos.value ? pos.value.x : rect.left,
|
||||
originY: pos.value ? pos.value.y : rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
window.addEventListener('pointermove', onPointerMove)
|
||||
window.addEventListener('pointerup', onPointerUp)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function onPointerMove(e) {
|
||||
if (!dragStart) return
|
||||
const dx = e.clientX - dragStart.pointerX
|
||||
const dy = e.clientY - dragStart.pointerY
|
||||
// 边界限制:不拖出视口
|
||||
const x = Math.max(0, Math.min(dragStart.originX + dx, window.innerWidth - dragStart.width))
|
||||
const y = Math.max(0, Math.min(dragStart.originY + dy, window.innerHeight - dragStart.height))
|
||||
pos.value = { x, y }
|
||||
}
|
||||
|
||||
function onPointerUp() {
|
||||
dragStart = null
|
||||
window.removeEventListener('pointermove', onPointerMove)
|
||||
window.removeEventListener('pointerup', onPointerUp)
|
||||
}
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
onMounted(() => {
|
||||
resetSampleWindow()
|
||||
@@ -108,7 +147,15 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="fps-monitor" aria-label="FPS 性能监控" v-bind="$attrs">
|
||||
<aside
|
||||
ref="rootEl"
|
||||
class="fps-monitor"
|
||||
:class="{ 'is-dragging': dragStart }"
|
||||
:style="pos ? { left: pos.x + 'px', top: pos.y + 'px' } : undefined"
|
||||
aria-label="FPS 性能监控"
|
||||
v-bind="$attrs"
|
||||
@pointerdown="onPointerDown"
|
||||
>
|
||||
<div class="fps-monitor__header">
|
||||
<span class="fps-monitor__label">FPS</span>
|
||||
<strong class="fps-monitor__current" :style="{ color: currentFpsColor }">
|
||||
@@ -156,10 +203,18 @@ onBeforeUnmount(() => {
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0;
|
||||
user-select: none;
|
||||
cursor: move;
|
||||
touch-action: none;
|
||||
background: var(--fms-card);
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: var(--fms-control-radius);
|
||||
box-shadow: var(--fms-popover-shadow);
|
||||
transition: box-shadow 150ms ease;
|
||||
|
||||
&.is-dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
.fps-monitor__header {
|
||||
|
||||
@@ -591,7 +591,6 @@ defineExpose({
|
||||
<template v-else #empty>
|
||||
<div class="fms-table__empty" role="status">
|
||||
<strong>暂无数据</strong>
|
||||
<span>当前没有可显示的内容</span>
|
||||
</div>
|
||||
</template>
|
||||
</StkTable>
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
/* 4px 内边距为聚焦外发光(box-shadow 3px)留出空间,避免被 overflow 裁剪 */
|
||||
padding: 4px;
|
||||
color: var(--fms-text);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
|
||||
// 输入态判断:焦点在 input / textarea / 可编辑区域
|
||||
function isTypingTarget(e) {
|
||||
const el = e.target
|
||||
return !!el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable)
|
||||
}
|
||||
|
||||
// 快捷键匹配:精确匹配(未声明的修饰符按下则不匹配)
|
||||
function matches(e, s) {
|
||||
if (e.key.toLowerCase() !== String(s.key).toLowerCase()) return false
|
||||
// 主修饰键:声明了 mod / ctrl / meta 中任一 → 要求 Ctrl 或 Meta 按下
|
||||
const needsMod = s.mod === true || s.ctrl === true || s.meta === true
|
||||
const modPressed = e.ctrlKey || e.metaKey
|
||||
if (needsMod !== modPressed) return false
|
||||
// alt / shift 精确匹配(未声明即要求未按下)
|
||||
if (!!s.alt !== e.altKey) return false
|
||||
if (!!s.shift !== e.shiftKey) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局键盘快捷键注册(可组合、可复用)。
|
||||
*
|
||||
* 用法(在任意组件 setup 顶层调用):
|
||||
* useHotkeys([
|
||||
* { key: 's', mod: true, ignoreWhenTyping: false, handler: save }, // Ctrl/Cmd + S
|
||||
* { key: '/', handler: focusSearch }, // 单独键(无修饰符)
|
||||
* { key: 'r', alt: true, handler: reload }, // Alt + R
|
||||
* ])
|
||||
*
|
||||
* 修饰符语义(精确匹配,未声明的修饰符按下则不匹配):
|
||||
* mod —— 主修饰键:Ctrl 或 Meta(Cmd)任一(跨平台主修饰键)
|
||||
* ctrl / meta / alt / shift —— 单独指定(与 mod 可叠加,如 { key: 's', mod: true, shift: true })
|
||||
*
|
||||
* 其他选项:
|
||||
* ignoreWhenTyping(默认 true)—— 焦点在 input / textarea / 可编辑区域时忽略;
|
||||
* 组合保存类快捷键(如 Ctrl+S)通常传 false,保证输入时也能保存。
|
||||
* handler(e) —— 触发回调,默认已 preventDefault 阻止浏览器默认行为。
|
||||
*
|
||||
* 生命周期:onMounted 注册、onBeforeUnmount 卸载,组件销毁后自动解绑。
|
||||
*/
|
||||
export function useHotkeys(shortcuts) {
|
||||
function onKeydown(e) {
|
||||
for (const s of shortcuts) {
|
||||
if (!matches(e, s)) continue
|
||||
if (s.ignoreWhenTyping !== false && isTypingTarget(e)) continue
|
||||
e.preventDefault()
|
||||
s.handler(e)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('keydown', onKeydown))
|
||||
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
||||
}
|
||||
@@ -406,8 +406,8 @@ onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFulls
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--fms-primary) 12%, transparent);
|
||||
color: var(--fms-primary);
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
--fms-primary-active: color-mix(in srgb, var(--fms-primary) 76%, #000);
|
||||
|
||||
/* 字体 */
|
||||
--fms-font-size: 14px;
|
||||
--fms-line-height: 20px;
|
||||
--fms-font-size: 13px;
|
||||
--fms-line-height: 1.4;
|
||||
|
||||
/* 控件 */
|
||||
--fms-control-height: 32px;
|
||||
--fms-control-height: 28px;
|
||||
--fms-control-padding-x: 10px;
|
||||
--fms-control-radius: 6px;
|
||||
--fms-control-size: 16px;
|
||||
|
||||
+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