-
(activeKey = String(row.b_id))"
- @area-selection-change="handleAreaSelectionChange"
+ @change="onCellChange"
/>
@@ -263,22 +239,4 @@ const columns = ref([
min-height: 0;
width: 100%;
}
-
-// 编辑态 input 样式
-:deep(.fms-editable-input) {
- width: 100%;
- height: 100%;
- border: 1px solid #1677ff;
- border-radius: 4px;
- padding: 0 8px;
- font-size: 13px;
- outline: none;
- box-sizing: border-box;
- background: #fff;
-
- &:focus {
- border-color: #1677ff;
- box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.12);
- }
-}
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
index 49dac577..8ab89d0b 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
@@ -32,16 +32,22 @@ const activeKey = ref(null);
watch(
() => props.queryConfig,
(val) => {
+ if (val === localQuery.value) return;
localQuery.value = val?.map((r) => ({ ...r })) || [];
},
{ immediate: true },
);
function emitUpdate() {
- emit(
- "update",
- localQuery.value.map((r) => ({ ...r })),
- );
+ emit("update", localQuery.value);
+}
+
+function onRowOrderChange(rows) {
+ localQuery.value = rows.map((row, index) => ({
+ ...row,
+ b_xh: (index + 1) * 10,
+ }));
+ emitUpdate();
}
const availableFields = computed(() =>
@@ -51,6 +57,7 @@ const availableFields = computed(() =>
);
function addRow() {
+ if (props.loading) return;
localQuery.value.push({
b_id: createTempId(),
b_module_id: props.moduleId,
@@ -65,6 +72,7 @@ function addRow() {
}
function deleteRow(record) {
+ if (props.loading) return;
localQuery.value = localQuery.value.filter((r) => r.b_id !== record.b_id);
emitUpdate();
}
@@ -130,16 +138,19 @@ const columns = shallowRef([
(activeKey = row.b_id)"
+ @row-order-change="onRowOrderChange"
/>
diff --git a/code/fms/fms-vue/src/views/module-management/index.vue b/code/fms/fms-vue/src/views/module-management/index.vue
index 1bcd7dfc..0ab3b45a 100644
--- a/code/fms/fms-vue/src/views/module-management/index.vue
+++ b/code/fms/fms-vue/src/views/module-management/index.vue
@@ -72,6 +72,13 @@ const drafts = ref({
const configLoading = ref(false);
const configSaving = ref(false);
const i18nPanelRef = ref(null);
+let configRequestId = 0;
+
+// ref 写在 v-for 内部会被 Vue 收集为数组,这里统一取单个组件实例
+function getI18nPanel() {
+ const r = i18nPanelRef.value;
+ return Array.isArray(r) ? r[0] : r;
+}
// 当前选中模块对象(从 drafts.module 获取)
const selectedModule = computed(() => drafts.value.module);
@@ -138,7 +145,7 @@ function buildTree(modules) {
async function loadTree() {
treeLoading.value = true;
try {
- const res = await loadDataApi("b_module", "b_canuse = 1", "b_xh ASC, b_id ASC");
+ const res = await loadDataApi("s_module", "b_canuse = 1", "b_xh ASC, b_id ASC");
treeData.value = buildTree(res.data || []);
} finally {
treeLoading.value = false;
@@ -146,19 +153,19 @@ async function loadTree() {
}
// ---- 模块配置加载 ----
-async function loadModuleConfig(moduleId) {
+async function loadModuleConfig(moduleId, requestId) {
configLoading.value = true;
try {
const id = String(moduleId);
const [moduleRes, fieldsRes, groupsRes, listRes, editRes, queryRes, powersRes] =
await Promise.all([
- loadDataApi("b_module", `b_id = ${id}`),
- loadDataApi("b_module_field", `b_module_id = ${id}`, "b_id ASC"),
- loadDataApi("b_module_field_group", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
- loadDataApi("b_module_field_list", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
- loadDataApi("b_module_field_edit", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
- loadDataApi("b_module_field_query", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
- loadDataApi("b_module_power", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
+ loadDataApi("s_module", `b_id = ${id}`),
+ loadDataApi("s_module_field", `b_module_id = ${id}`, "b_id ASC"),
+ loadDataApi("s_module_field_group", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
+ loadDataApi("s_module_field_list", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
+ loadDataApi("s_module_field_edit", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
+ loadDataApi("s_module_field_query", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
+ loadDataApi("s_module_power", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
]);
const original = {
@@ -170,11 +177,16 @@ async function loadModuleConfig(moduleId) {
queryConfig: queryRes.data || [],
powers: powersRes.data || [],
};
+ if (requestId !== configRequestId) return;
originals.value = original;
drafts.value = cloneData(original);
activeTab.value = "basic";
+ } catch (e) {
+ if (requestId === configRequestId) {
+ message.error("模块配置加载失败:" + (e.message || e));
+ }
} finally {
- configLoading.value = false;
+ if (requestId === configRequestId) configLoading.value = false;
}
}
@@ -186,12 +198,17 @@ function onTreeSelect(keys) {
}
watch(selectedModuleId, (id) => {
- if (!id) return;
- if (skipLoad.value) {
- skipLoad.value = false;
+ const requestId = ++configRequestId;
+ if (!id) {
+ configLoading.value = false;
return;
}
- loadModuleConfig(id);
+ if (skipLoad.value) {
+ skipLoad.value = false;
+ configLoading.value = false;
+ return;
+ }
+ void loadModuleConfig(id, requestId);
});
// ---- 子面板数据变更 ----
@@ -314,7 +331,7 @@ async function onDeleteNodeById(moduleId) {
return;
}
try {
- await saveObjectApi([{ table: "b_module", key_field: "b_id", deletes: [{ b_id: moduleId }] }]);
+ await saveObjectApi([{ table: "s_module", key_field: "b_id", deletes: [{ b_id: moduleId }] }]);
message.success("模块已删除");
if (selectedModuleId.value === moduleId) {
selectedModuleId.value = null;
@@ -334,13 +351,13 @@ async function saveAll() {
try {
const saveReqs = [];
- // b_module 主表
+ // s_module 主表
const moduleData = drafts.value.module;
const moduleOriginal = originals.value.module;
if (moduleData && JSON.stringify(moduleData) !== JSON.stringify(moduleOriginal)) {
const isNew = !moduleOriginal?.b_code;
saveReqs.push({
- table: "b_module",
+ table: "s_module",
key_field: "b_id",
[isNew ? "inserts" : "updates"]: [moduleData],
});
@@ -348,11 +365,11 @@ async function saveAll() {
// 子表:fields, listConfig, editConfig, queryConfig, powers
const subTables = [
- ["b_module_field", "fields"],
- ["b_module_field_list", "listConfig"],
- ["b_module_field_edit", "editConfig"],
- ["b_module_field_query", "queryConfig"],
- ["b_module_power", "powers"],
+ ["s_module_field", "fields"],
+ ["s_module_field_list", "listConfig"],
+ ["s_module_field_edit", "editConfig"],
+ ["s_module_field_query", "queryConfig"],
+ ["s_module_power", "powers"],
];
for (const [table, section] of subTables) {
const current = drafts.value[section] || [];
@@ -366,8 +383,9 @@ async function saveAll() {
}
// 多语言:由各子面板收集变更,统一在此保存
- if (i18nPanelRef.value) {
- const i18nReq = await i18nPanelRef.value.buildSaveRequest();
+ const i18nPanel = getI18nPanel();
+ if (i18nPanel) {
+ const i18nReq = await i18nPanel.buildSaveRequest();
if (i18nReq) saveReqs.push(i18nReq);
}
@@ -380,7 +398,7 @@ async function saveAll() {
// 更新 originals
originals.value = cloneData(drafts.value);
- i18nPanelRef.value?.markSaved();
+ getI18nPanel()?.markSaved();
message.success("保存成功");
await loadTree();
} catch (e) {
@@ -390,7 +408,7 @@ async function saveAll() {
}
}
-// ---- 字段同步 ----
+// ---- 字段同步与默认配置 ----
function typeFromSqlType(sqlType) {
if (!sqlType) return "input";
const t = sqlType.toLowerCase();
@@ -419,16 +437,109 @@ function typeFromSqlType(sqlType) {
return "input";
}
-async function syncFields() {
+function guessFieldWidth(fieldName) {
+ const n = (fieldName || "").toLowerCase();
+ if (n === "b_id" || n.endsWith("_id")) return 100;
+ if (n.includes("time") || n.includes("date") || n.includes("_at")) return 160;
+ if (n.includes("name")) return 150;
+ if (n.includes("code") || n.includes("type")) return 120;
+ if (n.includes("ip")) return 140;
+ if (n.includes("status") || n.includes("state") || n.includes("result")) return 90;
+ return 120;
+}
+
+function guessQueryComponent(b_type) {
+ const t = (b_type || "").toLowerCase();
+ if (t === "date" || t === "datetime") return "date-picker";
+ if (t === "select" || t === "switch") return "select";
+ if (t === "number" || t === "money") return "number-range";
+ return "input";
+}
+
+function guessQueryOperator(b_type) {
+ const t = (b_type || "").toLowerCase();
+ if (t === "datetime" || t === "number" || t === "money") return "between";
+ return "=";
+}
+
+function configuredFieldIds(section) {
+ return new Set(
+ [...(originals.value[section] || []), ...(drafts.value[section] || [])]
+ .map((row) => row.b_field_id)
+ .filter((id) => id != null)
+ .map(String),
+ );
+}
+
+function buildMissingDefaultConfigs(module, fields) {
+ const moduleId = String(module.b_id);
+ const listFieldIds = configuredFieldIds("listConfig");
+ const editFieldIds = configuredFieldIds("editConfig");
+ const queryFieldIds = configuredFieldIds("queryConfig");
+ const generated = { listConfig: [], editConfig: [], queryConfig: [] };
+
+ fields
+ .filter((field) => field.b_canuse === 1)
+ .forEach((field, index) => {
+ const fieldId = String(field.b_id);
+ const order = (index + 1) * 10;
+
+ if (!listFieldIds.has(fieldId)) {
+ generated.listConfig.push({
+ b_id: createTempId(),
+ b_module_id: moduleId,
+ b_field_id: fieldId,
+ b_group_id: null,
+ b_title: null,
+ b_i18n: field.b_i18n || null,
+ b_visible: 1,
+ b_xh: order,
+ b_width: guessFieldWidth(field.b_field),
+ b_sortable: 0,
+ });
+ }
+
+ if (!editFieldIds.has(fieldId)) {
+ generated.editConfig.push({
+ b_id: createTempId(),
+ b_module_id: moduleId,
+ b_field_id: fieldId,
+ b_required: 0,
+ b_readonly: 0,
+ b_disabled: 0,
+ b_group_id: null,
+ b_xh: order,
+ });
+ }
+
+ if (!queryFieldIds.has(fieldId)) {
+ generated.queryConfig.push({
+ b_id: createTempId(),
+ b_module_id: moduleId,
+ b_field_id: fieldId,
+ b_component: guessQueryComponent(field.b_type),
+ b_operator: guessQueryOperator(field.b_type),
+ b_default_value: null,
+ b_xh: order,
+ b_canuse: 1,
+ });
+ }
+ });
+
+ return generated;
+}
+
+async function syncFields(tableName) {
const module = selectedModule.value;
- if (!module?.b_viewtable) {
- message.warning("当前模块未配置查询视图");
+ const view = tableName || module?.b_viewtable;
+ if (!view) {
+ message.warning("请先配置查询视图或输入表名");
return;
}
try {
configSaving.value = true;
- const res = await describeApi(module.b_viewtable);
+ const res = await describeApi(view);
const columns = res.data || [];
const existingFields = originals.value.fields || [];
@@ -469,27 +580,55 @@ async function syncFields() {
}
}
- // 保存到 b_module_field
+ // 字段多语言键只补空值,保留已有人工配置。
+ mergedFields.forEach((field) => {
+ if (!field.b_i18n) field.b_i18n = `field.${field.b_field}`;
+ });
+
+ // 先为新字段分配真实 ID,后续配置通过外键引用这些 ID。
const changes = diffRows(mergedFields, existingFields);
- const req = tableChange("b_module_field", changes);
- if (hasChanges(req)) {
- await allocateTemporaryIds([req.inserts || [], req.updates || [], req.deletes || []]);
- await saveObjectApi([req]);
- message.success(
- `字段同步完成:${columns.length} 列,新增 ${req.inserts?.length || 0},停用 ${req.deletes?.length || 0}`,
- );
- } else {
- message.info("字段已是最新,无需同步");
+ const fieldReq = tableChange("s_module_field", changes);
+ await allocateTemporaryIds([
+ fieldReq.inserts || [],
+ fieldReq.updates || [],
+ fieldReq.deletes || [],
+ ]);
+
+ const generated = buildMissingDefaultConfigs(module, mergedFields);
+ const generatedSections = ["listConfig", "editConfig", "queryConfig"];
+ const generatedCount = generatedSections.reduce(
+ (total, section) => total + generated[section].length,
+ 0,
+ );
+
+ if (!hasChanges(fieldReq) && !generatedCount) {
+ message.info("字段和默认配置已是最新,无需同步");
+ return;
}
- // 重新加载
- const fieldsRes = await loadDataApi(
- "b_module_field",
- `b_module_id = ${module.b_id}`,
- "b_id ASC",
+ if (hasChanges(fieldReq)) {
+ // 字段先保存以取得真实 ID,自动生成的配置留在草稿中由顶部“保存”统一提交。
+ await saveObjectApi([fieldReq]);
+ const fieldsRes = await loadDataApi(
+ "s_module_field",
+ `b_module_id = ${module.b_id}`,
+ "b_id ASC",
+ );
+ originals.value.fields = fieldsRes.data || [];
+ drafts.value.fields = cloneData(originals.value.fields);
+ }
+
+ for (const section of generatedSections) {
+ if (!generated[section].length) continue;
+ drafts.value[section] = [...(drafts.value[section] || []), ...cloneData(generated[section])];
+ }
+
+ const disabledCount = existingFields.filter(
+ (field) => field.b_canuse === 1 && !viewColumnSet.has(field.b_field?.toLowerCase()),
+ ).length;
+ message.success(
+ `字段同步完成:${columns.length} 列,新增 ${fieldReq.inserts?.length || 0},停用 ${disabledCount};自动生成列表 ${generated.listConfig.length}、编辑 ${generated.editConfig.length}、查询 ${generated.queryConfig.length} 项配置${generatedCount ? ",请点击保存" : ""}`,
);
- originals.value.fields = fieldsRes.data || [];
- drafts.value.fields = cloneData(originals.value.fields);
} catch (e) {
message.error("字段同步失败:" + (e.message || e));
} finally {
@@ -498,7 +637,7 @@ async function syncFields() {
}
// ---- 初始化 ----
-loadTree();
+void loadTree().catch((e) => message.error("模块树加载失败:" + (e.message || e)));
@@ -549,13 +688,14 @@ loadTree();
启用
onSetField('b_canuse', v ? 1 : 0)"
/>
显示菜单
onSetField('b_canmenu', v ? 1 : 0)"
/>
diff --git a/code/fms/fms-vue/src/views/system/login-log/index.vue b/code/fms/fms-vue/src/views/system/login-log/index.vue
new file mode 100644
index 00000000..c17a2616
--- /dev/null
+++ b/code/fms/fms-vue/src/views/system/login-log/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/code/fms/新数据库结构.md b/code/fms/新数据库结构.md
index c10f857d..d55a442a 100644
--- a/code/fms/新数据库结构.md
+++ b/code/fms/新数据库结构.md
@@ -2,38 +2,38 @@
## 设计原则
-- `b_module` 是核心元数据对象,统一表示菜单目录、页面和数据模块。
+- `s_module` 是核心元数据对象,统一表示菜单目录、页面和数据模块。
- 字段来源于数据模块配置的查询视图。系统不创建业务字段,只同步视图字段,并维护其展示、编辑、查询和权限配置。
- 页面和业务逻辑不完全元数据化。海运等复杂业务页面由 Vue 页面显式组合多个数据模块,元数据只提供 Table、Form 和 Query 配置。
- 字段基础定义、列表、编辑、查询四个职责分离;一个字段可以只参与其中任一场景。
-- 编辑规则与列表布局解耦:`b_module_field_edit` 是通用编辑配置,FmsForm 和 FmsTable 可按自身规则消费;`b_module_field_list` 只描述列表展示,不保存编辑状态。
+- 编辑规则与列表布局解耦:`s_module_field_edit` 是通用编辑配置,FmsForm 和 FmsTable 可按自身规则消费;`s_module_field_list` 只描述列表展示,不保存编辑状态。
- 模块权限控制访问,操作权限控制按钮动作;操作权限必须依赖模块访问权限。
-- 表名和字段名沿用旧系统的 `b_` 风格。全部 `b_id` 使用 `bigint` 雪花 ID,返回前端时序列化为字符串。
+- 模块元数据表沿用旧系统的 `s_module*` 命名;基础资料和用户授权表使用 `b_`,字段名继续使用 `b_`。全部 `b_id` 使用 `bigint` 雪花 ID,返回前端时序列化为字符串。
- 布尔字段统一使用 `int`,`1` 表示是、`0` 表示否;`b_xh` 数值越小越靠前。
- 当前按机构使用独立数据库,表中不保存机构编号。表名、视图名和字段名必须由后端白名单校验,不能直接拼接 SQL。
## 整体关系
```text
-b_module
- |-- parent_id --> b_module
- |-- 1:N --------> b_module_field
- |-- 1:N --------> b_module_field_group -- parent_id --> b_module_field_group
- |-- 1:N --------> b_module_power ---- 1:N ----> b_user_power
+s_module
+ |-- parent_id --> s_module
+ |-- 1:N --------> s_module_field
+ |-- 1:N --------> s_module_field_group -- parent_id --> s_module_field_group
+ |-- 1:N --------> s_module_power ---- 1:N ----> b_user_power
`-- 1:N --------> b_user_module
-b_module_field
- |-- 0:N --------> b_module_field_list -- group_id --> b_module_field_group
- |-- 0:1 --------> b_module_field_edit -- group_id --> b_module_field_group
- `-- 0:1 --------> b_module_field_query
+s_module_field
+ |-- 0:N --------> s_module_field_list -- group_id --> s_module_field_group
+ |-- 0:1 --------> s_module_field_edit -- group_id --> s_module_field_group
+ `-- 0:1 --------> s_module_field_query
-b_module / b_module_field / b_module_field_list / b_module_field_group / b_module_power
+s_module / s_module_field / s_module_field_list / s_module_field_group / s_module_power
`-- b_i18n --> b_i18n.b_key
```
## 模块
-### `dbo.b_module`
+### `dbo.s_module`
统一保存目录、页面和数据模块。
@@ -56,7 +56,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
约束:
-- `b_parent_id` 外键引用 `b_module.b_id`;保存服务校验父节点不形成循环。
+- `b_parent_id` 外键引用 `s_module.b_id`;保存服务校验父节点不形成循环。
- `b_code` 唯一,格式为 `^[a-z][a-z0-9_]{1,49}$`;`b_route` 非空时唯一。
- `directory` 不能配置 `b_route`、`b_viewtable` 或 `b_savetable`。
- `page` 必须配置 `b_route`;其业务数据由页面组合的数据模块提供。
@@ -65,7 +65,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
## 字段来源与同步
-### `dbo.b_module_field`
+### `dbo.s_module_field`
保存字段的基础定义。字段来自数据模块的 `b_viewtable`,并以 `(b_module_id, b_field)` 作为稳定标识;列表、编辑和查询场景属性均由独立配置表维护。
@@ -83,7 +83,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
约束:
-- `b_module_id` 外键引用 `b_module.b_id`,`(b_module_id, b_field)` 唯一。
+- `b_module_id` 外键引用 `s_module.b_id`,`(b_module_id, b_field)` 唯一。
- `b_canuse` 只能为 `0` 或 `1`;`b_options` 非空时通过 `ISJSON` 校验。建议建立 `(b_module_id, b_canuse, b_id)` 索引。
- 字段没有 list、edit 或 query 配置记录时,不参与对应场景。
- `b_type` 是展示和编辑的唯一类型来源:Table 按它渲染默认显示器,Form 或可编辑 Table 按它选择控件。
@@ -94,57 +94,55 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
字段同步服务仅面向 `b_module_type = 'data'` 且配置了 `b_viewtable` 的模块执行:
1. 后端校验视图名并读取列名、SQL 类型和可空性等结构信息。
-2. 按列名查找同模块的 `b_module_field`;新列创建字段基础记录。
+2. 按列名查找同模块的 `s_module_field`;新列创建字段基础记录。
3. 新字段按 SQL 类型初始化 `b_type`,例如字符串映射为 `input`、长文本映射为 `textarea`、数值映射为 `number`、日期映射为 `date` 或 `datetime`、位值映射为 `switch`。已有字段不覆盖管理员维护的 `b_type` 与 `b_options`;`select`、`money` 等业务类型由管理员按语义调整。
4. 本次视图中不存在的已有字段保留其记录和全部场景配置,但将 `b_canuse` 置为 `0`。
5. 不覆盖人工维护的 `b_name`、`b_i18n`、`b_default_value` 与 `b_options`,不物理删除字段或场景配置。
## 列表配置
-### `dbo.b_module_field_list`
+### `dbo.s_module_field_list`
-控制列表字段列。多级表头由 `b_module_field_group` 的 `list` 分组树表达;本表不保存编辑状态。
+控制列表字段列。多级表头由 `s_module_field_group` 的 `list` 分组树表达;本表不保存编辑状态。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `b_id` | `bigint` | 雪花 ID 主键 |
| `b_module_id` | `bigint` | 所属模块 ID |
-| `b_field_id` | `bigint` | 关联 `b_module_field.b_id` |
+| `b_field_id` | `bigint` | 关联 `s_module_field.b_id` |
| `b_group_id` | `bigint` | 所属分组 ID;为空时为顶层列 |
-| `b_title` | `nvarchar(100)` | 字段标题覆盖值;为空时使用默认字段名称 |
| `b_i18n` | `varchar(150)` | 字段标题多语言键覆盖值 |
| `b_visible` | `int` | 是否显示,默认 `1` |
| `b_xh` | `int` | 同一分组内的显示顺序,默认 `0` |
| `b_width` | `int` | 字段列宽 |
-| `b_sortable` | `int` | 是否允许排序,默认 `0` |
约束:
-- `b_module_id` 外键引用 `b_module.b_id`,`b_field_id` 外键引用 `b_module_field.b_id`,`b_group_id` 引用同模块的 `b_module_field_group`。
+- `b_module_id` 外键引用 `s_module.b_id`,`b_field_id` 外键引用 `s_module_field.b_id`,`b_group_id` 引用同模块的 `s_module_field_group`。
- `b_field_id` 必填;同一字段在同一模块的列表中最多出现一次,使用 `(b_module_id, b_field_id)` 唯一约束。
-- `b_visible`、`b_sortable` 只能为 `0` 或 `1`。
+- `b_visible` 只能为 `0` 或 `1`。
- 字段列的显示器由关联字段的 `b_type` 与 `b_options` 决定,例如 `money`、`date`、`datetime` 均由前端按统一规则渲染。建议建立 `(b_module_id, b_group_id, b_xh, b_id)` 索引。
- 不保存 `level`、`colspan`、`rowspan`;前端加载 Table 分组树和字段列后,按分组的 `b_parent_id`、`b_xh` 组装嵌套表头。隐藏分组的后代不显示;没有可见字段后代的分组不渲染。
示例:
```text
-费用(`b_module_field_group`)
-|-- 人工费(`b_module_field_list.b_group_id` 指向费用)
-`-- 海运费(`b_module_field_list.b_group_id` 指向费用)
+费用(`s_module_field_group`)
+|-- 人工费(`s_module_field_list.b_group_id` 指向费用)
+`-- 海运费(`s_module_field_list.b_group_id` 指向费用)
```
## 编辑配置
-### `dbo.b_module_field_edit`
+### `dbo.s_module_field_edit`
-控制字段的通用编辑属性与编辑分组。控件类型、显示规则和类型专属参数统一由 `b_module_field.b_type`、`b_options` 决定;FmsForm 与 FmsTable 自行决定何时使用本配置。
+控制字段的通用编辑属性与编辑分组。控件类型、显示规则和类型专属参数统一由 `s_module_field.b_type`、`b_options` 决定;FmsForm 与 FmsTable 自行决定何时使用本配置。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `b_id` | `bigint` | 雪花 ID 主键 |
| `b_module_id` | `bigint` | 所属模块 ID |
-| `b_field_id` | `bigint` | 关联 `b_module_field.b_id` |
+| `b_field_id` | `bigint` | 关联 `s_module_field.b_id` |
| `b_required` | `int` | 是否必填,默认 `0` |
| `b_readonly` | `int` | 是否只读,默认 `0` |
| `b_disabled` | `int` | 是否禁用,默认 `0` |
@@ -153,18 +151,18 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
约束:
-- `b_module_id` 外键引用 `b_module.b_id`,`b_field_id` 外键引用 `b_module_field.b_id`;`(b_module_id, b_field_id)` 唯一,保存服务校验字段属于同一模块。
+- `b_module_id` 外键引用 `s_module.b_id`,`b_field_id` 外键引用 `s_module_field.b_id`;`(b_module_id, b_field_id)` 唯一,保存服务校验字段属于同一模块。
- `b_required`、`b_readonly`、`b_disabled` 只能为 `0` 或 `1`。
-- `b_group_id` 必须关联同模块的 `b_module_field_group`;分组为空时字段进入默认编辑区域。
+- `b_group_id` 必须关联同模块的 `s_module_field_group`;分组为空时字段进入默认编辑区域。
- 已分组字段按分组的 `b_xh` 排列,组内按 `b_xh` 排列;默认区域字段按 `b_xh` 排列。
## 布局分组
-### `dbo.b_module_field_group`
+### `dbo.s_module_field_group`
统一保存列表多级表头和编辑分组。分组只负责布局,不定义编辑能力;列表或编辑用途由引用它的配置表决定。
-被 `b_module_field_list` 引用时,分组组成同一份 FmsTable 的多级表头;被 `b_module_field_edit` 引用时,分组组成同一份编辑界面中的业务区块,例如提单、订舱、报关、备注,不代表拆分为多个 Form。
+被 `s_module_field_list` 引用时,分组组成同一份 FmsTable 的多级表头;被 `s_module_field_edit` 引用时,分组组成同一份编辑界面中的业务区块,例如提单、订舱、报关、备注,不代表拆分为多个 Form。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
@@ -178,7 +176,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
约束:
-- `b_module_id` 外键引用 `b_module.b_id`;`b_id` 是分组的稳定关联键。
+- `b_module_id` 外键引用 `s_module.b_id`;`b_id` 是分组的稳定关联键。
- `b_parent_id` 必须引用同模块的分组且不能形成循环。
- 多级分组可用于 FmsTable 表头;FmsForm 与 FmsTable 如何消费编辑分组由各自实现决定。
- `b_canuse` 只能为 `0` 或 `1`。建议建立 `(b_module_id, b_canuse, b_xh, b_id)` 索引。
@@ -200,7 +198,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
## 查询配置
-### `dbo.b_module_field_query`
+### `dbo.s_module_field_query`
独立控制主列表查询条件,不依赖字段是否配置为可见列表列。
@@ -208,7 +206,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
| --- | --- | --- |
| `b_id` | `bigint` | 雪花 ID 主键 |
| `b_module_id` | `bigint` | 所属模块 ID |
-| `b_field_id` | `bigint` | 关联 `b_module_field.b_id` |
+| `b_field_id` | `bigint` | 关联 `s_module_field.b_id` |
| `b_component` | `varchar(30)` | 查询组件 |
| `b_operator` | `varchar(20)` | `=`、`like`、`between` 或 `in` |
| `b_default_value` | `nvarchar(500)` | 默认查询值 |
@@ -217,14 +215,14 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
约束:
-- `b_module_id` 外键引用 `b_module.b_id`,`b_field_id` 外键引用 `b_module_field.b_id`;`(b_module_id, b_field_id)` 唯一,保存服务校验模块一致性。
+- `b_module_id` 外键引用 `s_module.b_id`,`b_field_id` 外键引用 `s_module_field.b_id`;`(b_module_id, b_field_id)` 唯一,保存服务校验模块一致性。
- `b_canuse` 只能为 `0` 或 `1`;`b_operator` 必须属于定义的操作符集合。
- 查询组件与操作符由后端白名单校验;查询值必须参数化,不能由配置直接拼接 SQL。
- 建议建立 `(b_module_id, b_canuse, b_xh, b_id)` 索引。
## 权限与多语言
-### `dbo.b_module_power`
+### `dbo.s_module_power`
定义模块内可授权的按钮操作。
@@ -238,7 +236,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
| `b_xh` | `int` | 显示顺序 |
| `b_canuse` | `int` | 是否启用,默认 `1` |
-约束:`b_module_id` 外键引用 `b_module.b_id`,`(b_module_id, b_code)` 唯一,`b_canuse` 只能为 `0` 或 `1`。
+约束:`b_module_id` 外键引用 `s_module.b_id`,`(b_module_id, b_code)` 唯一,`b_canuse` 只能为 `0` 或 `1`。
### `dbo.b_user_module`
@@ -252,7 +250,7 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
| `b_inputuser_id` | `bigint` | 授权人雪花 ID |
| `b_inputdatetime` | `datetime` | 授权时间 |
-约束:`b_module_id` 外键引用 `b_module.b_id`,`(b_user_id, b_module_id)` 唯一。目录不单独授权;用户拥有下级模块时自动显示上级目录。
+约束:`b_module_id` 外键引用 `s_module.b_id`,`(b_user_id, b_module_id)` 唯一。目录不单独授权;用户拥有下级模块时自动显示上级目录。
### `dbo.b_user_power`
@@ -262,11 +260,11 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
| --- | --- | --- |
| `b_id` | `bigint` | 雪花 ID 主键 |
| `b_user_id` | `bigint` | 用户雪花 ID |
-| `b_power_id` | `bigint` | `b_module_power.b_id` |
+| `b_power_id` | `bigint` | `s_module_power.b_id` |
| `b_inputuser_id` | `bigint` | 授权人雪花 ID |
| `b_inputdatetime` | `datetime` | 授权时间 |
-约束:`b_power_id` 外键引用 `b_module_power.b_id`,`(b_user_id, b_power_id)` 唯一。
+约束:`b_power_id` 外键引用 `s_module_power.b_id`,`(b_user_id, b_power_id)` 唯一。
权限规则:
@@ -299,27 +297,27 @@ b_module / b_module_field / b_module_field_list / b_module_field_group / b_modul
`-- 箱明细 d_container(data)
```
-若订舱字段实际保存于海运主单表,不创建 `d_booking`,而在 `d_sea` 的编辑配置中用 `b_group_id` 和 `b_xh` 定义编辑分组与顺序,分组标题来自 `b_module_field_group`。主子表关联、加载时机、保存顺序和页面布局均由业务 Vue 页面明确实现。
+若订舱字段实际保存于海运主单表,不创建 `d_booking`,而在 `d_sea` 的编辑配置中用 `b_group_id` 和 `b_xh` 定义编辑分组与顺序,分组标题来自 `s_module_field_group`。主子表关联、加载时机、保存顺序和页面布局均由业务 Vue 页面明确实现。
## 现有字段迁移
-当前 `b_module_field` 混合保存基础、列表、编辑和查询属性。迁移应分阶段实施:
+当前 `s_module_field` 混合保存基础、列表、编辑和查询属性。迁移应分阶段实施:
| 当前字段 | 目标位置 |
| --- | --- |
-| `b_type` | `b_module_field.b_type` |
-| `b_canlist` | `b_module_field_list.b_visible` |
-| `b_width` | `b_module_field_list.b_width` |
-| `b_xh` | 初始化 `b_module_field_list.b_xh`、`b_module_field_edit.b_xh` 与 `b_module_field_query.b_xh` |
-| `b_canform` | 为原 Form 字段创建 `b_module_field_edit` 记录 |
-| `b_must` | `b_module_field_edit.b_required` |
-| `b_readonly` | `b_module_field_edit.b_readonly` |
-| `b_canquery` | 创建 `b_module_field_query` 记录 |
-| `b_value` | 迁移为 `b_module_field.b_default_value` |
+| `b_type` | `s_module_field.b_type` |
+| `b_canlist` | `s_module_field_list.b_visible` |
+| `b_width` | `s_module_field_list.b_width` |
+| `b_xh` | 初始化 `s_module_field_list.b_xh`、`s_module_field_edit.b_xh` 与 `s_module_field_query.b_xh` |
+| `b_canform` | 为原 Form 字段创建 `s_module_field_edit` 记录 |
+| `b_must` | `s_module_field_edit.b_required` |
+| `b_readonly` | `s_module_field_edit.b_readonly` |
+| `b_canquery` | 创建 `s_module_field_query` 记录 |
+| `b_value` | 迁移为 `s_module_field.b_default_value` |
迁移步骤:
-1. 新建 list、edit、group、query 表和必要索引,新增 `b_module.b_module_type` 与 `b_module_field.b_type` 的兼容列。
+1. 新建 list、edit、group、query 表和必要索引,新增 `s_module.b_module_type` 与 `s_module_field.b_type` 的兼容列。
2. 为原有字段创建列表配置;根据原 `b_canform`、`b_canquery` 初始化编辑和查询配置。
3. 前端和后端改为读取新场景表,查询不再读取 list 配置;模块类型切换为读取 `b_module_type`。
4. 完成视图同步服务并验证新结构的读写稳定后,删除旧混合列和兼容字段。
diff --git a/code/fms/模块管理实施计划.md b/code/fms/模块管理实施计划.md
index 7ed1c4e4..8648c2c4 100644
--- a/code/fms/模块管理实施计划.md
+++ b/code/fms/模块管理实施计划.md
@@ -30,9 +30,9 @@ fms-api/config/migrations/003_module_surrogate_key.sql
按以下顺序建立表:
-1. `b_module`
-2. `b_module_field`
-3. `b_module_power`
+1. `s_module`
+2. `s_module_field`
+3. `s_module_power`
4. `b_user_module`
5. `b_user_power`
6. `b_i18n`
@@ -91,9 +91,9 @@ grant 用户授权
| 数据 | 接口 | 数据源 |
| --- | --- | --- |
-| 模块树 | `/data/loaddata` | `b_module` |
-| 模块字段 | `/data/loaddata` | `b_module_field` |
-| 操作权限定义 | `/data/loaddata` | `b_module_power` |
+| 模块树 | `/data/loaddata` | `s_module` |
+| 模块字段 | `/data/loaddata` | `s_module_field` |
+| 操作权限定义 | `/data/loaddata` | `s_module_power` |
| 多语言 | `/data/loaddata` | `b_i18n` |
| 删除依赖检查 | `/data/loaddata` | `b_user_module`、`b_user_power` |
@@ -103,21 +103,21 @@ grant 用户授权
新增以下数据前调用 `/data/nextid`:
-- `b_module_field.b_id`
-- `b_module_power.b_id`
+- `s_module_field.b_id`
+- `s_module_power.b_id`
- `b_i18n.b_id`
-- `b_module.b_id`
+- `s_module.b_id`
-`b_module.b_id` 使用不可变雪花 ID;可修改的小写模块编码保存在 `b_module.b_code`。
+`s_module.b_id` 使用不可变雪花 ID;可修改的小写模块编码保存在 `s_module.b_code`。
### 模块保存
前端维护服务器快照和编辑副本,计算 inserts、updates、deletes,然后一次调用 `/data/saveobjt` 保存:
```text
-b_module
-b_module_field
-b_module_power
+s_module
+s_module_field
+s_module_power
b_i18n
```
@@ -126,12 +126,12 @@ b_i18n
```json
[
{
- "table": "b_module",
+ "table": "s_module",
"key_field": "b_id",
"updates": []
},
{
- "table": "b_module_field",
+ "table": "s_module_field",
"key_field": "b_id",
"inserts": [],
"updates": [],
@@ -159,10 +159,10 @@ b_i18n
```text
b_user_power
b_user_module
-b_module_power
-b_module_field
+s_module_power
+s_module_field
b_i18n
-b_module
+s_module
```
## 第四阶段:将 Demo 改为真实页面
@@ -200,7 +200,7 @@ services/moduleManagement.js
### 菜单
-`AppSidebar` 通过现有通用查询接口读取当前用户的 `b_user_module` 和可用的 `b_module`,在前端构建菜单树。
+`AppSidebar` 通过现有通用查询接口读取当前用户的 `b_user_module` 和可用的 `s_module`,在前端构建菜单树。
- 只显示 `b_canmenu = 1`、`b_canuse = 1` 且用户有权限的页面。
- 上级目录根据可访问下级页面自动显示。
diff --git a/新建 文本文档.txt b/新建 文本文档.txt
new file mode 100644
index 00000000..e69de29b