feat(wordcloud): 前端 R4 类型/api/词云页真实接口 + stickerEdit 线稿真实地址
This commit is contained in:
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import './index.scss'
|
||||
import { getProductById } from '../../../utils/productConfig'
|
||||
import { getDesignList, setDesignList, updateDesign, type DesignItem, type StickerItem } from '../../../utils/store'
|
||||
import { sketchImage } from '../../../utils/api'
|
||||
import { useThemeContext } from '../../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../../hooks/useStatusBar'
|
||||
@@ -263,36 +264,25 @@ export default function StickerEditPage() {
|
||||
})
|
||||
}
|
||||
|
||||
/** 线稿:预留后端AI接口 */
|
||||
const API_BASE = 'https://your-api-domain.com' // ← 填入你的服务器地址
|
||||
|
||||
const handleSketch = () => {
|
||||
/** 线稿:调真实后端接口(POST /api/sketch,带 token);失败降级前端灰度 */
|
||||
const handleSketch = async () => {
|
||||
if (!sticker) return
|
||||
setLoading(true)
|
||||
Taro.uploadFile({
|
||||
url: `${API_BASE}/api/sketch`,
|
||||
filePath: sticker.src,
|
||||
name: 'image',
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
if (data.url) {
|
||||
const newSticker = { ...sticker, src: data.url, edits: { ...sticker.edits, sketchSrc: data.url } }
|
||||
setSticker(newSticker)
|
||||
setTimeout(() => redraw(), 200)
|
||||
Taro.showToast({ title: '线稿生成成功', icon: 'success' })
|
||||
} else {
|
||||
throw new Error('no url')
|
||||
}
|
||||
} catch {
|
||||
// 如果接口不可用,降级为前端灰度+边缘检测
|
||||
applyFrontendSketch()
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
applyFrontendSketch()
|
||||
try {
|
||||
const res = await sketchImage(sticker.src)
|
||||
if (res?.imageUrl) {
|
||||
const newSticker = { ...sticker, src: res.imageUrl, edits: { ...sticker.edits, sketchSrc: res.imageUrl } }
|
||||
setSticker(newSticker)
|
||||
setLoading(false)
|
||||
setTimeout(() => redraw(), 200)
|
||||
Taro.showToast({ title: '线稿生成成功', icon: 'success' })
|
||||
} else {
|
||||
throw new Error('no imageUrl')
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
// 如果接口不可用/失败,降级为前端灰度+边缘检测
|
||||
applyFrontendSketch()
|
||||
}
|
||||
}
|
||||
|
||||
/** 前端线稿降级方案:灰度+反相高对比 */
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import ScrollTopMask from '../../components/ScrollTopMask'
|
||||
import { assetUrl } from '../../utils/asset'
|
||||
import { createWordCloudJob, getWordCloudJob } from '../../utils/api'
|
||||
|
||||
export default function WordCloudPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -19,6 +20,7 @@ export default function WordCloudPage() {
|
||||
const [generatedImage, setGeneratedImage] = useState('')
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [progress, setProgress] = useState(0)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const steps = [
|
||||
{ num: 1, label: '上传底图' },
|
||||
@@ -33,24 +35,53 @@ export default function WordCloudPage() {
|
||||
})
|
||||
}
|
||||
|
||||
const handleGenerate = () => {
|
||||
const handleGenerate = async () => {
|
||||
if (!namesText.trim()) {
|
||||
Taro.showToast({ title: '请先输入名字', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setStep(3); setIsGenerating(true); setProgress(0)
|
||||
const timer = setInterval(() => {
|
||||
setProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(timer); setIsGenerating(false)
|
||||
setGeneratedImage(baseImage)
|
||||
return 100
|
||||
}
|
||||
return prev + Math.random() * 15
|
||||
})
|
||||
}, 1000)
|
||||
if (!baseImage) {
|
||||
Taro.showToast({ title: '请先上传底图', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setStep(3); setIsGenerating(true); setProgress(0); setError('')
|
||||
try {
|
||||
// 底图以 multipart 直接交给后端创建词云任务(不再本地模拟)
|
||||
const { jobId } = await createWordCloudJob(baseImage, namesText.trim())
|
||||
await pollWordCloudJob(jobId)
|
||||
} catch (e) {
|
||||
setIsGenerating(false)
|
||||
setError(e instanceof Error ? e.message : '生成失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** 轮询词云任务:success 取结果图,failed 展示错误;单次网络抖动自动重试 */
|
||||
const pollWordCloudJob = (jobId: string) => new Promise<void>((resolve, reject) => {
|
||||
let retries = 0
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const job = await getWordCloudJob(jobId)
|
||||
setProgress(Math.min(job.progress, 100))
|
||||
if (job.status === 'success') {
|
||||
clearInterval(timer); setIsGenerating(false)
|
||||
setGeneratedImage(job.imageUrl || baseImage)
|
||||
resolve()
|
||||
} else if (job.status === 'failed') {
|
||||
clearInterval(timer); setIsGenerating(false)
|
||||
setError(job.error || '词云生成失败')
|
||||
reject(new Error(job.error || '词云生成失败'))
|
||||
}
|
||||
} catch {
|
||||
// 连续 5 次请求失败才终止,避免瞬时网络问题打断任务
|
||||
retries += 1
|
||||
if (retries > 5) {
|
||||
clearInterval(timer); setIsGenerating(false)
|
||||
reject(new Error('任务状态获取失败,请稍后重试'))
|
||||
}
|
||||
}
|
||||
}, 1500)
|
||||
})
|
||||
|
||||
const handleExportImage = () => {
|
||||
if (!generatedImage) return
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
@@ -148,10 +179,21 @@ export default function WordCloudPage() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 步骤3: 生成中/预览 */}
|
||||
{/* 步骤3: 生成中/结果/失败 */}
|
||||
{step === 3 && (
|
||||
<View className='step-content'>
|
||||
{isGenerating ? (
|
||||
{error ? (
|
||||
<View className='generating-panel'>
|
||||
<Image className='generating-icon' src={assetUrl('/icon/词云生成.png')} mode='aspectFit' />
|
||||
<Text className='generating-title'>生成失败</Text>
|
||||
<Text className='generating-subtitle'>{error}</Text>
|
||||
<View className='action-btns'>
|
||||
<View className='btn-primary' onTap={() => { setError(''); setStep(2) }}>
|
||||
<Text>返回修改名单</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : isGenerating ? (
|
||||
<View className='generating-panel'>
|
||||
<Image className='generating-icon' src={assetUrl('/icon/词云生成.png')} mode='aspectFit' />
|
||||
<Text className='generating-title'>正在生成词云...</Text>
|
||||
|
||||
Reference in New Issue
Block a user