需求 1:系统确定以 GLB / VRM 为模型主力格式
- 服务端新增支持库:
- model-inspector.ts:解析 GLB/VRM 的面数、顶点、材质、贴图、形变目标、
**骨骼树(名称/层级/是否蒙皮/humanoid 映射/子节点数)**、humanoid 覆盖率与可驱动性
- model-thumbnail.ts:**纯 TS 软件光栅化**(z-buffer + Lambert + 完整蒙皮矩阵)
生成 512×512 PNG 缩略图,零原生依赖、不装 headless-gl
- vrm-writer.ts:GLB → VRM 1.0(写入 VRMC_vrm 扩展与 humanoid 映射)
- gltf-packer.ts:glTF(.gltf + .bin + 贴图)→ 单文件 GLB
- humanoid 候选表与 Godot 客户端 humanoid_map.gd **逐字对齐**,并有单测断言两边一致
(服务端判定的"可驱动" = 客户端实际能否驱动)
需求 2:模型管理
- .glb / .vrm / .gltf **直接导入**(不重编码,GLB 含 VRM 扩展会自动识别为 VRM)
- .pmx / .fbx 服务端转换:assimp → .glb →(失败)→ .gltf 后自行打包成 GLB
→(MODEL_CONVERT_FORMAT=vrm 时)补 VRMC_vrm 产出 VRM 1.0;每步 assimp info 回读校验
- .zip **自动搜索包内可用模型**:.vrm → .glb → .gltf(直接导入)优先,
其次 .pmx → .fbx(转换);同优先级内按"指名字 → 目录最浅 → 文件最大"
- 入库后自动分析(models.stats)与生成缩略图(models.thumbnail_asset_id),
失败均不阻断入库;管理端列表显示小缩略图、预览放开到 vrm/glb/gltf
需求 3:审核功能
- 上传的模型状态**一律 pending**(不再自动发布、不再自动设默认)
- 审核界面新增模型面板:体积、面数、顶点、材质、贴图、骨骼、形变目标等指标,
humanoid 覆盖进度、可驱动性结论(缺哪些必需骨骼)、warnings,以及
**骨骼详细预览**(层级缩进、蒙皮/Humanoid 徽章、子节点数、搜索保留祖先链、
仅看蒙皮、限高滚动 + 只渲染前 400 行)
- 审核通过可带 setPrimary=true 一并设为默认模型(此时才写源码种子);
POST /api/models/:id/primary 仅允许 published(否则 409)
- scripts/import-default-model.mjs 改为"上传 → 审核通过 + 设默认"流程
其它:
- models 新增 stats 列(迁移 AddModelStats1789460000000,已在真实库执行)
- 契约升至 rest-api/data-models/model-formats 0.4.0、database 0.3.0(另两库已同步)
- 版本号 0.1.0 → 0.2.0(package.json ×2 + lockfile + project.yaml)
- 文档:README.txt / DSH.md / project.yaml / tasks 005·007·010 全面更新;
顶层开发书更新为 v1.4
验证:168 项单测通过(10 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实端到端:MMD 压缩包 → GLB + 分析(面数 9071/骨骼 238/humanoid 0-22) + 缩略图(15KB PNG);
GLB 直接导入 sha256 与源文件一致;审核通过后 defaultModelId 由 VRM 变为该模型
1488 lines
24 KiB
CSS
1488 lines
24 KiB
CSS
/* ==========================================================================
|
|
OpenDance Studio 管理端 —— 单一样式表(不引入任何 UI 框架)
|
|
========================================================================== */
|
|
|
|
:root {
|
|
--bg: #0e1116;
|
|
--bg-elev: #161b22;
|
|
--bg-elev-2: #1c232c;
|
|
--border: #262e39;
|
|
--border-strong: #38424f;
|
|
--text: #e6edf3;
|
|
--text-dim: #9aa7b4;
|
|
--text-mute: #6e7d8c;
|
|
--accent: #4c9aff;
|
|
--accent-strong: #2f7ce0;
|
|
--ok: #3fb950;
|
|
--warn: #d29922;
|
|
--danger: #f85149;
|
|
--purple: #a371f7;
|
|
--radius: 8px;
|
|
--radius-sm: 5px;
|
|
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html,
|
|
body,
|
|
#root {
|
|
height: 100%;
|
|
margin: 0;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
|
|
'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
a {
|
|
color: var(--accent);
|
|
text-decoration: none;
|
|
}
|
|
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
code {
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3 {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 20px;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 15px;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ 布局骨架 */
|
|
|
|
.app-shell {
|
|
display: flex;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 224px;
|
|
flex: 0 0 224px;
|
|
background: var(--bg-elev);
|
|
border-right: 1px solid var(--border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 16px 12px;
|
|
gap: 16px;
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100vh;
|
|
}
|
|
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 0 6px;
|
|
}
|
|
|
|
.brand-logo {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: var(--radius-sm);
|
|
background: linear-gradient(135deg, var(--accent), var(--purple));
|
|
display: grid;
|
|
place-items: center;
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
color: #fff;
|
|
}
|
|
|
|
.brand-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.brand-text span {
|
|
color: var(--text-mute);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.nav {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
padding: 8px 10px;
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-dim);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: var(--bg-elev-2);
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.nav-item em {
|
|
font-style: normal;
|
|
font-size: 11px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.nav-item-active {
|
|
background: rgba(76, 154, 255, 0.14);
|
|
color: #cfe3ff;
|
|
box-shadow: inset 2px 0 0 var(--accent);
|
|
}
|
|
|
|
.sidebar-foot {
|
|
margin-top: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
padding: 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 11px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.sidebar-foot code {
|
|
color: var(--text-dim);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 20px 24px 48px;
|
|
}
|
|
|
|
.page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
max-width: 1680px;
|
|
}
|
|
|
|
.page-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.page-head p {
|
|
margin: 2px 0 0;
|
|
}
|
|
|
|
.page-head-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ 文本工具 */
|
|
|
|
.muted {
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.small {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.mono {
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.break {
|
|
word-break: break-all;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 卡片 */
|
|
|
|
.card-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 14px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.stat-card header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
.stat-total {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.stat-total .muted {
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.stat-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.stat-list li {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.stat-list li strong {
|
|
margin-left: auto;
|
|
font-family: var(--mono);
|
|
}
|
|
|
|
.dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--text-mute);
|
|
}
|
|
|
|
.dot-draft {
|
|
background: var(--text-mute);
|
|
}
|
|
|
|
.dot-pending {
|
|
background: var(--warn);
|
|
}
|
|
|
|
.dot-published {
|
|
background: var(--ok);
|
|
}
|
|
|
|
.dot-rejected {
|
|
background: var(--danger);
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 面板 */
|
|
|
|
.panel {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.panel-body {
|
|
padding: 14px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.kv-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 10px 18px;
|
|
}
|
|
|
|
.kv {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.kv code {
|
|
color: var(--text);
|
|
}
|
|
|
|
.kv-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.kv-list li {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
font-size: 13px;
|
|
border-bottom: 1px dashed var(--border);
|
|
padding-bottom: 4px;
|
|
}
|
|
|
|
.kv-list li:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
/* -------------------------------------------------------- 提示 / 横幅 */
|
|
|
|
.banner {
|
|
border-radius: var(--radius);
|
|
padding: 12px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
border: 1px solid;
|
|
}
|
|
|
|
.banner-warn {
|
|
background: rgba(210, 153, 34, 0.12);
|
|
border-color: rgba(210, 153, 34, 0.5);
|
|
color: #f0d08a;
|
|
}
|
|
|
|
.notice {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 9px 12px;
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.notice span {
|
|
flex: 1;
|
|
}
|
|
|
|
.notice-success {
|
|
background: rgba(63, 185, 80, 0.12);
|
|
border-color: rgba(63, 185, 80, 0.45);
|
|
color: #8fe0a0;
|
|
}
|
|
|
|
.notice-error {
|
|
background: rgba(248, 81, 73, 0.12);
|
|
border-color: rgba(248, 81, 73, 0.45);
|
|
color: #ffb3ae;
|
|
}
|
|
|
|
.notice-info {
|
|
background: rgba(76, 154, 255, 0.12);
|
|
border-color: rgba(76, 154, 255, 0.4);
|
|
color: #b9d6ff;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 筛选栏 */
|
|
|
|
.filter-bar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 12px;
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.filter-bar input[type='search'],
|
|
.filter-bar input[type='text'] {
|
|
min-width: 220px;
|
|
flex: 1 1 260px;
|
|
width: auto;
|
|
}
|
|
|
|
.filter-bar select {
|
|
flex: 0 1 210px;
|
|
min-width: 150px;
|
|
width: auto;
|
|
}
|
|
|
|
.filter-bar .btn {
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 表单 */
|
|
|
|
input,
|
|
select,
|
|
textarea {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
padding: 6px 9px;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
input:focus,
|
|
select:focus,
|
|
textarea:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(76, 154, 255, 0.16);
|
|
}
|
|
|
|
input[type='checkbox'] {
|
|
width: auto;
|
|
min-width: 0;
|
|
height: 16px;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
input[type='file'] {
|
|
padding: 5px;
|
|
}
|
|
|
|
textarea {
|
|
resize: vertical;
|
|
}
|
|
|
|
.form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 12px 14px;
|
|
}
|
|
|
|
.fieldset {
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 12px 14px 14px;
|
|
margin: 0;
|
|
}
|
|
|
|
.fieldset legend {
|
|
padding: 0 6px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.field-label {
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.req {
|
|
color: var(--danger);
|
|
font-style: normal;
|
|
margin-left: 3px;
|
|
}
|
|
|
|
.field-hint {
|
|
font-size: 11px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.field-error {
|
|
font-size: 11px;
|
|
color: #ff9d96;
|
|
}
|
|
|
|
.field-invalid input,
|
|
.field-invalid select,
|
|
.field-invalid textarea {
|
|
border-color: var(--danger);
|
|
}
|
|
|
|
.inline-field {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.inline-field input {
|
|
width: 140px;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 按钮 */
|
|
|
|
.btn {
|
|
border: 1px solid transparent;
|
|
border-radius: var(--radius-sm);
|
|
padding: 6px 12px;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
background: var(--bg-elev-2);
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--accent-strong);
|
|
border-color: var(--accent-strong);
|
|
color: #fff;
|
|
}
|
|
|
|
.btn-primary:hover:not(:disabled) {
|
|
background: var(--accent);
|
|
}
|
|
|
|
.btn-ghost {
|
|
background: transparent;
|
|
border-color: var(--border-strong);
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.btn-ghost:hover:not(:disabled) {
|
|
color: var(--text);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.btn-danger {
|
|
background: var(--danger);
|
|
border-color: var(--danger);
|
|
color: #fff;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 3px 9px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.icon-btn {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-dim);
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.icon-btn:hover {
|
|
color: var(--text);
|
|
background: var(--bg-elev-2);
|
|
}
|
|
|
|
.link-btn {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
color: var(--accent);
|
|
font: inherit;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.link-btn:hover:not(:disabled) {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.link-btn:disabled {
|
|
color: var(--text-mute);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.link-danger {
|
|
color: #ff9d96;
|
|
}
|
|
|
|
.link-disabled {
|
|
color: var(--text-mute);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.ext-link {
|
|
word-break: break-all;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 表格 */
|
|
|
|
.table-wrap {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.table th,
|
|
.table td {
|
|
padding: 9px 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
text-align: left;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.table th {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
background: var(--bg-elev-2);
|
|
white-space: nowrap;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
}
|
|
|
|
.table tbody tr:hover {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
}
|
|
|
|
.row-active {
|
|
background: rgba(76, 154, 255, 0.08);
|
|
}
|
|
|
|
.cell-title {
|
|
font-weight: 600;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.col-source {
|
|
min-width: 240px;
|
|
max-width: 320px;
|
|
}
|
|
|
|
.col-name {
|
|
min-width: 170px;
|
|
}
|
|
|
|
.col-actions {
|
|
min-width: 150px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.col-actions .link-btn,
|
|
.col-actions .link-disabled {
|
|
display: block;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.col-audio {
|
|
min-width: 190px;
|
|
}
|
|
|
|
.mini-audio {
|
|
width: 170px;
|
|
height: 30px;
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 徽标 */
|
|
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 1px 8px;
|
|
border-radius: 999px;
|
|
font-size: 11px;
|
|
line-height: 18px;
|
|
border: 1px solid var(--border-strong);
|
|
color: var(--text-dim);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-draft {
|
|
border-color: var(--border-strong);
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.badge-pending {
|
|
border-color: rgba(210, 153, 34, 0.6);
|
|
color: #e8c26a;
|
|
background: rgba(210, 153, 34, 0.1);
|
|
}
|
|
|
|
.badge-published {
|
|
border-color: rgba(63, 185, 80, 0.6);
|
|
color: #7ddc8d;
|
|
background: rgba(63, 185, 80, 0.1);
|
|
}
|
|
|
|
.badge-rejected {
|
|
border-color: rgba(248, 81, 73, 0.6);
|
|
color: #ff9d96;
|
|
background: rgba(248, 81, 73, 0.1);
|
|
}
|
|
|
|
.badge-vrm {
|
|
border-color: rgba(163, 113, 247, 0.7);
|
|
color: #cbb2ff;
|
|
background: rgba(163, 113, 247, 0.14);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge-primary {
|
|
border-color: rgba(76, 154, 255, 0.7);
|
|
color: #a8ccff;
|
|
background: rgba(76, 154, 255, 0.14);
|
|
}
|
|
|
|
.badge-pre-none {
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.badge-pre-running {
|
|
border-color: rgba(76, 154, 255, 0.6);
|
|
color: #a8ccff;
|
|
}
|
|
|
|
.badge-pre-done {
|
|
border-color: rgba(63, 185, 80, 0.6);
|
|
color: #7ddc8d;
|
|
}
|
|
|
|
.badge-pre-failed {
|
|
border-color: rgba(248, 81, 73, 0.6);
|
|
color: #ff9d96;
|
|
}
|
|
|
|
.chip {
|
|
display: inline-block;
|
|
padding: 0 6px;
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-elev-2);
|
|
border: 1px solid var(--border);
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* --------------------------------------------------------- 来源五元组 */
|
|
|
|
.source-cell {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.source-line {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.source-name {
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
word-break: break-word;
|
|
}
|
|
|
|
.source-sub {
|
|
color: var(--text-mute);
|
|
gap: 10px;
|
|
}
|
|
|
|
.source-detail {
|
|
margin: 6px 0 0;
|
|
padding: 8px 10px;
|
|
border: 1px dashed var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.source-detail > div {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.source-detail dt {
|
|
flex: 0 0 84px;
|
|
color: var(--text-mute);
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.source-detail dd {
|
|
margin: 0;
|
|
word-break: break-all;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 缩略图 */
|
|
|
|
.thumb {
|
|
width: 74px;
|
|
height: 74px;
|
|
padding: 0;
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg);
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
display: block;
|
|
}
|
|
|
|
.thumb img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.thumb-placeholder {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 2px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.thumb-placeholder em {
|
|
font-style: normal;
|
|
font-size: 10px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 分页 */
|
|
|
|
.pagination {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 10px 16px;
|
|
border-top: 1px solid var(--border);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pagination-info {
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.pagination-actions {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 弹层 */
|
|
|
|
.modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(4, 7, 11, 0.68);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
padding: 40px 16px;
|
|
overflow-y: auto;
|
|
z-index: 50;
|
|
}
|
|
|
|
.modal {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: var(--radius);
|
|
width: 100%;
|
|
max-width: 560px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.modal-lg {
|
|
max-width: 760px;
|
|
}
|
|
|
|
.modal-xl {
|
|
max-width: 940px;
|
|
}
|
|
|
|
.modal-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 16px;
|
|
max-height: 68vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.modal-foot {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
padding: 12px 16px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.confirm-body p {
|
|
margin: 0 0 6px;
|
|
}
|
|
|
|
/* --------------------------------------------------------- 状态占位块 */
|
|
|
|
.state-box {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
padding: 36px 16px;
|
|
color: var(--text-dim);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.state-error {
|
|
justify-content: space-between;
|
|
background: rgba(248, 81, 73, 0.08);
|
|
border-bottom: 1px solid rgba(248, 81, 73, 0.35);
|
|
color: #ffb3ae;
|
|
}
|
|
|
|
.state-error-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.state-empty {
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.spinner {
|
|
width: 14px;
|
|
height: 14px;
|
|
border: 2px solid var(--border-strong);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
animation: spin 0.7s linear infinite;
|
|
}
|
|
|
|
.spinner-xs {
|
|
width: 9px;
|
|
height: 9px;
|
|
border-width: 1.5px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------- 模型预览布局 */
|
|
|
|
.split-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) 380px;
|
|
gap: 14px;
|
|
align-items: start;
|
|
}
|
|
|
|
@media (max-width: 1280px) {
|
|
.split-layout {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
}
|
|
|
|
.preview-column {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
position: sticky;
|
|
top: 16px;
|
|
}
|
|
|
|
.vrm-panel {
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.vrm-panel-head,
|
|
.vrm-panel-foot {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
padding: 10px 14px;
|
|
}
|
|
|
|
.vrm-panel-head {
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.vrm-panel-head strong {
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.vrm-panel-foot {
|
|
border-top: 1px solid var(--border);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.vrm-stage {
|
|
position: relative;
|
|
background: #14181f;
|
|
}
|
|
|
|
.vrm-canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
.vrm-canvas canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
cursor: grab;
|
|
}
|
|
|
|
.vrm-canvas canvas:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.vrm-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
text-align: center;
|
|
padding: 16px;
|
|
background: rgba(14, 17, 22, 0.72);
|
|
color: var(--text-dim);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.vrm-overlay p {
|
|
margin: 0;
|
|
}
|
|
|
|
.vrm-overlay-error {
|
|
color: #ffb3ae;
|
|
}
|
|
|
|
/* -------------------------------------------------------------- 标签页 */
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: 4px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.tab {
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
color: var(--text-dim);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
padding: 8px 14px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.tab:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.tab-active {
|
|
color: var(--text);
|
|
border-bottom-color: var(--accent);
|
|
}
|
|
|
|
.tab-count {
|
|
background: var(--bg-elev-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
padding: 0 7px;
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
模型审核(T-WEB-005 追加):待审列表 + 右侧模型审核面板
|
|
========================================================================== */
|
|
|
|
.review-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(420px, 620px);
|
|
gap: 14px;
|
|
align-items: start;
|
|
}
|
|
|
|
@media (max-width: 1440px) {
|
|
.review-layout {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
}
|
|
|
|
.row-clickable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.check-field {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.check-field input[type='checkbox'] {
|
|
width: auto;
|
|
height: 16px;
|
|
margin: 0;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.panel-foot {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
padding: 10px 16px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.panel-foot-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* -------------------------------------------------- 列表小缩略图 */
|
|
|
|
.thumb-link {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.thumb-sm {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border-strong);
|
|
background: var(--bg);
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
/* -------------------------------------------------- 审核面板骨架 */
|
|
|
|
.model-review {
|
|
position: sticky;
|
|
top: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.model-review .panel-body {
|
|
gap: 14px;
|
|
max-height: 76vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.model-review-top {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.model-review-thumb {
|
|
width: 116px;
|
|
height: 116px;
|
|
flex: 0 0 116px;
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg);
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.model-review-thumb-empty {
|
|
display: grid;
|
|
place-items: center;
|
|
text-align: center;
|
|
color: var(--text-mute);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.model-review-basic {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* -------------------------------------------------- 关键指标卡片 */
|
|
|
|
.metric-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
|
gap: 8px;
|
|
}
|
|
|
|
.metric {
|
|
background: var(--bg-elev-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 7px 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.metric-strong {
|
|
border-color: rgba(76, 154, 255, 0.55);
|
|
background: rgba(76, 154, 255, 0.08);
|
|
}
|
|
|
|
.metric-label {
|
|
font-size: 11px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.metric-value {
|
|
font-family: var(--mono);
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.metric-hint {
|
|
font-size: 11px;
|
|
color: var(--text-mute);
|
|
}
|
|
|
|
.banner-ok {
|
|
background: rgba(63, 185, 80, 0.12);
|
|
border-color: rgba(63, 185, 80, 0.5);
|
|
color: #8fe0a0;
|
|
}
|
|
|
|
.banner-danger {
|
|
background: rgba(248, 81, 73, 0.12);
|
|
border-color: rgba(248, 81, 73, 0.5);
|
|
color: #ffb3ae;
|
|
}
|
|
|
|
/* -------------------------------------------------- humanoid 覆盖 */
|
|
|
|
.coverage {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.coverage-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
|
|
.progress {
|
|
height: 8px;
|
|
border-radius: 999px;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--accent-strong), var(--accent));
|
|
transition: width 0.2s ease;
|
|
}
|
|
|
|
.humanoid-names {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
max-height: 120px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.badge-humanoid {
|
|
border-color: rgba(163, 113, 247, 0.6);
|
|
color: #cbb2ff;
|
|
background: rgba(163, 113, 247, 0.12);
|
|
font-family: var(--mono);
|
|
}
|
|
|
|
.warn-list {
|
|
margin: 0;
|
|
padding-left: 18px;
|
|
color: #e8c26a;
|
|
font-size: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
/* -------------------------------------------------- 骨骼树 */
|
|
|
|
.bone-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.bone-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
|
|
.bone-toolbar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.bone-toolbar input[type='search'] {
|
|
flex: 1 1 200px;
|
|
min-width: 160px;
|
|
}
|
|
|
|
.bone-stats {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px 14px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.bone-stats strong {
|
|
font-family: var(--mono);
|
|
color: var(--text);
|
|
}
|
|
|
|
.bone-tree {
|
|
max-height: 60vh;
|
|
overflow: auto;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg);
|
|
}
|
|
|
|
.bone-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 2px 8px;
|
|
min-height: 22px;
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
border-bottom: 1px solid rgba(38, 46, 57, 0.6);
|
|
}
|
|
|
|
.bone-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.bone-row:hover {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
}
|
|
|
|
.bone-row .badge {
|
|
padding: 0 6px;
|
|
line-height: 16px;
|
|
font-size: 10px;
|
|
}
|
|
|
|
.bone-name {
|
|
color: var(--text);
|
|
max-width: 45%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.bone-children {
|
|
margin-left: auto;
|
|
}
|