20260726225220
This commit is contained in:
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>
|
||||
|
||||
Reference in new issue
Block a user