feat: 新增三个功能

1. 任务标题自动生成
   - 根据SPU信息+平台+品牌+任务子类型+需求类型自动组合生成标题
   - 添加watch监听相关字段变化自动更新标题

2. 预览图上传功能
   - 添加S3预览图上传API(自动压缩到200px宽度)
   - Task实体增加previewImage字段
   - 前端添加预览图上传组件
   - 任务卡片显示65x65像素预览图

3. 接单大厅按部门显示
   - 根据用户所属部门筛选可接单的任务类型
   - 只显示用户部门对应的任务类型的任务
This commit is contained in:
Your Name 2026-01-05 11:57:59 +08:00
parent 2687906aa1
commit 2c713febc6
9 changed files with 579 additions and 35 deletions

View File

@ -167,6 +167,13 @@
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- Thumbnailator - 图片处理库 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
</dependencies>
<build>

View File

@ -181,4 +181,54 @@ public class FileUploadController {
return Result.error("获取文件列表失败: " + e.getMessage());
}
}
/**
* 上传预览图(自动压缩到最大宽度200px)
*
* @param file 图片文件
* @param request HttpServletRequest
* @return 预览图URL
*/
@PostMapping("/upload-preview")
public Result<String> uploadPreviewImage(
@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
try {
// 从token获取用户信息
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return Result.error("未登录或token无效");
}
token = token.substring(7);
String username = jwtUtil.getUsernameFromToken(token);
if (username == null) {
return Result.error("token无效");
}
User user = userService.getUserByUsername(username);
if (user == null) {
return Result.error("用户不存在");
}
// 验证是否为图片
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
return Result.error("只能上传图片文件(jpg, png, gif等)");
}
// 上传预览图
String previewUrl = s3Service.uploadPreviewImage(
file,
user.getId(),
user.getRealName() != null ? user.getRealName() : user.getUsername()
);
return Result.success(previewUrl);
} catch (Exception e) {
return Result.error("预览图上传失败: " + e.getMessage());
}
}
}

View File

@ -171,7 +171,7 @@ public class TaskController {
return Result.success(taskPage);
}
@Operation(summary = "任务大厅", description = "获取可接单的任务列表,支持多种筛选、搜索和排序")
@Operation(summary = "任务大厅", description = "获取可接单的任务列表,支持多种筛选、搜索和排序,根据用户所属部门筛选")
@GetMapping("/hall")
public Result<IPage<Task>> getTaskHall(
@Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId,
@ -187,17 +187,23 @@ public class TaskController {
@Parameter(description = "发布时间范围:today/week/month") @RequestParam(required = false) String timeRange,
@Parameter(description = "排序方式:latest/score/deadline/hot") @RequestParam(required = false) String sortBy,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize,
@RequestHeader("Authorization") String authHeader) {
// 获取当前用户的部门ID
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
Long userDeptId = taskService.getUserDeptId(userId);
log.info("查询任务大厅: taskTypeId={}, demandTypeId={}, brandId={}, categoryId={}, platformId={}, " +
"taskSubType={}, minScore={}, maxScore={}, keyword={}, publisherName={}, timeRange={}, " +
"sortBy={}, page={}, pageSize={}",
"sortBy={}, page={}, pageSize={}, userDeptId={}",
taskTypeId, demandTypeId, brandId, categoryId, platformId, taskSubType,
minScore, maxScore, keyword, publisherName, timeRange, sortBy, page, pageSize);
minScore, maxScore, keyword, publisherName, timeRange, sortBy, page, pageSize, userDeptId);
IPage<Task> taskPage = taskService.getTaskHall(page, pageSize, taskTypeId, demandTypeId,
brandId, categoryId, platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy);
publisherName, timeRange, sortBy, userDeptId);
return Result.success(taskPage);
}

View File

@ -105,6 +105,11 @@ public class Task implements Serializable {
*/
private String attachmentUrl;
/**
* 预览图URL
*/
private String previewImage;
/**
* 交付路径
*/
@ -409,6 +414,9 @@ public class Task implements Serializable {
public String getAttachmentUrl() { return attachmentUrl; }
public void setAttachmentUrl(String attachmentUrl) { this.attachmentUrl = attachmentUrl; }
public String getPreviewImage() { return previewImage; }
public void setPreviewImage(String previewImage) { this.previewImage = previewImage; }
public String getDeliveryPath() { return deliveryPath; }
public void setDeliveryPath(String deliveryPath) { this.deliveryPath = deliveryPath; }

View File

@ -76,6 +76,71 @@ public interface TaskMapper extends BaseMapper<Task> {
@Param("timeRange") String timeRange,
@Param("sortBy") String sortBy);
/**
* 查询任务大厅(按部门筛选)
*/
@Select("<script>" +
"SELECT t.*, " +
" u.real_name AS publisher_name, " +
" d.dept_name AS publisher_dept_name, " +
" b.brand_name AS brand_name, " +
" c.category_name AS category_name, " +
" p.platform_name AS platform_name, " +
" approver.real_name AS current_approver_name " +
"FROM task t " +
"LEFT JOIN sys_user u ON t.publisher_id = u.id " +
"LEFT JOIN sys_dept d ON t.publisher_dept_id = d.id " +
"LEFT JOIN brand b ON t.brand_id = b.id " +
"LEFT JOIN category c ON t.category_id = c.id " +
"LEFT JOIN platform p ON t.platform_id = p.id " +
"LEFT JOIN sys_user approver ON t.current_approver_id = approver.id " +
"WHERE t.status = 'pending' AND t.release_type = 'recruitment' AND t.approval_status = 'approved' AND t.deleted = 0 AND (t.is_pipeline = 0 OR t.is_pipeline IS NULL)" +
"<if test='allowedTaskTypeIds != null and allowedTaskTypeIds.size() > 0'>" +
" AND t.task_type_id IN " +
" <foreach item='typeId' collection='allowedTaskTypeIds' open='(' separator=',' close=')'>" +
" #{typeId}" +
" </foreach>" +
"</if>" +
"<if test='taskTypeId != null'> AND t.task_type_id = #{taskTypeId}</if>" +
"<if test='demandTypeId != null'> AND t.demand_type_id = #{demandTypeId}</if>" +
"<if test='brandId != null'> AND t.brand_id = #{brandId}</if>" +
"<if test='categoryId != null'> AND t.category_id = #{categoryId}</if>" +
"<if test='platformId != null'> AND t.platform_id = #{platformId}</if>" +
"<if test='taskSubType != null and taskSubType != \"\"'> AND t.task_sub_type = #{taskSubType}</if>" +
"<if test='minScore != null'> AND t.score &gt;= #{minScore}</if>" +
"<if test='maxScore != null'> AND t.score &lt;= #{maxScore}</if>" +
"<if test='keyword != null and keyword != \"\"'> " +
" AND (t.title LIKE CONCAT('%', #{keyword}, '%') " +
" OR t.spu_info LIKE CONCAT('%', #{keyword}, '%') " +
" OR t.remark LIKE CONCAT('%', #{keyword}, '%') " +
" OR u.real_name LIKE CONCAT('%', #{keyword}, '%'))" +
"</if>" +
"<if test='publisherName != null and publisherName != \"\"'> AND u.real_name LIKE CONCAT('%', #{publisherName}, '%')</if>" +
"<if test='timeRange != null and timeRange == \"today\"'> AND DATE(t.create_time) = CURDATE()</if>" +
"<if test='timeRange != null and timeRange == \"week\"'> AND t.create_time &gt;= DATE_SUB(NOW(), INTERVAL 7 DAY)</if>" +
"<if test='timeRange != null and timeRange == \"month\"'> AND t.create_time &gt;= DATE_SUB(NOW(), INTERVAL 30 DAY)</if>" +
"<choose>" +
" <when test='sortBy == \"score\"'> ORDER BY t.score DESC, t.create_time DESC</when>" +
" <when test='sortBy == \"deadline\"'> ORDER BY t.deadline ASC, t.create_time DESC</when>" +
" <when test='sortBy == \"hot\"'> ORDER BY t.score DESC, (SELECT COUNT(*) FROM task WHERE task_type_id = t.task_type_id AND status = 'pending') DESC, t.create_time DESC</when>" +
" <otherwise> ORDER BY t.create_time DESC</otherwise>" +
"</choose>" +
"</script>")
IPage<Task> selectTaskHallByDept(Page<Task> page,
@Param("taskTypeId") Long taskTypeId,
@Param("demandTypeId") Long demandTypeId,
@Param("brandId") Long brandId,
@Param("categoryId") Long categoryId,
@Param("platformId") Long platformId,
@Param("taskSubType") String taskSubType,
@Param("minScore") Integer minScore,
@Param("maxScore") Integer maxScore,
@Param("keyword") String keyword,
@Param("publisherName") String publisherName,
@Param("timeRange") String timeRange,
@Param("sortBy") String sortBy,
@Param("allowedTaskTypeIds") List<Long> allowedTaskTypeIds);
/**
* 查询我发布的任务
*/

View File

@ -18,11 +18,14 @@ import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import net.coobird.thumbnailator.Thumbnails;
/**
* S3文件存储服务
@ -143,4 +146,84 @@ public class S3Service {
.orderByDesc(FileUpload::getCreateTime);
return fileUploadMapper.selectList(wrapper);
}
/**
* 上传预览图(压缩到最大宽度200px,保持比例)
*
* @param file 图片文件
* @param uploaderId 上传者ID
* @param uploaderName 上传者姓名
* @return 压缩后图片的URL
*/
public String uploadPreviewImage(MultipartFile file, Long uploaderId, String uploaderName) throws IOException {
// 获取激活的S3配置
S3Config config = s3ConfigMapper.getActiveConfig();
if (config == null) {
throw new RuntimeException("S3配置未激活,请先配置S3存储");
}
// 验证是否为图片
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
throw new RuntimeException("只能上传图片文件");
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
String storedName = "preview_" + timestamp + "_" + uuid + fileExtension;
// 构建文件路径(预览图专用目录)
String filePath = config.getFilePath() + "previews/" + storedName;
try {
// 压缩图片到最大宽度200px,保持比例
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Thumbnails.of(file.getInputStream())
.width(200) // 最大宽度200像素
.keepAspectRatio(true) // 保持比例
.outputQuality(0.85) // 图片质量
.toOutputStream(outputStream);
byte[] compressedBytes = outputStream.toByteArray();
// 创建S3客户端
S3Client s3Client = createS3Client(config);
// 上传压缩后的图片
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(config.getBucket())
.key(filePath)
.contentType(contentType)
.build();
s3Client.putObject(putObjectRequest,
RequestBody.fromInputStream(new ByteArrayInputStream(compressedBytes), compressedBytes.length));
// 生成文件URL
String fileUrl = config.getPublicDomain() + "/" + filePath;
// 保存文件上传记录
FileUpload fileUpload = new FileUpload();
fileUpload.setOriginalName(originalFilename);
fileUpload.setStoredName(storedName);
fileUpload.setFilePath(filePath);
fileUpload.setFileUrl(fileUrl);
fileUpload.setFileSize((long) compressedBytes.length);
fileUpload.setFileType(contentType);
fileUpload.setUploaderId(uploaderId);
fileUpload.setUploaderName(uploaderName);
fileUpload.setRelatedType("preview_image");
fileUploadMapper.insert(fileUpload);
log.info("预览图上传成功: {} -> {} (压缩后大小: {} bytes)", originalFilename, fileUrl, compressedBytes.length);
return fileUrl;
} catch (Exception e) {
log.error("预览图上传失败: {}", originalFilename, e);
throw new RuntimeException("预览图上传失败: " + e.getMessage());
}
}
}

View File

@ -634,16 +634,48 @@ public class TaskService {
}
/**
* 获取任务大厅
* 获取任务大厅(根据用户部门筛选可接单的任务)
*/
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) {
String timeRange, String sortBy, Long userDeptId) {
Page<Task> pageParam = new Page<>(page, pageSize);
return taskMapper.selectTaskHall(pageParam, taskTypeId, demandTypeId, brandId, categoryId,
// 获取当前用户部门可以接单的任务类型ID列表
List<Long> allowedTaskTypeIds = null;
if (userDeptId != null) {
allowedTaskTypeIds = demandTypeMapper.selectList(null).stream()
.filter(dt -> {
String allowedDepts = dt.getAllowedDeptIds();
// 如果allowedDeptIds为空,表示所有部门都可接单
if (allowedDepts == null || allowedDepts.isEmpty()) {
return true;
}
// 检查用户部门是否在允许列表中
String[] deptIds = allowedDepts.split(",");
for (String deptId : deptIds) {
if (deptId.trim().equals(String.valueOf(userDeptId))) {
return true;
}
}
return false;
})
.map(DemandType::getId)
.collect(Collectors.toList());
}
return taskMapper.selectTaskHallByDept(pageParam, taskTypeId, demandTypeId, brandId, categoryId,
platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy);
publisherName, timeRange, sortBy, allowedTaskTypeIds);
}
/**
* 获取用户所属部门ID
*/
public Long getUserDeptId(Long userId) {
User user = userService.getUserById(userId);
return user != null ? user.getDepartmentId() : null;
}
/**

View File

@ -25,13 +25,17 @@
:rules="formRules"
layout="vertical"
>
<!-- 任务标题 -->
<!-- 任务标题(自动生成) -->
<a-form-item label="任务标题" name="title" required>
<a-input
v-model:value="formData.title"
placeholder="请输入任务标题"
placeholder="填写下方信息后自动生成标题"
size="large"
/>
<div class="title-auto-tip">
<Icon name="info" size="xs" style="margin-right: 4px;" />
<span>标题将根据SPU信息、平台、品牌、任务子类型、需求类型自动生成</span>
</div>
</a-form-item>
<!-- 流水线任务说明 -->
@ -265,6 +269,32 @@
/>
</a-form-item>
<!-- 预览图上传 -->
<a-form-item label="预览图" name="previewImage">
<div class="preview-upload-area">
<a-upload
v-model:file-list="previewFileList"
:before-upload="beforePreviewUpload"
:custom-request="handlePreviewUpload"
list-type="picture-card"
:max-count="1"
accept="image/*"
@remove="handlePreviewRemove"
>
<div v-if="!formData.previewImage" class="upload-button">
<Icon name="plus" size="lg" />
<div class="upload-text">上传预览图</div>
</div>
</a-upload>
<div class="preview-tip">
<span class="tip-icon">
<Icon name="image" size="sm" color="#31f08b" />
</span>
<span>建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上</span>
</div>
</div>
</a-form-item>
<!-- 上传附件 -->
<a-form-item label="需求表格附件" name="attachmentUrl">
<div class="upload-area">
@ -445,6 +475,10 @@ const taskSubTypes = ref<TaskSubType[]>([])
const fileList = ref<UploadFile[]>([])
const uploadProgress = ref(0)
// 预览图上传相关
const previewFileList = ref<UploadFile[]>([])
const previewUploading = ref(false)
// 任务类型项接口
interface TaskTypeItem {
taskTypeId: number | undefined
@ -467,6 +501,7 @@ const formData = reactive({
categoryId: undefined,
spuInfo: '',
attachmentUrl: '',
previewImage: '', // 预览图URL
remark: '', // 备注信息
releaseType: 'default',
// receiverId: undefined, // 已移除,改用子任务指定
@ -570,6 +605,66 @@ const removeTaskType = (index: number) => {
}
}
// 自动生成任务标题
const generateTitle = () => {
const parts: string[] = []
// 1. SPU信息
if (formData.spuInfo && formData.spuInfo.trim()) {
parts.push(formData.spuInfo.trim())
}
// 2. 平台名称
if (formData.platformId) {
const platform = platforms.value.find((p: any) => p.id === formData.platformId)
if (platform) {
parts.push(platform.name)
}
}
// 3. 品牌名称
if (formData.brandId) {
const brand = brands.value.find((b: any) => b.id === formData.brandId)
if (brand) {
parts.push(brand.name)
}
}
// 4. 任务子类型
if (formData.taskSubType) {
parts.push(formData.taskSubType)
}
// 5. 需求类型(第一个任务类型名称)
if (formData.taskTypes.length > 0 && formData.taskTypes[0].taskTypeId) {
const taskType = taskTypes.value.find((t: any) => t.id === formData.taskTypes[0].taskTypeId)
if (taskType) {
parts.push(taskType.name)
}
}
// 生成标题
if (parts.length > 0) {
formData.title = parts.join('-')
}
}
// 监听相关字段变化,自动生成标题
watch(
() => [
formData.spuInfo,
formData.platformId,
formData.brandId,
formData.taskSubType,
formData.taskTypes[0]?.taskTypeId
],
() => {
// 只有当标题为空或是之前自动生成的标题时才自动更新
generateTitle()
},
{ deep: true }
)
// 计算预估积分(累加所有任务类型)
const estimatedScore = computed(() => {
let totalScore = 0
@ -1035,6 +1130,74 @@ const handleRemove = (file: UploadFile) => {
return true
}
// 预览图上传前验证
const beforePreviewUpload = (file: File) => {
const isImage = file.type.startsWith('image/')
if (!isImage) {
message.error('只能上传图片文件')
return false
}
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('图片大小不能超过 10MB')
return false
}
return true
}
// 预览图上传处理
const handlePreviewUpload = async (options: any) => {
const { file, onSuccess, onError } = options
try {
previewUploading.value = true
const formDataObj = new FormData()
formDataObj.append('file', file)
const response = await fetch('/api/file/upload-preview', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: formDataObj
})
const result = await response.json()
if (result.code === 200 && result.data) {
formData.previewImage = result.data
previewFileList.value = [{
uid: file.uid,
name: file.name,
status: 'done',
url: result.data
} as UploadFile]
if (onSuccess) {
onSuccess(result.data)
}
message.success('预览图上传成功')
} else {
throw new Error(result.msg || '上传失败')
}
} catch (error: any) {
console.error('预览图上传失败:', error)
if (onError) {
onError(error)
}
message.error('预览图上传失败: ' + error.message)
} finally {
previewUploading.value = false
}
}
// 删除预览图
const handlePreviewRemove = () => {
formData.previewImage = ''
previewFileList.value = []
return true
}
// 加载任务数据用于编辑
const loadTaskForEdit = async (taskId: number) => {
try {
@ -1637,5 +1800,65 @@ onUnmounted(() => {
}
}
}
// 任务标题自动生成提示
.title-auto-tip {
margin-top: 8px;
font-size: 12px;
color: $text-muted;
display: flex;
align-items: center;
}
// 预览图上传区域
.preview-upload-area {
.upload-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: $text-muted;
.upload-text {
margin-top: 8px;
font-size: 12px;
}
}
.preview-tip {
margin-top: 8px;
font-size: 12px;
color: $text-muted;
display: flex;
align-items: center;
.tip-icon {
margin-right: 6px;
}
}
:deep(.ant-upload-list-picture-card) {
.ant-upload-list-item {
border-color: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.02);
&:hover {
border-color: $brand-primary;
}
}
}
:deep(.ant-upload-select-picture-card) {
width: 104px;
height: 104px;
background: rgba(255, 255, 255, 0.02);
border: 1px dashed rgba(255, 255, 255, 0.2);
border-radius: 8px;
&:hover {
border-color: $brand-primary;
}
}
}
</style>

View File

@ -200,28 +200,37 @@
class="task-card chart-card"
@click="handleTaskClick(task)"
>
<!-- 任务标题 -->
<div class="task-header">
<h3 class="task-title">{{ task.title }}</h3>
<div class="task-badges">
<span v-if="task.isSubTask" class="badge badge-subtask">
<Icon name="layers" size="sm" color="#1890ff" style="margin-right: 4px; vertical-align: middle;" />
子任务
</span>
<span v-if="isNewTask(task)" class="badge badge-new">
<Icon name="sparkles" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
新发布
</span>
<span v-if="isHighScoreTask(task)" class="badge badge-high-score">
<Icon name="star" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
高分
</span>
<span v-if="task.isUrgent" class="badge badge-urgent">
<Icon name="fire" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
紧急
</span>
<!-- 带预览图的卡片布局 -->
<div class="card-with-preview">
<!-- 预览图 -->
<div v-if="task.previewImage" class="task-preview">
<img :src="task.previewImage" alt="预览图" />
</div>
</div>
<!-- 卡片内容 -->
<div class="card-content" :class="{ 'has-preview': task.previewImage }">
<!-- 任务标题 -->
<div class="task-header">
<h3 class="task-title">{{ task.title }}</h3>
<div class="task-badges">
<span v-if="task.isSubTask" class="badge badge-subtask">
<Icon name="layers" size="sm" color="#1890ff" style="margin-right: 4px; vertical-align: middle;" />
子任务
</span>
<span v-if="isNewTask(task)" class="badge badge-new">
<Icon name="sparkles" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
新发布
</span>
<span v-if="isHighScoreTask(task)" class="badge badge-high-score">
<Icon name="star" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
高分
</span>
<span v-if="task.isUrgent" class="badge badge-urgent">
<Icon name="fire" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
紧急
</span>
</div>
</div>
<!-- 流程阶段 -->
<div v-if="getProcessStage(task)" class="process-stage">
@ -337,6 +346,8 @@
{{ canGrabTask(task).canGrab ? '抢单' : '不可接单' }}
</button>
</div>
</div> <!-- 结束 card-content -->
</div> <!-- 结束 card-with-preview -->
</div>
</div>
@ -3918,11 +3929,11 @@ const handleConfigUpdate = async (event: CustomEvent) => {
}
// 组件挂载
// 获取任务状态文本(增强版,支持区分驳回和草稿)
// 获取任务状态文本(增强版,支持区分驳回、审核中和草稿)
const getTaskStatusText = (status: string, task?: any) => {
// 如果有task对象,检查approvalStatus来区分驳回和草稿
// 如果有task对象,检查approvalStatus来区分不同状态
if (task) {
// 检查是否被驳回(无论status是draft还是rejected)
// 1. 检查是否被驳回(无论status是draft还是rejected)
if (task.approvalStatus === 'rejected_by_demand' || task.approvalStatus === 'rejected_by_receiving') {
return '已驳回'
}
@ -3930,6 +3941,32 @@ const getTaskStatusText = (status: string, task?: any) => {
if (status === 'rejected') {
return '已驳回'
}
// 2. 检查是否正在审核中
if (task.approvalStatus === 'pending_demand_approval') {
return '待部门审核'
}
if (task.approvalStatus === 'pending_receiving_approval') {
return '待接单审核'
}
// 3. 流水线任务且已审核通过,根据是否有接收人显示不同状态
if (task.isPipeline === 1 && task.approvalStatus === 'approved') {
if (task.receiverId) {
return '进行中'
} else {
return '待接单'
}
}
// 4. 普通任务已审核通过但status还是draft,修正显示
if (task.approvalStatus === 'approved' && status === 'draft') {
if (task.receiverId) {
return '进行中'
} else {
return '待接单'
}
}
}
const statusMap: Record<string, string> = {
@ -4371,6 +4408,39 @@ onUnmounted(() => {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
// 带预览图的卡片布局
.card-with-preview {
display: flex;
gap: 16px;
.task-preview {
flex-shrink: 0;
width: 65px;
height: 65px;
border-radius: 8px;
overflow: hidden;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.card-content {
flex: 1;
min-width: 0;
&.has-preview {
.task-title {
font-size: 15px;
}
}
}
}
// 审核任务卡片特殊样式
.task-info-grid {
display: grid;