feat: 需求类型支持大分类+子细分结构,部门选择器展开显示所有层级
This commit is contained in:
parent
d130e310ea
commit
39e8b43630
@ -29,18 +29,42 @@ public class ConfigController {
|
||||
|
||||
// ============ 需求类型管理 ============
|
||||
|
||||
@Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)")
|
||||
@Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)- 平铺列表")
|
||||
@GetMapping("/demand-types")
|
||||
public Result<List<DemandType>> getAllDemandTypes() {
|
||||
return Result.success(configService.getAllDemandTypes());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有启用的需求类型(用于下拉选择)")
|
||||
@Operation(summary = "获取所有需求类型(树形结构)")
|
||||
@GetMapping("/demand-types/tree")
|
||||
public Result<List<DemandType>> getDemandTypesTree() {
|
||||
return Result.success(configService.getDemandTypesTree());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有大分类(parentId为null的)")
|
||||
@GetMapping("/demand-types/parents")
|
||||
public Result<List<DemandType>> getParentDemandTypes() {
|
||||
return Result.success(configService.getParentDemandTypes());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据父ID获取子分类")
|
||||
@GetMapping("/demand-types/parent/{parentId}")
|
||||
public Result<List<DemandType>> getDemandTypesByParentId(@PathVariable Long parentId) {
|
||||
return Result.success(configService.getDemandTypesByParentId(parentId));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有启用的需求类型(用于下拉选择)- 树形结构")
|
||||
@GetMapping("/demand-types/enabled")
|
||||
public Result<List<DemandType>> getEnabledDemandTypes() {
|
||||
return Result.success(configService.getEnabledDemandTypes());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有启用的需求类型(平铺列表)")
|
||||
@GetMapping("/demand-types/enabled/list")
|
||||
public Result<List<DemandType>> getEnabledDemandTypesList() {
|
||||
return Result.success(configService.getEnabledDemandTypesList());
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID获取需求类型")
|
||||
@GetMapping("/demand-types/{id:\\d+}")
|
||||
public Result<DemandType> getDemandTypeById(@PathVariable Long id) {
|
||||
|
||||
@ -78,28 +78,4 @@ public class TaskSubTypeController {
|
||||
taskSubTypeService.updateSortOrder(id, sortOrder);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "获取树形结构列表(父子层级)")
|
||||
@GetMapping("/tree")
|
||||
public Result<List<TaskSubType>> getTreeList() {
|
||||
return Result.success(taskSubTypeService.getTreeList());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取启用的树形结构")
|
||||
@GetMapping("/tree/enabled")
|
||||
public Result<List<TaskSubType>> getEnabledTreeList() {
|
||||
return Result.success(taskSubTypeService.getEnabledTreeList());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取父级分类列表(需求类型大分类)")
|
||||
@GetMapping("/parents")
|
||||
public Result<List<TaskSubType>> getParentSubTypes() {
|
||||
return Result.success(taskSubTypeService.getParentSubTypes());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取指定父级下的子分类")
|
||||
@GetMapping("/children/{parentId}")
|
||||
public Result<List<TaskSubType>> getChildrenByParentId(@PathVariable Long parentId) {
|
||||
return Result.success(taskSubTypeService.getChildrenByParentId(parentId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<DemandType> children;
|
||||
|
||||
/**
|
||||
* 父分类名称(不存储在数据库)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String parentName;
|
||||
}
|
||||
|
||||
|
||||
@ -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<TaskSubType> children;
|
||||
|
||||
/**
|
||||
* 前端使用的部门ID数组(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> 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<TaskSubType> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<TaskSubType> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,31 +23,126 @@ public class ConfigService {
|
||||
// ============ 需求类型管理 ============
|
||||
|
||||
/**
|
||||
* 获取所有需求类型(包括禁用的,用于管理页面)
|
||||
* 获取所有需求类型(包括禁用的,用于管理页面)- 平铺列表
|
||||
*/
|
||||
public List<DemandType> getAllDemandTypes() {
|
||||
List<DemandType> list = demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.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<DemandType> getDemandTypesTree() {
|
||||
List<DemandType> allTypes = demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.orderByAsc(DemandType::getSortOrder)
|
||||
);
|
||||
// 构建树形结构
|
||||
return buildDemandTypeTree(allTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有大分类(parentId为null的)
|
||||
*/
|
||||
public List<DemandType> getParentDemandTypes() {
|
||||
return demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.isNull(DemandType::getParentId)
|
||||
.orderByAsc(DemandType::getSortOrder)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(用于下拉选择)
|
||||
* 根据父ID获取子分类
|
||||
*/
|
||||
public List<DemandType> getDemandTypesByParentId(Long parentId) {
|
||||
return demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.eq(DemandType::getParentId, parentId)
|
||||
.orderByAsc(DemandType::getSortOrder)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(用于下拉选择)- 树形结构
|
||||
*/
|
||||
public List<DemandType> getEnabledDemandTypes() {
|
||||
return demandTypeMapper.selectList(
|
||||
List<DemandType> allTypes = demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.eq(DemandType::getIsEnabled, 1)
|
||||
.orderByAsc(DemandType::getSortOrder)
|
||||
);
|
||||
return buildDemandTypeTree(allTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(平铺列表,用于表单选择)
|
||||
*/
|
||||
public List<DemandType> getEnabledDemandTypesList() {
|
||||
List<DemandType> list = demandTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<DemandType>()
|
||||
.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<DemandType> buildDemandTypeTree(List<DemandType> allTypes) {
|
||||
// 获取所有根节点(parentId为null)
|
||||
List<DemandType> roots = allTypes.stream()
|
||||
.filter(dt -> dt.getParentId() == null)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// 为每个根节点填充子节点
|
||||
for (DemandType root : roots) {
|
||||
List<DemandType> 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<DemandType>()
|
||||
.eq(DemandType::getParentId, id)
|
||||
);
|
||||
// 再删除自身
|
||||
return demandTypeMapper.deleteById(id) > 0;
|
||||
}
|
||||
|
||||
|
||||
@ -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<TaskSubType> getTreeList() {
|
||||
List<TaskSubType> allSubTypes = taskSubTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<TaskSubType>()
|
||||
.orderByAsc(TaskSubType::getSortOrder)
|
||||
);
|
||||
|
||||
// 找出所有顶级分类(parentId为null)
|
||||
List<TaskSubType> parents = allSubTypes.stream()
|
||||
.filter(st -> st.getParentId() == null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 按parentId分组子分类
|
||||
Map<Long, List<TaskSubType>> 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<TaskSubType> getParentSubTypes() {
|
||||
return taskSubTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<TaskSubType>()
|
||||
.isNull(TaskSubType::getParentId)
|
||||
.orderByAsc(TaskSubType::getSortOrder)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定父级下的子分类
|
||||
*/
|
||||
public List<TaskSubType> getChildrenByParentId(Long parentId) {
|
||||
return taskSubTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<TaskSubType>()
|
||||
.eq(TaskSubType::getParentId, parentId)
|
||||
.orderByAsc(TaskSubType::getSortOrder)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的树形结构
|
||||
*/
|
||||
public List<TaskSubType> getEnabledTreeList() {
|
||||
List<TaskSubType> allSubTypes = taskSubTypeMapper.selectList(
|
||||
new LambdaQueryWrapper<TaskSubType>()
|
||||
.eq(TaskSubType::getIsEnabled, 1)
|
||||
.orderByAsc(TaskSubType::getSortOrder)
|
||||
);
|
||||
|
||||
// 找出所有顶级分类(parentId为null)
|
||||
List<TaskSubType> parents = allSubTypes.stream()
|
||||
.filter(st -> st.getParentId() == null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 按parentId分组子分类
|
||||
Map<Long, List<TaskSubType>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
13
database/update_demand_type_hierarchy.sql
Normal file
13
database/update_demand_type_hierarchy.sql
Normal file
@ -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;
|
||||
|
||||
@ -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<any>('/config/demand-types')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(用于下拉选择)
|
||||
* 获取所有需求类型(树形结构)
|
||||
*/
|
||||
export const getDemandTypesTree = () => {
|
||||
return request.get<any>('/config/demand-types/tree')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有大分类(parentId为null的)
|
||||
*/
|
||||
export const getParentDemandTypes = () => {
|
||||
return request.get<any>('/config/demand-types/parents')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父ID获取子分类
|
||||
*/
|
||||
export const getDemandTypesByParentId = (parentId: number) => {
|
||||
return request.get<any>(`/config/demand-types/parent/${parentId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(用于下拉选择)- 树形结构
|
||||
*/
|
||||
export const getEnabledDemandTypes = () => {
|
||||
return request.get<any>('/config/demand-types/enabled')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的需求类型(平铺列表)
|
||||
*/
|
||||
export const getEnabledDemandTypesList = () => {
|
||||
return request.get<any>('/config/demand-types/enabled/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有品牌(包括禁用的,用于管理页面)
|
||||
*/
|
||||
@ -317,31 +342,3 @@ export const toggleTaskSubTypeEnabled = (id: number) => {
|
||||
return request.post<any>(`/task-sub-types/${id}/toggle-enabled`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树形结构的任务子类型(父子层级)
|
||||
*/
|
||||
export const getTaskSubTypeTree = () => {
|
||||
return request.get<any>('/task-sub-types/tree')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的树形结构
|
||||
*/
|
||||
export const getEnabledTaskSubTypeTree = () => {
|
||||
return request.get<any>('/task-sub-types/tree/enabled')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父级分类列表(需求类型大分类)
|
||||
*/
|
||||
export const getTaskSubTypeParents = () => {
|
||||
return request.get<any>('/task-sub-types/parents')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定父级下的子分类
|
||||
*/
|
||||
export const getTaskSubTypeChildren = (parentId: number) => {
|
||||
return request.get<any>(`/task-sub-types/children/${parentId}`)
|
||||
}
|
||||
|
||||
|
||||
@ -89,22 +89,41 @@
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">需求类型管理</h2>
|
||||
<a-button type="primary" @click="showDemandTypeModal()">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="showDemandTypeModal(null, null)">
|
||||
<template #icon><Icon name="plus" size="sm" /></template>
|
||||
添加类型
|
||||
添加大分类
|
||||
</a-button>
|
||||
<a-button @click="showDemandTypeModal(null, 'sub')" :disabled="parentDemandTypes.length === 0">
|
||||
<template #icon><Icon name="plus" size="sm" /></template>
|
||||
添加子分类
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-alert type="info" show-icon style="margin-bottom: 16px;">
|
||||
<template #message>
|
||||
<span>大分类用于设置可接单部门限制,子分类继承大分类的部门设置。单价分值支持小数(如0.35)</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<a-table
|
||||
:columns="demandTypeColumns"
|
||||
:data-source="demandTypes"
|
||||
:loading="loading.demandTypes"
|
||||
row-key="id"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
:pagination="{ pageSize: 20 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<span>{{ record.name || record.description || '--' }}</span>
|
||||
<template v-if="column.key === 'parentName'">
|
||||
<span v-if="record.parentId">{{ record.parentName || '--' }}</span>
|
||||
<a-tag v-else color="blue">大分类</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'name'">
|
||||
<span :style="{ paddingLeft: record.parentId ? '20px' : '0' }">
|
||||
<span v-if="record.parentId">└ </span>
|
||||
{{ record.name || '--' }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'unitScore'">
|
||||
<span>{{ record.unitScore ?? '--' }}</span>
|
||||
@ -112,6 +131,15 @@
|
||||
<template v-else-if="column.key === 'estimatedHours'">
|
||||
<span>{{ record.estimatedHours ?? '--' }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'allowedDepts'">
|
||||
<template v-if="!record.parentId">
|
||||
<span v-if="record.allowedDeptIds">
|
||||
{{ getDeptNamesFromIds(record.allowedDeptIds) }}
|
||||
</span>
|
||||
<span v-else class="text-muted">所有接单部门</span>
|
||||
</template>
|
||||
<span v-else class="text-muted">继承大分类</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.isEnabled === 1"
|
||||
@ -121,7 +149,18 @@
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="showDemandTypeModal(record)">编辑</a-button>
|
||||
<a-popconfirm title="确定删除?" @confirm="deleteItem('demandType', record.id)">
|
||||
<a-button
|
||||
v-if="!record.parentId"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="showDemandTypeModal(null, 'sub', record.id)"
|
||||
>
|
||||
添加子分类
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="record.parentId ? '确定删除此子分类?' : '确定删除此大分类及其所有子分类?'"
|
||||
@confirm="deleteItem('demandType', record.id)"
|
||||
>
|
||||
<a-button type="link" danger size="small">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
@ -136,57 +175,21 @@
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">任务子类型管理</h2>
|
||||
<a-space>
|
||||
<a-select
|
||||
v-model:value="taskSubTypeFilter.parentId"
|
||||
placeholder="按需求类型筛选"
|
||||
style="width: 160px"
|
||||
allow-clear
|
||||
@change="filterTaskSubTypes"
|
||||
>
|
||||
<a-select-option :value="null">全部类型</a-select-option>
|
||||
<a-select-option v-for="p in taskSubTypeParents" :key="p.id" :value="p.id">
|
||||
{{ p.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="primary" @click="showTaskSubTypeModal(null, true)">
|
||||
<template #icon><Icon name="plus" size="sm" /></template>
|
||||
添加大分类
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showTaskSubTypeModal()">
|
||||
<template #icon><Icon name="plus" size="sm" /></template>
|
||||
添加子类型
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="taskSubTypeColumns"
|
||||
:data-source="filteredTaskSubTypes"
|
||||
:data-source="taskSubTypes"
|
||||
:loading="loading.taskSubTypes"
|
||||
row-key="id"
|
||||
:pagination="{ pageSize: 15 }"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'parentName'">
|
||||
<a-tag v-if="!record.parentId" color="blue">{{ record.name }}</a-tag>
|
||||
<span v-else>{{ getParentName(record.parentId) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'allowedDepts'">
|
||||
<template v-if="!record.parentId">
|
||||
<!-- 大分类显示接单部门 -->
|
||||
<span v-if="record.allowedDeptIds" style="font-size: 12px;">
|
||||
{{ getAllowedDeptNames(record.allowedDeptIds) }}
|
||||
</span>
|
||||
<span v-else style="color: #999; font-size: 12px;">全部部门</span>
|
||||
</template>
|
||||
<span v-else style="color: #999; font-size: 12px;">继承大分类</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'checkInventory'">
|
||||
<a-tag v-if="record.checkInventory === 1" color="orange">是</a-tag>
|
||||
<span v-else style="color: #999;">否</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.isEnabled === 1"
|
||||
@change="(checked: boolean) => toggleStatus('taskSubType', record.id, checked)"
|
||||
@ -1158,24 +1161,43 @@
|
||||
<!-- 需求类型弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modals.demandType"
|
||||
:title="editingItem ? '编辑需求类型' : '添加需求类型'"
|
||||
:title="getDemandTypeModalTitle()"
|
||||
@ok="saveDemandType"
|
||||
@cancel="closeModal('demandType')"
|
||||
width="600px"
|
||||
>
|
||||
<a-form :model="formData.demandType" layout="vertical">
|
||||
<!-- 选择所属大分类(仅子分类显示) -->
|
||||
<a-form-item v-if="formData.demandType.isSubType" label="所属大分类" required>
|
||||
<a-select
|
||||
v-model:value="formData.demandType.parentId"
|
||||
placeholder="请选择所属大分类"
|
||||
:disabled="!!editingItem"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="parent in parentDemandTypes"
|
||||
:key="parent.id"
|
||||
:value="parent.id"
|
||||
>
|
||||
{{ parent.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="类型名称" required>
|
||||
<a-input v-model:value="formData.demandType.name" placeholder="请输入类型名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="单价积分" required>
|
||||
<a-form-item label="单价分值" required>
|
||||
<a-input-number
|
||||
v-model:value="formData.demandType.unitScore"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
style="width: 100%"
|
||||
placeholder="请输入单价积分"
|
||||
placeholder="请输入单价分值(支持小数,如0.35)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="预计时长(小时)" required>
|
||||
<a-form-item label="预计时长(小时)">
|
||||
<a-input-number
|
||||
v-model:value="formData.demandType.estimatedHours"
|
||||
:min="0"
|
||||
@ -1187,58 +1209,57 @@
|
||||
<a-form-item label="描述">
|
||||
<a-textarea v-model:value="formData.demandType.description" :rows="3" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
<a-form-item label="可接单部门">
|
||||
|
||||
<!-- 可接单部门(仅大分类显示) -->
|
||||
<a-form-item v-if="!formData.demandType.isSubType" label="可接单部门">
|
||||
<a-select
|
||||
v-model:value="formData.demandType.departmentIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择可接单部门(不选则所有接单部门都可接)"
|
||||
allow-clear
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-select-opt-group
|
||||
v-for="group in flatDepartmentOptions"
|
||||
:key="group.label"
|
||||
:label="group.label"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="dept in receivingDepartments"
|
||||
v-for="dept in group.options"
|
||||
:key="dept.id"
|
||||
:value="dept.id"
|
||||
>
|
||||
{{ dept.deptName }}
|
||||
{{ dept.displayName }}
|
||||
</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</a-select>
|
||||
<div class="form-tip">
|
||||
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
|
||||
限制哪些接单部门可以接这种类型的任务,不选则所有接单部门都可接
|
||||
选择父级部门会自动包含所有子部门。子分类会继承大分类的部门设置。
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 子分类提示 -->
|
||||
<a-alert v-if="formData.demandType.isSubType" type="info" show-icon>
|
||||
<template #message>
|
||||
<span>子分类将继承大分类的可接单部门设置</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 任务子类型弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modals.taskSubType"
|
||||
:title="taskSubTypeModalTitle"
|
||||
:title="editingItem ? '编辑任务子类型' : '添加任务子类型'"
|
||||
@ok="saveTaskSubType"
|
||||
@cancel="closeModal('taskSubType')"
|
||||
width="640px"
|
||||
>
|
||||
<a-form :model="formData.taskSubType" layout="vertical">
|
||||
<a-form-item v-if="!isAddingParentType" label="所属需求类型(大分类)">
|
||||
<a-select
|
||||
v-model:value="formData.taskSubType.parentId"
|
||||
placeholder="选择大分类(留空则为顶级分类)"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option v-for="p in taskSubTypeParents" :key="p.id" :value="p.id">
|
||||
{{ p.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div class="form-tip" style="margin-top: 4px;">
|
||||
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
|
||||
选择大分类后,此子类型将归属于该分类下,并继承大分类的接单部门设置
|
||||
</div>
|
||||
<a-form-item label="子类型名称" required>
|
||||
<a-input v-model:value="formData.taskSubType.name" placeholder="请输入子类型名称(如:新品、优化)" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="isAddingParentType ? '需求类型名称' : '需求类型细分名称'" required>
|
||||
<a-input v-model:value="formData.taskSubType.name" :placeholder="isAddingParentType ? '请输入大分类名称(如:主图、详情)' : '请输入细分名称(如:制作、部分调整)'" />
|
||||
</a-form-item>
|
||||
<a-form-item label="编码" required>
|
||||
<a-form-item label="子类型编码" required>
|
||||
<a-input v-model:value="formData.taskSubType.code" placeholder="留空自动生成">
|
||||
<template #addonAfter>
|
||||
<a-button size="small" type="link" @click="autoGenerateTaskSubTypeCode" style="padding: 0 8px;">
|
||||
@ -1247,60 +1268,25 @@
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<!-- 只有大分类才需要设置接单部门 -->
|
||||
<a-form-item v-if="isAddingParentType || (!formData.taskSubType.parentId && editingItem)" label="可接单部门">
|
||||
<a-tree-select
|
||||
v-model:value="formData.taskSubType.departmentIds"
|
||||
:tree-data="receivingDeptTreeData"
|
||||
:field-names="{ children: 'children', label: 'title', value: 'value' }"
|
||||
tree-checkable
|
||||
:show-checked-strategy="SHOW_PARENT"
|
||||
placeholder="选择可接单的部门(选择父部门会自动包含所有子部门)"
|
||||
style="width: 100%"
|
||||
:tree-default-expand-all="true"
|
||||
:max-tag-count="3"
|
||||
/>
|
||||
<div class="form-tip" style="margin-top: 4px;">
|
||||
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
|
||||
选择父部门后,其下所有子部门都可接单。子分类会自动继承此设置。
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isAddingParentType" label="单价分值">
|
||||
<a-input-number
|
||||
v-model:value="formData.taskSubType.unitPrice"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="3"
|
||||
style="width: 100%"
|
||||
placeholder="请输入单价分值(如:0.6)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述">
|
||||
<a-textarea v-model:value="formData.taskSubType.description" :rows="2" placeholder="请输入描述" />
|
||||
<a-textarea v-model:value="formData.taskSubType.description" :rows="3" placeholder="请输入描述" />
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="排序">
|
||||
<a-input-number
|
||||
v-model:value="formData.taskSubType.sortOrder"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
style="width: 100%"
|
||||
placeholder="值越小越靠前"
|
||||
placeholder="排序值越小越靠前"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item v-if="!isAddingParentType" label="是否检查库存">
|
||||
<a-form-item label="是否检查库存">
|
||||
<a-switch v-model:checked="formData.taskSubType.checkInventory" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-alert v-if="!isAddingParentType && formData.taskSubType.checkInventory" type="info" show-icon style="margin-top: 8px;">
|
||||
<template #message>
|
||||
<div class="form-tip" style="margin-top: 8px;">
|
||||
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
|
||||
开启后,发布此类型任务时需要输入商品条码并检查库存,库存不足则无法发布任务
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@ -1737,8 +1723,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||
import { message, TreeSelect } from 'ant-design-vue'
|
||||
const { SHOW_PARENT } = TreeSelect
|
||||
import { message } from 'ant-design-vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useSiteSettingsStore } from '@/stores/siteSettings'
|
||||
@ -1750,7 +1735,6 @@ import {
|
||||
getPlatforms, createPlatform, updatePlatform, deletePlatform,
|
||||
getCategories, createCategory, updateCategory, deleteCategory,
|
||||
getAllTaskSubTypes, createTaskSubType, updateTaskSubType, deleteTaskSubType, toggleTaskSubTypeEnabled,
|
||||
getTaskSubTypeParents,
|
||||
type DemandType, type Brand, type Platform, type Category, type TeamScorePool, type TaskSubType
|
||||
} from '@/api/config'
|
||||
import {
|
||||
@ -1864,9 +1848,6 @@ const modals = reactive({
|
||||
|
||||
const demandTypes = ref<DemandType[]>([])
|
||||
const taskSubTypes = ref<TaskSubType[]>([])
|
||||
const taskSubTypeParents = ref<TaskSubType[]>([]) // 需求类型大分类列表
|
||||
const taskSubTypeFilter = reactive({ parentId: null as number | null }) // 筛选条件
|
||||
const isAddingParentType = ref(false) // 是否正在添加大分类
|
||||
const brands = ref<Brand[]>([])
|
||||
const platforms = ref<Platform[]>([])
|
||||
const categories = ref<Category[]>([])
|
||||
@ -1881,107 +1862,6 @@ const scoreAdjustForm = reactive<ScoreAdjustPayload>({
|
||||
})
|
||||
const rechargeAmount = ref<number>(0)
|
||||
|
||||
// 任务子类型相关计算属性和方法
|
||||
const filteredTaskSubTypes = computed(() => {
|
||||
if (!taskSubTypeFilter.parentId) {
|
||||
return taskSubTypes.value
|
||||
}
|
||||
return taskSubTypes.value.filter(t =>
|
||||
t.parentId === taskSubTypeFilter.parentId || t.id === taskSubTypeFilter.parentId
|
||||
)
|
||||
})
|
||||
|
||||
const taskSubTypeModalTitle = computed(() => {
|
||||
if (editingItem.value) {
|
||||
return editingItem.value.parentId ? '编辑需求类型细分' : '编辑需求类型'
|
||||
}
|
||||
return isAddingParentType.value ? '添加需求类型(大分类)' : '添加需求类型细分'
|
||||
})
|
||||
|
||||
const getParentName = (parentId: number | null | undefined) => {
|
||||
if (!parentId) return '-'
|
||||
const parent = taskSubTypeParents.value.find(p => p.id === parentId)
|
||||
return parent ? parent.name : '-'
|
||||
}
|
||||
|
||||
const filterTaskSubTypes = () => {
|
||||
// 筛选器变化时触发重新渲染
|
||||
}
|
||||
|
||||
// 构建接单部门树形数据(只显示接单部门,带层级标识)
|
||||
interface DeptTreeNode {
|
||||
title: string
|
||||
value: number
|
||||
key: number
|
||||
children?: DeptTreeNode[]
|
||||
}
|
||||
|
||||
const receivingDeptTreeData = computed((): DeptTreeNode[] => {
|
||||
// 只获取接单部门
|
||||
const receivingDepts = departments.value.filter(d => d.deptType === 'receiving')
|
||||
|
||||
// 构建层级映射
|
||||
const deptMap = new Map<number, DepartmentOverview>()
|
||||
receivingDepts.forEach(d => deptMap.set(d.id!, d))
|
||||
|
||||
// 找出所有顶级部门(parentId为0或null)
|
||||
const rootDepts = receivingDepts.filter(d => !d.parentId || d.parentId === 0)
|
||||
|
||||
// 递归构建树
|
||||
const buildTree = (dept: DepartmentOverview, level: number): DeptTreeNode => {
|
||||
const levelPrefix = level === 1 ? '【一级】' : level === 2 ? '【二级】' : level === 3 ? '【三级】' : ''
|
||||
const children = receivingDepts
|
||||
.filter(d => d.parentId === dept.id)
|
||||
.map(child => buildTree(child, level + 1))
|
||||
|
||||
return {
|
||||
title: `${levelPrefix}${dept.deptName}`,
|
||||
value: dept.id!,
|
||||
key: dept.id!,
|
||||
children: children.length > 0 ? children : undefined
|
||||
}
|
||||
}
|
||||
|
||||
return rootDepts.map(d => buildTree(d, 1))
|
||||
})
|
||||
|
||||
// 获取部门及其所有子部门ID
|
||||
const getAllChildDeptIds = (deptId: number): number[] => {
|
||||
const result: number[] = [deptId]
|
||||
const children = departments.value.filter(d => d.parentId === deptId)
|
||||
children.forEach(child => {
|
||||
result.push(...getAllChildDeptIds(child.id!))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// 展开选中的部门ID,包含所有子部门
|
||||
const expandDeptIdsWithChildren = (deptIds: number[]): number[] => {
|
||||
const allIds = new Set<number>()
|
||||
deptIds.forEach(id => {
|
||||
getAllChildDeptIds(id).forEach(childId => allIds.add(childId))
|
||||
})
|
||||
return Array.from(allIds)
|
||||
}
|
||||
|
||||
// 获取接单部门名称(用于表格显示)
|
||||
const getAllowedDeptNames = (allowedDeptIds: string): string => {
|
||||
if (!allowedDeptIds) return '全部部门'
|
||||
const ids = allowedDeptIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
|
||||
// 只显示顶级选中的部门(非子部门)
|
||||
const topLevelIds = ids.filter(id => {
|
||||
const dept = departments.value.find(d => d.id === id)
|
||||
if (!dept) return false
|
||||
// 如果父部门也在列表中,则不显示此部门
|
||||
return !ids.includes(dept.parentId || 0)
|
||||
})
|
||||
const names = topLevelIds.map(id => {
|
||||
const dept = departments.value.find(d => d.id === id)
|
||||
return dept ? dept.deptName : ''
|
||||
}).filter(n => n)
|
||||
return names.length > 0 ? names.slice(0, 3).join('、') + (names.length > 3 ? '...' : '') : '全部部门'
|
||||
}
|
||||
|
||||
const userLoading = ref(false)
|
||||
const usersLoaded = ref(false)
|
||||
const usersByDept = ref<Record<number, User[]>>({})
|
||||
@ -2063,7 +1943,7 @@ const defaultDepartmentForm = (): DepartmentForm => ({
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
demandType: {} as Partial<DemandType> & { departmentIds?: number[] },
|
||||
demandType: {} as Partial<DemandType> & { departmentIds?: number[], isSubType?: boolean },
|
||||
taskSubType: {} as Partial<TaskSubType>,
|
||||
brand: {} as Partial<Brand>,
|
||||
platform: {} as Partial<Platform>,
|
||||
@ -2182,6 +2062,84 @@ const receivingDepartments = computed(() => {
|
||||
return departments.value.filter(dept => dept.deptType === 'receiving')
|
||||
})
|
||||
|
||||
// 大分类列表(parentId为null的需求类型)
|
||||
const parentDemandTypes = computed(() => {
|
||||
return demandTypes.value.filter(dt => !dt.parentId)
|
||||
})
|
||||
|
||||
// 扁平化的部门选项(带层级显示)
|
||||
const flatDepartmentOptions = computed(() => {
|
||||
const receiving = departments.value.filter(dept => dept.deptType === 'receiving')
|
||||
|
||||
// 按层级分组
|
||||
const level1 = receiving.filter(d => !d.parentId)
|
||||
const groups: { label: string, options: { id: number, displayName: string }[] }[] = []
|
||||
|
||||
// 添加一级部门组
|
||||
if (level1.length > 0) {
|
||||
groups.push({
|
||||
label: '一级部门',
|
||||
options: level1.map(d => ({
|
||||
id: d.id!,
|
||||
displayName: d.deptName
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
// 添加二级部门组
|
||||
const level2 = receiving.filter(d => d.parentId && level1.some(l1 => l1.id === d.parentId))
|
||||
if (level2.length > 0) {
|
||||
groups.push({
|
||||
label: '二级部门',
|
||||
options: level2.map(d => {
|
||||
const parent = level1.find(l1 => l1.id === d.parentId)
|
||||
return {
|
||||
id: d.id!,
|
||||
displayName: parent ? `${parent.deptName} / ${d.deptName}` : d.deptName
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 添加三级部门组
|
||||
const level3 = receiving.filter(d => d.parentId && level2.some(l2 => l2.id === d.parentId))
|
||||
if (level3.length > 0) {
|
||||
groups.push({
|
||||
label: '三级部门',
|
||||
options: level3.map(d => {
|
||||
const parent2 = level2.find(l2 => l2.id === d.parentId)
|
||||
const parent1 = parent2 ? level1.find(l1 => l1.id === parent2.parentId) : null
|
||||
const path = [parent1?.deptName, parent2?.deptName, d.deptName].filter(Boolean).join(' / ')
|
||||
return {
|
||||
id: d.id!,
|
||||
displayName: path
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return groups
|
||||
})
|
||||
|
||||
// 根据部门ID字符串获取部门名称
|
||||
const getDeptNamesFromIds = (idsStr: string) => {
|
||||
if (!idsStr) return ''
|
||||
const ids = idsStr.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
|
||||
const names = ids.map(id => {
|
||||
const dept = departments.value.find(d => d.id === id)
|
||||
return dept?.deptName || `ID:${id}`
|
||||
})
|
||||
return names.join(', ')
|
||||
}
|
||||
|
||||
// 获取需求类型弹窗标题
|
||||
const getDemandTypeModalTitle = () => {
|
||||
if (editingItem.value) {
|
||||
return formData.demandType.parentId ? '编辑子分类' : '编辑大分类'
|
||||
}
|
||||
return formData.demandType.isSubType ? '添加子分类' : '添加大分类'
|
||||
}
|
||||
|
||||
const leaderOptions = computed(() =>
|
||||
allUsers.value.map((user) => ({
|
||||
label: user.realName || user.username || `用户${user.id}`,
|
||||
@ -2210,28 +2168,27 @@ const getLimitPeriodLabel = (value?: string) => {
|
||||
|
||||
// 表格列定义
|
||||
const demandTypeColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
|
||||
{ title: '分类', key: 'parentName', width: 100 },
|
||||
{ title: '类型名称', dataIndex: 'name', key: 'name' },
|
||||
{ title: '单价积分', dataIndex: 'unitScore', key: 'unitScore' },
|
||||
{ title: '预计时长(h)', dataIndex: 'estimatedHours', key: 'estimatedHours' },
|
||||
{ title: '单价分值', dataIndex: 'unitScore', key: 'unitScore', width: 100 },
|
||||
{ title: '预计时长(h)', dataIndex: 'estimatedHours', key: 'estimatedHours', width: 110 },
|
||||
{ title: '可接单部门', key: 'allowedDepts', ellipsis: true },
|
||||
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 60 },
|
||||
{ title: '状态', key: 'status', width: 80 },
|
||||
{ title: '操作', key: 'actions', width: 200 }
|
||||
]
|
||||
|
||||
const taskSubTypeColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: '子类型名称', dataIndex: 'name', key: 'name' },
|
||||
{ title: '子类型编码', dataIndex: 'code', key: 'code' },
|
||||
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
||||
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 80 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '操作', key: 'actions', width: 150 }
|
||||
]
|
||||
|
||||
const taskSubTypeColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 50 },
|
||||
{ title: '需求类型', key: 'parentName', width: 90 },
|
||||
{ title: '需求类型细分', dataIndex: 'name', key: 'name', width: 150 },
|
||||
{ title: '单价分值', dataIndex: 'unitPrice', key: 'unitPrice', width: 80 },
|
||||
{ title: '可接单部门', key: 'allowedDepts', ellipsis: true },
|
||||
{ title: '检查库存', key: 'checkInventory', width: 80 },
|
||||
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 60 },
|
||||
{ title: '状态', key: 'status', width: 70 },
|
||||
{ title: '操作', key: 'actions', width: 120 }
|
||||
]
|
||||
|
||||
const brandColumns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: '品牌名称', dataIndex: 'name', key: 'name' },
|
||||
@ -2304,13 +2261,8 @@ const loadDemandTypes = async () => {
|
||||
const loadTaskSubTypes = async () => {
|
||||
loading.taskSubTypes = true
|
||||
try {
|
||||
// 同时加载所有子类型和父级分类
|
||||
const [allResponse, parentsResponse] = await Promise.all([
|
||||
getAllTaskSubTypes(),
|
||||
getTaskSubTypeParents()
|
||||
])
|
||||
taskSubTypes.value = allResponse.data || []
|
||||
taskSubTypeParents.value = parentsResponse.data || []
|
||||
const response = await getAllTaskSubTypes()
|
||||
taskSubTypes.value = response.data || []
|
||||
} catch (error: any) {
|
||||
console.error('加载任务子类型失败:', error)
|
||||
const errorMsg = error?.response?.data?.message || error?.message || '加载任务子类型失败'
|
||||
@ -2568,49 +2520,39 @@ const autoGenerateTaskSubTypeCode = () => {
|
||||
}
|
||||
|
||||
// 显示弹窗
|
||||
const showDemandTypeModal = async (item?: DemandType) => {
|
||||
editingItem.value = item
|
||||
const showDemandTypeModal = async (item?: DemandType | null, type?: string | null, defaultParentId?: number) => {
|
||||
editingItem.value = item || null
|
||||
|
||||
if (item) {
|
||||
// 编辑模式:将allowedDeptIds(逗号分隔字符串)转换为数组
|
||||
const departmentIds = item.allowedDeptIds
|
||||
? item.allowedDeptIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
|
||||
: []
|
||||
formData.demandType = { ...item, departmentIds }
|
||||
formData.demandType = {
|
||||
...item,
|
||||
departmentIds,
|
||||
isSubType: !!item.parentId
|
||||
}
|
||||
} else {
|
||||
// 新建模式
|
||||
formData.demandType = { sortOrder: 0, isEnabled: 1, departmentIds: [] }
|
||||
const isSubType = type === 'sub'
|
||||
formData.demandType = {
|
||||
sortOrder: 0,
|
||||
isEnabled: 1,
|
||||
departmentIds: [],
|
||||
isSubType,
|
||||
parentId: isSubType ? (defaultParentId || null) : null
|
||||
}
|
||||
}
|
||||
|
||||
modals.demandType = true
|
||||
}
|
||||
|
||||
const showTaskSubTypeModal = (item?: TaskSubType | null, addParent: boolean = false) => {
|
||||
const showTaskSubTypeModal = (item?: TaskSubType) => {
|
||||
editingItem.value = item
|
||||
isAddingParentType.value = addParent
|
||||
|
||||
if (item) {
|
||||
// 编辑模式:将allowedDeptIds(逗号分隔字符串)转换为数组
|
||||
const departmentIds = item.allowedDeptIds
|
||||
? item.allowedDeptIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
|
||||
: []
|
||||
formData.taskSubType = {
|
||||
...item,
|
||||
checkInventory: item.checkInventory === 1,
|
||||
unitPrice: item.unitPrice || 0,
|
||||
departmentIds
|
||||
}
|
||||
} else {
|
||||
// 新建模式
|
||||
formData.taskSubType = {
|
||||
parentId: addParent ? null : undefined, // 如果是添加大分类,parentId为null
|
||||
sortOrder: 0,
|
||||
isEnabled: 1,
|
||||
checkInventory: false,
|
||||
unitPrice: 0,
|
||||
departmentIds: []
|
||||
}
|
||||
}
|
||||
formData.taskSubType = item
|
||||
? { ...item, checkInventory: item.checkInventory === 1 }
|
||||
: { sortOrder: 0, isEnabled: 1, checkInventory: false }
|
||||
modals.taskSubType = true
|
||||
}
|
||||
|
||||
@ -2678,6 +2620,12 @@ const closeModal = (type: string) => {
|
||||
// 保存操作
|
||||
const saveDemandType = async () => {
|
||||
try {
|
||||
// 子分类必须选择父级
|
||||
if (formData.demandType.isSubType && !formData.demandType.parentId) {
|
||||
message.error('请选择所属大分类')
|
||||
return
|
||||
}
|
||||
|
||||
// 构建保存数据,确保字段名正确
|
||||
const saveData: Partial<DemandType> = {
|
||||
name: formData.demandType.name,
|
||||
@ -2686,8 +2634,10 @@ const saveDemandType = async () => {
|
||||
description: formData.demandType.description,
|
||||
sortOrder: formData.demandType.sortOrder ?? 0,
|
||||
isEnabled: formData.demandType.isEnabled ?? 1,
|
||||
// 将部门ID数组转换为逗号分隔的字符串
|
||||
allowedDeptIds: formData.demandType.departmentIds?.length > 0
|
||||
// 父级ID(子分类需要设置)
|
||||
parentId: formData.demandType.isSubType ? formData.demandType.parentId : null,
|
||||
// 将部门ID数组转换为逗号分隔的字符串(仅大分类需要设置)
|
||||
allowedDeptIds: !formData.demandType.isSubType && formData.demandType.departmentIds?.length > 0
|
||||
? formData.demandType.departmentIds.join(',')
|
||||
: null
|
||||
}
|
||||
@ -2713,26 +2663,13 @@ const saveDemandType = async () => {
|
||||
|
||||
const saveTaskSubType = async () => {
|
||||
try {
|
||||
// 处理接单部门:只有大分类需要设置,选中的部门会自动展开包含所有子部门
|
||||
const isParentType = isAddingParentType.value || (!formData.taskSubType.parentId && editingItem.value)
|
||||
let allowedDeptIds: string | null = null
|
||||
|
||||
if (isParentType && formData.taskSubType.departmentIds && formData.taskSubType.departmentIds.length > 0) {
|
||||
// 展开所有选中的部门,包含其子部门
|
||||
const expandedIds = expandDeptIdsWithChildren(formData.taskSubType.departmentIds as number[])
|
||||
allowedDeptIds = expandedIds.join(',')
|
||||
}
|
||||
|
||||
const saveData: Partial<TaskSubType> & { checkInventory?: number; allowedDeptIds?: string | null } = {
|
||||
parentId: isAddingParentType.value ? null : (formData.taskSubType.parentId || null),
|
||||
const saveData: Partial<TaskSubType> & { checkInventory?: number } = {
|
||||
name: formData.taskSubType.name,
|
||||
code: formData.taskSubType.code,
|
||||
description: formData.taskSubType.description,
|
||||
unitPrice: formData.taskSubType.unitPrice ?? 0,
|
||||
sortOrder: formData.taskSubType.sortOrder ?? 0,
|
||||
isEnabled: formData.taskSubType.isEnabled ?? 1,
|
||||
checkInventory: isAddingParentType.value ? 0 : ((formData.taskSubType as any).checkInventory ? 1 : 0),
|
||||
allowedDeptIds: isParentType ? allowedDeptIds : undefined // 只有大分类保存接单部门设置
|
||||
checkInventory: (formData.taskSubType as any).checkInventory ? 1 : 0
|
||||
}
|
||||
|
||||
if (editingItem.value) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user