feat: 可接任务页面增加接单部门筛选和显示标签

1. 后端 TaskController/TaskService 增加 filterDeptId 参数支持按接单部门筛选
2. 前端增加接单部门筛选下拉框
3. 任务卡片上显示可接单部门标签
This commit is contained in:
Your Name 2026-01-16 16:48:58 +08:00
parent ea3680cb67
commit 9e257caf15
3 changed files with 169 additions and 11 deletions

View File

@ -257,6 +257,7 @@ public class TaskController {
@Parameter(description = "发布人姓名") @RequestParam(required = false) String publisherName,
@Parameter(description = "发布时间范围:today/week/month") @RequestParam(required = false) String timeRange,
@Parameter(description = "排序方式:latest/score/deadline/hot") @RequestParam(required = false) String sortBy,
@Parameter(description = "筛选可接单部门ID") @RequestParam(required = false) Long filterDeptId,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize,
@RequestHeader("Authorization") String authHeader) {
@ -268,13 +269,13 @@ public class TaskController {
log.info("查询任务大厅: taskTypeId={}, demandTypeId={}, brandId={}, categoryId={}, platformId={}, " +
"taskSubType={}, minScore={}, maxScore={}, keyword={}, publisherName={}, timeRange={}, " +
"sortBy={}, page={}, pageSize={}, userDeptId={}",
"sortBy={}, filterDeptId={}, page={}, pageSize={}, userDeptId={}",
taskTypeId, demandTypeId, brandId, categoryId, platformId, taskSubType,
minScore, maxScore, keyword, publisherName, timeRange, sortBy, page, pageSize, userDeptId);
minScore, maxScore, keyword, publisherName, timeRange, sortBy, filterDeptId, page, pageSize, userDeptId);
IPage<Task> taskPage = taskService.getTaskHall(page, pageSize, taskTypeId, demandTypeId,
brandId, categoryId, platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy, userDeptId);
publisherName, timeRange, sortBy, userDeptId, filterDeptId);
return Result.success(taskPage);
}

View File

@ -1022,21 +1022,22 @@ public class TaskService {
/**
* 获取任务大厅(根据用户部门筛选可接单的任务)
* @param filterDeptId 筛选只显示该部门可接单的任务(可选)
*/
public IPage<Task> getTaskHall(Integer page, Integer pageSize, Long taskTypeId, Long demandTypeId,
Long brandId, Long categoryId, Long platformId, String taskSubType,
Integer minScore, Integer maxScore, String keyword, String publisherName,
String timeRange, String sortBy, Long userDeptId) {
String timeRange, String sortBy, Long userDeptId, Long filterDeptId) {
Page<Task> pageParam = new Page<>(page, pageSize);
List<DemandType> allDemandTypes = demandTypeMapper.selectList(null);
// 构建ID到DemandType的映射,用于快速查找父级
Map<Long, DemandType> demandTypeMap = allDemandTypes.stream()
.collect(Collectors.toMap(DemandType::getId, dt -> dt));
// 获取当前用户部门可以接单的任务类型ID列表(支持三级分类继承二级分类的配置)
List<Long> allowedTaskTypeIds = null;
if (userDeptId != null) {
List<DemandType> allDemandTypes = demandTypeMapper.selectList(null);
// 构建ID到DemandType的映射,用于快速查找父级
Map<Long, DemandType> demandTypeMap = allDemandTypes.stream()
.collect(Collectors.toMap(DemandType::getId, dt -> dt));
allowedTaskTypeIds = allDemandTypes.stream()
.filter(dt -> {
// 获取允许的部门ID(支持继承)
@ -1066,9 +1067,46 @@ public class TaskService {
.collect(Collectors.toList());
}
// 🔥 如果指定了筛选部门,进一步过滤只显示该部门可接单的任务类型
List<Long> filteredTaskTypeIds = allowedTaskTypeIds;
if (filterDeptId != null) {
List<Long> filterDeptAllowedIds = allDemandTypes.stream()
.filter(dt -> {
String allowedDepts = dt.getAllowedDeptIds();
if ((allowedDepts == null || allowedDepts.isEmpty()) && dt.getParentId() != null) {
DemandType parentType = demandTypeMap.get(dt.getParentId());
if (parentType != null) {
allowedDepts = parentType.getAllowedDeptIds();
}
}
// 只筛选明确指定了该部门的任务类型(不包括所有部门可接单的)
if (allowedDepts == null || allowedDepts.isEmpty()) {
return false; // 所有部门都可接单的不纳入筛选结果
}
String[] deptIds = allowedDepts.split(",");
for (String deptId : deptIds) {
if (deptId.trim().equals(String.valueOf(filterDeptId))) {
return true;
}
}
return false;
})
.map(DemandType::getId)
.collect(Collectors.toList());
// 取交集:用户可接单的 ∩ 筛选部门可接单的
if (allowedTaskTypeIds != null) {
filteredTaskTypeIds = allowedTaskTypeIds.stream()
.filter(filterDeptAllowedIds::contains)
.collect(Collectors.toList());
} else {
filteredTaskTypeIds = filterDeptAllowedIds;
}
}
return taskMapper.selectTaskHallByDept(pageParam, taskTypeId, demandTypeId, brandId, categoryId,
platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy, allowedTaskTypeIds);
publisherName, timeRange, sortBy, filteredTaskTypeIds);
}
/**

View File

@ -176,6 +176,23 @@
</a-select-option>
</a-select>
<!-- 🔥 接单部门筛选 -->
<a-select
v-model:value="filters.filterDeptId"
placeholder="接单部门"
class="filter-select"
allowClear
@change="handleFilterChange"
>
<a-select-option
v-for="dept in receivingDeptList"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
</a-select>
<a-select
v-model:value="filters.timeRange"
placeholder="发布时间"
@ -284,6 +301,21 @@
</div>
</div>
<!-- 🔥 可接单部门标签 -->
<div v-if="getTaskAllowedDeptNames(task).length > 0" class="allowed-depts-section">
<span class="allowed-depts-label">可接单部门:</span>
<span class="allowed-depts-tags">
<a-tag
v-for="(deptName, idx) in getTaskAllowedDeptNames(task)"
:key="idx"
color="blue"
size="small"
>
{{ deptName }}
</a-tag>
</span>
</div>
<!-- 任务类型明细 -->
<div v-if="parseTypeDetails(task.typeDetails).length > 0" class="type-details-section">
<div class="type-details-title">任务类型明细</div>
@ -2598,6 +2630,7 @@ import * as taskApi from '@/api/task'
import * as userApi from '@/api/user'
import * as configApi from '@/api/config'
import * as authApi from '@/api/auth'
import * as departmentApi from '@/api/department'
import { getWorkHoursConfig } from '@/api/system'
import type { Task, TaskApprovalDTO, TaskProduct } from '@/api/task'
import type { DesignerWithWorkload } from '@/api/user'
@ -2829,6 +2862,9 @@ const workHoursPerDay = ref(9) // 默认9小时
// 层级任务类型数据(用于级联选择器)
const hierarchicalTaskTypes = ref<any[]>([])
// 🔥 接单部门列表(用于筛选和显示标签)
const receivingDeptList = ref<Array<{ id: number; deptName: string }>>([])
// 递归查找任务类型(从层级数据中查找,支持三级分类)
const findTaskTypeById = (id: number, items: any[] = hierarchicalTaskTypes.value): any => {
for (const item of items) {
@ -2860,6 +2896,42 @@ const getTaskTypeFullPath = (id: number, items: any[] = hierarchicalTaskTypes.va
return null
}
// 🔥 获取任务可接单部门名称列表
const getTaskAllowedDeptNames = (task: any): string[] => {
if (!task) return []
// 优先从任务类型配置中获取
const taskTypeId = task.taskTypeId
if (!taskTypeId) return []
// 从层级任务类型数据中查找
const taskType = findTaskTypeById(taskTypeId)
if (!taskType) return []
// 获取 allowedDeptIds(支持从父级继承)
let allowedDeptIds = taskType.allowedDeptIds
if ((!allowedDeptIds || allowedDeptIds.trim() === '') && taskType.parentId) {
const parentType = findTaskTypeById(taskType.parentId)
if (parentType) {
allowedDeptIds = parentType.allowedDeptIds
}
}
// 如果为空,表示所有接单部门都可以接单
if (!allowedDeptIds || allowedDeptIds.trim() === '') {
return ['全部接单部门']
}
// 将部门ID转换为部门名称
const deptIds = allowedDeptIds.split(',').map((id: string) => parseInt(id.trim()))
const deptNames = deptIds.map((id: number) => {
const dept = receivingDeptList.value.find(d => d.id === id)
return dept ? dept.deptName : `部门${id}`
})
return deptNames.filter((name: string) => name)
}
// 构建级联选择器选项
const taskTypeCascaderOptions = computed(() => {
const buildOptions = (items: any[]): any[] => {
@ -2891,7 +2963,8 @@ const filters = reactive({
keyword: undefined as string | undefined,
publisherName: undefined as string | undefined,
timeRange: undefined as 'today' | 'week' | 'month' | undefined,
sortBy: 'latest' as 'latest' | 'score' | 'deadline' | 'hot'
sortBy: 'latest' as 'latest' | 'score' | 'deadline' | 'hot',
filterDeptId: undefined as number | undefined // 🔥 按接单部门筛选
})
// 已完成任务筛选条件
@ -3249,6 +3322,20 @@ const loadConfigs = async () => {
if (response && response.data) {
hierarchicalTaskTypes.value = response.data
}
// 🔥 加载接单部门列表
const deptResponse = await departmentApi.getDepartmentList()
if (deptResponse && deptResponse.data) {
// 过滤出接单部门(deptType === 'receiving'),只保留一级和二级部门
const allDepts = deptResponse.data
const level1DeptIds = new Set(
allDepts.filter((d: any) => d.deptType === 'receiving' && (!d.parentId || d.parentId === 0))
.map((d: any) => d.id)
)
receivingDeptList.value = allDepts.filter((d: any) =>
d.deptType === 'receiving' &&
(!d.parentId || d.parentId === 0 || level1DeptIds.has(d.parentId))
)
}
} catch (error) {
console.error('加载配置数据失败:', error)
}
@ -3303,6 +3390,7 @@ const loadTasks = async (forceReload = false) => {
if (filters.publisherName) params.publisherName = filters.publisherName.trim()
if (filters.timeRange) params.timeRange = filters.timeRange
if (filters.sortBy) params.sortBy = filters.sortBy
if (filters.filterDeptId) params.filterDeptId = filters.filterDeptId // 🔥 按接单部门筛选
let allTasks: any[] = []
@ -6512,6 +6600,37 @@ onUnmounted(() => {
}
}
// 🔥 可接单部门标签样式
.allowed-depts-section {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: $space-xs;
margin-bottom: $space-md;
padding: $space-xs $space-sm;
background: rgba(#1890ff, 0.08);
border-radius: 6px;
.allowed-depts-label {
font-size: $font-size-xs;
color: $text-tertiary;
white-space: nowrap;
}
.allowed-depts-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
:deep(.ant-tag) {
margin: 0;
font-size: $font-size-xs;
line-height: 18px;
padding: 0 6px;
}
}
}
// 任务详情
.task-details {
display: grid;