fix(admin): 转码媒资 id 按字符串传递并处理空态

This commit is contained in:
Zhou Weidong committed 2026-10-22 14:59:22 +08:00
1 parent a4893c31a2
commit fecc3b4817
6 files changed
+82 -13

No files matched your search

@@ -0,0 +1,18 @@
# 转码媒资 ID 前端精度修复
## 根因
聚合接口把 Snowflake 媒资 ID 以字符串返回,但面板使用
`Number(detail.mediaAssetId)`,超过 `2^53` 后发生精度丢失,手动转码请求了错误 ID。
## 修复
- 聚合响应中的 episodeId/mediaAssetId/taskId 类型改为字符串。
- 父面板不再 Number 归一化,媒体 ID 校验改为字符串 `/\d+/`。
- 手动转码、帧图分页 URL 原样传递字符串。
- mediaAssetId 为空时禁用重新转码并展示明确空态。
## 验证
- 超 2^53 ID 字符串保持逐位一致。
- `pnpm lint:eslint`、`pnpm build:dev`。
+51
View File
@@ -100,3 +100,54 @@ Build successful. Please see dist-dev directory
解读:组件 props/emits、类型检查、ESLint 和开发构建全部通过;
循环 chunk 为项目既有警告。轮询启停、接口路径、分页参数和 locales 文案未改变。
## 2026-10-22 转码媒资 ID 前端精度修复
- 分支:`feature/transcode-media-id-fix-ui`
- 基线:`develop` / `origin/develop` = `a4893c3`
已复现 `Number('2113150398670966786')` 变为 `2113150398670966800`。
本次将媒资、任务 id 全链路按字符串处理,禁止 Number 转换;没有媒资时
禁用重新转码并显示空态。
```text
$ node -e "const id='2113150398670966786'; console.log(String(Number(id)))"
2113150398670966800
```
解读:Snowflake id 超过 JS 安全整数,字符串化后的 Number 已失真。
### 实现
- `EpisodeTranscodeItem` 的 episodeId/mediaAssetId/taskId,以及 rendition/thumbnail
相关 id 类型改为字符串。
- 父面板使用 `detail.mediaAssetId` 原始字符串和正则校验,不再 Number 转换。
- 手动转码与帧图分页接口原样传递字符串 id。
- `mediaAssetId` 为空时按钮保持禁用,空态优先展示后端 errorMsg 原因。
静态检查确认:
```text
$ rg -n "Number\([^\n]*mediaAssetId|mediaAssetId[^\n]*Number\(" \
src/views/contentManagement/upload src/api/admin
(无匹配)
```
### 门禁
```text
$ pnpm lint:eslint
> eslint --fix --ext .js,.ts,.vue ./src
(0 error / 0 warning)
$ pnpm ts:check
> vue-tsc --noEmit --skipLibCheck
(通过)
$ pnpm build:dev
WARN Circular chunk: element-plus -> vue-chunks -> element-plus.
Build successful. Please see dist-dev directory
```
解读:字符串 id 链路、类型检查、ESLint 与开发构建均通过;
循环 chunk 为项目既有警告。
+2 -2
View File
@@ -12,7 +12,7 @@ export const getEpisodeTranscodeApi = (episodeId: number | string) => {
/** 按媒体分页读取帧图;页面切换时复用该接口。 */
export const getMediaThumbnailsApi = (
mediaAssetId: number | string,
mediaAssetId: string,
pageIndex: number,
pageSize: number
) => {
@@ -23,7 +23,7 @@ export const getMediaThumbnailsApi = (
}
/** 手动触发或重转当前分集绑定的媒体。 */
export const triggerMediaTranscodeApi = (mediaAssetId: number | string) => {
export const triggerMediaTranscodeApi = (mediaAssetId: string) => {
return request.post<TranscodeTriggerResult>({
url: `/admin/media/${mediaAssetId}/transcode`
})
+7 -7
View File
@@ -249,7 +249,7 @@ export interface MediaTranscodeProfile {
}
export interface MediaRenditionItem {
id: number
id: string
quality: string
qualityName: string
width: number
@@ -292,8 +292,8 @@ export interface MediaThumbnailSpriteInfo {
}
export interface MediaThumbnailResult {
mediaAssetId?: number | null
episodeId?: number | null
mediaAssetId?: string | null
episodeId?: string | null
/** READY / FAILED / EMPTY / INVALID / NO_EPISODE */
status: string
errorMsg?: string
@@ -304,9 +304,9 @@ export interface MediaThumbnailResult {
}
export interface EpisodeTranscodeItem {
episodeId: number
mediaAssetId?: number | null
taskId?: number | null
episodeId: string
mediaAssetId?: string | null
taskId?: string | null
status: string
progress?: number
retryCount?: number
@@ -316,7 +316,7 @@ export interface EpisodeTranscodeItem {
}
export interface TranscodeTriggerResult {
taskId: number
taskId: string
status: string
}
@@ -24,7 +24,7 @@ import {
} from './episodeTranscodeUi'
const props = defineProps<{
episodeId: number
episodeId: string | number
active: boolean
}>()
@@ -45,8 +45,8 @@ let pollTimer: ReturnType<typeof setTimeout> | null = null
let requestSeq = 0
let thumbnailSeq = 0
const mediaAssetId = computed(() => Number(detail.value?.mediaAssetId || 0))
const hasMedia = computed(() => mediaAssetId.value > 0)
const mediaAssetId = computed(() => detail.value?.mediaAssetId || '')
const hasMedia = computed(() => /^[1-9]\d*$/.test(mediaAssetId.value))
const normalizedStatus = computed(() => String(detail.value?.status || '').toUpperCase())
const renditions = computed(() => detail.value?.renditions || [])
const isProcessing = computed(
@@ -47,7 +47,7 @@ const { t } = useI18n()
</div>
<el-empty
v-else-if="detail && !view.hasMedia"
:description="t('episodeTranscode.noMedia')"
:description="detail.errorMsg || t('episodeTranscode.noMedia')"
:image-size="72"
/>
<template v-else-if="detail">