fix: 设计师绩效排行榜横向拉满,修复积分统计,优化表格深色样式,白色星星评分

This commit is contained in:
Your Name 2026-01-08 11:59:27 +08:00
parent 8c4f37d309
commit 81cb6b3eed
5 changed files with 206 additions and 115 deletions

View File

@ -676,12 +676,18 @@ public class AnalyticsService {
log.warn("获取评价数据失败: {}", e.getMessage());
}
// 计算获得积分
BigDecimal totalScore = designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()))
.map(Task::getScoreReward)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 计算获得积分(使用 score 字段,score_reward 可能未设置)
BigDecimal totalScore = BigDecimal.ZERO;
for (Task task : designerTasks) {
if ("completed".equals(task.getStatus())) {
// 优先使用 scoreReward,如果为空或0则使用 score
if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
totalScore = totalScore.add(task.getScoreReward());
} else if (task.getScore() != null) {
totalScore = totalScore.add(BigDecimal.valueOf(task.getScore()));
}
}
}
// 本周完成数
int weekCompletedCount = (int) designerTasks.stream()

View File

@ -63,24 +63,24 @@ public class TaskApprovalService {
String releaseType = task.getReleaseType();
// 确定接单部门负责人
Long receivingLeaderId = determineReceivingLeader(task);
if (receivingLeaderId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "无法确定接单部门负责人,请在系统设置中为接单部门配置负责人");
}
Long receivingLeaderId = determineReceivingLeader(task);
if (receivingLeaderId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "无法确定接单部门负责人,请在系统设置中为接单部门配置负责人");
}
// 更新任务状态为待接单部门审核
task.setApprovalStatus("pending_receiving_approval");
task.setDemandApprovalTime(LocalDateTime.now());
task.setCurrentApproverId(receivingLeaderId);
task.setCurrentApproverRole("receiving_leader");
taskMapper.updateById(task);
// 更新任务状态为待接单部门审核
task.setApprovalStatus("pending_receiving_approval");
task.setDemandApprovalTime(LocalDateTime.now());
task.setCurrentApproverId(receivingLeaderId);
task.setCurrentApproverRole("receiving_leader");
taskMapper.updateById(task);
// 记录审核日志
logApproval(taskId, approverId, 1, "approve", dto.getComment());
// 记录审核日志
logApproval(taskId, approverId, 1, "approve", dto.getComment());
// 发送通知给接单部门负责人
User receivingLeader = userMapper.selectById(receivingLeaderId);
if (receivingLeader != null) {
// 发送通知给接单部门负责人
User receivingLeader = userMapper.selectById(receivingLeaderId);
if (receivingLeader != null) {
// 根据是否预先指定了设计师,调整通知内容
String notificationContent;
if ("assigned".equals(releaseType) && task.getReceiverId() != null) {
@ -90,14 +90,14 @@ public class TaskApprovalService {
} else {
notificationContent = String.format("任务【%s】需要您审核并分配设计师", task.getTitle());
}
notificationService.sendNotification(
receivingLeaderId,
"新任务待审核",
notificationService.sendNotification(
receivingLeaderId,
"新任务待审核",
notificationContent,
"task_approval",
taskId
);
}
"task_approval",
taskId
);
}
log.info("需求部门负责人审核通过,任务ID:{},审核人:{},发布类型:{},等待接单部门负责人审核", taskId, approverId, releaseType);
}

View File

@ -197,42 +197,43 @@
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :xs="24" :lg="14">
<div class="table-card">
<h3 class="chart-title">设计师绩效排行榜</h3>
<a-table
:columns="designerColumns"
:data-source="designerPerformance.designerStats || []"
:pagination="{ pageSize: 10 }"
row-key="userId"
size="small"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'rank'">
<span class="rank-badge" :class="getRankClass(index + 1)">
{{ index + 1 }}
</span>
</template>
<template v-else-if="column.key === 'onTimeRate'">
<span :class="record.onTimeRate >= 80 ? 'text-success' : record.onTimeRate >= 60 ? 'text-warning' : 'text-danger'">
{{ record.onTimeRate }}%
</span>
</template>
<template v-else-if="column.key === 'avgRating'">
<a-rate :value="record.avgRating" disabled allow-half :count="5" style="font-size: 12px;" />
</template>
</template>
</a-table>
</div>
</a-col>
<a-col :xs="24" :lg="10">
<div class="chart-card">
<h3 class="chart-title">工作负载分布</h3>
<div ref="workloadChartRef" style="width: 100%; height: 300px;"></div>
</div>
</a-col>
</a-row>
<!-- 设计师绩效排行榜 - 全宽显示 -->
<div class="table-card full-width">
<h3 class="chart-title">设计师绩效排行榜</h3>
<a-table
:columns="designerColumns"
:data-source="designerPerformance.designerStats || []"
:pagination="{ pageSize: 10 }"
row-key="userId"
size="middle"
class="dark-table"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'rank'">
<span class="rank-badge" :class="getRankClass(index + 1)">
{{ index + 1 }}
</span>
</template>
<template v-else-if="column.key === 'onTimeRate'">
<span :class="record.onTimeRate >= 80 ? 'text-success' : record.onTimeRate >= 60 ? 'text-warning' : 'text-danger'">
{{ record.onTimeRate }}%
</span>
</template>
<template v-else-if="column.key === 'avgRating'">
<a-rate :value="record.avgRating" disabled allow-half :count="5" class="white-rate" />
</template>
<template v-else-if="column.key === 'totalScore'">
<span class="score-value">{{ record.totalScore || 0 }}</span>
</template>
</template>
</a-table>
</div>
<!-- 工作负载分布 - 全宽显示 -->
<div class="chart-card full-width" style="margin-top: 16px;">
<h3 class="chart-title">工作负载分布</h3>
<div ref="workloadChartRef" style="width: 100%; height: 350px;"></div>
</div>
</div>
</a-tab-pane>
@ -1342,10 +1343,116 @@ onMounted(() => {
:deep(.ant-rate) {
font-size: 14px;
color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.15);
.ant-rate-star-full .ant-rate-star-second {
color: #FFD700 !important;
}
}
// 白色星星评分样式
.white-rate {
:deep(.ant-rate-star) {
color: rgba(255, 255, 255, 0.2) !important;
&.ant-rate-star-full,
&.ant-rate-star-half {
.ant-rate-star-second {
color: #ffffff !important;
}
}
}
}
// 积分显示样式
.score-value {
font-weight: 600;
color: $brand-primary;
font-size: 14px;
}
// 全宽卡片
.full-width {
width: 100%;
}
// 深色表格样式覆盖 - 防止闪白
.dark-table {
:deep(.ant-table) {
background: transparent !important;
.ant-table-thead > tr > th {
background: rgba(0, 0, 0, 0.2) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
color: rgba(255, 255, 255, 0.65) !important;
font-weight: 500;
padding: 14px 16px;
}
.ant-table-tbody > tr {
background: transparent !important;
> td {
background: transparent !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.03) !important;
padding: 14px 16px;
color: rgba(255, 255, 255, 0.85);
transition: none !important;
}
&:hover > td {
background: rgba(49, 240, 139, 0.05) !important;
}
}
// 分页器深色样式
.ant-pagination {
.ant-pagination-item {
background: transparent;
border-color: rgba(255, 255, 255, 0.1);
a {
color: rgba(255, 255, 255, 0.65);
}
&:hover, &-active {
border-color: $brand-primary;
a {
color: $brand-primary;
}
}
}
.ant-pagination-prev, .ant-pagination-next {
.ant-pagination-item-link {
background: transparent;
border-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.65);
}
&:hover .ant-pagination-item-link {
border-color: $brand-primary;
color: $brand-primary;
}
}
.ant-pagination-options {
.ant-select-selector {
background: rgba(0, 0, 0, 0.2) !important;
border-color: rgba(255, 255, 255, 0.1) !important;
}
}
}
// 排序图标
.ant-table-column-sorter {
color: rgba(255, 255, 255, 0.45);
&-up.active, &-down.active {
color: $brand-primary;
}
}
}
}
</style>

View File

@ -621,8 +621,8 @@ interface CascaderOption {
estimatedHours?: number
}
// 设计师列表缓存 { cacheKey: designers[] }
const designersCache = reactive<Record<string, any[]>>({})
// 设计师列表缓存 { deptId: designers[] }
const designersCache = reactive<Record<number, any[]>>({})
// 表单数据
const formData = reactive({
@ -853,22 +853,27 @@ const findTaskTypeById = (id: number, items: any[] = hierarchicalTaskTypes.value
return null
}
// 加载设计师列表(根据任务类型配置的接单部门获取设计师及工作量)
// 加载设计师列表
const loadDesignersForItem = async (item: TaskTypeItem) => {
if (!item.taskTypeId) return
// 使用 taskTypeId 作为缓存key
const cacheKey = `type_${item.taskTypeId}`
const typeInfo = getTaskTypeInfo(item.taskTypeId)
if (!typeInfo || !typeInfo.allowedDeptIds) return
if (!designersCache[cacheKey]) {
// 简化处理:取第一个允许的部门ID
const deptIds = typeInfo.allowedDeptIds.split(',')
if (deptIds.length === 0) return
const deptId = Number(deptIds[0])
if (!designersCache[deptId]) {
try {
// 传入 taskTypeId,后端会根据任务类型配置的接单部门获取设计师
const designers = await userApi.getDesignersWithWorkload(undefined, item.taskTypeId)
designersCache[cacheKey] = designers || []
console.log(`加载任务类型 ${item.taskTypeId} 的设计师列表:`, designers?.length || 0, '人')
const res = await userApi.getDesignersWithWorkload(deptId)
if (res.code === 200) {
designersCache[deptId] = res.data
}
} catch (error) {
console.error('加载设计师失败:', error)
designersCache[cacheKey] = []
}
}
}
@ -877,8 +882,14 @@ const loadDesignersForItem = async (item: TaskTypeItem) => {
const getDesignersForItem = (item: TaskTypeItem) => {
if (!item.taskTypeId) return []
const cacheKey = `type_${item.taskTypeId}`
return designersCache[cacheKey] || []
const typeInfo = getTaskTypeInfo(item.taskTypeId)
if (!typeInfo || !typeInfo.allowedDeptIds) return []
const deptIds = typeInfo.allowedDeptIds.split(',')
if (deptIds.length === 0) return []
const deptId = Number(deptIds[0])
return designersCache[deptId] || []
}
// 添加任务类型
@ -951,39 +962,6 @@ watch(
}
)
// 监听发布类型变化,当切换到"指定任务"模式时,自动加载设计师列表
watch(
() => formData.releaseType,
async (newValue) => {
if (newValue === 'assigned') {
// 为所有已选择任务类型的项加载设计师列表
for (const item of formData.taskTypes) {
if (item.taskTypeId) {
await loadDesignersForItem(item)
}
}
}
}
)
// 监听任务类型选择变化,当在"指定任务"模式下选择新任务类型时,自动加载设计师
watch(
() => formData.taskTypes.map(item => item.taskTypeId),
async (newIds, oldIds) => {
if (formData.releaseType === 'assigned') {
// 找出新增或变化的任务类型
for (let i = 0; i < newIds.length; i++) {
const newId = newIds[i]
const oldId = oldIds?.[i]
if (newId && newId !== oldId) {
await loadDesignersForItem(formData.taskTypes[i])
}
}
}
},
{ deep: true }
)
// 计算预估积分(累加所有任务类型)
const estimatedScore = computed(() => {
let totalScore = 0

View File

@ -3150,8 +3150,8 @@ const showAssignDesignerModal = async (task: Task) => {
console.log('任务已预先指定设计师:', preAssignedDesignerName.value)
} else {
// 未预先指定,重置状态
approveAssignType.value = 'auto'
approveDesignerId.value = undefined
approveAssignType.value = 'auto'
approveDesignerId.value = undefined
preAssignedDesignerName.value = ''
}
@ -3773,8 +3773,8 @@ const loadInProgressTasks = async () => {
// 处理子任务
if (itemResponse.data && itemResponse.data.records) {
const items = itemResponse.data.records.map((item: any) => mapTaskItemToTask(item))
allTasks = [...allTasks, ...items]
totalCount += itemResponse.data.total || 0
allTasks = [...allTasks, ...items]
totalCount += itemResponse.data.total || 0
}
// 处理直接分配的普通任务(排除流水线任务,因为流水线任务由子任务显示)