diff --git a/backend/src/main/java/com/designhub/config/RestTemplateConfig.java b/backend/src/main/java/com/designhub/config/RestTemplateConfig.java new file mode 100644 index 0000000..4305904 --- /dev/null +++ b/backend/src/main/java/com/designhub/config/RestTemplateConfig.java @@ -0,0 +1,22 @@ +package com.designhub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * RestTemplate配置类 + */ +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(10000); // 连接超时10秒 + factory.setReadTimeout(30000); // 读取超时30秒 + return new RestTemplate(factory); + } +} + diff --git a/backend/src/main/java/com/designhub/controller/ExternalAssetController.java b/backend/src/main/java/com/designhub/controller/ExternalAssetController.java new file mode 100644 index 0000000..4703360 --- /dev/null +++ b/backend/src/main/java/com/designhub/controller/ExternalAssetController.java @@ -0,0 +1,32 @@ +package com.designhub.controller; + +import com.designhub.common.Result; +import com.designhub.service.ExternalAssetService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * 第三方素材API控制器 + */ +@Tag(name = "第三方素材API", description = "第三方素材API相关接口") +@RestController +@RequestMapping("/api/external-asset") +@RequiredArgsConstructor +public class ExternalAssetController { + + private final ExternalAssetService externalAssetService; + + @Operation(summary = "测试API连接") + @PostMapping("/test") + public Result testConnection() { + boolean success = externalAssetService.testConnection(); + if (success) { + return Result.success(true); + } else { + return Result.error("连接测试失败,请检查API地址和密钥"); + } + } +} + diff --git a/backend/src/main/java/com/designhub/dto/ExternalAssetDTO.java b/backend/src/main/java/com/designhub/dto/ExternalAssetDTO.java new file mode 100644 index 0000000..2d39623 --- /dev/null +++ b/backend/src/main/java/com/designhub/dto/ExternalAssetDTO.java @@ -0,0 +1,55 @@ +package com.designhub.dto; + +import lombok.Data; +import java.util.List; +import java.util.Map; + +/** + * 第三方素材API响应DTO + */ +@Data +public class ExternalAssetDTO { + + private boolean success; + private AssetData data; + private ErrorInfo error; + + @Data + public static class AssetData { + private String taskNo; + private String productModel; + private String categoryName; + private String sceneName; + private String status; + private String createdAt; + private String updatedAt; + private Map> assets; + private DownloadUrls downloadUrls; + } + + @Data + public static class AssetItem { + private String id; + private String url; + private String downloadUrl; + private String fileName; + private Long fileSize; + private Integer sortOrder; + } + + @Data + public static class DownloadUrls { + private String all; + private String main; + private String sku; + private String detail; + private String psd; + } + + @Data + public static class ErrorInfo { + private String code; + private String message; + } +} + diff --git a/backend/src/main/java/com/designhub/entity/TaskItem.java b/backend/src/main/java/com/designhub/entity/TaskItem.java index bcd66a6..a77a2ea 100644 --- a/backend/src/main/java/com/designhub/entity/TaskItem.java +++ b/backend/src/main/java/com/designhub/entity/TaskItem.java @@ -96,6 +96,16 @@ public class TaskItem implements Serializable { */ private String deliveryPath; + /** + * 素材下载链接(从第三方API获取) + */ + private String downloadUrl; + + /** + * 前置子任务的素材下载链接(流水线任务中,前一环节完成后传递) + */ + private String prevDownloadUrl; + /** * 提交时间 */ diff --git a/backend/src/main/java/com/designhub/service/ExternalAssetService.java b/backend/src/main/java/com/designhub/service/ExternalAssetService.java new file mode 100644 index 0000000..7827c35 --- /dev/null +++ b/backend/src/main/java/com/designhub/service/ExternalAssetService.java @@ -0,0 +1,163 @@ +package com.designhub.service; + +import com.designhub.dto.ExternalAssetDTO; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * 第三方素材API服务 + * 用于查询外部素材系统的任务完成状态和下载链接 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ExternalAssetService { + + private final SystemConfigService systemConfigService; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + /** + * 查询任务素材 + * @param taskNo 任务编号 + * @return 素材信息,包含下载链接 + */ + public ExternalAssetDTO queryAssets(String taskNo) { + String apiUrl = systemConfigService.getConfigValue("external_asset_api_url"); + String apiKey = systemConfigService.getConfigValue("external_asset_api_key"); + + if (apiUrl == null || apiUrl.isEmpty()) { + log.warn("第三方素材API地址未配置"); + ExternalAssetDTO result = new ExternalAssetDTO(); + result.setSuccess(false); + ExternalAssetDTO.ErrorInfo error = new ExternalAssetDTO.ErrorInfo(); + error.setCode("CONFIG_ERROR"); + error.setMessage("第三方素材API未配置"); + result.setError(error); + return result; + } + + if (apiKey == null || apiKey.isEmpty()) { + log.warn("第三方素材API Key未配置"); + ExternalAssetDTO result = new ExternalAssetDTO(); + result.setSuccess(false); + ExternalAssetDTO.ErrorInfo error = new ExternalAssetDTO.ErrorInfo(); + error.setCode("CONFIG_ERROR"); + error.setMessage("第三方素材API Key未配置"); + result.setError(error); + return result; + } + + try { + // 构建请求URL + String url = apiUrl.endsWith("/") ? apiUrl : apiUrl + "/"; + url = url + "product-assets?taskNo=" + taskNo; + + log.info("查询第三方素材API: {}", url); + + // 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.set("X-API-Key", apiKey); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity entity = new HttpEntity<>(headers); + + // 发送请求 + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + String.class + ); + + log.info("第三方素材API响应状态: {}", response.getStatusCode()); + log.debug("第三方素材API响应内容: {}", response.getBody()); + + if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { + return objectMapper.readValue(response.getBody(), ExternalAssetDTO.class); + } else { + ExternalAssetDTO result = new ExternalAssetDTO(); + result.setSuccess(false); + ExternalAssetDTO.ErrorInfo error = new ExternalAssetDTO.ErrorInfo(); + error.setCode("REQUEST_FAILED"); + error.setMessage("请求失败: " + response.getStatusCode()); + result.setError(error); + return result; + } + + } catch (Exception e) { + log.error("调用第三方素材API失败", e); + ExternalAssetDTO result = new ExternalAssetDTO(); + result.setSuccess(false); + ExternalAssetDTO.ErrorInfo error = new ExternalAssetDTO.ErrorInfo(); + error.setCode("EXCEPTION"); + error.setMessage("调用API失败: " + e.getMessage()); + result.setError(error); + return result; + } + } + + /** + * 检查任务素材是否完成 + * @param taskNo 任务编号 + * @return 下载链接(如果完成),否则返回null + */ + public String checkTaskCompletion(String taskNo) { + ExternalAssetDTO result = queryAssets(taskNo); + + if (result.isSuccess() && result.getData() != null) { + ExternalAssetDTO.DownloadUrls urls = result.getData().getDownloadUrls(); + if (urls != null && urls.getAll() != null && !urls.getAll().isEmpty()) { + log.info("任务 {} 素材已完成,下载链接: {}", taskNo, urls.getAll()); + return urls.getAll(); + } + } + + log.info("任务 {} 素材未完成或查询失败", taskNo); + return null; + } + + /** + * 测试API连接 + * @return 测试结果 + */ + public boolean testConnection() { + String apiUrl = systemConfigService.getConfigValue("external_asset_api_url"); + String apiKey = systemConfigService.getConfigValue("external_asset_api_key"); + + if (apiUrl == null || apiUrl.isEmpty() || apiKey == null || apiKey.isEmpty()) { + return false; + } + + try { + // 测试连接(使用一个测试任务号) + String url = apiUrl.endsWith("/") ? apiUrl : apiUrl + "/"; + url = url + "product-assets?taskNo=TEST000000000000"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("X-API-Key", apiKey); + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + String.class + ); + + // 只要能收到响应(无论是404还是200),说明连接正常 + return response.getStatusCode().is2xxSuccessful() || + response.getStatusCode() == HttpStatus.NOT_FOUND; + + } catch (Exception e) { + log.error("测试第三方素材API连接失败", e); + return false; + } + } +} + diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index ac058f8..49189b1 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -35,6 +35,8 @@ public class TaskItemService { private final NotificationService notificationService; private final DingTalkService dingTalkService; private final ConfigService configService; + private final ExternalAssetService externalAssetService; + private final SystemConfigService systemConfigService; /** * 获取可接单的子任务列表 @@ -262,6 +264,9 @@ public class TaskItemService { /** * 完结子任务(关键方法:包含自动解锁逻辑) + * @param itemId 子任务ID + * @param userId 用户ID + * @param deliveryPath 交付路径(可选,如果启用第三方API则自动获取) */ @Transactional(rollbackFor = Exception.class) public Map completeItem(Long itemId, Long userId, String deliveryPath) { @@ -283,10 +288,40 @@ public class TaskItemService { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能完结进行中的子任务"); } - // 4. 更新子任务状态 + // 4. 检查是否启用第三方素材API验证 + String downloadUrl = null; + boolean externalApiEnabled = "true".equalsIgnoreCase( + systemConfigService.getConfigValue("external_asset_api_enabled", "false")); + + if (externalApiEnabled) { + // 调用第三方素材API验证任务完成状态 + String taskNo = item.getItemNo(); // 使用子任务编号作为任务号 + + try { + downloadUrl = externalAssetService.checkTaskCompletion(taskNo); + } catch (Exception e) { + log.warn("调用第三方素材API失败", e); + } + + if (downloadUrl == null) { + // 如果第三方API未返回下载链接,提示用户去上传素材 + throw new BusinessException(ResultCode.ERROR, + "素材未提交,请先前往 3D.YYDD.AI 提交任务素材后再完结任务"); + } + + log.info("第三方素材API返回下载链接: {}", downloadUrl); + } else { + // 未启用第三方API,使用传统方式(需要传入deliveryPath) + if (deliveryPath == null || deliveryPath.trim().isEmpty()) { + throw new BusinessException(ResultCode.ERROR, "交付路径不能为空"); + } + } + + // 5. 更新子任务状态 item.setStatus("completed"); item.setProgress(100); - item.setDeliveryPath(deliveryPath); + item.setDeliveryPath(downloadUrl != null ? downloadUrl : deliveryPath); + item.setDownloadUrl(downloadUrl); item.setCompleteTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); @@ -321,6 +356,26 @@ public class TaskItemService { if (allGroupCompleted) { log.info("✅ 第 {} 组所有子任务已完成,准备解锁下一组", currentGroupNo); + // 🔥 收集当前组所有子任务的下载链接,传递给下一组 + StringBuilder prevDownloadUrls = new StringBuilder(); + for (TaskItem groupItem : currentGroupItems) { + if (groupItem.getDownloadUrl() != null && !groupItem.getDownloadUrl().isEmpty()) { + if (prevDownloadUrls.length() > 0) { + prevDownloadUrls.append(","); + } + prevDownloadUrls.append(groupItem.getDownloadUrl()); + } + // 同时继承前面环节的下载链接 + if (groupItem.getPrevDownloadUrl() != null && !groupItem.getPrevDownloadUrl().isEmpty()) { + if (prevDownloadUrls.length() > 0) { + prevDownloadUrls.append(","); + } + prevDownloadUrls.append(groupItem.getPrevDownloadUrl()); + } + } + String prevDownloadUrlStr = prevDownloadUrls.toString(); + log.info("前置环节素材下载链接: {}", prevDownloadUrlStr); + // 解锁下一组的所有子任务 Integer nextGroupNo = currentGroupNo + 1; List nextGroupItems = taskItemMapper.selectList( @@ -331,14 +386,15 @@ public class TaskItemService { ); if (!nextGroupItems.isEmpty()) { - // 批量解锁下一组的所有子任务,状态设为待审核 + // 批量解锁下一组的所有子任务,状态设为待审核,并传递前置环节的下载链接 for (TaskItem nextItem : nextGroupItems) { nextItem.setStatus("pending_approval"); // 待接单部门负责人审核 nextItem.setIsLocked(0); + nextItem.setPrevDownloadUrl(prevDownloadUrlStr); // 🔥 传递前置环节的下载链接 nextItem.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(nextItem); - log.info("✅ 解锁子任务: {} (组号: {}, ID: {}),待接单部门负责人审核", + log.info("✅ 解锁子任务: {} (组号: {}, ID: {}),待接单部门负责人审核,已传递前置素材链接", nextItem.getItemNo(), nextGroupNo, nextItem.getId()); } @@ -380,10 +436,40 @@ public class TaskItemService { int totalCount = taskItemMapper.countTotalItems(item.getTaskId()); if (completedCount == totalCount) { + // 🔥 获取最后一组子任务的下载链接,保存到主任务 + Integer maxGroupNo = taskItemMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(TaskItem::getTaskId, item.getTaskId()) + .orderByDesc(TaskItem::getGroupNo) + .last("LIMIT 1") + ).stream().findFirst().map(TaskItem::getGroupNo).orElse(1); + + List lastGroupItems = taskItemMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(TaskItem::getTaskId, item.getTaskId()) + .eq(TaskItem::getGroupNo, maxGroupNo) + ); + + // 收集最后一组的下载链接 + StringBuilder finalDownloadUrl = new StringBuilder(); + for (TaskItem lastItem : lastGroupItems) { + if (lastItem.getDownloadUrl() != null && !lastItem.getDownloadUrl().isEmpty()) { + if (finalDownloadUrl.length() > 0) { + finalDownloadUrl.append(","); + } + finalDownloadUrl.append(lastItem.getDownloadUrl()); + } + } + // 所有子任务完成,更新主任务状态 mainTask.setStatus("completed"); mainTask.setCompleteTime(LocalDateTime.now()); mainTask.setUpdateTime(LocalDateTime.now()); + // 🔥 保存最后一组的下载链接到主任务 + if (finalDownloadUrl.length() > 0) { + mainTask.setDeliveryPath(finalDownloadUrl.toString()); + log.info("主任务最终素材下载链接: {}", finalDownloadUrl); + } taskMapper.updateById(mainTask); log.info("🎉 主任务 {} 所有子任务已完成,主任务标记为完成", mainTask.getId()); diff --git a/backend/src/main/resources/db/migration/V9__add_external_asset_api.sql b/backend/src/main/resources/db/migration/V9__add_external_asset_api.sql new file mode 100644 index 0000000..79038fb --- /dev/null +++ b/backend/src/main/resources/db/migration/V9__add_external_asset_api.sql @@ -0,0 +1,15 @@ +-- 添加第三方素材API相关配置 +-- V9: 第三方素材API配置 + +-- 在task_item表添加下载链接字段 +ALTER TABLE task_item ADD COLUMN IF NOT EXISTS download_url VARCHAR(1000) COMMENT '素材下载链接(从第三方API获取)'; +ALTER TABLE task_item ADD COLUMN IF NOT EXISTS prev_download_url VARCHAR(2000) COMMENT '前置子任务的素材下载链接(流水线任务中传递)'; + +-- 添加系统配置:第三方素材API +INSERT INTO sys_config (config_key, config_value, config_group, config_name, description, value_type, built_in, sort_order, create_time, update_time, deleted) +VALUES +('external_asset_api_url', '', 'external_api', '第三方素材API地址', '第三方素材系统的API地址,例如:https://your-domain.com/api/external/', 'string', 1, 1, NOW(), NOW(), 0), +('external_asset_api_key', '', 'external_api', '第三方素材API密钥', '用于调用第三方素材API的密钥', 'string', 1, 2, NOW(), NOW(), 0), +('external_asset_api_enabled', 'false', 'external_api', '启用第三方素材API', '是否启用第三方素材API验证任务完结', 'boolean', 1, 3, NOW(), NOW(), 0) +ON DUPLICATE KEY UPDATE update_time = NOW(); + diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index d04d53f..666f250 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -70,6 +70,8 @@ export interface Task { allDeliveryPaths?: string[] // 预览图 previewImage?: string + // 素材下载链接 + downloadUrl?: string } // 任务子项(流水线任务) @@ -107,6 +109,9 @@ export interface TaskItem { spuInfo?: string taskSubType?: string attachmentUrl?: string + previewImage?: string + downloadUrl?: string + prevDownloadUrl?: string // 前置子任务的素材下载链接 } // 创建流水线任务请求 diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index e913af1..150a584 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -1188,6 +1188,73 @@ + + +
+
+

第三方素材API配置

+
+ + + + + + +
+ 启用后,设计师完结任务时将验证素材是否已在第三方平台提交 +
+
+ + + +
+ 第三方素材系统的API地址,需以/结尾 +
+
+ + + +
+ 用于调用第三方素材API的密钥(X-API-Key) +
+
+ + + + + + 保存配置 + + + + 测试连接 + + + +
+
+
+
@@ -3506,6 +3573,98 @@ const delayPenaltyConfig = ref({ const savingDelayPenaltyConfig = ref(false) +// ==================== 第三方素材API配置 ==================== +const externalAssetApiConfig = reactive({ + enabled: false, + apiUrl: '', + apiKey: '' +}) +const savingExternalAssetApi = ref(false) +const testingExternalAssetApi = ref(false) + +// 加载第三方素材API配置 +const loadExternalAssetApiConfig = async () => { + try { + const response = await getSystemConfigsByGroup('external_api') + const configs = response.data || [] + + const urlConfig = configs.find((c: any) => c.configKey === 'external_asset_api_url') + const keyConfig = configs.find((c: any) => c.configKey === 'external_asset_api_key') + const enabledConfig = configs.find((c: any) => c.configKey === 'external_asset_api_enabled') + + externalAssetApiConfig.apiUrl = urlConfig?.configValue || '' + externalAssetApiConfig.apiKey = keyConfig?.configValue || '' + externalAssetApiConfig.enabled = enabledConfig?.configValue === 'true' + } catch (error) { + console.error('加载第三方素材API配置失败:', error) + } +} + +// 保存第三方素材API配置 +const handleSaveExternalAssetApiConfig = async () => { + if (externalAssetApiConfig.enabled) { + if (!externalAssetApiConfig.apiUrl) { + message.error('请输入API地址') + return + } + if (!externalAssetApiConfig.apiKey) { + message.error('请输入API密钥') + return + } + } + + savingExternalAssetApi.value = true + try { + await batchUpdateSystemConfigs({ + 'external_asset_api_url': externalAssetApiConfig.apiUrl, + 'external_asset_api_key': externalAssetApiConfig.apiKey, + 'external_asset_api_enabled': externalAssetApiConfig.enabled.toString() + }) + message.success('第三方素材API配置保存成功') + } catch (error: any) { + message.error(error.message || '保存失败') + } finally { + savingExternalAssetApi.value = false + } +} + +// 测试第三方素材API连接 +const handleTestExternalAssetApi = async () => { + if (!externalAssetApiConfig.apiUrl || !externalAssetApiConfig.apiKey) { + message.error('请先填写API地址和密钥') + return + } + + testingExternalAssetApi.value = true + try { + // 先保存配置 + await batchUpdateSystemConfigs({ + 'external_asset_api_url': externalAssetApiConfig.apiUrl, + 'external_asset_api_key': externalAssetApiConfig.apiKey + }) + + // 调用测试接口 + const response = await fetch('/api/external-asset/test', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }) + const result = await response.json() + + if (result.code === 200 && result.data === true) { + message.success('连接测试成功') + } else { + message.error(result.message || '连接测试失败') + } + } catch (error: any) { + message.error(error.message || '连接测试失败') + } finally { + testingExternalAssetApi.value = false + } +} + // 加载延误扣分配置 const loadDelayPenaltyConfig = async () => { try { @@ -3771,6 +3930,8 @@ onMounted(() => { loadTaskReleaseConfig() // 加载延误扣分配置 loadDelayPenaltyConfig() + // 加载第三方素材API配置 + loadExternalAssetApiConfig() }) // S3配置相关方法 diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index 3c95f7e..e25258d 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -945,6 +945,17 @@
查看详情 + + + + 上一环节素材 + 更新进度 @@ -1636,6 +1647,28 @@
+ +
+ + + 下载素材 + + + + 暂无素材 + +
+
{ // 完结任务(支持普通任务和子任务) // 编辑被驳回的任务 +// 下载素材 +const handleDownloadAssets = (task: Task) => { + const downloadUrl = task.downloadUrl || task.deliveryPath + if (downloadUrl) { + // 如果是http链接,直接打开 + if (downloadUrl.startsWith('http')) { + window.open(downloadUrl, '_blank') + } else { + // 复制到剪贴板 + navigator.clipboard.writeText(downloadUrl).then(() => { + message.success('素材路径已复制到剪贴板') + }).catch(() => { + message.info(`素材路径: ${downloadUrl}`) + }) + } + } else { + message.warning('暂无可下载的素材') + } +} + +// 下载上一环节素材(流水线任务) +const handleDownloadPrevAssets = (task: TaskOrSubTask) => { + const prevDownloadUrl = task.prevDownloadUrl + if (prevDownloadUrl) { + // 如果包含多个链接(逗号分隔),显示弹窗让用户选择 + const urls = prevDownloadUrl.split(',').filter(url => url.trim()) + if (urls.length === 1) { + // 只有一个链接,直接打开 + if (urls[0].startsWith('http')) { + window.open(urls[0], '_blank') + } else { + navigator.clipboard.writeText(urls[0]).then(() => { + message.success('素材路径已复制到剪贴板') + }).catch(() => { + message.info(`素材路径: ${urls[0]}`) + }) + } + } else { + // 多个链接,显示列表让用户选择 + Modal.info({ + title: '上一环节素材下载', + content: h('div', [ + h('p', { style: 'margin-bottom: 12px;' }, `共 ${urls.length} 个素材文件:`), + ...urls.map((url, index) => + h('div', { style: 'margin-bottom: 8px;' }, [ + h('a', { + href: url, + target: '_blank', + style: 'color: #1890ff; word-break: break-all;' + }, `素材 ${index + 1}: ${url.length > 50 ? url.substring(0, 50) + '...' : url}`) + ]) + ) + ]), + width: 600 + }) + } + } else { + message.warning('暂无上一环节的素材') + } +} + const handleEditRejectedTask = (task: Task) => { // 跳转到创建任务页面,并传入任务ID作为编辑标识 router.push({ @@ -3698,43 +3794,57 @@ const handleEditRejectedTask = (task: Task) => { } const handleCompleteTask = (task: TaskOrSubTask) => { - let deliveryPath = '' const isSubTask = task.isSubTask const taskType = isSubTask ? '子任务' : '任务' + const taskNo = isSubTask ? (task.itemNo || task.taskNo) : task.taskNo Modal.confirm({ title: `完结${taskType}`, content: h('div', [ h('div', { style: 'margin-bottom: 12px;' }, `确认完结${taskType}:${task.title}`), - h('div', { style: 'margin-bottom: 12px;' }, '交付路径:'), - h(Textarea, { - rows: 4, - placeholder: '请输入交付路径或说明', - style: 'width: 100%', - onChange: (e: any) => { deliveryPath = e.target.value } - }) + h('div', { style: 'color: #666; font-size: 13px;' }, [ + h('p', { style: 'margin-bottom: 8px;' }, '系统将自动从素材平台获取您提交的素材。'), + h('p', { style: 'margin: 0;' }, `任务编号:${taskNo || '--'}`) + ]) ]), + okText: '确认完结', + cancelText: '取消', onOk: async () => { - if (!deliveryPath.trim()) { - message.error('请输入交付路径') - return Promise.reject() - } - try { if (isSubTask) { - // 完结子任务 - await taskApi.completeTaskItem(task.itemId || task.id!, { deliveryPath }) - message.success('子任务已完成,等待下一个子任务解锁') + // 完结子任务(后端会自动调用第三方API验证) + await taskApi.completeTaskItem(task.itemId || task.id!, { deliveryPath: '' }) + message.success('子任务已完成') } else { // 完结普通任务 - await taskApi.completeTask(task.id!, { deliveryPath }) + await taskApi.completeTask(task.id!, { deliveryPath: '' }) message.success('任务已提交审核') } loadInProgressTasks() loadAllTabCounts() loadDashboardStats() } catch (error: any) { - message.error(error.message || '任务完结失败') + // 如果是素材未提交的错误,显示特定提示 + const errorMsg = error.message || error.msg || '任务完结失败' + if (errorMsg.includes('素材未提交') || errorMsg.includes('3D.YYDD.AI')) { + Modal.warning({ + title: '素材未提交', + content: h('div', [ + h('p', '请先前往素材平台提交任务素材后再完结任务。'), + h('p', { style: 'margin-top: 8px;' }, [ + '任务编号:', + h('strong', taskNo || '--') + ]), + h('a', { + href: 'https://3d.yydd.ai', + target: '_blank', + style: 'color: #1890ff; margin-top: 12px; display: inline-block;' + }, '前往素材平台 →') + ]) + }) + } else { + message.error(errorMsg) + } } } }) @@ -5124,6 +5234,17 @@ onUnmounted(() => { gap: $space-sm; padding-top: $space-md; border-top: 1px solid $border-primary; + flex-wrap: wrap; + + .prev-assets-btn { + background: rgba(24, 144, 255, 0.1); + border-color: #1890ff; + color: #1890ff; + + &:hover { + background: rgba(24, 144, 255, 0.2); + } + } margin-top: $space-md; button { diff --git a/产品全案外部接口文档.md b/第三方系统完结任务数据接口文档.md similarity index 100% rename from 产品全案外部接口文档.md rename to 第三方系统完结任务数据接口文档.md