242 lines
6.2 KiB
Vue
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>
|