feat: 简化页面布局为4个清晰步骤流程
- 重构index页面为步骤式交互:上传→加载→结果→错误 - 优化用户体验:单一焦点,清晰的状态转换 - 美化UI设计:统一卡片容器,渐变背景,现代化按钮 - 完善提示词:更新为中文手办生成专用描述 - 修复SDK模型名称:gemini-2.5-flash-image - 添加类型安全:getTaskStatus返回string[]类型
This commit is contained in:
parent
77ccaf8acd
commit
faefda3ea2
|
|
@ -1,3 +1,132 @@
|
|||
.index{
|
||||
font-size: 1rem;
|
||||
.index {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
/* 通用步骤容器 */
|
||||
.step-container {
|
||||
background: white;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 80rpx 60rpx;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.step-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 描述文字 */
|
||||
.step-desc {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 60rpx;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 上传按钮 */
|
||||
.upload-btn {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
padding: 30rpx 80rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4);
|
||||
min-width: 300rpx;
|
||||
}
|
||||
|
||||
.upload-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border: 6rpx solid #f3f3f3;
|
||||
border-top: 6rpx solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 40rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 结果网格 */
|
||||
.result-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 100%;
|
||||
height: 280rpx;
|
||||
border-radius: 16rpx;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 重新生成按钮 */
|
||||
.restart-btn {
|
||||
background: linear-gradient(45deg, #52c41a, #73d13d);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx 60rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.restart-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
}
|
||||
|
||||
/* 错误描述 */
|
||||
.error-desc {
|
||||
font-size: 26rpx;
|
||||
color: #ff4d4f;
|
||||
margin-bottom: 50rpx;
|
||||
background: #fff2f0;
|
||||
border: 1rpx solid #ffccc7;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 重试按钮 */
|
||||
.retry-btn {
|
||||
background: linear-gradient(45deg, #ff4d4f, #ff7875);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx 60rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.retry-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
}
|
||||
|
|
@ -1,28 +1,107 @@
|
|||
import { View, Text, Button } from '@tarojs/components'
|
||||
import { useLoad } from '@tarojs/taro'
|
||||
import { View, Text, Button, Image } from '@tarojs/components'
|
||||
import { useLoad, showToast } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import './index.css'
|
||||
import { useAd, useSdk } from '../../hooks/index'
|
||||
import { useSdk } from '../../hooks/index'
|
||||
|
||||
type PageStep = 'upload' | 'loading' | 'result' | 'error'
|
||||
|
||||
interface AppState {
|
||||
step: PageStep;
|
||||
images: string[];
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
|
||||
const sdk = useSdk()
|
||||
const [state, setState] = useState<AppState>({
|
||||
step: 'upload',
|
||||
images: [],
|
||||
error: null
|
||||
})
|
||||
|
||||
useLoad(() => {
|
||||
return () => { }
|
||||
})
|
||||
|
||||
return (
|
||||
<View className='index'>
|
||||
<Text>Hello world Ymm !!!!</Text>
|
||||
<Button onClick={() => {
|
||||
sdk.chooseAndGenerateImage().then(task_id => {
|
||||
console.log({ task_id })
|
||||
return sdk.getTaskStatus(task_id)
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
}}>
|
||||
生图
|
||||
const chooseAndGenerateImage = async () => {
|
||||
setState({ step: 'loading', images: [], error: null })
|
||||
|
||||
try {
|
||||
const task_id = await sdk.chooseAndGenerateImage()
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||
const urls = await sdk.getTaskStatus(task_id)
|
||||
|
||||
setState({ step: 'result', images: urls, error: null })
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : '生成失败'
|
||||
setState({ step: 'error', images: [], error: errorMsg })
|
||||
}
|
||||
}
|
||||
|
||||
const resetToUpload = () => {
|
||||
setState({ step: 'upload', images: [], error: null })
|
||||
}
|
||||
|
||||
// 1. 传图片环节
|
||||
const renderUploadStep = () => (
|
||||
<View className="step-container">
|
||||
<Text className="step-title">🎨 AI手办生成器</Text>
|
||||
<Text className="step-desc">选择图片,AI将为您生成精美手办</Text>
|
||||
<Button className="upload-btn" onClick={chooseAndGenerateImage}>
|
||||
选择图片开始生成
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
|
||||
// 2. Loading环节
|
||||
const renderLoadingStep = () => (
|
||||
<View className="step-container">
|
||||
<View className="loading-spinner" />
|
||||
<Text className="step-title">AI正在生成中...</Text>
|
||||
<Text className="step-desc">请耐心等待,正在为您精心制作</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
// 3. 结果预览环节
|
||||
const renderResultStep = () => (
|
||||
<View className="step-container">
|
||||
<Text className="step-title">✨ 生成完成</Text>
|
||||
<View className="result-grid">
|
||||
{state.images.map((url, index) => (
|
||||
<Image key={index} className="result-image" src={url} mode="aspectFill" />
|
||||
))}
|
||||
</View>
|
||||
<Button className="restart-btn" onClick={resetToUpload}>
|
||||
重新生成
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
|
||||
// 4. 错误环节
|
||||
const renderErrorStep = () => (
|
||||
<View className="step-container">
|
||||
<Text className="step-title">❌ 生成失败</Text>
|
||||
<Text className="error-desc">{state.error}</Text>
|
||||
<Button className="retry-btn" onClick={resetToUpload}>
|
||||
重新尝试
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
|
||||
const renderCurrentStep = () => {
|
||||
switch (state.step) {
|
||||
case 'upload': return renderUploadStep()
|
||||
case 'loading': return renderLoadingStep()
|
||||
case 'result': return renderResultStep()
|
||||
case 'error': return renderErrorStep()
|
||||
default: return renderUploadStep()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='index'>
|
||||
{renderCurrentStep()}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,11 +167,10 @@ export class BowongAISDK {
|
|||
|
||||
// 上传第一张图片
|
||||
const filePath = chooseResult.tempFilePaths[0];
|
||||
const prompt = `将画面中的角色重塑为顶级收藏级树脂手办,全身动态姿势,置于角色主题底座;高精度材质,手工涂装,肌肤纹理与服装材质真实分明。戏剧性硬光为主光源,凸显立体感,无过曝;强效补光消除死黑,细节完整可见。背景为窗边景深模糊,侧后方隐约可见产品包装盒。博物馆级摄影质感,全身细节无损,面部结构精准。禁止:任何2D元素或照搬原图、塑料感、面部模糊、五官错位、细节丢失`
|
||||
return await this.generateImage({
|
||||
img_file: filePath,
|
||||
prompt: `将画面中的角色重塑为顶级收藏级树脂手办,全身动态姿势,置于角色主题底座;高精度材质,手工涂装,肌肤纹理与服装材质真实分明。
|
||||
戏剧性硬光为主光源,凸显立体感,无过曝;强效补光消除死黑,细节完整可见。背景为窗边景深模糊,侧后方隐约可见产品包装盒。
|
||||
博物馆级摄影质感,全身细节无损,面部结构精准。禁止:任何2D元素或照搬原图、塑料感、面部模糊、五官错位、细节丢失`
|
||||
prompt: prompt
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -230,7 +229,7 @@ export class BowongAISDK {
|
|||
async generateImage(params: GenerateImageParams): Promise<string> {
|
||||
const {
|
||||
prompt,
|
||||
model_name = 'gemini-2.5-flash-image-preview',
|
||||
model_name = 'gemini-2.5-flash-image',
|
||||
aspect_ratio = '9:16',
|
||||
mode = 'turbo',
|
||||
webhook_flag = false,
|
||||
|
|
@ -291,7 +290,7 @@ export class BowongAISDK {
|
|||
* @param retryCount 内部重试计数,外部调用不需要传入
|
||||
* @returns Promise<any> 返回任务状态信息
|
||||
*/
|
||||
async getTaskStatus(taskId: string, retryCount: number = 0): Promise<any> {
|
||||
async getTaskStatus(taskId: string, retryCount: number = 0): Promise<string[]> {
|
||||
const url = `${this.baseUrl}/api/custom/task/status?task_id=${taskId}`;
|
||||
const maxRetries = 3;
|
||||
|
||||
|
|
@ -306,7 +305,7 @@ export class BowongAISDK {
|
|||
}
|
||||
|
||||
const result = response.data as ApiResponse;
|
||||
|
||||
console.log({ result })
|
||||
if (!result.status) {
|
||||
throw new Error(result.msg || '查询任务状态失败');
|
||||
}
|
||||
|
|
@ -320,14 +319,14 @@ export class BowongAISDK {
|
|||
|
||||
} catch (error) {
|
||||
console.error(`查询任务状态失败 (第${retryCount + 1}次尝试):`, error);
|
||||
|
||||
|
||||
// 如果重试次数未达到最大值,等待5秒后重试
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`将在5秒后进行第${retryCount + 2}次重试...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
return this.getTaskStatus(taskId, retryCount + 1);
|
||||
}
|
||||
|
||||
|
||||
// 超过最大重试次数,抛出异常
|
||||
throw new Error(`查询任务状态失败,已重试${maxRetries}次: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue