Compare commits

...

6 Commits

Author SHA1 Message Date
18dfc75372 fix(backend): 修复视频生成接口401认证失败问题
- 修正可灵视频任务提交时认证失败导致的视频生成错误
- 解决接口返回500错误的问题
- 优化视频生成相关的错误日志提示
- 保证上传目录准备状态的正确显示
- 提升后台服务日志的稳定性和连续性
2026-04-13 14:41:02 +08:00
7f84a04e27 feat(frontend): 添加首页及调整导航和路由逻辑
- 新增完整首页视图,展示核心功能、品类覆盖及使用流程
- 设计首页响应式布局及样式,优化多终端展示效果
- 修改AdminLayout中返回前台链接为/design路径
- 删除AppHeader中顶部设计、生成、管理后台菜单链接
- 在用户下拉菜单新增管理后台入口,点击跳转/admin
- 新增路由/home和/design页面,完善路由配置
- 将登录、注册、生成页跳转路径由根目录改为/design,增强用户体验
2026-03-29 17:15:14 +08:00
4382feedb3 style(frontend): 优化前端样式和界面细节
- 统一并丰富主题色变量,新增多级浅色和圆角、阴影变量
- 调整应用头部布局及风格,增加logo子标题和用户头像显示
- 细化分类导航样式,添加品类图标和渐变背景
- 优化颜色选择器的交互动效和样式细节
- 美化设计预览组件,提升边框圆角和阴影效果
- 调整子类型面板布局、尺寸及交互动画效果
- 修正全局样式中字体和滚动条的表现,增强用户体验
- 统一按钮、标签等控件的圆角和颜色渐变样式
- 增强Element Plus UI组件的主题覆盖和交互状态样式
2026-03-29 15:55:27 +08:00
5f3cda2a63 chore(server): 记录服务器启动与热重载日志
- 详细记录服务启动及关闭过程中的日志信息
- 添加文件变动检测后的自动重启警告和操作日志
- 记录上传目录准备状态日志
- 追踪接口请求响应日志,包括视频和设计资源访问
- 添加应用关闭和重启通知日志
2026-03-28 19:51:52 +08:00
2ef126e445 feat: 强化多视角图片一致性 + 修复下载逻辑 + 技术文档
- 新增品类专属背面/侧面描述(BACK_VIEW_HINTS/SIDE_VIEW_HINTS)
- 强化一致性前缀策略,按视角定制相机位置描述
- 更新视角映射提示词为纯摄影术语
- 修复前端下载逻辑:改用fetch直接下载当前视角图片
- HTTPS改HTTP修复外网URL访问
- 新增多视角一致性与3D视频生成技术文档
2026-03-28 19:51:08 +08:00
1d94ec114a feat(video): 集成可灵AI多图参考生视频生成服务
- 替换视频生成服务为可灵AI多图参考生视频API,支持1-4张多视角图片输入
- 调整图片拼接逻辑,生成横向长图传入即梦API备用
- 实现基于JWT认证的可灵API请求和轮询机制,支持高品质1:1正方形视频生成
- 在设计详情页新增视频展示区域及生成、重新生成和下载视频操作
- 更新后台系统配置,支持配置可灵AI Access Key和Secret Key
- 删除即梦视频相关配置及逻辑,所有视频生成功能切换到可灵AI实现
- 优化视频生成提示词,提升视频质感和展示效果
- 增加视频文件本地存储和路径管理,保证视频可访问和下载
- 前端增加视频生成状态管理和用户界面交互提示
- 后端添加PyJWT依赖,支持JWT认证流程
2026-03-28 00:20:48 +08:00
37 changed files with 3341 additions and 906 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -13,7 +13,7 @@ from ..models import User, Design, DesignImage
from ..schemas import DesignCreate, DesignResponse, DesignListResponse, DesignImageResponse from ..schemas import DesignCreate, DesignResponse, DesignListResponse, DesignImageResponse
from ..utils.deps import get_current_user from ..utils.deps import get_current_user
from ..services import design_service from ..services import design_service
from ..services import ai_video_generator from ..services import ai_video_generator_kling
from ..services import ai_3d_generator from ..services import ai_3d_generator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -255,7 +255,7 @@ async def generate_video(
): ):
""" """
为设计生成 360 度旋转展示视频 为设计生成 360 度旋转展示视频
取设计的多视角图片,通过火山引擎即梦 3.0 Pro 生成视频 取设计的多视角图片,通过可灵 AI 多图参考生视频 API 生成视频
""" """
design = design_service.get_design_by_id(db=db, design_id=design_id, user_id=current_user.id) design = design_service.get_design_by_id(db=db, design_id=design_id, user_id=current_user.id)
if not design: if not design:
@@ -285,7 +285,7 @@ async def generate_video(
logger.info(f"设计 {design_id} 生成视频,共收集到 {len(image_urls)} 张图片") logger.info(f"设计 {design_id} 生成视频,共收集到 {len(image_urls)} 张图片")
try: try:
video_url = await ai_video_generator.generate_video(image_urls) video_url = await ai_video_generator_kling.generate_video(image_urls)
design.video_url = video_url design.video_url = video_url
db.commit() db.commit()
db.refresh(design) db.refresh(design)

View File

@@ -27,8 +27,9 @@ class DesignCreate(BaseModel):
"""创建设计请求""" """创建设计请求"""
category_id: int = Field(..., description="品类ID") category_id: int = Field(..., description="品类ID")
sub_type_id: Optional[int] = Field(None, description="子类型ID") sub_type_id: Optional[int] = Field(None, description="子类型ID")
sub_type_name: Optional[str] = Field(None, max_length=50, description="自定义子类型名称")
color_id: Optional[int] = Field(None, description="颜色ID") color_id: Optional[int] = Field(None, description="颜色ID")
prompt: str = Field(..., min_length=1, max_length=2000, description="设计需求") prompt: Optional[str] = Field(None, max_length=2000, description="设计需求")
carving_technique: Optional[str] = Field(None, max_length=50, description="雕刻工艺") carving_technique: Optional[str] = Field(None, max_length=50, description="雕刻工艺")
design_style: Optional[str] = Field(None, max_length=50, description="设计风格") design_style: Optional[str] = Field(None, max_length=50, description="设计风格")
motif: Optional[str] = Field(None, max_length=100, description="题材纹样") motif: Optional[str] = Field(None, max_length=100, description="题材纹样")

View File

@@ -126,6 +126,18 @@ _VIEW_NAME_MAP = {
} }
def _to_public_url(url: str) -> str:
"""将本地路径转换为外网可访问的完整 URL
第三方API如混元3D、可灵AI需要外网可访问的图片URL
本地存储路径(/uploads/xxx需要拼接域名。
"""
if url and url.startswith("/uploads/"):
base_domain = get_config_value("SITE_DOMAIN", "http://c02.wsg.plus")
return f"{base_domain}{url}"
return url
async def generate_3d_model(image_urls: list, view_names: Optional[list] = None) -> str: async def generate_3d_model(image_urls: list, view_names: Optional[list] = None) -> str:
""" """
调用腾讯混元3D 专业版 API 将图片生成 3D 模型 调用腾讯混元3D 专业版 API 将图片生成 3D 模型
@@ -146,6 +158,9 @@ async def generate_3d_model(image_urls: list, view_names: Optional[list] = None)
if not view_names: if not view_names:
view_names = ["效果图"] + ["未知"] * (len(image_urls) - 1) view_names = ["效果图"] + ["未知"] * (len(image_urls) - 1)
# 将本地路径转换为外网可访问URL第三方API需要完整URL
image_urls = [_to_public_url(u) for u in image_urls]
# 选择主图(正面图优先,其次效果图,否则第一张) # 选择主图(正面图优先,其次效果图,否则第一张)
main_url = None main_url = None
multi_views = [] multi_views = []

View File

@@ -152,12 +152,11 @@ def _get_volc_keys() -> tuple:
async def _merge_images_to_base64(image_urls: List[str]) -> str: async def _merge_images_to_base64(image_urls: List[str]) -> str:
""" """
下载多张图片并拼接为一张网格图,返回 base64 编码 下载多张图片并横向拼接为一张图,返回 base64 编码
拼接策略: 拼接策略:
- 1张: 直接使用 - 1张: 直接使用
- 2张: 1×2 横向拼接 - 张: 横向一字排开拼接(展示各角度全貌)
- 3~4张: 2×2 网格拼接
""" """
# 下载所有图片 # 下载所有图片
images = [] images = []
@@ -179,36 +178,43 @@ async def _merge_images_to_base64(image_urls: List[str]) -> str:
if len(images) == 1: if len(images) == 1:
merged = images[0] merged = images[0]
else: else:
# 统一尺寸到最大尺寸 # 统一高度,横向拼接
max_w = max(img.width for img in images) target_h = max(img.height for img in images)
max_h = max(img.height for img in images) resized = []
for img in images:
if img.height != target_h:
ratio = target_h / img.height
new_w = int(img.width * ratio)
img = img.resize((new_w, target_h), Image.LANCZOS)
resized.append(img)
# 计算网格布局 total_w = sum(img.width for img in resized)
n = len(images) merged = Image.new("RGB", (total_w, target_h), (255, 255, 255))
if n == 2: x_offset = 0
cols, rows = 2, 1 for img in resized:
else: merged.paste(img, (x_offset, 0))
cols = 2 x_offset += img.width
rows = math.ceil(n / cols)
# 创建拼接画布(白色背景) logger.info(f"图片横向拼接完成: {len(resized)}张 -> 尺寸={merged.size}")
canvas_w = cols * max_w
canvas_h = rows * max_h
merged = Image.new("RGB", (canvas_w, canvas_h), (255, 255, 255))
for idx, img in enumerate(images): # 压缩图片尺寸即梦API对请求体大小有限制
r = idx // cols max_width = 1920
c = idx % cols if merged.width > max_width:
# 居中放置 ratio = max_width / merged.width
x = c * max_w + (max_w - img.width) // 2 new_h = int(merged.height * ratio)
y = r * max_h + (max_h - img.height) // 2 merged = merged.resize((max_width, new_h), Image.LANCZOS)
merged.paste(img, (x, y)) logger.info(f"拼接图已压缩至: {merged.size}")
logger.info(f"图片拼接完成: {n}张 -> {cols}x{rows}网格, 尺寸={merged.size}") # 转 base64控制质量确保 base64 不会过大
# 转 base64
buf = io.BytesIO() buf = io.BytesIO()
merged.save(buf, format="JPEG", quality=90) quality = 75
merged.save(buf, format="JPEG", quality=quality)
# 如果超过 5MB进一步压缩
while buf.tell() > 5 * 1024 * 1024 and quality > 30:
buf = io.BytesIO()
quality -= 10
merged.save(buf, format="JPEG", quality=quality)
logger.info(f"图片超过 5MB降低质量到 {quality},大小={buf.tell()} bytes")
b64 = base64.b64encode(buf.getvalue()).decode("utf-8") b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
logger.info(f"拼接图 base64 长度: {len(b64)}") logger.info(f"拼接图 base64 长度: {len(b64)}")
return b64 return b64
@@ -222,44 +228,56 @@ async def generate_video(
""" """
调用即梦3.0 Pro 生成 360 度旋转展示视频 调用即梦3.0 Pro 生成 360 度旋转展示视频
流程:
1. 将多视角图片横向拼接成一张长图
2. 以 base64 方式传入即梦API
3. 使用强化提示词描述单品展示
Args: Args:
image_urls: 多视角图片 URL 列表(取第一张作为首帧) image_urls: 多视角图片 URL 列表
prompt: 视频生成提示词 prompt: 视频生成提示词
duration_seconds: 预留参数(即梦目前固定帧数) duration_seconds: 预留参数
Returns: Returns:
生成的视频远程 URL 生成的视频本地 URL
Raises:
RuntimeError: 视频生成失败
""" """
access_key, secret_key = _get_volc_keys() access_key, secret_key = _get_volc_keys()
logger.info(f"传入视频生成的图片数量: {len(image_urls)}") logger.info(f"传入视频生成的图片数量: {len(image_urls)}")
# 即梦API只支持单张图片输入取第一张正面效果图作为基准 # Step 0: 拼接多视角图片为横向长图,转 base64
first_url = image_urls[0] merged_b64 = await _merge_images_to_base64(image_urls)
logger.info(f"使用第一张图片生成视频: {first_url[:80]}...") logger.info(f"多视角图片已拼接为长图base64长度: {len(merged_b64)}")
# 从配置读取默认 prompt # 从配置读取默认 prompt,如果没有则使用强化提示词
if not prompt: if not prompt:
prompt = get_config_value("VIDEO_PROMPT", "") prompt = get_config_value("VIDEO_PROMPT", "")
if not prompt: if not prompt:
prompt = ( prompt = (
"玉雕作品在摄影棚内缓慢旋转360度展示全貌" "参考图展示的是同一件精美玉雕工艺品的多个角度"
"专业珠宝摄影灯光,纯白色背景,平稳旋转," "请生成这一件玉雕作品在专业珠宝摄影棚内的展示视频。"
"展示正面、侧面、背面各个角度,电影级画质" "纯白色背景,柔和的珠宝摄影灯光,"
"这一件玉石作品放在旋转展台上缓慢平稳地旋转360度"
"展现其温润的质感、细腻的雕刻纹理和通透的光泽,"
"电影级画质,微距的细节感,平稳流畅的转台旋转,"
"画面中只有一件玉雕作品"
) )
# Step 1: 提交任务(只传第一张图片URL # Step 1: 提交任务(用 base64 拼接长图,失败则降级为第一张图片 URL
task_id = await _submit_video_task(access_key, secret_key, first_url, prompt) try:
logger.info(f"即梦视频生成任务已提交: task_id={task_id}") task_id = await _submit_video_task(access_key, secret_key, None, prompt, merged_b64)
logger.info(f"即梦视频生成任务已提交(base64拼接图): task_id={task_id}")
except Exception as e:
logger.warning(f"base64 拼接图提交失败降级为第一张图片URL: {e}")
first_url = image_urls[0]
task_id = await _submit_video_task(access_key, secret_key, first_url, prompt)
logger.info(f"即梦视频生成任务已提交(单图URL): task_id={task_id}")
# Step 2: 轮询等待结果 # Step 2: 轮询等待结果
remote_video_url = await _poll_video_result(access_key, secret_key, task_id) remote_video_url = await _poll_video_result(access_key, secret_key, task_id)
logger.info(f"即梦视频生成完成: {remote_video_url[:80]}...") logger.info(f"即梦视频生成完成: {remote_video_url[:80]}...")
# Step 3: 下载视频到本地存储即梦URL有效期约 1 小时,必须保存到本地) # Step 3: 下载视频到本地存储
local_path = await _download_video_to_local(remote_video_url) local_path = await _download_video_to_local(remote_video_url)
logger.info(f"视频已保存到本地: {local_path}") logger.info(f"视频已保存到本地: {local_path}")
@@ -269,31 +287,43 @@ async def generate_video(
async def _submit_video_task( async def _submit_video_task(
access_key: str, access_key: str,
secret_key: str, secret_key: str,
image_url: str, image_url: Optional[str],
prompt: str, prompt: str,
image_base64: Optional[str] = None,
) -> str: ) -> str:
"""提交图生视频任务到即梦3.0 Pro使用单张图片URL""" """提交图生视频任务到即梦3.0 Pro支持 URL 或 base64 输入"""
action = "CVSync2AsyncSubmitTask" action = "CVSync2AsyncSubmitTask"
logger.info(f"提交即梦视频任务图片URL: {image_url[:80]}...")
payload = { payload = {
"req_key": REQ_KEY_I2V, "req_key": REQ_KEY_I2V,
"prompt": prompt, "prompt": prompt,
"image_urls": [image_url],
"seed": -1, "seed": -1,
"frames": int(get_config_value("VIDEO_FRAMES", "121")), "frames": int(get_config_value("VIDEO_FRAMES", "121")),
"aspect_ratio": "1:1", # 玉雕展示用正方形 "aspect_ratio": "1:1",
} }
# 优先使用 base64拼接长图否则用 URL
if image_base64:
payload["binary_data_base64"] = [image_base64]
logger.info(f"使用 base64 拼接长图提交即梦视频任务base64长度={len(image_base64)}")
elif image_url:
payload["image_urls"] = [image_url]
logger.info(f"使用图片URL提交即梦视频任务: {image_url[:80]}...")
else:
raise RuntimeError("未提供图片输入")
body = json.dumps(payload, ensure_ascii=False) body = json.dumps(payload, ensure_ascii=False)
headers = _build_signed_headers(access_key, secret_key, action, body) headers = _build_signed_headers(access_key, secret_key, action, body)
url = f"{VISUAL_API_URL}?Action={action}&Version={API_VERSION}" url = f"{VISUAL_API_URL}?Action={action}&Version={API_VERSION}"
async with httpx.AsyncClient(timeout=SUBMIT_TIMEOUT) as client: # base64 数据量大,需要更长的超时时间
timeout = 120 if image_base64 else SUBMIT_TIMEOUT
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(url, content=body, headers=headers) resp = await client.post(url, content=body, headers=headers)
if resp.status_code != 200: if resp.status_code != 200:
logger.error(f"即梦视频任务提交失败: status={resp.status_code}, body={resp.text[:500]}") error_body = resp.text[:1000]
logger.error(f"即梦视频任务提交失败: status={resp.status_code}, body={error_body}")
resp.raise_for_status() resp.raise_for_status()
data = resp.json() data = resp.json()

View File

@@ -0,0 +1,280 @@
"""
AI 视频生成服务 - 可灵Kling多图参考生视频
使用可灵 AI 的多图参考生视频 API原生支持传入 1-4 张参考图片
AI 会理解为同一物体的多角度参考,生成单品旋转展示视频
API 文档: https://app.klingai.com/cn/dev/document-api/apiReference/model/multiImageToVideo
认证方式: JWT (Access Key + Secret Key)
API 端点: https://api.klingai.com
"""
import asyncio
import json
import logging
import time
import uuid
from pathlib import Path
from typing import Optional, List
import httpx
import jwt
from .config_service import get_config_value
# 视频本地存储目录
VIDEO_UPLOAD_DIR = Path(__file__).resolve().parent.parent.parent / "uploads" / "videos"
VIDEO_UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger(__name__)
# 可灵 API 配置(中国区域名)
KLING_API_BASE = "https://api-beijing.klingai.com"
# 超时与轮询配置
SUBMIT_TIMEOUT = 30
POLL_TIMEOUT = 15
MAX_POLL_ATTEMPTS = 120 # 约 10 分钟
POLL_INTERVAL = 5
# ============================================================
# JWT 认证
# ============================================================
def _generate_jwt_token(access_key: str, secret_key: str) -> str:
"""
使用 Access Key 和 Secret Key 生成 JWT Token
可灵 API 使用 JWT 认证token 有效期 30 分钟
"""
now = int(time.time())
headers = {
"alg": "HS256",
"typ": "JWT"
}
payload = {
"iss": access_key,
"exp": now + 1800, # 30 分钟过期
"nbf": now - 5, # 允许 5 秒时钟偏差
"iat": now, # 签发时间
}
token = jwt.encode(payload, secret_key, algorithm="HS256", headers=headers)
return token
def _get_kling_keys() -> tuple:
"""获取可灵 Access Key 和 Secret Key"""
access_key = get_config_value("KLING_ACCESS_KEY", "")
secret_key = get_config_value("KLING_SECRET_KEY", "")
if not access_key or not secret_key:
raise RuntimeError(
"未配置 KLING_ACCESS_KEY 或 KLING_SECRET_KEY无法使用可灵视频生成。"
"请在管理后台 系统配置 中添加可灵 AI 的 Access Key 和 Secret Key。"
)
return access_key, secret_key
def _build_headers(access_key: str, secret_key: str) -> dict:
"""构建带 JWT 认证的请求头"""
token = _generate_jwt_token(access_key, secret_key)
return {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
}
# ============================================================
# 视频生成核心逻辑
# ============================================================
async def generate_video(
image_urls: List[str],
prompt: str = "",
duration_seconds: int = 5,
) -> str:
"""
调用可灵多图参考生视频 API生成 360 度旋转展示视频
核心优势原生支持传入多张参考图1-4张
AI 理解为同一物体的多角度参考,生成单品视频。
Args:
image_urls: 多视角图片 URL 列表最多4张
prompt: 视频生成提示词
duration_seconds: 视频时长5 或 10 秒)
Returns:
生成的视频本地 URL
"""
access_key, secret_key = _get_kling_keys()
logger.info(f"可灵视频生成,传入图片数量: {len(image_urls)}")
# 将本地路径转换为外网可访问URL可灵API需要完整URL
from .ai_3d_generator import _to_public_url
image_urls = [_to_public_url(u) for u in image_urls]
# 可灵最多支持 4 张参考图
if len(image_urls) > 4:
image_urls = image_urls[:4]
logger.info("图片数量超过4张截取前4张")
# 构建提示词
if not prompt:
prompt = get_config_value("VIDEO_PROMPT", "")
if not prompt:
prompt = (
"精美玉雕工艺品在专业珠宝摄影棚内展示,"
"纯白色背景,柔和的珠宝摄影灯光,"
"玉石作品放在旋转展台上缓慢平稳地旋转360度"
"展示正面、侧面、背面全貌,"
"展现玉石温润的质感、细腻的雕刻纹理和通透的光泽,"
"电影级画质,微距细节感,平稳流畅的转台旋转"
)
# 视频时长,仅支持 5 或 10
duration = str(duration_seconds) if duration_seconds in (5, 10) else "5"
# Step 1: 提交任务
task_id = await _submit_video_task(
access_key, secret_key, image_urls, prompt, duration
)
logger.info(f"可灵视频生成任务已提交: task_id={task_id}")
# Step 2: 轮询等待结果
remote_video_url = await _poll_video_result(access_key, secret_key, task_id)
logger.info(f"可灵视频生成完成: {remote_video_url[:80]}...")
# Step 3: 下载视频到本地存储
local_path = await _download_video_to_local(remote_video_url)
logger.info(f"视频已保存到本地: {local_path}")
return local_path
async def _submit_video_task(
access_key: str,
secret_key: str,
image_urls: List[str],
prompt: str,
duration: str = "5",
) -> str:
"""提交多图参考生视频任务到可灵 API"""
url = f"{KLING_API_BASE}/v1/videos/multi-image2video"
# 构建 image_list每个元素是 {"image": "url"}
image_list = [{"image": img_url} for img_url in image_urls]
payload = {
"model_name": "kling-v1-6",
"image_list": image_list,
"prompt": prompt,
"mode": "pro", # 高品质模式
"duration": duration, # 视频时长
"aspect_ratio": "1:1", # 1:1 正方形
}
headers = _build_headers(access_key, secret_key)
body = json.dumps(payload, ensure_ascii=False)
logger.info(f"提交可灵视频任务: {len(image_urls)}张参考图, 时长={duration}s, 模式=pro")
async with httpx.AsyncClient(timeout=SUBMIT_TIMEOUT) as client:
resp = await client.post(url, content=body, headers=headers)
if resp.status_code not in (200, 201):
error_body = resp.text[:1000]
logger.error(f"可灵视频任务提交失败: status={resp.status_code}, body={error_body}")
resp.raise_for_status()
data = resp.json()
# 检查响应
code = data.get("code", -1)
if code != 0:
msg = data.get("message", "未知错误")
raise RuntimeError(f"可灵视频任务提交失败 (code={code}): {msg}")
task_id = data.get("data", {}).get("task_id")
if not task_id:
raise RuntimeError(f"可灵响应中未找到 task_id: {data}")
return task_id
async def _poll_video_result(
access_key: str,
secret_key: str,
task_id: str,
) -> str:
"""轮询可灵视频生成结果"""
url = f"{KLING_API_BASE}/v1/videos/multi-image2video/{task_id}"
for attempt in range(1, MAX_POLL_ATTEMPTS + 1):
await asyncio.sleep(POLL_INTERVAL)
# 每次轮询重新生成 JWT避免过期
headers = _build_headers(access_key, secret_key)
try:
async with httpx.AsyncClient(timeout=POLL_TIMEOUT) as client:
resp = await client.get(url, headers=headers)
if resp.status_code != 200:
logger.warning(
f"轮询可灵视频结果失败 (attempt={attempt}): "
f"status={resp.status_code}, body={resp.text[:300]}"
)
continue
data = resp.json()
except Exception as e:
logger.warning(f"轮询可灵视频异常 (attempt={attempt}): {e}")
continue
code = data.get("code", -1)
if code != 0:
msg = data.get("message", "未知错误")
logger.warning(f"轮询可灵视频返回错误 (attempt={attempt}): code={code}, msg={msg}")
continue
task_data = data.get("data", {})
task_status = task_data.get("task_status", "")
if task_status == "succeed":
# 从 task_result.videos 中提取视频 URL
task_result = task_data.get("task_result", {})
videos = task_result.get("videos", [])
if videos and videos[0].get("url"):
return videos[0]["url"]
raise RuntimeError(f"可灵视频生成完成但未找到视频URL: {data}")
elif task_status == "failed":
fail_msg = task_data.get("task_status_msg", "未知原因")
raise RuntimeError(f"可灵视频生成失败: {fail_msg}")
else:
# submitted / processing
if attempt % 6 == 0:
logger.info(
f"可灵视频生成中... (attempt={attempt}, status={task_status})"
)
raise RuntimeError(f"可灵视频生成超时: 轮询 {MAX_POLL_ATTEMPTS} 次后仍未完成")
async def _download_video_to_local(remote_url: str) -> str:
"""
下载远程视频到本地 uploads/videos/ 目录
Returns:
本地视频的 URL 路径,如 /uploads/videos/xxx.mp4
"""
filename = f"{uuid.uuid4().hex}.mp4"
local_file = VIDEO_UPLOAD_DIR / filename
try:
async with httpx.AsyncClient(timeout=120, follow_redirects=True) as client:
resp = await client.get(remote_url)
resp.raise_for_status()
local_file.write_bytes(resp.content)
logger.info(f"视频下载完成: {len(resp.content)} 字节 -> {local_file}")
except Exception as e:
logger.error(f"视频下载失败: {e}")
raise RuntimeError(f"视频下载失败: {e}")
return f"/uploads/videos/{filename}"

View File

@@ -3,8 +3,11 @@
处理设计相关的业务逻辑,支持 AI 多视角生图 + mock 降级 处理设计相关的业务逻辑,支持 AI 多视角生图 + mock 降级
""" """
import os import os
import uuid
import logging import logging
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
import httpx
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import desc from sqlalchemy import desc
@@ -136,7 +139,7 @@ async def _generate_ai_images(
prompt_text = build_prompt( prompt_text = build_prompt(
category_name=category.name, category_name=category.name,
view_name=view_name, view_name=view_name,
sub_type_name=sub_type.name if sub_type else None, sub_type_name=sub_type.name if sub_type else design_data.sub_type_name,
color_name=color.name if color else None, color_name=color.name if color else None,
user_prompt=design_data.prompt, user_prompt=design_data.prompt,
carving_technique=design_data.carving_technique, carving_technique=design_data.carving_technique,
@@ -150,8 +153,26 @@ async def _generate_ai_images(
# 调用 AI 生图 # 调用 AI 生图
# 后续视角传入 seedKolors或参考图 URLSeedream保持一致性 # 后续视角传入 seedKolors或参考图 URLSeedream保持一致性
ref_url = first_remote_url if idx > 0 else None ref_url = first_remote_url if idx > 0 else None
# 后续视角在提示词前加一致性约束前缀(根据视角名称定制)
final_prompt = prompt_text
if idx > 0:
# 根据视角名称生成更具体的一致性前缀
view_angle_map = {
"正面图": "moving the camera to face the object directly from the front",
"侧面图": "moving the camera 90 degrees to the left side of the object",
"背面图": "moving the camera 180 degrees to see the reverse/back side of the object",
}
angle_desc = view_angle_map.get(view_name, "changing the camera angle")
consistency_prefix = (
f"Photograph the EXACT SAME jade object from the reference image, {angle_desc}. "
"The object does NOT move or change - only the camera position changes. "
"The shape, size, color, material texture, and all physical features must remain IDENTICAL. "
)
final_prompt = consistency_prefix + prompt_text
remote_url, returned_seed = await ai_generator.generate_image( remote_url, returned_seed = await ai_generator.generate_image(
prompt_text, model, seed=shared_seed, ref_image_url=ref_url final_prompt, model, seed=shared_seed, ref_image_url=ref_url
) )
# 第一张图保存信息供后续视角复用 # 第一张图保存信息供后续视角复用
@@ -161,8 +182,9 @@ async def _generate_ai_images(
shared_seed = returned_seed shared_seed = returned_seed
logger.info(f"多视角生图: seed={shared_seed}, ref_url={remote_url[:60]}...") logger.info(f"多视角生图: seed={shared_seed}, ref_url={remote_url[:60]}...")
# 直接使用远程 URL不下载到本地节省服务器存储空间 # 下载到本地持久化存储远程URL是临时链接会过期失效
image_url = remote_url image_url = await _download_image_to_local(remote_url, design.id, idx)
logger.info(f"视角[{view_name}] 已下载到本地: {image_url}")
# 创建 DesignImage 记录 # 创建 DesignImage 记录
design_image = DesignImage( design_image = DesignImage(
@@ -182,6 +204,34 @@ async def _generate_ai_images(
design.status = "completed" design.status = "completed"
async def _download_image_to_local(remote_url: str, design_id: int, idx: int) -> str:
"""
下载远程 AI 生成的图片到本地 uploads/designs/ 目录
第三方AI服务生成的图片URL是临时链接会过期失效必须下载到本地持久化
Returns:
本地图片 URL 路径,如 /uploads/designs/123_0_xxxx.png
"""
designs_dir = os.path.join(settings.UPLOAD_DIR, "designs")
os.makedirs(designs_dir, exist_ok=True)
filename = f"{design_id}_{idx}_{uuid.uuid4().hex[:8]}.png"
local_path = os.path.join(designs_dir, filename)
try:
async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client:
resp = await client.get(remote_url)
resp.raise_for_status()
with open(local_path, "wb") as f:
f.write(resp.content)
logger.info(f"图片下载完成: {len(resp.content)} 字节 -> {local_path}")
except Exception as e:
logger.error(f"图片下载失败回退使用远程URL: {e}")
return remote_url # 下载失败时回退使用远程URL
return f"/uploads/designs/{filename}"
def _generate_mock_fallback( def _generate_mock_fallback(
db: Session, db: Session,
design: Design, design: Design,
@@ -194,7 +244,7 @@ def _generate_mock_fallback(
save_path = os.path.join(settings.UPLOAD_DIR, "designs", f"{design.id}.png") save_path = os.path.join(settings.UPLOAD_DIR, "designs", f"{design.id}.png")
image_url = generate_mock_design( image_url = generate_mock_design(
category_name=category.name, category_name=category.name,
sub_type_name=sub_type.name if sub_type else None, sub_type_name=sub_type.name if sub_type else design_data.sub_type_name,
color_name=color.name if color else None, color_name=color.name if color else None,
prompt=design_data.prompt, prompt=design_data.prompt,
save_path=save_path, save_path=save_path,

View File

@@ -29,6 +29,41 @@ CATEGORY_VIEWS: Dict[str, List[str]] = {
"随形": ["效果图", "正面图", "侧面图", "背面图"], "随形": ["效果图", "正面图", "侧面图", "背面图"],
} }
# ============================================================
# 品类专属背面描述(不同品类的背面特征差异很大)
# ============================================================
BACK_VIEW_HINTS: Dict[str, str] = {
"牌子": (
"IMPORTANT: The reverse/back side of a jade pendant plaque is traditionally a smooth, flat, polished surface. "
"It may have a brief inscription or seal mark, but it must NOT have any carved figure, face, or decorative relief pattern. "
"The back is plain and minimalist. Do NOT mirror or duplicate the front carving on the back."
),
"手把件": (
"The back of this hand-held jade piece continues the same sculptural form as the front. "
"It is part of the same three-dimensional object, showing the natural continuation of the carving from the rear angle."
),
"雕刻件": (
"The back of this carved jade piece shows the rear of the same three-dimensional sculpture. "
"The carving continues around the object naturally, not a separate or different design."
),
"摆件": (
"The back of this jade display piece shows the rear of the same three-dimensional artwork. "
"The form and carving continue naturally around the object."
),
"随形": (
"The back of this free-form jade piece shows the natural stone surface from the rear. "
"The organic shape continues naturally, the back may show more of the raw jade texture."
),
}
# 品类专属侧面描述
SIDE_VIEW_HINTS: Dict[str, str] = {
"牌子": (
"The side/edge view of a jade pendant plaque shows its thin, flat profile. "
"The plaque is typically 5-10mm thick, showing the edge thickness and any subtle edge carving."
),
}
def _load_mappings(mapping_type: str) -> Dict[str, str]: def _load_mappings(mapping_type: str) -> Dict[str, str]:
"""从数据库加载指定类型的映射字典""" """从数据库加载指定类型的映射字典"""
@@ -159,7 +194,13 @@ def build_prompt(
view_desc = view_map.get(view_name, "three-quarter view") view_desc = view_map.get(view_name, "three-quarter view")
parts.append(view_desc) parts.append(view_desc)
# 12. 质量后缀 # 12. 品类专属视角描述(背面/侧面特征)
if view_name == "背面图" and category_name in BACK_VIEW_HINTS:
parts.append(BACK_VIEW_HINTS[category_name])
elif view_name == "侧面图" and category_name in SIDE_VIEW_HINTS:
parts.append(SIDE_VIEW_HINTS[category_name])
# 13. 质量后缀
parts.append(quality_suffix) parts.append(quality_suffix)
return ", ".join(parts) return ", ".join(parts)

View File

@@ -13,3 +13,4 @@ Pillow==10.2.0
pydantic[email]==2.6.1 pydantic[email]==2.6.1
pydantic-settings==2.1.0 pydantic-settings==2.1.0
httpx==0.27.0 httpx==0.27.0
PyJWT==2.8.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

441
backend/uvicorn.log Normal file
View File

@@ -0,0 +1,441 @@
INFO: Will watch for changes in these directories: ['/Users/changyoutongxue/开发-qoder/YuShiSheJi/backend']
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [63566] using StatReload
INFO: Started server process [63686]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:62589 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:62593 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:62599 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:62605 - "GET /api/admin/dashboard HTTP/1.1" 200 OK
INFO: 127.0.0.1:62608 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:62786 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:62786 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:62835 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:62838 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:62913 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:62982 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:63026 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:63030 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:63052 - "PUT /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:63055 - "GET /api/admin/configs HTTP/1.1" 200 OK
INFO: 127.0.0.1:63063 - "GET /api/admin/users?page=1&page_size=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63072 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:63075 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:63079 - "GET /api/designs?page=1&page_size=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63081 - "GET /uploads/designs/26.png HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63086 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63088 - "GET /uploads/videos/f2e6c3eac03b485f8115e72a1d33b491.mp4 HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63091 - "GET /uploads/models/506909359d6c45fe9e43108ee7765a9a.glb HTTP/1.1" 304 Not Modified
可灵视频任务提交失败: status=401, body={"code":1002,"message":"Auth failed","request_id":"CjyhVGikJMQAAAAAGOrdCQ","data":null}
视频生成失败: Client error '401 ' for url 'https://api.klingai.com/v1/videos/multi-image2video'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
INFO: 127.0.0.1:63096 - "POST /api/designs/27/generate-video?force=true HTTP/1.1" 500 Internal Server Error
WARNING: StatReload detected changes in 'app/services/ai_video_generator_kling.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [63686]
👋 应用已关闭
INFO: Started server process [64167]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:63400 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:63405 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:63407 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63409 - "GET /uploads/videos/f2e6c3eac03b485f8115e72a1d33b491.mp4 HTTP/1.1" 404 Not Found
可灵视频任务提交失败: status=401, body={"code":1002,"message":"Auth failed","request_id":"CjMkpGikJkcAAAAAGOrbdA","data":null}
视频生成失败: Client error '401 ' for url 'https://api.klingai.com/v1/videos/multi-image2video'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
INFO: 127.0.0.1:63412 - "POST /api/designs/27/generate-video?force=true HTTP/1.1" 500 Internal Server Error
INFO: 127.0.0.1:63418 - "GET /uploads/videos/f2e6c3eac03b485f8115e72a1d33b491.mp4 HTTP/1.1" 404 Not Found
WARNING: StatReload detected changes in 'app/services/ai_video_generator_kling.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [64167]
👋 应用已关闭
INFO: Started server process [64861]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:63556 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:63563 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:63565 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63567 - "GET /uploads/videos/f2e6c3eac03b485f8115e72a1d33b491.mp4 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:63569 - "GET /uploads/models/506909359d6c45fe9e43108ee7765a9a.glb HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63575 - "POST /api/designs/27/generate-video?force=true HTTP/1.1" 200 OK
INFO: 127.0.0.1:63785 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 200 OK
INFO: 127.0.0.1:63846 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/routers/designs.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [64861]
👋 应用已关闭
INFO: Started server process [65045]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:64149 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 304 Not Modified
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [65045]
👋 应用已关闭
INFO: Started server process [69988]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [69988]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [69995]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [69995]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [70000]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/ai_3d_generator.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [70000]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [71187]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/ai_video_generator_kling.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [71187]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [71203]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/ai_3d_generator.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [71203]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [71899]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:52861 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:52869 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:52871 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52874 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:52943 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:52951 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:52953 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53198 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53206 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:53208 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53211 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:53280 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53288 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:53290 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53426 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53434 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:53436 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53602 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53611 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:53613 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53616 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:55190 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:55199 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:55201 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55204 - "GET /uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4 HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:55234 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:55241 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:55243 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56832 - "GET /uploads/models/506909359d6c45fe9e43108ee7765a9a.glb HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:56840 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:56846 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:56849 - "GET /api/designs/27 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56856 - "GET /api/designs?page=1&page_size=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56859 - "GET /uploads/designs/26.png HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:56865 - "DELETE /api/designs/26 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56868 - "GET /api/designs?page=1&page_size=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56902 - "GET /api/designs/24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56925 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:56927 - "GET /api/designs?page=1&page_size=20 HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [71899]
👋 应用已关闭
INFO: Started server process [75417]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/prompt_builder.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [75417]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [76192]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [76192]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [76199]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:63820 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:63827 - "GET /api/designs?page=1&page_size=20 HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/services/prompt_builder.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [76199]
👋 应用已关闭
INFO: Started server process [83069]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/prompt_builder.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [83069]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [903]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:53852 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:53856 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53862 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:53865 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:53866 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:53870 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:53872 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:53877 - "GET /api/categories/3/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:53878 - "GET /api/categories/3/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:53883 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:53885 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:53890 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:54023 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54021 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54029 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54027 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54050 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54048 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54076 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54075 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54092 - "GET /api/categories/6/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54091 - "GET /api/categories/6/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54098 - "GET /api/categories/7/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54096 - "GET /api/categories/7/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54104 - "GET /api/categories/8/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54102 - "GET /api/categories/8/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54110 - "GET /api/categories/9/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54109 - "GET /api/categories/9/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54117 - "GET /api/categories/10/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54115 - "GET /api/categories/10/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54123 - "GET /api/categories/11/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54121 - "GET /api/categories/11/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54131 - "GET /api/categories/12/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54129 - "GET /api/categories/12/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54134 - "GET /api/categories/13/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54140 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:54139 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:54330 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:54332 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:54842 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:56316 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57491 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57495 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57499 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57627 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57625 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:57636 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:57664 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58159 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:58162 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58168 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58174 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58172 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58181 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58182 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58189 - "GET /api/categories/3/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58188 - "GET /api/categories/3/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58243 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58249 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58250 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58257 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58256 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58266 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58264 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58272 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58271 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58276 - "GET /api/categories/6/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58278 - "GET /api/categories/6/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58284 - "GET /api/categories/11/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58283 - "GET /api/categories/11/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58287 - "GET /api/categories/13/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58295 - "GET /api/categories/12/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58296 - "GET /api/categories/12/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58305 - "GET /api/categories/8/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58304 - "GET /api/categories/8/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58314 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58313 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58320 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58319 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58325 - "GET /api/categories/11/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58326 - "GET /api/categories/11/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58332 - "GET /api/categories/12/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58331 - "GET /api/categories/12/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58335 - "GET /api/categories/13/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58341 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58340 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58408 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58417 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58419 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58504 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58503 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58509 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58511 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58517 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58519 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58527 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58529 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58535 - "GET /api/categories/3/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58534 - "GET /api/categories/3/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58550 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58551 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58638 - "GET /api/categories/13/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58644 - "GET /api/categories/12/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58643 - "GET /api/categories/12/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58649 - "GET /api/categories/11/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58651 - "GET /api/categories/11/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58658 - "GET /api/categories/10/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58659 - "GET /api/categories/10/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58665 - "GET /api/categories/9/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58664 - "GET /api/categories/9/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58670 - "GET /api/categories/8/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58671 - "GET /api/categories/8/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58677 - "GET /api/categories/7/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58678 - "GET /api/categories/7/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58684 - "GET /api/categories/6/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58686 - "GET /api/categories/6/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58692 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58691 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/schemas/design.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [903]
👋 应用已关闭
INFO: Started server process [3994]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:58700 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58699 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58708 - "GET /api/categories/3/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58709 - "GET /api/categories/3/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58715 - "GET /api/categories/4/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58716 - "GET /api/categories/4/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58721 - "GET /api/categories/5/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58722 - "GET /api/categories/5/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58727 - "GET /api/categories/6/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58728 - "GET /api/categories/6/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58734 - "GET /api/categories/7/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58733 - "GET /api/categories/7/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58739 - "GET /api/categories/8/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58740 - "GET /api/categories/8/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58746 - "GET /api/categories/9/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58747 - "GET /api/categories/9/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58751 - "GET /api/categories/10/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58753 - "GET /api/categories/10/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58758 - "GET /api/categories/9/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58759 - "GET /api/categories/9/colors HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [3994]
👋 应用已关闭
INFO: Started server process [4005]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'app/services/design_service.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [4005]
✅ 上传目录已准备: uploads
👋 应用已关闭
INFO: Started server process [4016]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:58910 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58916 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58918 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58925 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:58930 - "GET /api/categories/2/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:58931 - "GET /api/categories/2/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58937 - "GET /api/categories/3/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:58936 - "GET /api/categories/3/sub-types HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'app/schemas/design.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [4016]
👋 应用已关闭
INFO: Started server process [4272]
INFO: Waiting for application startup.
INFO: Application startup complete.
✅ 上传目录已准备: uploads
INFO: 127.0.0.1:59022 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:59025 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:59072 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:59076 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:59082 - "GET /api/categories HTTP/1.1" 200 OK
INFO: 127.0.0.1:59089 - "GET /api/categories/1/sub-types HTTP/1.1" 200 OK
INFO: 127.0.0.1:59090 - "GET /api/categories/1/colors HTTP/1.1" 200 OK
INFO: 127.0.0.1:59224 - "POST /api/designs/generate HTTP/1.1" 200 OK
INFO: 127.0.0.1:59331 - "GET /uploads/designs/28_0_8f1d51f1.png HTTP/1.1" 200 OK
INFO: 127.0.0.1:59345 - "GET /uploads/designs/28_2_afd0819c.png HTTP/1.1" 200 OK
INFO: 127.0.0.1:59348 - "GET /uploads/designs/28_3_597f23e1.png HTTP/1.1" 200 OK
INFO: 127.0.0.1:59352 - "GET /uploads/designs/28_1_652b3742.png HTTP/1.1" 200 OK
INFO: 127.0.0.1:50695 - "GET /projects.php HTTP/1.1" 404 Not Found
INFO: ('127.0.0.1', 55036) - "WebSocket /CLodopfuncs.js" 403
INFO: connection rejected (403 Forbidden)
INFO: connection closed
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [4272]
👋 应用已关闭
INFO: Stopping reloader process [63566]

View File

@@ -0,0 +1,539 @@
# 多视角图片一致性 & 3D/视频生成 技术文档
## 一、系统概述
玉宗珠宝 AI 设计系统支持根据用户选择的品类、颜色、工艺等参数,自动生成多视角设计图(效果图、正面图、侧面图、背面图),并基于这些图片进一步生成 3D 模型和 360 度旋转展示视频。
**核心挑战**AI 生图模型(如 Seedream 5.0 lite的"参考图"参数本质上是**风格参考**,而非"同一物体不同角度"的语义理解。因此需要通过多层提示词策略来约束 AI 保持多视角图片的一致性。
---
## 二、整体架构
```
用户提交设计请求
┌──────────────────────────────────────────────────────────────┐
│ design_service.py - create_design_async() │
│ │
│ 1. 获取品类视角列表prompt_builder.get_views_for_category
│ 2. 循环每个视角: │
│ ├── prompt_builder.build_prompt() → 构建英文提示词 │
│ ├── 添加一致性前缀(相机位置描述) │
│ ├── ai_generator.generate_image() → 调用AI生图API │
│ ├── 下载图片到本地持久化 │
│ └── 创建 DesignImage 记录 │
│ 3. 第一张图的 seed/URL 传给后续视角复用 │
└──────────────────────────────────────────────────────────────┘
▼ 用户手动触发
┌──────────────┐ ┌──────────────────┐
│ 生成3D模型 │ │ 生成旋转视频 │
│ ai_3d_ │ │ ai_video_ │
│ generator.py │ │ generator_ │
│ │ │ kling.py │
│ 腾讯混元3D │ │ 可灵AI多图参考 │
│ 专业版 │ │ 生视频 │
└──────────────┘ └──────────────────┘
```
---
## 三、涉及文件清单
| 文件路径 | 职责 |
|---------|------|
| `backend/app/services/design_service.py` | 设计生成主流程,多视角循环、一致性策略协调 |
| `backend/app/services/prompt_builder.py` | 提示词构建器,品类视角配置、品类专属背面/侧面描述 |
| `backend/app/services/ai_generator.py` | AI 生图服务,支持 SiliconFlow Kolors 和 Seedream 双模型 |
| `backend/app/services/ai_3d_generator.py` | 腾讯混元3D 模型生成服务多视角→3D |
| `backend/app/services/ai_video_generator_kling.py` | 可灵 AI 多图参考生视频服务 |
| `backend/app/routers/designs.py` | REST API 路由,收集多视角图片调用 3D/视频服务 |
| `backend/app/models/design_image.py` | DesignImage 数据模型 |
| `backend/app/services/config_service.py` | 配置服务AI Key 等从数据库优先读取 |
| `init_data.sql` | 数据库初始化,包含 prompt_mappings 视角映射 |
| `frontend/src/components/DesignPreview.vue` | 前端多视角展示与下载 |
---
## 四、品类视角配置
不同品类生成的视角数量和种类不同,定义在 `prompt_builder.py``CATEGORY_VIEWS` 字典中:
| 品类 | 视角列表 | 视角数 |
|------|---------|-------|
| 牌子 | 效果图、正面图、背面图 | 3 |
| 珠子 | 效果图、正面图 | 2 |
| 手把件 | 效果图、正面图、侧面图、背面图 | 4 |
| 雕刻件 | 效果图、正面图、侧面图、背面图 | 4 |
| 摆件 | 效果图、正面图、侧面图、背面图 | 4 |
| 随形 | 效果图、正面图、侧面图、背面图 | 4 |
| 手镯 | 效果图、正面图、侧面图 | 3 |
| 戒指 | 效果图、正面图、侧面图 | 3 |
| 耳钉/耳饰/手链/项链/表带 | 效果图、正面图 | 2 |
**生成顺序**:严格按列表顺序,第一张始终是"效果图"45度英雄镜头后续视角依次生成。
---
## 五、多视角图片一致性策略(四层保障)
### 第一层AI 模型级参考机制
位置:`design_service.py``_generate_ai_images()` 第 134-183 行
**两种模型的不同一致性方式:**
| 模型 | 一致性机制 | 参数 | 说明 |
|------|-----------|------|------|
| SiliconFlow Kolors | seed 复用 | `seed` | 第一张图返回的 seed 值传给后续视角,确保随机过程相同 |
| Seedream 5.0 lite | 参考图 | `ref_image_url``image: [url]` | 第一张图的远程 URL 作为风格参考传入后续视角 |
**代码流程:**
```python
shared_seed = None # Kolors 用: 第一张图的 seed
first_remote_url = None # Seedream 用: 第一张图的远程 URL
for idx, view_name in enumerate(views):
# 后续视角传入 seed 或参考图 URL
ref_url = first_remote_url if idx > 0 else None
remote_url, returned_seed = await ai_generator.generate_image(
final_prompt, model, seed=shared_seed, ref_image_url=ref_url
)
# 第一张图保存信息供后续复用
if idx == 0:
first_remote_url = remote_url
if returned_seed is not None:
shared_seed = returned_seed
```
**关键限制**Seedream 的 `image` 参数本质上是**风格参考**AI 不能直接理解为"同一个物体的不同角度",所以需要第二、三、四层提示词来补充约束。
---
### 第二层:一致性前缀(相机位置描述)
位置:`design_service.py` 第 159-172 行
从第 2 张图起idx > 0在提示词**最前面**拼接一段一致性约束前缀,按视角名称定制相机位置描述:
```python
view_angle_map = {
"正面图": "moving the camera to face the object directly from the front",
"侧面图": "moving the camera 90 degrees to the left side of the object",
"背面图": "moving the camera 180 degrees to see the reverse/back side of the object",
}
consistency_prefix = (
f"Photograph the EXACT SAME jade object from the reference image, {angle_desc}. "
"The object does NOT move or change - only the camera position changes. "
"The shape, size, color, material texture, and all physical features must remain IDENTICAL. "
)
final_prompt = consistency_prefix + prompt_text
```
**设计思路**
- 强调"EXACT SAME"和"IDENTICAL"强制 AI 保持物体一致
- 用"相机移动"的摄影术语描述视角变化,而非"生成另一张图"
- 明确"物体不动,只有相机位置改变"
---
### 第三层:品类专属视角描述
位置:`prompt_builder.py` 第 35-65 行 & 第 197-201 行
不同品类的背面特征差异极大,需要品类专属描述来避免 AI 产生错误理解。
**背面描述BACK_VIEW_HINTS**
| 品类 | 背面特征 | 核心约束 |
|------|---------|---------|
| **牌子** | 光滑平面,可能有题款,**绝对没有雕刻图案** | "Do NOT mirror or duplicate the front carving on the back" |
| **手把件** | 同一件三维雕塑的背面,自然延续 | "natural continuation of the carving from the rear angle" |
| **雕刻件** | 三维雕塑的背面,雕刻自然延续 | "not a separate or different design" |
| **摆件** | 三维陈列品的背面 | "form and carving continue naturally" |
| **随形** | 天然石料表面的背面 | "may show more of the raw jade texture" |
**侧面描述SIDE_VIEW_HINTS**
| 品类 | 侧面特征 |
|------|---------|
| **牌子** | 薄片轮廓5-10mm 厚度 |
**解决的核心问题**
牌子品类出现"双面佛"效果 —— 背面也有雕刻图案。真实的玉雕牌子背面是光滑平面。通过 `BACK_VIEW_HINTS["牌子"]` 强制约束 AI
> "The reverse/back side of a jade pendant plaque is traditionally a smooth, flat, polished surface. It may have a brief inscription or seal mark, but it must NOT have any carved figure, face, or decorative relief pattern."
**应用时机**:在 `build_prompt()` 构建提示词时,根据视角名称和品类名称自动追加:
```python
if view_name == "背面图" and category_name in BACK_VIEW_HINTS:
parts.append(BACK_VIEW_HINTS[category_name])
elif view_name == "侧面图" and category_name in SIDE_VIEW_HINTS:
parts.append(SIDE_VIEW_HINTS[category_name])
```
---
### 第四层:数据库视角映射提示词
位置:`prompt_mappings`mapping_type='view'
每个视角有标准化的英文摄影术语描述,存储在数据库中,支持后台热更新:
| 视角 | 英文描述 |
|------|---------|
| 效果图 | `three-quarter view at 45-degree angle, hero shot showing the complete jade artwork with depth and dimension, single object on pure white background` |
| 正面图 | `front view, camera positioned directly in front of the object facing it straight-on, showing only the front carved surface, single object on pure white background` |
| 侧面图 | `side view, camera positioned at exactly 90 degrees to the left of the object, showing the edge profile and thickness, single object on pure white background` |
| 背面图 | `back view, camera positioned directly behind the object at 180 degrees, showing the reverse side, single object on pure white background` |
**设计特点**
- 使用纯**摄影术语**camera positioned, facing, straight-on
- 精确的**角度度数**45°、90°、180°
- 统一的**纯白背景**要求pure white background
- 强调**单一物体**single object避免生成多个物体
---
### 四层策略的组合效果
以"牌子"品类的背面图为例,最终提示词结构:
```
[第二层:一致性前缀]
Photograph the EXACT SAME jade object from the reference image,
moving the camera 180 degrees to see the reverse/back side of the object.
The object does NOT move or change - only the camera position changes.
The shape, size, color, material texture, and all physical features must remain IDENTICAL.
[常规提示词内容]
Chinese Hetian nephrite jade pendant plaque,
natural Hetian nephrite jade with warm luster,
featuring dragon-phoenix auspicious pattern,
relief carving with raised design emerging from surface,
...
[第四层:视角映射]
back view, camera positioned directly behind the object at 180 degrees,
showing the reverse side, single object on pure white background,
[第三层:品类专属背面描述]
IMPORTANT: The reverse/back side of a jade pendant plaque is traditionally
a smooth, flat, polished surface. It may have a brief inscription or seal mark,
but it must NOT have any carved figure, face, or decorative relief pattern.
The back is plain and minimalist. Do NOT mirror or duplicate the front carving on the back.
[质量后缀]
professional jewelry product photography, studio lighting setup,
pure white background, ultra-detailed, sharp focus, 8K resolution,
photorealistic rendering, high-end commercial quality
```
加上 [第一层] Seedream API 的 `image` 参数传入第一张效果图作为风格参考。
---
## 六、3D 模型生成
### 6.1 技术方案
- **服务**腾讯混元3D 专业版Hunyuan3D Pro
- **API 认证**TC3-HMAC-SHA256 签名
- **输入**:主图 + 多视角图片
- **输出**ZIP 包(包含 GLB/OBJ/FBX/STL 多种格式)
### 6.2 多视角图片收集
位置:`designs.py``generate_3d_model()` 第 329-339 行
```python
# 收集所有多视角图片 URL 和视角名称
image_urls = []
view_names = []
if design.images:
for img in sorted(design.images, key=lambda x: x.sort_order):
if img.image_url:
image_urls.append(img.image_url)
view_names.append(img.view_name or "效果图")
```
### 6.3 视角映射
位置:`ai_3d_generator.py` 第 121-126 行
混元3D API 的 `MultiViewImages` 参数支持指定每张图的视角类型:
```python
_VIEW_NAME_MAP = {
"侧面图": "left",
"背面图": "back",
}
```
**主图选择逻辑**:正面图优先 → 效果图次之 → 否则取第一张
```python
for url, name in zip(image_urls, view_names):
if name == "正面图" and not main_url:
main_url = url # 正面图作为主图
elif name in _VIEW_NAME_MAP:
multi_views.append({ # 侧面/背面作为辅助视角
"ViewType": _VIEW_NAME_MAP[name],
"ViewImageUrl": url
})
elif not main_url:
main_url = url # 效果图或其他作为备选主图
```
### 6.4 URL 转换
位置:`ai_3d_generator.py``_to_public_url()` 第 129-138 行
第三方 API 需要外网可访问的完整 URL本地存储路径/uploads/xxx需要拼接域名
```python
def _to_public_url(url: str) -> str:
if url and url.startswith("/uploads/"):
base_domain = get_config_value("SITE_DOMAIN", "http://c02.wsg.plus")
return f"{base_domain}{url}"
return url
```
> **注意**:服务器仅支持 HTTP不支持 HTTPSHTTPS 会 ERR_CONNECTION_REFUSED
### 6.5 生成流程
```
收集多视角图片 → 转换为外网URL → 选择主图/辅助视角
→ 提交 SubmitHunyuanTo3DProJob (ImageUrl + MultiViewImages)
→ 轮询 QueryHunyuanTo3DProJob (每5秒最多120次≈10分钟)
→ 下载 ZIP 解压提取 .glb → 保存到 /uploads/models/
```
### 6.6 结果存储
3D 模型 URL 保存到**第一张 DesignImage** 的 `model_3d_url` 字段。同时保留 ZIP 包(包含 GLB/OBJ/FBX/STL供用户完整下载。
---
## 七、视频生成
### 7.1 技术方案
- **服务**:可灵 AIKling多图参考生视频
- **API 端点**`https://api-beijing.klingai.com/v1/videos/multi-image2video`
- **API 认证**JWTHS256Access Key + Secret Key 签发30 分钟有效期)
- **输入**1-4 张参考图片 + 提示词
- **输出**MP4 视频
### 7.2 多视角图片收集
位置:`designs.py``generate_video()` 第 273-280 行
```python
# 收集多视角图片 URL
image_urls = []
if design.images:
for img in sorted(design.images, key=lambda x: x.sort_order):
if img.image_url:
image_urls.append(img.image_url)
if not image_urls and design.image_url:
image_urls.append(design.image_url) # 兼容旧数据:只有 design.image_url
```
### 7.3 核心优势
可灵 AI 的多图参考生视频 API **原生支持传入 1-4 张参考图**AI 会理解为同一物体的多个角度参考,生成单品旋转展示视频。这是选择可灵 AI 的核心原因。
### 7.4 视频提示词
默认提示词(可通过数据库 `VIDEO_PROMPT` 配置覆盖):
```
精美玉雕工艺品在专业珠宝摄影棚内展示,
纯白色背景,柔和的珠宝摄影灯光,
玉石作品放在旋转展台上缓慢平稳地旋转360度
展示正面、侧面、背面全貌,
展现玉石温润的质感、细腻的雕刻纹理和通透的光泽,
电影级画质,微距细节感,平稳流畅的转台旋转
```
### 7.5 API 参数
```python
payload = {
"model_name": "kling-v1-6", # 可灵 v1.6 模型
"image_list": image_list, # [{"image": "url"}, ...]最多4张
"prompt": prompt, # 视频提示词
"mode": "pro", # 高品质模式Pro
"duration": "5", # 视频时长5秒或10秒
"aspect_ratio": "1:1", # 正方形比例
}
```
### 7.6 生成流程
```
收集多视角图片(最多4张) → 转换为外网URL → 构建image_list
→ 提交 POST /v1/videos/multi-image2video
→ 轮询 GET /v1/videos/multi-image2video/{task_id} (每5秒最多120次)
→ 下载 MP4 → 保存到 /uploads/videos/
```
### 7.7 JWT 认证细节
```python
headers = {"alg": "HS256", "typ": "JWT"}
payload = {
"iss": access_key, # 签发者 = Access Key
"exp": now + 1800, # 30 分钟过期
"nbf": now - 5, # 允许 5 秒时钟偏差
"iat": now, # 签发时间(必须包含!)
}
token = jwt.encode(payload, secret_key, algorithm="HS256", headers=headers)
```
> **踩坑记录**`headers` 中的 `typ` 和 `payload` 中的 `iat` 字段缺一不可,否则返回 401 认证失败。
---
## 八、图片持久化存储
### 8.1 问题背景
第三方 AI 服务(火山引擎 Seedream生成的图片 URL 是**临时链接**,一段时间后会过期失效(返回 NoSuchKey 错误)。
### 8.2 解决方案
生成图片后立即下载到本地 `uploads/designs/` 目录持久化存储:
```python
async def _download_image_to_local(remote_url, design_id, idx):
filename = f"{design_id}_{idx}_{uuid.uuid4().hex[:8]}.png"
local_path = os.path.join(designs_dir, filename)
# 下载远程图片
async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client:
resp = await client.get(remote_url)
with open(local_path, "wb") as f:
f.write(resp.content)
return f"/uploads/designs/{filename}"
```
### 8.3 URL 格式
- **本地存储路径**`/uploads/designs/{design_id}_{idx}_{随机8位}.png`
- **外网访问 URL**`http://c02.wsg.plus/uploads/designs/{design_id}_{idx}_{随机8位}.png`
---
## 九、前端展示与下载
### 9.1 多视角切换
前端 `DesignPreview.vue` 通过 Tab 栏展示多视角图片,用户点击不同视角 Tab 切换显示。
### 9.2 图片下载
前端直接通过 `fetch()` 下载当前视角图片(不走后端 download 接口),确保下载的是当前展示的视角图片:
```typescript
const handleDownload = async () => {
const imgUrl = currentImageUrl.value // 当前视角的图片URL
const res = await fetch(imgUrl)
const blob = await res.blob()
// 创建 <a> 标签触发下载
_downloadBlob(blob, downloadFilename.value)
}
```
> **踩坑记录**:之前使用后端 `/api/designs/{id}/download` 接口,该接口只返回 `design.image_url`(第一张效果图),导致所有视角下载的都是效果图。
---
## 十、数据模型
### DesignImage 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键 |
| design_id | Integer | 关联设计 ID |
| view_name | String(20) | 视角名称:效果图/正面图/侧面图/背面图 |
| image_url | String(500) | 图片 URL本地路径或远程链接 |
| model_used | String(50) | AI 模型标识seedream-5.0 / kolors |
| prompt_used | Text | 实际使用的英文提示词 |
| sort_order | Integer | 视角排序0=效果图, 1=正面图, 2=侧面图, 3=背面图) |
| model_3d_url | String(500) | 对应的 3D 模型 URL |
### Design 表(关键字段)
| 字段 | 说明 |
|------|------|
| image_url | 第一张效果图 URL兼容旧逻辑 |
| video_url | 生成的视频 URL |
| images | 关联的 DesignImage 列表 |
---
## 十一、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/designs/generate` | 创建设计AI 多视角生图 |
| GET | `/api/designs` | 分页查询设计历史 |
| GET | `/api/designs/{id}` | 设计详情(含 images 数组) |
| DELETE | `/api/designs/{id}` | 删除设计及所有图片 |
| GET | `/api/designs/{id}/download` | 下载效果图(兼容旧逻辑) |
| POST | `/api/designs/{id}/generate-video` | 生成旋转视频 |
| POST | `/api/designs/{id}/generate-3d` | 生成 3D 模型 |
---
## 十二、成本估算
| 服务 | 单价 | 说明 |
|------|------|------|
| Seedream 5.0 lite 生图 | ≈ 0.22 元/张 | 4 个视角 ≈ 0.88 元 |
| 腾讯混元3D 专业版 | 按量计费 | 单次 3D 生成 |
| 可灵 AI 视频 Pro 5s | ≈ 7 元/次 | Pro 模式5 秒时长 |
**单次完整设计成本**4视角品类≈ 0.88(生图)+ 混元3D + 7视频
---
## 十三、已知问题与踩坑记录
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 背面图出现"双面佛" | Seedream 参考图是风格参考AI 把正面图案复制到背面 | 品类专属 BACK_VIEW_HINTS + 一致性前缀 |
| 图片过期 404 (NoSuchKey) | 火山引擎临时链接过期 | 生成后立即下载到本地持久化 |
| HTTPS 连接被拒绝 | 服务器未配置 HTTPS | `_to_public_url` 使用 `http://` |
| 下载所有视角都是效果图 | 后端 download 接口只返回 image_url | 前端改用 fetch 直接下载当前视角 URL |
| 可灵 AI 401 认证失败 | JWT 缺少 typ header 和 iat 字段 | 显式传入 headers={typ:JWT} 和 iat |
| 混元3D 字段名错误 | ViewImage 应为 ViewImageUrl | 使用正确的 ViewImageUrl 字段 |
| 前端更新不生效 | 只上传了 .vue 源文件 | 前端是编译型项目,需上传编译后的 dist/ |
---
## 十四、配置项
以下配置通过数据库 `system_configs` 表管理,支持后台热更新:
| 配置键 | 说明 | 默认值 |
|-------|------|--------|
| `VOLCENGINE_API_KEY` | 火山引擎 Seedream API Key | - |
| `SILICONFLOW_API_KEY` | SiliconFlow Kolors API Key | - |
| `AI_IMAGE_MODEL` | 生图模型选择 | `flux-dev` |
| `AI_IMAGE_SIZE` | 图片尺寸 | `1024` |
| `TENCENT_SECRET_ID` | 腾讯云 SecretId混元3D | - |
| `TENCENT_SECRET_KEY` | 腾讯云 SecretKey混元3D | - |
| `KLING_ACCESS_KEY` | 可灵 AI Access Key | - |
| `KLING_SECRET_KEY` | 可灵 AI Secret Key | - |
| `SITE_DOMAIN` | 站点域名外网URL拼接 | `http://c02.wsg.plus` |
| `VIDEO_PROMPT` | 视频生成提示词 | 内置默认值 |
| `VIDEO_DURATION` | 视频时长(秒) | `5` |

View File

@@ -52,6 +52,7 @@ export interface DesignListResponse {
export interface GenerateDesignParams { export interface GenerateDesignParams {
category_id: number category_id: number
sub_type_id?: number sub_type_id?: number
sub_type_name?: string
color_id?: number color_id?: number
prompt: string prompt: string
carving_technique?: string carving_technique?: string

View File

@@ -1,25 +1,67 @@
// 玉宗品牌色 - 中式雅致风格 // 玉宗品牌色 - 中式雅致风格
$primary-color: #5B7E6B; // 青玉色 - 主色调 $primary-color: #5B7E6B; // 青玉色 - 主色调
$primary-light: #8BAF9C; // 浅青 $primary-light: #8BAF9C; // 浅青
$primary-lighter: #D4E5DB; // 极浅青
$primary-dark: #3D5A4A; // 深青 $primary-dark: #3D5A4A; // 深青
$secondary-color: #C4A86C; // 金缕色 - 辅助色 $secondary-color: #C4A86C; // 金缕色 - 辅助色
$secondary-light: #E0D1A8; // 浅金
$bg-color: #FAF8F5; // 暖白底色 $bg-color: #FAF8F5; // 暖白底色
$bg-dark: #F0EDE8; // 深一级底色 $bg-dark: #F0EDE8; // 深一级底色
$bg-card: #FFFFFF; // 卡片底色
$text-primary: #2C2C2C; // 主文字 $text-primary: #2C2C2C; // 主文字
$text-secondary: #6B6B6B; // 次要文字 $text-secondary: #6B6B6B; // 次要文字
$text-light: #999999; // 辅助文字 $text-light: #999999; // 辅助文字
$text-placeholder: #BFBFBF; // 占位文字
$border-color: #E8E4DF; // 边框色 $border-color: #E8E4DF; // 边框色
$border-light: #F0EDE8; // 浅边框
$ink-color: #1A1A2E; // 墨色 $ink-color: #1A1A2E; // 墨色
// 圆角
$radius-sm: 8px;
$radius: 12px;
$radius-lg: 16px;
$radius-xl: 20px;
$radius-pill: 999px;
// 阴影
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
$shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.08);
// Element Plus 主题覆盖 // Element Plus 主题覆盖
:root { :root {
// 颜色
--el-color-primary: #{$primary-color}; --el-color-primary: #{$primary-color};
--el-color-primary-light-3: #{$primary-light}; --el-color-primary-light-3: #{$primary-light};
--el-color-primary-light-5: #a8c7b5;
--el-color-primary-light-7: #{$primary-lighter};
--el-color-primary-light-8: #dfeee5;
--el-color-primary-light-9: #eaf4ee;
--el-color-primary-dark-2: #{$primary-dark}; --el-color-primary-dark-2: #{$primary-dark};
--el-bg-color: #{$bg-color}; --el-bg-color: #{$bg-color};
--el-bg-color-overlay: #{$bg-card};
--el-border-color: #{$border-color}; --el-border-color: #{$border-color};
--el-border-color-light: #{$border-light};
--el-text-color-primary: #{$text-primary}; --el-text-color-primary: #{$text-primary};
--el-text-color-regular: #{$text-secondary}; --el-text-color-regular: #{$text-secondary};
--el-text-color-secondary: #{$text-light};
--el-text-color-placeholder: #{$text-placeholder};
--el-fill-color-light: #{$bg-color};
--el-fill-color-lighter: #{$bg-color};
// 圆角
--el-border-radius-base: #{$radius-sm};
--el-border-radius-small: 6px;
--el-border-radius-round: #{$radius-pill};
// 字体
--el-font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', -apple-system, sans-serif;
--el-font-size-base: 14px;
// 阴影
--el-box-shadow: #{$shadow};
--el-box-shadow-light: #{$shadow-sm};
--el-box-shadow-lighter: 0 1px 4px rgba(0, 0, 0, 0.03);
} }
// 全局基础样式 // 全局基础样式
@@ -30,7 +72,7 @@ $ink-color: #1A1A2E; // 墨色
} }
body { body {
font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', -apple-system, sans-serif;
background-color: $bg-color; background-color: $bg-color;
color: $text-primary; color: $text-primary;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@@ -39,6 +81,10 @@ body {
// 滚动条美化 // 滚动条美化
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: $border-color; background: $border-color;
@@ -47,3 +93,44 @@ body {
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: $text-light; background: $text-light;
} }
// Element Plus 组件全局样式增强
.el-button--primary {
background-color: $primary-color !important;
border-color: $primary-color !important;
border-radius: $radius-sm !important;
&:hover, &:focus {
background-color: $primary-dark !important;
border-color: $primary-dark !important;
}
}
.el-input__wrapper {
border-radius: $radius-sm !important;
transition: all 0.25s ease !important;
}
.el-textarea__inner {
border-radius: $radius !important;
}
.el-card {
border-radius: $radius !important;
border: 1px solid $border-light !important;
box-shadow: $shadow-sm !important;
}
.el-dialog {
border-radius: $radius-lg !important;
}
.el-message-box {
border-radius: $radius !important;
}
.el-dropdown-menu {
border-radius: $radius-sm !important;
border: 1px solid $border-light !important;
box-shadow: $shadow !important;
}

View File

@@ -34,7 +34,7 @@
</router-link> </router-link>
</nav> </nav>
<div class="sidebar-footer"> <div class="sidebar-footer">
<router-link to="/" class="back-link"> <router-link to="/design" class="back-link">
<el-icon><Back /></el-icon> <el-icon><Back /></el-icon>
<span>返回前台</span> <span>返回前台</span>
</router-link> </router-link>

View File

@@ -2,24 +2,24 @@
<header class="app-header"> <header class="app-header">
<div class="header-left"> <div class="header-left">
<router-link to="/" class="logo"> <router-link to="/" class="logo">
<span class="logo-text">玉宗</span> <span class="logo-text"> </span>
<span class="logo-sub">YUZONG JEWELRY</span>
</router-link> </router-link>
</div> </div>
<nav class="header-nav"> <nav class="header-nav">
<router-link to="/" class="nav-link">设计</router-link>
<router-link to="/generate" class="nav-link">生成</router-link>
<router-link to="/admin" class="nav-link admin-link" v-if="isAdmin">管理后台</router-link>
</nav> </nav>
<div class="header-right"> <div class="header-right">
<template v-if="isLoggedIn"> <template v-if="isLoggedIn">
<el-dropdown trigger="click" @command="handleCommand"> <el-dropdown trigger="click" @command="handleCommand">
<span class="user-dropdown"> <span class="user-dropdown">
<span class="user-avatar">{{ avatarChar }}</span>
<span class="user-nickname">{{ userNickname }}</span> <span class="user-nickname">{{ userNickname }}</span>
<el-icon><ArrowDown /></el-icon> <el-icon><ArrowDown /></el-icon>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item> <el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item command="admin" v-if="isAdmin">管理后台</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item> <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@@ -45,10 +45,13 @@ const userStore = useUserStore()
const isLoggedIn = computed(() => !!userStore.token) const isLoggedIn = computed(() => !!userStore.token)
const userNickname = computed(() => userStore.userInfo?.nickname || '用户') const userNickname = computed(() => userStore.userInfo?.nickname || '用户')
const isAdmin = computed(() => !!userStore.userInfo?.is_admin) const isAdmin = computed(() => !!userStore.userInfo?.is_admin)
const avatarChar = computed(() => (userStore.userInfo?.nickname || '用')[0])
const handleCommand = (command: string) => { const handleCommand = (command: string) => {
if (command === 'user') { if (command === 'user') {
router.push('/user') router.push('/user')
} else if (command === 'admin') {
router.push('/admin')
} else if (command === 'logout') { } else if (command === 'logout') {
userStore.logout() userStore.logout()
router.push('/login') router.push('/login')
@@ -57,49 +60,82 @@ const handleCommand = (command: string) => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B;
$primary-dark: #3D5A4A;
$text-secondary: #6B6B6B;
$text-primary: #2C2C2C;
$border-color: #E8E4DF;
$bg-color: #FAF8F5;
.app-header { .app-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 60px; height: 64px;
padding: 0 32px; padding: 0 32px;
background-color: #fff; background-color: #fff;
border-bottom: 1px solid #E8E4DF; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.04);
position: sticky;
top: 0;
z-index: 100;
} }
.header-left { .header-left {
.logo { .logo {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none; text-decoration: none;
gap: 2px;
} }
.logo-text { .logo-text {
font-size: 24px; font-size: 22px;
font-weight: 600; font-weight: 700;
color: #5B7E6B; color: $primary-color;
letter-spacing: 4px; letter-spacing: 6px;
line-height: 1.2;
}
.logo-sub {
font-size: 9px;
color: $text-secondary;
letter-spacing: 2.5px;
font-weight: 400;
opacity: 0.7;
} }
} }
.header-nav { .header-nav {
display: flex; display: flex;
gap: 32px; gap: 8px;
.nav-link { .nav-link {
color: #6B6B6B; color: $text-secondary;
text-decoration: none; text-decoration: none;
font-size: 15px; font-size: 14px;
padding: 8px 0; padding: 8px 20px;
border-bottom: 2px solid transparent; border-radius: 20px;
transition: all 0.3s; transition: all 0.25s ease;
font-weight: 500;
&:hover {
color: $primary-color;
background: rgba($primary-color, 0.06);
}
&:hover,
&.router-link-active { &.router-link-active {
color: #5B7E6B; color: #fff;
border-bottom-color: #5B7E6B; background: $primary-color;
} }
&.admin-link { &.admin-link {
color: #E6A23C; color: #E6A23C;
&:hover, &.router-link-active { color: #E6A23C; border-bottom-color: #E6A23C; } &:hover {
background: rgba(#E6A23C, 0.08);
}
&.router-link-active {
color: #fff;
background: #E6A23C;
}
} }
} }
} }
@@ -107,26 +143,27 @@ const handleCommand = (command: string) => {
.header-right { .header-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 12px;
.auth-link { .auth-link {
color: #6B6B6B; color: $text-secondary;
text-decoration: none; text-decoration: none;
font-size: 14px; font-size: 14px;
padding: 6px 16px; padding: 8px 20px;
transition: color 0.3s; border-radius: 20px;
transition: all 0.25s ease;
&:hover { &:hover {
color: #5B7E6B; color: $primary-color;
background: rgba($primary-color, 0.06);
} }
&.auth-register { &.auth-register {
background-color: #5B7E6B; background-color: $primary-color;
color: #fff; color: #fff;
border-radius: 4px;
&:hover { &:hover {
background-color: #3D5A4A; background-color: $primary-dark;
} }
} }
} }
@@ -134,18 +171,35 @@ const handleCommand = (command: string) => {
.user-dropdown { .user-dropdown {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 8px;
cursor: pointer; cursor: pointer;
color: #2C2C2C; color: $text-primary;
font-size: 14px; font-size: 14px;
padding: 6px 12px;
border-radius: 20px;
transition: all 0.25s ease;
&:hover { &:hover {
color: #5B7E6B; background: rgba($primary-color, 0.06);
color: $primary-color;
} }
} }
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, $primary-color, $primary-dark);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 500;
}
.user-nickname { .user-nickname {
max-width: 120px; max-width: 100px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;

View File

@@ -1,7 +1,8 @@
<template> <template>
<nav class="category-nav"> <nav class="category-nav">
<div class="nav-header"> <div class="nav-brand">
<h3>石品类</h3> <h3 class="brand-title"> </h3>
<span class="brand-sub">YUZONG JEWELRY</span>
</div> </div>
<ul class="category-list"> <ul class="category-list">
<li <li
@@ -11,6 +12,7 @@
:class="{ active: currentCategory?.id === category.id }" :class="{ active: currentCategory?.id === category.id }"
@click="handleSelect(category)" @click="handleSelect(category)"
> >
<span class="category-icon">{{ getCategoryIcon(category.name) }}</span>
<span class="category-name">{{ category.name }}</span> <span class="category-name">{{ category.name }}</span>
</li> </li>
</ul> </ul>
@@ -30,52 +32,93 @@ const currentCategory = computed(() => categoryStore.currentCategory)
const handleSelect = (category: Category) => { const handleSelect = (category: Category) => {
categoryStore.selectCategory(category) categoryStore.selectCategory(category)
} }
// 品类图标映射
const getCategoryIcon = (name: string): string => {
const iconMap: Record<string, string> = {
'牌子': '🏷️',
'珠子': '🔵',
'手把件': '🤲',
'雕刻件': '💎',
'摆件': '🏺',
'手镯': '⭕',
'耳钉': '⭐',
'耳饰': '🌙',
'手链': '🔗',
'项链': '📿',
'戒指': '💍',
'表带': '⌚',
'随形': '🗿',
'吊坠': '🔻',
'珠串': '📿',
}
for (const [key, icon] of Object.entries(iconMap)) {
if (name.includes(key)) return icon
}
return '💠'
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B; $primary-color: #5B7E6B;
$primary-light: #8BAF9C; $primary-light: #8BAF9C;
$primary-lighter: #D4E5DB;
$bg-color: #FAF8F5; $bg-color: #FAF8F5;
$border-color: #E8E4DF; $border-color: #E8E4DF;
$text-primary: #2C2C2C; $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
$text-light: #999999;
.category-nav { .category-nav {
width: 200px; width: 220px;
min-width: 200px; min-width: 220px;
height: 100%; height: 100%;
background-color: #fff; background: linear-gradient(180deg, #f7f9f7 0%, #f0f4f1 100%);
border-right: 1px solid $border-color;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: 1px solid rgba($border-color, 0.5);
} }
.nav-header { .nav-brand {
padding: 20px 16px; padding: 24px 20px 20px;
border-bottom: 1px solid $border-color; text-align: center;
border-bottom: 1px solid rgba($border-color, 0.4);
h3 { .brand-title {
font-size: 16px; font-size: 26px;
font-weight: 500; font-weight: 700;
color: $text-primary; color: $primary-color;
margin: 0; letter-spacing: 8px;
margin: 0 0 4px 0;
}
.brand-sub {
font-size: 10px;
color: $text-light;
letter-spacing: 2px; letter-spacing: 2px;
font-weight: 400;
} }
} }
.category-list { .category-list {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 12px 12px;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
} }
.category-item { .category-item {
padding: 14px 20px; display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
cursor: pointer; cursor: pointer;
border-radius: 12px;
transition: all 0.25s ease; transition: all 0.25s ease;
border-bottom: 1px solid $border-color;
&:hover { &:hover {
background-color: rgba($primary-color, 0.06); background-color: rgba($primary-color, 0.06);
@@ -83,18 +126,29 @@ $text-secondary: #6B6B6B;
&.active { &.active {
background-color: $primary-color; background-color: $primary-color;
.category-icon {
filter: brightness(1.2);
}
.category-name { .category-name {
color: #fff; color: #fff;
font-weight: 500; font-weight: 600;
} }
} }
} }
.category-icon {
font-size: 16px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.category-name { .category-name {
font-size: 14px; font-size: 14px;
color: $text-secondary; color: $text-secondary;
letter-spacing: 1px; letter-spacing: 1px;
transition: color 0.25s ease; transition: all 0.25s ease;
} }
</style> </style>

View File

@@ -44,12 +44,12 @@ $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
.color-picker { .color-picker {
margin-top: 24px; margin-top: 28px;
} }
.picker-title { .picker-title {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 16px 0; margin: 0 0 16px 0;
letter-spacing: 1px; letter-spacing: 1px;
@@ -66,21 +66,26 @@ $text-secondary: #6B6B6B;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
padding: 8px; padding: 10px;
border-radius: 8px; border-radius: 12px;
transition: all 0.25s ease; transition: all 0.25s ease;
&:hover { &:hover {
background-color: rgba($primary-color, 0.06); background-color: rgba($primary-color, 0.06);
.color-swatch {
transform: scale(1.08);
}
} }
&.active { &.active {
background-color: rgba($primary-color, 0.08);
.color-swatch { .color-swatch {
box-shadow: 0 0 0 3px $secondary-color; transform: scale(1.1);
box-shadow: 0 0 0 3px $secondary-color, 0 4px 12px rgba(0, 0, 0, 0.12);
} }
.color-name { .color-name {
color: $primary-color; color: $primary-color;
font-weight: 500; font-weight: 600;
} }
} }
} }
@@ -89,9 +94,9 @@ $text-secondary: #6B6B6B;
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 50%; border-radius: 50%;
border: 2px solid $border-color; border: 2px solid rgba($border-color, 0.6);
transition: all 0.25s ease; transition: all 0.25s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), inset 0 1px 2px rgba(255, 255, 255, 0.3);
} }
.color-name { .color-name {

View File

@@ -98,6 +98,38 @@
</button> </button>
</div> </div>
</div> </div>
<!-- 展示视频区域 -->
<div class="video-section" v-if="design.video_url || generatingVideo">
<h4 class="section-title">展示视频</h4>
<div v-if="generatingVideo" class="generating-state">
<el-icon class="loading-icon"><Loading /></el-icon>
<span>正在生成展示视频预计需要 2-5 分钟...</span>
</div>
<div v-else-if="design.video_url" class="video-wrapper">
<video
:key="design.video_url"
controls
preload="auto"
crossorigin="anonymous"
class="preview-video"
>
<source :src="design.video_url" type="video/mp4" />
您的浏览器不支持视频播放
</video>
<div class="video-actions">
<button class="model3d-action-btn" @click="handleDownloadVideo">
<el-icon><Download /></el-icon>
<span>下载视频</span>
</button>
<button class="model3d-action-btn" @click="handleRegenVideo">
<el-icon><RefreshRight /></el-icon>
<span>重新生成</span>
</button>
</div>
</div>
</div>
<div class="design-info"> <div class="design-info">
<h4 class="info-title">设计详情</h4> <h4 class="info-title">设计详情</h4>
<div class="info-grid"> <div class="info-grid">
@@ -140,6 +172,15 @@
<el-icon><Platform /></el-icon> <el-icon><Platform /></el-icon>
<span>生成3D模型</span> <span>生成3D模型</span>
</button> </button>
<!-- 生成展示视频按钮 -->
<button
v-if="!design.video_url && !generatingVideo"
class="action-btn video-btn"
@click="handleGenerateVideo"
>
<el-icon><VideoCameraFilled /></el-icon>
<span>生成展示视频</span>
</button>
<button class="action-btn secondary-btn" @click="goToUserCenter"> <button class="action-btn secondary-btn" @click="goToUserCenter">
<el-icon><User /></el-icon> <el-icon><User /></el-icon>
<span>查看我的设计</span> <span>查看我的设计</span>
@@ -154,8 +195,7 @@ import { useRouter } from 'vue-router'
import { Loading, PictureFilled, ZoomIn, ZoomOut, RefreshRight, Download, User, Platform, VideoCameraFilled } from '@element-plus/icons-vue' import { Loading, PictureFilled, ZoomIn, ZoomOut, RefreshRight, Download, User, Platform, VideoCameraFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import type { Design } from '@/stores/design' import type { Design } from '@/stores/design'
import { getDesignDownloadUrl, generate3DModelApi } from '@/api/design' import { generate3DModelApi, generateVideoApi } from '@/api/design'
import request from '@/api/request'
const props = defineProps<{ const props = defineProps<{
design: Design design: Design
@@ -179,6 +219,9 @@ const scale = ref(1)
// 3D 模型生成状态 // 3D 模型生成状态
const generating3D = ref(false) const generating3D = ref(false)
// 视频生成状态
const generatingVideo = ref(false)
// 是否有多视角图片 // 是否有多视角图片
const hasMultipleViews = computed(() => { const hasMultipleViews = computed(() => {
return props.design.images && props.design.images.length > 1 return props.design.images && props.design.images.length > 1
@@ -252,6 +295,56 @@ const handleRegen3D = async () => {
} }
} }
// 生成展示视频
const handleGenerateVideo = async () => {
generatingVideo.value = true
try {
const updated = await generateVideoApi(props.design.id)
if (updated.video_url) {
props.design.video_url = updated.video_url
}
ElMessage.success('展示视频生成成功!')
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || e?.detail || '视频生成失败,请重试')
} finally {
generatingVideo.value = false
}
}
// 重新生成展示视频
const handleRegenVideo = async () => {
generatingVideo.value = true
try {
const updated = await generateVideoApi(props.design.id, true)
if (updated.video_url) {
props.design.video_url = updated.video_url
}
ElMessage.success('展示视频重新生成成功!')
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || '视频重新生成失败')
} finally {
generatingVideo.value = false
}
}
// 下载展示视频
const handleDownloadVideo = async () => {
const videoUrl = props.design.video_url
if (!videoUrl) return
try {
const res = await fetch(videoUrl)
if (!res.ok) throw new Error('下载失败')
const blob = await res.blob()
const category = props.design.category?.name || '设计'
const subType = props.design.sub_type?.name || ''
const filename = `${category}${subType ? '-' + subType : ''}-展示视频-${props.design.id}.mp4`
_downloadBlob(blob, filename)
ElMessage.success('视频下载成功')
} catch {
ElMessage.error('视频下载失败')
}
}
// 获取图片URL // 获取图片URL
const toImageUrl = (url: string | null): string => { const toImageUrl = (url: string | null): string => {
if (!url) return '' if (!url) return ''
@@ -290,38 +383,31 @@ const downloadFilename = computed(() => {
return `${category}${subType ? '-' + subType : ''}${viewSuffix}-${props.design.id}.png` return `${category}${subType ? '-' + subType : ''}${viewSuffix}-${props.design.id}.png`
}) })
// 下载设计图 // 下载设计图(直接下载当前视角的图片,而非固定第一张)
const handleDownload = () => { const handleDownload = async () => {
const imgUrl = currentImageUrl.value const imgUrl = currentImageUrl.value
if (!imgUrl) { if (!imgUrl) {
ElMessage.error('图片不存在') ElMessage.error('图片不存在')
return return
} }
// 远程 URL 直接新窗口打开(用户右键可保存)
if (imgUrl.startsWith('http')) {
window.open(imgUrl, '_blank')
return
}
// 本地文件通过 axios 携带 Token 下载
downloading.value = true downloading.value = true
request.get(getDesignDownloadUrl(props.design.id), { try {
responseType: 'blob' // 远程 URL 或本地路径都通过 fetch 下载当前视角的图片
}).then((response: any) => { const res = await fetch(imgUrl)
const blob = new Blob([response], { type: 'image/png' }) if (!res.ok) throw new Error('下载失败')
const url = window.URL.createObjectURL(blob) const blob = await res.blob()
const link = document.createElement('a') _downloadBlob(blob, downloadFilename.value)
link.href = url
link.download = downloadFilename.value
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('下载成功') ElMessage.success('下载成功')
}).catch(() => { } catch {
ElMessage.error('下载失败,请重试') // 远程 URL fetch 失败时新窗口打开
}).finally(() => { if (imgUrl.startsWith('http')) {
window.open(imgUrl, '_blank')
} else {
ElMessage.error('下载失败,请重试')
}
} finally {
downloading.value = false downloading.value = false
}) }
} }
// 切换视角时重置缩放 // 切换视角时重置缩放
@@ -520,10 +606,13 @@ const goToUserCenter = () => {
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B; $primary-color: #5B7E6B;
$primary-light: #8BAF9C; $primary-light: #8BAF9C;
$primary-lighter: #D4E5DB;
$primary-dark: #3D5A4A;
$secondary-color: #C4A86C; $secondary-color: #C4A86C;
$bg-color: #FAF8F5; $bg-color: #FAF8F5;
$bg-dark: #F0EDE8; $bg-dark: #F0EDE8;
$border-color: #E8E4DF; $border-color: #E8E4DF;
$border-light: #F0EDE8;
$text-primary: #2C2C2C; $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
$text-light: #999999; $text-light: #999999;
@@ -539,8 +628,8 @@ $text-light: #999999;
gap: 8px; gap: 8px;
padding: 4px; padding: 4px;
background: #fff; background: #fff;
border-radius: 10px; border-radius: 14px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
} }
.view-tab { .view-tab {
@@ -548,7 +637,7 @@ $text-light: #999999;
padding: 10px 16px; padding: 10px 16px;
background: transparent; background: transparent;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 8px; border-radius: 10px;
font-size: 14px; font-size: 14px;
color: $text-secondary; color: $text-secondary;
cursor: pointer; cursor: pointer;
@@ -572,9 +661,10 @@ $text-light: #999999;
position: absolute; position: absolute;
top: 16px; top: 16px;
left: 16px; left: 16px;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.45);
padding: 4px 12px; padding: 4px 14px;
border-radius: 12px; border-radius: 20px;
backdrop-filter: blur(4px);
} }
.indicator-text { .indicator-text {
@@ -585,9 +675,9 @@ $text-light: #999999;
.preview-container { .preview-container {
position: relative; position: relative;
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
padding: 24px; padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
overflow: hidden; overflow: hidden;
} }
@@ -604,8 +694,8 @@ $text-light: #999999;
.design-image { .design-image {
max-width: 100%; max-width: 100%;
max-height: 450px; max-height: 450px;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
cursor: zoom-in; cursor: zoom-in;
:deep(img) { :deep(img) {
@@ -625,7 +715,7 @@ $text-light: #999999;
width: 300px; width: 300px;
height: 300px; height: 300px;
background: $bg-color; background: $bg-color;
border-radius: 8px; border-radius: 12px;
color: $text-light; color: $text-light;
.el-icon { .el-icon {
@@ -651,8 +741,9 @@ $text-light: #999999;
gap: 8px; gap: 8px;
background: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.95);
padding: 8px 12px; padding: 8px 12px;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
backdrop-filter: blur(4px);
} }
.zoom-btn { .zoom-btn {
@@ -663,7 +754,7 @@ $text-light: #999999;
height: 32px; height: 32px;
background: transparent; background: transparent;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 6px; border-radius: 8px;
cursor: pointer; cursor: pointer;
color: $text-secondary; color: $text-secondary;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -689,18 +780,18 @@ $text-light: #999999;
.design-info { .design-info {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
padding: 20px 24px; padding: 20px 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
} }
.info-title { .info-title {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 16px 0; margin: 0 0 16px 0;
padding-bottom: 12px; padding-bottom: 12px;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-light;
letter-spacing: 1px; letter-spacing: 1px;
} }
@@ -738,7 +829,7 @@ $text-light: #999999;
.action-buttons { .action-buttons {
display: flex; display: flex;
gap: 16px; gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -748,8 +839,9 @@ $text-light: #999999;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
padding: 14px 28px; padding: 14px 28px;
border-radius: 8px; border-radius: 12px;
font-size: 15px; font-size: 14px;
font-weight: 500;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
text-decoration: none; text-decoration: none;
@@ -761,14 +853,13 @@ $text-light: #999999;
} }
.download-btn { .download-btn {
background: $primary-color; background: linear-gradient(135deg, $primary-color, $primary-dark);
color: #fff; color: #fff;
border: none; border: none;
&:hover { &:hover {
background: #4a6a5a;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba($primary-color, 0.3); box-shadow: 0 6px 20px rgba($primary-color, 0.3);
} }
} }
@@ -784,14 +875,13 @@ $text-light: #999999;
} }
.model3d-btn { .model3d-btn {
background: #8E44AD; background: linear-gradient(135deg, $primary-light, $primary-color);
color: #fff; color: #fff;
border: none; border: none;
&:hover:not(:disabled) { &:hover:not(:disabled) {
background: #7D3C98;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(#8E44AD, 0.3); box-shadow: 0 6px 20px rgba($primary-color, 0.25);
} }
&:disabled { &:disabled {
@@ -802,18 +892,18 @@ $text-light: #999999;
.model3d-section { .model3d-section {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
padding: 20px 24px; padding: 20px 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
} }
.section-title { .section-title {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 16px 0; margin: 0 0 16px 0;
padding-bottom: 12px; padding-bottom: 12px;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-light;
letter-spacing: 1px; letter-spacing: 1px;
} }
@@ -839,19 +929,19 @@ $text-light: #999999;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
padding: 6px 14px; padding: 8px 16px;
font-size: 12px; font-size: 13px;
color: #666; color: $text-secondary;
background: #f5f5f5; background: $bg-color;
border: 1px solid #e0e0e0; border: 1px solid $border-color;
border-radius: 6px; border-radius: 20px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.25s ease;
&:hover { &:hover {
color: $primary-color; color: $primary-color;
border-color: $primary-color; border-color: $primary-color;
background: rgba($primary-color, 0.05); background: rgba($primary-color, 0.04);
} }
&:disabled { &:disabled {
@@ -863,8 +953,47 @@ $text-light: #999999;
} }
.model-viewer-wrapper { .model-viewer-wrapper {
border-radius: 8px; border-radius: 12px;
overflow: hidden; overflow: hidden;
background: $bg-color; background: $bg-color;
} }
// 视频区域样式
.video-section {
background: #fff;
border-radius: 16px;
padding: 20px 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.video-wrapper {
border-radius: 12px;
overflow: hidden;
background: #000;
}
.preview-video {
width: 100%;
max-height: 500px;
display: block;
}
.video-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 0 0;
background: #fff;
}
.video-btn {
background: linear-gradient(135deg, $secondary-color, #a89058) !important;
color: #fff !important;
border: none !important;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
}
</style> </style>

View File

@@ -27,7 +27,7 @@
v-for="subType in subTypes" v-for="subType in subTypes"
:key="subType.id" :key="subType.id"
class="subtype-card" class="subtype-card"
:class="{ active: currentSubType?.id === subType.id }" :class="{ active: !useCustomSubType && currentSubType?.id === subType.id }"
@click="handleSelectSubType(subType)" @click="handleSelectSubType(subType)"
> >
<div class="card-preview"> <div class="card-preview">
@@ -44,9 +44,33 @@
<span class="card-name">{{ subType.name }}</span> <span class="card-name">{{ subType.name }}</span>
</div> </div>
</div> </div>
<!-- 自定义类型卡片 -->
<div
class="subtype-card custom-card"
:class="{ active: useCustomSubType }"
@click="enableCustomSubType"
>
<div class="card-preview">
<div class="card-placeholder custom-placeholder">
<span>+</span>
</div>
</div>
<div class="card-info">
<span class="card-name">自定义</span>
</div>
</div>
</div>
<!-- 自定义输入框 -->
<div v-if="useCustomSubType" class="custom-input-area">
<el-input
v-model="customSubTypeName"
placeholder="请输入自定义类型名称"
size="large"
clearable
/>
</div> </div>
<!-- 选择子类型后显示颜色选择如有颜色数据 --> <!-- 选择子类型后显示颜色选择如有颜色数据 -->
<template v-if="currentSubType && colors.length > 0"> <template v-if="(currentSubType || (useCustomSubType && customSubTypeName)) && colors.length > 0">
<ColorPicker <ColorPicker
v-model="selectedColor" v-model="selectedColor"
:colors="colors" :colors="colors"
@@ -57,7 +81,7 @@
</button> </button>
</div> </div>
</template> </template>
<div v-else-if="currentSubType" class="action-bar"> <div v-else-if="currentSubType || (useCustomSubType && customSubTypeName)" class="action-bar">
<button class="btn-primary" @click="goToGenerate"> <button class="btn-primary" @click="goToGenerate">
开始设计 开始设计
</button> </button>
@@ -73,15 +97,31 @@
v-for="subType in subTypes" v-for="subType in subTypes"
:key="subType.id" :key="subType.id"
class="size-tag" class="size-tag"
:class="{ active: currentSubType?.id === subType.id }" :class="{ active: !useCustomSubType && currentSubType?.id === subType.id }"
@click="handleSelectSubType(subType)" @click="handleSelectSubType(subType)"
> >
{{ subType.name }} {{ subType.name }}
</div> </div>
<div
class="size-tag custom-tag"
:class="{ active: useCustomSubType }"
@click="enableCustomSubType"
>
+ 自定义
</div>
</div>
<!-- 自定义输入框 -->
<div v-if="useCustomSubType" class="custom-input-area">
<el-input
v-model="customSubTypeName"
placeholder="请输入自定义规格"
size="large"
clearable
/>
</div> </div>
<!-- 选择尺寸后显示颜色选择 --> <!-- 选择尺寸后显示颜色选择 -->
<template v-if="currentSubType && colors.length > 0"> <template v-if="(currentSubType || (useCustomSubType && customSubTypeName)) && colors.length > 0">
<ColorPicker <ColorPicker
v-model="selectedColor" v-model="selectedColor"
:colors="colors" :colors="colors"
@@ -148,6 +188,17 @@ const loading = computed(() => categoryStore.loading)
// 本地颜色选择状态,用于 v-model 双向绑定 // 本地颜色选择状态,用于 v-model 双向绑定
const selectedColor = ref<ColorOption | null>(null) const selectedColor = ref<ColorOption | null>(null)
// 自定义子类型状态
const useCustomSubType = ref(false)
const customSubTypeName = ref('')
const enableCustomSubType = () => {
useCustomSubType.value = true
customSubTypeName.value = ''
categoryStore.selectSubType(null as any)
selectedColor.value = null
}
// size_color 流程的动态标题 // size_color 流程的动态标题
const sizeColorTitle = computed(() => { const sizeColorTitle = computed(() => {
const name = currentCategory.value?.name || '' const name = currentCategory.value?.name || ''
@@ -178,6 +229,8 @@ watch(currentSubType, () => {
}) })
const handleSelectSubType = (subType: SubType) => { const handleSelectSubType = (subType: SubType) => {
useCustomSubType.value = false
customSubTypeName.value = ''
categoryStore.selectSubType(subType) categoryStore.selectSubType(subType)
} }
@@ -190,6 +243,8 @@ const goToGenerate = () => {
if (currentSubType.value) { if (currentSubType.value) {
query.subTypeId = String(currentSubType.value.id) query.subTypeId = String(currentSubType.value.id)
} else if (useCustomSubType.value && customSubTypeName.value) {
query.customSubType = customSubTypeName.value
} }
if (selectedColor.value) { if (selectedColor.value) {
@@ -203,16 +258,19 @@ const goToGenerate = () => {
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B; $primary-color: #5B7E6B;
$primary-light: #8BAF9C; $primary-light: #8BAF9C;
$primary-lighter: #D4E5DB;
$primary-dark: #3D5A4A;
$secondary-color: #C4A86C; $secondary-color: #C4A86C;
$bg-color: #FAF8F5; $bg-color: #FAF8F5;
$border-color: #E8E4DF; $border-color: #E8E4DF;
$border-light: #F0EDE8;
$text-primary: #2C2C2C; $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
$text-light: #999999; $text-light: #999999;
.subtype-panel { .subtype-panel {
flex: 1; flex: 1;
padding: 32px 40px; padding: 36px 44px;
overflow-y: auto; overflow-y: auto;
background-color: $bg-color; background-color: $bg-color;
} }
@@ -227,8 +285,9 @@ $text-light: #999999;
} }
.empty-icon { .empty-icon {
color: $border-color; color: $primary-light;
margin-bottom: 24px; margin-bottom: 24px;
opacity: 0.6;
} }
.empty-text { .empty-text {
@@ -263,8 +322,8 @@ $text-light: #999999;
} }
.panel-title { .panel-title {
font-size: 20px; font-size: 22px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 8px 0; margin: 0 0 8px 0;
letter-spacing: 2px; letter-spacing: 2px;
@@ -273,19 +332,19 @@ $text-light: #999999;
.panel-desc { .panel-desc {
font-size: 14px; font-size: 14px;
color: $text-secondary; color: $text-secondary;
margin: 0 0 24px 0; margin: 0 0 28px 0;
} }
// 牌型卡片网格 // 牌型卡片网格
.card-grid { .card-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px; gap: 20px;
} }
.subtype-card { .subtype-card {
background: #fff; background: #fff;
border-radius: 8px; border-radius: 12px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
@@ -293,13 +352,13 @@ $text-light: #999999;
border: 2px solid transparent; border: 2px solid transparent;
&:hover { &:hover {
transform: translateY(-2px); transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
} }
&.active { &.active {
border-color: $primary-color; border-color: $primary-color;
box-shadow: 0 4px 16px rgba($primary-color, 0.2); box-shadow: 0 4px 16px rgba($primary-color, 0.15);
} }
} }
@@ -318,8 +377,8 @@ $text-light: #999999;
} }
.card-placeholder { .card-placeholder {
width: 60px; width: 56px;
height: 60px; height: 56px;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, $primary-light, $primary-color); background: linear-gradient(135deg, $primary-light, $primary-color);
display: flex; display: flex;
@@ -327,7 +386,7 @@ $text-light: #999999;
justify-content: center; justify-content: center;
span { span {
font-size: 24px; font-size: 22px;
color: #fff; color: #fff;
font-weight: 500; font-weight: 500;
} }
@@ -336,12 +395,13 @@ $text-light: #999999;
.card-info { .card-info {
padding: 12px; padding: 12px;
text-align: center; text-align: center;
border-top: 1px solid $border-color; border-top: 1px solid $border-light;
} }
.card-name { .card-name {
font-size: 14px; font-size: 14px;
color: $text-primary; color: $text-primary;
font-weight: 500;
} }
// 尺寸标签网格 // 尺寸标签网格
@@ -352,10 +412,10 @@ $text-light: #999999;
} }
.size-tag { .size-tag {
padding: 10px 20px; padding: 10px 22px;
background: #fff; background: #fff;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 6px; border-radius: 20px;
font-size: 14px; font-size: 14px;
color: $text-secondary; color: $text-secondary;
cursor: pointer; cursor: pointer;
@@ -364,6 +424,7 @@ $text-light: #999999;
&:hover { &:hover {
border-color: $primary-light; border-color: $primary-light;
color: $primary-color; color: $primary-color;
background: rgba($primary-color, 0.04);
} }
&.active { &.active {
@@ -371,6 +432,41 @@ $text-light: #999999;
border-color: $primary-color; border-color: $primary-color;
color: #fff; color: #fff;
} }
&.custom-tag {
border-style: dashed;
color: $primary-color;
&:hover {
background: rgba($primary-color, 0.06);
}
&.active {
background-color: $primary-color;
border-color: $primary-color;
border-style: solid;
color: #fff;
}
}
}
// 自定义输入区域
.custom-input-area {
margin-top: 20px;
max-width: 400px;
}
// 自定义卡片
.custom-card {
.custom-placeholder {
background: linear-gradient(135deg, rgba($primary-light, 0.5), rgba($primary-color, 0.5));
border: 2px dashed rgba(#fff, 0.6);
span {
font-size: 28px;
font-weight: 300;
}
}
} }
// Simple 类型介绍 // Simple 类型介绍
@@ -378,11 +474,12 @@ $text-light: #999999;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 40px; padding: 48px 40px;
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
text-align: center; text-align: center;
margin-bottom: 24px; margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
} }
.intro-icon { .intro-icon {
@@ -400,23 +497,24 @@ $text-light: #999999;
.action-bar { .action-bar {
margin-top: 32px; margin-top: 32px;
padding-top: 24px; padding-top: 24px;
border-top: 1px solid $border-color; border-top: 1px solid $border-light;
} }
.btn-primary { .btn-primary {
padding: 12px 32px; padding: 14px 36px;
background-color: $primary-color; background: linear-gradient(135deg, $primary-color, $primary-dark);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 6px; border-radius: 12px;
font-size: 15px; font-size: 15px;
font-weight: 500;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
letter-spacing: 2px; letter-spacing: 2px;
&:hover { &:hover {
background-color: #4a6a5a; transform: translateY(-2px);
transform: translateY(-1px); box-shadow: 0 6px 20px rgba($primary-color, 0.3);
} }
&:active { &:active {

View File

@@ -5,6 +5,11 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'Home',
component: () => import('@/views/HomePage.vue')
},
{
path: '/design',
name: 'Design', name: 'Design',
component: () => import('@/views/DesignPage.vue'), component: () => import('@/views/DesignPage.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true }

View File

@@ -1,296 +1,108 @@
/* ========================================
玉宗珠宝设计系统 - 全局基础样式
======================================== */
:root { :root {
--text: #6b6375; /* 品牌色系 */
--text-h: #08060d; --yz-primary: #5B7E6B;
--bg: #fff; --yz-primary-light: #8BAF9C;
--border: #e5e4e7; --yz-primary-lighter: #D4E5DB;
--code-bg: #f4f3ec; --yz-primary-dark: #3D5A4A;
--accent: #aa3bff; --yz-secondary: #C4A86C;
--accent-bg: rgba(170, 59, 255, 0.1); --yz-secondary-light: #E0D1A8;
--accent-border: rgba(170, 59, 255, 0.5);
--social-bg: rgba(244, 243, 236, 0.5);
--shadow:
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
--sans: system-ui, 'Segoe UI', Roboto, sans-serif; /* 背景色 */
--heading: system-ui, 'Segoe UI', Roboto, sans-serif; --yz-bg: #FAF8F5;
--mono: ui-monospace, Consolas, monospace; --yz-bg-dark: #F0EDE8;
--yz-bg-card: #FFFFFF;
font: 18px/145% var(--sans); /* 文字色 */
letter-spacing: 0.18px; --yz-text: #2C2C2C;
color-scheme: light dark; --yz-text-secondary: #6B6B6B;
color: var(--text); --yz-text-light: #999999;
background: var(--bg); --yz-text-placeholder: #BFBFBF;
/* 边框 & 分割线 */
--yz-border: #E8E4DF;
--yz-border-light: #F0EDE8;
--yz-divider: #F0EDE8;
/* 阴影 */
--yz-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
--yz-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
--yz-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.08);
/* 圆角 */
--yz-radius-sm: 8px;
--yz-radius: 12px;
--yz-radius-lg: 16px;
--yz-radius-xl: 20px;
--yz-radius-pill: 999px;
/* 字体 */
--yz-font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', -apple-system, sans-serif;
/* 过渡 */
--yz-transition: all 0.25s ease;
--yz-transition-fast: all 0.15s ease;
font-family: var(--yz-font);
font-size: 14px;
line-height: 1.6;
color: var(--yz-text);
background: var(--yz-bg);
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@media (max-width: 1024px) {
font-size: 16px;
}
}
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--text-h: #f3f4f6;
--bg: #16171d;
--border: #2e303a;
--code-bg: #1f2028;
--accent: #c084fc;
--accent-bg: rgba(192, 132, 252, 0.15);
--accent-border: rgba(192, 132, 252, 0.5);
--social-bg: rgba(47, 48, 58, 0.5);
--shadow:
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
}
#social .button-icon {
filter: invert(1) brightness(2);
}
} }
body { body {
margin: 0; margin: 0;
min-height: 100vh;
} }
h1, h1, h2, h3, h4, h5, h6 {
h2 { font-family: var(--yz-font);
font-family: var(--heading); color: var(--yz-text);
font-weight: 500; font-weight: 600;
color: var(--text-h);
}
h1 {
font-size: 56px;
letter-spacing: -1.68px;
margin: 32px 0;
@media (max-width: 1024px) {
font-size: 36px;
margin: 20px 0;
}
}
h2 {
font-size: 24px;
line-height: 118%;
letter-spacing: -0.24px;
margin: 0 0 8px;
@media (max-width: 1024px) {
font-size: 20px;
}
}
p {
margin: 0; margin: 0;
} }
code, h1 { font-size: 28px; letter-spacing: 2px; }
.counter { h2 { font-size: 22px; letter-spacing: 1.5px; }
font-family: var(--mono); h3 { font-size: 18px; letter-spacing: 1px; }
display: inline-flex; h4 { font-size: 16px; letter-spacing: 0.5px; }
border-radius: 4px;
color: var(--text-h); p { margin: 0; }
a {
color: var(--yz-primary);
text-decoration: none;
transition: var(--yz-transition-fast);
} }
a:hover {
code { color: var(--yz-primary-dark);
font-size: 15px;
line-height: 135%;
padding: 4px 8px;
background: var(--code-bg);
}
.counter {
font-size: 16px;
padding: 5px 10px;
border-radius: 5px;
color: var(--accent);
background: var(--accent-bg);
border: 2px solid transparent;
transition: border-color 0.3s;
margin-bottom: 24px;
&:hover {
border-color: var(--accent-border);
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
}
.hero {
position: relative;
.base,
.framework,
.vite {
inset-inline: 0;
margin: 0 auto;
}
.base {
width: 170px;
position: relative;
z-index: 0;
}
.framework,
.vite {
position: absolute;
}
.framework {
z-index: 1;
top: 34px;
height: 28px;
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
scale(1.4);
}
.vite {
z-index: 0;
top: 107px;
height: 26px;
width: auto;
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
scale(0.8);
}
} }
#app { #app {
width: 1126px; width: 100%;
max-width: 100%; max-width: 100%;
margin: 0 auto; min-height: 100vh;
text-align: center;
border-inline: 1px solid var(--border);
min-height: 100svh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
} }
#center { /* 全局选中态 */
display: flex; ::selection {
flex-direction: column; background: var(--yz-primary-lighter);
gap: 25px; color: var(--yz-text);
place-content: center;
place-items: center;
flex-grow: 1;
@media (max-width: 1024px) {
padding: 32px 20px 24px;
gap: 18px;
}
} }
#next-steps { /* 响应式基础 */
display: flex; @media (max-width: 768px) {
border-top: 1px solid var(--border); :root {
text-align: left; font-size: 13px;
& > div {
flex: 1 1 0;
padding: 32px;
@media (max-width: 1024px) {
padding: 24px 20px;
}
}
.icon {
margin-bottom: 16px;
width: 22px;
height: 22px;
}
@media (max-width: 1024px) {
flex-direction: column;
text-align: center;
}
}
#docs {
border-right: 1px solid var(--border);
@media (max-width: 1024px) {
border-right: none;
border-bottom: 1px solid var(--border);
}
}
#next-steps ul {
list-style: none;
padding: 0;
display: flex;
gap: 8px;
margin: 32px 0 0;
.logo {
height: 18px;
}
a {
color: var(--text-h);
font-size: 16px;
border-radius: 6px;
background: var(--social-bg);
display: flex;
padding: 6px 12px;
align-items: center;
gap: 8px;
text-decoration: none;
transition: box-shadow 0.3s;
&:hover {
box-shadow: var(--shadow);
}
.button-icon {
height: 18px;
width: 18px;
}
}
@media (max-width: 1024px) {
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
li {
flex: 1 1 calc(50% - 8px);
}
a {
width: 100%;
justify-content: center;
box-sizing: border-box;
}
}
}
#spacer {
height: 88px;
border-top: 1px solid var(--border);
@media (max-width: 1024px) {
height: 48px;
}
}
.ticks {
position: relative;
width: 100%;
&::before,
&::after {
content: '';
position: absolute;
top: -4.5px;
border: 5px solid transparent;
}
&::before {
left: 0;
border-left-color: var(--border);
}
&::after {
right: 0;
border-right-color: var(--border);
} }
} }

View File

@@ -22,7 +22,7 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.design-page { .design-page {
display: flex; display: flex;
height: calc(100vh - 60px); height: calc(100vh - 64px);
background-color: #FAF8F5; background-color: #FAF8F5;
} }
</style> </style>

View File

@@ -221,7 +221,7 @@
<div class="generate-action"> <div class="generate-action">
<button <button
class="btn-generate" class="btn-generate"
:disabled="!prompt.trim() || generating" :disabled="generating"
@click="handleGenerate" @click="handleGenerate"
> >
<el-icon v-if="!generating"><MagicStick /></el-icon> <el-icon v-if="!generating"><MagicStick /></el-icon>
@@ -277,6 +277,9 @@ const subTypeId = computed(() => {
const id = route.query.sub_type_id || route.query.subTypeId const id = route.query.sub_type_id || route.query.subTypeId
return id ? Number(id) : null return id ? Number(id) : null
}) })
const customSubType = computed(() => {
return (route.query.customSubType as string) || ''
})
const colorId = computed(() => { const colorId = computed(() => {
const id = route.query.color_id || route.query.colorId const id = route.query.color_id || route.query.colorId
return id ? Number(id) : null return id ? Number(id) : null
@@ -296,6 +299,7 @@ const categoryName = computed(() => {
const subTypeName = computed(() => { const subTypeName = computed(() => {
if (currentDesign.value?.sub_type?.name) return currentDesign.value.sub_type.name if (currentDesign.value?.sub_type?.name) return currentDesign.value.sub_type.name
if (customSubType.value) return customSubType.value
if (!subTypeId.value) return '' if (!subTypeId.value) return ''
const st = categoryStore.subTypes.find(s => s.id === subTypeId.value) const st = categoryStore.subTypes.find(s => s.id === subTypeId.value)
return st?.name || '' return st?.name || ''
@@ -393,7 +397,7 @@ const promptPlaceholder = computed(() => {
// 返回设计页 // 返回设计页
const goBack = () => { const goBack = () => {
router.push('/') router.push('/design')
} }
// 构建最终prompt随形品类时将形状描述拼接到前面 // 构建最终prompt随形品类时将形状描述拼接到前面
@@ -413,15 +417,11 @@ const handleGenerate = async () => {
ElMessage.error('缺少品类参数') ElMessage.error('缺少品类参数')
return return
} }
if (!prompt.value.trim()) {
ElMessage.warning('请输入设计描述')
return
}
try { try {
await designStore.generateDesign({ await designStore.generateDesign({
category_id: categoryId.value, category_id: categoryId.value,
sub_type_id: subTypeId.value || undefined, sub_type_id: subTypeId.value || undefined,
sub_type_name: customSubType.value || undefined,
color_id: colorId.value || undefined, color_id: colorId.value || undefined,
prompt: _buildFinalPrompt(), prompt: _buildFinalPrompt(),
carving_technique: carvingTechnique.value || customCarving.value || undefined, carving_technique: carvingTechnique.value || customCarving.value || undefined,
@@ -476,17 +476,19 @@ onUnmounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B; $primary-color: #5B7E6B;
$primary-light: #8BAF9C; $primary-light: #8BAF9C;
$primary-lighter: #D4E5DB;
$primary-dark: #3D5A4A; $primary-dark: #3D5A4A;
$secondary-color: #C4A86C; $secondary-color: #C4A86C;
$bg-color: #FAF8F5; $bg-color: #FAF8F5;
$bg-dark: #F0EDE8; $bg-dark: #F0EDE8;
$border-color: #E8E4DF; $border-color: #E8E4DF;
$border-light: #F0EDE8;
$text-primary: #2C2C2C; $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
$text-light: #999999; $text-light: #999999;
.generate-page { .generate-page {
min-height: calc(100vh - 60px); min-height: calc(100vh - 64px);
background-color: $bg-color; background-color: $bg-color;
padding-bottom: 60px; padding-bottom: 60px;
} }
@@ -496,27 +498,28 @@ $text-light: #999999;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 24px; gap: 24px;
padding: 20px 40px; padding: 16px 40px;
background: #fff; background: #fff;
border-bottom: 1px solid $border-color; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03);
} }
.back-btn { .back-btn {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
padding: 8px 16px; padding: 8px 18px;
background: transparent; background: transparent;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 6px; border-radius: 20px;
color: $text-secondary; color: $text-secondary;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.25s ease;
&:hover { &:hover {
border-color: $primary-color; border-color: $primary-color;
color: $primary-color; color: $primary-color;
background: rgba($primary-color, 0.04);
} }
} }
@@ -591,7 +594,7 @@ $text-light: #999999;
.section-title { .section-title {
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 8px 0; margin: 0 0 8px 0;
letter-spacing: 2px; letter-spacing: 2px;
@@ -610,14 +613,15 @@ $text-light: #999999;
// 参数面板 // 参数面板
.params-panel { .params-panel {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
padding: 24px 28px; padding: 28px 32px;
margin-bottom: 28px; margin-bottom: 28px;
border: 1px solid $border-color; border: 1px solid $border-light;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
} }
.param-group { .param-group {
margin-bottom: 18px; margin-bottom: 20px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
@@ -627,9 +631,9 @@ $text-light: #999999;
.param-label { .param-label {
display: block; display: block;
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 600;
color: $text-secondary; color: $text-secondary;
margin-bottom: 8px; margin-bottom: 10px;
letter-spacing: 1px; letter-spacing: 1px;
.param-hint { .param-hint {
@@ -648,19 +652,20 @@ $text-light: #999999;
.tag-item { .tag-item {
display: inline-block; display: inline-block;
padding: 6px 14px; padding: 7px 16px;
background: $bg-color; background: $bg-color;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 16px; border-radius: 20px;
font-size: 13px; font-size: 13px;
color: $text-secondary; color: $text-secondary;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.25s ease;
user-select: none; user-select: none;
&:hover { &:hover {
border-color: $primary-light; border-color: $primary-light;
color: $primary-color; color: $primary-color;
background: rgba($primary-color, 0.04);
} }
&.active { &.active {
@@ -674,7 +679,7 @@ $text-light: #999999;
width: 130px; width: 130px;
:deep(.el-input__inner) { :deep(.el-input__inner) {
border-radius: 16px; border-radius: 20px;
font-size: 13px; font-size: 13px;
} }
} }
@@ -685,7 +690,7 @@ $text-light: #999999;
.prompt-title { .prompt-title {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0; margin: 0;
letter-spacing: 1px; letter-spacing: 1px;
@@ -695,9 +700,9 @@ $text-light: #999999;
:deep(.el-textarea__inner) { :deep(.el-textarea__inner) {
background: #fff; background: #fff;
border: 2px solid $border-color; border: 2px solid $border-color;
border-radius: 12px; border-radius: 14px;
padding: 20px; padding: 20px;
font-size: 16px; font-size: 15px;
line-height: 1.8; line-height: 1.8;
color: $text-primary; color: $text-primary;
transition: all 0.3s ease; transition: all 0.3s ease;
@@ -708,7 +713,7 @@ $text-light: #999999;
&:focus { &:focus {
border-color: $primary-color; border-color: $primary-color;
box-shadow: 0 0 0 4px rgba($primary-color, 0.1); box-shadow: 0 0 0 4px rgba($primary-color, 0.08);
} }
} }
@@ -728,16 +733,16 @@ $text-light: #999999;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 16px 48px; padding: 16px 52px;
background: linear-gradient(135deg, $primary-color, $primary-dark); background: linear-gradient(135deg, $primary-color, $primary-dark);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 12px; border-radius: 14px;
font-size: 17px; font-size: 17px;
font-weight: 500; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
letter-spacing: 2px; letter-spacing: 3px;
.el-icon { .el-icon {
font-size: 20px; font-size: 20px;
@@ -745,7 +750,7 @@ $text-light: #999999;
&:hover:not(:disabled) { &:hover:not(:disabled) {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 8px 24px rgba($primary-color, 0.4); box-shadow: 0 8px 28px rgba($primary-color, 0.35);
} }
&:active:not(:disabled) { &:active:not(:disabled) {
@@ -764,16 +769,18 @@ $text-light: #999999;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 14px 32px; padding: 14px 32px;
background: $primary-color; background: linear-gradient(135deg, $primary-color, $primary-dark);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 12px;
font-size: 15px; font-size: 15px;
font-weight: 500;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
&:hover { &:hover {
background: $primary-dark; transform: translateY(-2px);
box-shadow: 0 6px 20px rgba($primary-color, 0.3);
} }
} }
@@ -783,7 +790,7 @@ $text-light: #999999;
justify-content: center; justify-content: center;
margin-top: 32px; margin-top: 32px;
padding-top: 24px; padding-top: 24px;
border-top: 1px solid $border-color; border-top: 1px solid $border-light;
} }
.btn-regenerate { .btn-regenerate {
@@ -794,7 +801,7 @@ $text-light: #999999;
background: #fff; background: #fff;
color: $text-secondary; color: $text-secondary;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 8px; border-radius: 20px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.25s ease;
@@ -802,6 +809,7 @@ $text-light: #999999;
&:hover { &:hover {
border-color: $primary-color; border-color: $primary-color;
color: $primary-color; color: $primary-color;
background: rgba($primary-color, 0.04);
} }
} }
@@ -812,7 +820,7 @@ $text-light: #999999;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(255, 255, 255, 0.95); background: rgba(250, 248, 245, 0.96);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -833,8 +841,8 @@ $text-light: #999999;
} }
.ink-drop { .ink-drop {
width: 16px; width: 14px;
height: 16px; height: 14px;
background: $primary-color; background: $primary-color;
border-radius: 50%; border-radius: 50%;
animation: ink-spread 1.4s ease-in-out infinite; animation: ink-spread 1.4s ease-in-out infinite;
@@ -864,6 +872,7 @@ $text-light: #999999;
color: $text-primary; color: $text-primary;
letter-spacing: 2px; letter-spacing: 2px;
margin: 0; margin: 0;
font-weight: 500;
} }
.loading-hint { .loading-hint {
@@ -875,7 +884,7 @@ $text-light: #999999;
// 响应式 // 响应式
@media (max-width: 768px) { @media (max-width: 768px) {
.page-header { .page-header {
padding: 16px 20px; padding: 12px 20px;
gap: 16px; gap: 16px;
} }

View File

@@ -0,0 +1,712 @@
<template>
<div class="home-page">
<!-- Hero 区域 -->
<section class="hero">
<div class="hero-bg">
<div class="hero-circle hero-circle-1"></div>
<div class="hero-circle hero-circle-2"></div>
<div class="hero-circle hero-circle-3"></div>
</div>
<div class="hero-content">
<div class="hero-badge">AI 驱动 · 珠宝设计</div>
<h1 class="hero-title">
<span class="hero-brand"> </span>
<span class="hero-subtitle">珠宝设计大师</span>
</h1>
<p class="hero-desc">
融合人工智能与传统玉雕美学为您提供专业级珠宝玉石设计图生成服务<br />
覆盖 12 种主流玉石品类多视角智能出图让创意即刻成形
</p>
<div class="hero-actions">
<router-link to="/design" class="btn-primary">
<span>开始设计</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</router-link>
<a href="#features" class="btn-outline" @click.prevent="scrollToFeatures">了解更多</a>
</div>
</div>
</section>
<!-- 功能亮点 -->
<section id="features" class="features">
<div class="section-header">
<span class="section-tag">核心能力</span>
<h2 class="section-title">为珠宝设计而生的 AI 工具</h2>
<p class="section-desc">从创意构思到效果呈现全流程智能辅助</p>
</div>
<div class="feature-grid">
<div class="feature-card" v-for="(feat, i) in features" :key="i">
<div class="feature-icon">{{ feat.icon }}</div>
<h3 class="feature-title">{{ feat.title }}</h3>
<p class="feature-desc">{{ feat.desc }}</p>
</div>
</div>
</section>
<!-- 品类展示 -->
<section class="categories-section">
<div class="section-header">
<span class="section-tag">品类覆盖</span>
<h2 class="section-title">12 种主流玉石品类全面支持</h2>
<p class="section-desc">每种品类均配备专属参数与视角配置满足不同产品设计需求</p>
</div>
<div class="category-grid">
<div class="category-chip" v-for="(cat, i) in categories" :key="i">
<span class="chip-icon">{{ cat.icon }}</span>
<span class="chip-name">{{ cat.name }}</span>
</div>
</div>
</section>
<!-- 工作流程 -->
<section class="workflow-section">
<div class="section-header">
<span class="section-tag">使用流程</span>
<h2 class="section-title">四步完成专业设计</h2>
<p class="section-desc">简单直观的操作流程无需设计经验也能轻松上手</p>
</div>
<div class="workflow-steps">
<div class="step-item" v-for="(step, i) in steps" :key="i">
<div class="step-num">{{ String(i + 1).padStart(2, '0') }}</div>
<h3 class="step-title">{{ step.title }}</h3>
<p class="step-desc">{{ step.desc }}</p>
</div>
</div>
</section>
<!-- 底部 CTA -->
<section class="cta-section">
<div class="cta-content">
<h2 class="cta-title">开始您的珠宝设计之旅</h2>
<p class="cta-desc">AI 赋能传统玉雕美学让每一件作品都独一无二</p>
<router-link to="/design" class="btn-primary btn-large">
<span>立即体验</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</router-link>
</div>
</section>
<!-- Footer -->
<footer class="home-footer">
<div class="footer-brand">
<span class="footer-logo"> </span>
<span class="footer-sub">YUZONG JEWELRY</span>
</div>
<p class="footer-copy">© {{ currentYear }} 玉宗珠宝设计大师 · AI 驱动珠宝玉石设计生成系统</p>
</footer>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const currentYear = computed(() => new Date().getFullYear())
const features = [
{
icon: '🎨',
title: 'AI 智能生图',
desc: '输入设计描述AI 自动理解创意意图,生成专业级珠宝效果图,支持中文自然语言描述'
},
{
icon: '🔄',
title: '多视角展示',
desc: '自动生成 2~4 个不同视角(效果图、正面、侧面、背面),全方位呈现设计效果'
},
{
icon: '💎',
title: '12 大品类覆盖',
desc: '牌子、珠子、手把件、雕刻件、摆件、手镯、耳钉、耳饰、手链、项链、戒指、表带'
},
{
icon: '⚙️',
title: '专业参数配置',
desc: '雕刻工艺、设计风格、题材纹样、尺寸规格、表面处理、用途场景六大维度精准控制'
},
{
icon: '📦',
title: '设计全流程管理',
desc: '设计历史保存、多视角预览、高清图片下载,完整的设计作品生命周期管理'
}
]
const categories = [
{ icon: '🏮', name: '牌子' },
{ icon: '🔮', name: '珠子' },
{ icon: '🤲', name: '手把件' },
{ icon: '🗿', name: '雕刻件' },
{ icon: '🏺', name: '摆件' },
{ icon: '⭕', name: '手镯' },
{ icon: '✨', name: '耳钉' },
{ icon: '💫', name: '耳饰' },
{ icon: '📿', name: '手链' },
{ icon: '💍', name: '项链' },
{ icon: '💎', name: '戒指' },
{ icon: '⌚', name: '表带' }
]
const steps = [
{ title: '选择品类', desc: '从 12 种玉石品类中选择您要设计的产品类型,系统自动适配参数' },
{ title: '配置参数', desc: '选择子类型、颜色、工艺、风格等专业参数,精准定义设计方向' },
{ title: '描述创意', desc: '用自然语言描述您的设计构想AI 将理解并转化为专业设计指令' },
{ title: '生成预览', desc: '一键生成多视角设计效果图,预览、下载、调整,直到满意为止' }
]
const scrollToFeatures = () => {
document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' })
}
</script>
<style scoped lang="scss">
$primary: #5B7E6B;
$primary-dark: #3D5A4A;
$primary-light: #8BAF9C;
$primary-lighter: #D4E5DB;
$secondary: #C4A86C;
$bg: #FAF8F5;
$text: #2C2C2C;
$text-secondary: #6B6B6B;
$text-light: #999;
/* ========== Hero ========== */
.hero {
position: relative;
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: linear-gradient(160deg, #f7f9f7 0%, $bg 40%, #f5f0ea 100%);
}
.hero-bg {
position: absolute;
inset: 0;
pointer-events: none;
}
.hero-circle {
position: absolute;
border-radius: 50%;
opacity: 0.12;
}
.hero-circle-1 {
width: 600px; height: 600px;
background: radial-gradient(circle, $primary-light, transparent 70%);
top: -200px; right: -100px;
}
.hero-circle-2 {
width: 400px; height: 400px;
background: radial-gradient(circle, $secondary, transparent 70%);
bottom: -100px; left: -50px;
}
.hero-circle-3 {
width: 300px; height: 300px;
background: radial-gradient(circle, $primary-lighter, transparent 70%);
top: 40%; left: 60%;
}
.hero-content {
position: relative;
z-index: 1;
text-align: center;
padding: 60px 24px;
max-width: 720px;
}
.hero-badge {
display: inline-block;
padding: 6px 20px;
background: rgba($primary, 0.08);
color: $primary;
border-radius: 999px;
font-size: 13px;
font-weight: 500;
letter-spacing: 2px;
margin-bottom: 32px;
}
.hero-title {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
margin-bottom: 24px;
}
.hero-brand {
font-size: 56px;
font-weight: 800;
color: $primary;
letter-spacing: 16px;
line-height: 1.2;
}
.hero-subtitle {
font-size: 24px;
font-weight: 400;
color: $text-secondary;
letter-spacing: 8px;
}
.hero-desc {
font-size: 16px;
line-height: 1.8;
color: $text-secondary;
margin-bottom: 40px;
}
.hero-actions {
display: flex;
gap: 16px;
justify-content: center;
}
.btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 16px 42px;
background: linear-gradient(135deg, $primary, $primary-dark);
color: #fff;
border-radius: 999px;
font-size: 17px;
font-weight: 500;
text-decoration: none;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba($primary, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba($primary, 0.4);
color: #fff;
}
}
.btn-large {
padding: 18px 48px;
font-size: 18px;
}
.btn-outline {
display: inline-flex;
align-items: center;
padding: 16px 42px;
background: transparent;
color: $text-secondary;
border: 1.5px solid #ddd;
border-radius: 999px;
font-size: 17px;
font-weight: 500;
text-decoration: none;
transition: all 0.3s ease;
&:hover {
border-color: $primary;
color: $primary;
background: rgba($primary, 0.04);
}
}
/* ========== Section Common ========== */
.section-header {
text-align: center;
margin-bottom: 48px;
}
.section-tag {
display: inline-block;
padding: 4px 16px;
background: rgba($primary, 0.08);
color: $primary;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
letter-spacing: 2px;
margin-bottom: 16px;
}
.section-title {
font-size: 28px;
font-weight: 700;
color: $text;
margin-bottom: 12px;
letter-spacing: 2px;
}
.section-desc {
font-size: 15px;
color: $text-secondary;
max-width: 520px;
margin: 0 auto;
}
/* ========== Features ========== */
.features {
padding: 80px 48px;
max-width: 1200px;
margin: 0 auto;
}
.feature-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 24px;
}
.feature-card {
width: calc(33.333% - 16px);
min-width: 280px;
background: #fff;
border-radius: 16px;
padding: 32px 28px;
transition: all 0.3s ease;
border: 1px solid transparent;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
border-color: $primary-lighter;
}
}
.feature-icon {
font-size: 36px;
margin-bottom: 16px;
}
.feature-title {
font-size: 17px;
font-weight: 600;
color: $text;
margin-bottom: 10px;
}
.feature-desc {
font-size: 14px;
line-height: 1.7;
color: $text-secondary;
}
/* ========== Categories ========== */
.categories-section {
padding: 80px 48px;
background: linear-gradient(180deg, #fff 0%, $bg 100%);
}
.category-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
max-width: 800px;
margin: 0 auto;
}
.category-chip {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #fff;
border-radius: 999px;
border: 1px solid $primary-lighter;
transition: all 0.25s ease;
cursor: default;
&:hover {
background: $primary;
color: #fff;
border-color: $primary;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba($primary, 0.2);
.chip-name { color: #fff; }
}
}
.chip-icon {
font-size: 20px;
}
.chip-name {
font-size: 14px;
font-weight: 500;
color: $text;
transition: color 0.25s ease;
}
/* ========== Workflow ========== */
.workflow-section {
padding: 80px 48px;
max-width: 1000px;
margin: 0 auto;
}
.workflow-steps {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.step-item {
text-align: center;
padding: 32px 20px;
position: relative;
&:not(:last-child)::after {
content: '';
position: absolute;
right: -10px;
top: 50%;
width: 20px;
height: 2px;
background: $primary-lighter;
}
}
.step-num {
font-size: 40px;
font-weight: 800;
color: $primary-lighter;
margin-bottom: 12px;
line-height: 1;
}
.step-title {
font-size: 16px;
font-weight: 600;
color: $text;
margin-bottom: 8px;
}
.step-desc {
font-size: 13px;
line-height: 1.7;
color: $text-secondary;
}
/* ========== CTA ========== */
.cta-section {
padding: 100px 48px;
text-align: center;
background: linear-gradient(160deg, #f7f9f7 0%, $bg 50%, #f5f0ea 100%);
position: relative;
overflow: hidden;
}
.cta-title {
font-size: 32px;
font-weight: 700;
color: $text;
margin-bottom: 16px;
letter-spacing: 2px;
}
.cta-desc {
font-size: 16px;
color: $text-secondary;
margin-bottom: 40px;
}
/* ========== Footer ========== */
.home-footer {
padding: 40px 48px;
text-align: center;
border-top: 1px solid #eee;
background: #fff;
}
.footer-brand {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
margin-bottom: 12px;
}
.footer-logo {
font-size: 18px;
font-weight: 700;
color: $primary;
letter-spacing: 6px;
}
.footer-sub {
font-size: 9px;
color: $text-light;
letter-spacing: 2.5px;
}
.footer-copy {
font-size: 12px;
color: $text-light;
}
/* ========== Responsive ========== */
/* 平板端 */
@media (max-width: 1024px) {
.feature-card {
width: calc(50% - 12px);
min-width: auto;
}
.workflow-steps {
grid-template-columns: repeat(2, 1fr);
}
.step-item::after { display: none !important; }
}
/* 手机端 */
@media (max-width: 768px) {
.hero {
min-height: 70vh;
padding: 0 16px;
}
.hero-content {
padding: 40px 16px;
}
.hero-badge {
font-size: 12px;
padding: 5px 16px;
margin-bottom: 24px;
}
.hero-brand {
font-size: 36px;
letter-spacing: 10px;
}
.hero-subtitle {
font-size: 16px;
letter-spacing: 4px;
}
.hero-desc {
font-size: 14px;
line-height: 1.7;
margin-bottom: 32px;
br { display: none; }
}
.hero-actions {
flex-direction: column;
align-items: center;
gap: 12px;
}
.btn-primary, .btn-outline {
padding: 14px 36px;
font-size: 15px;
width: 200px;
justify-content: center;
}
.btn-large {
padding: 14px 36px;
font-size: 16px;
}
.section-header {
margin-bottom: 32px;
}
.section-title {
font-size: 20px;
letter-spacing: 1px;
}
.section-desc {
font-size: 13px;
padding: 0 8px;
}
.features, .categories-section, .workflow-section, .cta-section {
padding: 48px 20px;
}
.feature-grid {
gap: 16px;
}
.feature-card {
width: 100%;
min-width: auto;
padding: 24px 20px;
}
.feature-icon {
font-size: 28px;
margin-bottom: 12px;
}
.feature-title {
font-size: 15px;
}
.feature-desc {
font-size: 13px;
}
.category-grid {
gap: 10px;
}
.category-chip {
padding: 10px 18px;
}
.chip-icon {
font-size: 16px;
}
.chip-name {
font-size: 13px;
}
.workflow-steps {
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.step-item {
padding: 20px 12px;
&::after { display: none !important; }
}
.step-num {
font-size: 32px;
}
.step-title {
font-size: 14px;
}
.step-desc {
font-size: 12px;
}
.cta-section {
padding: 60px 20px;
}
.cta-title {
font-size: 22px;
letter-spacing: 1px;
}
.cta-desc {
font-size: 14px;
margin-bottom: 28px;
}
.home-footer {
padding: 28px 20px;
}
.footer-copy {
font-size: 11px;
}
}
/* 小屏手机 */
@media (max-width: 375px) {
.hero-brand {
font-size: 30px;
letter-spacing: 8px;
}
.hero-subtitle {
font-size: 14px;
letter-spacing: 3px;
}
.workflow-steps {
grid-template-columns: 1fr;
}
.section-title {
font-size: 18px;
}
}
</style>

View File

@@ -3,7 +3,8 @@
<div class="login-card"> <div class="login-card">
<!-- 品牌区域 --> <!-- 品牌区域 -->
<div class="brand-section"> <div class="brand-section">
<h1 class="brand-name">玉宗</h1> <h1 class="brand-name"> </h1>
<p class="brand-en">YUZONG JEWELRY</p>
<p class="brand-subtitle">珠宝设计大师</p> <p class="brand-subtitle">珠宝设计大师</p>
</div> </div>
@@ -95,7 +96,7 @@ const handleLogin = async () => {
try { try {
await userStore.login(form.username, form.password) await userStore.login(form.username, form.password)
ElMessage.success('登录成功') ElMessage.success('登录成功')
router.push('/') router.push('/design')
} catch (error: any) { } catch (error: any) {
// 错误已在拦截器中处理 // 错误已在拦截器中处理
} finally { } finally {
@@ -105,22 +106,41 @@ const handleLogin = async () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B;
$primary-dark: #3D5A4A;
$primary-lighter: #D4E5DB;
.login-page { .login-page {
min-height: calc(100vh - 108px); min-height: calc(100vh - 64px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: #FAF8F5; background: linear-gradient(160deg, #FAF8F5 0%, #f0f4f1 40%, #e8efe9 100%);
padding: 24px; padding: 24px;
position: relative;
&::before {
content: '';
position: absolute;
top: 10%;
right: 10%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba($primary-color, 0.05) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
} }
.login-card { .login-card {
width: 100%; width: 100%;
max-width: 400px; max-width: 420px;
padding: 48px 40px; padding: 48px 44px;
background: #fff; background: #fff;
border-radius: 12px; border-radius: 20px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.06);
position: relative;
z-index: 1;
} }
.brand-section { .brand-section {
@@ -129,10 +149,18 @@ const handleLogin = async () => {
.brand-name { .brand-name {
font-size: 36px; font-size: 36px;
font-weight: 600; font-weight: 700;
color: #5B7E6B; color: $primary-color;
letter-spacing: 8px; letter-spacing: 10px;
margin: 0 0 8px 0; margin: 0 0 4px 0;
}
.brand-en {
font-size: 10px;
color: #999;
letter-spacing: 3px;
margin: 0 0 12px 0;
opacity: 0.7;
} }
.brand-subtitle { .brand-subtitle {
@@ -145,12 +173,18 @@ const handleLogin = async () => {
.login-form { .login-form {
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
border-radius: 8px; border-radius: 10px;
padding: 4px 12px; padding: 6px 14px;
box-shadow: 0 0 0 1px #E8E4DF inset !important;
transition: all 0.25s ease;
&:hover, &.is-focus {
box-shadow: 0 0 0 1px $primary-color inset !important;
}
} }
:deep(.el-input__prefix) { :deep(.el-input__prefix) {
color: #999; color: #bbb;
} }
:deep(.el-form-item) { :deep(.el-form-item) {
@@ -160,24 +194,24 @@ const handleLogin = async () => {
.login-button { .login-button {
width: 100%; width: 100%;
height: 48px; height: 50px;
border-radius: 8px; border-radius: 12px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 600;
background-color: #5B7E6B; background: linear-gradient(135deg, $primary-color, $primary-dark);
border-color: #5B7E6B; border-color: $primary-color;
letter-spacing: 2px; letter-spacing: 3px;
&:hover, &:hover,
&:focus { &:focus {
background-color: #3D5A4A; background: linear-gradient(135deg, $primary-dark, #2d4638);
border-color: #3D5A4A; border-color: $primary-dark;
} }
} }
.footer-links { .footer-links {
text-align: center; text-align: center;
margin-top: 24px; margin-top: 28px;
font-size: 14px; font-size: 14px;
.text { .text {
@@ -185,13 +219,13 @@ const handleLogin = async () => {
} }
.link { .link {
color: #5B7E6B; color: $primary-color;
text-decoration: none; text-decoration: none;
margin-left: 4px; margin-left: 4px;
font-weight: 500; font-weight: 600;
&:hover { &:hover {
color: #3D5A4A; color: $primary-dark;
text-decoration: underline; text-decoration: underline;
} }
} }

View File

@@ -3,7 +3,8 @@
<div class="register-card"> <div class="register-card">
<!-- 品牌区域 --> <!-- 品牌区域 -->
<div class="brand-section"> <div class="brand-section">
<h1 class="brand-name">玉宗</h1> <h1 class="brand-name"> </h1>
<p class="brand-en">YUZONG JEWELRY</p>
<p class="brand-subtitle">创建您的账户</p> <p class="brand-subtitle">创建您的账户</p>
</div> </div>
@@ -134,7 +135,7 @@ const handleRegister = async () => {
try { try {
await userStore.register(form.username, form.password, form.nickname) await userStore.register(form.username, form.password, form.nickname)
ElMessage.success('注册成功') ElMessage.success('注册成功')
router.push('/') router.push('/design')
} catch (error: any) { } catch (error: any) {
// 错误已在拦截器中处理 // 错误已在拦截器中处理
} finally { } finally {
@@ -144,34 +145,60 @@ const handleRegister = async () => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B;
$primary-dark: #3D5A4A;
.register-page { .register-page {
min-height: calc(100vh - 108px); min-height: calc(100vh - 64px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: #FAF8F5; background: linear-gradient(160deg, #FAF8F5 0%, #f0f4f1 40%, #e8efe9 100%);
padding: 24px; padding: 24px;
position: relative;
&::before {
content: '';
position: absolute;
bottom: 15%;
left: 8%;
width: 250px;
height: 250px;
background: radial-gradient(circle, rgba($primary-color, 0.04) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
} }
.register-card { .register-card {
width: 100%; width: 100%;
max-width: 400px; max-width: 420px;
padding: 48px 40px; padding: 48px 44px;
background: #fff; background: #fff;
border-radius: 12px; border-radius: 20px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.06);
position: relative;
z-index: 1;
} }
.brand-section { .brand-section {
text-align: center; text-align: center;
margin-bottom: 40px; margin-bottom: 36px;
.brand-name { .brand-name {
font-size: 36px; font-size: 36px;
font-weight: 600; font-weight: 700;
color: #5B7E6B; color: $primary-color;
letter-spacing: 8px; letter-spacing: 10px;
margin: 0 0 8px 0; margin: 0 0 4px 0;
}
.brand-en {
font-size: 10px;
color: #999;
letter-spacing: 3px;
margin: 0 0 12px 0;
opacity: 0.7;
} }
.brand-subtitle { .brand-subtitle {
@@ -184,39 +211,45 @@ const handleRegister = async () => {
.register-form { .register-form {
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
border-radius: 8px; border-radius: 10px;
padding: 4px 12px; padding: 6px 14px;
box-shadow: 0 0 0 1px #E8E4DF inset !important;
transition: all 0.25s ease;
&:hover, &.is-focus {
box-shadow: 0 0 0 1px $primary-color inset !important;
}
} }
:deep(.el-input__prefix) { :deep(.el-input__prefix) {
color: #999; color: #bbb;
} }
:deep(.el-form-item) { :deep(.el-form-item) {
margin-bottom: 24px; margin-bottom: 22px;
} }
} }
.register-button { .register-button {
width: 100%; width: 100%;
height: 48px; height: 50px;
border-radius: 8px; border-radius: 12px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 600;
background-color: #5B7E6B; background: linear-gradient(135deg, $primary-color, $primary-dark);
border-color: #5B7E6B; border-color: $primary-color;
letter-spacing: 2px; letter-spacing: 3px;
&:hover, &:hover,
&:focus { &:focus {
background-color: #3D5A4A; background: linear-gradient(135deg, $primary-dark, #2d4638);
border-color: #3D5A4A; border-color: $primary-dark;
} }
} }
.footer-links { .footer-links {
text-align: center; text-align: center;
margin-top: 24px; margin-top: 28px;
font-size: 14px; font-size: 14px;
.text { .text {
@@ -224,13 +257,13 @@ const handleRegister = async () => {
} }
.link { .link {
color: #5B7E6B; color: $primary-color;
text-decoration: none; text-decoration: none;
margin-left: 4px; margin-left: 4px;
font-weight: 500; font-weight: 600;
&:hover { &:hover {
color: #3D5A4A; color: $primary-dark;
text-decoration: underline; text-decoration: underline;
} }
} }

View File

@@ -396,14 +396,16 @@ const handleChangePassword = async () => {
<style scoped lang="scss"> <style scoped lang="scss">
$primary-color: #5B7E6B; $primary-color: #5B7E6B;
$primary-dark: #3D5A4A; $primary-dark: #3D5A4A;
$primary-lighter: #D4E5DB;
$bg-color: #FAF8F5; $bg-color: #FAF8F5;
$border-color: #E8E4DF; $border-color: #E8E4DF;
$border-light: #F0EDE8;
$text-primary: #2C2C2C; $text-primary: #2C2C2C;
$text-secondary: #6B6B6B; $text-secondary: #6B6B6B;
$text-light: #999999; $text-light: #999999;
.user-center { .user-center {
min-height: calc(100vh - 108px); min-height: calc(100vh - 64px);
background-color: $bg-color; background-color: $bg-color;
padding: 32px 24px; padding: 32px 24px;
} }
@@ -414,8 +416,8 @@ $text-light: #999999;
} }
.page-title { .page-title {
font-size: 28px; font-size: 26px;
font-weight: 600; font-weight: 700;
color: $text-primary; color: $text-primary;
margin: 0 0 24px 0; margin: 0 0 24px 0;
letter-spacing: 2px; letter-spacing: 2px;
@@ -423,22 +425,22 @@ $text-light: #999999;
.user-tabs { .user-tabs {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 16px;
padding: 24px; padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03);
:deep(.el-tabs__nav-wrap::after) { :deep(.el-tabs__nav-wrap::after) {
height: 1px; height: 1px;
background-color: $border-color; background-color: $border-light;
} }
:deep(.el-tabs__item) { :deep(.el-tabs__item) {
font-size: 16px; font-size: 15px;
color: $text-secondary; color: $text-secondary;
&.is-active { &.is-active {
color: $primary-color; color: $primary-color;
font-weight: 500; font-weight: 600;
} }
&:hover { &:hover {
@@ -448,6 +450,7 @@ $text-light: #999999;
:deep(.el-tabs__active-bar) { :deep(.el-tabs__active-bar) {
background-color: $primary-color; background-color: $primary-color;
border-radius: 2px;
} }
} }
@@ -489,19 +492,20 @@ $text-light: #999999;
} }
.empty-text { .empty-text {
font-size: 16px; font-size: 15px;
color: $text-secondary; color: $text-secondary;
margin: 0 0 24px 0; margin: 0 0 24px 0;
} }
.create-btn { .create-btn {
background-color: $primary-color; background: linear-gradient(135deg, $primary-color, $primary-dark);
border-color: $primary-color; border-color: $primary-color;
padding: 12px 32px; padding: 12px 32px;
font-size: 15px; font-size: 15px;
border-radius: 12px;
&:hover { &:hover {
background-color: $primary-dark; background: linear-gradient(135deg, $primary-dark, #2d4638);
border-color: $primary-dark; border-color: $primary-dark;
} }
} }
@@ -517,24 +521,24 @@ $text-light: #999999;
.design-card { .design-card {
background: #fff; background: #fff;
border-radius: 8px; border-radius: 14px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
cursor: pointer; cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s; transition: all 0.25s ease;
border: 1px solid $border-color; border: 1px solid $border-light;
&:hover { &:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.08);
} }
} }
.card-image { .card-image {
position: relative; position: relative;
width: 100%; width: 100%;
padding-top: 75%; // 4:3 aspect ratio padding-top: 75%;
background-color: #f5f5f5; background-color: $bg-color;
overflow: hidden; overflow: hidden;
img { img {
@@ -544,6 +548,11 @@ $text-light: #999999;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
transition: transform 0.3s ease;
}
.design-card:hover & img {
transform: scale(1.03);
} }
.no-image { .no-image {
@@ -557,7 +566,7 @@ $text-light: #999999;
justify-content: center; justify-content: center;
color: $text-light; color: $text-light;
font-size: 14px; font-size: 14px;
background-color: #f9f9f9; background-color: $bg-color;
} }
} }
@@ -569,7 +578,7 @@ $text-light: #999999;
font-size: 12px; font-size: 12px;
color: $primary-color; color: $primary-color;
margin-bottom: 8px; margin-bottom: 8px;
font-weight: 500; font-weight: 600;
} }
.card-prompt { .card-prompt {
@@ -623,7 +632,7 @@ $text-light: #999999;
:deep(.el-pager li.is-active) { :deep(.el-pager li.is-active) {
color: $primary-color; color: $primary-color;
font-weight: 500; font-weight: 600;
} }
} }
@@ -635,14 +644,14 @@ $text-light: #999999;
} }
.password-section { .password-section {
border-top: 1px solid $border-color; border-top: 1px solid $border-light;
margin-top: 24px; margin-top: 24px;
padding-top: 32px; padding-top: 32px;
} }
.section-title { .section-title {
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 600;
color: $text-primary; color: $text-primary;
margin: 0 0 24px 0; margin: 0 0 24px 0;
} }
@@ -654,19 +663,20 @@ $text-light: #999999;
} }
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
border-radius: 6px; border-radius: 10px;
} }
:deep(.el-input.is-disabled .el-input__wrapper) { :deep(.el-input.is-disabled .el-input__wrapper) {
background-color: #f9f9f9; background-color: $bg-color;
} }
:deep(.el-button--primary) { :deep(.el-button--primary) {
background-color: $primary-color; background: linear-gradient(135deg, $primary-color, $primary-dark);
border-color: $primary-color; border-color: $primary-color;
border-radius: 10px;
&:hover { &:hover {
background-color: $primary-dark; background: linear-gradient(135deg, $primary-dark, #2d4638);
border-color: $primary-dark; border-color: $primary-dark;
} }
} }

View File

@@ -97,45 +97,38 @@
</el-form> </el-form>
</div> </div>
<!-- 即梦视频生成配置卡片 --> <!-- 可灵AI 视频生成配置卡片 -->
<div class="section-card"> <div class="section-card">
<div class="card-header"> <div class="card-header">
<div class="card-title-row"> <div class="card-title-row">
<h3 class="section-title">即梦 3.0 Pro 视频生成</h3> <h3 class="section-title">可灵 AI 视频生成</h3>
<el-tag :type="volcVideoStatus" size="small"> <el-tag :type="klingVideoStatus" size="small">
{{ volcVideoStatusText }} {{ klingVideoStatusText }}
</el-tag> </el-tag>
</div> </div>
<p class="card-desc">火山引擎即梦 3.0 Pro 图生视频 API将设计图生成 360 度旋转展示视频需要火山引擎 Access Key Secret Key API Key</p> <p class="card-desc">可灵 AI 多图参考生视频 API支持传入多张参考图1-4AI 理解为同一物体多角度参考生成单品 360 度旋转展示视频</p>
</div> </div>
<el-form label-width="120px" class="config-form"> <el-form label-width="120px" class="config-form">
<el-form-item label="Access Key"> <el-form-item label="Access Key">
<el-input <el-input
v-model="volcAccessKey" v-model="klingAccessKey"
type="password" type="password"
show-password show-password
placeholder="请输入火山引擎 Access Key" placeholder="请输入可灵 AI Access Key"
clearable clearable
/> />
</el-form-item> </el-form-item>
<el-form-item label="Secret Key"> <el-form-item label="Secret Key">
<el-input <el-input
v-model="volcSecretKey" v-model="klingSecretKey"
type="password" type="password"
show-password show-password
placeholder="请输入火山引擎 Secret Key" placeholder="请输入可灵 AI Secret Key"
clearable clearable
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<span class="form-tip">获取方式火山引擎控制台 右上角头像 API访问密钥或访问 https://console.volcengine.com/iam/keymanage/</span> <span class="form-tip">获取方式访问 <a href="https://klingai.com" target="_blank">可灵AI开放平台</a> 注册/登录 控制台 API密钥管理</span>
</el-form-item>
<el-form-item label="视频时长">
<el-select v-model="videoFrames" style="width: 200px">
<el-option label="2 秒(快速预览)" value="49" />
<el-option label="5 秒(完整展示)" value="121" />
</el-select>
<span class="form-tip">推荐 5 足够展示完整 360 度旋转</span>
</el-form-item> </el-form-item>
<el-form-item label="视频提示词"> <el-form-item label="视频提示词">
<el-input <el-input
@@ -144,7 +137,7 @@
:rows="3" :rows="3"
placeholder="玉雕作品在摄影棚内缓慢旋转360度展示全貌..." placeholder="玉雕作品在摄影棚内缓慢旋转360度展示全貌..."
/> />
<span class="form-tip">用于控制视频生成效果尽情描述旋转展示方式灯光背景等</span> <span class="form-tip">用于控制视频生成效果留空使用默认提示词</span>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -230,12 +223,11 @@ const siliconflowKey = ref('')
const siliconflowUrl = ref('https://api.siliconflow.cn/v1') const siliconflowUrl = ref('https://api.siliconflow.cn/v1')
const volcengineKey = ref('') const volcengineKey = ref('')
const volcengineUrl = ref('https://ark.cn-beijing.volces.com/api/v3') const volcengineUrl = ref('https://ark.cn-beijing.volces.com/api/v3')
const volcAccessKey = ref('') const klingAccessKey = ref('')
const volcSecretKey = ref('') const klingSecretKey = ref('')
const tencentSecretId = ref('') const tencentSecretId = ref('')
const tencentSecretKey = ref('') const tencentSecretKey = ref('')
const videoPrompt = ref('') const videoPrompt = ref('')
const videoFrames = ref('121')
const model3dPrompt = ref('') const model3dPrompt = ref('')
const imageSize = ref('1024') const imageSize = ref('1024')
const saving = ref(false) const saving = ref(false)
@@ -243,8 +235,8 @@ const saving = ref(false)
// 后端是否已配置 API Key脱敏值也算已配置 // 后端是否已配置 API Key脱敏值也算已配置
const siliconflowConfigured = ref(false) const siliconflowConfigured = ref(false)
const volcengineConfigured = ref(false) const volcengineConfigured = ref(false)
const volcAccessKeyConfigured = ref(false) const klingAccessKeyConfigured = ref(false)
const volcSecretKeyConfigured = ref(false) const klingSecretKeyConfigured = ref(false)
const tencentSecretIdConfigured = ref(false) const tencentSecretIdConfigured = ref(false)
const tencentSecretKeyConfigured = ref(false) const tencentSecretKeyConfigured = ref(false)
@@ -259,8 +251,8 @@ const siliconflowStatus = computed(() => (siliconflowKey.value || siliconflowCon
const siliconflowStatusText = computed(() => (siliconflowKey.value || siliconflowConfigured.value) ? '已配置' : '未配置') const siliconflowStatusText = computed(() => (siliconflowKey.value || siliconflowConfigured.value) ? '已配置' : '未配置')
const volcengineStatus = computed(() => (volcengineKey.value || volcengineConfigured.value) ? 'success' : 'info') const volcengineStatus = computed(() => (volcengineKey.value || volcengineConfigured.value) ? 'success' : 'info')
const volcengineStatusText = computed(() => (volcengineKey.value || volcengineConfigured.value) ? '已配置' : '未配置') const volcengineStatusText = computed(() => (volcengineKey.value || volcengineConfigured.value) ? '已配置' : '未配置')
const volcVideoStatus = computed(() => ((volcAccessKey.value || volcAccessKeyConfigured.value) && (volcSecretKey.value || volcSecretKeyConfigured.value)) ? 'success' : 'info') const klingVideoStatus = computed(() => ((klingAccessKey.value || klingAccessKeyConfigured.value) && (klingSecretKey.value || klingSecretKeyConfigured.value)) ? 'success' : 'info')
const volcVideoStatusText = computed(() => ((volcAccessKey.value || volcAccessKeyConfigured.value) && (volcSecretKey.value || volcSecretKeyConfigured.value)) ? '已配置' : '未配置') const klingVideoStatusText = computed(() => ((klingAccessKey.value || klingAccessKeyConfigured.value) && (klingSecretKey.value || klingSecretKeyConfigured.value)) ? '已配置' : '未配置')
const hunyuan3dStatus = computed(() => ((tencentSecretId.value || tencentSecretIdConfigured.value) && (tencentSecretKey.value || tencentSecretKeyConfigured.value)) ? 'success' : 'info') const hunyuan3dStatus = computed(() => ((tencentSecretId.value || tencentSecretIdConfigured.value) && (tencentSecretKey.value || tencentSecretKeyConfigured.value)) ? 'success' : 'info')
const hunyuan3dStatusText = computed(() => ((tencentSecretId.value || tencentSecretIdConfigured.value) && (tencentSecretKey.value || tencentSecretKeyConfigured.value)) ? '已配置' : '未配置') const hunyuan3dStatusText = computed(() => ((tencentSecretId.value || tencentSecretIdConfigured.value) && (tencentSecretKey.value || tencentSecretKeyConfigured.value)) ? '已配置' : '未配置')
@@ -288,14 +280,14 @@ const loadConfigs = async () => {
volcengineKey.value = map['VOLCENGINE_API_KEY'] volcengineKey.value = map['VOLCENGINE_API_KEY']
} }
volcengineUrl.value = map['VOLCENGINE_BASE_URL'] || 'https://ark.cn-beijing.volces.com/api/v3' volcengineUrl.value = map['VOLCENGINE_BASE_URL'] || 'https://ark.cn-beijing.volces.com/api/v3'
// 即梦视频 Access Key / Secret Key // 可灵 AI Access Key / Secret Key
volcAccessKeyConfigured.value = !!map['VOLC_ACCESS_KEY'] klingAccessKeyConfigured.value = !!map['KLING_ACCESS_KEY']
volcSecretKeyConfigured.value = !!map['VOLC_SECRET_KEY'] klingSecretKeyConfigured.value = !!map['KLING_SECRET_KEY']
if (map['VOLC_ACCESS_KEY'] && !map['VOLC_ACCESS_KEY'].includes('****')) { if (map['KLING_ACCESS_KEY'] && !map['KLING_ACCESS_KEY'].includes('****')) {
volcAccessKey.value = map['VOLC_ACCESS_KEY'] klingAccessKey.value = map['KLING_ACCESS_KEY']
} }
if (map['VOLC_SECRET_KEY'] && !map['VOLC_SECRET_KEY'].includes('****')) { if (map['KLING_SECRET_KEY'] && !map['KLING_SECRET_KEY'].includes('****')) {
volcSecretKey.value = map['VOLC_SECRET_KEY'] klingSecretKey.value = map['KLING_SECRET_KEY']
} }
// 腾讯云 SecretId / SecretKey // 腾讯云 SecretId / SecretKey
tencentSecretIdConfigured.value = !!map['TENCENT_SECRET_ID'] tencentSecretIdConfigured.value = !!map['TENCENT_SECRET_ID']
@@ -308,7 +300,6 @@ const loadConfigs = async () => {
} }
// 提示词配置 // 提示词配置
videoPrompt.value = map['VIDEO_PROMPT'] || '' videoPrompt.value = map['VIDEO_PROMPT'] || ''
videoFrames.value = map['VIDEO_FRAMES'] || '121'
model3dPrompt.value = map['MODEL3D_PROMPT'] || '' model3dPrompt.value = map['MODEL3D_PROMPT'] || ''
imageSize.value = map['AI_IMAGE_SIZE'] || '1024' imageSize.value = map['AI_IMAGE_SIZE'] || '1024'
} catch (e) { } catch (e) {
@@ -338,11 +329,11 @@ const handleSave = async () => {
if (volcengineKey.value) { if (volcengineKey.value) {
configs['VOLCENGINE_API_KEY'] = volcengineKey.value configs['VOLCENGINE_API_KEY'] = volcengineKey.value
} }
if (volcAccessKey.value) { if (klingAccessKey.value) {
configs['VOLC_ACCESS_KEY'] = volcAccessKey.value configs['KLING_ACCESS_KEY'] = klingAccessKey.value
} }
if (volcSecretKey.value) { if (klingSecretKey.value) {
configs['VOLC_SECRET_KEY'] = volcSecretKey.value configs['KLING_SECRET_KEY'] = klingSecretKey.value
} }
if (tencentSecretId.value) { if (tencentSecretId.value) {
configs['TENCENT_SECRET_ID'] = tencentSecretId.value configs['TENCENT_SECRET_ID'] = tencentSecretId.value
@@ -352,7 +343,6 @@ const handleSave = async () => {
} }
// 提示词始终提交(包括空值,允许清空) // 提示词始终提交(包括空值,允许清空)
configs['VIDEO_PROMPT'] = videoPrompt.value configs['VIDEO_PROMPT'] = videoPrompt.value
configs['VIDEO_FRAMES'] = videoFrames.value
configs['MODEL3D_PROMPT'] = model3dPrompt.value configs['MODEL3D_PROMPT'] = model3dPrompt.value
await updateConfigsBatch(configs) await updateConfigsBatch(configs)
ElMessage.success('配置已保存') ElMessage.success('配置已保存')

View File

@@ -1,165 +1,22 @@
-- 玉宗 - 珠宝设计大师 数据库初始化脚本(全量数据) -- 玉宗珠宝设计系统 全量数据库初始化脚本
-- 使用前请先创建数据库: CREATE DATABASE yssjs CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 自动生成,包含建表语句和全量数据
-- 导入命令: mysql --default-character-set=utf8mb4 -u yssjs -pyssjs yssjs < init_data.sql
-- 包含全量数据users、system_configs 含真实 API Key
SET NAMES utf8mb4; SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; SET FOREIGN_KEY_CHECKS = 0;
-- ======================================== -- ----------------------------
-- 清空所有表数据(保证每次导入都是全新数据) -- Table: categories
-- ======================================== -- ----------------------------
TRUNCATE TABLE `design_images`; DROP TABLE IF EXISTS `categories`;
TRUNCATE TABLE `designs`; CREATE TABLE `categories` (
TRUNCATE TABLE `prompt_mappings`; `id` int NOT NULL AUTO_INCREMENT COMMENT '品类ID',
TRUNCATE TABLE `prompt_templates`; `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品类名称',
TRUNCATE TABLE `system_configs`; `icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '品类图标',
TRUNCATE TABLE `colors`; `sort_order` int DEFAULT NULL COMMENT '排序',
TRUNCATE TABLE `sub_types`; `flow_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程类型full/size_color/simple',
TRUNCATE TABLE `categories`; PRIMARY KEY (`id`)
TRUNCATE TABLE `users`; ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ========================================
-- 建表: users
-- ========================================
CREATE TABLE IF NOT EXISTS `users` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
`phone` VARCHAR(20) DEFAULT NULL UNIQUE COMMENT '手机号',
`hashed_password` VARCHAR(255) NOT NULL COMMENT '加密密码',
`nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
`is_admin` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否管理员',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- ========================================
-- 数据: users
-- ========================================
INSERT INTO `users` (`id`, `username`, `phone`, `hashed_password`, `nickname`, `avatar`, `is_admin`, `created_at`, `updated_at`) VALUES (1, 'demo', NULL, '$2b$12$bHrcm45CwD0wbQVZIQxOmuZcx/B/MEbGYFTGpPawinDkDWI.jilY2', '演示用户', NULL, 1, '2026-03-27 04:20:30', '2026-03-27 04:20:30');
INSERT INTO `users` (`id`, `username`, `phone`, `hashed_password`, `nickname`, `avatar`, `is_admin`, `created_at`, `updated_at`) VALUES (2, 'test1', NULL, '$2b$12$BG5/I4CVswjNWhfdIP9kJeVqbqLEcWkRq8ioxdpwON7eitL.lSVMW', '测试用户', NULL, 0, '2026-03-27 04:21:36', '2026-03-27 04:21:36');
-- ========================================
-- 建表: categories
-- ========================================
CREATE TABLE IF NOT EXISTS `categories` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '品类ID',
`name` VARCHAR(50) NOT NULL COMMENT '品类名称',
`icon` VARCHAR(255) DEFAULT NULL COMMENT '品类图标',
`sort_order` INT DEFAULT 0 COMMENT '排序',
`flow_type` VARCHAR(20) NOT NULL COMMENT '流程类型full/size_color/simple'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='品类表';
-- ========================================
-- 建表: sub_types
-- ========================================
CREATE TABLE IF NOT EXISTS `sub_types` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '子类型ID',
`category_id` INT NOT NULL COMMENT '所属品类',
`name` VARCHAR(50) NOT NULL COMMENT '名称',
`description` VARCHAR(255) DEFAULT NULL COMMENT '描述',
`preview_image` VARCHAR(255) DEFAULT NULL COMMENT '预览图',
`sort_order` INT DEFAULT 0 COMMENT '排序',
CONSTRAINT `fk_sub_types_category` FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='子类型表';
-- ========================================
-- 建表: colors
-- ========================================
CREATE TABLE IF NOT EXISTS `colors` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '颜色ID',
`category_id` INT NOT NULL COMMENT '适用品类',
`name` VARCHAR(50) NOT NULL COMMENT '颜色名称',
`hex_code` VARCHAR(7) NOT NULL COMMENT '色值',
`sort_order` INT DEFAULT 0 COMMENT '排序',
CONSTRAINT `fk_colors_category` FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='颜色表';
-- ========================================
-- 建表: designs
-- ========================================
CREATE TABLE IF NOT EXISTS `designs` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '设计ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`category_id` INT NOT NULL COMMENT '品类ID',
`sub_type_id` INT DEFAULT NULL COMMENT '子类型ID',
`color_id` INT DEFAULT NULL COMMENT '颜色ID',
`prompt` TEXT NOT NULL COMMENT '设计需求',
`carving_technique` VARCHAR(50) DEFAULT NULL COMMENT '雕刻工艺',
`design_style` VARCHAR(50) DEFAULT NULL COMMENT '设计风格',
`motif` VARCHAR(100) DEFAULT NULL COMMENT '题材纹样',
`size_spec` VARCHAR(100) DEFAULT NULL COMMENT '尺寸规格',
`surface_finish` VARCHAR(50) DEFAULT NULL COMMENT '表面处理',
`usage_scene` VARCHAR(50) DEFAULT NULL COMMENT '用途场景',
`image_url` TEXT DEFAULT NULL COMMENT '设计图URL',
`video_url` TEXT DEFAULT NULL COMMENT '360度展示视频URL',
`status` VARCHAR(20) DEFAULT 'generating' COMMENT '状态',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
CONSTRAINT `fk_designs_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
CONSTRAINT `fk_designs_category` FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`),
CONSTRAINT `fk_designs_sub_type` FOREIGN KEY (`sub_type_id`) REFERENCES `sub_types`(`id`),
CONSTRAINT `fk_designs_color` FOREIGN KEY (`color_id`) REFERENCES `colors`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设计作品表';
-- ========================================
-- 建表: design_images如不存在
-- ========================================
CREATE TABLE IF NOT EXISTS design_images (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '图片ID',
design_id BIGINT NOT NULL COMMENT '关联设计ID',
view_name VARCHAR(20) NOT NULL COMMENT '视角名称: 效果图/正面图/侧面图/背面图',
image_url TEXT DEFAULT NULL COMMENT '图片URL路径',
model_used VARCHAR(50) DEFAULT NULL COMMENT '使用的AI模型',
prompt_used TEXT DEFAULT NULL COMMENT '实际使用的英文prompt',
sort_order INT DEFAULT 0 COMMENT '排序',
model_3d_url TEXT DEFAULT NULL COMMENT '3D模型URL(.glb)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
CONSTRAINT fk_design_images_design FOREIGN KEY (design_id) REFERENCES designs(id) ON DELETE CASCADE,
INDEX idx_design_images_design_id (design_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI多视角设计图';
-- ========================================
-- 建表: system_configs如不存在
-- ========================================
CREATE TABLE IF NOT EXISTS system_configs (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
config_value TEXT COMMENT '配置值',
description VARCHAR(255) COMMENT '配置说明',
config_group VARCHAR(50) NOT NULL DEFAULT 'general' COMMENT '配置分组: ai/general',
is_secret CHAR(1) NOT NULL DEFAULT 'N' COMMENT '是否敏感信息(Y/N)',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表';
-- ========================================
-- 建表: prompt_mappings如不存在
-- ========================================
CREATE TABLE IF NOT EXISTS prompt_mappings (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',
mapping_type VARCHAR(20) NOT NULL COMMENT '映射类型: category/color/view/carving/style/motif/finish/scene/sub_type',
cn_key VARCHAR(50) NOT NULL COMMENT '中文键',
en_value TEXT NOT NULL COMMENT '英文值',
sort_order INT DEFAULT 0 COMMENT '排序',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_type_key (mapping_type, cn_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中英文提示词映射表';
-- ========================================
-- 建表: prompt_templates如不存在
-- ========================================
CREATE TABLE IF NOT EXISTS prompt_templates (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',
template_key VARCHAR(50) NOT NULL UNIQUE COMMENT '模板键',
template_value TEXT NOT NULL COMMENT '模板内容',
description VARCHAR(255) COMMENT '说明',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='提示词模板表';
-- ========================================
-- categories 数据 (12 条)
-- ========================================
INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (1, '牌子', NULL, 1, 'full'); INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (1, '牌子', NULL, 1, 'full');
INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (2, '珠子', NULL, 2, 'size_color'); INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (2, '珠子', NULL, 2, 'size_color');
INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (3, '手把件', NULL, 3, 'full'); INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (3, '手把件', NULL, 3, 'full');
@@ -174,81 +31,21 @@ INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUE
INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (12, '表带', NULL, 12, 'size_color'); INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (12, '表带', NULL, 12, 'size_color');
INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (13, '随形', NULL, 13, 'simple'); INSERT INTO `categories` (`id`, `name`, `icon`, `sort_order`, `flow_type`) VALUES (13, '随形', NULL, 13, 'simple');
-- ======================================== -- ----------------------------
-- sub_types 数据 (68 条) -- Table: colors
-- ======================================== -- ----------------------------
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (1, 1, '二五牌', NULL, NULL, 1); DROP TABLE IF EXISTS `colors`;
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (2, 1, '三角牌', NULL, NULL, 2); CREATE TABLE `colors` (
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (3, 1, '三五牌', NULL, NULL, 3); `id` int NOT NULL AUTO_INCREMENT COMMENT '颜色ID',
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (4, 1, '四六牌', NULL, NULL, 4); `category_id` int NOT NULL COMMENT '适用品类',
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (5, 1, '正方形', NULL, NULL, 5); `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '颜色名称',
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (6, 1, '椭圆形', NULL, NULL, 6); `hex_code` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '色值',
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (7, 2, '4mm', NULL, NULL, 1); `sort_order` int DEFAULT NULL COMMENT '排序',
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (8, 2, '6mm', NULL, NULL, 2); PRIMARY KEY (`id`),
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (9, 2, '8mm', NULL, NULL, 3); KEY `category_id` (`category_id`),
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (10, 2, '10mm', NULL, NULL, 4); CONSTRAINT `colors_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`)
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (11, 2, '12mm', NULL, NULL, 5); ) ENGINE=InnoDB AUTO_INCREMENT=132 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (12, 2, '14mm', NULL, NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (13, 2, '16mm', NULL, NULL, 7);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (14, 2, '18mm', NULL, NULL, 8);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (15, 2, '20mm', NULL, NULL, 9);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (16, 3, '山水手把件', '山水意境题材', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (17, 3, '动物手把件', '动物造型题材', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (18, 3, '瑞兽手把件', '貔貅、麒麟等瑞兽', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (19, 3, '人物手把件', '人物造型题材', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (20, 3, '花鸟手把件', '花鸟自然题材', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (21, 3, '佛像手把件', '佛教题材', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (22, 4, '山水雕刻', '山水意境雕刻', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (23, 4, '花鸟雕刻', '花鸟自然雕刻', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (24, 4, '人物雕刻', '人物造型雕刻', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (25, 4, '佛像雕刻', '佛教题材雕刻', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (26, 4, '瑞兽雕刻', '瑞兽神兽雕刻', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (27, 4, '仿古雕刻', '仿古纹饰雕刻', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (28, 5, '山水摆件', '山水意境摆件', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (29, 5, '人物摆件', '人物造型摆件', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (30, 5, '动物摆件', '动物造型摆件', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (31, 5, '佛像摆件', '佛教题材摆件', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (32, 5, '花鸟摆件', '花鸟自然摆件', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (33, 5, '器皿摆件', '香炉、花瓶等器皿', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (34, 6, '平安镯', '内平外圆,最经典的镯型', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (35, 6, '福镯', '内圆外圆,圆条造型', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (36, 6, '贵妃镯', '椭圆形,贴合手腕', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (37, 6, '美人镯', '条杆纤细,秀气典雅', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (38, 6, '方镯', '方形截面,棱角分明', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (39, 6, '雕花镯', '表面雕刻纹饰', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (40, 7, '圆形耳钉', '经典圆形造型', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (41, 7, '水滴形耳钉', '水滴形优雅造型', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (42, 7, '方形耳钉', '方形简约造型', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (43, 7, '花朵形耳钉', '花朵造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (44, 7, '心形耳钉', '心形浪漫造型', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (45, 7, '几何形耳钉', '几何抽象造型', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (46, 8, '耳环', '圆环形耳饰', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (47, 8, '耳坠', '垂坠型耳饰', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (48, 8, '耳夹', '无需耳洞的耳饰', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (49, 8, '流苏耳饰', '长款流苏造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (50, 9, '6mm', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (51, 9, '8mm', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (52, 9, '10mm', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (53, 9, '12mm', NULL, NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (54, 9, '14mm', NULL, NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (55, 10, '锁骨链', '短款锁骨链', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (56, 10, '吊坠项链', '搭配玉石吊坠', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (57, 10, '串珠项链', '玉珠串联而成', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (58, 10, '编绳项链', '编织绳搭配玉石', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (59, 10, '毛衣链', '长款毛衣链', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (60, 11, '素面戒指', '光面简约戒指', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (61, 11, '镶嵌戒指', '金属镶嵌玉石', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (62, 11, '雕花戒指', '表面雕刻纹饰', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (63, 11, '扳指', '传统扳指造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (64, 11, '指环', '环形简约指环', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (65, 12, '18mm', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (66, 12, '20mm', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (67, 12, '22mm', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (68, 12, '24mm', NULL, NULL, 4);
-- ========================================
-- colors 数据 (121 条)
-- ========================================
INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (1, 2, '糖白', '#F5F0E8', 1); INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (1, 2, '糖白', '#F5F0E8', 1);
INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (2, 2, '白玉', '#FEFEF2', 2); INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (2, 2, '白玉', '#FEFEF2', 2);
INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (3, 2, '碧玉', '#2D5F2D', 3); INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (3, 2, '碧玉', '#2D5F2D', 3);
@@ -381,9 +178,85 @@ INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VAL
INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (130, 13, '藕粉', '#E8B4B8', 9); INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (130, 13, '藕粉', '#E8B4B8', 9);
INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (131, 13, '烟紫', '#8B7D9B', 10); INSERT INTO `colors` (`id`, `category_id`, `name`, `hex_code`, `sort_order`) VALUES (131, 13, '烟紫', '#8B7D9B', 10);
-- ======================================== -- ----------------------------
-- prompt_mappings 数据 (93 条) -- Table: design_images
-- ======================================== -- ----------------------------
DROP TABLE IF EXISTS `design_images`;
CREATE TABLE `design_images` (
`id` bigint NOT NULL AUTO_INCREMENT,
`design_id` bigint NOT NULL,
`view_name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`image_url` text COLLATE utf8mb4_unicode_ci,
`model_used` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`prompt_used` text COLLATE utf8mb4_unicode_ci,
`sort_order` int DEFAULT '0',
`model_3d_url` text COLLATE utf8mb4_unicode_ci COMMENT '3D模型URL(.glb)',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_design_images_design_id` (`design_id`),
CONSTRAINT `fk_design_images_design` FOREIGN KEY (`design_id`) REFERENCES `designs` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (26, 24, '效果图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/021774603662634abef7db0e51ae42a75ef9551e31b2ffa297e1d_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T092814Z&X-Tos-Expires=86400&X-Tos-Signature=4c45a7e5cd9deab6608a0a088896b66ea86b978f80c22d725c5cdbdd8d48e72c&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade pendant plaque, rectangular tablet shape, square shaped plaque, pure white nephrite jade, milky translucent, warm ivory tone, featuring Pixiu mythical beast, Chinese fortune guardian creature, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 60x40x12mm, design concept: 桌子上, three-quarter view, 45-degree angle, hero shot, dramatic perspective showing depth and dimension, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 0, NULL, '2026-03-27 09:29:10');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (27, 24, '正面图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/021774603695370cd417e3a5d5f9b5e363d704a28c424928021b1_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T092844Z&X-Tos-Expires=86400&X-Tos-Signature=c6782b121c95d03ce6a06e492a5cc388232220c6b3647052e21ae7861090c1f8&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade pendant plaque, rectangular tablet shape, square shaped plaque, pure white nephrite jade, milky translucent, warm ivory tone, featuring Pixiu mythical beast, Chinese fortune guardian creature, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 60x40x12mm, design concept: 桌子上, front view, straight-on, flat lay centered, facing camera directly, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 1, NULL, '2026-03-27 09:29:10');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (28, 24, '背面图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/0217746037252401fad3a044f20103f8e4b726dec7c2fa48ca5da_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T092910Z&X-Tos-Expires=86400&X-Tos-Signature=4736a14a81377c899dd03fbbf28f2d70f4447c16368ea656517a260fc89c91f0&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade pendant plaque, rectangular tablet shape, square shaped plaque, pure white nephrite jade, milky translucent, warm ivory tone, featuring Pixiu mythical beast, Chinese fortune guardian creature, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 60x40x12mm, design concept: 桌子上, back view, rear side, showing reverse surface and texture, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 2, NULL, '2026-03-27 09:29:10');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (29, 27, '效果图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/0217746123473820b865ba46ab853a653f94346fa59dc1da3cf44_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T115254Z&X-Tos-Expires=86400&X-Tos-Signature=1d5da361de1e5113a0bb5d01840b388f0fd80f358ed559f28ceb3da5e75a3ee3&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form, celadon nephrite jade, muted sage green, natural earthy green, featuring Maitreya laughing Buddha, jovial happy Buddha figure, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 小(约60mm), design concept: 形状:原石随形,自由发挥, three-quarter view, 45-degree angle, hero shot, dramatic perspective showing depth and dimension, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 0, '/uploads/models/506909359d6c45fe9e43108ee7765a9a.glb', '2026-03-27 11:54:22');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (30, 27, '正面图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/021774612375050be847cf78a18700c7c5f1c903c2ea6d9bd058e_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T115322Z&X-Tos-Expires=86400&X-Tos-Signature=8436646d32886371fb2693a7d2a43476f2df124c0c6c821b64c1adb63649964e&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form, celadon nephrite jade, muted sage green, natural earthy green, featuring Maitreya laughing Buddha, jovial happy Buddha figure, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 小(约60mm), design concept: 形状:原石随形,自由发挥, front view, straight-on, flat lay centered, facing camera directly, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 1, NULL, '2026-03-27 11:54:22');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (31, 27, '侧面图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/02177461240230991172dea4c1343f5a1ee75fbf798a6b0d4fd26_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T115351Z&X-Tos-Expires=86400&X-Tos-Signature=3d15b4eb02c16e32b3c5ea6bf0c65966e97822edf9da84f072d0f9e54665f777&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form, celadon nephrite jade, muted sage green, natural earthy green, featuring Maitreya laughing Buddha, jovial happy Buddha figure, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 小(约60mm), design concept: 形状:原石随形,自由发挥, side profile view, 90-degree lateral angle, showing thickness and contour, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 2, NULL, '2026-03-27 11:54:22');
INSERT INTO `design_images` (`id`, `design_id`, `view_name`, `image_url`, `model_used`, `prompt_used`, `sort_order`, `model_3d_url`, `created_at`) VALUES (32, 27, '背面图', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/021774612432177ca53c98b4a9847edf4b59a94723c611efd12a3_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T115422Z&X-Tos-Expires=86400&X-Tos-Signature=0be7759a2bfc3b6b323417c29665910745a2d687996fb2d889a525c56b4c80ee&X-Tos-SignedHeaders=host', 'seedream-5.0', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form, celadon nephrite jade, muted sage green, natural earthy green, featuring Maitreya laughing Buddha, jovial happy Buddha figure, relief carving with raised design emerging from surface, classical traditional Chinese style, antique aesthetic, heritage craftsmanship, high-gloss mirror polish, reflective glossy surface, designed for daily wear, comfortable and practical, size approximately 小(约60mm), design concept: 形状:原石随形,自由发挥, back view, rear side, showing reverse surface and texture, professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', 3, NULL, '2026-03-27 11:54:22');
-- ----------------------------
-- Table: designs
-- ----------------------------
DROP TABLE IF EXISTS `designs`;
CREATE TABLE `designs` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '设计ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`category_id` int NOT NULL COMMENT '品类ID',
`sub_type_id` int DEFAULT NULL COMMENT '子类型ID',
`color_id` int DEFAULT NULL COMMENT '颜色ID',
`prompt` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设计需求',
`usage_scene` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`surface_finish` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size_spec` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`motif` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`design_style` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`carving_technique` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_url` text COLLATE utf8mb4_unicode_ci,
`video_url` text COLLATE utf8mb4_unicode_ci COMMENT '360度展示视频URL',
`status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '状态',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `category_id` (`category_id`),
KEY `sub_type_id` (`sub_type_id`),
KEY `color_id` (`color_id`),
CONSTRAINT `designs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `designs_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`),
CONSTRAINT `designs_ibfk_3` FOREIGN KEY (`sub_type_id`) REFERENCES `sub_types` (`id`),
CONSTRAINT `designs_ibfk_4` FOREIGN KEY (`color_id`) REFERENCES `colors` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `designs` (`id`, `user_id`, `category_id`, `sub_type_id`, `color_id`, `prompt`, `usage_scene`, `surface_finish`, `size_spec`, `motif`, `design_style`, `carving_technique`, `image_url`, `video_url`, `status`, `created_at`, `updated_at`) VALUES (24, 1, 1, 5, 4, '桌子上', '日常佩戴', '高光抛光', '60x40x12mm', '貔貅', '古典传统', '浮雕', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/021774603662634abef7db0e51ae42a75ef9551e31b2ffa297e1d_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T092814Z&X-Tos-Expires=86400&X-Tos-Signature=4c45a7e5cd9deab6608a0a088896b66ea86b978f80c22d725c5cdbdd8d48e72c&X-Tos-SignedHeaders=host', NULL, 'completed', '2026-03-27 09:27:42', '2026-03-27 09:29:10');
INSERT INTO `designs` (`id`, `user_id`, `category_id`, `sub_type_id`, `color_id`, `prompt`, `usage_scene`, `surface_finish`, `size_spec`, `motif`, `design_style`, `carving_technique`, `image_url`, `video_url`, `status`, `created_at`, `updated_at`) VALUES (26, 1, 13, NULL, 124, '形状:原石随形,自由发挥', '日常佩戴', '高光抛光', '小(约60mm)', '弥勒', '古典传统', '浮雕', '/uploads/designs/26.png', NULL, 'completed', '2026-03-27 11:50:56', '2026-03-27 11:50:56');
INSERT INTO `designs` (`id`, `user_id`, `category_id`, `sub_type_id`, `color_id`, `prompt`, `usage_scene`, `surface_finish`, `size_spec`, `motif`, `design_style`, `carving_technique`, `image_url`, `video_url`, `status`, `created_at`, `updated_at`) VALUES (27, 1, 13, NULL, 124, '形状:原石随形,自由发挥', '日常佩戴', '高光抛光', '小(约60mm)', '弥勒', '古典传统', '浮雕', 'https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/doubao-seedream-5-0/0217746123473820b865ba46ab853a653f94346fa59dc1da3cf44_0.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20260327%2Fcn-beijing%2Ftos%2Frequest&X-Tos-Date=20260327T115254Z&X-Tos-Expires=86400&X-Tos-Signature=1d5da361de1e5113a0bb5d01840b388f0fd80f358ed559f28ceb3da5e75a3ee3&X-Tos-SignedHeaders=host', '/uploads/videos/6ed3f33421a44876b303c7671c1597d5.mp4', 'completed', '2026-03-27 11:52:27', '2026-03-27 16:16:40');
-- ----------------------------
-- Table: prompt_mappings
-- ----------------------------
DROP TABLE IF EXISTS `prompt_mappings`;
CREATE TABLE `prompt_mappings` (
`id` bigint NOT NULL AUTO_INCREMENT,
`mapping_type` varchar(50) NOT NULL COMMENT '映射类型',
`cn_key` varchar(100) NOT NULL COMMENT '中文键',
`en_value` text NOT NULL COMMENT '英文描述',
`sort_order` int DEFAULT '0' COMMENT '排序',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_type_key` (`mapping_type`,`cn_key`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='提示词映射';
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (1, 'category', '牌子', 'Chinese Hetian nephrite jade pendant plaque, rectangular tablet shape', 0, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (1, 'category', '牌子', 'Chinese Hetian nephrite jade pendant plaque, rectangular tablet shape', 0, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (2, 'category', '珠子', 'Chinese Hetian nephrite jade bead, perfectly round sphere', 1, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (2, 'category', '珠子', 'Chinese Hetian nephrite jade bead, perfectly round sphere', 1, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (3, 'category', '手把件', 'Chinese Hetian nephrite jade hand piece (palm stone), ergonomic carved ornament for hand play', 2, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (3, 'category', '手把件', 'Chinese Hetian nephrite jade hand piece (palm stone), ergonomic carved ornament for hand play', 2, '2026-03-27 07:20:55');
@@ -396,7 +269,6 @@ INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (10, 'category', '项链', 'Chinese Hetian nephrite jade necklace, elegant pendant on chain', 9, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (10, 'category', '项链', 'Chinese Hetian nephrite jade necklace, elegant pendant on chain', 9, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (11, 'category', '戒指', 'Chinese Hetian nephrite jade ring, polished jade mounted on band', 10, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (11, 'category', '戒指', 'Chinese Hetian nephrite jade ring, polished jade mounted on band', 10, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (12, 'category', '表带', 'Chinese Hetian nephrite jade watch strap, segmented jade links', 11, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (12, 'category', '表带', 'Chinese Hetian nephrite jade watch strap, segmented jade links', 11, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (93, 'category', '随形', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form', 12, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (13, 'color', '白玉', 'pure white nephrite jade, milky translucent, warm ivory tone', 0, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (13, 'color', '白玉', 'pure white nephrite jade, milky translucent, warm ivory tone', 0, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (14, 'color', '青白玉', 'celadon-white nephrite jade, pale greenish-white, subtle cool tone', 1, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (14, 'color', '青白玉', 'celadon-white nephrite jade, pale greenish-white, subtle cool tone', 1, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (15, 'color', '青玉', 'celadon nephrite jade, muted sage green, natural earthy green', 2, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (15, 'color', '青玉', 'celadon nephrite jade, muted sage green, natural earthy green', 2, '2026-03-27 07:20:55');
@@ -408,10 +280,10 @@ INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (21, 'color', '藕粉', 'lotus-pink nephrite jade, soft blush pink, delicate pastel rose', 8, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (21, 'color', '藕粉', 'lotus-pink nephrite jade, soft blush pink, delicate pastel rose', 8, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (22, 'color', '烟紫', 'smoky purple nephrite jade, muted lavender grey, subtle violet', 9, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (22, 'color', '烟紫', 'smoky purple nephrite jade, muted lavender grey, subtle violet', 9, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (23, 'color', '糖白', 'sugar-white nephrite jade, creamy white with light brown edges', 10, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (23, 'color', '糖白', 'sugar-white nephrite jade, creamy white with light brown edges', 10, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (24, 'view', '效果图', 'three-quarter view, 45-degree angle, hero shot, dramatic perspective showing depth and dimension', 0, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (24, 'view', '效果图', 'three-quarter view at 45-degree angle, hero shot showing the complete jade artwork with depth and dimension, single object on pure white background', 0, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (25, 'view', '正面图', 'front view, straight-on, flat lay centered, facing camera directly', 1, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (25, 'view', '正面图', 'front view, camera positioned directly in front of the object facing it straight-on, showing only the front carved surface, single object on pure white background', 1, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (26, 'view', '侧面图', 'side profile view, 90-degree lateral angle, showing thickness and contour', 2, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (26, 'view', '侧面图', 'side view, camera positioned at exactly 90 degrees to the left of the object, showing the edge profile and thickness, single object on pure white background', 2, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (27, 'view', '背面图', 'back view, rear side, showing reverse surface and texture', 3, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (27, 'view', '背面图', 'back view, camera positioned directly behind the object at 180 degrees, showing the reverse side, single object on pure white background', 3, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (28, 'carving', '浮雕', 'relief carving with raised design emerging from surface', 0, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (28, 'carving', '浮雕', 'relief carving with raised design emerging from surface', 0, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (29, 'carving', '圆雕', 'full three-dimensional round carving, sculptural in the round', 1, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (29, 'carving', '圆雕', 'full three-dimensional round carving, sculptural in the round', 1, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (30, 'carving', '镂空雕', 'openwork pierced carving, intricate hollow cutout patterns', 2, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (30, 'carving', '镂空雕', 'openwork pierced carving, intricate hollow cutout patterns', 2, '2026-03-27 07:20:55');
@@ -436,7 +308,7 @@ INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (49, 'motif', '人物', 'human figure motif, classical Chinese character portrayal', 8, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (49, 'motif', '人物', 'human figure motif, classical Chinese character portrayal', 8, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (50, 'motif', '回纹', 'Greek key fret pattern, Chinese meander geometric border', 9, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (50, 'motif', '回纹', 'Greek key fret pattern, Chinese meander geometric border', 9, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (51, 'motif', '如意', 'Ruyi scepter motif, auspicious cloud-head wish-granting symbol', 10, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (51, 'motif', '如意', 'Ruyi scepter motif, auspicious cloud-head wish-granting symbol', 10, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (52, 'motif', '平安扣', 'Ping\'an buckle motif, smooth circular safety and peace symbol', 11, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (52, 'motif', '平安扣', 'Ping''an buckle motif, smooth circular safety and peace symbol', 11, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (53, 'finish', '高光抛光', 'high-gloss mirror polish, reflective glossy surface', 0, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (53, 'finish', '高光抛光', 'high-gloss mirror polish, reflective glossy surface', 0, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (54, 'finish', '亚光/哑光', 'matte satin finish, soft non-reflective surface', 1, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (54, 'finish', '亚光/哑光', 'matte satin finish, soft non-reflective surface', 1, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (55, 'finish', '磨砂', 'frosted textured finish, fine granular surface', 2, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (55, 'finish', '磨砂', 'frosted textured finish, fine granular surface', 2, '2026-03-27 07:20:55');
@@ -477,32 +349,164 @@ INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (90, 'sub_type', '串珠项链', 'beaded jade strand necklace', 29, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (90, 'sub_type', '串珠项链', 'beaded jade strand necklace', 29, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (91, 'sub_type', '编绳项链', 'braided cord necklace with jade', 30, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (91, 'sub_type', '编绳项链', 'braided cord necklace with jade', 30, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (92, 'sub_type', '毛衣链', 'long sweater chain necklace', 31, '2026-03-27 07:20:55'); INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (92, 'sub_type', '毛衣链', 'long sweater chain necklace', 31, '2026-03-27 07:20:55');
INSERT INTO `prompt_mappings` (`id`, `mapping_type`, `cn_key`, `en_value`, `sort_order`, `updated_at`) VALUES (93, 'category', '随形', 'Chinese Hetian nephrite jade freeform sculpture, organic natural shape following the raw stone contour, artistic interpretation of the original rough jade form', 12, '2026-03-27 11:38:59');
-- ----------------------------
-- Table: prompt_templates
-- ----------------------------
DROP TABLE IF EXISTS `prompt_templates`;
CREATE TABLE `prompt_templates` (
`id` bigint NOT NULL AUTO_INCREMENT,
`template_key` varchar(100) NOT NULL COMMENT '模板键',
`template_value` text NOT NULL COMMENT '模板内容',
`description` varchar(255) DEFAULT NULL COMMENT '说明',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `template_key` (`template_key`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='提示词模板';
-- ========================================
-- prompt_templates 数据 (3 条)
-- ========================================
INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (1, 'main_template', '{subject}, {sub_type}, {color}, {motif}, {carving}, {style}, {finish}, {scene}, {size}, {user_prompt}, {view}, {quality}', '主提示词模板 - 用变量拼接最终prompt。可用变量: {subject}品类主体, {sub_type}子类型, {color}颜色, {motif}题材, {carving}工艺, {style}风格, {finish}表面处理, {scene}用途, {size}尺寸, {user_prompt}用户描述, {view}视角, {quality}质量后缀', '2026-03-27 07:20:55'); INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (1, 'main_template', '{subject}, {sub_type}, {color}, {motif}, {carving}, {style}, {finish}, {scene}, {size}, {user_prompt}, {view}, {quality}', '主提示词模板 - 用变量拼接最终prompt。可用变量: {subject}品类主体, {sub_type}子类型, {color}颜色, {motif}题材, {carving}工艺, {style}风格, {finish}表面处理, {scene}用途, {size}尺寸, {user_prompt}用户描述, {view}视角, {quality}质量后缀', '2026-03-27 07:20:55');
INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (2, 'quality_suffix', 'professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', '质量后缀标签 - 附加在prompt末尾的通用质量描述', '2026-03-27 07:20:55'); INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (2, 'quality_suffix', 'professional jewelry product photography, studio lighting setup, pure white background, ultra-detailed, sharp focus, 8K resolution, photorealistic rendering, high-end commercial quality', '质量后缀标签 - 附加在prompt末尾的通用质量描述', '2026-03-27 07:20:55');
INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (3, 'default_color', 'natural Hetian nephrite jade with warm luster', '未选择颜色时的默认颜色描述', '2026-03-27 07:20:55'); INSERT INTO `prompt_templates` (`id`, `template_key`, `template_value`, `description`, `updated_at`) VALUES (3, 'default_color', 'natural Hetian nephrite jade with warm luster', '未选择颜色时的默认颜色描述', '2026-03-27 07:20:55');
-- ======================================== -- ----------------------------
-- system_configs 数据 (6 条) -- Table: sub_types
-- ======================================== -- ----------------------------
DROP TABLE IF EXISTS `sub_types`;
CREATE TABLE `sub_types` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '子类型ID',
`category_id` int NOT NULL COMMENT '所属品类',
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称',
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
`preview_image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '预览图',
`sort_order` int DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
CONSTRAINT `sub_types_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=69 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (1, 1, '二五牌', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (2, 1, '三角牌', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (3, 1, '三五牌', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (4, 1, '四六牌', NULL, NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (5, 1, '正方形', NULL, NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (6, 1, '椭圆形', NULL, NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (7, 2, '4mm', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (8, 2, '6mm', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (9, 2, '8mm', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (10, 2, '10mm', NULL, NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (11, 2, '12mm', NULL, NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (12, 2, '14mm', NULL, NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (13, 2, '16mm', NULL, NULL, 7);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (14, 2, '18mm', NULL, NULL, 8);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (15, 2, '20mm', NULL, NULL, 9);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (16, 3, '山水手把件', '山水意境题材', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (17, 3, '动物手把件', '动物造型题材', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (18, 3, '瑞兽手把件', '貔貅、麒麟等瑞兽', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (19, 3, '人物手把件', '人物造型题材', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (20, 3, '花鸟手把件', '花鸟自然题材', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (21, 3, '佛像手把件', '佛教题材', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (22, 4, '山水雕刻', '山水意境雕刻', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (23, 4, '花鸟雕刻', '花鸟自然雕刻', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (24, 4, '人物雕刻', '人物造型雕刻', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (25, 4, '佛像雕刻', '佛教题材雕刻', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (26, 4, '瑞兽雕刻', '瑞兽神兽雕刻', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (27, 4, '仿古雕刻', '仿古纹饰雕刻', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (28, 5, '山水摆件', '山水意境摆件', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (29, 5, '人物摆件', '人物造型摆件', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (30, 5, '动物摆件', '动物造型摆件', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (31, 5, '佛像摆件', '佛教题材摆件', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (32, 5, '花鸟摆件', '花鸟自然摆件', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (33, 5, '器皿摆件', '香炉、花瓶等器皿', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (34, 6, '平安镯', '内平外圆,最经典的镯型', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (35, 6, '福镯', '内圆外圆,圆条造型', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (36, 6, '贵妃镯', '椭圆形,贴合手腕', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (37, 6, '美人镯', '条杆纤细,秀气典雅', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (38, 6, '方镯', '方形截面,棱角分明', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (39, 6, '雕花镯', '表面雕刻纹饰', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (40, 7, '圆形耳钉', '经典圆形造型', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (41, 7, '水滴形耳钉', '水滴形优雅造型', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (42, 7, '方形耳钉', '方形简约造型', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (43, 7, '花朵形耳钉', '花朵造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (44, 7, '心形耳钉', '心形浪漫造型', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (45, 7, '几何形耳钉', '几何抽象造型', NULL, 6);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (46, 8, '耳环', '圆环形耳饰', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (47, 8, '耳坠', '垂坠型耳饰', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (48, 8, '耳夹', '无需耳洞的耳饰', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (49, 8, '流苏耳饰', '长款流苏造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (50, 9, '6mm', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (51, 9, '8mm', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (52, 9, '10mm', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (53, 9, '12mm', NULL, NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (54, 9, '14mm', NULL, NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (55, 10, '锁骨链', '短款锁骨链', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (56, 10, '吊坠项链', '搭配玉石吊坠', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (57, 10, '串珠项链', '玉珠串联而成', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (58, 10, '编绳项链', '编织绳搭配玉石', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (59, 10, '毛衣链', '长款毛衣链', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (60, 11, '素面戒指', '光面简约戒指', NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (61, 11, '镶嵌戒指', '金属镶嵌玉石', NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (62, 11, '雕花戒指', '表面雕刻纹饰', NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (63, 11, '扳指', '传统扳指造型', NULL, 4);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (64, 11, '指环', '环形简约指环', NULL, 5);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (65, 12, '18mm', NULL, NULL, 1);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (66, 12, '20mm', NULL, NULL, 2);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (67, 12, '22mm', NULL, NULL, 3);
INSERT INTO `sub_types` (`id`, `category_id`, `name`, `description`, `preview_image`, `sort_order`) VALUES (68, 12, '24mm', NULL, NULL, 4);
-- ----------------------------
-- Table: system_configs
-- ----------------------------
DROP TABLE IF EXISTS `system_configs`;
CREATE TABLE `system_configs` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID',
`config_key` varchar(100) NOT NULL COMMENT '配置键',
`config_value` text COMMENT '配置值',
`description` varchar(255) DEFAULT NULL COMMENT '配置说明',
`config_group` varchar(50) NOT NULL DEFAULT 'general' COMMENT '配置分组',
`is_secret` char(1) NOT NULL DEFAULT 'N' COMMENT '是否敏感信息(Y/N)',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `config_key` (`config_key`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统配置表';
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (1, 'SILICONFLOW_API_KEY', 'sk-gvszgchguyhhpevafchnimpljasvtuxxuoxeilhiqzlhyvpq', 'SiliconFlow API Key', 'ai', 'Y', '2026-03-27 07:43:07'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (1, 'SILICONFLOW_API_KEY', 'sk-gvszgchguyhhpevafchnimpljasvtuxxuoxeilhiqzlhyvpq', 'SiliconFlow API Key', 'ai', 'Y', '2026-03-27 07:43:07');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (2, 'SILICONFLOW_BASE_URL', 'https://api.siliconflow.cn/v1', 'SiliconFlow 接口地址', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (2, 'SILICONFLOW_BASE_URL', 'https://api.siliconflow.cn/v1', 'SiliconFlow 接口地址', 'ai', 'N', '2026-03-27 07:09:05');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (3, 'VOLCENGINE_API_KEY', '8598f9a9-0d0b-4963-b116-d7ef5b230e04', '火山引擎 API Key', 'ai', 'Y', '2026-03-27 08:13:23'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (3, 'VOLCENGINE_API_KEY', '8598f9a9-0d0b-4963-b116-d7ef5b230e04', '火山引擎 API Key', 'ai', 'Y', '2026-03-27 08:13:23');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (4, 'VOLCENGINE_BASE_URL', 'https://ark.cn-beijing.volces.com/api/v3', '火山引擎接口地址', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (4, 'VOLCENGINE_BASE_URL', 'https://ark.cn-beijing.volces.com/api/v3', '火山引擎接口地址', 'ai', 'N', '2026-03-27 07:09:05');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (5, 'AI_IMAGE_MODEL', 'seedream-5.0', '默认AI生图模型 (flux-dev / seedream-4.5)', 'ai', 'N', '2026-03-27 08:20:02'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (5, 'AI_IMAGE_MODEL', 'seedream-5.0', '默认AI生图模型 (flux-dev / seedream-4.5)', 'ai', 'N', '2026-03-27 08:20:02');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (6, 'AI_IMAGE_SIZE', '1024', 'AI生图默认尺寸', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (6, 'AI_IMAGE_SIZE', '1024', 'AI生图默认尺寸', 'ai', 'N', '2026-03-27 07:09:05');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (7, 'TENCENT_SECRET_ID', '', ' SecretId (3D模型生成)', 'ai', 'Y', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (7, 'TRIPO_API_KEY', '', 'Tripo3D API Key (用于图生3D模型)', 'ai', 'Y', '2026-03-27 12:20:57');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (13, 'TENCENT_SECRET_KEY', '', ' SecretKey (3D模型生成)', 'ai', 'Y', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (8, 'VOLC_ACCESS_KEY', 'AKLTOGI4NTIxNTJkZGUzNGUwMmJjMTdmYTcwOTE4ZDE0MDg', '火山引擎 Access Key (用于即梦视频生成)', 'ai', 'Y', '2026-03-27 12:53:19');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (8, 'VOLC_ACCESS_KEY', '', ' Access Key ()', 'ai', 'Y', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (9, 'VOLC_SECRET_KEY', 'TlRJMFpEZG1OMk0wWW1aak5ERTRNamcxT1dKbU5UazNPR00yWXpGbE1tSQ==', '火山引擎 Secret Key (用于即梦视频生成)', 'ai', 'Y', '2026-03-27 12:53:19');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (9, 'VOLC_SECRET_KEY', '', ' Secret Key ()', 'ai', 'Y', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (10, 'VIDEO_PROMPT', '玉雕作品在摄影棚内缓慢旋转360度展示全貌专业珠宝摄影灯光纯白色背景平稳旋转展示正面、侧面、背面各个角度电影级画质', '视频生成默认提示词 (即梦 3.0 Pro 图生视频)', 'ai', 'N', '2026-03-27 13:45:55');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (10, 'VIDEO_PROMPT', '360', ' ( 3.0 Pro )', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (11, 'MODEL3D_PROMPT', '', '3D模型生成默认提示词 (Tripo3D备用)', 'ai', 'N', '2026-03-27 12:55:39');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (11, 'MODEL3D_PROMPT', '', '3D模型生成默认提示词 (3D备用)', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (12, 'VIDEO_FRAMES', '121', '视频帧数: 49=2秒, 121=5秒', 'ai', 'N', '2026-03-27 13:02:32');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (12, 'VIDEO_FRAMES', '121', ': 49=2, 121=5', 'ai', 'N', '2026-03-27 07:09:05'); INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (13, 'TENCENT_SECRET_ID', 'AKID31oQoyyqVwCl7FALL8h9OQHxvhGfrrdB', NULL, 'general', 'N', '2026-03-27 14:26:02');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (14, 'TENCENT_SECRET_KEY', 'QeeR1Umqh9QULyWrzwiHeqrhLoAJF5Wn', NULL, 'general', 'N', '2026-03-27 14:26:02');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (15, 'KLING_ACCESS_KEY', 'Anra4QJQLD9hhbNdpMmypHKhYpta4HYJ', NULL, 'general', 'N', '2026-03-27 16:08:09');
INSERT INTO `system_configs` (`id`, `config_key`, `config_value`, `description`, `config_group`, `is_secret`, `updated_at`) VALUES (16, 'KLING_SECRET_KEY', 'pkLQffCMC4epEb38PkRArBdd4Jg8C3hL', NULL, 'general', 'N', '2026-03-27 16:08:09');
-- ----------------------------
-- Table: users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
`phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
`hashed_password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '加密密码',
`nickname` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
`avatar` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像URL',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否管理员',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `phone` (`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `users` (`id`, `username`, `phone`, `hashed_password`, `nickname`, `avatar`, `is_admin`, `created_at`, `updated_at`) VALUES (1, 'demo', NULL, '$2b$12$bHrcm45CwD0wbQVZIQxOmuZcx/B/MEbGYFTGpPawinDkDWI.jilY2', '演示用户', NULL, 1, '2026-03-27 04:20:30', '2026-03-27 04:20:30');
INSERT INTO `users` (`id`, `username`, `phone`, `hashed_password`, `nickname`, `avatar`, `is_admin`, `created_at`, `updated_at`) VALUES (2, 'test1', NULL, '$2b$12$BG5/I4CVswjNWhfdIP9kJeVqbqLEcWkRq8ioxdpwON7eitL.lSVMW', '测试用户', NULL, 0, '2026-03-27 04:21:36', '2026-03-27 04:21:36');
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;
-- users 表添加 is_admin 字段(如果不存在)
-- ALTER TABLE users ADD COLUMN is_admin TINYINT(1) NOT NULL DEFAULT 0 COMMENT '' AFTER avatar;

Binary file not shown.