Files
yingqing/core/frontend/src/routes/route-config.ts
T
seaislee1209andClaude Opus 4.8 44e90233ff feat(models): 期1 模特库地基 — Model 顶级实体 + 模特库页 + 审核范围扩展
后端:
- 新增 Model(模特)团队级实体:形象图+三视图+声线(声线尾期),官方模板标签,软删
- Asset.Category 枚举一次加全(model_portrait/tri_view/voice/model_tryon/platform_kit/free_create/storyboard),逐期接线
- 送审范围 person → REVIEW_CATEGORIES{person,tri_view,storyboard}(review.py + adminpanel 队列),图片趴不送审;当前行为不变(forward-compat)
- /api/models/ ModelLibraryViewSet:本团队∪官方模板、官方/我的 tab、真人上传、软删(官方不可删)、跨团队资产引用防越权
- backfill_models 命令把历史成套模特(kind=model + 项目流命名配对)归集成 Model,dry-run 默认

前端:
- 左菜单新增「模特库」顶级入口(person 图标)+ 路由 /models
- 模特库页:全部/官方模板/我的模特 tab + 真人上传 + 模特卡(形象图+三视图缩略+官方标签),仅用 design token

测试:assets 10/10 绿;tsc+build 全绿;无头走查 0 console error(全部5/官方3/我的2,官方标签达标)
基线既有 7 失败(ai/projects provider mock 漂移)零新增

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:53:46 +08:00

209 lines
7.5 KiB
TypeScript

import {
Film,
FolderKanban,
Home,
ImageIcon,
Library,
MessageSquare,
Package,
Settings,
UserRound,
Users,
Wallet,
WandSparkles
} from "lucide-react";
export type Page =
| "dashboard"
| "products"
| "productDetail"
| "productCreateUpload"
| "models"
| "projects"
| "projectWizard"
| "pipeline"
| "library"
| "account"
| "team"
| "messages"
| "assetFactory"
| "imageOptimize"
| "modelPhoto"
| "modelPhotoDemoA"
| "modelPhotoDemoB"
| "platformCover"
| "settings"
| "settingsNotify"
| "trash";
export type AuthMode = "login" | "register";
export type ResolvedRoute = {
page: Page;
authMode: AuthMode;
productId?: string;
projectId?: string;
hash?: string;
// 平台超管后台:/admin → ""(概览),/admin/<section> → section slug。undefined = 非 admin 路由。
admin?: string;
};
export type NavigateOptions = {
productId?: string;
projectId?: string;
replace?: boolean;
hash?: string;
};
export type NavigateFn = (page: Page, options?: NavigateOptions) => void;
export type Notice = { type: "success" | "error" | "info"; text: string } | null;
export type NavItem = { page: Page; label: string; icon: typeof Home; badge?: string };
export const mainNav: NavItem[] = [
{ page: "dashboard", label: "工作台", icon: Home },
{ page: "products", label: "商品库", icon: Package },
{ page: "models", label: "模特库", icon: UserRound },
{ page: "projects", label: "视频项目", icon: FolderKanban },
{ page: "pipeline", label: "生产管线", icon: WandSparkles },
{ page: "library", label: "资产库", icon: Library },
{ page: "account", label: "账户", icon: Wallet }
];
export const supplementNav: NavItem[] = [
{ page: "team", label: "团队", icon: Users },
{ page: "messages", label: "消息", icon: MessageSquare, badge: "3" },
{ page: "assetFactory", label: "图片工具", icon: ImageIcon, badge: "AI" },
{ page: "settings", label: "设置", icon: Settings }
];
export const allNav = [...mainNav, ...supplementNav];
export const routeLabels: Record<Page, string> = {
dashboard: "工作台",
products: "商品库",
productDetail: "商品详情",
productCreateUpload: "上传创建商品",
models: "模特库",
projects: "视频项目",
projectWizard: "新建视频项目",
pipeline: "生产管线",
library: "资产库",
account: "账户",
team: "团队",
messages: "消息",
assetFactory: "图片工具",
imageOptimize: "图片创作",
modelPhoto: "模特上身图",
modelPhotoDemoA: "模特图方案 A",
modelPhotoDemoB: "模特图方案 B",
platformCover: "平台套图",
settings: "设置",
settingsNotify: "通知设置",
trash: "垃圾桶"
};
export const routePages = Object.keys(routeLabels) as Page[];
export function isPage(value: string): value is Page {
return routePages.includes(value as Page);
}
export function parentPage(page: Page): Page {
if (["productDetail", "productCreateUpload"].includes(page)) return "products";
if (page === "projectWizard") return "projects";
if (["imageOptimize", "modelPhoto", "modelPhotoDemoA", "modelPhotoDemoB", "platformCover"].includes(page)) {
return "assetFactory";
}
if (page === "settingsNotify") return "settings";
return page;
}
export function resolveRoute(): ResolvedRoute {
const rawPath = window.location.pathname.replace(/\/+$/, "") || "/";
const path = rawPath.toLowerCase();
const search = new URLSearchParams(window.location.search);
const hash = window.location.hash.replace("#", "");
if (path === "/register" || hash === "register") return { page: "dashboard", authMode: "register", hash };
if (path === "/login") return { page: "dashboard", authMode: "login", hash };
// 平台超管后台:独立外壳,page 仅占位(渲染由 route.admin 接管)
if (path === "/admin") return { page: "dashboard", authMode: "login", admin: "", hash };
if (path.startsWith("/admin/")) {
return { page: "dashboard", authMode: "login", admin: path.slice("/admin/".length), hash };
}
if (path === "/" && isPage(hash)) return { page: hash, authMode: "login", hash };
if (path === "/" || path === "/dashboard") return { page: "dashboard", authMode: "login", hash };
if (path === "/products") return { page: "products", authMode: "login", hash };
if (path === "/products/new") return { page: "productCreateUpload", authMode: "login", hash };
if (path.startsWith("/products/")) {
return { page: "productDetail", authMode: "login", productId: decodeURIComponent(path.slice("/products/".length)), hash };
}
if (path === "/models") return { page: "models", authMode: "login", hash };
if (path === "/projects") return { page: "projects", authMode: "login", hash };
if (path === "/projects/new") return { page: "projectWizard", authMode: "login", hash };
if (path === "/pipeline") {
return { page: "pipeline", authMode: "login", projectId: search.get("project_id") || undefined, hash };
}
if (path.startsWith("/pipeline/")) {
return { page: "pipeline", authMode: "login", projectId: decodeURIComponent(path.slice("/pipeline/".length)), hash };
}
if (path === "/library") return { page: "library", authMode: "login", hash };
if (path === "/account") return { page: "account", authMode: "login", hash };
if (path === "/team") return { page: "team", authMode: "login", hash };
if (path === "/messages") return { page: "messages", authMode: "login", hash };
if (path === "/asset-factory") return { page: "assetFactory", authMode: "login", hash };
if (path === "/image-optimize") return { page: "imageOptimize", authMode: "login", hash };
if (path === "/model-photo") return { page: "modelPhoto", authMode: "login", hash };
if (path === "/model-photo/demo-a") return { page: "modelPhotoDemoA", authMode: "login", hash };
if (path === "/model-photo/demo-b") return { page: "modelPhotoDemoB", authMode: "login", hash };
if (path === "/platform-cover") return { page: "platformCover", authMode: "login", hash };
if (path === "/settings/notify") return { page: "settingsNotify", authMode: "login", hash };
if (path === "/settings") return { page: "settings", authMode: "login", hash };
if (path === "/trash") return { page: "trash", authMode: "login", hash };
return { page: "dashboard", authMode: "login", hash };
}
export function pathForPage(page: Page, options: NavigateOptions = {}) {
switch (page) {
case "dashboard":
return "/dashboard";
case "products":
return "/products";
case "productCreateUpload":
return "/products/new";
case "productDetail":
return options.productId ? `/products/${encodeURIComponent(options.productId)}` : "/products";
case "models":
return "/models";
case "projects":
return "/projects";
case "projectWizard":
return "/projects/new";
case "pipeline":
return options.projectId ? `/pipeline/${encodeURIComponent(options.projectId)}` : "/pipeline";
case "library":
return "/library";
case "account":
return "/account";
case "team":
return "/team";
case "messages":
return "/messages";
case "assetFactory":
return "/asset-factory";
case "imageOptimize":
return "/image-optimize";
case "modelPhoto":
return "/model-photo";
case "modelPhotoDemoA":
return "/model-photo/demo-a";
case "modelPhotoDemoB":
return "/model-photo/demo-b";
case "platformCover":
return "/platform-cover";
case "settingsNotify":
return "/settings/notify";
case "settings":
return "/settings";
case "trash":
return "/trash";
default:
return "/dashboard";
}
}