feat(wordcloud): 前端 R4 类型/api/词云页真实接口 + stickerEdit 线稿真实地址

This commit is contained in:
2026-08-12 19:10:41 +08:00
parent 85356e7a39
commit 73fac0cb9c
4 changed files with 252 additions and 54 deletions
+102 -5
View File
@@ -1,7 +1,104 @@
import Taro from '@tarojs/taro'
import http, { BASE_URL, getToken } from '../request'
import type {
CosCredentials,
SketchResult,
WordCloudJob,
WordCloudStatus,
} from '../../types'
/**
* R4 上传接口归属文件
* 后端 COS/wordcloud 适配器就绪后,在这里补充:
* getUploadCredentials / uploadImage / generateWordCloud / getWordCloudJob / sketchImage
* 词云任务使用“创建任务 -> 轮询状态 -> 取结果链接”的异步模型
* R4 上传 / 词云 / 线稿接口。
* 契约:前端与后端见 docs 里 api-contract-v1.md §8;后端与 wordcloud 见 wordcloud-contract.md。
* multipart 上传经 Taro.uploadFile 统一带 Bearer token、解析后端 { code, message, data } 信封;
* 与 request.ts 的 JSON 封装保持一致的成功/失败语义
*/
export {}
interface Envelope<T> {
code: number
message: string
data?: T
}
/** multipart 上传封装:带 token、解析统一信封;业务失败抛 Error(message) */
async function uploadMultipart<T>(
path: string,
filePath: string,
name: string,
formData?: Record<string, string>,
): Promise<T> {
const res = await Taro.uploadFile({
url: `${BASE_URL}${path}`,
filePath,
name,
formData,
header: { Authorization: `Bearer ${getToken()}` },
})
let body: Envelope<T>
try {
body = JSON.parse(res.data)
} catch {
throw new Error('网络异常,请稍后重试')
}
if (body && typeof body.code === 'number' && body.code !== 0) {
throw new Error(body.message || '请求失败')
}
return (body && typeof body.code === 'number' ? body.data : body) as T
}
/** 获取 COS 直传凭证(STS 临时凭证或预签名 URL;小程序不持有永久密钥) */
export function getUploadCredentials(key: string): Promise<CosCredentials> {
return http.get<CosCredentials>(`/api/upload/credentials?key=${encodeURIComponent(key)}`)
}
/**
* 直传 COS:服务端下发预签名 URL 时用 PUT 提交文件字节;否则返回凭证由调用方决定。
* 注:COS 直传的最终语义以联调阶段服务端 upload 模块实现为准(当前后端为占位)。
*/
export async function uploadToCos(localPath: string, credentials: CosCredentials): Promise<string> {
if (!credentials.presignedUrl) {
throw new Error('获取 COS 直传地址失败')
}
const fileSystem = Taro.getFileSystemManager()
const data = fileSystem.readFileSync(localPath)
await Taro.request({
url: credentials.presignedUrl,
method: 'PUT',
data,
header: { 'Content-Type': 'application/octet-stream' },
})
// 直传成功后返回去掉签名参数的对象地址
return credentials.presignedUrl.split('?')[0]
}
/** 线稿:图片 → 处理后图片 URL(失败由调用方降级本地灰度) */
export function sketchImage(localPath: string): Promise<SketchResult> {
return uploadMultipart<SketchResult>('/api/sketch', localPath, 'image')
}
/** 创建词云任务:底图 + 名单 → jobId(异步模型,随后轮询) */
export function createWordCloudJob(
imagePath: string,
names: string,
params?: Record<string, unknown>,
): Promise<{ jobId: string }> {
return uploadMultipart<{ jobId: string }>(
'/api/wordcloud/generate',
imagePath,
'image',
{ names, ...(params ? { params: JSON.stringify(params) } : {}) },
)
}
/** 轮询词云任务状态;success 后 WordCloudJob.imageUrl 为结果图 COS 链接 */
export function getWordCloudJob(jobId: string): Promise<WordCloudJob> {
return http.get<WordCloudJob>(`/api/wordcloud/jobs/${jobId}`)
}
/** 取词云任务结果(后端将 wordcloud 产物转存 COS 后返回) */
export function getWordCloudResult(jobId: string): Promise<{ imageUrl: string; svgUrl?: string }> {
return http.get<{ imageUrl: string; svgUrl?: string }>(`/api/wordcloud/jobs/${jobId}/result`)
}
export type { WordCloudStatus }