forked from b2894lxlx/tuanwei-admin
fix(cms): 分类树防环——上级候选剔除自身子树,列表跳过自指向脏数据
与后端 checkParentValid 配套的前端侧防御(分类是自关联结构,成环会让分类树整个渲染不出来): - category-edit:新增 parentOptions 计算属性,修改分类时把「自己及自己的整棵子树」 从上级候选中剔除,避免在界面上就能选出环。 - index:parseData 先过滤掉 categoryId === parentId 的记录(toTreeData 遇到这类数据 会直接 throw,拖垮整个列表),并把被跳过的分类 id 提示给用户,让其去库里修正。
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
<a-form-item label="上级分类" name="parentId">
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
:tree-data="categoryList"
|
||||
:tree-data="parentOptions"
|
||||
tree-default-expand-all
|
||||
placeholder="请选择上级分类"
|
||||
:value="form.parentId || undefined"
|
||||
@@ -84,13 +84,12 @@
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -141,6 +140,26 @@
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/*
|
||||
* 上级分类候选:修改时要把自己及自己的整棵子树剔除,
|
||||
* 否则可以把上级选成自己或自己的下级,形成环导致分类树无法渲染。
|
||||
*/
|
||||
const parentOptions = computed(() => {
|
||||
const currentId = form.categoryId;
|
||||
if (currentId == null) {
|
||||
return props.categoryList ?? [];
|
||||
}
|
||||
const filterSelf = (list: ArticleCategory[]): ArticleCategory[] =>
|
||||
(list ?? [])
|
||||
.filter((d) => d.categoryId !== currentId)
|
||||
.map((d) =>
|
||||
d.children?.length
|
||||
? { ...d, children: filterSelf(d.children) }
|
||||
: { ...d }
|
||||
);
|
||||
return filterSelf(props.categoryList);
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
title: [
|
||||
|
||||
@@ -195,8 +195,23 @@
|
||||
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data: ArticleCategory[]) => {
|
||||
/*
|
||||
* 防御性过滤:分类是自关联结构,如果库里存在「自己指向自己」的脏数据,
|
||||
* toTreeData 会直接 throw,导致整个页面无法渲染(报 data error: {categoryId: x, parentId: x})。
|
||||
* 这里先剔除这类记录,只影响异常的那一行,不拖垮整个列表。
|
||||
*/
|
||||
const validData = (data ?? []).filter((d) => d.categoryId !== d.parentId);
|
||||
if (validData.length !== (data ?? []).length) {
|
||||
const badIds = (data ?? [])
|
||||
.filter((d) => d.categoryId === d.parentId)
|
||||
.map((d) => d.categoryId)
|
||||
.join('、');
|
||||
message.error(
|
||||
`分类 ${badIds} 的上级分类是它自己,已跳过显示,请在数据库中修正`
|
||||
);
|
||||
}
|
||||
return toTreeData({
|
||||
data: data.map((d) => {
|
||||
data: validData.map((d) => {
|
||||
return { ...d, key: d.categoryId, value: d.categoryId };
|
||||
}),
|
||||
idField: 'categoryId',
|
||||
|
||||
Reference in New Issue
Block a user