diff --git a/backend/src/main/java/com/designhub/controller/ConfigController.java b/backend/src/main/java/com/designhub/controller/ConfigController.java index 9e4835d..cae7b8a 100644 --- a/backend/src/main/java/com/designhub/controller/ConfigController.java +++ b/backend/src/main/java/com/designhub/controller/ConfigController.java @@ -29,18 +29,42 @@ public class ConfigController { // ============ 需求类型管理 ============ - @Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)") + @Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)- 平铺列表") @GetMapping("/demand-types") public Result> getAllDemandTypes() { return Result.success(configService.getAllDemandTypes()); } + + @Operation(summary = "获取所有需求类型(树形结构)") + @GetMapping("/demand-types/tree") + public Result> getDemandTypesTree() { + return Result.success(configService.getDemandTypesTree()); + } + + @Operation(summary = "获取所有大分类(parentId为null的)") + @GetMapping("/demand-types/parents") + public Result> getParentDemandTypes() { + return Result.success(configService.getParentDemandTypes()); + } + + @Operation(summary = "根据父ID获取子分类") + @GetMapping("/demand-types/parent/{parentId}") + public Result> getDemandTypesByParentId(@PathVariable Long parentId) { + return Result.success(configService.getDemandTypesByParentId(parentId)); + } - @Operation(summary = "获取所有启用的需求类型(用于下拉选择)") + @Operation(summary = "获取所有启用的需求类型(用于下拉选择)- 树形结构") @GetMapping("/demand-types/enabled") public Result> getEnabledDemandTypes() { return Result.success(configService.getEnabledDemandTypes()); } + @Operation(summary = "获取所有启用的需求类型(平铺列表)") + @GetMapping("/demand-types/enabled/list") + public Result> getEnabledDemandTypesList() { + return Result.success(configService.getEnabledDemandTypesList()); + } + @Operation(summary = "根据ID获取需求类型") @GetMapping("/demand-types/{id:\\d+}") public Result getDemandTypeById(@PathVariable Long id) { diff --git a/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java b/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java index adca708..b03bef0 100644 --- a/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java +++ b/backend/src/main/java/com/designhub/controller/TaskSubTypeController.java @@ -78,28 +78,4 @@ public class TaskSubTypeController { taskSubTypeService.updateSortOrder(id, sortOrder); return Result.success(); } - - @Operation(summary = "获取树形结构列表(父子层级)") - @GetMapping("/tree") - public Result> getTreeList() { - return Result.success(taskSubTypeService.getTreeList()); - } - - @Operation(summary = "获取启用的树形结构") - @GetMapping("/tree/enabled") - public Result> getEnabledTreeList() { - return Result.success(taskSubTypeService.getEnabledTreeList()); - } - - @Operation(summary = "获取父级分类列表(需求类型大分类)") - @GetMapping("/parents") - public Result> getParentSubTypes() { - return Result.success(taskSubTypeService.getParentSubTypes()); - } - - @Operation(summary = "获取指定父级下的子分类") - @GetMapping("/children/{parentId}") - public Result> getChildrenByParentId(@PathVariable Long parentId) { - return Result.success(taskSubTypeService.getChildrenByParentId(parentId)); - } } diff --git a/backend/src/main/java/com/designhub/entity/DemandType.java b/backend/src/main/java/com/designhub/entity/DemandType.java index 3fb72ff..16bd9d4 100644 --- a/backend/src/main/java/com/designhub/entity/DemandType.java +++ b/backend/src/main/java/com/designhub/entity/DemandType.java @@ -3,7 +3,9 @@ package com.designhub.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.List; /** * 需求类型实体类 @@ -20,6 +22,12 @@ public class DemandType implements Serializable { @TableId(value = "id", type = IdType.AUTO) private Long id; + /** + * 父级ID,null表示大分类 + */ + @TableField("parent_id") + private Long parentId; + /** * 类型名称 */ @@ -27,10 +35,16 @@ public class DemandType implements Serializable { private String name; /** - * 单价(积分) + * 单价分值(支持小数) */ @TableField("unit_score") - private Integer unitScore; + private BigDecimal unitScore; + + /** + * 旧单价积分字段(兼容) + */ + @TableField(exist = false) + private Integer unitScoreInt; /** * 预估工时(小时) @@ -71,5 +85,17 @@ public class DemandType implements Serializable { */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; + + /** + * 子分类列表(不存储在数据库) + */ + @TableField(exist = false) + private List children; + + /** + * 父分类名称(不存储在数据库) + */ + @TableField(exist = false) + private String parentName; } diff --git a/backend/src/main/java/com/designhub/entity/TaskSubType.java b/backend/src/main/java/com/designhub/entity/TaskSubType.java index 40fe049..c30bfe8 100644 --- a/backend/src/main/java/com/designhub/entity/TaskSubType.java +++ b/backend/src/main/java/com/designhub/entity/TaskSubType.java @@ -4,9 +4,7 @@ import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serializable; -import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.List; /** * 任务子类型实体 @@ -22,11 +20,6 @@ public class TaskSubType implements Serializable { @TableId(type = IdType.AUTO) private Long id; - /** - * 父级ID,NULL表示顶级分类(需求类型大分类) - */ - private Long parentId; - /** * 子类型名称 */ @@ -42,11 +35,6 @@ public class TaskSubType implements Serializable { */ private String description; - /** - * 单价分值 - */ - private BigDecimal unitPrice; - /** * 排序 */ @@ -63,24 +51,6 @@ public class TaskSubType implements Serializable { */ private Integer checkInventory; - /** - * 可接单部门ID列表(逗号分隔),只在父级分类上设置 - * 子分类自动继承父分类的设置 - */ - private String allowedDeptIds; - - /** - * 子分类列表(非数据库字段) - */ - @TableField(exist = false) - private List children; - - /** - * 前端使用的部门ID数组(非数据库字段) - */ - @TableField(exist = false) - private List departmentIds; - /** * 创建时间 */ @@ -165,28 +135,4 @@ public class TaskSubType implements Serializable { public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } - - public Long getParentId() { - return parentId; - } - - public void setParentId(Long parentId) { - this.parentId = parentId; - } - - public BigDecimal getUnitPrice() { - return unitPrice; - } - - public void setUnitPrice(BigDecimal unitPrice) { - this.unitPrice = unitPrice; - } - - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } } diff --git a/backend/src/main/java/com/designhub/service/ConfigService.java b/backend/src/main/java/com/designhub/service/ConfigService.java index a3ee383..21d24c9 100644 --- a/backend/src/main/java/com/designhub/service/ConfigService.java +++ b/backend/src/main/java/com/designhub/service/ConfigService.java @@ -23,31 +23,126 @@ public class ConfigService { // ============ 需求类型管理 ============ /** - * 获取所有需求类型(包括禁用的,用于管理页面) + * 获取所有需求类型(包括禁用的,用于管理页面)- 平铺列表 */ public List getAllDemandTypes() { + List list = demandTypeMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(DemandType::getParentId) + .orderByAsc(DemandType::getSortOrder) + ); + // 填充父分类名称 + for (DemandType dt : list) { + if (dt.getParentId() != null) { + DemandType parent = demandTypeMapper.selectById(dt.getParentId()); + if (parent != null) { + dt.setParentName(parent.getName()); + } + } + } + return list; + } + + /** + * 获取所有需求类型(树形结构) + */ + public List getDemandTypesTree() { + List allTypes = demandTypeMapper.selectList( + new LambdaQueryWrapper() + .orderByAsc(DemandType::getSortOrder) + ); + // 构建树形结构 + return buildDemandTypeTree(allTypes); + } + + /** + * 获取所有大分类(parentId为null的) + */ + public List getParentDemandTypes() { return demandTypeMapper.selectList( new LambdaQueryWrapper() + .isNull(DemandType::getParentId) + .orderByAsc(DemandType::getSortOrder) + ); + } + + /** + * 根据父ID获取子分类 + */ + public List getDemandTypesByParentId(Long parentId) { + return demandTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(DemandType::getParentId, parentId) .orderByAsc(DemandType::getSortOrder) ); } /** - * 获取所有启用的需求类型(用于下拉选择) + * 获取所有启用的需求类型(用于下拉选择)- 树形结构 */ public List getEnabledDemandTypes() { - return demandTypeMapper.selectList( + List allTypes = demandTypeMapper.selectList( new LambdaQueryWrapper() .eq(DemandType::getIsEnabled, 1) .orderByAsc(DemandType::getSortOrder) ); + return buildDemandTypeTree(allTypes); + } + + /** + * 获取所有启用的需求类型(平铺列表,用于表单选择) + */ + public List getEnabledDemandTypesList() { + List list = demandTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(DemandType::getIsEnabled, 1) + .orderByAsc(DemandType::getParentId) + .orderByAsc(DemandType::getSortOrder) + ); + // 填充父分类名称 + for (DemandType dt : list) { + if (dt.getParentId() != null) { + DemandType parent = demandTypeMapper.selectById(dt.getParentId()); + if (parent != null) { + dt.setParentName(parent.getName()); + } + } + } + return list; + } + + /** + * 构建需求类型树形结构 + */ + private List buildDemandTypeTree(List allTypes) { + // 获取所有根节点(parentId为null) + List roots = allTypes.stream() + .filter(dt -> dt.getParentId() == null) + .collect(java.util.stream.Collectors.toList()); + + // 为每个根节点填充子节点 + for (DemandType root : roots) { + List children = allTypes.stream() + .filter(dt -> root.getId().equals(dt.getParentId())) + .collect(java.util.stream.Collectors.toList()); + root.setChildren(children); + } + + return roots; } /** * 根据ID获取需求类型 */ public DemandType getDemandTypeById(Long id) { - return demandTypeMapper.selectById(id); + DemandType dt = demandTypeMapper.selectById(id); + if (dt != null && dt.getParentId() != null) { + DemandType parent = demandTypeMapper.selectById(dt.getParentId()); + if (parent != null) { + dt.setParentName(parent.getName()); + } + } + return dt; } /** @@ -66,9 +161,15 @@ public class ConfigService { } /** - * 删除需求类型 + * 删除需求类型(如果是大分类,同时删除所有子分类) */ public boolean deleteDemandType(Long id) { + // 先删除所有子分类 + demandTypeMapper.delete( + new LambdaQueryWrapper() + .eq(DemandType::getParentId, id) + ); + // 再删除自身 return demandTypeMapper.deleteById(id) > 0; } diff --git a/backend/src/main/java/com/designhub/service/TaskSubTypeService.java b/backend/src/main/java/com/designhub/service/TaskSubTypeService.java index 2c96132..74cb851 100644 --- a/backend/src/main/java/com/designhub/service/TaskSubTypeService.java +++ b/backend/src/main/java/com/designhub/service/TaskSubTypeService.java @@ -10,10 +10,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** * 任务子类型服务 @@ -145,82 +142,4 @@ public class TaskSubTypeService { subType.setSortOrder(sortOrder); taskSubTypeMapper.updateById(subType); } - - /** - * 获取树形结构的子类型列表 - * 返回父子层级结构,便于前端展示和筛选 - */ - public List getTreeList() { - List allSubTypes = taskSubTypeMapper.selectList( - new LambdaQueryWrapper() - .orderByAsc(TaskSubType::getSortOrder) - ); - - // 找出所有顶级分类(parentId为null) - List parents = allSubTypes.stream() - .filter(st -> st.getParentId() == null) - .collect(Collectors.toList()); - - // 按parentId分组子分类 - Map> childrenMap = allSubTypes.stream() - .filter(st -> st.getParentId() != null) - .collect(Collectors.groupingBy(TaskSubType::getParentId)); - - // 为每个父分类设置子分类 - for (TaskSubType parent : parents) { - parent.setChildren(childrenMap.getOrDefault(parent.getId(), new ArrayList<>())); - } - - return parents; - } - - /** - * 获取所有父级分类(需求类型大分类) - */ - public List getParentSubTypes() { - return taskSubTypeMapper.selectList( - new LambdaQueryWrapper() - .isNull(TaskSubType::getParentId) - .orderByAsc(TaskSubType::getSortOrder) - ); - } - - /** - * 获取指定父级下的子分类 - */ - public List getChildrenByParentId(Long parentId) { - return taskSubTypeMapper.selectList( - new LambdaQueryWrapper() - .eq(TaskSubType::getParentId, parentId) - .orderByAsc(TaskSubType::getSortOrder) - ); - } - - /** - * 获取启用的树形结构 - */ - public List getEnabledTreeList() { - List allSubTypes = taskSubTypeMapper.selectList( - new LambdaQueryWrapper() - .eq(TaskSubType::getIsEnabled, 1) - .orderByAsc(TaskSubType::getSortOrder) - ); - - // 找出所有顶级分类(parentId为null) - List parents = allSubTypes.stream() - .filter(st -> st.getParentId() == null) - .collect(Collectors.toList()); - - // 按parentId分组子分类 - Map> childrenMap = allSubTypes.stream() - .filter(st -> st.getParentId() != null) - .collect(Collectors.groupingBy(TaskSubType::getParentId)); - - // 为每个父分类设置子分类 - for (TaskSubType parent : parents) { - parent.setChildren(childrenMap.getOrDefault(parent.getId(), new ArrayList<>())); - } - - return parents; - } } diff --git a/database/update_demand_type_hierarchy.sql b/database/update_demand_type_hierarchy.sql new file mode 100644 index 0000000..419139b --- /dev/null +++ b/database/update_demand_type_hierarchy.sql @@ -0,0 +1,13 @@ +-- 需求类型添加层级结构支持 +-- 添加父级ID字段 +ALTER TABLE task_type ADD COLUMN IF NOT EXISTS parent_id BIGINT DEFAULT NULL COMMENT '父级ID,null表示大分类'; + +-- 修改单价字段为支持小数 +ALTER TABLE task_type MODIFY COLUMN unit_score DECIMAL(10, 3) DEFAULT 0 COMMENT '单价分值'; + +-- 添加索引 +CREATE INDEX IF NOT EXISTS idx_task_type_parent_id ON task_type(parent_id); + +-- 为了保持兼容,确保现有数据的parent_id为null(即都是大分类) +UPDATE task_type SET parent_id = NULL WHERE parent_id IS NULL; + diff --git a/frontend/src/api/config.ts b/frontend/src/api/config.ts index 3c6344f..255ade8 100644 --- a/frontend/src/api/config.ts +++ b/frontend/src/api/config.ts @@ -2,14 +2,17 @@ import request from '@/utils/request' export interface DemandType { id: number + parentId?: number | null // 父级ID,null表示大分类 name: string - unitScore: number + unitScore: number // 单价分值(支持小数) estimatedHours: number description?: string sortOrder?: number isEnabled?: number allowedDeptIds?: string // 可接单部门ID列表(逗号分隔) departmentIds?: number[] // 前端使用的部门ID数组 + children?: DemandType[] // 子分类列表 + parentName?: string // 父分类名称 } export interface Brand { @@ -63,35 +66,57 @@ export interface TeamScorePool { export interface TaskSubType { id: number - parentId?: number | null // 父级ID,null表示顶级分类 name: string code: string description?: string - unitPrice?: number // 单价分值 sortOrder?: number isEnabled?: number - checkInventory?: number // 是否检查库存 - allowedDeptIds?: string // 可接单部门ID(逗号分隔),只在父级分类设置 - departmentIds?: number[] // 前端使用的部门ID数组 createTime?: string updateTime?: string - children?: TaskSubType[] // 子分类列表 } /** - * 获取所有需求类型(包括禁用的,用于管理页面) + * 获取所有需求类型(包括禁用的,用于管理页面)- 平铺列表 */ export const getAllDemandTypes = () => { return request.get('/config/demand-types') } /** - * 获取所有启用的需求类型(用于下拉选择) + * 获取所有需求类型(树形结构) + */ +export const getDemandTypesTree = () => { + return request.get('/config/demand-types/tree') +} + +/** + * 获取所有大分类(parentId为null的) + */ +export const getParentDemandTypes = () => { + return request.get('/config/demand-types/parents') +} + +/** + * 根据父ID获取子分类 + */ +export const getDemandTypesByParentId = (parentId: number) => { + return request.get(`/config/demand-types/parent/${parentId}`) +} + +/** + * 获取所有启用的需求类型(用于下拉选择)- 树形结构 */ export const getEnabledDemandTypes = () => { return request.get('/config/demand-types/enabled') } +/** + * 获取所有启用的需求类型(平铺列表) + */ +export const getEnabledDemandTypesList = () => { + return request.get('/config/demand-types/enabled/list') +} + /** * 获取所有品牌(包括禁用的,用于管理页面) */ @@ -317,31 +342,3 @@ export const toggleTaskSubTypeEnabled = (id: number) => { return request.post(`/task-sub-types/${id}/toggle-enabled`) } -/** - * 获取树形结构的任务子类型(父子层级) - */ -export const getTaskSubTypeTree = () => { - return request.get('/task-sub-types/tree') -} - -/** - * 获取启用的树形结构 - */ -export const getEnabledTaskSubTypeTree = () => { - return request.get('/task-sub-types/tree/enabled') -} - -/** - * 获取父级分类列表(需求类型大分类) - */ -export const getTaskSubTypeParents = () => { - return request.get('/task-sub-types/parents') -} - -/** - * 获取指定父级下的子分类 - */ -export const getTaskSubTypeChildren = (parentId: number) => { - return request.get(`/task-sub-types/children/${parentId}`) -} - diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 18a01e7..bcb0dc3 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -89,22 +89,41 @@

需求类型管理

- - - 添加类型 - + + + + 添加大分类 + + + + 添加子分类 + +
+ + + +