feat(web): 用户工作台 - 产品化壳层、内容库布局与积分入口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-16 22:59:45 +08:00
parent f52702b70f
commit 969e2831b2
46 changed files with 5355 additions and 944 deletions

View File

@@ -1,12 +1,10 @@
import { App, Button, Input, InputNumber } from "antd";
import { Select } from "@/components/ui/base/select";
import { App, Button, InputNumber } from "antd";
import { SettingsRow } from "@/components/ui/product/settings-row";
import { ArrowLeft, Boxes, Bug, Cloud, MessageSquareText, RadioTower, SlidersHorizontal, Workflow } from "lucide-react";
import { useEffect, useLayoutEffect, useMemo, useState, type ReactNode } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { UserOSSSettingsForm } from "@/components/layout/user-oss-settings-form";
import { audioFormatOptions, audioVoiceOptions, normalizeAudioSpeedValue } from "@/lib/audio-generation";
import { refreshSystemChannels } from "@/lib/user-session";
import { defaultConfig, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
import { useUserStore } from "@/stores/use-user-store";
@@ -24,7 +22,7 @@ const configSections: Array<{ key: ConfigSectionKey; label: string; description:
{ key: "channels", label: "个人渠道", description: "模型服务与个人工作流", icon: <RadioTower className="size-4" /> },
{ key: "runninghub", label: "RunningHub 工作流", description: "个人渠道的云端工作流配置", icon: <Workflow className="size-4" /> },
{ key: "models", label: "模型选择", description: "按领域选择默认模型", icon: <Boxes className="size-4" /> },
{ key: "preferences", label: "生成偏好", description: "画布、视频与音频默认值", icon: <SlidersHorizontal className="size-4" /> },
{ key: "preferences", label: "生成偏好", description: "画布生成默认值", icon: <SlidersHorizontal className="size-4" /> },
{ key: "prompts", label: "提示词偏好", description: "按任务定制平台模板", icon: <MessageSquareText className="size-4" /> },
{ key: "storage", label: "我的对象存储", description: "管理个人媒体存储", icon: <Cloud className="size-4" /> },
{ key: "diagnostics", label: "问题诊断", description: "导出日志协助排查", icon: <Bug className="size-4" /> },
@@ -127,14 +125,14 @@ export default function SettingsPage() {
<div className="settings-pane-header">
<div className="min-w-0">
<h2>生成偏好</h2>
<p>画布、视频与音频默认值,节点内仍可单独覆盖。</p>
<p>设置新建生成任务时使用的初始值,节点内仍可单独覆盖。</p>
</div>
</div>
<div className="settings-section">
<section className="settings-preference-block py-4">
<div className="mb-3">
<h3 className="text-[13px] font-medium text-foreground/60">画布生成</h3>
<p className="mt-1 text-xs text-foreground/55">设置新建生成任务时使用的初始值,节点内仍可单独覆盖。</p>
<section className="settings-preference-block">
<div className="settings-preference-heading">
<h3>画布生成</h3>
<p>用于新建图片生成任务,节点内仍可单独覆盖。</p>
</div>
<SettingsRow
label="默认生图张数"
@@ -151,42 +149,6 @@ export default function SettingsPage() {
controlClassName="w-[200px]"
/>
</section>
<section className="settings-preference-block py-4">
<div className="mb-3">
<h3 className="text-[13px] font-medium text-foreground/60">音频默认值</h3>
<p className="mt-1 text-xs text-foreground/55">用于新建音频节点和未单独设置参数的生成任务。</p>
</div>
<SettingsRow label="默认声音" control={<Select value={config.audioVoice} options={audioVoiceOptions} onChange={(value) => updateConfig("audioVoice", value)} />} controlClassName="w-[200px]" />
<SettingsRow label="文件格式" control={<Select value={config.audioFormat} options={audioFormatOptions} onChange={(value) => updateConfig("audioFormat", value)} />} controlClassName="w-[200px]" />
<SettingsRow
label="语速"
description="音频朗读速度(0.25–4)。"
control={
<InputNumber
min={0.25}
max={4}
step={0.05}
precision={2}
className="w-full"
value={Number(config.audioSpeed)}
onChange={(value) => updateConfig("audioSpeed", normalizeAudioSpeedValue(String(value ?? defaultConfig.audioSpeed)))}
/>
}
controlClassName="w-[200px]"
/>
</section>
<section className="settings-preference-block py-4">
<div className="mb-3">
<h3 className="text-[13px] font-medium text-foreground/60">音频指令</h3>
<p className="mt-1 text-xs text-foreground/55">在音频节点没有单独填写时使用。</p>
</div>
<SettingsRow
label="默认音频指令"
align="top"
control={<Input.TextArea rows={5} value={config.audioInstructions} placeholder="例如:自然、温暖、适合旁白。" onChange={(event) => updateConfig("audioInstructions", event.target.value)} />}
controlClassName="w-full max-w-[480px]"
/>
</section>
</div>
</SettingsPane>
),
@@ -203,17 +165,14 @@ export default function SettingsPage() {
return (
<main className="settings-page app-workspace-page app-user-workspace flex h-full min-h-0 flex-col text-foreground">
<header className="settings-topbar shrink-0">
<div className="flex min-w-0 items-center gap-2.5">
{shouldPromptContinue ? (
<button type="button" className="app-workspace-icon-button shrink-0" onClick={() => navigate(-1)} aria-label="返回创作页面" title="返回创作页面">
<ArrowLeft className="size-4" />
</button>
) : null}
<h1 className="truncate text-sm font-semibold">设置</h1>
{shouldPromptContinue ? (
<div className="settings-topbar shrink-0">
<div className="ml-auto flex flex-wrap items-center justify-end gap-2">
<Button icon={<ArrowLeft className="size-4" />} onClick={() => navigate(-1)}>返回创作</Button>
<Button type="primary" onClick={finishConfig}>保存并返回</Button>
</div>
</div>
{shouldPromptContinue ? <Button type="primary" size="small" onClick={finishConfig}>保存并返回</Button> : null}
</header>
) : null}
<div className="settings-library-frame flex min-h-0 flex-1 flex-col md:flex-row">
<aside className="settings-nav-panel w-full shrink-0 md:w-[200px]">
<nav className="thin-scrollbar flex gap-1 overflow-x-auto p-2 md:block md:space-y-1 md:p-2.5" aria-label="配置分类">