Files
msh-system/msh_single_uniapp/pages/ai-generate/result.vue

1143 lines
26 KiB
Vue
Raw Normal View History

<template>
<view class="result-container" :style="{ paddingTop: statusBarHeight + 'px' }">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-left" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<view class="nav-title">生成结果</view>
<view class="nav-right"></view>
</view>
<!-- AI生成内容标识 -->
<view class="ai-notice">
<text class="ai-notice-icon">🤖</text>
<text class="ai-notice-text">图片由AI生成结果仅供参考</text>
</view>
<scroll-view scroll-y class="main-content">
<!-- 生成进度提示生成中状态 -->
<view class="generating-tip" v-if="isGenerating">
<view class="generating-animation">
<view class="dot dot-1"></view>
<view class="dot dot-2"></view>
<view class="dot dot-3"></view>
</view>
<text class="generating-text">正在创作中...</text>
<text class="generating-time">预计{{ estimatedTime }}</text>
</view>
<!-- 图片结果展示 -->
<view class="result-section" v-if="!isGenerating">
<!-- 单张图片显示 -->
<view class="single-image-container" v-if="generatedImages.length > 0">
<view class="image-item" @click="previewImage(0)">
<image
:src="generatedImages[0].url"
mode="aspectFit"
class="result-image"
:class="{ 'loading': generatedImages[0].loading }"
@load="onImageLoad"
@error="onImageError"
:show-menu-by-longpress="true"
></image>
<!-- 加载动画 -->
<view class="image-loading" v-if="generatedImages[0].loading">
<view class="loading-spinner"></view>
</view>
<!-- 图片加载失败提示 -->
<view class="image-error" v-if="imageLoadError">
<text class="error-text">图片加载</text>
<text class="error-url">{{ generatedImages[0].url }}</text>
</view>
</view>
</view>
<!-- 调试信息 -->
<view class="debug-info" v-if="showDebugInfo && generatedImages.length > 0">
<text class="debug-title">调试信息:</text>
<text class="debug-text">图片数量: {{ generatedImages.length }}</text>
<text class="debug-text">图片URL: {{ generatedImages[0].url }}</text>
<text class="debug-text">标题: {{ promptText }}</text>
<text class="debug-text">加载状态: {{ generatedImages[0].loading ? '加载中' : '已完成' }}</text>
<text class="debug-text">错误状态: {{ imageLoadError ? '失败' : '正常' }}</text>
<button class="debug-btn" @click="testImageLoad">测试图片加载</button>
<button class="debug-btn" @click="showDebugInfo = false">关闭调试</button>
</view>
<!-- 生成信息 -->
<view class="info-section">
<view class="info-header" @click="showPrompt = !showPrompt">
<text class="info-title">生成描述</text>
<text class="iconfont" :class="showPrompt ? 'icon-xiangshang' : 'icon-xiangxia'"></text>
</view>
<view class="prompt-content" v-if="showPrompt">
<!-- 直接显示编辑输入框 -->
<textarea
v-model="promptText"
class="prompt-input"
placeholder="请输入生成描述"
:maxlength="800"
:auto-height="true"
></textarea>
</view>
</view>
<!-- 配置选项 -->
<view class="section config-section">
<!-- 生成比例选择 -->
<view class="ratio-section">
<view class="ratio-header" @click="showRatioList = !showRatioList">
<view class="ratio-title-wrapper">
<text class="ratio-title">生成比例</text>
<text class="ratio-current" v-if="!showRatioList">{{ selectedRatio }}</text>
</view>
<text class="iconfont" :class="showRatioList ? 'icon-xiangshang' : 'icon-xiangxia'"></text>
</view>
<view class="ratio-content" v-if="showRatioList">
<view class="ratio-list">
<view
v-for="(ratio, index) in ratios"
:key="index"
:class="['ratio-item', selectedRatio === ratio.value ? 'active' : '']"
@click="selectRatio(ratio.value)"
>
<view class="ratio-box" :style="{ aspectRatio: ratio.aspect }"></view>
<text class="ratio-text">{{ ratio.value }}</text>
</view>
</view>
</view>
</view>
<!-- 联想词助手开关 -->
<view class="config-item assistant-item">
<view class="assistant-info">
<text class="assistant-title">联想词助手</text>
<text class="assistant-desc">AI智能补充和优化提示词</text>
</view>
<switch
:checked="enableAssistant"
@change="toggleAssistant"
color="#42ca4d"
/>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-section">
<button class="action-btn primary" @click="handleReGenerate">
<text class="btn-text">再次生成</text>
</button>
</view>
</view>
<!-- 底部占位 -->
<view class="bottom-placeholder"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="footer-bar" v-if="!isGenerating">
<button class="footer-btn secondary" @click="handleShare">
<text class="iconfont icon-fenxiang"></text>
<text class="btn-text">分享</text>
</button>
<button class="footer-btn secondary" @click="handleDownload">
<text class="iconfont icon-xiazai"></text>
<text class="btn-text">下载</text>
</button>
<button class="footer-btn primary" @click="handleApply">
<text class="btn-text">应用作品</text>
</button>
</view>
<!-- 图片预览弹窗 -->
<view class="preview-overlay" v-if="showPreview" @click="closePreview">
<swiper
class="preview-swiper"
:current="currentPreviewIndex"
@change="onSwiperChange"
@click.stop
>
<swiper-item v-for="(img, index) in generatedImages" :key="index">
<view class="swiper-item-content">
<image
:src="img.url"
mode="aspectFit"
class="preview-full-image"
></image>
</view>
</swiper-item>
</swiper>
<!-- 预览工具栏 -->
<view class="preview-toolbar" @click.stop>
<view class="toolbar-left">
<text class="page-indicator">{{ currentPreviewIndex + 1 }}/{{ generatedImages.length }}</text>
</view>
<view class="toolbar-right">
<view class="tool-btn" @click.stop="downloadSingleImage(currentPreviewIndex)">
<text class="iconfont icon-xiazai"></text>
</view>
<view class="tool-btn" @click.stop="closePreview">
<text class="iconfont icon-guanbi"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import api from '@/api/models-api.js'
export default {
data() {
return {
statusBarHeight: 0,
// 生成状态
isGenerating: false,
estimatedTime: 30,
// 生成参数
model: '4.0',
promptText: '按照参考图的宽高比例,生成一个新的"会员礼包"的商城小程序banner展示沉香类商品电商活动限时特惠满赠香薰',
referenceImages: [], // 参考图列表
// 生成比例
selectedRatio: '9:16',
ratios: [
{ value: '9:16', aspect: '9/16', label: '竖屏' },
{ value: '3:4', aspect: '3/4', label: '竖屏' },
{ value: '2:3', aspect: '2/3', label: '竖屏' },
{ value: '1:1', aspect: '1/1', label: '方形' },
{ value: '3:2', aspect: '3/2', label: '横屏' }
],
showRatioList: false, // 比例列表展开状态
// 联想词助手
enableAssistant: true,
// 生成结果
generatedImages: [
{
url: '/static/images/demo-result-1.jpg',
loading: false
}
],
// UI控制
showPrompt: true,
showPreview: false,
currentPreviewIndex: 0,
imageLoadError: false,
showDebugInfo: false // 开启调试信息显示
};
},
onLoad(options) {
console.log('result.vue onLoad 接收参数:', options);
this.initPage();
// 接收生成参数
if (options.promptText) {
this.promptText = decodeURIComponent(options.promptText);
}
if (options.ratio) {
this.selectedRatio = options.ratio;
}
if (options.model) {
this.model = options.model;
}
if (options.referenceImages) {
try {
this.referenceImages = JSON.parse(decodeURIComponent(options.referenceImages));
} catch (e) {
console.error('解析参考图失败:', e);
}
}
// 如果是新生成,显示生成动画
if (options.generate === 'true') {
this.startGenerating();
return;
}
// 如果是从灵感广场跳转过来,加载对应的图片
if (options.image) {
const imageUrl = decodeURIComponent(options.image);
const title = options.title ? decodeURIComponent(options.title) : '未命名作品';
console.log('解码后的图片URL:', imageUrl);
console.log('解码后的标题:', title);
// 更新显示数据
this.promptText = title;
this.generatedImages = [
{
url: imageUrl,
loading: false
}
];
// 将图片URL作为参考图
this.referenceImages = [imageUrl];
console.log('更新后的 generatedImages:', this.generatedImages);
console.log('更新后的 referenceImages:', this.referenceImages);
} else {
console.warn('未接收到 options.image 参数');
}
},
methods: {
initPage() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
},
// 图片加载成功
onImageLoad(e) {
console.log('图片加载成功:', e);
this.imageLoadError = false;
if (this.generatedImages.length > 0) {
this.generatedImages[0].loading = false;
}
},
// 图片加载失败
onImageError(e) {
console.error('图片加载失败:', e);
if (this.generatedImages.length > 0) {
console.error('失败的图片URL:', this.generatedImages[0].url);
this.generatedImages[0].loading = false;
}
this.imageLoadError = true;
uni.showToast({
title: '图片加载失败',
icon: 'none',
duration: 2000
});
},
// 测试图片加载
testImageLoad() {
if (this.generatedImages.length > 0) {
const imageUrl = this.generatedImages[0].url;
console.log('测试图片URL:', imageUrl);
// 尝试重新加载图片
this.imageLoadError = false;
this.generatedImages[0].loading = true;
// 强制刷新
const tempUrl = this.generatedImages[0].url;
this.generatedImages[0].url = '';
this.$nextTick(() => {
this.generatedImages[0].url = tempUrl;
this.generatedImages[0].loading = false;
});
uni.showToast({
title: '正在重新加载图片',
icon: 'none'
});
}
},
// 开始生成
startGenerating() {
this.isGenerating = true;
// 模拟生成过程
const timer = setInterval(() => {
this.estimatedTime--;
if (this.estimatedTime <= 0) {
clearInterval(timer);
this.isGenerating = false;
uni.showToast({
title: '生成完成',
icon: 'success'
});
setTimeout(() => {
uni.navigateTo({ url: '/pages/ai-generate/inspiration' });
}, 600);
}
}, 1000);
},
// 返回
goBack() {
uni.navigateBack();
},
// 预览图片
previewImage(index) {
this.currentPreviewIndex = index;
this.showPreview = true;
},
// 关闭预览
closePreview() {
this.showPreview = false;
},
// 切换预览图片
onSwiperChange(e) {
this.currentPreviewIndex = e.detail.current;
},
// 选择比例
selectRatio(value) {
this.selectedRatio = value;
},
// 切换联想词助手
toggleAssistant(e) {
this.enableAssistant = e.detail.value;
},
// 再次生成
async handleReGenerate() {
// 验证提示词
if (!this.promptText.trim()) {
uni.showToast({
title: '请输入生成描述',
icon: 'none'
});
return;
}
// 验证是否有参考图
if (this.referenceImages.length === 0) {
uni.showToast({
title: '缺少参考图,请返回重新配置',
icon: 'none',
duration: 2000
});
return;
}
try {
uni.showLoading({
title: '生成中...'
});
// 调用图片编辑API
const response = await api.createImageEditTask({
prompt: this.promptText,
image_urls: this.referenceImages,
output_format: 'png',
image_size: this.selectedRatio,
title: this.promptText
});
console.log('图片编辑任务创建成功:', response);
uni.hideLoading();
// 显示生成动画
this.startGenerating();
uni.showToast({
title: '任务已提交',
icon: 'success'
});
} catch (error) {
console.error('图片编辑任务创建失败:', error);
uni.hideLoading();
uni.showToast({
title: error.message || '生成失败,请重试',
icon: 'none',
duration: 2000
});
}
},
// 分享
handleShare() {
uni.showActionSheet({
itemList: ['分享到微信', '分享到朋友圈', '分享到微博'],
success: (res) => {
uni.showToast({
title: '分享功能开发中',
icon: 'none'
});
}
});
},
// 下载
handleDownload() {
if (this.generatedImages.length === 0) {
uni.showToast({
title: '暂无图片',
icon: 'none'
});
return;
}
uni.showLoading({
title: '下载中...'
});
// TODO: 实现图片下载
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '已保存到相册',
icon: 'success'
});
}, 1500);
},
// 下载单张图片
downloadSingleImage(index) {
uni.showLoading({
title: '下载中...'
});
// TODO: 实现单张下载
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '已保存到相册',
icon: 'success'
});
}, 1000);
},
// 应用到商品
handleApply() {
uni.showActionSheet({
itemList: ['设置为商品主图', '添加到商品详情', '用作活动Banner', '生成推广海报'],
success: (res) => {
uni.showToast({
title: '应用成功',
icon: 'success'
});
}
});
}
}
};
</script>
<style lang="scss" scoped>
.result-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%);
position: relative;
overflow-x: hidden;
}
// 顶部导航
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
.nav-left,
.nav-right {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 20px;
color: #ffffff;
}
}
.nav-title {
font-size: 17px;
font-weight: 500;
color: #ffffff;
}
}
// AI生成内容标识
.ai-notice {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px 16px;
margin: 8px 16px;
background: rgba(66, 202, 77, 0.1);
border: 1px solid rgba(66, 202, 77, 0.3);
border-radius: 8px;
backdrop-filter: blur(10px);
.ai-notice-icon {
font-size: 16px;
}
.ai-notice-text {
font-size: 12px;
color: #42ca4d;
line-height: 1.4;
}
}
// 主内容区
.main-content {
height: calc(100vh - 44px - 80px);
padding: 0 16px; width:92%
}
// 生成中提示
.generating-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 0;
.generating-animation {
display: flex;
gap: 12px;
margin-bottom: 24px;
.dot {
width: 12px;
height: 12px;
background: #42ca4d;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
&.dot-1 {
animation-delay: -0.32s;
}
&.dot-2 {
animation-delay: -0.16s;
}
&.dot-3 {
animation-delay: 0s;
}
}
}
.generating-text {
font-size: 16px;
color: #ffffff;
margin-bottom: 8px;
}
.generating-time {
font-size: 13px;
color: #8f9bb3;
}
}
// 结果区域
.result-section {
padding-top: 12px;
}
// 单张图片容器
.single-image-container {
width: 100%;
margin-bottom: 12px;
.image-item {
width: 100%;
min-height: 200px;
border-radius: 16px;
overflow: hidden;
position: relative;
background: rgba(61, 68, 88, 0.3);
.result-image {
width: 100%;
display: block;
&.loading {
opacity: 0.3;
}
}
.image-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.2);
border-top-color: #42ca4d;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
}
.image-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 12px;
max-width: 80%;
.error-icon {
font-size: 48px;
margin-bottom: 12px;
}
.error-text {
font-size: 14px;
color: #ff6b6b;
margin-bottom: 8px;
}
.error-url {
font-size: 11px;
color: #8f9bb3;
word-break: break-all;
text-align: center;
}
}
.image-tags {
position: absolute;
bottom: 12px;
left: 12px;
display: flex;
gap: 8px;
.tag {
padding: 6px 12px;
background: rgba(0, 0, 0, 0.7);
border-radius: 6px;
font-size: 12px;
color: #ffffff;
backdrop-filter: blur(10px);
}
}
}
}
// 调试信息
.debug-info {
background: rgba(61, 68, 88, 0.3);
border: 2px solid #42ca4d;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
.debug-title {
display: block;
font-size: 14px;
font-weight: 600;
color: #42ca4d;
margin-bottom: 12px;
}
.debug-text {
display: block;
font-size: 12px;
color: #c5cee0;
line-height: 1.8;
margin-bottom: 6px;
word-break: break-all;
}
.debug-btn {
margin-top: 12px;
margin-right: 8px;
padding: 8px 16px;
background: #42ca4d;
color: #ffffff;
border: none;
border-radius: 6px;
font-size: 12px;
}
}
// 信息区域
.info-section {
background: rgba(61, 68, 88, 0.3);
border-radius: 16px;
padding: 16px;
margin-bottom: 16px;
.info-header {
display: flex;
align-items: center;
justify-content: space-between;
.info-title {
font-size: 15px;
font-weight: 500;
color: #ffffff;
}
.iconfont {
font-size: 14px;
color: #8f9bb3;
}
}
.prompt-content {
margin-top: 12px;
.prompt-text {
display: block;
font-size: 14px;
color: #c5cee0;
line-height: 1.6;
margin-bottom: 12px;
}
.prompt-input {
width: 100%;
min-height: 100px;
padding: 12px;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(143, 155, 179, 0.3);
border-radius: 8px;
font-size: 14px;
color: #ffffff;
line-height: 1.6;
box-sizing: border-box;
&:focus {
border-color: #42ca4d;
background: rgba(0, 0, 0, 0.3);
}
}
.info-meta {
display: flex;
align-items: center;
gap: 8px;
.meta-item {
font-size: 12px;
color: #8f9bb3;
}
.meta-divider {
color: #4a5568;
}
}
}
}
// 配置选项
.config-section {
.config-item {
margin-bottom: 20px;
}
}
// 比例选择区域
.ratio-section {
background: rgba(61, 68, 88, 0.3);
border-radius: 16px;
padding: 16px;
margin-bottom: 16px;
.ratio-header {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.ratio-title-wrapper {
display: flex;
align-items: center;
gap: 8px;
.ratio-title {
font-size: 15px;
font-weight: 500;
color: #ffffff;
}
.ratio-current {
font-size: 14px;
color: #42ca4d;
font-weight: 500;
}
}
.iconfont {
font-size: 14px;
color: #8f9bb3;
}
}
.ratio-content {
margin-top: 12px;
}
}
// 比例列表
.ratio-list {
display: flex;
gap: 12px;
.ratio-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px;
background: rgba(61, 68, 88, 0.3);
border-radius: 12px;
border: 2px solid transparent;
.ratio-box {
width: 32px;
background: #8f9bb3;
border-radius: 4px;
margin-bottom: 6px;
}
.ratio-text {
font-size: 12px;
color: #8f9bb3;
}
&.active {
background: rgba(6, 67, 14, 0.1);
border-color: #42ca4d;
.ratio-box {
background: #42ca4d;
}
.ratio-text {
color: #42ca4d;
}
}
}
}
// 联想词助手
.assistant-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 11px 16px;
background: rgba(61, 68, 88, 0.3);
border-radius: 12px;
.assistant-info {
flex: 1;
.assistant-title {
display: block;
font-size: 15px;
color: #ffffff;
margin-bottom: 4px;
}
.assistant-desc {
display: block;
font-size: 12px;
color: #8f9bb3;
}
}
}
// 操作按钮
.action-section {
display: flex;
gap: 12px;
margin-bottom: 16px;
.action-btn {
flex: 1;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
border-radius: 12px;
border: none;
&.primary {
background: linear-gradient(135deg, #42ca4d 0%, #38b045 100%);
.btn-text {
color: #ffffff;
font-weight: 500;
}
}
.btn-text {
font-size: 16px;
}
}
}
// 底部占位
.bottom-placeholder {
height: 20px;
}
// 底部操作栏
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 12px;
padding: 16px;
background: linear-gradient(0deg, rgba(10, 14, 26, 0.95) 0%, rgba(10, 14, 26, 0.8) 100%);
backdrop-filter: blur(20px);
.footer-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
height: 48px;
border-radius: 12px;
border: none;
&.secondary {
flex: 0 0 80px;
background: rgba(61, 68, 88, 0.4);
.iconfont,
.btn-text {
color: #ffffff;
}
}
&.primary {
flex: 1;
background: linear-gradient(135deg, #42ca4d 0%, #38b045 100%);
.btn-text {
color: #ffffff;
font-weight: 500;
}
}
.iconfont {
font-size: 18px;
}
.btn-text {
font-size: 14px;
}
}
}
// 图片预览
.preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.95);
z-index: 9999;
.preview-swiper {
width: 100%;
height: 100%;
.swiper-item-content {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.preview-full-image {
width: 100%;
height: 100%;
}
}
}
.preview-toolbar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%);
.toolbar-left {
.page-indicator {
font-size: 14px;
color: #ffffff;
}
}
.toolbar-right {
display: flex;
gap: 20px;
.tool-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
backdrop-filter: blur(10px);
.iconfont {
font-size: 20px;
color: #ffffff;
}
}
}
}
}
// 动画
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>