feat: 在一级需求部门统计页面增加用户维度的数据分析功能
This commit is contained in:
parent
4a74768a32
commit
348e7ed71e
@ -153,5 +153,15 @@ public class AnalyticsController {
|
||||
log.info("获取品牌任务分布: startTime={}, endTime={}", startTime, endTime);
|
||||
return Result.success(analyticsService.getPrimaryTaskBrandDistribution(startTime, endTime));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取一级需求部门用户排名(用户维度统计)")
|
||||
@GetMapping("/primary-dept/user-ranking")
|
||||
public Result<List<PrimaryDeptUserRankingVO>> getPrimaryDeptUserRanking(
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime
|
||||
) {
|
||||
log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime);
|
||||
return Result.success(analyticsService.getPrimaryDeptUserRanking(startTime, endTime));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2695,5 +2695,135 @@ public class AnalyticsService {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一级需求部门用户排名(用户维度统计)
|
||||
*/
|
||||
public List<PrimaryDeptUserRankingVO> getPrimaryDeptUserRanking(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime);
|
||||
|
||||
List<PrimaryDeptUserRankingVO> result = new java.util.ArrayList<>();
|
||||
|
||||
// 1. 查询时间范围内的一级需求任务
|
||||
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(Task::getIsPrimaryTask, 1)
|
||||
.ge(Task::getCreateTime, startTime)
|
||||
.le(Task::getCreateTime, endTime)
|
||||
.eq(Task::getDeleted, 0)
|
||||
.isNotNull(Task::getPrimaryPublisherId);
|
||||
|
||||
List<Task> allTasks = taskMapper.selectList(queryWrapper);
|
||||
|
||||
// 2. 按用户分组
|
||||
java.util.Map<Long, List<Task>> tasksByUser = allTasks.stream()
|
||||
.filter(t -> t.getPrimaryPublisherId() != null)
|
||||
.collect(java.util.stream.Collectors.groupingBy(Task::getPrimaryPublisherId));
|
||||
|
||||
// 3. 为每个用户创建统计数据
|
||||
for (java.util.Map.Entry<Long, List<Task>> entry : tasksByUser.entrySet()) {
|
||||
Long userId = entry.getKey();
|
||||
List<Task> userTasks = entry.getValue();
|
||||
|
||||
// 获取用户信息
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null || user.getDeleted() == 1 || user.getStatus() == 0) {
|
||||
continue; // 跳过已删除或禁用的用户
|
||||
}
|
||||
|
||||
PrimaryDeptUserRankingVO vo = new PrimaryDeptUserRankingVO();
|
||||
vo.setUserId(userId);
|
||||
vo.setUserName(user.getRealName() != null && !user.getRealName().isEmpty()
|
||||
? user.getRealName() : user.getUsername());
|
||||
|
||||
// 获取用户部门信息
|
||||
if (user.getDeptId() != null) {
|
||||
Department userDept = departmentMapper.selectById(user.getDeptId());
|
||||
if (userDept != null) {
|
||||
vo.setDeptId(userDept.getId());
|
||||
vo.setDeptName(userDept.getDeptName());
|
||||
|
||||
// 获取一级部门和二级部门名称(向上查找部门层级)
|
||||
List<Department> hierarchy = new java.util.ArrayList<>();
|
||||
Department current = userDept;
|
||||
while (current != null && current.getParentId() != null && current.getParentId() > 0) {
|
||||
Department parent = departmentMapper.selectById(current.getParentId());
|
||||
if (parent != null) {
|
||||
hierarchy.add(0, parent); // 插入到列表开头
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
// hierarchy 现在从根部门到当前部门的父部门
|
||||
// 一级部门是 hierarchy[0],二级部门是 hierarchy[1](如果存在)
|
||||
if (hierarchy.size() >= 1) {
|
||||
vo.setFirstLevelDeptName(hierarchy.get(0).getDeptName());
|
||||
}
|
||||
if (hierarchy.size() >= 2) {
|
||||
vo.setSecondLevelDeptName(hierarchy.get(1).getDeptName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vo.setTaskCount(userTasks.size());
|
||||
|
||||
// 统计审核通过数
|
||||
long approvedCount = userTasks.stream()
|
||||
.filter(t -> !"rejected_by_demand".equals(t.getApprovalStatus()) && !"draft".equals(t.getStatus()))
|
||||
.count();
|
||||
vo.setApprovedCount((int) approvedCount);
|
||||
|
||||
// 统计审核驳回数
|
||||
long rejectedCount = userTasks.stream()
|
||||
.filter(t -> "rejected_by_demand".equals(t.getApprovalStatus()))
|
||||
.count();
|
||||
vo.setRejectedCount((int) rejectedCount);
|
||||
|
||||
// 计算审核通过率
|
||||
if (userTasks.size() > 0) {
|
||||
vo.setApprovalRate(BigDecimal.valueOf(approvedCount * 100.0 / userTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
vo.setApprovalRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算消耗积分
|
||||
BigDecimal consumedScore = userTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
vo.setConsumedScore(consumedScore);
|
||||
|
||||
// 统计已完成任务数
|
||||
long completedCount = userTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
vo.setCompletedCount((int) completedCount);
|
||||
|
||||
// 统计进行中任务数
|
||||
long inProgressCount = userTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
vo.setInProgressCount((int) inProgressCount);
|
||||
|
||||
// 统计待审核任务数
|
||||
long pendingCount = userTasks.stream()
|
||||
.filter(t -> "pending_primary_approval".equals(t.getApprovalStatus()) ||
|
||||
"pending_detail_completion".equals(t.getApprovalStatus()))
|
||||
.count();
|
||||
vo.setPendingCount((int) pendingCount);
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
// 4. 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTaskCount(), a.getTaskCount()));
|
||||
|
||||
// 5. 设置排名
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result.get(i).setRank(i + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
package com.designhub.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 一级需求部门用户排名VO(用户维度统计)
|
||||
*/
|
||||
public class PrimaryDeptUserRankingVO {
|
||||
|
||||
/**
|
||||
* 排名
|
||||
*/
|
||||
private Integer rank;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 一级部门名称
|
||||
*/
|
||||
private String firstLevelDeptName;
|
||||
|
||||
/**
|
||||
* 二级部门名称
|
||||
*/
|
||||
private String secondLevelDeptName;
|
||||
|
||||
/**
|
||||
* 发起任务数
|
||||
*/
|
||||
private Integer taskCount;
|
||||
|
||||
/**
|
||||
* 审核通过数
|
||||
*/
|
||||
private Integer approvedCount;
|
||||
|
||||
/**
|
||||
* 审核驳回数
|
||||
*/
|
||||
private Integer rejectedCount;
|
||||
|
||||
/**
|
||||
* 审核通过率(百分比)
|
||||
*/
|
||||
private BigDecimal approvalRate;
|
||||
|
||||
/**
|
||||
* 消耗积分
|
||||
*/
|
||||
private BigDecimal consumedScore;
|
||||
|
||||
/**
|
||||
* 已完成任务数
|
||||
*/
|
||||
private Integer completedCount;
|
||||
|
||||
/**
|
||||
* 进行中任务数
|
||||
*/
|
||||
private Integer inProgressCount;
|
||||
|
||||
/**
|
||||
* 待审核任务数
|
||||
*/
|
||||
private Integer pendingCount;
|
||||
|
||||
// ==================== Getters and Setters ====================
|
||||
|
||||
public Integer getRank() { return rank; }
|
||||
public void setRank(Integer rank) { this.rank = rank; }
|
||||
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
|
||||
public String getUserName() { return userName; }
|
||||
public void setUserName(String userName) { this.userName = userName; }
|
||||
|
||||
public Long getDeptId() { return deptId; }
|
||||
public void setDeptId(Long deptId) { this.deptId = deptId; }
|
||||
|
||||
public String getDeptName() { return deptName; }
|
||||
public void setDeptName(String deptName) { this.deptName = deptName; }
|
||||
|
||||
public String getFirstLevelDeptName() { return firstLevelDeptName; }
|
||||
public void setFirstLevelDeptName(String firstLevelDeptName) { this.firstLevelDeptName = firstLevelDeptName; }
|
||||
|
||||
public String getSecondLevelDeptName() { return secondLevelDeptName; }
|
||||
public void setSecondLevelDeptName(String secondLevelDeptName) { this.secondLevelDeptName = secondLevelDeptName; }
|
||||
|
||||
public Integer getTaskCount() { return taskCount; }
|
||||
public void setTaskCount(Integer taskCount) { this.taskCount = taskCount; }
|
||||
|
||||
public Integer getApprovedCount() { return approvedCount; }
|
||||
public void setApprovedCount(Integer approvedCount) { this.approvedCount = approvedCount; }
|
||||
|
||||
public Integer getRejectedCount() { return rejectedCount; }
|
||||
public void setRejectedCount(Integer rejectedCount) { this.rejectedCount = rejectedCount; }
|
||||
|
||||
public BigDecimal getApprovalRate() { return approvalRate; }
|
||||
public void setApprovalRate(BigDecimal approvalRate) { this.approvalRate = approvalRate; }
|
||||
|
||||
public BigDecimal getConsumedScore() { return consumedScore; }
|
||||
public void setConsumedScore(BigDecimal consumedScore) { this.consumedScore = consumedScore; }
|
||||
|
||||
public Integer getCompletedCount() { return completedCount; }
|
||||
public void setCompletedCount(Integer completedCount) { this.completedCount = completedCount; }
|
||||
|
||||
public Integer getInProgressCount() { return inProgressCount; }
|
||||
public void setInProgressCount(Integer inProgressCount) { this.inProgressCount = inProgressCount; }
|
||||
|
||||
public Integer getPendingCount() { return pendingCount; }
|
||||
public void setPendingCount(Integer pendingCount) { this.pendingCount = pendingCount; }
|
||||
}
|
||||
@ -555,6 +555,25 @@ export interface BrandTaskDistribution {
|
||||
completedCount: number;
|
||||
}
|
||||
|
||||
// 一级需求部门用户排名(用户维度统计)
|
||||
export interface PrimaryDeptUserRanking {
|
||||
rank: number;
|
||||
userId: number;
|
||||
userName: string;
|
||||
deptId?: number;
|
||||
deptName?: string;
|
||||
firstLevelDeptName?: string;
|
||||
secondLevelDeptName?: string;
|
||||
taskCount: number;
|
||||
approvedCount: number;
|
||||
rejectedCount: number;
|
||||
approvalRate: number;
|
||||
consumedScore: number;
|
||||
completedCount: number;
|
||||
inProgressCount: number;
|
||||
pendingCount: number;
|
||||
}
|
||||
|
||||
// 获取一级需求部门统计数据
|
||||
export const getPrimaryDeptStatistics = async (
|
||||
startTime: string,
|
||||
@ -612,3 +631,17 @@ export const getPrimaryTaskBrandDistribution = async (
|
||||
}
|
||||
return response as unknown as BrandTaskDistribution[];
|
||||
};
|
||||
|
||||
// 获取一级需求部门用户排名(用户维度统计)
|
||||
export const getPrimaryDeptUserRanking = async (
|
||||
startTime: string,
|
||||
endTime: string
|
||||
): Promise<PrimaryDeptUserRanking[]> => {
|
||||
const params = { startTime, endTime };
|
||||
|
||||
const response = await request.get('/analytics/primary-dept/user-ranking', { params });
|
||||
if (response && typeof response === 'object' && 'data' in response) {
|
||||
return response.data as PrimaryDeptUserRanking[];
|
||||
}
|
||||
return response as unknown as PrimaryDeptUserRanking[];
|
||||
};
|
||||
|
||||
@ -670,6 +670,37 @@
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 用户维度统计 -->
|
||||
<div class="table-card full-width" style="margin-top: 16px;">
|
||||
<div class="table-header">
|
||||
<h3 class="chart-title">用户维度统计(一级需求)</h3>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="primaryDeptUserColumns"
|
||||
:data-source="primaryDeptUserRanking"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
row-key="userId"
|
||||
size="middle"
|
||||
class="dark-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'rank'">
|
||||
<span :class="['rank-badge', record.rank <= 3 ? 'top-' + record.rank : '']">
|
||||
{{ record.rank }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'approvalRate'">
|
||||
<span :class="record.approvalRate >= 90 ? 'text-success' : record.approvalRate >= 70 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.approvalRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'consumedScore'">
|
||||
<span class="score-value">{{ formatScore(record.consumedScore) }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
@ -769,6 +800,7 @@ import {
|
||||
getPrimaryDeptStatistics,
|
||||
getPrimaryDeptRanking,
|
||||
getPrimaryTaskBrandDistribution,
|
||||
getPrimaryDeptUserRanking,
|
||||
type DemandStatistics,
|
||||
type DesignerPerformance,
|
||||
type ProcessEfficiency,
|
||||
@ -779,7 +811,8 @@ import {
|
||||
type DimensionAnalysis,
|
||||
type PrimaryDeptStatistics,
|
||||
type PrimaryDeptRanking,
|
||||
type BrandTaskDistribution
|
||||
type BrandTaskDistribution,
|
||||
type PrimaryDeptUserRanking
|
||||
} from '@/api/analytics'
|
||||
import { message } from 'ant-design-vue'
|
||||
import * as echarts from 'echarts'
|
||||
@ -901,6 +934,7 @@ const primaryDeptStats = reactive<PrimaryDeptStatistics>({
|
||||
})
|
||||
const primaryDeptRanking = ref<PrimaryDeptRanking[]>([])
|
||||
const brandDistribution = ref<BrandTaskDistribution[]>([])
|
||||
const primaryDeptUserRanking = ref<PrimaryDeptUserRanking[]>([])
|
||||
|
||||
// 部门筛选
|
||||
const selectedDeptId = ref<number | undefined>(undefined)
|
||||
@ -1152,6 +1186,23 @@ const brandDistributionColumns = [
|
||||
{ title: '已完成', dataIndex: 'completedCount', key: 'completedCount', align: 'center', width: 80 }
|
||||
]
|
||||
|
||||
// 一级需求部门用户排名列(用户维度统计)
|
||||
const primaryDeptUserColumns = [
|
||||
{ title: '排名', key: 'rank', dataIndex: 'rank', align: 'center', width: 60 },
|
||||
{ title: '用户姓名', dataIndex: 'userName', key: 'userName', ellipsis: true, width: 120 },
|
||||
{ title: '部门名称', dataIndex: 'deptName', key: 'deptName', ellipsis: true },
|
||||
{ title: '一级部门', dataIndex: 'firstLevelDeptName', key: 'firstLevelDeptName', ellipsis: true, width: 120 },
|
||||
{ title: '二级部门', dataIndex: 'secondLevelDeptName', key: 'secondLevelDeptName', ellipsis: true, width: 120 },
|
||||
{ title: '发起任务数', dataIndex: 'taskCount', key: 'taskCount', align: 'center', width: 100 },
|
||||
{ title: '审核通过数', dataIndex: 'approvedCount', key: 'approvedCount', align: 'center', width: 100 },
|
||||
{ title: '审核驳回数', dataIndex: 'rejectedCount', key: 'rejectedCount', align: 'center', width: 100 },
|
||||
{ title: '审核通过率', key: 'approvalRate', align: 'center', width: 100 },
|
||||
{ title: '消耗积分', key: 'consumedScore', align: 'center', width: 100 },
|
||||
{ title: '已完成', dataIndex: 'completedCount', key: 'completedCount', align: 'center', width: 80 },
|
||||
{ title: '进行中', dataIndex: 'inProgressCount', key: 'inProgressCount', align: 'center', width: 80 },
|
||||
{ title: '待审核', dataIndex: 'pendingCount', key: 'pendingCount', align: 'center', width: 80 }
|
||||
]
|
||||
|
||||
// 方法
|
||||
const handleMonthChange = (value: string | null) => {
|
||||
selectedMonth.value = value
|
||||
@ -1229,14 +1280,16 @@ const loadAllData = async () => {
|
||||
|
||||
// 加载一级需求部门统计数据
|
||||
try {
|
||||
const [primaryStats, primaryRanking, brandDist] = await Promise.all([
|
||||
const [primaryStats, primaryRanking, brandDist, userRanking] = await Promise.all([
|
||||
getPrimaryDeptStatistics(startTime, endTime),
|
||||
getPrimaryDeptRanking(startTime, endTime),
|
||||
getPrimaryTaskBrandDistribution(startTime, endTime)
|
||||
getPrimaryTaskBrandDistribution(startTime, endTime),
|
||||
getPrimaryDeptUserRanking(startTime, endTime)
|
||||
])
|
||||
Object.assign(primaryDeptStats, primaryStats)
|
||||
primaryDeptRanking.value = primaryRanking
|
||||
brandDistribution.value = brandDist
|
||||
primaryDeptUserRanking.value = userRanking
|
||||
} catch (e) {
|
||||
console.warn('加载一级需求部门统计失败:', e)
|
||||
}
|
||||
|
||||
356
更新通知-2026-01-22.md
Normal file
356
更新通知-2026-01-22.md
Normal file
@ -0,0 +1,356 @@
|
||||
# 系统更新通知
|
||||
|
||||
**更新日期:** 2026年1月22日
|
||||
|
||||
---
|
||||
|
||||
## 📋 更新概览
|
||||
|
||||
本次更新主要包含以下内容:
|
||||
- ✨ 扩展任务搜索功能,支持搜索任务卡片的所有信息
|
||||
- 🔒 一级需求部门发起任务必须上传预览图
|
||||
- 🎨 任务卡片增加一级需求人显示,优化长名字显示
|
||||
- 🐛 修复流水线任务异常扣分问题
|
||||
- 🐛 修复需求部门看不到一级需求任务的问题
|
||||
- 🐛 修复需求部门负责人无法驳回任务的问题
|
||||
- 🐛 修复筛选功能搜索报错问题
|
||||
- 🐛 修复 SQL 查询错误
|
||||
|
||||
---
|
||||
|
||||
## ✨ 新增功能
|
||||
|
||||
### 1. 扩展任务搜索功能
|
||||
|
||||
**功能描述:** 任务搜索功能现在支持搜索任务卡片的所有信息,不再仅限于任务标题和编号。
|
||||
|
||||
**搜索范围包括:**
|
||||
- ✅ 任务标题 (`title`)
|
||||
- ✅ 任务编号 (`task_no`)
|
||||
- ✅ SPU信息 (`spu_info`)
|
||||
- ✅ 备注信息 (`remark`)
|
||||
- ✅ 产品条码 (`product_barcodes`)
|
||||
- ✅ 发布人姓名 (`publisher_name`)
|
||||
- ✅ 发布人部门名称 (`publisher_dept_name`)
|
||||
- ✅ 品牌名称 (`brand_name`)
|
||||
- ✅ 类目名称 (`category_name`)
|
||||
- ✅ 平台名称 (`platform_name`)
|
||||
- ✅ 任务类型名称 (`task_type_name`)
|
||||
- ✅ 一级需求人姓名 (`primary_publisher_name`)
|
||||
- ✅ 一级需求部门名称 (`primary_publisher_dept_name`)
|
||||
- ✅ 需求部门发起人姓名 (`detail_completer_name`)
|
||||
- ✅ 接收人姓名 (`receiver_name`)
|
||||
|
||||
**适用场景:**
|
||||
- 任务大厅搜索
|
||||
- 进行中任务搜索
|
||||
- 已完成任务搜索
|
||||
- 子任务搜索
|
||||
|
||||
**使用示例:**
|
||||
- 搜索品牌名称(如"卓禾")可以找到该品牌的所有任务
|
||||
- 搜索发布人姓名可以找到该用户发布的所有任务
|
||||
- 搜索一级需求人姓名可以找到该用户发起的一级需求任务
|
||||
|
||||
---
|
||||
|
||||
### 2. 一级需求部门必须上传预览图
|
||||
|
||||
**功能描述:** 一级需求部门用户发起任务时,系统强制要求上传预览图。
|
||||
|
||||
**实现方式:**
|
||||
- 前端验证:表单验证规则中添加预览图必填验证
|
||||
- 后端验证:创建任务时检查预览图是否上传
|
||||
- 用户提示:预览图上传区域显示明确的必填提示
|
||||
|
||||
**影响范围:**
|
||||
- 一级需求部门用户创建普通任务
|
||||
- 一级需求部门用户创建流水线任务
|
||||
|
||||
**用户体验:**
|
||||
- 如果未上传预览图,提交时会显示错误提示:"一级需求部门发起任务必须上传预览图"
|
||||
- 预览图上传区域会显示红色警告图标和提示文字
|
||||
|
||||
---
|
||||
|
||||
### 3. 任务卡片增加一级需求人显示
|
||||
|
||||
**功能描述:** 对于一级需求部门发起的任务,任务卡片现在会显示一级需求人和需求部门发起人的信息,让任务来源更加清晰。
|
||||
|
||||
**显示内容:**
|
||||
- ✅ **一级需求人**:显示一级需求部门发起人的姓名和部门名称(橙色标识)
|
||||
- ✅ **需求部门发起人**:显示补充任务明细的需求部门成员姓名(绿色标识)
|
||||
- ✅ **发布人**:显示任务的发布人姓名(默认绿色标识)
|
||||
|
||||
**显示逻辑:**
|
||||
- 如果是一级需求任务(`isPrimaryTask=1`):
|
||||
- 显示"一级需求人"(如果存在)
|
||||
- 显示"需求部门发起人"(如果存在,即已补充明细)
|
||||
- 否则显示"发布人"
|
||||
- 如果是普通任务:
|
||||
- 显示"发布人"
|
||||
|
||||
**优化内容:**
|
||||
- 一级需求人名字超过 8 个字符时显示省略号
|
||||
- 需求部门发起人名字超过 8 个字符时显示省略号
|
||||
- 发布人名字超过 8 个字符时显示省略号
|
||||
- 鼠标悬停时通过 Tooltip 显示完整信息(包含部门名称)
|
||||
|
||||
**显示效果:**
|
||||
- 一级需求任务示例:
|
||||
- `一级需求人: 张三... (运营中心...)`(橙色)
|
||||
- `需求部门发起人: 李四...`(绿色)
|
||||
- 悬停提示:`一级需求人: 张三丰 (运营中心A-事业二部)`
|
||||
|
||||
**适用位置:**
|
||||
- 任务大厅任务卡片
|
||||
- 进行中任务卡片
|
||||
- 已完成任务卡片
|
||||
- 我发起的任务卡片
|
||||
- 待审核任务卡片
|
||||
- 草稿箱任务卡片
|
||||
- 子任务卡片
|
||||
|
||||
---
|
||||
|
||||
## 🐛 问题修复
|
||||
|
||||
### 1. 修复流水线任务异常扣分问题
|
||||
|
||||
**问题描述:**
|
||||
- 流水线任务的子任务按时完成后,主任务仍然被系统检查进度更新情况
|
||||
- 导致已完成子任务的用户被错误扣分
|
||||
- 影响范围:所有流水线任务的接收人
|
||||
|
||||
**问题原因:**
|
||||
- 进度检查服务 (`ProgressCheckService`) 在检查任务进度时,没有区分流水线任务的主任务和子任务
|
||||
- 流水线任务的主任务 (`is_pipeline=1`) 在子任务完成后,主任务状态仍为 `in_progress`,但主任务本身不需要更新进度
|
||||
- 系统错误地对主任务进行了进度检查,导致已完成子任务的用户被扣分
|
||||
|
||||
**解决方案:**
|
||||
- 修改 `ProgressCheckService.checkDailyProgressUpdate()` 方法
|
||||
- 在进度检查时排除流水线任务的主任务(`is_pipeline=1`)
|
||||
- 只对流水线任务的子任务进行进度检查
|
||||
- 清理了历史错误扣分记录(共 21 条)
|
||||
|
||||
**修复效果:**
|
||||
- ✅ 流水线任务的主任务不再被检查进度
|
||||
- ✅ 已完成子任务的用户不会再被错误扣分
|
||||
- ✅ 历史错误扣分记录已清理
|
||||
|
||||
**影响用户:**
|
||||
- 所有流水线任务的接收人
|
||||
- 特别是已完成子任务但主任务仍在进行中的用户
|
||||
|
||||
---
|
||||
|
||||
### 2. 修复需求部门看不到一级需求任务的问题
|
||||
|
||||
**问题描述:**
|
||||
- 需求部门负责人审核通过一级需求部门的任务后,在"进行中任务"中看不到该任务
|
||||
- 需求部门成员补充完任务明细并发布后,在"进行中任务"中也看不到该任务
|
||||
- 影响范围:所有需求部门负责人和成员
|
||||
|
||||
**问题原因:**
|
||||
- 一级需求部门的任务在需求部门负责人审核通过后,状态变为 `pending_detail_completion`(待明细完善)
|
||||
- 此时任务的 `publisher_dept_id` 是一级需求部门的 ID,不是需求部门的 ID
|
||||
- 需求部门负责人的"进行中任务"查询只按 `publisherDeptIds` 过滤,无法找到这些任务
|
||||
- 需求部门成员的"进行中任务"查询只查询 `role='publisher'` 的任务,无法找到作为 `detail_completer` 的任务
|
||||
|
||||
**解决方案:**
|
||||
- **后端修改:**
|
||||
- 在 `TaskController.getTaskList` 中添加 `includeDetailCompleterDeptIds` 参数
|
||||
- 在 `TaskService.getTaskList` 中添加按明细完善人部门查询的逻辑
|
||||
- 在 `TaskController.getMyTasks` 中添加 `role='detail_completer'` 支持
|
||||
- 新增 `TaskService.getMyDetailCompleterTasks` 方法,查询用户作为明细完善人的任务
|
||||
- **前端修改:**
|
||||
- 需求部门负责人查看"进行中任务"时,传递 `includeDetailCompleterDeptIds` 参数
|
||||
- 需求部门成员查看"进行中任务"时,同时查询 `publisher` 和 `detail_completer` 角色的任务并合并结果
|
||||
|
||||
**修复效果:**
|
||||
- ✅ 需求部门负责人可以在"进行中任务"中看到已审核通过的一级需求任务
|
||||
- ✅ 需求部门成员可以在"进行中任务"中看到自己作为明细完善人的任务
|
||||
- ✅ 任务状态流转更加清晰,用户能正确跟踪任务进度
|
||||
|
||||
**影响用户:**
|
||||
- 所有需求部门负责人
|
||||
- 所有需求部门成员
|
||||
|
||||
---
|
||||
|
||||
### 3. 修复需求部门负责人无法驳回任务的问题
|
||||
|
||||
**问题描述:**
|
||||
- 需求部门负责人在驳回一级需求部门的任务时,系统报错:"需求部门负责人不存在"
|
||||
- 导致需求部门负责人无法正常驳回任务
|
||||
- 影响范围:所有需求部门负责人
|
||||
|
||||
**问题原因:**
|
||||
- 一级需求部门的任务被驳回时,系统尝试将任务返回给需求部门的负责人
|
||||
- 但某些一级需求部门(`dept_type='primary_demand'`)可能没有设置 `leader_id`
|
||||
- 当 `TaskApprovalService.rejectByReceivingLeader` 方法检查 `demandDept.getLeaderId()` 时,如果为 `null`,会抛出异常
|
||||
|
||||
**解决方案:**
|
||||
- 修改 `TaskApprovalService.rejectByReceivingLeader` 方法
|
||||
- 当需求部门的 `leader_id` 为 `null` 时,不再抛出异常
|
||||
- 改为将任务的 `currentApproverId` 设置为任务的 `publisherId`,将任务返回给原始发起人
|
||||
- 这样即使需求部门没有负责人,任务也能正常被驳回并返回给发起人
|
||||
|
||||
**修复效果:**
|
||||
- ✅ 需求部门负责人可以正常驳回一级需求部门的任务
|
||||
- ✅ 即使需求部门没有负责人,任务也能正常被驳回
|
||||
- ✅ 任务会正确返回给原始发起人
|
||||
|
||||
**影响用户:**
|
||||
- 所有需求部门负责人
|
||||
- 特别是需要驳回一级需求部门任务的负责人
|
||||
|
||||
---
|
||||
|
||||
### 4. 修复筛选功能搜索报错
|
||||
|
||||
**问题描述:**
|
||||
- 错误信息:`TypeError: Assignment to constant variable`
|
||||
- 触发场景:在任务大厅、进行中任务、已完成任务等页面使用筛选功能时
|
||||
|
||||
**问题原因:**
|
||||
- 使用 `v-model` 绑定 `reactive` 对象时,Vue 尝试重新赋值整个对象
|
||||
- `reactive` 对象不能直接重新赋值,导致报错
|
||||
|
||||
**解决方案:**
|
||||
- 将所有 `TaskFilters` 组件的 `v-model` 改为 `:model-value` 和 `@update:model-value`
|
||||
- 在事件处理函数中使用 `Object.assign` 更新对象属性,而不是重新赋值整个对象
|
||||
|
||||
**修复的筛选器:**
|
||||
- ✅ 可接任务筛选 (`filters`)
|
||||
- ✅ 待审核任务筛选 (`reviewFilters`)
|
||||
- ✅ 进行中任务筛选 (`inProgressFilters`)
|
||||
- ✅ 已完成任务筛选 (`completedFilters`)
|
||||
- ✅ 我发起的任务筛选 (`publishedFilters`)
|
||||
- ✅ 草稿箱筛选 (`draftsFilters`)
|
||||
|
||||
---
|
||||
|
||||
### 4. 修复 SQL 查询错误
|
||||
|
||||
**问题描述:**
|
||||
- 错误信息:`Unknown column 't.description' in 'where clause'`
|
||||
- 触发场景:使用搜索功能时
|
||||
|
||||
**问题原因:**
|
||||
- 在扩展搜索功能时,SQL 查询中引用了 `task` 表中不存在的 `description` 字段
|
||||
|
||||
**解决方案:**
|
||||
- 从所有 SQL 查询中移除了对 `t.description` 字段的引用
|
||||
- 修复了以下文件中的查询:
|
||||
- `TaskMapper.java` - `selectTaskHall` 和 `selectTaskHallByDept` 方法
|
||||
- `TaskItemMapper.java` - 所有子任务查询方法
|
||||
- `TaskService.java` - `getCompletedTasks` 方法
|
||||
|
||||
---
|
||||
|
||||
## 📝 技术细节
|
||||
|
||||
### 前端修改
|
||||
|
||||
**文件:** `frontend/src/views/TaskHall.vue`
|
||||
- 导入 `Tooltip` 组件
|
||||
- 添加 `truncateText` 辅助函数
|
||||
- 修改所有显示需求人名字的地方,添加 Tooltip 和截断显示
|
||||
- 修复 `v-model` 绑定问题,改用 `:model-value` 和 `@update:model-value`
|
||||
- **修复需求部门看不到一级需求任务:**
|
||||
- 需求部门负责人查看"进行中任务"时,添加 `includeDetailCompleterDeptIds` 参数
|
||||
- 需求部门成员查看"进行中任务"时,同时查询 `publisher` 和 `detail_completer` 角色的任务并合并结果
|
||||
- 更新 `loadDashboardStats` 统计逻辑,包含 `detail_completer` 角色的任务
|
||||
|
||||
**文件:** `frontend/src/views/CreateTask.vue`
|
||||
- 添加预览图必填验证规则
|
||||
- 在提交时添加额外的预览图验证
|
||||
- 更新预览图上传区域的提示信息
|
||||
|
||||
**文件:** `frontend/src/api/task.ts`
|
||||
- 在 `MyTasksParams` 接口中添加 `detail_completer` 作为有效的 `role` 值
|
||||
- 在 `getTasks` 函数中添加 `includeDetailCompleterDeptIds` 参数
|
||||
|
||||
### 后端修改
|
||||
|
||||
**文件:** `backend/src/main/java/com/designhub/mapper/TaskMapper.java`
|
||||
- 扩展 `selectTaskHall` 和 `selectTaskHallByDept` 的搜索条件
|
||||
- 添加明细完善人、接收人、任务类型的关联查询
|
||||
- 移除不存在的 `description` 字段引用
|
||||
|
||||
**文件:** `backend/src/main/java/com/designhub/mapper/TaskItemMapper.java`
|
||||
- 扩展所有子任务查询方法的搜索条件
|
||||
- 移除不存在的 `description` 字段引用
|
||||
|
||||
**文件:** `backend/src/main/java/com/designhub/service/TaskService.java`
|
||||
- 扩展 `getCompletedTasks` 方法的搜索条件
|
||||
- 在 `createTask` 和 `createPipelineTask` 方法中添加预览图必填验证
|
||||
- 移除不存在的 `description` 字段引用
|
||||
- **修复需求部门看不到一级需求任务:**
|
||||
- 在 `getTaskList` 方法中添加 `includeDetailCompleterDeptIds` 参数支持
|
||||
- 添加按明细完善人部门查询的逻辑(支持一级需求任务)
|
||||
- 在 `getMyTasks` 方法中添加 `role='detail_completer'` 支持
|
||||
- 新增 `getMyDetailCompleterTasks` 方法,查询用户作为明细完善人的任务
|
||||
- 在 `fillTaskRelatedInfo` 方法中添加 `detailCompleterName` 的填充逻辑
|
||||
|
||||
**文件:** `backend/src/main/java/com/designhub/controller/TaskController.java`
|
||||
- **修复需求部门看不到一级需求任务:**
|
||||
- 在 `getTaskList` 接口中添加 `includeDetailCompleterDeptIds` 参数(逗号分隔的部门ID列表)
|
||||
- 在 `getMyTasks` 接口中添加 `role='detail_completer'` 支持
|
||||
|
||||
**文件:** `backend/src/main/java/com/designhub/service/ProgressCheckService.java`
|
||||
- **修复流水线任务异常扣分:**
|
||||
- 修改 `checkDailyProgressUpdate` 方法,排除流水线任务的主任务(`is_pipeline=1`)
|
||||
- 只对流水线任务的子任务进行进度检查
|
||||
|
||||
---
|
||||
|
||||
## 🎯 影响范围
|
||||
|
||||
### 用户影响
|
||||
- ✅ **一级需求部门用户**:发起任务时必须上传预览图
|
||||
- ✅ **所有用户**:搜索功能更强大,可以搜索更多字段
|
||||
- ✅ **所有用户**:任务卡片显示更美观,长名字不再影响布局
|
||||
- ✅ **流水线任务接收人**:不再被错误扣分,已完成子任务后不会被检查主任务进度
|
||||
- ✅ **需求部门负责人**:可以在"进行中任务"中看到已审核通过的一级需求任务
|
||||
- ✅ **需求部门成员**:可以在"进行中任务"中看到自己作为明细完善人的任务
|
||||
|
||||
### 功能影响
|
||||
- ✅ **搜索功能**:支持搜索任务卡片的所有信息
|
||||
- ✅ **筛选功能**:修复了搜索时的报错问题
|
||||
- ✅ **任务创建**:一级需求部门用户必须上传预览图
|
||||
- ✅ **进度检查**:流水线任务的主任务不再被检查进度,避免错误扣分
|
||||
- ✅ **任务可见性**:需求部门可以正确查看一级需求任务的进度
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **一级需求部门用户**:从本次更新开始,发起任务时必须上传预览图,否则无法提交任务。
|
||||
|
||||
2. **搜索功能**:搜索功能已扩展,现在可以搜索更多字段,但不再支持搜索 `description` 字段(该字段在数据库中不存在)。
|
||||
|
||||
3. **名字显示**:任务卡片中的需求人名字如果超过 8 个字符会自动截断,鼠标悬停可查看完整信息。
|
||||
|
||||
4. **流水线任务进度检查**:流水线任务的主任务不再进行进度检查,只有子任务会被检查进度。已完成子任务的用户不会再被错误扣分。
|
||||
|
||||
5. **一级需求任务可见性**:
|
||||
- 需求部门负责人审核通过一级需求任务后,可以在"进行中任务"中看到该任务
|
||||
- 需求部门成员补充完任务明细并发布后,可以在"进行中任务"中看到该任务
|
||||
- 任务状态为 `pending_detail_completion`(待明细完善)时,会显示在需求部门的"进行中任务"中
|
||||
|
||||
---
|
||||
|
||||
## 📞 问题反馈
|
||||
|
||||
如果在使用过程中遇到任何问题,请及时反馈:
|
||||
- 系统异常
|
||||
- 功能异常
|
||||
- 用户体验问题
|
||||
|
||||
---
|
||||
|
||||
**更新完成时间:** 2026年1月22日 15:40
|
||||
|
||||
**部署状态:** ✅ 已部署到生产环境
|
||||
Loading…
x
Reference in New Issue
Block a user