20260826173130

This commit is contained in:
oneao committed 2026-08-26 17:31:31 +08:00
1 parent 9957757b5d
commit 0bbbbd3ecc
15 files changed
+2472 -874

No files matched your search

@@ -149,9 +149,9 @@ const totalCells = computed(() => columns.value.length * dataSource.value.length
display: flex;
flex-direction: column;
gap: 12px;
/* 页面高度 = fms-content 可视内容高度(100vh - 顶栏40 - 布局gap8 - 页签40 - 内容padding 12+16),
/* 高度 = 内容区可视高度(由布局实测写入 --fms-content-height,自动跟随顶栏/页签/padding),
让滚动条落在表格内部而不是整个页面滚动 */
height: calc(100vh - 116px);
height: var(--fms-content-height, calc(100vh - 116px));
color: var(--fms-text);
}
@@ -204,16 +204,16 @@ const totalCells = computed(() => columns.value.length * dataSource.value.length
flex-wrap: wrap;
}
/* ---- demo 卡片:工具栏 + 表格 ---- */
/* ---- demo 卡片:工具栏 + 表格(灰底画布上的白卡,靠阴影分层,无边框线) ---- */
.demo-card {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
overflow: hidden;
border: 1px solid var(--fms-border);
border-radius: 8px;
background: var(--fms-card);
box-shadow: var(--fms-shadow);
}
.demo-toolbar {
@@ -70,9 +70,9 @@ const counter = ref(0)
flex-direction: column;
gap: 16px;
padding: 16px;
border: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
background: var(--fms-card);
box-shadow: var(--fms-shadow);
}
.detail-hint {
@@ -193,9 +193,9 @@ function clearEvents() {
flex-direction: column;
gap: 10px;
padding: 12px;
border: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
background: var(--fms-card);
box-shadow: var(--fms-shadow);
&--grow {
flex: 1;
@@ -12,7 +12,7 @@ import Modal from '@/components/ui/modal/modal.vue'
import Button from '@/components/ui/button/button.vue'
import Switch from '@/components/ui/switch/switch.vue'
import Tree from '@/components/ui/tree/tree.vue'
import { Plus, Pencil, Trash2 } from '@lucide/vue'
import { Plus, Pencil, Trash2, ArrowUp, ArrowDown } from '@lucide/vue'
import { buildTree } from '@/utils/tree'
import { createTempId } from '@/utils/tempId'
import { cloneData, diffRows, allocateTemporaryIds, tableChange, hasChanges } from '@/utils/dataChanges'
@@ -112,6 +112,24 @@ function toggleLayout(node) {
node.b_layout_direction = node.b_layout_direction === 'horizontal' ? 'vertical' : 'horizontal'
}
/* 同级排序:上移/下移(交换同级顺序并重新编号 b_xh,随保存提交) */
function moveGroup(node, delta) {
if (!node) return
const parentId = node.b_parent_id == null ? null : String(node.b_parent_id)
const siblings = localGroups.value.filter(
(g) => (g.b_parent_id == null ? null : String(g.b_parent_id)) === parentId,
)
siblings.sort((a, b) => (a.b_xh || 0) - (b.b_xh || 0))
const idx = siblings.findIndex((g) => String(g.b_id) === String(node.b_id))
const target = idx + delta
if (idx < 0 || target < 0 || target >= siblings.length) return
const [moved] = siblings.splice(idx, 1)
siblings.splice(target, 0, moved)
siblings.forEach((g, i) => {
g.b_xh = (i + 1) * 10
})
}
/* 删除:有子分组 / 仍被引用时禁止 */
function deleteGroup(node) {
if (!node) return
@@ -168,6 +186,10 @@ async function save() {
@ok="save"
>
<div class="mm-groups">
<div class="mm-groups__head">
<div class="mm-groups__head-title">分组结构</div>
<span class="mm-groups__head-count">共 {{ localGroups.length }} 个分组</span>
</div>
<div class="mm-groups__add">
<input
ref="addInputRef"
@@ -216,6 +238,25 @@ async function save() {
{{ node.b_title || '未命名分组' }}
</span>
<span class="mm-groups__node-actions" @click.stop>
<button
type="button"
class="mm-groups__node-btn"
title="上移"
aria-label="上移分组"
@click="moveGroup(node, -1)"
>
<ArrowUp :size="13" />
</button>
<button
type="button"
class="mm-groups__node-btn"
title="下移"
aria-label="下移分组"
@click="moveGroup(node, 1)"
>
<ArrowDown :size="13" />
</button>
<span class="mm-groups__node-divider"></span>
<Switch
:model-value="Number(node.b_canuse) === 1"
title="启用/停用"
@@ -268,6 +309,26 @@ async function save() {
min-height: 0;
}
/* ---- 顶部摘要 ---- */
.mm-groups__head {
display: flex;
align-items: center;
justify-content: space-between;
flex: none;
padding: 2px 2px 0;
}
.mm-groups__head-title {
font-size: 13px;
font-weight: 600;
color: var(--fms-text);
}
.mm-groups__head-count {
font-size: 12px;
color: var(--fms-text-secondary);
}
/* ---- 顶部输入即新增 ---- */
.mm-groups__add {
display: flex;
@@ -310,20 +371,26 @@ async function save() {
flex: 1;
min-height: 0;
overflow: auto;
padding: 8px;
padding: 6px;
border: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
background: var(--fms-secondary-hover);
}
/* 节点行:名称撑满,操作靠右 */
/* 节点行:名称撑满,操作靠右;hover 时显示完整操作 */
.mm-groups__node {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
min-width: 0;
padding: 1px 0;
padding: 3px 6px;
border-radius: 6px;
transition: background 0.15s;
&:hover {
background: var(--fms-card);
}
}
.mm-groups__node-name {
@@ -346,26 +413,41 @@ async function save() {
flex: none;
}
/* 移动按钮与其它操作的分隔线 */
.mm-groups__node-divider {
width: 1px;
height: 14px;
margin: 0 2px;
background: var(--fms-border);
}
.mm-groups__node-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
width: 22px;
height: 22px;
padding: 0;
border: none;
border-radius: 4px;
border: 1px solid transparent;
border-radius: 5px;
background: transparent;
color: var(--fms-text-secondary);
cursor: pointer;
transition:
background 0.15s,
color 0.15s,
border-color 0.15s;
&:hover {
background: var(--fms-secondary);
color: var(--fms-text);
border-color: var(--fms-border);
}
&.is-danger:hover {
background: #fdecec;
color: #d95757;
border-color: #f5c6c6;
}
}
File diff suppressed because it is too large. Load diff
@@ -363,7 +363,11 @@ async function onSwitchSave() {
switchSaving.value = true
try {
const ok = await saveModule()
if (!ok) return
if (!ok) {
// 保存失败(原因已由 saveModule 内部提示):留在当前对象,弹窗不关闭,可重试或放弃
Message.warning('保存未完成,已留在当前对象,请处理后再切换')
return
}
moduleSelectedId.value = state.pendingId
switchConfirm.value = null
} finally {
@@ -527,6 +531,26 @@ async function deleteModule(id) {
if (powers.length) {
reqs.push({ table: 's_power', key_field: 'b_id', deletes: powers.map((r) => ({ b_id: r.b_id })) })
}
// 多语言(s_i18n 无 b_module_id 字段,需按模块编码匹配 b_key 前缀)。
// 若不清理,删除后残留的孤儿键会被同编码新模块的多语言面板加载,
// 进而产生指向已不存在记录的 UPDATE,导致后端「修改记录数异常: 0」。
const moduleCode = String(module.b_code || '').trim()
if (moduleCode) {
const prefix = module.b_module_type === 'category' ? 'menu' : 'module'
const i18nRes = await loadDataApi(
's_i18n',
`b_key = '${prefix}.${moduleCode}' OR b_key LIKE '${prefix}.${moduleCode}.%' ` +
`OR b_key LIKE 'field.${moduleCode}.%' OR b_key LIKE 'power.${moduleCode}.%'`,
)
const i18nRows = i18nRes.data || []
if (i18nRows.length) {
reqs.push({
table: 's_i18n',
key_field: 'b_id',
deletes: i18nRows.map((r) => ({ b_id: r.b_id })),
})
}
}
reqs.push({ table: 's_module', key_field: 'b_id', deletes: [{ b_id: id }] })
await saveObjectApi(reqs)
// 本地移除整棵子树(未保存子孙随父一并清理),并清除未保存标记
@@ -1003,7 +1027,6 @@ loadModuleTree()
<Tabs
v-if="drafts"
v-model="activeSection"
type="pill"
nav-only
class="mm-config-nav"
:items="configNavItems"
@@ -1235,9 +1258,17 @@ loadModuleTree()
min-height: 0;
}
/* Splitter 适配:面板内容各自内部滚动,避免双滚动条 */
/* Splitter 适配:
* - 面板内容各自内部滚动,避免双滚动条
* - 面板左右留 3px,两张白卡之间形成灰缝(3 + 分隔条 6 + 3 = 12px),无需边框线
* - 隐藏分隔条常显的 1px 线(hover 时的手柄仍会显示,可拖拽调宽) */
.mm-body-splitter :deep(> .fms-splitter-panel) {
overflow: hidden;
padding: 0 3px;
}
.mm-body-splitter :deep(.fms-splitter-bar::before) {
display: none;
}
/* ---- 左树顶部 header:搜索框在左,操作在右,无边框无标题 ---- */
@@ -1296,6 +1327,11 @@ loadModuleTree()
height: 100%;
min-height: 0;
padding: 8px;
/* 灰底画布上的白卡:白底 + 圆角 + 阴影,与右侧内容卡用灰缝分隔 */
background: var(--fms-card);
border-radius: var(--fms-control-radius);
box-shadow: var(--fms-shadow);
overflow: hidden;
}
.mm-tree-panel__scroll {
@@ -1350,6 +1386,10 @@ loadModuleTree()
min-width: 0;
min-height: 0;
overflow: hidden;
/* 灰底画布上的白卡:与左侧树卡用灰缝分隔 */
background: var(--fms-card);
border-radius: var(--fms-control-radius);
box-shadow: var(--fms-shadow);
}
.mm-main__empty {
@@ -1379,28 +1419,9 @@ loadModuleTree()
min-height: 0;
}
/* ---- 配置导航:横向胶囊式 Tabs,位于 header 内左侧,操作按钮贴右 ---- */
/* ---- 配置导航:横向胶囊式 Tabs,位于工作区顶部,操作按钮在 rightExtra 插槽 ---- */
/* ---- 配置导航:横向 Tab 导航(line 类型,不作内部覆盖,组件默认样式即可) ---- */
.mm-config-nav.tabs {
flex: none;
/* 与左侧树 header 的分割线对齐:左右两栏 header 同高同线,视觉统一 */
border-bottom: 1px solid var(--fms-border);
}
.mm-config-nav :deep(.tabs-nav) {
padding: 6px;
}
/* 标签列表由 Scrollbar 组件接管滚动,这里只保持垂直居中 */
.mm-config-nav :deep(.tabs-nav-list) {
align-items: center;
}
/* rightExtra 操作区与标签在同一行垂直居中 */
.mm-config-nav :deep(.tabs-nav-extra) {
margin-top: 0;
margin-bottom: 0;
padding-right: 10px;
}
/* 标签内布局:图标 + 文字 + badge */
+1 -1
View File
@@ -62,9 +62,9 @@ const index = computed(() => Number(route.params.id ?? 0))
.fms-test-page__card {
padding: 16px;
border: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
background: var(--fms-card);
box-shadow: var(--fms-shadow);
font-size: 13px;
line-height: 1.8;
}