图生图

This commit is contained in:
ZLI263
2025-10-19 13:44:32 +08:00
parent d9d798ac72
commit 0d21742e12
11 changed files with 902 additions and 13 deletions

View File

@@ -87,13 +87,26 @@ export function imageProcessing(data) {
// 文本生图(基于参考图像和提示词)
export function imageGenByText(data) {
return request({
url: '/image-model/image-gen-byText',
url: '/image-model/text-to-image',
method: 'post',
data,
timeout: 60000
})
}
// 图像生图(基于源图像生成新图像)
export function imageGenByImage(formData) {
return request({
url: '/image-model/image-to-image',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 60000
})
}
// 检查任务状态
export function checkImageModelStatus() {
return request({

View File

@@ -311,18 +311,18 @@ export const constantRoutes = [
component: () => import('@/views/image-model/image-list'),
meta: { title: '图像管理列表', icon: 'el-icon-picture' }
},
{
path: 'poster-generation',
name: 'PosterGeneration',
component: () => import('@/views/image-model/poster-generation'),
meta: { title: '海报生成', icon: 'el-icon-picture-outline-round' }
},
{
path: 'image-generation',
name: 'ImageGeneration',
component: () => import('@/views/image-model/image-generation'),
meta: { title: '文本生图', icon: 'el-icon-picture-outline' }
},
{
path: 'image-to-image',
name: 'ImageToImage',
component: () => import('@/views/image-model/image-to-image'),
meta: { title: '图像生图', icon: 'el-icon-picture-outline' }
},
{
path: 'background-model',
name: 'BackgroundModel',
@@ -330,10 +330,10 @@ export const constantRoutes = [
meta: { title: '图像智聚', icon: 'el-icon-picture-outline' }
},
{
path: 'virtual-model',
name: 'VirtualModel',
component: () => import('@/views/image-model/virtual-model'),
meta: { title: '虚拟模特儿模型', icon: 'el-icon-user' }
path: 'smart-image-analysis',
name: 'SmartImageAnalysis',
component: () => import('@/views/video/image-video-analysis'),
meta: { title: '智解图片', icon: 'el-icon-cpu' }
}
]
},

View File

@@ -39,7 +39,7 @@
>
<el-option label="全部" value="" />
<el-option label="素材图" value="material" />
<el-option label="图生图" value="result" />
<el-option label="图生图" value="result" />
<el-option label="文本生图" value="text_to_image" />
<el-option label="去除水印" value="remove_water" />
<el-option label="其他" value="other" />
@@ -407,7 +407,10 @@
</el-button>
</el-form-item>
<el-form-item label="成品图片URL">
<el-input v-model="temp.resultImageUrl" placeholder="请输入成品图片URL" />
<el-input v-model="temp.resultImageTempUrl" placeholder="成品图片临时URL" readonly />
<el-button v-if="temp.resultImageTempUrl" type="text" size="small" style="margin-top: 5px;" @click="openMaterialUrl(temp.resultImageTempUrl)">
查看成品图片
</el-button>
</el-form-item>
<el-form-item label="提示词">
<el-input
@@ -643,6 +646,10 @@ export default {
if (row.materialUrl && !this.temp.materialUrl) {
this.temp.materialUrl = row.materialUrl
}
// 确保成品图片临时URL字段正确复制
if (row.resultImageTempUrl && !this.temp.resultImageTempUrl) {
this.temp.resultImageTempUrl = row.resultImageTempUrl
}
this.dialogStatus = 'view'
this.dialogTitle = '查看图像详情'
this.dialogVisible = true

View File

@@ -0,0 +1,397 @@
<template>
<div class="app-container">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>图像生图</span>
</div>
<div class="content">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="inline-form half-width">
<el-form-item label="图片名称" prop="imageName">
<el-input v-model="form.imageName" placeholder="请输入图片名称" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="所属人姓名" prop="ownerName">
<el-input v-model="form.ownerName" placeholder="请输入所属人姓名" maxlength="30" show-word-limit :disabled="true" />
</el-form-item>
<el-form-item label="所属人电话" prop="ownerPhone">
<el-input v-model="form.ownerPhone" placeholder="请输入所属人电话" maxlength="20" :disabled="true" />
</el-form-item>
<el-form-item label="模型" prop="model">
<el-select v-model="form.model" placeholder="请选择模型" filterable style="width: 100%">
<el-option v-for="m in modelOptions" :key="m.value" :label="m.label" :value="m.value" />
</el-select>
</el-form-item>
<el-form-item label="基础图片" prop="baseImage">
<el-upload
ref="baseUpload"
:auto-upload="false"
:on-change="handleBaseImageChange"
:before-upload="beforeUpload"
:show-file-list="false"
accept="image/*"
class="image-upload"
>
<div v-if="!form.baseImageUrl" class="upload-area">
<i class="el-icon-plus" />
<div class="upload-text">点击上传基础图片</div>
<div class="upload-hint">支持 JPGPNGGIF 格式大小不超过 5MB</div>
</div>
<div v-else class="image-preview">
<img :src="form.baseImageUrl" alt="基础图片预览" class="preview-image">
<div class="image-actions">
<el-button type="text" @click="removeBaseImage">删除</el-button>
</div>
</div>
</el-upload>
</el-form-item>
<el-form-item label="参考提示词" prop="refPrompt">
<el-input
v-model="form.refPrompt"
type="textarea"
:rows="3"
placeholder="参考提示词"
maxlength="500"
show-word-limit
:disabled="true"
/>
</el-form-item>
<el-form-item label="生成指令" prop="prompt">
<el-input
v-model="form.prompt"
type="textarea"
:rows="5"
placeholder="请输入生成指令,如:将这张图片转换为油画风格"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item class="button-container">
<el-button
type="primary"
:loading="submitting"
class="generate-button"
@click="handleSubmit"
>
<i class="el-icon-magic-stick" />
生成图片
</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</div>
</template>
<script>
import request from '@/utils/request'
export default {
name: 'ImageToImage',
data() {
return {
submitting: false,
form: {
imageName: '',
ownerName: '',
ownerPhone: '',
model: 'qwen-image-edit',
baseImage: null,
baseImageUrl: '',
refPrompt: '请参考:把图像转为蓝底证件照,工作上午照片; 新增元素,删除元素,替换元素,人像修改 ,获得正视视角,朝向左侧,将背景更改为海滩',
prompt: '',
parameters: ''
},
modelOptions: [
{ label: 'QW-PIC-EDIT', value: 'qwen-image-edit' },
{ label: 'W-PIC-EIDT', value: 'wanx2.1-imageedit' }
],
rules: {
imageName: [
{ required: true, message: '请填写图片名称', trigger: 'blur' }
],
ownerName: [
{ required: true, message: '请填写所属人姓名', trigger: 'blur' }
],
ownerPhone: [
{ required: true, message: '请填写所属人电话', trigger: 'blur' },
{ pattern: /^\+?\d[\d\-\s]{5,19}$/,
message: '电话格式不正确', trigger: 'blur' }
],
model: [
{ required: true, message: '请选择模型', trigger: 'change' }
],
baseImage: [
{ required: true, message: '请上传基础图片', trigger: 'change' }
],
prompt: [
{ required: true, message: '请填写生成指令', trigger: 'blur' },
{ min: 4, message: '指令至少 4 个字符', trigger: 'blur' }
]
}
}
},
created() {
// 预填充当前登录人的姓名和电话
try {
const name = this.$store.getters && this.$store.getters.name
const phone = this.$store.getters && this.$store.getters.phone
if (name) this.form.ownerName = name
if (phone) this.form.ownerPhone = phone
} catch (e) {
// ignore
}
},
methods: {
beforeUpload(file) {
const isImage = file.type.startsWith('image/')
const isLt5M = file.size / 1024 / 1024 < 5 // 减少到5MB
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
const isValidType = allowedTypes.includes(file.type)
if (!isImage) {
this.$message.error('只能上传图片文件!')
return false
}
if (!isValidType) {
this.$message.error('只支持 JPG、PNG、GIF 格式的图片!')
return false
}
if (!isLt5M) {
this.$message.error('图片大小不能超过 5MB!')
return false
}
return false // 阻止自动上传
},
handleBaseImageChange(file, fileList) {
if (file.raw) {
// 如果文件大于2MB进行压缩
if (file.raw.size > 2 * 1024 * 1024) {
this.compressImage(file.raw)
} else {
this.form.baseImage = file.raw
this.form.baseImageUrl = URL.createObjectURL(file.raw)
this.$refs.formRef && this.$refs.formRef.validateField('baseImage')
}
}
},
compressImage(file) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.onload = () => {
// 计算压缩后的尺寸
const maxWidth = 1920
const maxHeight = 1080
let { width, height } = img
if (width > height) {
if (width > maxWidth) {
height = (height * maxWidth) / width
width = maxWidth
}
} else {
if (height > maxHeight) {
width = (width * maxHeight) / height
height = maxHeight
}
}
canvas.width = width
canvas.height = height
// 绘制压缩后的图片
ctx.drawImage(img, 0, 0, width, height)
canvas.toBlob((blob) => {
this.form.baseImage = blob
this.form.baseImageUrl = URL.createObjectURL(blob)
this.$refs.formRef && this.$refs.formRef.validateField('baseImage')
this.$message.success('图片已自动压缩')
}, 'image/jpeg', 0.8)
}
img.src = URL.createObjectURL(file)
},
removeBaseImage() {
this.form.baseImage = null
this.form.baseImageUrl = ''
this.$refs.formRef && this.$refs.formRef.validateField('baseImage')
},
handleSubmit() {
console.log('handleSubmit 被调用')
console.log('表单ref是否存在:', !!this.$refs.formRef)
this.submit()
},
submit() {
console.log('提交按钮被点击')
console.log('当前表单数据:', this.form)
this.$refs.formRef && this.$refs.formRef.validate(async valid => {
console.log('表单验证结果:', valid)
if (!valid) {
console.log('表单验证失败')
return
}
console.log('开始提交数据')
this.submitting = true
// 检查是否已选择图片
if (!this.form.baseImage) {
this.$message.error('请先上传基础图片')
this.submitting = false
return
}
// 立即显示提交成功提示
this.$message.success('提交成功,请在图像管理列表查看结果。')
this.submitting = false
// 异步提交到后端,不等待响应
this.submitToBackend()
})
},
async submitToBackend() {
try {
// 构建FormData请求数据匹配后端@RequestParam接口格式
const formData = new FormData()
formData.append('multipartFile', this.form.baseImage)
formData.append('refPrompt', this.form.refPrompt)
formData.append('prompt', this.form.prompt)
formData.append('ownerName', this.form.ownerName)
formData.append('ownerPhone', this.form.ownerPhone)
formData.append('imageName', this.form.imageName)
formData.append('model', this.form.model)
formData.append('parameters', this.form.parameters)
console.log('异步发送FormData请求数据')
// 使用项目的请求服务,但移除超时限制,不等待响应
request({
url: '/image-model/image-to-image',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 0 // 移除超时限制
}).then(response => {
console.log('后端请求已发送,状态:', response)
}).catch(error => {
console.log('后端请求发送完成,可能仍在处理中:', error.message)
})
} catch (e) {
console.log('异步提交过程中出现错误,但不影响用户体验:', e.message)
}
}
}
}
</script>
<style scoped>
.content {
padding: 20px 0;
display: flex;
justify-content: center;
}
.half-width {
max-width: 600px;
width: 100%;
margin: 0 auto;
}
.button-container {
display: flex;
justify-content: center;
}
.generate-button {
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 2px 8px rgba(64, 158, 255, 0.2);
transition: all 0.3s ease;
}
.generate-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
.generate-button:active {
transform: translateY(0);
}
.image-upload {
width: 100%;
}
.upload-area {
border: 2px dashed #d9d9d9;
border-radius: 6px;
width: 100%;
height: 200px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.upload-area:hover {
border-color: #409eff;
}
.upload-area i {
font-size: 28px;
color: #8c939d;
margin-bottom: 16px;
}
.upload-text {
font-size: 14px;
color: #606266;
margin-bottom: 8px;
}
.upload-hint {
font-size: 12px;
color: #909399;
}
.image-preview {
position: relative;
width: 100%;
height: 200px;
border: 1px solid #d9d9d9;
border-radius: 6px;
overflow: hidden;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-actions {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
padding: 4px 8px;
}
.image-actions .el-button {
color: white;
padding: 0;
}
</style>