feat: 简化页面布局为4个清晰步骤流程

- 重构index页面为步骤式交互:上传→加载→结果→错误
- 优化用户体验:单一焦点,清晰的状态转换
- 美化UI设计:统一卡片容器,渐变背景,现代化按钮
- 完善提示词:更新为中文手办生成专用描述
- 修复SDK模型名称:gemini-2.5-flash-image
- 添加类型安全:getTaskStatus返回string[]类型
This commit is contained in:
imeepos 2025-09-01 15:16:52 +08:00
parent 77ccaf8acd
commit faefda3ea2
3 changed files with 233 additions and 26 deletions

View File

@ -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);
}

View File

@ -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>
)
}

View File

@ -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)}`);
}