feat(ai): 支持双模型多视角AI设计生图与后台管理系统

- 实现AI多视角设计图生成功能,支持6个可选设计参数配置
- 集成SiliconFlow FLUX.1与火山引擎Seedream 4.5双模型切换
- 构建专业中文转英文prompt系统,提升AI生成质量
- 前端设计预览支持多视角切换与视角指示器展示
- 增加多视角设计图片DesignImage模型关联及存储
- 后端设计服务异步调用AI接口,失败时降级生成mock图
- 新增管理员后台管理路由及完整的权限校验机制
- 实现后台模块:仪表盘、系统配置、用户/品类/设计管理
- 配置数据库系统配置表,支持动态AI配置及热更新
- 增加用户管理员标识字段,管理后台登录鉴权支持
- 更新API接口支持多视角设计参数及后台管理接口
- 优化设计删除逻辑,删除多视角相关图片文件
- 前端新增管理后台页面与路由,布局样式独立分离
- 更新环境变量增加AI模型相关Key与参数配置说明
- 引入httpx异步HTTP客户端用于AI接口调用及图片下载
- README文档完善AI多视角生图与后台管理详细功能与流程说明
This commit is contained in:
2026-03-27 15:29:50 +08:00
parent e3ff55b4db
commit 032c43525a
41 changed files with 3756 additions and 81 deletions

View File

@@ -1,14 +1,27 @@
<template>
<div class="design-preview">
<!-- 视角 Tab 多图时显示 -->
<div class="view-tabs" v-if="hasMultipleViews">
<button
v-for="(img, idx) in design.images"
:key="img.id"
class="view-tab"
:class="{ active: activeViewIndex === idx }"
@click="activeViewIndex = idx"
>
{{ img.view_name }}
</button>
</div>
<!-- 图片预览区 -->
<div class="preview-container">
<div class="image-wrapper" :style="{ transform: `scale(${scale})` }">
<el-image
:src="imageUrl"
:src="currentImageUrl"
:alt="design.prompt"
fit="contain"
:preview-src-list="[imageUrl]"
:initial-index="0"
:preview-src-list="allImageUrls"
:initial-index="activeViewIndex"
preview-teleported
class="design-image"
>
@@ -27,6 +40,11 @@
</el-image>
</div>
<!-- 视角指示器多图时显示 -->
<div class="view-indicator" v-if="hasMultipleViews">
<span class="indicator-text">{{ activeViewName }} ({{ activeViewIndex + 1 }}/{{ design.images.length }})</span>
</div>
<!-- 缩放控制 -->
<div class="zoom-controls">
<button class="zoom-btn" @click="zoomOut" :disabled="scale <= 0.5">
@@ -84,7 +102,7 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import { Loading, PictureFilled, ZoomIn, ZoomOut, RefreshRight, Download, User } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
@@ -97,17 +115,48 @@ const props = defineProps<{
const router = useRouter()
// 当前视角索引
const activeViewIndex = ref(0)
// 缩放比例
const scale = ref(1)
// 图片URL添加API前缀
const imageUrl = computed(() => {
if (!props.design.image_url) return ''
// 如果已经是完整URL则直接使用否则添加 /api 前缀
if (props.design.image_url.startsWith('http')) {
return props.design.image_url
// 是否有多视角图片
const hasMultipleViews = computed(() => {
return props.design.images && props.design.images.length > 1
})
// 当前视角名称
const activeViewName = computed(() => {
if (props.design.images && props.design.images.length > 0) {
return props.design.images[activeViewIndex.value]?.view_name || ''
}
return `/api${props.design.image_url}`
return ''
})
// 获取图片URL添加API前缀
const toImageUrl = (url: string | null): string => {
if (!url) return ''
if (url.startsWith('http')) return url
return `/api${url}`
}
// 当前显示的图片URL
const currentImageUrl = computed(() => {
// 优先用多视角图片
if (props.design.images && props.design.images.length > 0) {
return toImageUrl(props.design.images[activeViewIndex.value]?.image_url)
}
// 兼容旧数据,使用单图
return toImageUrl(props.design.image_url)
})
// 所有图片URL用于大图预览
const allImageUrls = computed(() => {
if (props.design.images && props.design.images.length > 0) {
return props.design.images.map(img => toImageUrl(img.image_url))
}
return [toImageUrl(props.design.image_url)]
})
// 下载URL
@@ -117,7 +166,13 @@ const downloadUrl = computed(() => getDesignDownloadUrl(props.design.id))
const downloadFilename = computed(() => {
const category = props.design.category?.name || '设计'
const subType = props.design.sub_type?.name || ''
return `${category}${subType ? '-' + subType : ''}-${props.design.id}.png`
const viewSuffix = hasMultipleViews.value ? `-${activeViewName.value}` : ''
return `${category}${subType ? '-' + subType : ''}${viewSuffix}-${props.design.id}.png`
})
// 切换视角时重置缩放
watch(activeViewIndex, () => {
scale.value = 1
})
// 放大
@@ -163,6 +218,54 @@ $text-light: #999999;
gap: 24px;
}
.view-tabs {
display: flex;
gap: 8px;
padding: 4px;
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.view-tab {
flex: 1;
padding: 10px 16px;
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
font-size: 14px;
color: $text-secondary;
cursor: pointer;
transition: all 0.25s ease;
letter-spacing: 1px;
&:hover {
color: $primary-color;
background: rgba($primary-color, 0.05);
}
&.active {
background: $primary-color;
color: #fff;
border-color: $primary-color;
font-weight: 500;
}
}
.view-indicator {
position: absolute;
top: 16px;
left: 16px;
background: rgba(0, 0, 0, 0.5);
padding: 4px 12px;
border-radius: 12px;
}
.indicator-text {
font-size: 12px;
color: #fff;
}
.preview-container {
position: relative;
background: #fff;