Files
opendance-web/web-server/src/common/enums.ts
T
opendance 584b7eaca9 feat(scenes): 场景管理后端 + 契约 0.5.0/0.7.0/0.4.0(T-WEB-015)
契约先行(三端逐字节一致,已同步 windows/android):
- rest-api -> 0.5.0:新增 GET/POST /api/scenes、GET/PATCH/DELETE /api/scenes/{id}
- data-models -> 0.7.0:新增 §7 Scene 与 7.1 SceneConfig(物理/环境/出生点/相机)
- database -> 0.4.0:新增 scenes 表(config/stats 用 MEDIUMTEXT,迁移可逆)

后端:
- entities/scene.entity.ts + 迁移 AddScenes1789650000000(scenes 表)
- scenes/scene-config.ts:SceneConfig 定义 + 默认值 + **边界收敛**(越界夹紧、
  非法枚举回退),保证客户端拿到的参数一定可解释;服务端只存参数不跑物理
- scenes/scenes.service.ts:上传(.glb/.gltf 直接导入;.zip/.pmx/.fbx 转 GLB,
  场景不做 humanoid 重绑定、不转 VRM)、列表/详情/更新/软删除、缩略图复用模型
  光栅化器、分析复用模型检查器、config 同时接受 JSON 字符串与平铺字段
- scenes/scenes.controller.ts + scenes.module.ts(app.module 注册)
- 审核复用 /api/reviews/:type/:id:TargetType.Scene 接入待审列表与状态流转
- catalog/export 增 scenes[],目录指纹纳入场景变更
- 枚举:TargetType.Scene、AssetKind.Scene / SceneThumbnail

测试:新增 test/scene-config.spec.ts 6 项(默认值/夹紧/枚举回退/出生点上限/颜色/
序列化);规模 19 文件 / 243 项;check-routes 35 条一致。版本 0.6.0。

注:管理端场景页(GLB 预览 + 物理参数可视化)与 Windows 场景选择留待下一轮。
2026-09-17 00:27:08 +08:00

133 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 跨模块共享枚举。
* 与 contracts/data-models.md 中的枚举**逐字对应**,改动必须同步契约版本。
*/
export enum SourceType {
/** 原创 */
Original = 'original',
/** 已获授权 */
Licensed = 'licensed',
/** Creative Commons */
Cc = 'cc',
/** 公共领域 */
PublicDomain = 'public_domain',
/** 用户上传(需审核) */
UserUpload = 'user_upload',
/** 其他(须在 sourceName 说明) */
Other = 'other',
}
export enum PublishStatus {
Draft = 'draft',
Pending = 'pending',
Published = 'published',
Rejected = 'rejected',
}
export enum MotionFormat {
Bvh = 'bvh',
/** MikuMikuDance 动作数据(二进制,含 Shift-JIS 骨骼名与四元数关键帧) */
Vmd = 'vmd',
Fbx = 'fbx',
Gltf = 'gltf',
Glb = 'glb',
KeypointsJson = 'keypoints-json',
}
export enum ModelFormat {
Vrm = 'vrm',
Glb = 'glb',
Gltf = 'gltf',
Fbx = 'fbx',
}
export enum PreprocessState {
None = 'none',
Running = 'running',
Done = 'done',
Failed = 'failed',
}
export enum ReviewAction {
Submit = 'submit',
Approve = 'approve',
Reject = 'reject',
Withdraw = 'withdraw',
}
export enum TargetType {
Song = 'song',
Motion = 'motion',
Model = 'model',
/** 场景(T-WEB-015) */
Scene = 'scene',
}
export enum AssetKind {
SongAudio = 'song-audio',
SongCover = 'song-cover',
Motion = 'motion',
Model = 'model',
/** 上传的模型压缩包(zip)原件,仅用于溯源;入库的是转换后的模型文件 */
ModelArchive = 'model-archive',
Thumbnail = 'thumbnail',
/** 动作关键点序列的骨架缩略图(T-WEB-014) */
MotionThumbnail = 'motion-thumbnail',
/** 场景 GLB(T-WEB-015) */
Scene = 'scene',
/** 场景缩略图 */
SceneThumbnail = 'scene-thumbnail',
Keypoints = 'keypoints',
}
export const SOURCE_TYPE_VALUES = Object.values(SourceType) as string[];
export const PUBLISH_STATUS_VALUES = Object.values(PublishStatus) as string[];
export const MOTION_FORMAT_VALUES = Object.values(MotionFormat) as string[];
export const MODEL_FORMAT_VALUES = Object.values(ModelFormat) as string[];
export const TARGET_TYPE_VALUES = Object.values(TargetType) as string[];
/** 允许上传的扩展名 → 动作格式 */
export const MOTION_EXTENSION_MAP: Record<string, MotionFormat> = {
'.bvh': MotionFormat.Bvh,
'.vmd': MotionFormat.Vmd,
'.fbx': MotionFormat.Fbx,
'.gltf': MotionFormat.Gltf,
'.glb': MotionFormat.Glb,
'.json': MotionFormat.KeypointsJson,
};
/** 允许上传的扩展名 → 模型格式 */
export const MODEL_EXTENSION_MAP: Record<string, ModelFormat> = {
'.vrm': ModelFormat.Vrm,
'.glb': ModelFormat.Glb,
'.gltf': ModelFormat.Gltf,
'.fbx': ModelFormat.Fbx,
};
export const AUDIO_EXTENSIONS = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'];
export const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp'];
/**
* 状态机:契约 database.md §状态机。
* key = 当前状态,value = 允许流转到的状态集合。
*/
export const STATUS_TRANSITIONS: Record<PublishStatus, PublishStatus[]> = {
[PublishStatus.Draft]: [PublishStatus.Pending, PublishStatus.Published],
[PublishStatus.Pending]: [PublishStatus.Published, PublishStatus.Rejected, PublishStatus.Draft],
[PublishStatus.Published]: [PublishStatus.Draft, PublishStatus.Rejected],
[PublishStatus.Rejected]: [PublishStatus.Pending, PublishStatus.Draft],
};
export function canTransition(from: PublishStatus, to: PublishStatus): boolean {
return (STATUS_TRANSITIONS[from] ?? []).includes(to);
}
/** 审核动作 → 目标状态 */
export const ACTION_TARGET_STATUS: Record<ReviewAction, PublishStatus> = {
[ReviewAction.Submit]: PublishStatus.Pending,
[ReviewAction.Approve]: PublishStatus.Published,
[ReviewAction.Reject]: PublishStatus.Rejected,
[ReviewAction.Withdraw]: PublishStatus.Draft,
};