20260723173317

This commit is contained in:
oneao committed 2026-07-23 17:33:18 +08:00
1 parent 59b3ccfb1c
commit 5f73b54802
33 files changed
+3064 -2108

No files matched your search

@@ -0,0 +1,256 @@
<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, tableChange, hasChanges } from "@/utils/dataChanges";
import { createTempId } from "@/utils/tempId";
const props = defineProps({
visible: { type: Boolean, default: false },
moduleId: { type: String, required: true },
groups: { type: Array, default: () => [] },
});
const emit = defineEmits(["update:visible", "update"]);
const localGroups = ref([]);
const selectedGroupId = ref(null);
const editForm = ref(null);
watch(
() => props.groups,
(val) => {
localGroups.value = cloneData(val || []);
},
{ immediate: true },
);
const treeData = computed(() => {
return buildTreeData(localGroups.value);
});
function buildTreeData(items) {
const map = new Map();
const roots = [];
items.forEach((item) => {
item.key = String(item.b_id);
map.set(item.key, item);
});
items.forEach((item) => {
if (item.b_parent_id && map.has(String(item.b_parent_id))) {
const parent = map.get(String(item.b_parent_id));
if (!parent.children) parent.children = [];
parent.children.push(item);
} else {
roots.push(item);
}
});
return roots.map((r) => ({
key: r.key,
title: r.b_title || `分组-${r.b_id}`,
children: r.children?.map((c) => ({
key: c.key,
title: c.b_title || `分组-${c.b_id}`,
children: c.children?.map((gc) => ({
key: gc.key,
title: gc.b_title || `分组-${gc.b_id}`,
})),
})) || [],
}));
}
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]);
const g = selectedGroup.value;
editForm.value = g ? { ...g } : null;
} else {
selectedGroupId.value = null;
editForm.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 * 10,
b_canuse: 1,
};
localGroups.value.push(newGroup);
selectedGroupId.value = String(newGroup.b_id);
editForm.value = { ...newGroup };
}
function deleteGroup() {
if (!selectedGroupId.value) return;
localGroups.value = localGroups.value.filter((g) => String(g.b_id) !== selectedGroupId.value);
selectedGroupId.value = null;
editForm.value = null;
}
async function saveAllGroups() {
const original = cloneData(props.groups || []);
const changes = diffRows(localGroups.value, original);
const req = tableChange("b_module_field_group", changes);
if (!hasChanges(req)) {
message.info("没有需要保存的变更");
return;
}
try {
await allocateTemporaryIds([req.inserts || [], req.updates || [], req.deletes || []]);
await saveObjectApi([req]);
emit("update", cloneData(localGroups.value));
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"
:field-names="{ title: 'title', key: 'key', children: 'children' }"
: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 class="group-detail" v-if="editForm">
<a-form layout="vertical">
<a-form-item label="分组标题" required>
<a-input
:value="editForm.b_title"
@update:value="(v) => editForm.b_title = v"
/>
</a-form-item>
<a-form-item label="多语言键">
<a-input
:value="editForm.b_i18n"
@update:value="(v) => editForm.b_i18n = v || null"
/>
</a-form-item>
<a-form-item label="排序号">
<a-input-number
:value="editForm.b_xh"
:min="0"
@update:value="(v) => editForm.b_xh = v"
/>
</a-form-item>
<a-form-item label="启用">
<a-switch
:checked="editForm.b_canuse === 1"
@update:checked="(v) => editForm.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>