fix: 设计师绩效排行榜横向拉满,修复积分统计,优化表格深色样式,白色星星评分
This commit is contained in:
parent
8c4f37d309
commit
81cb6b3eed
@ -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()
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
// 处理直接分配的普通任务(排除流水线任务,因为流水线任务由子任务显示)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user