20260726225220

This commit is contained in:
oneao committed 2026-07-26 22:52:21 +08:00
1 parent e3c7c6f8da
commit a8478d60ed
170 files changed
+6786 -1678

No files matched your search

@@ -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)));
</script>
<template>
@@ -549,13 +688,14 @@ loadTree();
<span>启用</span>
<a-switch
:checked="drafts.module?.b_canuse === 1"
:disabled="configSaving"
@update:checked="(v) => onSetField('b_canuse', v ? 1 : 0)"
/>
<a-divider type="vertical" />
<span>显示菜单</span>
<a-switch
:checked="drafts.module?.b_canmenu === 1"
:disabled="drafts.module?.b_module_type === 'data'"
:disabled="configSaving || drafts.module?.b_module_type === 'data'"
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
/>
</a-space>