From 7421019ef24dfbf77fd568934f8b1d9dfbdc0c86 Mon Sep 17 00:00:00 2001 From: oneao Date: Mon, 24 Aug 2026 22:54:22 +0800 Subject: [PATCH] 20260824225422 --- .../migrations/100_initialize_fms_new.sql | 3 + .../migrations/102_add_module_edit_mode.sql | 45 + .../components/ModuleBasicPanel.vue | 24 +- .../components/ModuleEditConfigPanel.vue | 1761 ++++++++++++++--- .../views/system/module-management/index.vue | 11 +- .../tests/views/module-management.spec.js | 767 ++++++- 6 files changed, 2210 insertions(+), 401 deletions(-) create mode 100644 code/fms/fms-api/config/migrations/102_add_module_edit_mode.sql diff --git a/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql b/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql index f9a98b68..4813c15c 100644 --- a/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql +++ b/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql @@ -67,6 +67,7 @@ BEGIN b_keyfield varchar(50) NULL, b_orderfield varchar(500) NULL, b_delete_policy varchar(20) NULL, + b_edit_mode varchar(10) NULL, b_canuse int NOT NULL CONSTRAINT DF_s_module_canuse DEFAULT (1), b_xh int NOT NULL CONSTRAINT DF_s_module_xh DEFAULT (0), b_bz nvarchar(1000) NULL, @@ -78,6 +79,7 @@ BEGIN CONSTRAINT UQ_s_module_code UNIQUE (b_code), CONSTRAINT CK_s_module_type CHECK (b_module_type IN ('category','data','query')), CONSTRAINT CK_s_module_delete_policy CHECK (b_delete_policy IS NULL OR b_delete_policy IN ('restrict','lifecycle','hard')), + CONSTRAINT CK_s_module_edit_mode CHECK (b_edit_mode IS NULL OR b_edit_mode IN ('form','table')), CONSTRAINT CK_s_module_config CHECK ( (b_module_type = 'category' AND b_viewtable IS NULL AND b_savetable IS NULL AND b_keyfield IS NULL AND b_orderfield IS NULL AND b_delete_policy IS NULL) OR (b_module_type = 'data' AND b_viewtable IS NOT NULL AND b_savetable IS NOT NULL AND b_keyfield IS NOT NULL AND b_delete_policy IS NOT NULL) @@ -172,6 +174,7 @@ BEGIN b_colspan int NOT NULL CONSTRAINT DF_s_field_edit_colspan DEFAULT (12), b_height int NULL, b_placeholder nvarchar(200) NULL, + b_width int NULL, b_xh int NOT NULL CONSTRAINT DF_s_field_edit_xh DEFAULT (0), CONSTRAINT PK_s_field_edit PRIMARY KEY (b_id), CONSTRAINT UQ_s_field_edit_field UNIQUE (b_module_id, b_field_id), diff --git a/code/fms/fms-api/config/migrations/102_add_module_edit_mode.sql b/code/fms/fms-api/config/migrations/102_add_module_edit_mode.sql new file mode 100644 index 00000000..4a431db9 --- /dev/null +++ b/code/fms/fms-api/config/migrations/102_add_module_edit_mode.sql @@ -0,0 +1,45 @@ +/* + 支持模块「编辑方式」: + 1. s_module.b_edit_mode —— 数据模块的编辑形态: + form (主表):单条记录以表单形式编辑(配合 s_field_edit 的分组 / 栅格 / 占位符设计) + table (子表):作为子表以可编辑表格(grid)编辑(配合 s_field_edit 的列顺序 / 列宽设计) + 每个 data 模块只能存在一种编辑方式,默认 form。 + 2. s_field_edit.b_width —— 子表(表格)模式下列的宽度(px),仅表格编辑方式使用。 + 可重复执行:列与约束均通过存在性判断避免重复。 +*/ +SET NOCOUNT ON; + +-- 1. s_module.b_edit_mode +IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'dbo.s_module') + AND name = N'b_edit_mode' +) +BEGIN + ALTER TABLE dbo.s_module + ADD b_edit_mode varchar(10) NULL; +END; + +IF NOT EXISTS ( + SELECT 1 + FROM sys.check_constraints + WHERE parent_object_id = OBJECT_ID(N'dbo.s_module') + AND name = N'CK_s_module_edit_mode' +) +BEGIN + ALTER TABLE dbo.s_module + ADD CONSTRAINT CK_s_module_edit_mode CHECK (b_edit_mode IS NULL OR b_edit_mode IN ('form', 'table')); +END; + +-- 2. s_field_edit.b_width +IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'dbo.s_field_edit') + AND name = N'b_width' +) +BEGIN + ALTER TABLE dbo.s_field_edit + ADD b_width int NULL; +END; diff --git a/code/fms/fms-vue/src/views/system/module-management/components/ModuleBasicPanel.vue b/code/fms/fms-vue/src/views/system/module-management/components/ModuleBasicPanel.vue index dcc2560d..e9572a66 100644 --- a/code/fms/fms-vue/src/views/system/module-management/components/ModuleBasicPanel.vue +++ b/code/fms/fms-vue/src/views/system/module-management/components/ModuleBasicPanel.vue @@ -119,10 +119,21 @@ function onI18nSaved(key) {
点击画布中的字段卡片查看并编辑属性
@@ -2062,7 +2702,7 @@ onBeforeUnmount(() => {
{ margin-bottom: 10px; } +.mm-edit__mode { + display: inline-flex; + align-items: center; + gap: 8px; +} + .mm-edit__view-switch { display: inline-flex; align-items: center; @@ -2139,9 +2785,84 @@ onBeforeUnmount(() => { gap: 10px; } -.mm-edit__hint { +/* ---- 快捷键帮助(右上角 ? 图标,hover 展示) ---- */ +.mm-edit__help { + position: relative; + display: inline-flex; + align-items: center; + align-self: center; + + &:hover .mm-edit__help-pop, + &:focus-within .mm-edit__help-pop { + display: block; + } +} + +.mm-edit__help-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: 1px solid var(--fms-border); + border-radius: 50%; + background: var(--fms-card); color: var(--fms-text-secondary); font-size: 12px; + font-weight: 700; + line-height: 1; + padding-bottom: 1px; /* 问号字形重心略偏上,下移 1px 视觉居中 */ + cursor: help; +} + +.mm-edit__help-pop { + display: none; + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 50; + width: 230px; + padding: 10px 12px; + border: 1px solid var(--fms-border); + border-radius: 8px; + background: var(--fms-card); + box-shadow: 0 6px 20px rgba(30, 45, 64, 0.14); +} + +.mm-edit__help-title { + margin-bottom: 6px; + color: var(--fms-text); + font-size: 12px; + font-weight: 600; +} + +.mm-edit__help-list { + margin: 0; + padding: 0; + list-style: none; + + li { + display: flex; + align-items: center; + gap: 8px; + padding: 3px 0; + color: var(--fms-text-secondary); + font-size: 11px; + } + + kbd { + flex: none; + min-width: 76px; + padding: 1px 6px; + border: 1px solid var(--fms-border); + border-bottom-width: 2px; + border-radius: 4px; + background: var(--fms-secondary); + color: var(--fms-text); + font-size: 10px; + font-family: inherit; + text-align: center; + } } /* ---- 撤销提示条 ---- */ @@ -2348,14 +3069,6 @@ onBeforeUnmount(() => { } /* 同行配对进行中的提示条 */ -.mm-fdesign__pair-hint { - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fms-primary) 12%, transparent); - color: var(--fms-primary); - font-weight: 600; -} - .mm-fdesign__sheet { position: relative; /* 拖拽行高亮框的绝对定位参照 */ /* 纸面宽度自适应:宽屏时 90% 上限 960px,窄屏时跟随容器尽量占满, @@ -2543,12 +3256,6 @@ onBeforeUnmount(() => { opacity: 0.55; } - /* 同行配对第一选择:虚线边框 + 外发光,提示等待第二个字段 */ - &.is-pairing { - border: 1px dashed var(--fms-primary); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--fms-primary) 18%, transparent); - } - /* ---- 拖拽插入方向高亮:目标卡片对应侧边框高亮(阿里式 inset 阴影,不撑布局) ---- */ &.is-drop-before { box-shadow: inset 2px 0 0 0 var(--fms-primary); @@ -2904,6 +3611,12 @@ textarea.mm-fdesign__ctrl { } } +/* 栅格宽度:去掉下拉后数字输入框即主控件,填充剩余宽度(列宽控件仍用下拉,不受影响) */ +.mm-fdesign__span-control input#mm-fdesign-colspan { + flex: 1; + width: auto; +} + .mm-fdesign__prop-static { display: flex; align-items: center; @@ -2986,15 +3699,6 @@ textarea.mm-fdesign__ctrl { } } -.mm-fdesign__inspector-foot { - margin-top: 14px; - padding-top: 10px; - border-top: 1px solid var(--fms-border); - color: var(--fms-text-secondary); - font-size: 11px; - line-height: 1.6; -} - .mm-fdesign__delete { margin-top: 12px; width: 100%; @@ -3179,90 +3883,539 @@ textarea.mm-fdesign__ctrl { } } -.mm-fdesign__span-usage { - display: block; - margin-top: 4px; - color: var(--fms-text-secondary); - font-size: 10px; +/* ---- 表格(子表)设计:列画布 ---- */ +.mm-tdesign__sheet { + display: flex; + flex-direction: column; + gap: 12px; + padding: 14px 16px; + border: 1px solid var(--fms-border); + border-radius: var(--fms-control-radius); + background: var(--fms-card); + box-shadow: 0 3px 14px rgba(39, 54, 70, 0.06); +} - &.is-over { - color: #d95757; - font-weight: 600; +/* 工具栏 */ +.mm-tdesign__toolbar { + display: flex; + align-items: center; + gap: 12px; +} + +.mm-tdesign__toolbar-title { + color: var(--fms-text); + font-size: 12px; + + strong { + color: var(--fms-primary); + font-variant-numeric: tabular-nums; } } -/* ---- 属性面板操作区:复制 / 粘贴 / 同行配对 ---- */ -.mm-fdesign__clip-row { - display: grid; - grid-template-columns: 1fr 1fr; +.mm-tdesign__toolbar-hidden { + color: var(--fms-text-secondary); +} + +.mm-tdesign__toolbar-hint { + margin-left: auto; + color: var(--fms-text-secondary); + font-size: 11px; +} + +.mm-tdesign__preview-btn { + flex: none; + padding: 4px 12px; + border: 1px solid color-mix(in srgb, var(--fms-primary) 45%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--fms-primary) 8%, transparent); + color: var(--fms-primary); + font-size: 12px; + cursor: pointer; + transition: + background 0.15s, + border-color 0.15s; + + &:hover:not(:disabled) { + background: color-mix(in srgb, var(--fms-primary) 14%, transparent); + border-color: var(--fms-primary); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +/* 列画布分区(表头分组) */ +.mm-tdesign__section { + border: 1px solid var(--fms-border); + border-radius: 8px; + overflow: hidden; + + &.is-ungrouped { + border-style: dashed; + background: color-mix(in srgb, var(--fms-secondary) 35%, transparent); + } +} + +.mm-tdesign__section-title { + display: flex; + align-items: center; gap: 8px; - margin-top: 10px; + padding: 5px 10px; + background: var(--fms-secondary); + + .mm-tdesign__section-flag { + font-style: normal; + color: var(--fms-text-secondary); + font-size: 11px; + } } -.mm-fdesign__clip-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - height: 28px; - padding: 0 8px; - border: 1px solid var(--fms-border); - border-radius: 5px; - background: var(--fms-card); +.mm-tdesign__section-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; color: var(--fms-text); - font-size: 11px; - cursor: pointer; - - &:hover:not(:disabled) { - border-color: var(--fms-primary); - color: var(--fms-primary); - } - - &:disabled { - opacity: 0.45; - cursor: not-allowed; - } + font-size: 12px; + font-weight: 600; } -.mm-fdesign__clip-hint { - margin: 6px 0 0; +.mm-tdesign__section-count { + margin-left: auto; + flex: none; color: var(--fms-text-secondary); - font-size: 10px; - line-height: 1.5; + font-size: 11px; + font-variant-numeric: tabular-nums; } -.mm-fdesign__pair-btn { - display: inline-flex; +.mm-tdesign__section-drop-hint { + display: flex; align-items: center; justify-content: center; - gap: 4px; - width: 100%; - height: 28px; - margin-top: 8px; - border: 1px solid var(--fms-border); - border-radius: 5px; - background: var(--fms-card); - color: var(--fms-text); + min-height: 34px; + border: 1px dashed var(--fms-border-strong, #c8d2dc); + border-radius: 6px; + color: var(--fms-text-secondary); + font-size: 11px; +} + +.mm-tdesign__add-group { + flex: none; + padding: 3px 10px; + border: 1px dashed var(--fms-primary); + border-radius: 6px; + background: transparent; + color: var(--fms-primary); font-size: 11px; cursor: pointer; + white-space: nowrap; &:hover:not(:disabled) { - border-color: var(--fms-primary); - color: var(--fms-primary); - } - - &:disabled { - opacity: 0.45; - cursor: not-allowed; - } - - &.is-active { - border-color: var(--fms-primary); - color: var(--fms-primary); background: color-mix(in srgb, var(--fms-primary) 8%, transparent); } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } } +/* WYSIWYG 表格:横向滚动 + 冻结序号/操作列 */ +.mm-tdesign__table { + overflow: auto; + max-height: 240px; + border: 1px solid var(--fms-border); + border-radius: 6px; + background: #fff; + + &.is-resizing { + user-select: none; + } +} + +.mm-tdesign__row { + display: flex; + align-items: center; + min-width: max-content; + border-bottom: 1px solid var(--fms-border); + + &:last-child { + border-bottom: none; + } +} + +.mm-tdesign__head-row { + background: var(--fms-secondary); +} + +/* 预览第一级表头:分组格(跨列合并 = 组内列宽之和) */ +.mm-tdesign__group-cell { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + height: 30px; + padding: 0 8px; + border-right: 1px solid var(--fms-border); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: var(--fms-text); + font-size: 12px; + font-weight: 600; + user-select: none; + + &.is-unnamed { + color: var(--fms-text-secondary); + font-weight: 400; + } +} + +.mm-tdesign__cols { + display: flex; + align-items: center; + min-width: max-content; +} + +/* 冻结列(横向滚动时保持固定) */ +.mm-tdesign__frozen { + flex: none; + position: sticky; + z-index: 6; + display: inline-flex; + align-items: center; + justify-content: center; + height: 100%; + font-size: 11px; + color: var(--fms-text-secondary); +} + +.mm-tdesign__head-row .mm-tdesign__frozen { + background: var(--fms-secondary); +} + +.mm-tdesign__row:not(.mm-tdesign__head-row) .mm-tdesign__frozen { + background: #fff; +} + +.mm-tdesign__frozen-left { + left: 0; + width: 44px; + min-width: 44px; + padding: 0 6px; + border-right: 1px solid var(--fms-border); +} + +.mm-tdesign__frozen-right { + right: 0; + min-width: 56px; + padding: 0 8px; + border-left: 1px solid var(--fms-border); +} + +/* 表头单元格 */ +.mm-tdesign__head-cell { + position: relative; + flex: none; + display: inline-flex; + align-items: center; + gap: 4px; + height: 34px; + padding: 0 6px; + border-right: 1px solid var(--fms-border); + font-size: 12px; + font-weight: 600; + color: var(--fms-text); + white-space: nowrap; + overflow: hidden; + cursor: pointer; + user-select: none; + transition: background 0.15s; + + &:hover { + background: color-mix(in srgb, var(--fms-primary) 6%, transparent); + } + + &.is-selected { + background: color-mix(in srgb, var(--fms-primary) 12%, transparent); + box-shadow: inset 0 -2px 0 var(--fms-primary); + } + + &.is-hidden { + opacity: 0.45; + } + + &.is-resizing-col { + background: color-mix(in srgb, var(--fms-primary) 10%, transparent); + } +} + +.mm-tdesign__head-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mm-tdesign__head-width { + flex: none; + color: var(--fms-text-secondary); + font-size: 10px; + font-weight: 400; + font-variant-numeric: tabular-nums; + opacity: 0.9; +} + +/* 列宽拖拽手柄:悬停在列右缘,按下后左右拖拽实时调整 b_width */ +.mm-tdesign__resize-handle { + position: absolute; + top: 0; + right: -3px; + z-index: 3; + width: 9px; + height: 100%; + cursor: col-resize; + touch-action: none; +} + +.mm-tdesign__resize-handle::after { + content: ''; + position: absolute; + top: 25%; + right: 4px; + width: 1px; + height: 50%; + background: transparent; + transition: background 0.15s ease; +} + +.mm-tdesign__resize-handle:hover::after, +.mm-tdesign__resize-handle:focus-visible::after, +.mm-tdesign__head-cell.is-resizing-col .mm-tdesign__resize-handle::after { + background: var(--fms-primary); +} + +.mm-tdesign__resize-handle:focus-visible { + outline: 2px solid color-mix(in srgb, var(--fms-primary) 55%, transparent); + outline-offset: -2px; + border-radius: 4px; +} + +.mm-tdesign__row-op { + padding: 2px 10px; + border-radius: 4px; + background: color-mix(in srgb, var(--fms-primary) 10%, transparent); + color: var(--fms-primary); + font-size: 11px; + white-space: nowrap; +} + +/* 预览弹窗内容 */ +.mm-tdesign__preview-wrap { + padding: 8px 0; + min-height: 120px; + + .mm-tdesign__table { + max-height: 60vh; + } +} + +/* 预览表格:数据单元格与样例控件外观 */ +.mm-tdesign__cell { + flex: none; + display: inline-flex; + align-items: center; + height: 40px; + padding: 4px 10px; + border-right: 1px solid var(--fms-border); + overflow: hidden; + + &.is-hidden { + opacity: 0.4; + text-decoration: line-through; + } +} + +.mm-tdesign__sample { + display: inline-flex; + align-items: center; + width: 100%; + height: 24px; + padding: 0 8px; + border: 1px solid var(--fms-border); + border-radius: 4px; + background: #fbfcfd; + color: var(--fms-text-secondary); + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-variant-numeric: tabular-nums; + + &.is-select { + position: relative; + padding-right: 22px; + + &::after { + content: ''; + position: absolute; + right: 9px; + top: 50%; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--fms-text-secondary); + transform: translateY(-45%); + } + } + + &.is-money { + justify-content: flex-end; + font-weight: 600; + color: var(--fms-text); + } + + &.is-checkbox { + border: none; + background: transparent; + color: var(--fms-primary); + font-size: 13px; + } + + &.is-textarea { + height: 34px; + align-items: flex-start; + padding-top: 6px; + } + + &.is-desc { + border: 1px dashed var(--fms-border-strong, #c8d2dc); + background: transparent; + font-style: italic; + } +} + +.mm-tdesign__row-op { + padding: 2px 10px; + border-radius: 4px; + background: color-mix(in srgb, var(--fms-primary) 10%, transparent); + color: var(--fms-primary); + font-size: 11px; + white-space: nowrap; +} + +/* 纵向列卡片列表(设计主操作区) */ +.mm-tdesign__list { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 60px; + padding: 8px; + + &.is-empty { + min-height: 0; + } +} + +.mm-tdesign__col { + display: flex; + align-items: center; + gap: 8px; + min-height: 40px; + padding: 5px 8px; + border: 1px solid transparent; + border-radius: 6px; + background: var(--fms-card); + cursor: grab; + user-select: none; + -webkit-user-select: none; + transition: + border-color 0.15s, + box-shadow 0.15s; + + &:hover { + border-color: color-mix(in srgb, var(--fms-primary) 45%, transparent); + } + + &.is-selected { + border-style: dashed; + border-color: var(--fms-primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--fms-primary) 16%, transparent); + } + + &.is-invisible { + opacity: 0.55; + } + + .mm-fdesign__field-name { + flex: 0 1 auto; + } +} + +.mm-tdesign__drag-handle { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + color: var(--fms-text-secondary); + cursor: grab; +} + +.mm-tdesign__col-meta { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: auto; + flex: none; +} + +.mm-tdesign__col-type { + padding: 0 6px; + border-radius: 4px; + background: var(--fms-secondary); + color: var(--fms-text-secondary); + font-size: 10px; + line-height: 16px; +} + +.mm-tdesign__col-width { + color: var(--fms-text-secondary); + font-size: 11px; + font-variant-numeric: tabular-nums; +} + +.mm-tdesign__col-del { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: none; + border-radius: 5px; + background: transparent; + color: var(--fms-text-disabled, #9ca3af); + cursor: pointer; + + &:hover { + background: #fdecec; + color: #d95757; + } +} + +.mm-tdesign__empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 56px; + border-top: 1px dashed var(--fms-border-strong, #c8d2dc); + color: var(--fms-text-secondary); + font-size: 12px; +}