feat: 增加字典树接口
This commit is contained in:
parent
bd4937a8c7
commit
40180ee0b0
@ -23,6 +23,7 @@ import {
|
||||
AdminDictBodyDto,
|
||||
AdminDictDto,
|
||||
AdminDictQueryDto,
|
||||
AdminDictTreeDto,
|
||||
AdminDictUpdateDto,
|
||||
DictDto,
|
||||
} from './dict.dto';
|
||||
@ -74,6 +75,35 @@ export class DictController {
|
||||
return vbenPage(page.items, page.total);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: '获取字典树列表' })
|
||||
@ApiArrayResponse(AdminDictTreeDto, [
|
||||
{
|
||||
id: '2041700000000300001',
|
||||
dictCode: 'COMPONENT_TYPE',
|
||||
label: '图表',
|
||||
value: '1',
|
||||
childrenCode: 'CHART',
|
||||
sort: 1,
|
||||
status: 1,
|
||||
treeKey: '2041700000000300001',
|
||||
children: [
|
||||
{
|
||||
id: '2041700000000300002',
|
||||
dictCode: 'CHART',
|
||||
label: '折线图',
|
||||
value: '1',
|
||||
sort: 1,
|
||||
status: 1,
|
||||
treeKey: '2041700000000300001/2041700000000300002',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
@Get('tree')
|
||||
async tree(@Query() query: AdminDictQueryDto) {
|
||||
return vbenSuccess(await this.dictService.tree(query));
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: '获取字典编码选项' })
|
||||
@Get('codes')
|
||||
async codes() {
|
||||
|
||||
@ -66,6 +66,18 @@ export class AdminDictDto {
|
||||
updateTime?: Date;
|
||||
}
|
||||
|
||||
export class AdminDictTreeDto extends AdminDictDto {
|
||||
@ApiProperty({
|
||||
example: '2041700000000300001/2041700000000300002',
|
||||
})
|
||||
treeKey: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: () => [AdminDictTreeDto],
|
||||
})
|
||||
children?: AdminDictTreeDto[];
|
||||
}
|
||||
|
||||
export class AdminDictQueryDto {
|
||||
@ApiPropertyOptional()
|
||||
page?: number | string;
|
||||
|
||||
@ -17,6 +17,23 @@ export type AdminDictItem = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
type AdminDictSerialized = {
|
||||
childrenCode?: string | null;
|
||||
createTime?: Date;
|
||||
dictCode: string;
|
||||
id: string;
|
||||
label: string;
|
||||
sort?: number;
|
||||
status?: number;
|
||||
updateTime?: Date;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type AdminDictTreeItem = AdminDictSerialized & {
|
||||
children?: AdminDictTreeItem[];
|
||||
treeKey: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class DictService implements OnApplicationBootstrap {
|
||||
constructor(
|
||||
@ -106,6 +123,23 @@ export class DictService implements OnApplicationBootstrap {
|
||||
}));
|
||||
}
|
||||
|
||||
async tree(query: AdminDictQueryDto = {}) {
|
||||
const items = await this.dictRepository.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
order: {
|
||||
dictCode: 'ASC',
|
||||
sort: 'ASC',
|
||||
createTime: 'ASC',
|
||||
},
|
||||
});
|
||||
const serializedItems = items.map((item) => this.serializeDict(item));
|
||||
const visibleItems = this.filterTreeItems(serializedItems, query);
|
||||
|
||||
return this.buildDictTree(visibleItems);
|
||||
}
|
||||
|
||||
async save(body: AdminDictBodyDto) {
|
||||
const input = this.normalizeInput(body);
|
||||
const existing = await this.findByCodeValue(input.dictCode, input.value);
|
||||
@ -274,6 +308,207 @@ export class DictService implements OnApplicationBootstrap {
|
||||
});
|
||||
}
|
||||
|
||||
private buildDictTree(items: AdminDictSerialized[]): AdminDictTreeItem[] {
|
||||
const byDictCode = this.groupItemsByDictCode(items);
|
||||
const dictCodes = new Set(items.map((item) => item.dictCode));
|
||||
const referencedCodes = new Set(
|
||||
items
|
||||
.map((item) => this.normalizeText(item.childrenCode))
|
||||
.filter((childrenCode) => childrenCode && dictCodes.has(childrenCode)),
|
||||
);
|
||||
const rootCodes = [...dictCodes].filter(
|
||||
(code) => !referencedCodes.has(code),
|
||||
);
|
||||
const targetRootCodes = rootCodes.length > 0 ? rootCodes : [...dictCodes];
|
||||
|
||||
return items
|
||||
.filter((item) => targetRootCodes.includes(item.dictCode))
|
||||
.map((item) =>
|
||||
this.createTreeNode(
|
||||
item,
|
||||
byDictCode,
|
||||
item.id,
|
||||
new Set([item.dictCode]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private createTreeNode(
|
||||
item: AdminDictSerialized,
|
||||
byDictCode: Map<string, AdminDictSerialized[]>,
|
||||
treeKey: string,
|
||||
pathCodes: Set<string>,
|
||||
): AdminDictTreeItem {
|
||||
const childrenCode = this.normalizeText(item.childrenCode);
|
||||
const children =
|
||||
childrenCode && !pathCodes.has(childrenCode)
|
||||
? byDictCode.get(childrenCode)
|
||||
: undefined;
|
||||
const nextPathCodes = new Set(pathCodes);
|
||||
if (childrenCode) nextPathCodes.add(childrenCode);
|
||||
const node: AdminDictTreeItem = {
|
||||
...item,
|
||||
treeKey,
|
||||
};
|
||||
|
||||
if (children?.length) {
|
||||
node.children = children.map((child) =>
|
||||
this.createTreeNode(
|
||||
child,
|
||||
byDictCode,
|
||||
`${treeKey}/${child.id}`,
|
||||
nextPathCodes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private filterTreeItems(
|
||||
items: AdminDictSerialized[],
|
||||
query: AdminDictQueryDto,
|
||||
) {
|
||||
if (!this.hasTreeFilter(query)) return items;
|
||||
|
||||
const byDictCode = this.groupItemsByDictCode(items);
|
||||
const parentsByChildrenCode = this.groupParentsByChildrenCode(items);
|
||||
const visibleIds = new Set<string>();
|
||||
const matchedItems = items.filter((item) =>
|
||||
this.matchesTreeFilter(item, query),
|
||||
);
|
||||
|
||||
matchedItems.forEach((item) => {
|
||||
this.collectRelatedTreeItems(
|
||||
item,
|
||||
byDictCode,
|
||||
parentsByChildrenCode,
|
||||
visibleIds,
|
||||
);
|
||||
});
|
||||
|
||||
return items.filter((item) => visibleIds.has(item.id));
|
||||
}
|
||||
|
||||
private collectRelatedTreeItems(
|
||||
item: AdminDictSerialized,
|
||||
byDictCode: Map<string, AdminDictSerialized[]>,
|
||||
parentsByChildrenCode: Map<string, AdminDictSerialized[]>,
|
||||
visibleIds: Set<string>,
|
||||
) {
|
||||
if (visibleIds.has(item.id)) return;
|
||||
|
||||
visibleIds.add(item.id);
|
||||
|
||||
const parents = parentsByChildrenCode.get(item.dictCode) || [];
|
||||
parents.forEach((parent) =>
|
||||
this.collectRelatedTreeItems(
|
||||
parent,
|
||||
byDictCode,
|
||||
parentsByChildrenCode,
|
||||
visibleIds,
|
||||
),
|
||||
);
|
||||
|
||||
const childrenCode = this.normalizeText(item.childrenCode);
|
||||
if (!childrenCode) return;
|
||||
|
||||
const children = byDictCode.get(childrenCode) || [];
|
||||
children.forEach((child) =>
|
||||
this.collectRelatedTreeItems(
|
||||
child,
|
||||
byDictCode,
|
||||
parentsByChildrenCode,
|
||||
visibleIds,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private groupItemsByDictCode(items: AdminDictSerialized[]) {
|
||||
const map = new Map<string, AdminDictSerialized[]>();
|
||||
|
||||
items.forEach((item) => {
|
||||
const list = map.get(item.dictCode) || [];
|
||||
list.push(item);
|
||||
map.set(item.dictCode, list);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private groupParentsByChildrenCode(items: AdminDictSerialized[]) {
|
||||
const map = new Map<string, AdminDictSerialized[]>();
|
||||
|
||||
items.forEach((item) => {
|
||||
const childrenCode = this.normalizeText(item.childrenCode);
|
||||
if (!childrenCode) return;
|
||||
|
||||
const list = map.get(childrenCode) || [];
|
||||
list.push(item);
|
||||
map.set(childrenCode, list);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private hasTreeFilter(query: AdminDictQueryDto) {
|
||||
return (
|
||||
[
|
||||
query.childrenCode,
|
||||
query.dictCode,
|
||||
query.keyword,
|
||||
query.label,
|
||||
query.value,
|
||||
].some((value) => !!this.normalizeText(value)) ||
|
||||
['0', '1'].includes(String(query.status))
|
||||
);
|
||||
}
|
||||
|
||||
private matchesTreeFilter(
|
||||
item: AdminDictSerialized,
|
||||
query: AdminDictQueryDto,
|
||||
) {
|
||||
const keyword = this.normalizeText(query.keyword);
|
||||
if (
|
||||
keyword &&
|
||||
![item.childrenCode, item.dictCode, item.label, item.value].some(
|
||||
(value) => this.includesText(value, keyword),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.matchesLike(item.dictCode, query.dictCode)) return false;
|
||||
if (!this.matchesLike(item.label, query.label)) return false;
|
||||
if (!this.matchesLike(item.value, query.value)) return false;
|
||||
if (!this.matchesLike(item.childrenCode, query.childrenCode)) return false;
|
||||
|
||||
if (['0', '1'].includes(String(query.status))) {
|
||||
return Number(item.status) === Number(query.status);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private matchesLike(
|
||||
value: number | string | null | undefined,
|
||||
keyword?: string,
|
||||
) {
|
||||
const normalizedKeyword = this.normalizeText(keyword);
|
||||
if (!normalizedKeyword) return true;
|
||||
|
||||
return this.includesText(value, normalizedKeyword);
|
||||
}
|
||||
|
||||
private includesText(
|
||||
value: number | string | null | undefined,
|
||||
keyword: string,
|
||||
) {
|
||||
return this.normalizeText(value)
|
||||
.toLowerCase()
|
||||
.includes(keyword.toLowerCase());
|
||||
}
|
||||
|
||||
private normalizeInput(body: AdminDictBodyDto): Partial<AdminDict> {
|
||||
const dictCode = this.normalizeText(body.dictCode);
|
||||
const label = this.normalizeText(body.label);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user