20260722173146
This commit is contained in:
1 parent
e9f099f044
commit
d04fad24ed
24 files changed
+1309
-422
No files matched your search
@@ -3,15 +3,20 @@ import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { App } from "antdv-next";
|
||||
import { Database, FileText, RotateCcw, Save, ShieldCheck, Trash2 } from "@lucide/vue";
|
||||
import { loadDataApi, nextIdApi, saveObjectApi } from "@/services/api";
|
||||
import { createTempId } from "@/utils/tempId";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { usePermissionStore } from "@/stores/permissions";
|
||||
import { I18N_LOCALE_CODES } from "@/utils/i18n";
|
||||
|
||||
// 默认语言:由实体的 b_name 字段承载,不写入 b_i18n 翻译表(方案 A)。
|
||||
const I18N_DEFAULT_LOCALE = "zh-CN";
|
||||
import ModuleBasicInfoPanel from "./components/ModuleBasicInfoPanel.vue";
|
||||
import ModuleFieldDefinitionPanel from "./components/ModuleFieldDefinitionPanel.vue";
|
||||
import ModulePowerPanel from "./components/ModulePowerPanel.vue";
|
||||
import ModuleTreePanel from "./components/ModuleTreePanel.vue";
|
||||
|
||||
const PROTECTED_MODULE_CODES = ["m_module", "m_i18n", "m_biz"];
|
||||
// 受保护的模块(禁止删除)。当前放开为可删除,留空即不做保护。
|
||||
const PROTECTED_MODULE_CODES = [];
|
||||
const { message } = App.useApp();
|
||||
const authStore = useAuthStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
@@ -22,8 +27,8 @@ const activeTab = ref("basic");
|
||||
const isNew = ref(false);
|
||||
const deleteConfirmOpen = ref(false);
|
||||
const pendingDeleteKey = ref("");
|
||||
const original = ref("");
|
||||
const originalTranslationKeys = ref([]);
|
||||
let originalDraft = null;
|
||||
const data = reactive({
|
||||
modules: [],
|
||||
fields: [],
|
||||
@@ -38,7 +43,6 @@ const canCreate = computed(() => permissionStore.canPower("m_module", "create"))
|
||||
const canUpdate = computed(() => permissionStore.canPower("m_module", "update"));
|
||||
const canDelete = computed(() => permissionStore.canPower("m_module", "delete"));
|
||||
const canSave = computed(() => (isNew.value ? canCreate.value : canUpdate.value));
|
||||
const dirty = computed(() => Boolean(draft.module) && serializeDraft() !== original.value);
|
||||
const parentOptions = computed(() =>
|
||||
data.modules
|
||||
.filter((item) => item.b_id !== draft.module?.b_id && item.b_type !== "data")
|
||||
@@ -61,15 +65,6 @@ function clone(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function serializeDraft() {
|
||||
return JSON.stringify({
|
||||
module: draft.module,
|
||||
fields: draft.fields,
|
||||
powers: draft.powers,
|
||||
translations: draft.translations,
|
||||
});
|
||||
}
|
||||
|
||||
function rowKeys() {
|
||||
return [
|
||||
draft.module?.b_i18n,
|
||||
@@ -85,7 +80,7 @@ function clearDraft() {
|
||||
draft.fields = [];
|
||||
draft.powers = [];
|
||||
draft.translations = {};
|
||||
original.value = "";
|
||||
originalDraft = null;
|
||||
originalTranslationKeys.value = [];
|
||||
activeTab.value = "basic";
|
||||
}
|
||||
@@ -108,12 +103,18 @@ function selectModule(moduleCode) {
|
||||
data.translations
|
||||
.filter((item) => keys.has(item.b_key))
|
||||
.forEach((item) => {
|
||||
if (item.b_locale === I18N_DEFAULT_LOCALE) return;
|
||||
draft.translations[item.b_key] ||= {};
|
||||
draft.translations[item.b_key][item.b_locale] = item.b_value;
|
||||
});
|
||||
|
||||
originalTranslationKeys.value = Array.from(keys);
|
||||
original.value = serializeDraft();
|
||||
originalDraft = clone({
|
||||
module: draft.module,
|
||||
fields: draft.fields,
|
||||
powers: draft.powers,
|
||||
translations: draft.translations,
|
||||
});
|
||||
if (module.b_type === "directory") activeTab.value = "basic";
|
||||
}
|
||||
|
||||
@@ -189,12 +190,10 @@ async function addModule(kind, contextKey) {
|
||||
draft.fields = [];
|
||||
draft.powers = [];
|
||||
originalTranslationKeys.value = [];
|
||||
draft.translations = {
|
||||
["module." + code]: { "zh-CN": draft.module.b_name },
|
||||
};
|
||||
draft.translations = {};
|
||||
originalDraft = { module: null, fields: [], powers: [], translations: {} };
|
||||
selectedKey.value = code;
|
||||
isNew.value = true;
|
||||
original.value = "";
|
||||
activeTab.value = "basic";
|
||||
}
|
||||
|
||||
@@ -278,6 +277,10 @@ function diffRows(current, previous) {
|
||||
};
|
||||
}
|
||||
|
||||
function sameRecord(current, previous) {
|
||||
return JSON.stringify(current) === JSON.stringify(previous);
|
||||
}
|
||||
|
||||
async function translationChanges(previousKeys) {
|
||||
const currentKeys = new Set([...rowKeys(), ...Object.keys(draft.translations)]);
|
||||
const relevantKeys = new Set([...previousKeys, ...currentKeys]);
|
||||
@@ -285,8 +288,9 @@ async function translationChanges(previousKeys) {
|
||||
const desired = [];
|
||||
const missing = [];
|
||||
|
||||
const editableLocales = I18N_LOCALE_CODES.filter((locale) => locale !== I18N_DEFAULT_LOCALE);
|
||||
for (const key of currentKeys) {
|
||||
for (const locale of I18N_LOCALE_CODES) {
|
||||
for (const locale of editableLocales) {
|
||||
const value = draft.translations[key]?.[locale]?.trim();
|
||||
if (!value) continue;
|
||||
const old = oldRows.find((item) => item.b_key === key && item.b_locale === locale);
|
||||
@@ -301,15 +305,10 @@ async function translationChanges(previousKeys) {
|
||||
.filter((item) => !I18N_LOCALE_CODES.includes(item.b_locale))
|
||||
.forEach((item) => desired.push(item));
|
||||
|
||||
const ids = [];
|
||||
for (let index = 0; index < missing.length; index += 1) {
|
||||
const response = await nextIdApi();
|
||||
ids.push(String(response.data));
|
||||
}
|
||||
const now = new Date().toISOString().slice(0, 19).replace("T", " ");
|
||||
missing.forEach((item, index) => {
|
||||
missing.forEach((item) => {
|
||||
desired.push({
|
||||
b_id: ids[index],
|
||||
b_id: createTempId(),
|
||||
b_key: item.key,
|
||||
b_locale: item.locale,
|
||||
b_value: item.value,
|
||||
@@ -339,15 +338,9 @@ async function saveModule() {
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
const oldModule = isNew.value
|
||||
? null
|
||||
: data.modules.find((item) => item.b_code === selectedKey.value);
|
||||
const oldFields = oldModule
|
||||
? data.fields.filter((item) => item.b_module_id === oldModule.b_id)
|
||||
: [];
|
||||
const oldPowers = oldModule
|
||||
? data.powers.filter((item) => item.b_module_id === oldModule.b_id)
|
||||
: [];
|
||||
const oldModule = originalDraft?.module ?? null;
|
||||
const oldFields = originalDraft?.fields ?? [];
|
||||
const oldPowers = originalDraft?.powers ?? [];
|
||||
const previousKeys = new Set(
|
||||
[
|
||||
...originalTranslationKeys.value,
|
||||
@@ -380,18 +373,21 @@ async function saveModule() {
|
||||
table: "b_module",
|
||||
key_field: "b_id",
|
||||
inserts: isNew.value ? [draft.module] : [],
|
||||
updates: isNew.value ? [] : [draft.module],
|
||||
updates: !isNew.value && !sameRecord(draft.module, oldModule) ? [draft.module] : [],
|
||||
deletes: [],
|
||||
},
|
||||
{ table: "b_module_field", key_field: "b_id", ...fieldChanges },
|
||||
{ table: "b_module_power", key_field: "b_id", ...powerChanges },
|
||||
{ table: "b_i18n", key_field: "b_id", ...i18nChanges },
|
||||
);
|
||||
await saveObjectApi(
|
||||
requests.filter(
|
||||
(request) => request.inserts.length || request.updates.length || request.deletes.length,
|
||||
),
|
||||
const pendingRequests = requests.filter(
|
||||
(request) => request.inserts.length || request.updates.length || request.deletes.length,
|
||||
);
|
||||
if (!pendingRequests.length) {
|
||||
message.info("没有需要保存的修改");
|
||||
return;
|
||||
}
|
||||
await saveObjectApi(pendingRequests);
|
||||
|
||||
const savedKey = draft.module.b_code;
|
||||
message.success("模块配置已保存");
|
||||
@@ -553,9 +549,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<a-button :disabled="!dirty" @click="resetDraft">
|
||||
<RotateCcw :size="15" />取消
|
||||
</a-button>
|
||||
<a-button @click="resetDraft"> <RotateCcw :size="15" />取消 </a-button>
|
||||
<a-popconfirm
|
||||
title="确定删除当前模块吗?"
|
||||
:disabled="
|
||||
@@ -572,12 +566,7 @@ onMounted(async () => {
|
||||
<Trash2 :size="15" />删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
:disabled="!dirty || !canSave"
|
||||
@click="saveModule"
|
||||
>
|
||||
<a-button type="primary" :loading="saving" :disabled="!canSave" @click="saveModule">
|
||||
<Save :size="15" />保存
|
||||
</a-button>
|
||||
</div>
|
||||
@@ -604,8 +593,10 @@ onMounted(async () => {
|
||||
</template>
|
||||
<ModuleFieldDefinitionPanel
|
||||
v-model:fields="draft.fields"
|
||||
v-model:translations="draft.translations"
|
||||
:module-id="draft.module.b_id"
|
||||
:module-code="draft.module.b_code"
|
||||
:view-table="draft.module.b_viewtable || draft.module.b_savetable"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-if="draft.module.b_type !== 'directory'" key="powers">
|
||||
@@ -616,6 +607,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
<ModulePowerPanel
|
||||
v-model:powers="draft.powers"
|
||||
v-model:translations="draft.translations"
|
||||
:module-id="draft.module.b_id"
|
||||
:module-code="draft.module.b_code"
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user