20260826173130
This commit is contained in:
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;
|
||||
|
||||
+90
-8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1051
-789
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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user