Files
workspace/code/fms/fms-vue/src/views/module-management/components/FieldGroupManager.vue
T
2026-07-28 17:30:27 +08:00

242 lines
6.2 KiB
Vue

<script setup>
import { computed, ref, watch } from "vue";
import { message } from "antdv-next";
import FmsTree from "@/components/FmsTree.vue";
import { saveObjectApi } from "@/services/api";
import {
cloneData,
diffRows,
allocateTemporaryIds,
remapForeignKeys,
tableChange,
hasChanges,
} from "@/utils/dataChanges";
import { createTempId } from "@/utils/tempId";
import { buildTree } from "@/utils/tree";
const props = defineProps({
visible: { type: Boolean, default: false },
moduleId: { type: String, required: true },
groups: { type: Array, default: () => [] },
referencedGroupIds: { type: Array, default: () => [] },
});
const emit = defineEmits(["update:visible", "saved"]);
const localGroups = ref([]);
const selectedGroupId = ref(null);
watch(
() => props.groups,
(val) => {
localGroups.value = cloneData(val || []);
},
{ immediate: true },
);
const treeData = computed(() =>
buildTree(localGroups.value, {
mapNode: (item) => ({
key: String(item.b_id),
title: item.b_title || `分组-${item.b_id}`,
children: [],
}),
}),
);
const selectedGroup = computed(() =>
localGroups.value.find((g) => String(g.b_id) === selectedGroupId.value),
);
function onTreeSelect(keys) {
if (keys.length > 0) {
selectedGroupId.value = String(keys[0]);
} else {
selectedGroupId.value = null;
}
}
function addGroup(parentId) {
const newGroup = {
b_id: createTempId(),
b_module_id: props.moduleId,
b_parent_id: parentId || null,
b_title: "",
b_i18n: null,
b_xh: (localGroups.value.length + 1) * 10,
b_canuse: 1,
};
localGroups.value.push(newGroup);
selectedGroupId.value = String(newGroup.b_id);
}
function deleteGroup() {
if (!selectedGroupId.value) return;
const hasChildren = localGroups.value.some(
(group) => String(group.b_parent_id) === selectedGroupId.value,
);
if (hasChildren) {
message.warning("该分组下还有子分组,请先删除子分组");
return;
}
if (props.referencedGroupIds.map(String).includes(selectedGroupId.value)) {
message.warning("该分组仍被列表或编辑配置引用,请先清空引用");
return;
}
localGroups.value = localGroups.value.filter((g) => String(g.b_id) !== selectedGroupId.value);
selectedGroupId.value = null;
}
async function saveAllGroups() {
if (localGroups.value.some((group) => !String(group.b_title || "").trim())) {
message.warning("请填写分组标题");
return;
}
const original = cloneData(props.groups || []);
const changes = diffRows(localGroups.value, original);
const req = tableChange("s_module_field_group", changes);
if (!hasChanges(req)) {
message.info("没有需要保存的变更");
return;
}
try {
const idMapping = await allocateTemporaryIds([
req.inserts || [],
req.updates || [],
req.deletes || [],
]);
remapForeignKeys(localGroups.value, ["b_parent_id"], idMapping);
await saveObjectApi([req]);
emit("saved", cloneData(localGroups.value), idMapping);
message.success("分组已保存");
emit("update:visible", false);
} catch (e) {
message.error("保存失败:" + (e.message || e));
}
}
function onClose() {
emit("update:visible", false);
}
</script>
<template>
<a-modal :open="visible" title="分组管理" width="650px" @cancel="onClose" @ok="saveAllGroups">
<div class="group-manager">
<div class="group-sidebar">
<div class="group-sidebar-header">
<span>分组树</span>
<a-button size="small" type="primary" @click="addGroup(null)">新增</a-button>
</div>
<FmsTree
v-if="treeData.length > 0"
class="group-tree"
:tree-data="treeData"
:selected-keys="selectedGroupId ? [selectedGroupId] : []"
@select="onTreeSelect"
>
<template #title="{ key, title: nodeTitle }">
<span class="group-tree-node">
<span>{{ nodeTitle }}</span>
<span class="group-tree-node-actions">
<a-button size="small" type="text" @click.stop="addGroup(key)">+</a-button>
</span>
</span>
</template>
</FmsTree>
<a-empty v-else description="暂无分组" />
</div>
<div v-if="selectedGroup" class="group-detail">
<a-form layout="vertical">
<a-form-item label="分组标题" required>
<a-input v-model:value="selectedGroup.b_title" />
</a-form-item>
<a-form-item label="多语言键">
<a-input
:value="selectedGroup.b_i18n"
@update:value="(v) => (selectedGroup.b_i18n = v || null)"
/>
</a-form-item>
<a-form-item label="排序号">
<a-input-number v-model:value="selectedGroup.b_xh" :min="0" />
</a-form-item>
<a-form-item label="启用">
<a-switch
:checked="selectedGroup.b_canuse === 1"
@update:checked="(v) => (selectedGroup.b_canuse = v ? 1 : 0)"
/>
</a-form-item>
<a-form-item>
<a-button danger size="small" @click="deleteGroup">删除此分组</a-button>
</a-form-item>
</a-form>
</div>
<a-empty v-else description="选择左侧分组节点编辑" class="group-detail-empty" />
</div>
</a-modal>
</template>
<style scoped lang="scss">
.group-manager {
display: flex;
gap: 16px;
min-height: 350px;
}
.group-sidebar {
display: flex;
flex-direction: column;
flex: none;
width: 240px;
border: 1px solid var(--fms-border);
border-radius: 4px;
overflow: hidden;
}
.group-sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
flex: none;
padding: 8px 12px;
border-bottom: 1px solid var(--fms-border);
font-size: 13px;
font-weight: 600;
}
.group-tree {
flex: 1;
min-height: 0;
overflow: auto;
padding: 4px 0;
}
.group-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.group-tree-node-actions {
display: none;
flex: none;
}
.group-tree-node:hover .group-tree-node-actions {
display: flex;
}
.group-detail {
flex: 1;
min-width: 0;
}
.group-detail-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
</style>