feat(web): 07 创作者中心四个页面(我的作品 / 作品编辑 / 收费配置 / 数据看板)
为什么:07 的接口五批都做完了,但创作者在界面上一个都点不到 —— 只能靠 curl。 这批把创作者中心做进后台管理(沿用 AppUsersView 那套 api 封装 + composable + 视图 + 样式与 console.css), 创作者用自己的后台账号登录就能完成"建草稿 → 编辑分集传片 → 提交审核 → 配置收费 → 看数据"的闭环。 - WorkspaceView:主体状态 / 我的作品(含最近一次提交的审核进度)/ 通知列表;新建草稿、按状态给出可提交的动作、撤回 - DramaEditView:作品信息(封面、预告片)、分集增删与上移下移、分片上传(复用 /admin/media)+ 断点续传 (会话存 localStorage,中断后「继续上传」接着传)、产物挂载、提交记录与撤回;非草稿/已下线状态整页转只读 - PriceConfigView:整剧免费集数与解锁金币 + 逐集免费/收费/试看秒数;已上线只读(需先申请下线) - DashboardView:作品 / 播放 / 互动 / 解锁收入四组指标,取不到的显示「暂无数据源」+ notes 口径说明 - 时间列统一 yyyy-MM-dd HH:mm:ss;表格列与后端字段一一对应,不在前端编字段 - media.ts 补上传进度接口;uploader 补 resumeUpload(先查服务端进度再续传)
This commit is contained in:
1 parent
f037d243cd
commit
0a2e3e0b81
13 files changed
+2113
-1
No files matched your search
+51
-1
@@ -1,4 +1,9 @@
|
||||
import { completeUploadApi, initUploadApi, uploadChunkApi } from '@/api/admin/media'
|
||||
import {
|
||||
completeUploadApi,
|
||||
getUploadProgressApi,
|
||||
initUploadApi,
|
||||
uploadChunkApi
|
||||
} from '@/api/admin/media'
|
||||
import type { MediaAsset } from '@/api/admin/types'
|
||||
|
||||
export interface UploadFileOptions {
|
||||
@@ -9,6 +14,8 @@ export interface UploadFileOptions {
|
||||
/** 视频时长(秒),由调用方探测后传入 */
|
||||
durationSec?: number
|
||||
onProgress?: (percent: number) => void
|
||||
/** init 建好会话就回调一次:调用方可以把它记下来,失败/刷新后走 resumeUpload 续传 */
|
||||
onSession?: (uploadId: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,6 +30,7 @@ export async function uploadFile(file: File, options: UploadFileOptions = {}): P
|
||||
bizType
|
||||
})
|
||||
const info = initRes.data
|
||||
options.onSession?.(info.uploadId)
|
||||
const total = Math.max(info.totalChunks, 1)
|
||||
const pending: number[] = Array.from({ length: total }, (_, index) => index)
|
||||
let finished = 0
|
||||
@@ -45,6 +53,48 @@ export async function uploadFile(file: File, options: UploadFileOptions = {}): P
|
||||
return doneRes.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 断点续传:先问服务端传到第几片,从断点接着传,再合并。
|
||||
*
|
||||
* 服务端只记账"已收到多少片",不回传具体是哪几片,所以这里按"已收片数之后的分片全部重传"处理 ——
|
||||
* 重传同一片是幂等覆盖,不会产生脏数据;片数落后于实际时最多多传几片。
|
||||
*/
|
||||
export async function resumeUpload(
|
||||
file: File,
|
||||
uploadId: string,
|
||||
options: UploadFileOptions = {}
|
||||
): Promise<MediaAsset> {
|
||||
const { concurrency = 3, onProgress } = options
|
||||
const progress = (await getUploadProgressApi(uploadId)).data
|
||||
const total = Math.max(progress.totalChunks, 1)
|
||||
const received = Math.min(progress.receivedChunks || 0, total)
|
||||
const pending: number[] = []
|
||||
for (let index = received; index < total; index += 1) {
|
||||
pending.push(index)
|
||||
}
|
||||
let finished = received
|
||||
onProgress?.(Math.min(95, Math.round((finished / total) * 95)))
|
||||
|
||||
const worker = async () => {
|
||||
for (;;) {
|
||||
const index = pending.shift()
|
||||
if (index === undefined) return
|
||||
const start = index * progress.chunkSize
|
||||
const chunk = file.slice(start, Math.min(start + progress.chunkSize, file.size))
|
||||
await uploadChunkApi(uploadId, index, chunk, file.name)
|
||||
finished += 1
|
||||
onProgress?.(Math.min(95, Math.round((finished / total) * 95)))
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from({ length: Math.min(concurrency, Math.max(pending.length, 1)) }, worker)
|
||||
)
|
||||
const doneRes = await completeUploadApi(uploadId, options.durationSec)
|
||||
onProgress?.(100)
|
||||
return doneRes.data
|
||||
}
|
||||
|
||||
/** 读取本地视频时长,取不到时返回 0。 */
|
||||
export function probeVideoDuration(file: File): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
Reference in new issue
Block a user