feat: 设计师绩效增加延误扣分、目标积分和目标完成率;系统设置增加设计师目标积分配置

This commit is contained in:
Your Name 2026-01-08 13:20:47 +08:00
parent 60b68e0490
commit 505efce1c6
10 changed files with 825 additions and 14 deletions

View File

@ -0,0 +1,57 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.Result;
import com.designhub.dto.DesignerTargetScoreDTO;
import com.designhub.entity.Department;
import com.designhub.service.DesignerTargetScoreService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/designer-target-score")
@Tag(name = "设计师目标积分管理")
@RequiredArgsConstructor
public class DesignerTargetScoreController {
private final DesignerTargetScoreService designerTargetScoreService;
@Operation(summary = "获取目标积分配置列表")
@GetMapping("/list")
public Result<Page<Map<String, Object>>> getList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize,
@RequestParam(required = false) Long deptId,
@RequestParam(required = false) String month
) {
return Result.success(designerTargetScoreService.getTargetScoreList(page, pageSize, deptId, month));
}
@Operation(summary = "保存目标积分配置")
@PostMapping("/save")
public Result<String> save(@RequestBody DesignerTargetScoreDTO dto) {
designerTargetScoreService.saveTargetScore(dto);
return Result.success("保存成功");
}
@Operation(summary = "删除目标积分配置")
@DeleteMapping("/{id}")
public Result<String> delete(@PathVariable Long id) {
designerTargetScoreService.deleteTargetScore(id);
return Result.success("删除成功");
}
@Operation(summary = "获取所有接单部门")
@GetMapping("/receiving-departments")
public Result<List<Department>> getReceivingDepartments() {
return Result.success(designerTargetScoreService.getReceivingDepartments());
}
}

View File

@ -0,0 +1,16 @@
package com.designhub.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class DesignerTargetScoreDTO {
private Long id;
private Long deptId;
private Long userId;
private BigDecimal targetScore;
private String effectiveMonth;
private BigDecimal deductionRate;
}

View File

@ -0,0 +1,56 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 设计师目标积分配置表
*/
@Data
@TableName("designer_target_score")
public class DesignerTargetScore {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 部门ID(接单部门)
*/
private Long deptId;
/**
* 用户ID(可选,如果为空则为部门通用配置)
*/
private Long userId;
/**
* 目标积分
*/
private BigDecimal targetScore;
/**
* 生效年月(格式:yyyy-MM)
*/
private String effectiveMonth;
/**
* 延误扣分比例(每延误1小时扣除积分百分比,默认1%)
*/
private BigDecimal deductionRate;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,10 @@
package com.designhub.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.designhub.entity.DesignerTargetScore;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DesignerTargetScoreMapper extends BaseMapper<DesignerTargetScore> {
}

View File

@ -36,6 +36,7 @@ public class AnalyticsService {
private final TeamScorePoolMapper teamScorePoolMapper;
private final TaskReviewMapper taskReviewMapper;
private final RoleService roleService;
private final DesignerTargetScoreMapper designerTargetScoreMapper;
/**
* 获取需求统计数据(根据角色返回不同维度的数据)
@ -676,19 +677,63 @@ public class AnalyticsService {
log.warn("获取评价数据失败: {}", e.getMessage());
}
// 计算获得积分(使用 score 字段,score_reward 可能未设置)
// 计算获得积分和延误扣分
BigDecimal totalScore = BigDecimal.ZERO;
BigDecimal deductionScore = BigDecimal.ZERO;
// 获取扣分比例配置(默认每延误1小时扣任务积分的1%)
BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth);
for (Task task : designerTasks) {
if ("completed".equals(task.getStatus())) {
// 优先使用 scoreReward,如果为空或0则使用 score
// 计算任务积分
BigDecimal taskScore = BigDecimal.ZERO;
if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) {
totalScore = totalScore.add(task.getScoreReward());
taskScore = task.getScoreReward();
} else if (task.getScore() != null) {
totalScore = totalScore.add(BigDecimal.valueOf(task.getScore()));
taskScore = BigDecimal.valueOf(task.getScore());
}
totalScore = totalScore.add(taskScore);
// 计算延误扣分(如果超时完成)
if (task.getDeadline() != null && task.getActualEndTime() != null
&& task.getActualEndTime().isAfter(task.getDeadline())) {
// 计算延误小时数
long overdueHours = Duration.between(task.getDeadline(), task.getActualEndTime()).toHours();
if (overdueHours > 0 && taskScore.compareTo(BigDecimal.ZERO) > 0) {
// 扣分 = 任务积分 * 延误小时数 * 扣分比例
BigDecimal taskDeduction = taskScore
.multiply(BigDecimal.valueOf(overdueHours))
.multiply(deductionRate)
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
// 扣分不能超过任务积分
if (taskDeduction.compareTo(taskScore) > 0) {
taskDeduction = taskScore;
}
deductionScore = deductionScore.add(taskDeduction);
}
}
}
}
// 净积分 = 总积分 - 扣分
BigDecimal netScore = totalScore.subtract(deductionScore);
if (netScore.compareTo(BigDecimal.ZERO) < 0) {
netScore = BigDecimal.ZERO;
}
// 获取目标积分
BigDecimal targetScore = getTargetScore(designer.getId(), designer.getDeptId(), currentMonth);
// 计算目标完成率
Double targetCompletionRate = 0.0;
if (targetScore != null && targetScore.compareTo(BigDecimal.ZERO) > 0) {
targetCompletionRate = netScore.divide(targetScore, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.doubleValue();
targetCompletionRate = Math.round(targetCompletionRate * 10) / 10.0;
}
// 本周完成数
int weekCompletedCount = (int) designerTasks.stream()
.filter(t -> "completed".equals(t.getStatus()) &&
@ -716,6 +761,10 @@ public class AnalyticsService {
.fiveStarCount(fiveStarCount)
.satisfactionRate(Math.round(satisfactionRate * 10) / 10.0)
.totalScore(totalScore)
.deductionScore(deductionScore)
.netScore(netScore)
.targetScore(targetScore)
.targetCompletionRate(targetCompletionRate)
.weekCompletedCount(weekCompletedCount)
.monthCompletedCount(completedCount)
.build());
@ -1067,5 +1116,101 @@ public class AnalyticsService {
default: return status;
}
}
/**
* 获取延误扣分比例
* @param deptId 部门ID
* @param month 月份 yyyy-MM
* @return 扣分比例(默认1%)
*/
private BigDecimal getDeductionRate(Long deptId, String month) {
if (deptId == null) {
return BigDecimal.ONE; // 默认1%
}
try {
// 先查找指定月份的配置
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId) // 部门通用配置
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getDeductionRate() != null) {
return config.getDeductionRate();
}
// 查找通用配置(没有指定月份的)
config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.isNull(DesignerTargetScore::getEffectiveMonth)
);
if (config != null && config.getDeductionRate() != null) {
return config.getDeductionRate();
}
} catch (Exception e) {
log.warn("获取扣分比例配置失败: deptId={}, month={}", deptId, month, e);
}
return BigDecimal.ONE; // 默认1%
}
/**
* 获取设计师目标积分
* @param userId 用户ID
* @param deptId 部门ID
* @param month 月份 yyyy-MM
* @return 目标积分
*/
private BigDecimal getTargetScore(Long userId, Long deptId, String month) {
try {
// 先查找用户个人配置
if (userId != null) {
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getUserId, userId)
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
}
// 查找部门通用配置
if (deptId != null) {
DesignerTargetScore config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.eq(DesignerTargetScore::getEffectiveMonth, month)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
// 查找部门默认配置(没有指定月份)
config = designerTargetScoreMapper.selectOne(
new LambdaQueryWrapper<DesignerTargetScore>()
.eq(DesignerTargetScore::getDeptId, deptId)
.isNull(DesignerTargetScore::getUserId)
.isNull(DesignerTargetScore::getEffectiveMonth)
);
if (config != null && config.getTargetScore() != null) {
return config.getTargetScore();
}
}
} catch (Exception e) {
log.warn("获取目标积分配置失败: userId={}, deptId={}, month={}", userId, deptId, month, e);
}
return BigDecimal.ZERO; // 默认0
}
}

View File

@ -0,0 +1,158 @@
package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.dto.DesignerTargetScoreDTO;
import com.designhub.entity.Department;
import com.designhub.entity.DesignerTargetScore;
import com.designhub.entity.User;
import com.designhub.mapper.DepartmentMapper;
import com.designhub.mapper.DesignerTargetScoreMapper;
import com.designhub.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DesignerTargetScoreService {
private final DesignerTargetScoreMapper designerTargetScoreMapper;
private final DepartmentMapper departmentMapper;
private final UserMapper userMapper;
/**
* 获取目标积分配置列表
*/
public Page<Map<String, Object>> getTargetScoreList(int page, int pageSize, Long deptId, String month) {
Page<DesignerTargetScore> pageParam = new Page<>(page, pageSize);
LambdaQueryWrapper<DesignerTargetScore> wrapper = new LambdaQueryWrapper<>();
if (deptId != null) {
wrapper.eq(DesignerTargetScore::getDeptId, deptId);
}
if (month != null && !month.isEmpty()) {
wrapper.eq(DesignerTargetScore::getEffectiveMonth, month);
}
wrapper.orderByDesc(DesignerTargetScore::getCreateTime);
Page<DesignerTargetScore> result = designerTargetScoreMapper.selectPage(pageParam, wrapper);
// 转换为包含部门名称和用户名的结果
Page<Map<String, Object>> resultPage = new Page<>(page, pageSize);
resultPage.setTotal(result.getTotal());
List<Map<String, Object>> records = result.getRecords().stream().map(config -> {
Map<String, Object> map = new HashMap<>();
map.put("id", config.getId());
map.put("deptId", config.getDeptId());
map.put("userId", config.getUserId());
map.put("targetScore", config.getTargetScore());
map.put("effectiveMonth", config.getEffectiveMonth());
map.put("deductionRate", config.getDeductionRate());
map.put("createTime", config.getCreateTime());
// 获取部门名称
if (config.getDeptId() != null) {
Department dept = departmentMapper.selectById(config.getDeptId());
map.put("deptName", dept != null ? dept.getDeptName() : "");
}
// 获取用户名
if (config.getUserId() != null) {
User user = userMapper.selectById(config.getUserId());
map.put("userName", user != null ? user.getRealName() : "");
} else {
map.put("userName", "部门通用配置");
}
return map;
}).collect(Collectors.toList());
resultPage.setRecords(records);
return resultPage;
}
/**
* 保存目标积分配置
*/
@Transactional
public void saveTargetScore(DesignerTargetScoreDTO dto) {
DesignerTargetScore config;
if (dto.getId() != null) {
// 更新
config = designerTargetScoreMapper.selectById(dto.getId());
if (config == null) {
config = new DesignerTargetScore();
}
} else {
// 新增 - 检查是否已存在
LambdaQueryWrapper<DesignerTargetScore> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DesignerTargetScore::getDeptId, dto.getDeptId());
if (dto.getUserId() != null) {
wrapper.eq(DesignerTargetScore::getUserId, dto.getUserId());
} else {
wrapper.isNull(DesignerTargetScore::getUserId);
}
if (dto.getEffectiveMonth() != null && !dto.getEffectiveMonth().isEmpty()) {
wrapper.eq(DesignerTargetScore::getEffectiveMonth, dto.getEffectiveMonth());
} else {
wrapper.isNull(DesignerTargetScore::getEffectiveMonth);
}
config = designerTargetScoreMapper.selectOne(wrapper);
if (config == null) {
config = new DesignerTargetScore();
config.setCreateTime(LocalDateTime.now());
}
}
config.setDeptId(dto.getDeptId());
config.setUserId(dto.getUserId());
config.setTargetScore(dto.getTargetScore());
config.setEffectiveMonth(dto.getEffectiveMonth());
config.setDeductionRate(dto.getDeductionRate());
config.setUpdateTime(LocalDateTime.now());
if (config.getId() != null) {
designerTargetScoreMapper.updateById(config);
} else {
designerTargetScoreMapper.insert(config);
}
log.info("保存设计师目标积分配置: deptId={}, userId={}, targetScore={}, month={}",
dto.getDeptId(), dto.getUserId(), dto.getTargetScore(), dto.getEffectiveMonth());
}
/**
* 删除目标积分配置
*/
public void deleteTargetScore(Long id) {
designerTargetScoreMapper.deleteById(id);
log.info("删除设计师目标积分配置: id={}", id);
}
/**
* 获取所有接单部门
*/
public List<Department> getReceivingDepartments() {
return departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getDeptType, "receiving")
.eq(Department::getStatus, 1)
.orderByAsc(Department::getSortOrder)
);
}
}

View File

@ -57,6 +57,10 @@ public class DesignerPerformanceVO {
private Integer fiveStarCount; // 5星评价数
private Double satisfactionRate; // 好评率
private BigDecimal totalScore; // 获得总积分
private BigDecimal deductionScore; // 延误扣分
private BigDecimal netScore; // 净积分(总积分-扣分)
private BigDecimal targetScore; // 当月目标积分
private Double targetCompletionRate;// 目标完成率
private Integer weekCompletedCount; // 本周完成数
private Integer monthCompletedCount;// 本月完成数
}

View File

@ -97,3 +97,49 @@ export const initDefaultConfigs = () => {
return request.post<any>('/system-config/init')
}
// ==================== 设计师目标积分配置 ====================
export interface DesignerTargetScore {
id?: number
deptId: number
userId?: number
targetScore: number
effectiveMonth?: string
deductionRate?: number
deptName?: string
userName?: string
}
/**
* 获取目标积分配置列表
*/
export const getDesignerTargetScoreList = (params: {
page?: number
pageSize?: number
deptId?: number
month?: string
}) => {
return request.get<any>('/designer-target-score/list', { params })
}
/**
* 保存目标积分配置
*/
export const saveDesignerTargetScore = (data: DesignerTargetScore) => {
return request.post<any>('/designer-target-score/save', data)
}
/**
* 删除目标积分配置
*/
export const deleteDesignerTargetScore = (id: number) => {
return request.delete<any>(`/designer-target-score/${id}`)
}
/**
* 获取所有接单部门
*/
export const getReceivingDepartments = () => {
return request.get<any>('/designer-target-score/receiving-departments')
}

View File

@ -225,6 +225,20 @@
<template v-else-if="column.key === 'totalScore'">
<span class="score-value">{{ record.totalScore || 0 }}</span>
</template>
<template v-else-if="column.key === 'deductionScore'">
<span class="deduction-value">{{ record.deductionScore || 0 }}</span>
</template>
<template v-else-if="column.key === 'netScore'">
<span class="net-score-value">{{ record.netScore || 0 }}</span>
</template>
<template v-else-if="column.key === 'targetScore'">
<span>{{ record.targetScore || '-' }}</span>
</template>
<template v-else-if="column.key === 'targetCompletionRate'">
<span :class="getTargetRateClass(record.targetCompletionRate)">
{{ record.targetScore > 0 ? (record.targetCompletionRate || 0) + '%' : '-' }}
</span>
</template>
</template>
</a-table>
</div>
@ -577,15 +591,18 @@ const userColumns = [
]
const designerColumns = [
{ title: '排名', key: 'rank', width: 80, align: 'center' },
{ title: '设计师', dataIndex: 'realName', key: 'realName' },
{ title: '部门', dataIndex: 'deptName', key: 'deptName' },
{ title: '接单量', dataIndex: 'receivedCount', key: 'receivedCount', sorter: (a: any, b: any) => a.receivedCount - b.receivedCount, align: 'center' },
{ title: '完成量', dataIndex: 'completedCount', key: 'completedCount', align: 'center' },
{ title: '完成率', dataIndex: 'completionRate', key: 'completionRate', customRender: ({ text }: any) => text + '%', align: 'center' },
{ title: '准时率', key: 'onTimeRate', align: 'center' },
{ title: '积分', dataIndex: 'totalScore', key: 'totalScore', sorter: (a: any, b: any) => (a.totalScore || 0) - (b.totalScore || 0), align: 'center' },
{ title: '评分', key: 'avgRating', width: 140, align: 'center' }
{ title: '排名', key: 'rank', width: 60, align: 'center' },
{ title: '设计师', dataIndex: 'realName', key: 'realName', width: 80 },
{ title: '部门', dataIndex: 'deptName', key: 'deptName', width: 100 },
{ title: '接单', dataIndex: 'receivedCount', key: 'receivedCount', sorter: (a: any, b: any) => a.receivedCount - b.receivedCount, align: 'center', width: 60 },
{ title: '完成', dataIndex: 'completedCount', key: 'completedCount', align: 'center', width: 60 },
{ title: '准时率', key: 'onTimeRate', align: 'center', width: 70 },
{ title: '获得积分', key: 'totalScore', align: 'center', width: 80 },
{ title: '延误扣分', key: 'deductionScore', align: 'center', width: 80 },
{ title: '净积分', key: 'netScore', sorter: (a: any, b: any) => (a.netScore || 0) - (b.netScore || 0), align: 'center', width: 70 },
{ title: '目标积分', key: 'targetScore', align: 'center', width: 80 },
{ title: '目标完成率', key: 'targetCompletionRate', align: 'center', width: 90 },
{ title: '评分', key: 'avgRating', width: 120, align: 'center' }
]
const efficiencyColumns = [
@ -776,6 +793,15 @@ const getRankClass = (rank: number) => {
return ''
}
// 目标完成率样式
const getTargetRateClass = (rate: number | null | undefined) => {
if (rate === null || rate === undefined) return ''
if (rate >= 100) return 'text-success' // 完成目标 - 绿色
if (rate >= 80) return 'text-primary' // 接近目标 - 蓝色
if (rate >= 50) return 'text-warning' // 进度一般 - 黄色
return 'text-danger' // 进度落后 - 红色
}
const getProgressColor = (rate: number) => {
if (rate >= 80) return '#31f08b'
if (rate >= 60) return '#3B82F6'
@ -1371,6 +1397,25 @@ onMounted(() => {
font-size: 14px;
}
// 延误扣分样式
.deduction-value {
font-weight: 500;
color: #ef4444;
font-size: 14px;
}
// 净积分样式
.net-score-value {
font-weight: 600;
color: #60a5fa;
font-size: 14px;
}
// 文字颜色
.text-primary {
color: #60a5fa !important;
}
// 全宽卡片
.full-width {
width: 100%;

View File

@ -1414,6 +1414,86 @@
</a-table>
</div>
</a-tab-pane>
<!-- 设计师目标积分设置 -->
<a-tab-pane key="designer-target" tab="设计师目标积分">
<div class="settings-section">
<div class="section-header">
<h2 class="section-title">设计师目标积分配置</h2>
<a-button type="primary" @click="showDesignerTargetModal()">
<template #icon><Icon name="plus" size="sm" /></template>
添加配置
</a-button>
</div>
<a-alert
message="配置说明"
description="为接单部门设计师设置月度目标积分和延误扣分比例。延误扣分 = 任务积分 × 延误小时数 × 扣分比例%。"
type="info"
show-icon
style="margin-bottom: 16px;"
/>
<div class="filter-bar" style="margin-bottom: 16px; display: flex; gap: 16px;">
<a-select
v-model:value="designerTargetFilter.deptId"
placeholder="筛选部门"
style="width: 200px"
allow-clear
@change="loadDesignerTargetList"
>
<a-select-option
v-for="dept in receivingDepartments"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
</a-select>
<a-date-picker
v-model:value="designerTargetFilter.monthValue"
picker="month"
placeholder="筛选月份"
format="YYYY-MM"
value-format="YYYY-MM"
@change="handleDesignerTargetMonthChange"
/>
</div>
<a-table
:columns="designerTargetColumns"
:data-source="designerTargetList"
:loading="loadingDesignerTarget"
row-key="id"
:pagination="{ pageSize: 10 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'targetScore'">
<span class="target-score">{{ record.targetScore }} 分</span>
</template>
<template v-else-if="column.key === 'deductionRate'">
<span>{{ record.deductionRate || 1 }}%</span>
</template>
<template v-else-if="column.key === 'effectiveMonth'">
<span>{{ record.effectiveMonth || '通用配置' }}</span>
</template>
<template v-else-if="column.key === 'actions'">
<a-space>
<a-button type="link" size="small" @click="showDesignerTargetModal(record)">
编辑
</a-button>
<a-popconfirm
title="确定删除?"
@confirm="handleDeleteDesignerTarget(record.id)"
>
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
</a-tab-pane>
</a-tabs>
<!-- 需求类型弹窗 -->
@ -2093,6 +2173,70 @@
</a-form-item>
</a-form>
</a-modal>
<!-- 设计师目标积分配置弹窗 -->
<a-modal
v-model:open="designerTargetModalVisible"
:title="designerTargetModalTitle"
width="500px"
@ok="handleSaveDesignerTarget"
@cancel="designerTargetModalVisible = false"
>
<a-form :model="designerTargetForm" layout="vertical">
<a-form-item label="接单部门" required>
<a-select
v-model:value="designerTargetForm.deptId"
placeholder="请选择接单部门"
>
<a-select-option
v-for="dept in receivingDepartments"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="目标积分" required>
<a-input-number
v-model:value="designerTargetForm.targetScore"
:min="0"
:max="10000"
style="width: 100%"
placeholder="每月目标积分,例如:500"
/>
<div style="font-size: 12px; color: #888; margin-top: 4px;">
设计师每月需完成的目标积分数
</div>
</a-form-item>
<a-form-item label="延误扣分比例 (%)">
<a-input-number
v-model:value="designerTargetForm.deductionRate"
:min="0"
:max="100"
:precision="1"
style="width: 100%"
placeholder="默认1%"
/>
<div style="font-size: 12px; color: #888; margin-top: 4px;">
每延误1小时,扣除任务积分的百分比。例如:设置为1%,延误2小时,100分任务扣除 2 分
</div>
</a-form-item>
<a-form-item label="生效月份">
<a-date-picker
v-model:value="designerTargetForm.effectiveMonth"
picker="month"
placeholder="留空则为通用配置"
format="YYYY-MM"
value-format="YYYY-MM"
style="width: 100%"
/>
<div style="font-size: 12px; color: #888; margin-top: 4px;">
留空表示通用配置,适用于所有月份
</div>
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</DefaultLayout>
@ -2134,7 +2278,12 @@ import {
batchUpdateSystemConfigs,
createSystemConfig,
updateSystemConfig,
type SystemConfig
getDesignerTargetScoreList,
saveDesignerTargetScore,
deleteDesignerTargetScore,
getReceivingDepartments,
type SystemConfig,
type DesignerTargetScore
} from '@/api/system'
import {
getAllUsers,
@ -2304,6 +2453,35 @@ const thirdPartyDbColumns = [
{ title: '操作', key: 'actions', width: 260 }
]
// 设计师目标积分配置相关
const designerTargetList = ref<any[]>([])
const loadingDesignerTarget = ref(false)
const receivingDepartments = ref<any[]>([])
const designerTargetFilter = reactive({
deptId: undefined as number | undefined,
month: undefined as string | undefined,
monthValue: undefined as any
})
const designerTargetForm = reactive<DesignerTargetScore>({
deptId: 0,
userId: undefined,
targetScore: 0,
effectiveMonth: undefined,
deductionRate: 1
})
const designerTargetModalVisible = ref(false)
const designerTargetModalTitle = ref('添加目标积分配置')
const designerTargetColumns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
{ title: '部门', dataIndex: 'deptName', key: 'deptName' },
{ title: '适用对象', dataIndex: 'userName', key: 'userName' },
{ title: '目标积分', key: 'targetScore', width: 100 },
{ title: '延误扣分比例', key: 'deductionRate', width: 120 },
{ title: '生效月份', key: 'effectiveMonth', width: 120 },
{ title: '操作', key: 'actions', width: 150 }
]
const defaultDepartmentForm = (): DepartmentForm => ({
deptName: '',
deptCode: '',
@ -4431,6 +4609,9 @@ onMounted(() => {
loadDelayPenaltyConfig()
// 加载第三方素材API配置
loadExternalAssetApiConfig()
// 加载设计师目标积分配置
loadReceivingDepartments()
loadDesignerTargetList()
})
// S3配置相关方法
@ -4623,6 +4804,99 @@ const handleDisableThirdPartyDb = async (id: number) => {
}
}
// ==================== 设计师目标积分配置相关方法 ====================
const loadReceivingDepartments = async () => {
try {
const response = await getReceivingDepartments()
receivingDepartments.value = response.data || []
} catch (error: any) {
console.error('加载接单部门失败:', error)
}
}
const loadDesignerTargetList = async () => {
loadingDesignerTarget.value = true
try {
const params: any = { page: 1, pageSize: 100 }
if (designerTargetFilter.deptId) {
params.deptId = designerTargetFilter.deptId
}
if (designerTargetFilter.month) {
params.month = designerTargetFilter.month
}
const response = await getDesignerTargetScoreList(params)
designerTargetList.value = response.data?.records || []
} catch (error: any) {
console.error('加载目标积分配置失败:', error)
message.error(error.response?.data?.message || '加载目标积分配置失败')
} finally {
loadingDesignerTarget.value = false
}
}
const handleDesignerTargetMonthChange = (value: any) => {
designerTargetFilter.month = value
loadDesignerTargetList()
}
const showDesignerTargetModal = (record?: any) => {
if (record) {
designerTargetModalTitle.value = '编辑目标积分配置'
Object.assign(designerTargetForm, {
id: record.id,
deptId: record.deptId,
userId: record.userId,
targetScore: record.targetScore,
effectiveMonth: record.effectiveMonth,
deductionRate: record.deductionRate || 1
})
} else {
designerTargetModalTitle.value = '添加目标积分配置'
Object.assign(designerTargetForm, {
id: undefined,
deptId: receivingDepartments.value[0]?.id || 0,
userId: undefined,
targetScore: 500,
effectiveMonth: undefined,
deductionRate: 1
})
}
designerTargetModalVisible.value = true
}
const handleSaveDesignerTarget = async () => {
if (!designerTargetForm.deptId) {
message.warning('请选择部门')
return
}
if (!designerTargetForm.targetScore || designerTargetForm.targetScore <= 0) {
message.warning('请输入有效的目标积分')
return
}
try {
await saveDesignerTargetScore(designerTargetForm)
message.success('保存成功')
designerTargetModalVisible.value = false
loadDesignerTargetList()
} catch (error: any) {
console.error('保存目标积分配置失败:', error)
message.error(error.response?.data?.message || '保存目标积分配置失败')
}
}
const handleDeleteDesignerTarget = async (id: number) => {
try {
await deleteDesignerTargetScore(id)
message.success('删除成功')
loadDesignerTargetList()
} catch (error: any) {
console.error('删除目标积分配置失败:', error)
message.error(error.response?.data?.message || '删除目标积分配置失败')
}
}
watch(activeTab, (value) => {
// 切换到用户与部门 tab 时,始终刷新数据以获取最新状态
if (value === 'users') {