diff --git a/src/api/image-management.js b/src/api/image-management.js index dce24a6..ec7de3a 100644 --- a/src/api/image-management.js +++ b/src/api/image-management.js @@ -5,7 +5,7 @@ export function getImageList(params) { return request({ url: '/image-model/page', method: 'post', - params: params // 使用params而不是data,这样参数会作为URL查询参数传递 + params: params // 使用params传递URL查询参数,因为后端使用@RequestParam }) } @@ -79,6 +79,16 @@ export function imageProcessing(data) { return request({ url: '/image-model/image-background', method: 'post', - data + data, + timeout: 60000 // 设置60秒超时,图像处理可能需要较长时间 + }) +} + +// 检查任务状态 +export function checkImageModelStatus() { + return request({ + url: '/image-model/check-status', + method: 'post', + timeout: 30000 // 设置30秒超时 }) } diff --git a/src/image-management.js b/src/image-management.js new file mode 100644 index 0000000..2fe46fe --- /dev/null +++ b/src/image-management.js @@ -0,0 +1,75 @@ +import request from '@/utils/request' + +// 获取图像管理列表 +export function getImageList(params) { + return request({ + url: '/image-model/page', + method: 'post', + params: params // 使用params而不是data,这样参数会作为URL查询参数传递 + }) +} + +// 获取图像详情 +export function getImageDetail(id) { + return request({ + url: `/api/image-management/${id}`, + method: 'get' + }) +} + +// 创建图像 +export function createImage(data) { + return request({ + url: '/api/image-management', + method: 'post', + data + }) +} + +// 更新图像 +export function updateImage(id, data) { + return request({ + url: `/api/image-management/${id}`, + method: 'put', + data + }) +} + +// 删除图像 +export function deleteImage(uuid) { + return request({ + url: `/image-model/delete/${uuid}`, + method: 'delete' + }) +} + +// 批量删除图像 +export function batchDeleteImages(ids) { + return request({ + url: '/api/image-management/batch-delete', + method: 'post', + data: { ids } + }) +} + +// 上传素材 +export function uploadMaterial(formData, params = {}) { + return request({ + url: '/image-model/upload', + method: 'post', + data: formData, + params: params, // 添加查询参数 + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +// 去除水印 +export function removeWatermark(data) { + return request({ + url: '/image-model/removeWatermar', + method: 'post', + data + }) +} diff --git a/src/utils/request.js b/src/utils/request.js index f894307..ca24843 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -14,6 +14,12 @@ const service = axios.create({ service.interceptors.request.use( config => { // do something before request is sent + console.log('请求配置:', { + url: config.url, + method: config.method, + data: config.data, + params: config.params + }) if (store.getters.token) { // 使用Bearer token格式 diff --git a/src/views/image-model/background-model.vue b/src/views/image-model/background-model.vue index 3b3262e..7739eb3 100644 --- a/src/views/image-model/background-model.vue +++ b/src/views/image-model/background-model.vue @@ -6,7 +6,7 @@
-

主图选择

+

主图选择 (必选)

@@ -18,6 +18,9 @@ placeholder="请选择主图" class="image-select" :loading="imageListLoading" + filterable + remote + :remote-method="handleBaseImageSearch" @change="handleBaseImageChange" >
-
+
-

{{ selectedImage.imageName }}

-

{{ selectedImage.ownerName }}

-

{{ getImageTypeText(selectedImage.imageType) }}

+

{{ selectedBaseImage.imageName }}

+

{{ selectedBaseImage.ownerName }}

+

{{ getImageTypeText(selectedBaseImage.imageType) }}

@@ -53,7 +56,7 @@
-

参考图选择

+

参考图选择 (必选)

@@ -65,6 +68,9 @@ placeholder="请选择参考图" class="image-select" :loading="imageListLoading" + filterable + remote + :remote-method="handleRefImageSearch" @change="handleRefImageChange" >
-

前景边缘图1选择

+

前景边缘图1选择 (可选)

+
+ + 请选择支持透明通道的图片(PNG、GIF、WebP格式),此字段为可选项 +
@@ -121,6 +131,9 @@ placeholder="请选择前景边缘图1" class="image-select" :loading="imageListLoading" + filterable + remote + :remote-method="handleForegroundEdge1Search" @change="handleForegroundEdge1Change" >
-

前景边缘图2选择

+

前景边缘图2选择 (可选)

+
+ + 请选择支持透明通道的图片(PNG、GIF、WebP格式),此字段为可选项 +
@@ -177,6 +194,9 @@ placeholder="请选择前景边缘图2" class="image-select" :loading="imageListLoading" + filterable + remote + :remote-method="handleForegroundEdge2Search" @change="handleForegroundEdge2Change" >
-

背景边缘图选择

+

背景边缘图选择 (可选)

+
+ + 此字段为可选项,可以选择也可以不选择 +
@@ -233,6 +257,9 @@ placeholder="请选择背景边缘图" class="image-select" :loading="imageListLoading" + filterable + remote + :remote-method="handleBackgroundEdgeSearch" @change="handleBackgroundEdgeChange" >
+
+ + +
{ console.log('图像列表响应:', response) console.log('响应数据结构:', { @@ -454,6 +520,22 @@ export default { }) }, + // 主图搜索方法 + handleBaseImageSearch(query) { + console.log('主图搜索关键词:', query) + this.searchKeywords.baseImage = query + + // 清除之前的定时器 + if (this.searchTimeouts.baseImage) { + clearTimeout(this.searchTimeouts.baseImage) + } + + // 设置防抖,500ms后执行搜索 + this.searchTimeouts.baseImage = setTimeout(() => { + this.loadImageList(query) + }, 500) + }, + // 处理主图选择变化 handleBaseImageChange(imageId) { console.log('选择的主图ID:', imageId) @@ -465,6 +547,22 @@ export default { } }, + // 参考图搜索方法 + handleRefImageSearch(query) { + console.log('参考图搜索关键词:', query) + this.searchKeywords.refImage = query + + // 清除之前的定时器 + if (this.searchTimeouts.refImage) { + clearTimeout(this.searchTimeouts.refImage) + } + + // 设置防抖,500ms后执行搜索 + this.searchTimeouts.refImage = setTimeout(() => { + this.loadImageList(query) + }, 500) + }, + // 处理参考图选择变化 handleRefImageChange(refImageId) { console.log('选择的参考图ID:', refImageId) @@ -476,6 +574,22 @@ export default { } }, + // 前景边缘图1搜索方法 + handleForegroundEdge1Search(query) { + console.log('前景边缘图1搜索关键词:', query) + this.searchKeywords.foregroundEdge1 = query + + // 清除之前的定时器 + if (this.searchTimeouts.foregroundEdge1) { + clearTimeout(this.searchTimeouts.foregroundEdge1) + } + + // 设置防抖,500ms后执行搜索 + this.searchTimeouts.foregroundEdge1 = setTimeout(() => { + this.loadImageList(query) + }, 500) + }, + // 处理前景边缘图1选择变化 handleForegroundEdge1Change(edge1Id) { console.log('选择的前景边缘图1 ID:', edge1Id) @@ -487,6 +601,22 @@ export default { } }, + // 前景边缘图2搜索方法 + handleForegroundEdge2Search(query) { + console.log('前景边缘图2搜索关键词:', query) + this.searchKeywords.foregroundEdge2 = query + + // 清除之前的定时器 + if (this.searchTimeouts.foregroundEdge2) { + clearTimeout(this.searchTimeouts.foregroundEdge2) + } + + // 设置防抖,500ms后执行搜索 + this.searchTimeouts.foregroundEdge2 = setTimeout(() => { + this.loadImageList(query) + }, 500) + }, + // 处理前景边缘图2选择变化 handleForegroundEdge2Change(edge2Id) { console.log('选择的前景边缘图2 ID:', edge2Id) @@ -498,6 +628,22 @@ export default { } }, + // 背景边缘图搜索方法 + handleBackgroundEdgeSearch(query) { + console.log('背景边缘图搜索关键词:', query) + this.searchKeywords.backgroundEdge = query + + // 清除之前的定时器 + if (this.searchTimeouts.backgroundEdge) { + clearTimeout(this.searchTimeouts.backgroundEdge) + } + + // 设置防抖,500ms后执行搜索 + this.searchTimeouts.backgroundEdge = setTimeout(() => { + this.loadImageList(query) + }, 500) + }, + // 处理背景边缘图选择变化 handleBackgroundEdgeChange(backgroundEdgeId) { console.log('选择的背景边缘图ID:', backgroundEdgeId) @@ -522,14 +668,66 @@ export default { person: '人物图', product: '产品图', material: '素材图', + result: '成品', other: '其他' } return typeMap[type] || '未知' }, + // 验证图片格式 + validateImageFormat(image, imageName) { + // 检查图片URL是否包含透明通道信息 + const url = image.materialTempUrl + if (!url) { + return { valid: false, message: `${imageName}图片URL无效` } + } + + console.log(`验证${imageName}格式:`, { + url: url, + imageName: image.imageName, + imageType: image.imageType + }) + + // 检查文件扩展名 - 需要先去除URL参数 + const urlWithoutParams = url.split('?')[0] // 去除问号后的签名信息 + const extension = urlWithoutParams.split('.').pop().toLowerCase() + const supportedFormats = ['png', 'gif', 'webp', 'jpg', 'jpeg'] + + console.log(`原始URL:${url}`) + console.log(`去除参数后的URL:${urlWithoutParams}`) + console.log(`文件扩展名:${extension}`) + + // 对于前景边缘图,我们放宽格式要求,因为后端会处理格式转换 + // 主要检查是否是有效的图片格式 + if (!supportedFormats.includes(extension)) { + return { + valid: false, + message: `${imageName}图片格式不支持,请使用常见的图片格式(PNG、JPG、GIF、WebP等)` + } + } + + // 如果是前景边缘图,给出建议但不强制要求特定格式 + if (imageName.includes('前景边缘图')) { + console.log(`前景边缘图格式检查通过:${extension}`) + + // 如果已经是PNG格式,不需要显示建议 + if (extension === 'png') { + return { valid: true } + } + + // 对于其他格式,给出建议但不阻止使用 + return { + valid: true, + message: `建议使用PNG格式以获得更好的透明通道支持,当前格式:${extension.toUpperCase()}` + } + } + + return { valid: true } + }, + // 处理生成按钮点击 handleGenerate() { - // 验证必填项 + // 验证必填项 - 只有主图和参考图是必选的 if (!this.form.baseImageId) { this.$message.warning('请选择主图') return @@ -538,17 +736,44 @@ export default { this.$message.warning('请选择参考图') return } - if (!this.form.foregroundEdge1Id) { - this.$message.warning('请选择前景边缘图1') + if (!this.form.imageName) { + this.$message.warning('请输入图片名称') return } - if (!this.form.foregroundEdge2Id) { - this.$message.warning('请选择前景边缘图2') - return + // 其他图片都是可选的,不进行必填验证 + + // 验证已选择的图片格式(仅检查基本格式,不强制要求特定格式) + // 前景边缘图1 - 可选 + if (this.form.foregroundEdge1Id && this.selectedForegroundEdge1) { + const foregroundEdge1Validation = this.validateImageFormat(this.selectedForegroundEdge1, '前景边缘图1') + if (!foregroundEdge1Validation.valid) { + this.$message.error(foregroundEdge1Validation.message) + return + } else if (foregroundEdge1Validation.message) { + console.log('前景边缘图1格式建议:', foregroundEdge1Validation.message) + } } - if (!this.form.backgroundEdgeId) { - this.$message.warning('请选择背景边缘图') - return + + // 前景边缘图2 - 可选 + if (this.form.foregroundEdge2Id && this.selectedForegroundEdge2) { + const foregroundEdge2Validation = this.validateImageFormat(this.selectedForegroundEdge2, '前景边缘图2') + if (!foregroundEdge2Validation.valid) { + this.$message.error(foregroundEdge2Validation.message) + return + } else if (foregroundEdge2Validation.message) { + console.log('前景边缘图2格式建议:', foregroundEdge2Validation.message) + } + } + + // 背景边缘图 - 可选 + if (this.form.backgroundEdgeId && this.selectedBackgroundEdge) { + const backgroundEdgeValidation = this.validateImageFormat(this.selectedBackgroundEdge, '背景边缘图') + if (!backgroundEdgeValidation.valid) { + this.$message.error(backgroundEdgeValidation.message) + return + } else if (backgroundEdgeValidation.message) { + console.log('背景边缘图格式建议:', backgroundEdgeValidation.message) + } } // 开始生成 @@ -556,36 +781,61 @@ export default { // 构建后端参数结构 const requestData = { - base_image_url: this.selectedBaseImage.materialTempUrl, - ref_image_url: this.selectedRefImage.materialTempUrl, - ref_prompt: this.form.refPrompt, - reference_edge: { - foreground_edge: [ - this.selectedForegroundEdge1.materialTempUrl, - this.selectedForegroundEdge2.materialTempUrl + baseImageUrl: this.selectedBaseImage.materialTempUrl, + refImageUrl: this.selectedRefImage.materialTempUrl, + refPrompt: this.form.refPrompt, + imageName: this.form.imageName, + ownerName: this.form.ownerName, + ownerPhone: this.form.ownerPhone, + referenceEdge: { + foregroundEdge: [ + // 前景边缘图1 - 可选 + ...(this.selectedForegroundEdge1 ? [this.selectedForegroundEdge1.materialTempUrl] : []), + // 前景边缘图2 - 可选 + ...(this.selectedForegroundEdge2 ? [this.selectedForegroundEdge2.materialTempUrl] : []) ], - background_edge: [ - this.selectedBackgroundEdge.materialTempUrl + backgroundEdge: [ + // 背景边缘图 - 可选 + ...(this.selectedBackgroundEdge ? [this.selectedBackgroundEdge.materialTempUrl] : []) ], - foreground_edge_prompt: [ - this.form.foregroundEdge1Prompt, - this.form.foregroundEdge2Prompt + foregroundEdgePrompt: [ + // 前景边缘图1提示词 - 可选,确保与foregroundEdge数组长度匹配 + ...(this.selectedForegroundEdge1 ? [this.form.foregroundEdge1Prompt || ''] : []), + // 前景边缘图2提示词 - 可选,确保与foregroundEdge数组长度匹配 + ...(this.selectedForegroundEdge2 ? [this.form.foregroundEdge2Prompt || ''] : []) ], - background_edge_prompt: [ - this.form.backgroundEdgePrompt + backgroundEdgePrompt: [ + // 背景边缘图提示词 - 可选 + ...(this.selectedBackgroundEdge ? [this.form.backgroundEdgePrompt || ''] : []) ] }, parameters: { n: this.form.n, - ref_prompt_weight: this.form.refPromptWeight, - model_version: this.form.modelVersion - }, - owner_info: { - owner_name: this.form.ownerName, - owner_phone: this.form.ownerPhone + refPromptWeight: this.form.refPromptWeight, + modelVersion: this.form.modelVersion } } + // 验证数组长度匹配 + const foregroundEdgeLength = requestData.referenceEdge.foregroundEdge.length + const foregroundEdgePromptLength = requestData.referenceEdge.foregroundEdgePrompt.length + + console.log('数组长度验证:', { + foregroundEdge: foregroundEdgeLength, + foregroundEdgePrompt: foregroundEdgePromptLength, + isMatched: foregroundEdgeLength === foregroundEdgePromptLength + }) + + if (foregroundEdgeLength !== foregroundEdgePromptLength) { + console.error('数组长度不匹配!', { + foregroundEdge: requestData.referenceEdge.foregroundEdge, + foregroundEdgePrompt: requestData.referenceEdge.foregroundEdgePrompt + }) + this.$message.error('前景边缘图和提示词数量不匹配,请检查输入') + this.generateLoading = false + return + } + console.log('开始生成图像,参数:', requestData) // 调用后端接口 @@ -610,13 +860,38 @@ export default { console.log('请求ID:', response.requestId) } } else { - this.$message.error(response.message || '图像处理失败') + // 处理特定的错误代码 + let errorMessage = response.message || '图像处理失败' + + if (response.code === 'BadRequest.UnsupportedFileFormat') { + errorMessage = '图片格式不支持:前景边缘图需要RGBA或LA模式的图片(支持透明通道的PNG、GIF或WebP格式)' + } else if (response.code === 'BadRequest.InvalidImageFormat') { + errorMessage = '图片格式无效,请检查图片文件是否损坏' + } else if (response.code === 'BadRequest.ImageSizeExceeded') { + errorMessage = '图片尺寸过大,请使用较小的图片' + } + + this.$message.error(errorMessage) console.error('图像处理失败:', response) } }).catch(error => { this.generateLoading = false console.error('调用图像处理接口失败:', error) - this.$message.error('调用图像处理接口失败:' + (error.message || '未知错误')) + + // 处理网络错误或服务器错误 + let errorMessage = '调用图像处理接口失败' + if (error.response && error.response.data) { + const errorData = error.response.data + if (errorData.code === 'BadRequest.UnsupportedFileFormat') { + errorMessage = '图片格式不支持:前景边缘图需要RGBA或LA模式的图片(支持透明通道的PNG、GIF或WebP格式)' + } else if (errorData.message) { + errorMessage = errorData.message + } + } else if (error.message) { + errorMessage = error.message + } + + this.$message.error(errorMessage) }) } } @@ -627,7 +902,7 @@ export default { .app-container { max-width: 1000px; margin: 0 auto; - padding: 20px; + padding: 10px; } .content { @@ -639,33 +914,67 @@ export default { } .form-section { - margin-bottom: 30px; + margin-bottom: 20px; border: 1px solid #e4e7ed; - border-radius: 12px; + border-radius: 8px; overflow: hidden; background: #fff; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); } .section-header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 15px 20px; - color: white; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + padding: 12px 16px; + color: #495057; + border-bottom: 1px solid #e4e7ed; } .section-header h3 { margin: 0; - font-size: 16px; - font-weight: 600; + font-size: 14px; + font-weight: 500; +} + +.format-tip { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; + font-size: 12px; + color: #909399; +} + +.format-tip i { + color: #409eff; +} + +.optional-tag { + font-size: 12px; + color: #909399; + font-weight: normal; + background: #f0f2f5; + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; +} + +.required-tag { + font-size: 12px; + color: #f56c6c; + font-weight: normal; + background: #fef0f0; + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; } .section-content { - padding: 20px; + padding: 16px; } .two-column-layout { display: flex; - gap: 30px; + gap: 20px; align-items: flex-start; } @@ -680,7 +989,7 @@ export default { } .selector-wrapper { - margin-bottom: 15px; + margin-bottom: 12px; } .image-select { @@ -690,23 +999,23 @@ export default { .image-preview { display: flex; align-items: center; - gap: 12px; - padding: 15px; + gap: 10px; + padding: 12px; border: 1px solid #e4e7ed; - border-radius: 8px; + border-radius: 6px; background: #f8f9fa; width: 100%; - height: 120px; + height: 100px; box-sizing: border-box; } .preview-image { - width: 60px; - height: 60px; + width: 50px; + height: 50px; object-fit: cover; - border-radius: 8px; - border: 2px solid #dcdfe6; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 6px; + border: 1px solid #dcdfe6; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); flex-shrink: 0; } @@ -735,7 +1044,7 @@ export default { } .prompt-container { - margin-top: 15px; + margin-top: 12px; } .prompt-input { @@ -757,12 +1066,12 @@ export default { .parameter-container { display: flex; flex-direction: column; - gap: 20px; + gap: 16px; } .parameter-row { display: flex; - gap: 30px; + gap: 20px; align-items: center; } @@ -787,23 +1096,23 @@ export default { .button-container { display: flex; justify-content: center; - padding: 20px 0; + padding: 16px 0; } .generate-button { - padding: 12px 40px; - font-size: 16px; - font-weight: 600; - border-radius: 8px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 10px 32px; + font-size: 14px; + font-weight: 500; + border-radius: 6px; + background: linear-gradient(135deg, #409eff 0%, #36a3f7 100%); border: none; - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2); transition: all 0.3s ease; } .generate-button:hover { - transform: translateY(-2px); - box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); } .generate-button:active { diff --git a/src/views/image-model/image-list.vue b/src/views/image-model/image-list.vue index f7196d1..2337a57 100644 --- a/src/views/image-model/image-list.vue +++ b/src/views/image-model/image-list.vue @@ -42,6 +42,7 @@ + 上传素材 + + 状态检查 + - + @@ -159,11 +188,6 @@ {{ scope.row.prompt || '-' }} - - - + + +