优化分类树加载逻辑,新增批量查询子节点接口以减少数据库往返,提升性能。重构相关服务和控制器,确保系统的可维护性和扩展性。

This commit is contained in:
geht
2026-05-15 09:58:30 +08:00
parent bc1de2c765
commit ffc390f3de
8 changed files with 85 additions and 13 deletions

View File

@@ -416,13 +416,7 @@ public class SysCategoryController {
* 递归求子节点 同步加载用到
*/
private void loadAllCategoryChildren(List<TreeSelectModel> ls) {
for (TreeSelectModel tsm : ls) {
List<TreeSelectModel> temp = this.sysCategoryService.queryListByPid(tsm.getKey());
if(temp!=null && temp.size()>0) {
tsm.setChildren(temp);
loadAllCategoryChildren(temp);
}
}
this.sysCategoryService.fillCategoryChildrenBatch(ls);
}
/**

View File

@@ -28,6 +28,11 @@ public interface SysCategoryMapper extends BaseMapper<SysCategory> {
*/
public List<TreeSelectModel> queryListByPid(@Param("pid") String pid,@Param("query") Map<String, String> query);
/**
* 批量按父 id 查询子节点(树同步加载按层拉取,避免 N+1
*/
List<TreeSelectModel> queryListByPidIn(@Param("pids") List<String> pids, @Param("query") Map<String, String> query);
/**
* 通过code查询分类字典表
* @param code

View File

@@ -33,5 +33,36 @@
</if>
</select>
<select id="queryListByPidIn" resultType="org.jeecg.modules.system.model.TreeSelectModel">
select code,
name as "title",
id as "key",
(case when has_child = '1' then 0 else 1 end) as isLeaf,
pid as parentId
from sys_category
where pid in
<foreach collection="pids" item="item" open="(" separator="," close=")">
#{item}
</foreach>
<if test="query!= null">
<if test="query.code !=null and query.code != ''">
and code = #{query.code}
</if>
<if test="query.name !=null and query.name != ''">
and name = #{query.name}
</if>
<if test="query.id !=null and query.id != ''">
and id = #{query.id}
</if>
<if test="query.createBy !=null and query.createBy != ''">
and create_by = #{query.createBy}
</if>
<if test="query.sysOrgCode !=null and query.sysOrgCode != ''">
and sys_org_code = #{query.sysOrgCode}
</if>
</if>
order by code
</select>
</mapper>

View File

@@ -98,4 +98,11 @@ public interface ISysCategoryService extends IService<SysCategory> {
*/
List<String> loadDictItemByNames(String names, boolean delNotExist);
/**
* 按层批量补全子节点(替代逐节点递归 queryListByPid显著减少数据库往返
*
* @param nodes 首层节点列表(通常来自 {@link #queryListByCode}
*/
void fillCategoryChildrenBatch(List<TreeSelectModel> nodes);
}

View File

@@ -126,6 +126,37 @@ public class SysCategoryServiceImpl extends ServiceImpl<SysCategoryMapper, SysCa
return baseMapper.queryListByPid(pid,condition);
}
@Override
public void fillCategoryChildrenBatch(List<TreeSelectModel> nodes) {
if (nodes == null || nodes.isEmpty()) {
return;
}
List<TreeSelectModel> frontier = new ArrayList<>(nodes);
while (!frontier.isEmpty()) {
List<String> pids = frontier.stream().map(TreeSelectModel::getKey).filter(Objects::nonNull).collect(Collectors.toList());
if (pids.isEmpty()) {
break;
}
List<TreeSelectModel> allChildren = baseMapper.queryListByPidIn(pids, null);
if (allChildren == null || allChildren.isEmpty()) {
break;
}
Map<String, List<TreeSelectModel>> byParent = allChildren.stream().collect(Collectors.groupingBy(TreeSelectModel::getParentId));
List<TreeSelectModel> nextFrontier = new ArrayList<>();
for (TreeSelectModel parent : frontier) {
List<TreeSelectModel> ch = byParent.get(parent.getKey());
if (ch != null && !ch.isEmpty()) {
parent.setChildren(ch);
nextFrontier.addAll(ch);
}
}
if (nextFrontier.isEmpty()) {
break;
}
frontier = nextFrontier;
}
}
@Override
public String queryIdByCode(String code) {
return baseMapper.queryIdByCode(code);

View File

@@ -114,6 +114,8 @@ const { tableContext, onExportXls, onImportXls } = useListPage({
title: '密炼物料信息',
api: list,
columns,
// 避免:表格默认 immediate 请求一次 + onMounted 末尾 reload 再请求一次(进入页列表闪两次)
immediate: false,
canResize: true,
formConfig: { labelWidth: 120, schemas: searchFormSchema, autoSubmitOnEnter: true, showAdvancedButton: true },
actionColumn: { width: 120 },
@@ -227,9 +229,8 @@ function findNodeByKey(nodes: Recordable[], key: string): Recordable | null {
async function loadCategoryTree() {
treeLoading.value = true;
try {
const root = await fetchMaterialCategoryRoot();
const [root, res] = await Promise.all([fetchMaterialCategoryRoot(), loadCategoryTreeRoot({ async: false, pcode: 'XSLMES_MATERIAL' })]);
materialCategoryRootId.value = root?.id != null ? String(root.id) : '';
const res = await loadCategoryTreeRoot({ async: false, pcode: 'XSLMES_MATERIAL' });
rawCategoryTree.value = Array.isArray(res) ? res : [];
if (!materialCategoryRootId.value || !rawCategoryTree.value.length) {
createMessage.warning('未加载到物料分类树,请确认分类字典根编码 XSLMES_MATERIAL 已存在。');

View File

@@ -271,9 +271,9 @@
async function loadCategoryTree() {
treeLoading.value = true;
try {
const root = await fetchUnitCategoryRoot();
// 根节点查询与整棵树接口并行,减少串行等待;后端已改为按层批量查子节点,降低 DB 往返
const [root, res] = await Promise.all([fetchUnitCategoryRoot(), loadUnitCategoryTreeRoot({ async: false, pcode: 'XSLMES_UNIT' })]);
unitCategoryRootId.value = root?.id != null ? String(root.id) : '';
const res = await loadUnitCategoryTreeRoot({ async: false, pcode: 'XSLMES_UNIT' });
rawUnitCategoryTree.value = Array.isArray(res) ? res : [];
if (!unitCategoryRootId.value || !rawUnitCategoryTree.value.length) {
createMessage.warning('未加载到单位分类树,请确认已执行库脚本且分类字典根编码为 XSLMES_UNIT。');
@@ -295,6 +295,8 @@
title: '单位管理',
api: list,
columns,
// 避免:表格默认 immediate 请求一次 + onMounted 末尾 reload 再请求一次(进入页列表闪两次)
immediate: false,
canResize: true,
formConfig: {
schemas: searchFormSchema,

View File

@@ -294,9 +294,8 @@
async function loadCategoryTree() {
treeLoading.value = true;
try {
const root = await fetchWarehouseCategoryRoot();
const [root, res] = await Promise.all([fetchWarehouseCategoryRoot(), loadCategoryTreeRoot({ async: false, pcode: 'XSLMES_WH' })]);
warehouseCategoryRootId.value = root?.id != null ? String(root.id) : '';
const res = await loadCategoryTreeRoot({ async: false, pcode: 'XSLMES_WH' });
rawWarehouseCategoryTree.value = Array.isArray(res) ? res : [];
if (!warehouseCategoryRootId.value || !rawWarehouseCategoryTree.value.length) {
createMessage.warning('未加载到仓库分类树,请确认已执行库脚本并已在「分类字典」中维护根节点 XSLMES_WH。');
@@ -385,6 +384,8 @@
title: '仓库管理',
api: list,
columns,
// 避免:表格默认 immediate 请求一次 + onMounted 末尾 reload 再请求一次(进入页列表闪两次)
immediate: false,
canResize: true,
formConfig: {
schemas: searchFormSchema,