基于多图生成图片模型

This commit is contained in:
ZLI263
2025-10-15 11:44:15 +08:00
parent a175b6d513
commit 84da5e60ed
3 changed files with 834 additions and 7 deletions

View File

@@ -73,3 +73,12 @@ export function removeWatermark(data) {
data
})
}
// 图像处理接口
export function imageProcessing(data) {
return request({
url: '/image-model/image-background',
method: 'post',
data
})
}

View File

@@ -327,7 +327,7 @@ export const constantRoutes = [
path: 'background-model',
name: 'BackgroundModel',
component: () => import('@/views/image-model/background-model'),
meta: { title: '图像背景模型', icon: 'el-icon-picture-outline' }
meta: { title: '图像智聚', icon: 'el-icon-picture-outline' }
},
{
path: 'virtual-model',

View File

@@ -1,26 +1,844 @@
<template>
<div class="app-container">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>图像背景模型</span>
<div class="content">
<el-form :model="form" class="image-form">
<!-- 主图选择区域 -->
<div class="form-section">
<div class="section-header">
<h3>主图选择</h3>
</div>
<div class="section-content">
<div class="two-column-layout">
<!-- 左列下拉框和提示词 -->
<div class="left-column">
<div class="selector-wrapper">
<el-select
v-model="form.baseImageId"
placeholder="请选择主图"
class="image-select"
:loading="imageListLoading"
@change="handleBaseImageChange"
>
<el-option
v-for="item in imageList"
:key="item.uuid"
:label="item.imageName"
:value="item.uuid"
/>
</el-select>
</div>
</div>
<div class="content">
<el-empty description="功能建设中,敬请期待" />
<!-- 右列图片预览 -->
<div class="right-column">
<div v-if="selectedImage" class="image-preview">
<img
:src="selectedImage.materialTempUrl"
:alt="selectedImage.imageName"
class="preview-image"
@error="handleImageError"
>
<div class="image-details">
<p><strong>{{ selectedImage.imageName }}</strong></p>
<p>{{ selectedImage.ownerName }}</p>
<p>{{ getImageTypeText(selectedImage.imageType) }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 参考图选择区域 -->
<div class="form-section">
<div class="section-header">
<h3>参考图选择</h3>
</div>
<div class="section-content">
<div class="two-column-layout">
<!-- 左列下拉框和提示词 -->
<div class="left-column">
<div class="selector-wrapper">
<el-select
v-model="form.refImageId"
placeholder="请选择参考图"
class="image-select"
:loading="imageListLoading"
@change="handleRefImageChange"
>
<el-option
v-for="item in imageList"
:key="item.uuid"
:label="item.imageName"
:value="item.uuid"
/>
</el-select>
</div>
<div class="prompt-container">
<el-input
v-model="form.refPrompt"
type="textarea"
:rows="3"
placeholder="请输入参考图提示词"
class="prompt-input"
/>
</div>
</div>
<!-- 右列图片预览 -->
<div class="right-column">
<div v-if="selectedRefImage" class="image-preview">
<img
:src="selectedRefImage.materialTempUrl"
:alt="selectedRefImage.imageName"
class="preview-image"
@error="handleImageError"
>
<div class="image-details">
<p><strong>{{ selectedRefImage.imageName }}</strong></p>
<p>{{ selectedRefImage.ownerName }}</p>
<p>{{ getImageTypeText(selectedRefImage.imageType) }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 前景边缘图1选择区域 -->
<div class="form-section">
<div class="section-header">
<h3>前景边缘图1选择</h3>
</div>
<div class="section-content">
<div class="two-column-layout">
<!-- 左列下拉框和提示词 -->
<div class="left-column">
<div class="selector-wrapper">
<el-select
v-model="form.foregroundEdge1Id"
placeholder="请选择前景边缘图1"
class="image-select"
:loading="imageListLoading"
@change="handleForegroundEdge1Change"
>
<el-option
v-for="item in imageList"
:key="item.uuid"
:label="item.imageName"
:value="item.uuid"
/>
</el-select>
</div>
<div class="prompt-container">
<el-input
v-model="form.foregroundEdge1Prompt"
type="textarea"
:rows="3"
placeholder="请输入前景边缘图1提示词"
class="prompt-input"
/>
</div>
</div>
<!-- 右列图片预览 -->
<div class="right-column">
<div v-if="selectedForegroundEdge1" class="image-preview">
<img
:src="selectedForegroundEdge1.materialTempUrl"
:alt="selectedForegroundEdge1.imageName"
class="preview-image"
@error="handleImageError"
>
<div class="image-details">
<p><strong>{{ selectedForegroundEdge1.imageName }}</strong></p>
<p>{{ selectedForegroundEdge1.ownerName }}</p>
<p>{{ getImageTypeText(selectedForegroundEdge1.imageType) }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 前景边缘图2选择区域 -->
<div class="form-section">
<div class="section-header">
<h3>前景边缘图2选择</h3>
</div>
<div class="section-content">
<div class="two-column-layout">
<!-- 左列下拉框和提示词 -->
<div class="left-column">
<div class="selector-wrapper">
<el-select
v-model="form.foregroundEdge2Id"
placeholder="请选择前景边缘图2"
class="image-select"
:loading="imageListLoading"
@change="handleForegroundEdge2Change"
>
<el-option
v-for="item in imageList"
:key="item.uuid"
:label="item.imageName"
:value="item.uuid"
/>
</el-select>
</div>
<div class="prompt-container">
<el-input
v-model="form.foregroundEdge2Prompt"
type="textarea"
:rows="3"
placeholder="请输入前景边缘图2提示词"
class="prompt-input"
/>
</div>
</div>
<!-- 右列图片预览 -->
<div class="right-column">
<div v-if="selectedForegroundEdge2" class="image-preview">
<img
:src="selectedForegroundEdge2.materialTempUrl"
:alt="selectedForegroundEdge2.imageName"
class="preview-image"
@error="handleImageError"
>
<div class="image-details">
<p><strong>{{ selectedForegroundEdge2.imageName }}</strong></p>
<p>{{ selectedForegroundEdge2.ownerName }}</p>
<p>{{ getImageTypeText(selectedForegroundEdge2.imageType) }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 背景边缘图选择区域 -->
<div class="form-section">
<div class="section-header">
<h3>背景边缘图选择</h3>
</div>
<div class="section-content">
<div class="two-column-layout">
<!-- 左列下拉框和提示词 -->
<div class="left-column">
<div class="selector-wrapper">
<el-select
v-model="form.backgroundEdgeId"
placeholder="请选择背景边缘图"
class="image-select"
:loading="imageListLoading"
@change="handleBackgroundEdgeChange"
>
<el-option
v-for="item in imageList"
:key="item.uuid"
:label="item.imageName"
:value="item.uuid"
/>
</el-select>
</div>
<div class="prompt-container">
<el-input
v-model="form.backgroundEdgePrompt"
type="textarea"
:rows="3"
placeholder="请输入背景边缘图提示词"
class="prompt-input"
/>
</div>
</div>
<!-- 右列图片预览 -->
<div class="right-column">
<div v-if="selectedBackgroundEdge" class="image-preview">
<img
:src="selectedBackgroundEdge.materialTempUrl"
:alt="selectedBackgroundEdge.imageName"
class="preview-image"
@error="handleImageError"
>
<div class="image-details">
<p><strong>{{ selectedBackgroundEdge.imageName }}</strong></p>
<p>{{ selectedBackgroundEdge.ownerName }}</p>
<p>{{ getImageTypeText(selectedBackgroundEdge.imageType) }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 生成参数区域 -->
<div class="form-section">
<div class="section-header">
<h3>生成参数</h3>
</div>
<div class="section-content">
<div class="parameter-container">
<div class="parameter-row">
<div class="parameter-item">
<label class="parameter-label">张数 (n)</label>
<el-input-number
v-model="form.n"
:min="1"
:max="100"
placeholder="请输入张数"
class="parameter-input"
/>
</div>
<div class="parameter-item">
<label class="parameter-label">参考提示词权重</label>
<el-input-number
v-model="form.refPromptWeight"
:min="0"
:max="1"
:precision="2"
:step="0.1"
placeholder="请输入权重"
class="parameter-input"
/>
</div>
<div class="parameter-item">
<label class="parameter-label">模型版本</label>
<el-input
v-model="form.modelVersion"
placeholder="请输入模型版本"
class="parameter-input"
/>
</div>
</div>
<div class="parameter-row">
<div class="parameter-item">
<label class="parameter-label">所有人姓名</label>
<el-input
v-model="form.ownerName"
placeholder="请输入所有人姓名"
class="parameter-input"
/>
</div>
<div class="parameter-item">
<label class="parameter-label">所有人电话</label>
<el-input
v-model="form.ownerPhone"
placeholder="请输入所有人电话"
class="parameter-input"
/>
</div>
</div>
<div class="button-container">
<el-button
type="primary"
size="large"
:loading="generateLoading"
class="generate-button"
@click="handleGenerate"
>
<i class="el-icon-magic-stick" />
生成图像
</el-button>
</div>
</div>
</div>
</div>
</el-form>
</div>
</el-card>
</div>
</template>
<script>
import { getImageList, imageProcessing } from '@/api/image-management'
import { mapGetters } from 'vuex'
export default {
name: 'BackgroundModel'
name: 'BackgroundModel',
data() {
return {
form: {
// 主图选择
baseImageId: '',
// 参考图选择
refImageId: '',
refPrompt: '',
// 前景边缘图选择
foregroundEdge1Id: '',
foregroundEdge2Id: '',
foregroundEdge1Prompt: '',
foregroundEdge2Prompt: '',
// 背景边缘图选择
backgroundEdgeId: '',
backgroundEdgePrompt: '',
// 生成参数
n: 1,
refPromptWeight: 0.5,
modelVersion: 'v3',
// 所有人信息
ownerName: '',
ownerPhone: ''
},
imageList: [],
imageListLoading: false,
generateLoading: false,
selectedBaseImage: null,
selectedRefImage: null,
selectedForegroundEdge1: null,
selectedForegroundEdge2: null,
selectedBackgroundEdge: null
}
},
computed: {
...mapGetters(['name', 'phone'])
},
created() {
this.loadImageList()
this.setDefaultUserInfo()
},
methods: {
// 设置默认用户信息
setDefaultUserInfo() {
if (this.name) {
this.form.ownerName = this.name
}
if (this.phone) {
this.form.ownerPhone = this.phone
}
},
// 加载图像列表
loadImageList() {
this.imageListLoading = true
const params = {
current: 1,
size: 20
}
getImageList(params).then(response => {
console.log('图像列表响应:', response)
console.log('响应数据结构:', {
success: response.success,
data: response.data,
total: response.total,
message: response.message
})
// 检查响应是否成功 - 参考图像管理列表的处理方式
const isSuccess = response.success === true || (response.data && response.data.length >= 0)
console.log('判断是否成功:', {
'response.success': response.success,
'response.data存在': !!response.data,
'isSuccess': isSuccess
})
if (isSuccess) {
// 根据后端返回的数据结构数据在response.data中
console.log('原始数据:', response.data)
this.imageList = response.data || []
console.log('设置后的图像列表:', this.imageList)
} else {
console.error('获取图像列表失败:', response.message)
this.$message.error(response.message || '获取图像列表失败')
this.imageList = []
}
}).catch(error => {
console.error('获取图像列表异常:', error)
this.$message.error('获取图像列表失败: ' + error.message)
this.imageList = []
}).finally(() => {
this.imageListLoading = false
})
},
// 处理主图选择变化
handleBaseImageChange(imageId) {
console.log('选择的主图ID', imageId)
if (imageId) {
this.selectedBaseImage = this.imageList.find(item => item.uuid === imageId)
console.log('选中的主图信息:', this.selectedBaseImage)
} else {
this.selectedBaseImage = null
}
},
// 处理参考图选择变化
handleRefImageChange(refImageId) {
console.log('选择的参考图ID', refImageId)
if (refImageId) {
this.selectedRefImage = this.imageList.find(item => item.uuid === refImageId)
console.log('选中的参考图信息:', this.selectedRefImage)
} else {
this.selectedRefImage = null
}
},
// 处理前景边缘图1选择变化
handleForegroundEdge1Change(edge1Id) {
console.log('选择的前景边缘图1 ID', edge1Id)
if (edge1Id) {
this.selectedForegroundEdge1 = this.imageList.find(item => item.uuid === edge1Id)
console.log('选中的前景边缘图1信息', this.selectedForegroundEdge1)
} else {
this.selectedForegroundEdge1 = null
}
},
// 处理前景边缘图2选择变化
handleForegroundEdge2Change(edge2Id) {
console.log('选择的前景边缘图2 ID', edge2Id)
if (edge2Id) {
this.selectedForegroundEdge2 = this.imageList.find(item => item.uuid === edge2Id)
console.log('选中的前景边缘图2信息', this.selectedForegroundEdge2)
} else {
this.selectedForegroundEdge2 = null
}
},
// 处理背景边缘图选择变化
handleBackgroundEdgeChange(backgroundEdgeId) {
console.log('选择的背景边缘图ID', backgroundEdgeId)
if (backgroundEdgeId) {
this.selectedBackgroundEdge = this.imageList.find(item => item.uuid === backgroundEdgeId)
console.log('选中的背景边缘图信息:', this.selectedBackgroundEdge)
} else {
this.selectedBackgroundEdge = null
}
},
// 处理图片加载错误
handleImageError(event) {
console.error('图片加载失败:', event)
this.$message.error('图片加载失败请检查图片URL')
},
// 获取图片类型文本
getImageTypeText(type) {
const typeMap = {
background: '背景图',
person: '人物图',
product: '产品图',
material: '素材图',
other: '其他'
}
return typeMap[type] || '未知'
},
// 处理生成按钮点击
handleGenerate() {
// 验证必填项
if (!this.form.baseImageId) {
this.$message.warning('请选择主图')
return
}
if (!this.form.refImageId) {
this.$message.warning('请选择参考图')
return
}
if (!this.form.foregroundEdge1Id) {
this.$message.warning('请选择前景边缘图1')
return
}
if (!this.form.foregroundEdge2Id) {
this.$message.warning('请选择前景边缘图2')
return
}
if (!this.form.backgroundEdgeId) {
this.$message.warning('请选择背景边缘图')
return
}
// 开始生成
this.generateLoading = true
// 构建后端参数结构
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
],
background_edge: [
this.selectedBackgroundEdge.materialTempUrl
],
foreground_edge_prompt: [
this.form.foregroundEdge1Prompt,
this.form.foregroundEdge2Prompt
],
background_edge_prompt: [
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
}
}
console.log('开始生成图像,参数:', requestData)
// 调用后端接口
imageProcessing(requestData).then(response => {
this.generateLoading = false
if (response.success) {
this.$message.success(response.message || '图像处理完成')
console.log('图像处理成功,返回数据:', response)
// 处理返回的图片数据
if (response.processedImages && response.processedImages.length > 0) {
console.log('生成的图片:', response.processedImages)
this.$message.info(`成功生成 ${response.processedImages.length} 张图像`)
}
// 显示处理信息
if (response.processingTimeMs) {
console.log('处理时间:', response.processingTimeMs + 'ms')
}
if (response.requestId) {
console.log('请求ID', response.requestId)
}
} else {
this.$message.error(response.message || '图像处理失败')
console.error('图像处理失败:', response)
}
}).catch(error => {
this.generateLoading = false
console.error('调用图像处理接口失败:', error)
this.$message.error('调用图像处理接口失败:' + (error.message || '未知错误'))
})
}
}
}
</script>
<style scoped>
.app-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
.content {
padding: 0;
}
.image-form {
width: 100%;
}
.form-section {
margin-bottom: 30px;
border: 1px solid #e4e7ed;
border-radius: 12px;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 15px 20px;
color: white;
}
.section-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.section-content {
padding: 20px;
}
.two-column-layout {
display: flex;
gap: 30px;
align-items: flex-start;
}
.left-column {
flex: 1;
min-width: 0;
}
.right-column {
flex: 1;
min-width: 0;
}
.selector-wrapper {
margin-bottom: 15px;
}
.image-select {
width: 100%;
}
.image-preview {
display: flex;
align-items: center;
gap: 12px;
padding: 15px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #f8f9fa;
width: 100%;
height: 120px;
box-sizing: border-box;
}
.preview-image {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 8px;
border: 2px solid #dcdfe6;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.image-details {
flex: 1;
min-width: 0;
}
.image-details p {
margin: 0 0 4px 0;
font-size: 13px;
color: #606266;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.image-details p:last-child {
margin-bottom: 0;
}
.image-details strong {
color: #303133;
font-weight: 600;
}
.prompt-container {
margin-top: 15px;
}
.prompt-input {
width: 100%;
}
.prompt-input .el-textarea__inner {
border-radius: 8px;
border: 1px solid #dcdfe6;
transition: all 0.3s;
}
.prompt-input .el-textarea__inner:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
/* 参数区域样式 */
.parameter-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.parameter-row {
display: flex;
gap: 30px;
align-items: center;
}
.parameter-item {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.parameter-label {
font-size: 14px;
font-weight: 600;
color: #303133;
margin: 0;
}
.parameter-input {
width: 100%;
}
.button-container {
display: flex;
justify-content: center;
padding: 20px 0;
}
.generate-button {
padding: 12px 40px;
font-size: 16px;
font-weight: 600;
border-radius: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
}
.generate-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
}
.generate-button:active {
transform: translateY(0);
}
/* 响应式设计 */
@media (max-width: 768px) {
.app-container {
padding: 10px;
}
.two-column-layout {
flex-direction: column;
gap: 20px;
}
.left-column,
.right-column {
width: 100%;
}
.section-content {
padding: 15px;
}
.parameter-row {
flex-direction: column;
gap: 20px;
}
.generate-button {
width: 100%;
padding: 15px 20px;
}
}
</style>