发布图片加稳定 id,截图列表不再按 name-index 做 key
- GamePublishImage 新增 id,本地新选图与线上沿用图各自铸一次 - 截图列表 key 改用 image.id,删除/重排时不再因 index 位移重建节点,同名文件也能区分 - 补同名本地图 id 互异的单测
This commit is contained in:
@@ -181,10 +181,7 @@ export function GamePublishMediaFieldset({
|
||||
{screenshots.length > 0 ? (
|
||||
<ul className="game-publish-media__strip">
|
||||
{screenshots.map((image, index) => (
|
||||
<li
|
||||
key={`${image.objectKey ?? image.name}-${index}`}
|
||||
className="game-publish-media__shot"
|
||||
>
|
||||
<li key={image.id} className="game-publish-media__shot">
|
||||
{image.src ? (
|
||||
<img src={image.src} alt={`截图 ${index + 1} 预览`} />
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createLocalGamePublishImage } from './gamePublishAssets';
|
||||
|
||||
describe('createLocalGamePublishImage', () => {
|
||||
it('本地新选图各自带稳定且不同的 id', () => {
|
||||
const first = createLocalGamePublishImage(
|
||||
new File(['a'], '同名.png', { type: 'image/png' }),
|
||||
'screenshot',
|
||||
);
|
||||
const second = createLocalGamePublishImage(
|
||||
new File(['b'], '同名.png', { type: 'image/png' }),
|
||||
'screenshot',
|
||||
);
|
||||
|
||||
// 同名的本地文件不能只靠 `name-index` 区分:id 必须是每次选择各自铸出来的一份。
|
||||
expect(first.id).toBeTruthy();
|
||||
expect(second.id).toBeTruthy();
|
||||
expect(first.id).not.toBe(second.id);
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,11 @@ export type GamePublishImageKind = 'cover' | 'screenshot';
|
||||
* `URL.createObjectURL`,线上图用 `/api/game-distribution/media/read-url` 换签),不参与提交。
|
||||
*/
|
||||
export type GamePublishImage = {
|
||||
/**
|
||||
* 稳定标识:本地新选图与线上沿用图各自生成一次。截图删除/重排时 React 不会因 `index`
|
||||
* 位移而重建节点,同名文件也能区分开。
|
||||
*/
|
||||
id: string;
|
||||
file?: File;
|
||||
objectKey?: string;
|
||||
/** 预览地址;换签失败或本地环境不支持时为空字符串,只显示占位。 */
|
||||
@@ -29,6 +34,17 @@ export type GamePublishImage = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** 生成发布图片的稳定标识;测试环境缺 `crypto.randomUUID` 时退回时间戳 + 随机串。 */
|
||||
export function createGamePublishImageId(): string {
|
||||
if (
|
||||
typeof crypto !== 'undefined' &&
|
||||
typeof crypto.randomUUID === 'function'
|
||||
) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `gpimg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number) {
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
||||
}
|
||||
@@ -76,6 +92,7 @@ export function createLocalGamePublishImage(
|
||||
): GamePublishImage {
|
||||
validateGamePublishImageFile(file, kind);
|
||||
return {
|
||||
id: createGamePublishImageId(),
|
||||
file,
|
||||
src: createLocalPreviewUrl(file),
|
||||
name: file.name.trim() || resolveGamePublishImageKindLabel(kind),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { GameDistributionVersionDetail } from '../../../packages/shared/src/contracts/gameDistribution';
|
||||
import {
|
||||
createGamePublishImageId,
|
||||
type GamePublishImage,
|
||||
resolveGamePublishImagePreview,
|
||||
} from './gamePublishAssets';
|
||||
@@ -31,6 +32,7 @@ export async function applyVersionMediaDefaults(
|
||||
detail.game.coverObjectKey?.trim() || frozen?.coverObjectKey?.trim() || '';
|
||||
const coverName = detail.game.title.trim() || '当前封面';
|
||||
const buildCover = (src: string): GamePublishImage => ({
|
||||
id: createGamePublishImageId(),
|
||||
objectKey: coverObjectKey,
|
||||
src,
|
||||
name: coverName,
|
||||
@@ -47,6 +49,7 @@ export async function applyVersionMediaDefaults(
|
||||
apply.setCover(() => (coverObjectKey ? buildCover('') : null));
|
||||
apply.setScreenshots(() =>
|
||||
screenshotObjectKeys.map((objectKey, index) => ({
|
||||
id: createGamePublishImageId(),
|
||||
objectKey,
|
||||
src: '',
|
||||
name: `截图 ${index + 1}`,
|
||||
|
||||
Reference in New Issue
Block a user