@@ -6,6 +6,20 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<link rel="icon" href="/assets/yz/logo.png" />
|
||||
<title>影擎</title>
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var key = "yingqing-appearance";
|
||||
var raw = localStorage.getItem(key) || "system";
|
||||
var theme = raw;
|
||||
if (raw !== "light" && raw !== "dark") {
|
||||
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"be": "npm --prefix ../.. run be",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
|
||||
+54
-17
@@ -21,6 +21,7 @@ import type {
|
||||
UserPreference
|
||||
} from "./types";
|
||||
import { publicModelDisplayName } from "./model-display";
|
||||
import { applyTheme, normalizeAppearance, readStoredAppearance } from "./theme";
|
||||
import { generationErrorText } from "./generation-error";
|
||||
import { isQuickCreateBusy, lockedQuickCreateProject, rememberQuickCreateJob, withQuickCreateStatus } from "./quick-create-lock";
|
||||
import { AccountMenu, CornerMarks, Decorations, ModeTabs, openCommandPalette, Sidebar, ToastLike, topModuleForPage } from "./components/app-shell";
|
||||
@@ -143,6 +144,22 @@ export function App() {
|
||||
// 合成成片单飞:合成不占全局 loading,自己守一把锁,防止连点起两轮轮询
|
||||
const exportingRef = useRef(false);
|
||||
const [preferences, setPreferences] = useState<UserPreference | null>(null);
|
||||
|
||||
// 首屏先用本地外观,后端 preferences 到达后再对齐;跟随系统时监听系统切换
|
||||
useEffect(() => {
|
||||
applyTheme(readStoredAppearance());
|
||||
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const onChange = () => {
|
||||
if (readStoredAppearance() === "system") applyTheme("system");
|
||||
};
|
||||
mq.addEventListener("change", onChange);
|
||||
return () => mq.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const appearance = preferences?.display?.appearance;
|
||||
if (appearance) applyTheme(normalizeAppearance(appearance));
|
||||
}, [preferences?.display?.appearance]);
|
||||
|
||||
const [sessions, setSessions] = useState<LoginSession[]>([]);
|
||||
|
||||
const [activeProductId, setActiveProductId] = useState(route.productId || "");
|
||||
@@ -211,19 +228,25 @@ export function App() {
|
||||
}
|
||||
}, [loadData]);
|
||||
|
||||
// 设置页数据:偏好 + 登录会话(进入设置页时按需加载)
|
||||
// 设置页数据:偏好 + 登录会话。settingsLoadSeq 挡住过期 GET 覆盖更新的 PUT 结果。
|
||||
const settingsLoadSeq = useRef(0);
|
||||
const loadSettingsData = useCallback(async () => {
|
||||
const seq = ++settingsLoadSeq.current;
|
||||
const [pref, sess] = await Promise.all([
|
||||
api.preferences().catch(() => null),
|
||||
api.loginSessions().catch(() => [])
|
||||
]);
|
||||
if (seq !== settingsLoadSeq.current) return;
|
||||
if (pref) setPreferences(pref);
|
||||
setSessions(sess);
|
||||
}, []);
|
||||
|
||||
async function savePreferences(payload: Partial<UserPreference>) {
|
||||
const next = await api.updatePreferences(payload).catch(() => null);
|
||||
if (next) setPreferences(next);
|
||||
// 失败必须抛出,不能吞成 null —— 否则 settings 会误以为已落盘并推进 baseline
|
||||
const next = await api.updatePreferences(payload);
|
||||
// 使在途的 preferences GET 失效,避免刚保存的外观被旧响应打回 system
|
||||
settingsLoadSeq.current += 1;
|
||||
setPreferences(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -399,16 +422,30 @@ export function App() {
|
||||
// Load preferences + sessions when entering settings.
|
||||
// 进创作相关页时重拉模型目录,避免后台刚改能力/积分,前端还捧着首屏缓存
|
||||
useEffect(() => {
|
||||
if (!authed || !dataLoaded) return;
|
||||
// 模型目录是创作页的必要数据,不能被商品/项目等全局数据的加载状态卡住。
|
||||
// 否则任一非关键列表短暂失败时,模型按钮仍显示默认名却展开空菜单。
|
||||
if (!authed) return;
|
||||
if (!["freeCreate", "omniCreate", "omniSession", "omniHistory", "quickCreate", "videoReplace", "pipeline"].includes(page)) return;
|
||||
refreshModelConfigs();
|
||||
}, [authed, page, dataLoaded, refreshModelConfigs]);
|
||||
}, [authed, page, refreshModelConfigs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authed || (page !== "settings" && page !== "settingsNotify")) return;
|
||||
loadSettingsData();
|
||||
}, [authed, page, loadSettingsData]);
|
||||
|
||||
// 登录后拉一次偏好(外观等),不必等进设置页才生效;仍走 settingsLoadSeq,与设置页重载互斥
|
||||
useEffect(() => {
|
||||
if (!authed || !user) return;
|
||||
const seq = ++settingsLoadSeq.current;
|
||||
void api.preferences()
|
||||
.then((pref) => {
|
||||
if (seq !== settingsLoadSeq.current) return;
|
||||
if (pref) setPreferences(pref);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}, [authed, user]);
|
||||
|
||||
// YYX#row22:登录后拉一次未读生成任务,并每 30s 静默轮询(生成是慢任务,出图后角标自动亮)
|
||||
useEffect(() => {
|
||||
if (!authed || !user) return;
|
||||
@@ -901,16 +938,15 @@ export function App() {
|
||||
return;
|
||||
}
|
||||
navigate("omniCreate", { replace: true });
|
||||
// 先把工作台必需数据拉好,这期间「登录页」保持「登录成功,正在进入工作台…」提示(authed 仍 false → AuthScreen 不卸载),
|
||||
// 数据就绪后才揭开外壳直接进有数据的工作台 —— 避免中间闪一个空白「加载中…」页(PMC#7)。
|
||||
try {
|
||||
await loadDataWithRetry();
|
||||
} catch (error) {
|
||||
// 登录凭证已验证即可进入工作台。此前等待商品/项目等整套数据(包含重试)才
|
||||
// setAuthed,任一慢接口都会让登录页长时间卡在「正在进入工作台」;而刷新后的
|
||||
// boot 路径本来就是先展示外壳、后台补数据。首次登录也保持同一体验。
|
||||
setDataLoaded(false);
|
||||
setAuthed(true);
|
||||
void loadDataWithRetry().catch((error) => {
|
||||
console.error("[login] data hydrate failed:", error);
|
||||
setNotice({ type: "error", text: "数据加载失败,请刷新页面重试" });
|
||||
} finally {
|
||||
setAuthed(true);
|
||||
}
|
||||
setNotice({ type: "error", text: "部分数据加载失败,页面会自动重试" });
|
||||
});
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
@@ -1216,9 +1252,9 @@ export function App() {
|
||||
case "modelPhotoDemoB":
|
||||
return <ModelPhotoDemoPage variant="B" products={products} onBack={() => goBack("modelPhoto")} navigate={navigate} />;
|
||||
case "settings":
|
||||
return <SettingsPage user={currentUser} team={currentTeam} preferences={preferences} sessions={sessions} onSavePreferences={savePreferences} onRevokeSession={revokeSession} onRevokeOthers={revokeOtherSessions} onSaveProfile={saveProfile} onChangePassword={changeOwnPassword} onUploadAvatar={uploadOwnAvatar} onResetAvatar={resetOwnAvatar} onNotify={(text) => setNotice({ type: "success", text })} onLogout={logout} />;
|
||||
return <SettingsPage user={currentUser} team={currentTeam} preferences={preferences} sessions={sessions} onSavePreferences={savePreferences} onRevokeSession={revokeSession} onRevokeOthers={revokeOtherSessions} onSaveProfile={saveProfile} onChangePassword={changeOwnPassword} onUploadAvatar={uploadOwnAvatar} onResetAvatar={resetOwnAvatar} onNotify={(type, text) => setNotice({ type, text })} onLogout={logout} />;
|
||||
case "settingsNotify":
|
||||
return <SettingsPage user={currentUser} team={currentTeam} initialSection="notify" preferences={preferences} sessions={sessions} onSavePreferences={savePreferences} onRevokeSession={revokeSession} onRevokeOthers={revokeOtherSessions} onSaveProfile={saveProfile} onChangePassword={changeOwnPassword} onUploadAvatar={uploadOwnAvatar} onResetAvatar={resetOwnAvatar} onNotify={(text) => setNotice({ type: "success", text })} onLogout={logout} />;
|
||||
return <SettingsPage user={currentUser} team={currentTeam} initialSection="notify" preferences={preferences} sessions={sessions} onSavePreferences={savePreferences} onRevokeSession={revokeSession} onRevokeOthers={revokeOtherSessions} onSaveProfile={saveProfile} onChangePassword={changeOwnPassword} onUploadAvatar={uploadOwnAvatar} onResetAvatar={resetOwnAvatar} onNotify={(type, text) => setNotice({ type, text })} onLogout={logout} />;
|
||||
default:
|
||||
return <OmniCreatePage modelConfigs={modelConfigs} navigate={navigate} onNotify={(type, text) => setNotice({ type, text })} />;
|
||||
}
|
||||
@@ -1379,7 +1415,8 @@ export function App() {
|
||||
)}
|
||||
<span className="balance-chip" onClick={() => navigate("account")}>
|
||||
<IconKitSvg name="creditCard" />
|
||||
余额 <strong>{money(billing?.account.balance)}</strong>
|
||||
<span className="balance-chip-label">余额</span>
|
||||
<strong>{money(billing?.account.balance)}</strong>
|
||||
</span>
|
||||
<button className="icon-btn" type="button" onClick={() => navigate("messages")} title="消息中心">
|
||||
<IconKitSvg name="bell" />
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
color: #0b1d4c;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
transition: color .18s ease, background .18s ease, transform .18s ease;
|
||||
@@ -716,7 +716,7 @@
|
||||
padding: 0 8px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
@@ -755,7 +755,7 @@
|
||||
padding: 0 12px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bill-jump button:disabled {
|
||||
@@ -807,7 +807,7 @@
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.11);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 24px 70px rgba(18, 25, 38, 0.18);
|
||||
transform: translateY(10px) scale(.985);
|
||||
transition: transform .2s ease;
|
||||
@@ -840,7 +840,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.1);
|
||||
border-radius: 9px;
|
||||
color: #20242c;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.billing-contact-close:hover {
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
height: var(--topbar-height);
|
||||
min-height: var(--topbar-height);
|
||||
padding: 0 28px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid rgba(27, 32, 40, 0.08);
|
||||
}
|
||||
.admin-crumb {
|
||||
@@ -370,7 +370,7 @@
|
||||
|
||||
.admin-table-wrap { overflow-x: auto; }
|
||||
.admin-page table.t.admin-table {
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border-color: var(--st-line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -626,7 +626,7 @@
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--st-line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -679,7 +679,7 @@
|
||||
.pt-foot .spacer { flex: 1; }
|
||||
|
||||
.admin-page .card-hard {
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border-color: var(--st-line);
|
||||
}
|
||||
.admin-page .empty-state {
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--af-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.asset-factory .af-search svg {
|
||||
width: 16px;
|
||||
@@ -229,7 +229,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 10px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -261,7 +261,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid var(--af-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -280,7 +280,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
@@ -2746,7 +2746,7 @@
|
||||
place-items: center;
|
||||
border: 1px solid rgba(34, 42, 54, 0.18);
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.yz-image .product-choice.active .choice-check,
|
||||
.yz-image .model-choice.active .choice-check,
|
||||
@@ -2761,7 +2761,7 @@
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.yz-image .product-library-choice,
|
||||
.yz-image .upload-choice {
|
||||
@@ -2909,7 +2909,7 @@
|
||||
}
|
||||
.yz-image .image-prompt:focus {
|
||||
border-color: rgba(0, 47, 167, 0.48);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 0 0 3px rgba(0, 47, 167, 0.07);
|
||||
}
|
||||
.yz-image .model-line {
|
||||
@@ -3310,7 +3310,7 @@
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
}
|
||||
.yz-image .platform-choice .p-logo.has-img { background: #fff; }
|
||||
.yz-image .platform-choice .p-logo.has-img { background: var(--surface); }
|
||||
|
||||
.yz-image .model-pill {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -584,8 +584,8 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
|
||||
aria-hidden="true"
|
||||
style={{ transform: `translateY(${liquid.y}px)` }}
|
||||
>
|
||||
<svg className="liquid-shape" viewBox="0 0 132 76" preserveAspectRatio="none">
|
||||
<path fill="#101012" d="M0 11H103C120 11 130 22 130 38C130 54 120 65 103 65H0Z" />
|
||||
<svg className="liquid-shape" viewBox="0 0 152 76" preserveAspectRatio="none">
|
||||
<path fill="currentColor" d="M0 11H123C140 11 150 22 150 38C150 54 140 65 123 65H0Z" />
|
||||
</svg>
|
||||
{activeMajor && (
|
||||
<div className="indicator-content">
|
||||
|
||||
@@ -97,8 +97,6 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
|
||||
return () => window.clearInterval(timer);
|
||||
}, [open, activeGroup, assets]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const createGroup = async () => {
|
||||
const name = newName.trim();
|
||||
if (!name || creatingGroup) return;
|
||||
@@ -160,6 +158,9 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
|
||||
}
|
||||
};
|
||||
|
||||
// 所有 Hook 必须在关闭态也保持同一调用顺序;不能在此之前提前 return。
|
||||
if (!open) return null;
|
||||
|
||||
const startRenameAsset = (item: FreeAssetItem) => {
|
||||
setConfirmDeleteAssetId(null);
|
||||
setConfirmDeleteGroupId(null);
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
100% { background-position: 480px 0; }
|
||||
}
|
||||
.skeleton-block {
|
||||
background: var(--background-base, #f1f1f1);
|
||||
background-image: linear-gradient(90deg, rgba(0,0,0,0.03) 0%, rgba(0,0,0,0.07) 40%, rgba(0,0,0,0.03) 80%);
|
||||
background: var(--black-alpha-4);
|
||||
background-image: linear-gradient(90deg, transparent 0%, var(--black-alpha-8) 40%, transparent 80%);
|
||||
background-size: 480px 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: sk-shimmer 1.2s ease-in-out infinite;
|
||||
border-radius: 8px;
|
||||
box-shadow: inset 0 0 0 1px var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
.skeleton-grid {
|
||||
display: grid;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -153,8 +153,228 @@
|
||||
--shadow-cta-active:
|
||||
0 1px 2px rgba(0, 47, 167, .18);
|
||||
--shadow-floating: 0 8px 28px rgba(17, 19, 24, .06);
|
||||
|
||||
/* 容器四角准星:用 mask 着色,让它跟随当前主题的边框层级。 */
|
||||
--corner-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21'%3E%3Cpath fill='%23000' d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
Dark theme · 同一套语义 token 反色
|
||||
由 html[data-theme="dark"] 驱动(见 theme.ts / settings 外观)
|
||||
============================================================ */
|
||||
html[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--background-base: #0f1115;
|
||||
--background-lighter: #14171c;
|
||||
--surface: #181b21;
|
||||
--surface-raised: #1e2229;
|
||||
|
||||
--border-faint: #2a2f38;
|
||||
--border-muted: #323843;
|
||||
--border-loud: #3c4450;
|
||||
|
||||
/* 主文字走近白;accent-white 保持真白(按钮/反色字) */
|
||||
--accent-black: #e8eaed;
|
||||
--accent-white: #ffffff;
|
||||
--nav-active: #e8eaed;
|
||||
|
||||
--forest-bg: rgba(66, 195, 102, .14);
|
||||
--forest-bd: rgba(66, 195, 102, .32);
|
||||
--crimson-bg: rgba(235, 52, 36, .14);
|
||||
--crimson-bd: rgba(235, 52, 36, .32);
|
||||
--honey-bg: rgba(236, 183, 48, .14);
|
||||
--honey-bd: rgba(236, 183, 48, .32);
|
||||
|
||||
/* 克莱因在深底上略提亮,避免发灰 */
|
||||
--klein: #3d6bff;
|
||||
--klein-hover: #5a82ff;
|
||||
--heat: var(--klein);
|
||||
--heat-hover: var(--klein-hover);
|
||||
|
||||
--black: var(--accent-black);
|
||||
--text: var(--accent-black);
|
||||
--heat-90: rgba(61, 107, 255, .90);
|
||||
--heat-40: rgba(61, 107, 255, .40);
|
||||
--heat-20: rgba(61, 107, 255, .20);
|
||||
--heat-16: rgba(61, 107, 255, .16);
|
||||
--heat-12: rgba(61, 107, 255, .14);
|
||||
--heat-8: rgba(61, 107, 255, .10);
|
||||
--heat-4: rgba(61, 107, 255, .06);
|
||||
|
||||
/* black-alpha → 白透明阶梯(同语义:轻微叠色) */
|
||||
--black-alpha-1: rgba(255, 255, 255, .015);
|
||||
--black-alpha-2: rgba(255, 255, 255, .025);
|
||||
--black-alpha-3: rgba(255, 255, 255, .035);
|
||||
--black-alpha-4: rgba(255, 255, 255, .05);
|
||||
--black-alpha-5: rgba(255, 255, 255, .06);
|
||||
--black-alpha-6: rgba(255, 255, 255, .07);
|
||||
--black-alpha-7: rgba(255, 255, 255, .08);
|
||||
--black-alpha-8: rgba(255, 255, 255, .09);
|
||||
--black-alpha-10: rgba(255, 255, 255, .11);
|
||||
--black-alpha-12: rgba(255, 255, 255, .13);
|
||||
--black-alpha-16: rgba(255, 255, 255, .16);
|
||||
--black-alpha-20: rgba(255, 255, 255, .20);
|
||||
--black-alpha-24: rgba(255, 255, 255, .24);
|
||||
--black-alpha-32: rgba(255, 255, 255, .32);
|
||||
--black-alpha-40: rgba(255, 255, 255, .40);
|
||||
--black-alpha-48: rgba(255, 255, 255, .48);
|
||||
--black-alpha-56: rgba(255, 255, 255, .56);
|
||||
--black-alpha-64: rgba(255, 255, 255, .64);
|
||||
--black-alpha-72: rgba(255, 255, 255, .72);
|
||||
--black-alpha-88: rgba(255, 255, 255, .88);
|
||||
|
||||
--shadow-cta:
|
||||
0 1px 2px rgba(61, 107, 255, .22),
|
||||
0 6px 16px rgba(61, 107, 255, .28);
|
||||
--shadow-cta-hover:
|
||||
0 2px 4px rgba(61, 107, 255, .26),
|
||||
0 10px 22px rgba(61, 107, 255, .34);
|
||||
--shadow-cta-active:
|
||||
0 1px 2px rgba(61, 107, 255, .22);
|
||||
--shadow-floating: 0 8px 28px rgba(0, 0, 0, .45);
|
||||
|
||||
/* styles.css legacy aliases */
|
||||
--bg: var(--background-base);
|
||||
--bg-soft: var(--background-lighter);
|
||||
--card: var(--surface);
|
||||
--border: var(--border-faint);
|
||||
--border-soft: var(--border-muted);
|
||||
--ink: var(--accent-black);
|
||||
--ink-2: rgba(232, 234, 237, .72);
|
||||
--ink-3: rgba(232, 234, 237, .52);
|
||||
--ink-4: rgba(232, 234, 237, .32);
|
||||
--orange: var(--klein);
|
||||
--orange-soft: var(--heat-40);
|
||||
--orange-tint: var(--heat-8);
|
||||
--orange-hover: var(--klein-hover);
|
||||
--green: #6dd88a;
|
||||
--green-bg: var(--forest-bg);
|
||||
--green-bd: var(--forest-bd);
|
||||
--red: #ff6b5c;
|
||||
--red-bg: var(--crimson-bg);
|
||||
--red-bd: var(--crimson-bd);
|
||||
}
|
||||
|
||||
/* 壳层硬编码浅色 → dark 对齐 */
|
||||
html[data-theme="dark"] aside.sidebar {
|
||||
/* 盖过 aside.sidebar 本地 --klein:#002fa7,否则子菜单 active 仍是深蓝 */
|
||||
--klein: #3d6bff;
|
||||
--klein-hover: #5a82ff;
|
||||
--st-black: #e8eaed;
|
||||
border-right-color: rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-submenu {
|
||||
border-left-color: rgba(61, 107, 255, 0.32);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-subitem {
|
||||
color: rgba(232, 234, 237, 0.78);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-subitem:hover {
|
||||
color: #a8bbff;
|
||||
background: rgba(61, 107, 255, 0.14);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-subitem.active {
|
||||
color: #c5d2ff;
|
||||
background: rgba(61, 107, 255, 0.2);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-subitem.active::before {
|
||||
box-shadow: 0 0 0 4px rgba(61, 107, 255, 0.22);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-subitem .pill-mini {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #a8bbff;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-head {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
html[data-theme="dark"] .brand-logo {
|
||||
mix-blend-mode: normal;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.92;
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-item {
|
||||
color: rgba(232, 234, 237, 0.78);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-item .nav-desc,
|
||||
html[data-theme="dark"] aside.sidebar .nav-item .nav-sub {
|
||||
color: rgba(232, 234, 237, 0.42);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-item.active,
|
||||
html[data-theme="dark"] aside.sidebar .nav-item[aria-current="page"] {
|
||||
color: #e8eaed;
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .org-switcher,
|
||||
html[data-theme="dark"] aside.sidebar .org-switcher span {
|
||||
color: rgba(232, 234, 237, 0.78);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .org-switcher svg {
|
||||
color: rgba(232, 234, 237, 0.48);
|
||||
}
|
||||
html[data-theme="dark"] .user .av {
|
||||
background: #3a4050;
|
||||
color: #fff;
|
||||
}
|
||||
html[data-theme="dark"] .grid-bg {
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
|
||||
}
|
||||
html[data-theme="dark"] .topbar {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
html[data-theme="dark"] .top-search {
|
||||
border-color: #3a4050;
|
||||
color: #e8eaed;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
html[data-theme="dark"] .top-search:hover {
|
||||
border-color: #4a5160;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
html[data-theme="dark"] .queue-chip,
|
||||
html[data-theme="dark"] .balance-chip {
|
||||
border-color: var(--border-faint);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] aside.sidebar .nav-item .nav-chevron {
|
||||
color: rgba(232, 234, 237, 0.42);
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-item:hover {
|
||||
color: #e8eaed;
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .liquid-shape {
|
||||
/* 深色下不用纯黑胶囊,改用品牌蓝选中态 */
|
||||
color: #3d6bff;
|
||||
filter: drop-shadow(0 5px 10px rgba(61, 107, 255, 0.28));
|
||||
}
|
||||
html[data-theme="dark"] aside.sidebar .nav-liquid,
|
||||
html[data-theme="dark"] aside.sidebar .nav-capsule {
|
||||
background: rgba(61, 107, 255, 0.12);
|
||||
box-shadow: 0 0 0 4px rgba(61, 107, 255, 0.12);
|
||||
}
|
||||
html[data-theme="dark"] .main,
|
||||
html[data-theme="dark"] .content,
|
||||
html[data-theme="dark"] .page {
|
||||
background: var(--background-base);
|
||||
color: var(--accent-black);
|
||||
}
|
||||
html[data-theme="dark"] .icon-btn {
|
||||
background: var(--surface);
|
||||
border-color: var(--border-faint);
|
||||
color: var(--black-alpha-64);
|
||||
}
|
||||
html[data-theme="dark"] .icon-btn:hover {
|
||||
background: var(--background-lighter);
|
||||
color: var(--accent-black);
|
||||
border-color: var(--border-loud);
|
||||
}
|
||||
|
||||
|
||||
::selection { background: var(--heat-20); color: var(--heat); }
|
||||
|
||||
html, body {
|
||||
@@ -268,7 +488,7 @@ aside.sidebar {
|
||||
min-height: var(--topbar-height);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid rgba(27, 32, 40, 0.07);
|
||||
}
|
||||
.brand { display: flex; align-items: center; justify-content: center; min-width: 0; color: var(--accent-black); }
|
||||
@@ -607,7 +827,7 @@ aside.sidebar .nav-subitem .pill-mini {
|
||||
padding: 0 5px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--klein);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
@@ -619,7 +839,7 @@ aside.sidebar .liquid-indicator {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
width: 132px;
|
||||
width: 152px;
|
||||
height: 76px;
|
||||
pointer-events: none;
|
||||
transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
@@ -635,6 +855,7 @@ aside.sidebar .liquid-shape {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
color: #101012;
|
||||
filter: drop-shadow(0 5px 7px rgba(0, 0, 0, 0.14));
|
||||
transform-origin: left center;
|
||||
}
|
||||
@@ -651,7 +872,7 @@ aside.sidebar .indicator-content {
|
||||
top: 0;
|
||||
left: 33px;
|
||||
height: 76px;
|
||||
width: 94px;
|
||||
width: 112px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
@@ -776,7 +997,7 @@ body.sidebar-collapsed .user::after { display: none; }
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow-x: clip;
|
||||
@@ -827,7 +1048,7 @@ body.sidebar-collapsed .user::after { display: none; }
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
padding: 0 clamp(36px, 3.2vw, 68px);
|
||||
border-bottom: 1px solid rgba(27, 32, 40, 0.08);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 4px 16px rgba(20, 27, 38, 0.035);
|
||||
position: sticky; top: 0; z-index: var(--z-topbar);
|
||||
align-self: start;
|
||||
@@ -1168,8 +1389,11 @@ body.sidebar-collapsed .user::after { display: none; }
|
||||
.with-corners .corner-tr, .with-corners .corner-bl {
|
||||
content: ''; position: absolute;
|
||||
width: 14px; height: 14px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
|
||||
background-size: contain;
|
||||
background-color: var(--border-muted);
|
||||
-webkit-mask: var(--corner-mark) center / contain no-repeat;
|
||||
mask: var(--corner-mark) center / contain no-repeat;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.card-hard.with-corners::before { top: -7px; left: -7px; }
|
||||
@@ -2269,6 +2493,11 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
color: var(--black-alpha-56);
|
||||
font-weight: 500;
|
||||
}
|
||||
html[data-theme="dark"] .placeholder .ph-frame {
|
||||
background: var(--surface-raised);
|
||||
border-color: var(--border-faint);
|
||||
color: var(--black-alpha-56);
|
||||
}
|
||||
.placeholder.has-mock-media {
|
||||
background-color: var(--background-lighter);
|
||||
background-image: var(--mock-media-url);
|
||||
@@ -2599,6 +2828,7 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
margin: 6px 4px;
|
||||
}
|
||||
|
||||
|
||||
/* ─── Modal ─── */
|
||||
.modal-bg {
|
||||
position: fixed; inset: 0;
|
||||
@@ -2632,24 +2862,27 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 14px; height: 14px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
|
||||
background-size: contain;
|
||||
background-color: var(--border-muted);
|
||||
-webkit-mask: var(--corner-mark) center / contain no-repeat;
|
||||
mask: var(--corner-mark) center / contain no-repeat;
|
||||
pointer-events: none;
|
||||
color: var(--black-alpha-48);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px; line-height: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
.modal::before { top: -7px; left: -7px; }
|
||||
.modal::after { bottom: -7px; right: -7px; }
|
||||
.modal::before { top: 0; left: 0; }
|
||||
.modal::after { bottom: 0; right: 0; }
|
||||
.modal .corner-tr, .modal .corner-bl {
|
||||
position: absolute;
|
||||
width: 14px; height: 14px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
|
||||
background-size: contain;
|
||||
background-color: var(--border-muted);
|
||||
-webkit-mask: var(--corner-mark) center / contain no-repeat;
|
||||
mask: var(--corner-mark) center / contain no-repeat;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.modal .corner-tr { top: -7px; right: -7px; }
|
||||
.modal .corner-bl { bottom: -7px; left: -7px; }
|
||||
.modal .corner-tr { top: 0; right: 0; }
|
||||
.modal .corner-bl { bottom: 0; left: 0; }
|
||||
.modal-h {
|
||||
padding: 22px 24px 16px;
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
@@ -2716,6 +2949,12 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
font-family: inherit;
|
||||
}
|
||||
.card-del-btn svg { width: 14px; height: 14px; }
|
||||
html[data-theme="dark"] .card-del-btn {
|
||||
background: var(--surface-raised);
|
||||
border-color: var(--border-faint);
|
||||
color: var(--black-alpha-56);
|
||||
box-shadow: none;
|
||||
}
|
||||
.product-card:hover .card-del-btn,
|
||||
.asset-card:hover .card-del-btn,
|
||||
.project-card:hover .card-del-btn,
|
||||
@@ -2804,10 +3043,18 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
position: fixed; top: 0; left: 0; bottom: 0;
|
||||
width: var(--sidebar-width);
|
||||
z-index: 1200;
|
||||
/* 手机上的侧栏是抽屉,必须遮住页面内容,不能沿用桌面端的半透明背景。 */
|
||||
background: var(--surface-raised);
|
||||
border-right-color: var(--border-faint);
|
||||
transform: translateX(-100%);
|
||||
transition: transform .28s cubic-bezier(.32, .72, 0, 1);
|
||||
box-shadow: 0 0 40px rgba(0, 0, 0, .18);
|
||||
}
|
||||
/* 暗色桌面侧栏可保留轻透层;窄屏抽屉必须是实底,避免透出页面内容。 */
|
||||
html[data-theme="dark"] aside.sidebar {
|
||||
background: var(--surface-raised);
|
||||
border-right-color: var(--border-faint);
|
||||
}
|
||||
/* 抽屉里收窄态不适用,强制展开宽度,避免 body.sidebar-collapsed 把抽屉压成 96px */
|
||||
body.sidebar-collapsed aside.sidebar { width: var(--sidebar-width); }
|
||||
aside.sidebar.mobile-open { transform: translateX(0); }
|
||||
@@ -2822,8 +3069,22 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
}
|
||||
.mobile-nav-btn span { display: block; width: 16px; height: 1.6px; background: var(--accent-black); border-radius: 2px; }
|
||||
.mobile-nav-backdrop { position: fixed; inset: 0; z-index: 1150; background: rgba(21, 20, 15, .42); }
|
||||
/* 顶栏给汉堡键让出左侧空间 */
|
||||
.topbar { padding-left: 64px; }
|
||||
/* 顶栏给汉堡键让出左侧空间;搜索与余额压缩为单行工具,避免中文在窄窗逐字折行。 */
|
||||
.topbar { padding: 0 16px 0 64px; gap: 8px; }
|
||||
.topbar .right { gap: 8px; flex: 0 0 auto; }
|
||||
.top-search {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.top-search span { display: none; }
|
||||
.balance-chip {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.balance-chip-label { display: none; }
|
||||
.stats { grid-template-columns: repeat(2, 1fr); }
|
||||
.stat:nth-child(2) { border-right: 0; }
|
||||
.stat:nth-child(1), .stat:nth-child(2) { border-bottom: 1px solid var(--border-faint); }
|
||||
@@ -2899,7 +3160,7 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
left: 0;
|
||||
width: 252px;
|
||||
padding: 11px 13px 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--accent-black);
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: var(--r-md);
|
||||
@@ -2921,7 +3182,7 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
position: absolute;
|
||||
top: -5px; left: 14px;
|
||||
width: 10px; height: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border-left: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-top: 1px solid rgba(34, 42, 54, 0.10);
|
||||
transform: rotate(45deg);
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
border: 1px solid rgba(28, 34, 43, 0.12);
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
@@ -598,7 +598,7 @@
|
||||
max-width: 280px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 30px rgba(20, 27, 38, 0.12);
|
||||
@@ -708,7 +708,7 @@
|
||||
left: 0;
|
||||
z-index: 60;
|
||||
min-width: 180px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 30px rgba(20, 27, 38, 0.12);
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--lib-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.library-page .lib-search svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--lib-muted); }
|
||||
.library-page .lib-search input {
|
||||
@@ -152,7 +152,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 10px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -175,7 +175,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid var(--lib-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -197,7 +197,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { applyTheme, readStoredAppearance } from "./theme";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./styles.css";
|
||||
@@ -22,5 +23,8 @@ import "./quick-create-page.css";
|
||||
import "./admin-page.css";
|
||||
import "./omni-create-page.css";
|
||||
import "./omni-session-page.css";
|
||||
import "./dark-mode-pages.css";
|
||||
|
||||
applyTheme(readStoredAppearance());
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(28, 34, 43, 0.12);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--st-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -178,7 +178,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--st-muted);
|
||||
}
|
||||
.msg-search input {
|
||||
@@ -196,6 +196,8 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.msg-load-more {
|
||||
padding: 14px 16px 18px;
|
||||
@@ -274,6 +276,7 @@
|
||||
}
|
||||
|
||||
.msg-empty {
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
.models-page .ml-check svg { width: 13px; height: 13px; }
|
||||
.models-page.manage-mode .ml-card:not(.selectable) { cursor: not-allowed; }
|
||||
.models-page.manage-mode .ml-card.selectable .ml-check { display: grid; }
|
||||
.models-page.manage-mode .ml-card.selected .ml-check {
|
||||
color: #fff;
|
||||
@@ -383,7 +384,7 @@
|
||||
padding: 0 12px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: #414750;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -36,24 +36,31 @@
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
height: 36px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
padding: 0 14px;
|
||||
border: 1.5px solid rgba(16, 16, 18, .22);
|
||||
border-radius: 12px;
|
||||
color: var(--black);
|
||||
background: #fff;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
color: var(--black-alpha-56);
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
box-shadow: 0 4px 12px rgba(16, 16, 18, .08);
|
||||
font-weight: 500;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--t-base), border-color var(--t-base), color var(--t-base);
|
||||
}
|
||||
|
||||
.omni-home-history:hover {
|
||||
border-color: var(--black);
|
||||
background: #f7f7f8;
|
||||
border-color: var(--black-alpha-24);
|
||||
color: var(--accent-black);
|
||||
background: var(--black-alpha-4);
|
||||
}
|
||||
|
||||
.omni-home-history:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--background-base), 0 0 0 4px var(--heat-40);
|
||||
}
|
||||
|
||||
.omni-home-history svg {
|
||||
@@ -378,7 +385,7 @@
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 13px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 18px 38px rgba(20, 27, 38, .15);
|
||||
}
|
||||
|
||||
@@ -447,7 +454,7 @@
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 13px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 18px 38px rgba(20, 27, 38, .15);
|
||||
}
|
||||
|
||||
@@ -582,7 +589,7 @@
|
||||
.omni-parameter .custom-select-trigger:hover,
|
||||
.omni-parameter .custom-select-shell.open .custom-select-trigger {
|
||||
border-color: rgba(0, 47, 167, .36);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.omni-parameter .custom-select-trigger svg {
|
||||
@@ -640,7 +647,7 @@
|
||||
.omni-duration-trigger:hover,
|
||||
.omni-duration-trigger[aria-expanded="true"] {
|
||||
border-color: rgba(0, 47, 167, .36);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 0 0 3px rgba(0, 47, 167, .07);
|
||||
}
|
||||
|
||||
@@ -664,7 +671,7 @@
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 18px 38px rgba(20, 27, 38, .15);
|
||||
}
|
||||
|
||||
@@ -738,7 +745,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: #575e69;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
@@ -995,15 +1002,15 @@
|
||||
.omni-preset-dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(1040px, 100%);
|
||||
width: min(1180px, 100%);
|
||||
display: grid;
|
||||
grid-template-columns: 440px minmax(0, 1fr);
|
||||
gap: 42px;
|
||||
padding: 30px;
|
||||
grid-template-columns: 500px minmax(0, 1fr);
|
||||
gap: 32px;
|
||||
padding: 28px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-top: 3px solid var(--klein);
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 28px 70px rgba(10, 17, 30, .22);
|
||||
animation: omniMessageIn 180ms ease both;
|
||||
}
|
||||
@@ -1139,7 +1146,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 10px;
|
||||
color: #616873;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1231,7 +1238,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 10px;
|
||||
color: #414750;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -1307,35 +1314,34 @@
|
||||
.omni-history-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.omni-history-item {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 150px minmax(0, 1fr) 70px;
|
||||
grid-template-columns: 150px minmax(0, 1fr) 36px;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 17px;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(34, 42, 54, .09);
|
||||
border-radius: 19px;
|
||||
border-radius: var(--r-md);
|
||||
background: rgba(255, 255, 255, .96);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 22px rgba(20, 27, 38, .05);
|
||||
transition: transform 170ms ease, border-color 170ms ease, box-shadow 170ms ease;
|
||||
box-shadow: none;
|
||||
transition: border-color 170ms ease, background-color 170ms ease;
|
||||
}
|
||||
|
||||
.omni-history-item:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(0, 47, 167, .30);
|
||||
box-shadow: 0 13px 26px rgba(20, 27, 38, .09);
|
||||
background: var(--background-lighter);
|
||||
}
|
||||
|
||||
.omni-history-item > img {
|
||||
width: 150px;
|
||||
height: 112px;
|
||||
display: block;
|
||||
border-radius: 13px;
|
||||
border-radius: var(--r-md);
|
||||
object-fit: cover;
|
||||
background: #edf0f4;
|
||||
}
|
||||
@@ -1354,21 +1360,24 @@
|
||||
}
|
||||
|
||||
.omni-history-item h2 {
|
||||
margin: 8px 0 7px;
|
||||
font-size: 17px;
|
||||
line-height: 1.35;
|
||||
margin: 6px 0 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.omni-history-item p,
|
||||
.omni-history-item small {
|
||||
color: #858b94;
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.omni-history-item small {
|
||||
display: block;
|
||||
margin-top: 9px;
|
||||
margin-top: 7px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.omni-history-actions {
|
||||
@@ -1389,14 +1398,16 @@
|
||||
}
|
||||
|
||||
.omni-history-actions button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
color: #858b94;
|
||||
background: #f4f5f7;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, opacity 160ms ease;
|
||||
}
|
||||
|
||||
.omni-history-actions button:hover {
|
||||
@@ -1404,6 +1415,25 @@
|
||||
background: #fff0f0;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.omni-history-actions button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.omni-history-item:hover .omni-history-actions button,
|
||||
.omni-history-item:focus-within .omni-history-actions button,
|
||||
.omni-history-actions button:focus-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.omni-history-item:hover .omni-history-actions button {
|
||||
border-color: rgba(34, 42, 54, .09);
|
||||
background: #f4f5f7;
|
||||
}
|
||||
}
|
||||
|
||||
.omni-delete-confirm {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -1432,7 +1462,7 @@
|
||||
padding: 26px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 17px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
text-align: center;
|
||||
box-shadow: 0 26px 64px rgba(10, 17, 30, .22);
|
||||
animation: omniMessageIn 180ms ease both;
|
||||
@@ -1476,7 +1506,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 9px;
|
||||
color: #5e6570;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
@@ -1547,7 +1577,7 @@
|
||||
.omni-parameter .rs-select-btn:hover,
|
||||
.omni-parameter .rs-select.open .rs-select-btn {
|
||||
border-color: rgba(0, 47, 167, .36);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.omni-parameter .rs-select-btn svg {
|
||||
|
||||
@@ -71,12 +71,25 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 10px;
|
||||
color: #414750;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
color: var(--black-alpha-56);
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--t-base), border-color var(--t-base), color var(--t-base);
|
||||
}
|
||||
|
||||
.omni-session-back:hover {
|
||||
border-color: var(--black-alpha-24);
|
||||
color: var(--accent-black);
|
||||
background: var(--black-alpha-4);
|
||||
}
|
||||
|
||||
.omni-session-back:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--background-base), 0 0 0 4px var(--heat-40);
|
||||
}
|
||||
|
||||
.omni-session-back svg {
|
||||
@@ -375,7 +388,7 @@
|
||||
margin: 0 0 24px 44px;
|
||||
border: 1px solid rgba(34, 42, 54, .09);
|
||||
border-radius: 15px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 10px 26px rgba(20, 27, 38, .06);
|
||||
animation: omniMessageIn 220ms ease both;
|
||||
overflow: hidden;
|
||||
@@ -407,7 +420,7 @@
|
||||
margin: 0 0 24px 44px;
|
||||
border: 1px solid rgba(34, 42, 54, .09);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 30px rgba(20, 27, 38, .065);
|
||||
animation: omniMessageIn 220ms ease both;
|
||||
}
|
||||
@@ -594,7 +607,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, .08);
|
||||
border-radius: 10px;
|
||||
color: #5b626d;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
@@ -689,7 +702,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 9px;
|
||||
color: #333944;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
@@ -712,7 +725,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, .20);
|
||||
border-radius: 9px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
@@ -814,7 +827,7 @@
|
||||
.omni-session-preset-trigger[aria-expanded="true"] {
|
||||
border-color: rgba(0, 47, 167, .36);
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.omni-session-preset-trigger span {
|
||||
@@ -837,7 +850,7 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 17px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 24px 60px rgba(20, 27, 38, .18);
|
||||
transform: translateX(-52%);
|
||||
}
|
||||
@@ -1035,7 +1048,7 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 16px 36px rgba(20, 27, 38, .15);
|
||||
}
|
||||
|
||||
@@ -1707,7 +1720,7 @@
|
||||
padding: 7px 13px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: #575d66;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
@@ -1730,7 +1743,7 @@
|
||||
padding: 9px 12px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--black);
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
@@ -1754,7 +1767,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1844,7 +1857,7 @@
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.09);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 30px rgba(20, 27, 38, .065);
|
||||
/* 不走入场 opacity 动画:poll 重渲染时避免「闪一下」 */
|
||||
animation: none;
|
||||
@@ -1905,7 +1918,7 @@
|
||||
}
|
||||
|
||||
.omni-step-confirm-actions button.is-secondary {
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid rgba(34, 42, 54, 0.14);
|
||||
color: #222a36;
|
||||
}
|
||||
@@ -1946,7 +1959,7 @@
|
||||
|
||||
.omni-step-confirm-revise-input:focus {
|
||||
border-color: rgba(0, 47, 167, .42);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 0 0 3px rgba(0, 47, 167, .14);
|
||||
}
|
||||
|
||||
@@ -1973,7 +1986,7 @@
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(34, 42, 54, .10);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
/* 时长/参数下拉要冒出卡片,不能被裁 */
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -2604,7 +2617,7 @@
|
||||
gap: 10px;
|
||||
padding: 14px 14px 12px;
|
||||
border-bottom: 1px solid rgba(34, 42, 54, .08);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.omni-prompt-drawer-file {
|
||||
@@ -2684,7 +2697,7 @@
|
||||
padding: 14px 15px;
|
||||
border: 1px solid rgba(34, 42, 54, .06);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 6px 16px rgba(20, 27, 38, .04);
|
||||
}
|
||||
|
||||
@@ -2756,7 +2769,7 @@
|
||||
overflow-x: auto;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(34, 42, 54, .08);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.omni-prompt-table {
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.08);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pipeline-page .pl-rail-summary > span { color: var(--pl-muted); font-size: 10px; }
|
||||
.pipeline-page .pl-rail-track {
|
||||
@@ -139,7 +139,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.085);
|
||||
border-radius: 12px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
@@ -156,7 +156,7 @@
|
||||
}
|
||||
.pipeline-page .pl-step.static:hover {
|
||||
border-color: rgba(34, 42, 54, 0.085);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pipeline-page .pl-step.static:hover .pl-step-chev { color: var(--pl-muted); }
|
||||
.pipeline-page .pl-step.current {
|
||||
@@ -250,7 +250,7 @@
|
||||
.pipeline-page .pl-ghost {
|
||||
border: 1px solid rgba(34, 42, 54, 0.11);
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pipeline-page .pl-next {
|
||||
min-width: 210px;
|
||||
@@ -328,7 +328,7 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
.chat-msg.ai .chat-bubble {
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid rgba(34, 42, 54, 0.08);
|
||||
border-radius: 8px 8px 8px 2px;
|
||||
}
|
||||
@@ -465,7 +465,7 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 47, 167, 0.13);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 11px 26px rgba(20, 27, 38, 0.07);
|
||||
}
|
||||
.shot-head {
|
||||
@@ -504,7 +504,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.09);
|
||||
border-radius: 8px;
|
||||
color: var(--pl-muted);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
}
|
||||
.shot-icon-btn svg { width: 13px; height: 13px; }
|
||||
@@ -542,7 +542,7 @@
|
||||
.shot-area:focus {
|
||||
outline: none;
|
||||
border-color: rgba(0, 47, 167, 0.38);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 0 0 3px rgba(0, 47, 167, 0.07);
|
||||
}
|
||||
.shot-dialogue { min-height: 42px; }
|
||||
@@ -564,7 +564,7 @@
|
||||
.icon-mini-btn.armed { width: auto; padding: 0 8px; font-size: 12px; color: var(--accent-crimson); border-color: var(--accent-crimson); background: var(--surface); }
|
||||
.shot-insert-gap { flex: 0 0 auto; height: 10px; position: relative; display: flex; align-items: center; justify-content: center; padding: 0; transition: height .24s cubic-bezier(.18,.72,.28,1), padding .24s cubic-bezier(.18,.72,.28,1); }
|
||||
.shot-insert-gap:hover { height: 72px; padding: 14px 0; }
|
||||
.shot-insert-gap .add-shot-btn { opacity: 0; transform: translateY(4px) scale(.96); height: 28px; padding: 0 14px; background: #fff; color: var(--klein); border: 1px dashed rgba(0, 47, 167, 0.35); border-radius: 8px; font-size: 13px; font-family: inherit; font-weight: 500; cursor: pointer; transition: opacity .2s ease .04s, transform .24s cubic-bezier(.18,.72,.28,1) .04s, background var(--t-base), border-color var(--t-base), color var(--t-base); display: inline-flex; align-items: center; gap: 6px; pointer-events: none; white-space: nowrap; }
|
||||
.shot-insert-gap .add-shot-btn { opacity: 0; transform: translateY(4px) scale(.96); height: 28px; padding: 0 14px; background: var(--surface); color: var(--klein); border: 1px dashed rgba(0, 47, 167, 0.35); border-radius: 8px; font-size: 13px; font-family: inherit; font-weight: 500; cursor: pointer; transition: opacity .2s ease .04s, transform .24s cubic-bezier(.18,.72,.28,1) .04s, background var(--t-base), border-color var(--t-base), color var(--t-base); display: inline-flex; align-items: center; gap: 6px; pointer-events: none; white-space: nowrap; }
|
||||
.shot-insert-gap .add-shot-btn svg { width: 12px; height: 12px; }
|
||||
.shot-insert-gap:hover .add-shot-btn { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
|
||||
.shot-insert-gap .add-shot-btn:hover { background: rgba(0, 47, 167, 0.06); border-style: solid; border-color: var(--klein); }
|
||||
@@ -613,7 +613,7 @@
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 0 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
border: 1px solid rgba(34, 42, 54, 0.11);
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
@@ -638,7 +638,7 @@
|
||||
display: grid;
|
||||
grid-template-rows: 72px minmax(0, 1fr) 74px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.as-head {
|
||||
display: flex;
|
||||
@@ -677,7 +677,7 @@
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(0, 47, 167, 0.13);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 30px rgba(20, 27, 38, 0.075);
|
||||
}
|
||||
.as-row::before {
|
||||
@@ -755,7 +755,7 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 47, 167, 0.15);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 7px 18px rgba(20, 27, 38, 0.06);
|
||||
}
|
||||
.as-preview.ready {
|
||||
@@ -782,7 +782,7 @@
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-color: #fff;
|
||||
background-color: var(--surface);
|
||||
background-image:
|
||||
radial-gradient(ellipse 4px 1px at center, rgba(0, 47, 167, 0.28) 98%, transparent),
|
||||
radial-gradient(ellipse 1px 4px at center, rgba(0, 47, 167, 0.28) 98%, transparent);
|
||||
@@ -807,7 +807,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.26);
|
||||
border-radius: 8px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 22px rgba(0, 47, 167, 0.11);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@@ -911,7 +911,7 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 47, 167, 0.15);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 28px rgba(20, 27, 38, 0.085);
|
||||
}
|
||||
.as-gen-preview {
|
||||
@@ -923,7 +923,7 @@
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(34, 42, 54, 0.08);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.as-gen-preview.ready {
|
||||
background-image: var(--mock-media-url);
|
||||
@@ -1001,7 +1001,7 @@
|
||||
}
|
||||
.as-gen-prompt:focus {
|
||||
border-color: rgba(0, 47, 167, 0.38);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 0 0 3px rgba(0, 47, 167, 0.06);
|
||||
}
|
||||
.as-gen-actions {
|
||||
@@ -1088,7 +1088,7 @@
|
||||
.as-ai-btn svg, .as-ghost-btn svg { width: 14px; height: 14px; }
|
||||
.as-ai-btn:disabled, .as-ghost-btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }
|
||||
.as-ai-btn-lg { min-height: 44px; margin-top: 18px; padding: 0 18px; font-size: 14px; }
|
||||
.as-ghost-btn { border: 1px solid rgba(0, 47, 167, 0.19); color: var(--klein); background: #fff; }
|
||||
.as-ghost-btn { border: 1px solid rgba(0, 47, 167, 0.19); color: var(--klein); background: var(--surface); }
|
||||
.as-ghost-btn:hover { transform: translateY(-1px); border-color: rgba(0, 47, 167, 0.38); background: #eef5ff; }
|
||||
.as-ghost-btn-sm { min-height: 30px; font-size: 12px; }
|
||||
@media (max-width: 1180px) {
|
||||
@@ -1264,14 +1264,14 @@
|
||||
aspect-ratio: 9 / 16;
|
||||
max-height: 280px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.video-thumb.pending::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-color: #fff;
|
||||
background-color: var(--surface);
|
||||
background-image:
|
||||
radial-gradient(ellipse 4px 1px at center, rgba(0, 47, 167, 0.28) 98%, transparent),
|
||||
radial-gradient(ellipse 1px 4px at center, rgba(0, 47, 167, 0.28) 98%, transparent);
|
||||
@@ -1296,7 +1296,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.26);
|
||||
border-radius: 8px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 22px rgba(0, 47, 167, 0.11);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: -28px 0 70px rgba(10, 15, 24, 0.22);
|
||||
transform: translateX(100%);
|
||||
transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
@@ -422,7 +422,7 @@
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -445,7 +445,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
@@ -635,7 +635,7 @@
|
||||
padding: 0 8px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--accent-black);
|
||||
font: inherit;
|
||||
font-size: 10px;
|
||||
@@ -766,7 +766,7 @@
|
||||
padding: 10px 14px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 28px rgba(20, 27, 38, 0.12);
|
||||
}
|
||||
.pcd-toast-ic {
|
||||
@@ -816,7 +816,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.11);
|
||||
border-radius: 11px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@@ -845,7 +845,7 @@
|
||||
background: rgba(255, 255, 255, 0.90);
|
||||
box-shadow: 0 10px 28px rgba(20, 27, 38, 0.07);
|
||||
}
|
||||
.pcd-gallery-slot img { width: 100%; height: 100%; display: block; object-fit: contain; background: #fff; }
|
||||
.pcd-gallery-slot img { width: 100%; height: 100%; display: block; object-fit: contain; background: var(--surface); }
|
||||
.pcd-gallery-slot.empty {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--pl-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.pl-search svg {
|
||||
width: 16px;
|
||||
@@ -148,7 +148,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 10px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -181,7 +181,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid var(--pl-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -203,7 +203,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
@@ -316,6 +316,35 @@
|
||||
border-color: var(--klein);
|
||||
box-shadow: 0 0 0 2px rgba(0, 47, 167, 0.16), var(--pl-shadow);
|
||||
}
|
||||
.pl-skeleton-card {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.pl-skeleton-card:hover { transform: none; }
|
||||
.pl-skeleton-thumb {
|
||||
aspect-ratio: 4 / 3;
|
||||
margin: 8px 8px 0;
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
.pl-skeleton-meta {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.pl-skeleton-line { display: block; height: 12px; }
|
||||
.pl-skeleton-line--title { width: 52%; }
|
||||
.pl-skeleton-line--copy { width: 76%; height: 10px; }
|
||||
.pl-skeleton-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.pl-skeleton-pill {
|
||||
display: block;
|
||||
width: 58px;
|
||||
height: 22px;
|
||||
border-radius: var(--r-sm);
|
||||
}
|
||||
.pl-skeleton-pill--short { width: 46px; }
|
||||
|
||||
.pl-thumb {
|
||||
position: relative;
|
||||
@@ -492,7 +521,15 @@
|
||||
border-color: var(--klein, #002fa7);
|
||||
background: var(--klein, #002fa7);
|
||||
}
|
||||
.products-page.manage-mode .pl-card .card-del-btn { opacity: 0 !important; pointer-events: none !important; }
|
||||
/* 删除是管理动作:普通浏览态不因 hover 露出,进入管理态才显示。 */
|
||||
.products-page:not(.manage-mode) .product-card:hover .card-del-btn {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.products-page.manage-mode .pl-card .card-del-btn {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.products-page.manage-mode .pl-card button.pl-tag { pointer-events: none; }
|
||||
|
||||
/* 影擎吸底选择条 · 管理模式即出现 */
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.08);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.project-wizard-page .nw-rail-summary > span { color: var(--nw-muted); font-size: 10px; }
|
||||
.project-wizard-page .nw-rail-track {
|
||||
@@ -148,7 +148,7 @@
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.085);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.project-wizard-page .nw-step[hidden] {
|
||||
display: none;
|
||||
@@ -270,7 +270,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--nw-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.project-wizard-page .nw-search svg { width: 16px; height: 16px; color: var(--nw-muted); }
|
||||
.project-wizard-page .nw-search input {
|
||||
@@ -297,7 +297,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.15);
|
||||
border-radius: 10px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -317,7 +317,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid var(--nw-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -334,7 +334,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
@@ -513,7 +513,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--nw-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
color: var(--accent-black);
|
||||
@@ -549,7 +549,7 @@
|
||||
border: 1px solid var(--nw-line);
|
||||
border-radius: 999px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid var(--vc-line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
.projects-page .vc-search svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--vc-muted); }
|
||||
.projects-page .vc-search input {
|
||||
@@ -220,7 +220,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.12);
|
||||
border-radius: 10px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -243,7 +243,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid var(--vc-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 14px 34px rgba(20, 27, 38, 0.14);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -262,7 +262,7 @@
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: var(--accent-black);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
.quick-create-page .quick-field-label { display: flex; align-items: center; justify-content: space-between; gap: 14px; font-size: 14px; font-weight: 700; }
|
||||
.quick-create-page .quick-field-label small { color: var(--quick-muted); font-size: 11px; font-weight: 500; }
|
||||
.quick-create-page .quick-name-input { height: 50px; padding: 0 15px; border: 1px solid rgba(34,42,54,.13); border-radius: 12px; outline: none; background: rgba(248,249,252,.88); transition: border-color 180ms ease, box-shadow 180ms ease, background 180ms ease; }
|
||||
.quick-create-page .quick-name-input:focus { border-color: rgba(0,47,167,.55); background: #fff; box-shadow: 0 0 0 4px rgba(0,47,167,.08); }
|
||||
.quick-create-page .quick-name-input:focus { border-color: rgba(0,47,167,.55); background: var(--surface); box-shadow: 0 0 0 4px rgba(0,47,167,.08); }
|
||||
.quick-create-page .quick-upload { position: relative; min-height: 190px; display: grid; place-items: center; overflow: hidden; border: 1px dashed rgba(0,47,167,.28); border-radius: 14px; background: radial-gradient(circle at center,rgba(0,47,167,.07),transparent 47%),rgba(248,249,252,.86); }
|
||||
.quick-create-page .quick-upload input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.quick-create-page .quick-upload-copy { display: grid; justify-items: center; gap: 8px; padding: 24px; border: 0; color: inherit; background: transparent; font: inherit; text-align: center; cursor: pointer; }
|
||||
@@ -52,7 +52,7 @@
|
||||
.quick-create-page .quick-image-tile button { position: absolute; top: 3px; right: 3px; width: 19px; height: 19px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 50%; color: #fff; background: rgba(25,28,34,.72); cursor: pointer; }.quick-create-page .quick-image-tile button svg { width: 12px; height: 12px; }
|
||||
.quick-create-page .quick-upload-more { min-height: 240px; display: grid; place-items: center; align-content: center; gap: 8px; border: 1px dashed rgba(0,47,167,.35); border-radius: 13px; background: rgba(243,247,255,.78); }
|
||||
.quick-create-page .quick-upload-trigger { display: grid; justify-items: center; gap: 8px; padding: 0; border: 0; color: #303746; background: transparent; font: inherit; cursor: pointer; }.quick-create-page .quick-upload-trigger strong { font-size: 14px; font-weight: 700; }.quick-create-page .quick-upload-trigger small { color: var(--quick-muted); font-size: 12px; }
|
||||
.quick-create-page .quick-upload-more-icon { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 12px; color: var(--quick-blue); background: #fff; box-shadow: 0 5px 16px rgba(0,47,167,.08); }.quick-create-page .quick-upload-more-icon svg { width: 24px; height: 24px; stroke-width: 1.75; }
|
||||
.quick-create-page .quick-upload-more-icon { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 12px; color: var(--quick-blue); background: var(--surface); box-shadow: 0 5px 16px rgba(0,47,167,.08); }.quick-create-page .quick-upload-more-icon svg { width: 24px; height: 24px; stroke-width: 1.75; }
|
||||
.quick-create-page .quick-clear-all { padding: 5px 9px; border: 0; border-radius: 7px; color: var(--quick-muted); background: rgba(255,255,255,.72); font: inherit; font-size: 11px; cursor: pointer; }
|
||||
.quick-create-page .quick-parameter-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; margin-top: 20px; }
|
||||
.quick-create-page .quick-parameter-field { min-width: 0; display: grid; gap: 9px; padding: 14px; border: 1px solid rgba(34,42,54,.10); border-radius: 12px; background: rgba(248,249,252,.72); }
|
||||
@@ -61,7 +61,7 @@
|
||||
.quick-create-page .quick-parameter-field .rs-select-btn { height: 38px; font-size: 12px; }
|
||||
.quick-create-page .quick-form-footer { display: flex; margin-top: 26px; }
|
||||
.quick-create-page .quick-generate-button { width: 100%; height: 48px; display: inline-flex; align-items: center; justify-content: center; gap: 9px; border: 0; border-radius: 12px; color: #fff; background: var(--quick-blue); font: inherit; font-weight: 700; cursor: pointer; box-shadow: 0 12px 24px rgba(0,47,167,.22); }.quick-create-page .quick-generate-button:disabled { color: #99a1ae; background: #e5e8ee; box-shadow: none; cursor: not-allowed; }.quick-create-page .quick-generate-button svg { width: 20px; height: 20px; }
|
||||
.quick-create-page .quick-cancel-button { width: 100%; height: 48px; display: inline-flex; align-items: center; justify-content: center; gap: 9px; border: 1px solid rgba(34,42,54,.16); border-radius: 12px; color: #272a30; background: #fff; font: inherit; font-weight: 700; cursor: pointer; }.quick-create-page .quick-cancel-button:hover:not(:disabled) { border-color: rgba(200,61,77,.35); color: #c83d4d; background: rgba(200,61,77,.06); }.quick-create-page .quick-cancel-button:disabled { opacity: .55; cursor: not-allowed; }.quick-create-page .quick-cancel-button svg { width: 18px; height: 18px; }
|
||||
.quick-create-page .quick-cancel-button { width: 100%; height: 48px; display: inline-flex; align-items: center; justify-content: center; gap: 9px; border: 1px solid rgba(34,42,54,.16); border-radius: 12px; color: #272a30; background: var(--surface); font: inherit; font-weight: 700; cursor: pointer; }.quick-create-page .quick-cancel-button:hover:not(:disabled) { border-color: rgba(200,61,77,.35); color: #c83d4d; background: rgba(200,61,77,.06); }.quick-create-page .quick-cancel-button:disabled { opacity: .55; cursor: not-allowed; }.quick-create-page .quick-cancel-button svg { width: 18px; height: 18px; }
|
||||
.quick-create-page .quick-create-shell.is-generating .quick-form-panel { opacity: .82; }
|
||||
.quick-create-page .quick-create-shell.is-generating .quick-form-footer { pointer-events: auto; opacity: 1; }
|
||||
.quick-create-page .quick-status-panel { position: relative; display: grid; place-items: center; min-height: 0; padding: 34px; background: radial-gradient(circle at 50% 45%,rgba(0,47,167,.075),transparent 34%),rgba(250,251,253,.88); }.quick-create-page .quick-state { width: min(560px,100%); display: none; }.quick-create-page .quick-state-ready { display: grid; justify-items: center; text-align: center; }
|
||||
@@ -85,7 +85,7 @@
|
||||
.quick-create-page .quick-state-ready { position: relative; top: -10px; }
|
||||
.quick-create-page .quick-state-ready h2 { margin: 0 0 9px; font-size: 23px; }.quick-create-page .quick-state-ready > p { max-width: 430px; margin: 0; color: var(--quick-muted); font-size: 13px; line-height: 1.7; }.quick-create-page .quick-ready-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 20px; }.quick-create-page .quick-ready-tags span { padding: 7px 10px; border: 1px solid rgba(34,42,54,.08); border-radius: 999px; color: #505762; background: rgba(255,255,255,.72); font-size: 11px; }
|
||||
.quick-create-page .quick-state-generating { width: min(460px,100%); justify-items: center; text-align: center; }
|
||||
.quick-create-page .quick-generating-preview { position: relative; width: 460px; max-width: 100%; height: 258px; display: grid; place-items: center; margin-bottom: 27px; border: 1px solid rgba(0,47,167,.16); border-radius: 15px; background: #edf4ff; box-shadow: 0 18px 34px rgba(0,47,167,.08); }.quick-create-page .quick-generating-preview::before { content: ""; position: absolute; inset: 18px; border-radius: 10px; background: #fff; }
|
||||
.quick-create-page .quick-generating-preview { position: relative; width: 460px; max-width: 100%; height: 258px; display: grid; place-items: center; margin-bottom: 27px; border: 1px solid rgba(0,47,167,.16); border-radius: 15px; background: #edf4ff; box-shadow: 0 18px 34px rgba(0,47,167,.08); }.quick-create-page .quick-generating-preview::before { content: ""; position: absolute; inset: 18px; border-radius: 10px; background: var(--surface); }
|
||||
.quick-create-page .quick-preview-spinner { position: relative; z-index: 1; width: 62px; height: 62px; border-radius: 50%; background: conic-gradient(from 160deg,transparent 0deg,transparent 136deg,var(--quick-blue) 300deg,rgba(0,47,167,.12) 360deg); -webkit-mask: radial-gradient(circle,transparent 0 66%,#000 69%); mask: radial-gradient(circle,transparent 0 66%,#000 69%); animation: quick-spinner-rotate 1.1s linear infinite; }
|
||||
.quick-create-page .quick-generating-copy { width: 100%; text-align: center; }.quick-create-page .quick-generating-copy h2 { margin: 0 0 8px; font-size: 23px; }.quick-create-page .quick-generating-copy p { margin: 0; color: var(--quick-muted); font-size: 13px; line-height: 1.7; }
|
||||
.quick-create-page .quick-progress-track { width: 100%; display: flex; align-items: flex-start; margin-top: 28px; }
|
||||
@@ -104,7 +104,7 @@
|
||||
.quick-create-page .quick-state-complete { width: min(560px,100%); gap: 17px; }
|
||||
.quick-create-page .quick-video-result-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; }
|
||||
.quick-create-page .quick-video-result-grid.is-single { grid-template-columns: minmax(0, 1fr); width: min(320px,100%); justify-self: center; }
|
||||
.quick-create-page .quick-video-result-card { min-width: 0; padding: 8px 8px 9px; border: 1px solid rgba(34,42,54,.10); border-radius: 8px; color: #25282d; background: #fff; font: inherit; text-align: left; cursor: pointer; }
|
||||
.quick-create-page .quick-video-result-card { min-width: 0; padding: 8px 8px 9px; border: 1px solid rgba(34,42,54,.10); border-radius: 8px; color: #25282d; background: var(--surface); font: inherit; text-align: left; cursor: pointer; }
|
||||
.quick-create-page .quick-video-result-thumb { position: relative; display: block; aspect-ratio: 16/9; overflow: hidden; border-radius: 8px; background: #eef1f5; }
|
||||
.quick-create-page .quick-video-result-thumb img,.quick-create-page .quick-video-result-thumb video { width: 100%; height: 100%; display: block; object-fit: cover; pointer-events: none; }
|
||||
.quick-create-page .quick-video-play { position: absolute; inset: 0; display: grid; place-items: center; color: #fff; background: rgba(0,0,0,.22); }
|
||||
@@ -130,7 +130,7 @@
|
||||
.quick-create-page .quick-history-head h2 { margin: 0; font-size: 20px; font-weight: 600; }
|
||||
.quick-create-page .quick-history-head span { color: var(--quick-muted); font-size: 13px; }
|
||||
.quick-create-page .quick-history-list { display: grid; gap: 10px; }
|
||||
.quick-create-page .quick-history-card { display: grid; grid-template-columns: 112px minmax(0,1fr) auto; align-items: center; gap: 16px; padding: 12px 14px; border: 1px solid rgba(34,42,54,.10); border-radius: 8px; background: #fff; box-shadow: inset 3px 0 0 var(--quick-blue); }
|
||||
.quick-create-page .quick-history-card { display: grid; grid-template-columns: 112px minmax(0,1fr) auto; align-items: center; gap: 16px; padding: 12px 14px; border: 1px solid rgba(34,42,54,.10); border-radius: 8px; background: var(--surface); box-shadow: inset 3px 0 0 var(--quick-blue); }
|
||||
.quick-create-page .quick-history-thumb { position: relative; width: 112px; aspect-ratio: 16/9; overflow: hidden; padding: 0; border: 0; border-radius: 8px; background: #eef1f5; cursor: pointer; }
|
||||
.quick-create-page .quick-history-thumb:disabled { cursor: default; }
|
||||
.quick-create-page .quick-history-thumb img { width: 100%; height: 100%; display: block; object-fit: cover; }
|
||||
@@ -144,7 +144,7 @@
|
||||
.quick-create-page .quick-history-badge.is-wait { color: var(--quick-muted); background: rgba(34,42,54,.06); }
|
||||
.quick-create-page .quick-history-copy h3 { margin: 6px 0 4px; font-size: 16px; font-weight: 600; }
|
||||
.quick-create-page .quick-history-copy p { margin: 0; color: var(--quick-muted); font-size: 12px; }
|
||||
.quick-create-page .quick-history-open { min-height: 36px; display: inline-flex; align-items: center; gap: 6px; padding: 0 12px; border: 1px solid rgba(34,42,54,.12); border-radius: 8px; color: var(--quick-blue); background: #fff; font: inherit; font-size: 13px; cursor: pointer; }
|
||||
.quick-create-page .quick-history-open { min-height: 36px; display: inline-flex; align-items: center; gap: 6px; padding: 0 12px; border: 1px solid rgba(34,42,54,.12); border-radius: 8px; color: var(--quick-blue); background: var(--surface); font: inherit; font-size: 13px; cursor: pointer; }
|
||||
.quick-create-page .quick-history-open:hover { background: rgba(0,47,167,.05); }
|
||||
.quick-create-page .quick-history-open svg { width: 15px; height: 15px; }
|
||||
.quick-create-page .quick-history-empty { margin: 0; padding: 28px 8px; color: var(--quick-muted); font-size: 13px; }
|
||||
|
||||
@@ -20,7 +20,7 @@ const TABS: { k: Tab; label: string; title: string; note: string }[] = [
|
||||
{ k: "mine", label: "我的模特", title: "我的模特", note: "维护可复用的品牌模特与人物参考资产" }
|
||||
];
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
const PAGE_SIZE = 24;
|
||||
|
||||
type Preview = { src: string; kind: "image"; name: string };
|
||||
|
||||
@@ -294,7 +294,7 @@ function modelTags(m: ModelEntity): string[] {
|
||||
}
|
||||
|
||||
export function ModelsPage({ onNotify, onBillingChanged }: {
|
||||
onNotify?: (type: "success" | "error", text: string) => void;
|
||||
onNotify?: (type: "success" | "error" | "info", text: string) => void;
|
||||
onBillingChanged?: () => void;
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("all");
|
||||
@@ -313,6 +313,14 @@ export function ModelsPage({ onNotify, onBillingChanged }: {
|
||||
const fileRef = useRef<HTMLInputElement | null>(null);
|
||||
const tabCopy = TABS.find((item) => item.k === tab) || TABS[0];
|
||||
const exitEdit = () => { setEditMode(false); setSelected(new Set()); };
|
||||
const enterEdit = () => {
|
||||
setEditMode(true);
|
||||
// 全部列表把官方模板排在前面,第一页往往一张都删不了,底栏会一直停在 0。
|
||||
if (items.length > 0 && items.every((m) => m.is_official) && tab !== "mine") {
|
||||
setTab("mine");
|
||||
onNotify?.("info", "官方模特不能删除,已切换到我的模特");
|
||||
}
|
||||
};
|
||||
const toggleSelect = (id: string) => setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
@@ -415,7 +423,7 @@ export function ModelsPage({ onNotify, onBillingChanged }: {
|
||||
<p>维护可复用的品牌模特与人物参考资产</p>
|
||||
</div>
|
||||
<div className="ml-actions">
|
||||
<button className={`ml-ghost${editMode ? " is-on" : ""}`} type="button" onClick={() => (editMode ? exitEdit() : setEditMode(true))}>
|
||||
<button className={`ml-ghost${editMode ? " is-on" : ""}`} type="button" onClick={() => (editMode ? exitEdit() : enterEdit())}>
|
||||
<Settings2 />
|
||||
<span>{editMode ? "完成" : "管理模特"}</span>
|
||||
</button>
|
||||
@@ -483,6 +491,7 @@ export function ModelsPage({ onNotify, onBillingChanged }: {
|
||||
const onCardClick = () => {
|
||||
if (editMode) {
|
||||
if (selectable) toggleSelect(m.id);
|
||||
else onNotify?.("info", "官方模特不能删除");
|
||||
return;
|
||||
}
|
||||
setDetail(m);
|
||||
|
||||
@@ -941,7 +941,7 @@ export function OmniHistoryPage({
|
||||
<div className="omni-history-actions">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="删除项目"
|
||||
aria-label="删除会话"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setDeleteTarget(item);
|
||||
|
||||
@@ -297,7 +297,7 @@ export function ProductsPage({ products, projects = [], loading = false, navigat
|
||||
</div>
|
||||
|
||||
{loading && products.length === 0 ? (
|
||||
<SkeletonGrid count={8} />
|
||||
<ProductGridSkeleton />
|
||||
) : pageItems.length === 0 ? (
|
||||
<div className="pl-empty" role="status">
|
||||
<PackageX />
|
||||
@@ -381,6 +381,26 @@ export function ProductsPage({ products, projects = [], loading = false, navigat
|
||||
);
|
||||
}
|
||||
|
||||
function ProductGridSkeleton() {
|
||||
return (
|
||||
<div className="pl-grid pl-skeleton-grid" aria-busy="true" aria-label="正在加载商品">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<article className="pl-card pl-skeleton-card" key={index} aria-hidden="true">
|
||||
<div className="pl-skeleton-thumb skeleton-block" />
|
||||
<div className="pl-meta pl-skeleton-meta">
|
||||
<span className="pl-skeleton-line pl-skeleton-line--title skeleton-block" />
|
||||
<span className="pl-skeleton-line pl-skeleton-line--copy skeleton-block" />
|
||||
<div className="pl-skeleton-tags">
|
||||
<span className="pl-skeleton-pill skeleton-block" />
|
||||
<span className="pl-skeleton-pill pl-skeleton-pill--short skeleton-block" />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductCard({ product, coverUrl = "", videoCount = 0, onOpen, onOpenVideos, editMode = false, selected = false, onDelete }: { product: Product; coverUrl?: string; videoCount?: number; onOpen: () => void; onOpenVideos?: () => void; editMode?: boolean; selected?: boolean; onDelete?: () => void }) {
|
||||
const mock = productCover(product.title);
|
||||
const assetCount = product.images?.length || 0;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Bell,
|
||||
KeyRound,
|
||||
LogOut,
|
||||
Monitor,
|
||||
Shield,
|
||||
Upload,
|
||||
User as UserIcon,
|
||||
@@ -12,16 +13,18 @@ import type { LoginSession, Team, User, UserPreference } from "../types";
|
||||
import { TeamModal } from "../components/overlays";
|
||||
import { CustomSelect } from "../components/custom-select";
|
||||
import { useFileDrop } from "../components/use-file-drop";
|
||||
import { applyTheme, normalizeAppearance, readStoredAppearance } from "../theme";
|
||||
|
||||
type SectionKey = "profile" | "security" | "notify" | "pref" | "display";
|
||||
|
||||
// 可导航(UI 可见)的分区。pref(创作默认)/ display(显示)已隐藏入口,故不在此列。
|
||||
const SECTION_KEYS: SectionKey[] = ["profile", "security", "notify"];
|
||||
// 可导航分区。pref(创作默认)仍隐藏;display(显示)开放外观切换。
|
||||
const SECTION_KEYS: SectionKey[] = ["profile", "security", "notify", "display"];
|
||||
|
||||
const NAV: Array<{ key: SectionKey; label: string; icon: ReactNode }> = [
|
||||
{ key: "profile", label: "个人信息", icon: <UserIcon /> },
|
||||
{ key: "security", label: "安全", icon: <Shield /> },
|
||||
{ key: "notify", label: "通知", icon: <Bell /> },
|
||||
{ key: "display", label: "显示", icon: <Monitor /> },
|
||||
];
|
||||
|
||||
function SettingRow({ title, hint, stack, children }: { title: string; hint?: string; stack?: boolean; children: ReactNode }) {
|
||||
@@ -174,7 +177,7 @@ export function SettingsPage({
|
||||
onChangePassword: (payload: { old_password: string; new_password: string }) => void | Promise<unknown>;
|
||||
onUploadAvatar: (formData: FormData) => void | Promise<unknown>;
|
||||
onResetAvatar?: () => void | Promise<unknown>;
|
||||
onNotify?: (text: string) => void;
|
||||
onNotify?: (type: "success" | "error" | "info", text: string) => void;
|
||||
onLogout?: () => void | Promise<void>;
|
||||
}) {
|
||||
const normalizedInitial = (SECTION_KEYS as readonly string[]).includes(initialSection)
|
||||
@@ -199,7 +202,10 @@ export function SettingsPage({
|
||||
transition: cd?.transition ?? DEFAULT_PREFS.transition,
|
||||
twoFactor: !!preferences?.two_factor_enabled,
|
||||
notify: { ...DEFAULT_PREFS.notify, ...(preferences?.notify || {}) },
|
||||
appearance: dp?.appearance ?? DEFAULT_PREFS.appearance,
|
||||
// preferences 尚未加载时用本地已选外观,避免一进设置就被默认 system 盖掉
|
||||
appearance: !preferences
|
||||
? readStoredAppearance()
|
||||
: (dp?.appearance ?? DEFAULT_PREFS.appearance),
|
||||
language: dp?.language ?? DEFAULT_PREFS.language,
|
||||
density: dp?.density ?? DEFAULT_PREFS.density,
|
||||
};
|
||||
@@ -210,18 +216,19 @@ export function SettingsPage({
|
||||
const [draft, setDraft] = useState<TrackedState>(baselineFromProps);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 后端 preferences / user 到达或刷新时,把新值同时灌进 baseline 与 draft(用户未改时跟随后端)
|
||||
// 外观即时生效(含后端 preferences 到达 / 放弃修改回滚)
|
||||
useEffect(() => {
|
||||
setBaseline(baselineFromProps);
|
||||
setDraft(baselineFromProps);
|
||||
}, [baselineFromProps]);
|
||||
applyTheme(normalizeAppearance(draft.appearance));
|
||||
}, [draft.appearance]);
|
||||
|
||||
// 单字段 draft 更新器
|
||||
const patchDraft = useCallback(<K extends keyof TrackedState>(key: K, value: TrackedState[K]) => {
|
||||
setDraft((prev) => ({ ...prev, [key]: value }));
|
||||
}, []);
|
||||
// 已保存基线的 ref,自动保存时避免连点用到过期 baseline
|
||||
const baselineRef = useRef(baseline);
|
||||
baselineRef.current = baseline;
|
||||
const draftRef = useRef(draft);
|
||||
draftRef.current = draft;
|
||||
const saveSeq = useRef(0);
|
||||
|
||||
// ─── 聚合脏字段集 + 涉及分区集(顶栏计数 / nav dirty-dot / beforeunload 全由此驱动) ───
|
||||
// ─── 聚合脏字段集(文本框编辑中仍可能短暂脏;选项类会立即落盘) ───
|
||||
const dirtyFields = useMemo<Array<keyof TrackedState>>(() => {
|
||||
const keys = Object.keys(FIELD_SECTION) as Array<keyof TrackedState>;
|
||||
return keys.filter((key) => {
|
||||
@@ -239,6 +246,97 @@ export function SettingsPage({
|
||||
const dirtyCount = dirtyFields.length;
|
||||
const isDirty = dirtyCount > 0;
|
||||
|
||||
// 后端 preferences / user 到达或刷新时同步;保存中或本地有未落盘编辑时不覆盖 draft
|
||||
useEffect(() => {
|
||||
if (saving || isDirty) return;
|
||||
setBaseline(baselineFromProps);
|
||||
setDraft(baselineFromProps);
|
||||
}, [baselineFromProps, saving, isDirty]);
|
||||
|
||||
// 把 next 相对 baseline 的变更拆成 profile / preferences 提交;成功后升基线
|
||||
const persist = useCallback(async (next: TrackedState) => {
|
||||
const base = baselineRef.current;
|
||||
const profilePayload: { name?: string; email?: string; phone?: string } = {};
|
||||
if (next.name !== base.name) profilePayload.name = next.name.trim();
|
||||
if (next.email !== base.email) profilePayload.email = next.email.trim();
|
||||
if (next.phone !== base.phone) profilePayload.phone = next.phone.trim();
|
||||
|
||||
const prefPayload: Partial<UserPreference> = {};
|
||||
const creationKeys: Array<keyof TrackedState> = ["template", "duration", "subtitle", "bgm", "transition"];
|
||||
if (creationKeys.some((key) => next[key] !== base[key])) {
|
||||
prefPayload.creation_defaults = {
|
||||
template: next.template,
|
||||
duration: next.duration,
|
||||
subtitle: next.subtitle,
|
||||
bgm: next.bgm,
|
||||
transition: next.transition,
|
||||
};
|
||||
}
|
||||
const displayKeys: Array<keyof TrackedState> = ["appearance", "language", "density"];
|
||||
if (displayKeys.some((key) => next[key] !== base[key])) {
|
||||
prefPayload.display = { appearance: next.appearance, language: next.language, density: next.density };
|
||||
}
|
||||
if (!notifyEqual(next.notify, base.notify)) prefPayload.notify = { ...next.notify };
|
||||
if (next.twoFactor !== base.twoFactor) prefPayload.two_factor_enabled = next.twoFactor;
|
||||
|
||||
const hasProfile = Object.keys(profilePayload).length > 0;
|
||||
const hasPref = Object.keys(prefPayload).length > 0;
|
||||
if (!hasProfile && !hasPref) {
|
||||
setBaseline(next);
|
||||
return;
|
||||
}
|
||||
|
||||
const seq = ++saveSeq.current;
|
||||
setSaving(true);
|
||||
try {
|
||||
const tasks: Array<Promise<unknown>> = [];
|
||||
if (hasProfile) tasks.push(Promise.resolve(onSaveProfile(profilePayload)));
|
||||
if (hasPref && onSavePreferences) {
|
||||
tasks.push(
|
||||
Promise.resolve(onSavePreferences(prefPayload)).then((result) => {
|
||||
if (result == null) throw new Error("偏好设置保存失败,请重试");
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
if (seq !== saveSeq.current) return;
|
||||
baselineRef.current = next;
|
||||
setBaseline(next);
|
||||
} catch (error) {
|
||||
if (seq !== saveSeq.current) return;
|
||||
// 失败不推进 baseline,回滚 draft 并恢复主题
|
||||
const restored = baselineRef.current;
|
||||
draftRef.current = restored;
|
||||
setDraft(restored);
|
||||
applyTheme(normalizeAppearance(restored.appearance));
|
||||
onNotify?.(
|
||||
"error",
|
||||
error instanceof Error ? error.message : "设置保存失败,请重试",
|
||||
);
|
||||
} finally {
|
||||
if (seq === saveSeq.current) setSaving(false);
|
||||
}
|
||||
}, [onSaveProfile, onSavePreferences, onNotify]);
|
||||
|
||||
// 选项类:改完立即存
|
||||
const commitField = useCallback(<K extends keyof TrackedState>(key: K, value: TrackedState[K]) => {
|
||||
const next = { ...draftRef.current, [key]: value };
|
||||
draftRef.current = next;
|
||||
setDraft(next);
|
||||
if (key === "appearance") applyTheme(normalizeAppearance(value as string));
|
||||
void persist(next);
|
||||
}, [persist]);
|
||||
|
||||
// 文本框:只改 draft,失焦再存
|
||||
const patchDraft = useCallback(<K extends keyof TrackedState>(key: K, value: TrackedState[K]) => {
|
||||
setDraft((prev) => ({ ...prev, [key]: value }));
|
||||
}, []);
|
||||
|
||||
const saveDraftNow = useCallback(() => {
|
||||
void persist(draftRef.current);
|
||||
}, [persist]);
|
||||
|
||||
// 个人信息 · 受控输入(draft 派生)
|
||||
const name = draft.name;
|
||||
const email = draft.email;
|
||||
@@ -271,55 +369,6 @@ export function SettingsPage({
|
||||
[notifyOnCount, sessions.length],
|
||||
);
|
||||
|
||||
// ─── 取消:draft 回退到 baseline(放弃所有未保存改动) ───
|
||||
function discardChanges() {
|
||||
if (!isDirty) return;
|
||||
setDraft(baseline);
|
||||
onNotify?.("已放弃未保存的改动");
|
||||
}
|
||||
|
||||
// ─── 统一保存:把所有脏字段拆成 profile / preferences 两个 payload 提交,成功后把 draft 升为新基线 ───
|
||||
async function handleSaveAll() {
|
||||
if (!isDirty || saving) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const profilePayload: { name?: string; email?: string; phone?: string } = {};
|
||||
if (draft.name !== baseline.name) profilePayload.name = draft.name.trim();
|
||||
if (draft.email !== baseline.email) profilePayload.email = draft.email.trim();
|
||||
if (draft.phone !== baseline.phone) profilePayload.phone = draft.phone.trim();
|
||||
|
||||
const prefPayload: Partial<UserPreference> = {};
|
||||
const creationKeys: Array<keyof TrackedState> = ["template", "duration", "subtitle", "bgm", "transition"];
|
||||
if (creationKeys.some((key) => draft[key] !== baseline[key])) {
|
||||
prefPayload.creation_defaults = {
|
||||
template: draft.template,
|
||||
duration: draft.duration,
|
||||
subtitle: draft.subtitle,
|
||||
bgm: draft.bgm,
|
||||
transition: draft.transition,
|
||||
};
|
||||
}
|
||||
const displayKeys: Array<keyof TrackedState> = ["appearance", "language", "density"];
|
||||
if (displayKeys.some((key) => draft[key] !== baseline[key])) {
|
||||
prefPayload.display = { appearance: draft.appearance, language: draft.language, density: draft.density };
|
||||
}
|
||||
if (!notifyEqual(draft.notify, baseline.notify)) prefPayload.notify = { ...draft.notify };
|
||||
if (draft.twoFactor !== baseline.twoFactor) prefPayload.two_factor_enabled = draft.twoFactor;
|
||||
|
||||
const tasks: Array<Promise<unknown>> = [];
|
||||
if (Object.keys(profilePayload).length > 0) tasks.push(Promise.resolve(onSaveProfile(profilePayload)));
|
||||
if (Object.keys(prefPayload).length > 0 && onSavePreferences) tasks.push(Promise.resolve(onSavePreferences(prefPayload)));
|
||||
await Promise.all(tasks);
|
||||
|
||||
// 提交成功 → draft 升为新基线,脏集清空
|
||||
const savedCount = dirtyCount;
|
||||
setBaseline(draft);
|
||||
onNotify?.(`${savedCount} 项已保存`);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openPasswordModal() {
|
||||
setOldPassword("");
|
||||
setNewPassword("");
|
||||
@@ -381,32 +430,13 @@ export function SettingsPage({
|
||||
return () => URL.revokeObjectURL(avatarPreview);
|
||||
}, [avatarPreview]);
|
||||
|
||||
// ─── 离开页面前提醒:有未保存改动时弹浏览器原生确认 ───
|
||||
useEffect(() => {
|
||||
if (!isDirty) return;
|
||||
const handler = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault();
|
||||
event.returnValue = "";
|
||||
};
|
||||
window.addEventListener("beforeunload", handler);
|
||||
return () => window.removeEventListener("beforeunload", handler);
|
||||
}, [isDirty]);
|
||||
|
||||
return (
|
||||
<section className="settings-page">
|
||||
<div className="settings-inner">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1>设置</h1>
|
||||
<div className="sub">个人信息、偏好、通知与安全</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button className="btn" type="button" onClick={discardChanges} disabled={!isDirty || saving}>取消</button>
|
||||
<button className="btn btn-primary" type="button" onClick={handleSaveAll} disabled={!isDirty || saving}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12l5 5L20 6" /></svg>
|
||||
保存所有更改
|
||||
{isDirty ? <span className="save-count">· {dirtyCount} 项</span> : null}
|
||||
</button>
|
||||
<div className="sub">个人信息、偏好、通知与安全 · 修改后自动保存</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -449,7 +479,7 @@ export function SettingsPage({
|
||||
</div>
|
||||
</SettingRow>
|
||||
<SettingRow title="显示名称" hint="用于团队和项目协作中展示">
|
||||
<input className="st-input" value={name} onChange={(event) => patchDraft("name", event.target.value)} />
|
||||
<input className="st-input" value={name} onChange={(event) => patchDraft("name", event.target.value)} onBlur={saveDraftNow} />
|
||||
</SettingRow>
|
||||
<SettingRow title="登录邮箱" hint="仅做记录用,不用于登录">
|
||||
<input
|
||||
@@ -458,13 +488,15 @@ export function SettingsPage({
|
||||
value={email}
|
||||
placeholder="邮箱验证未启用"
|
||||
onChange={(event) => patchDraft("email", event.target.value)}
|
||||
onBlur={saveDraftNow}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow title="手机号" hint="保存所有更改时一并提交">
|
||||
<SettingRow title="手机号" hint="修改后自动保存">
|
||||
<input
|
||||
className="st-input"
|
||||
value={phone}
|
||||
onChange={(event) => patchDraft("phone", event.target.value)}
|
||||
onBlur={saveDraftNow}
|
||||
placeholder="138****8000"
|
||||
/>
|
||||
</SettingRow>
|
||||
@@ -499,7 +531,7 @@ export function SettingsPage({
|
||||
</div>
|
||||
</SettingRow>
|
||||
<SettingRow title="两步验证" hint="推荐开启短信 + Authenticator">
|
||||
<Switch checked={draft.twoFactor} onChange={(v) => patchDraft("twoFactor", v)} />
|
||||
<Switch checked={draft.twoFactor} onChange={(v) => commitField("twoFactor", v)} />
|
||||
</SettingRow>
|
||||
<SettingRow stack title="在用设备" hint="真实登录会话 · 每次登录记录设备 UA / IP">
|
||||
<div className="device-list">
|
||||
@@ -541,7 +573,7 @@ export function SettingsPage({
|
||||
<section className="pane" aria-label="通知">
|
||||
{NOTIFY_ROWS.map((row) => (
|
||||
<SettingRow key={row.key} title={row.title} hint={row.sub?.replace(/^\/\/\s*/, "")}>
|
||||
<Switch checked={!!draft.notify[row.key]} onChange={(next) => patchDraft("notify", { ...draft.notify, [row.key]: next })} />
|
||||
<Switch checked={!!draft.notify[row.key]} onChange={(next) => commitField("notify", { ...draft.notify, [row.key]: next })} />
|
||||
</SettingRow>
|
||||
))}
|
||||
<div className="pane-desc" style={{ margin: "18px 0 4px" }}>消息中心分类</div>
|
||||
@@ -549,7 +581,7 @@ export function SettingsPage({
|
||||
<SettingRow key={row.key} title={row.title} hint={row.hint}>
|
||||
<Switch
|
||||
checked={!draft.notify[`mute-${row.key}`]}
|
||||
onChange={(show) => patchDraft("notify", { ...draft.notify, [`mute-${row.key}`]: !show })}
|
||||
onChange={(show) => commitField("notify", { ...draft.notify, [`mute-${row.key}`]: !show })}
|
||||
/>
|
||||
</SettingRow>
|
||||
))}
|
||||
@@ -571,7 +603,7 @@ export function SettingsPage({
|
||||
className={`pref-choice ${draft.template === choice.v ? "selected" : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => patchDraft("template", choice.v)}
|
||||
onClick={() => commitField("template", choice.v)}
|
||||
>
|
||||
<div className="t">{choice.t}</div>
|
||||
<div className="d">{choice.d}</div>
|
||||
@@ -590,7 +622,7 @@ export function SettingsPage({
|
||||
className={`dur-chip ${draft.duration === d ? "selected" : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => patchDraft("duration", d)}
|
||||
onClick={() => commitField("duration", d)}
|
||||
>
|
||||
{d}s
|
||||
</span>
|
||||
@@ -609,7 +641,7 @@ export function SettingsPage({
|
||||
className={`pref-choice ${draft.subtitle === choice.v ? "selected" : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => patchDraft("subtitle", choice.v)}
|
||||
onClick={() => commitField("subtitle", choice.v)}
|
||||
>
|
||||
<div className="t">{choice.t}</div>
|
||||
<div className="d">{choice.d}</div>
|
||||
@@ -621,7 +653,7 @@ export function SettingsPage({
|
||||
<div className="form-row">
|
||||
<div className="lbl">默认 BGM 库</div>
|
||||
<div className="val">
|
||||
<CustomSelect fill value={draft.bgm} onChange={(next) => patchDraft("bgm", next)} options={[
|
||||
<CustomSelect fill value={draft.bgm} onChange={(next) => commitField("bgm", next)} options={[
|
||||
{ value: "kapian", label: "抖音 Top10 卡点曲库" },
|
||||
{ value: "emotion", label: "情绪向 · 治愈/悬念" },
|
||||
{ value: "urban", label: "都市电子 · 通勤场景" },
|
||||
@@ -632,7 +664,7 @@ export function SettingsPage({
|
||||
<div className="form-row">
|
||||
<div className="lbl">默认转场</div>
|
||||
<div className="val">
|
||||
<CustomSelect fill value={draft.transition} onChange={(next) => patchDraft("transition", next)} options={[
|
||||
<CustomSelect fill value={draft.transition} onChange={(next) => commitField("transition", next)} options={[
|
||||
{ value: "none", label: "无转场" },
|
||||
{ value: "fade", label: "淡入淡出 · 0.3s" },
|
||||
{ value: "slide", label: "滑动 · 0.3s" },
|
||||
@@ -651,6 +683,8 @@ export function SettingsPage({
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{section === "display" && (
|
||||
<section className="pane" aria-label="显示">
|
||||
<h3>显示</h3>
|
||||
@@ -659,17 +693,17 @@ export function SettingsPage({
|
||||
<div className="form-row">
|
||||
<div className="lbl">外观</div>
|
||||
<div className="val">
|
||||
<CustomSelect fill value={draft.appearance} onChange={(next) => patchDraft("appearance", next)} options={[
|
||||
<CustomSelect fill value={draft.appearance} onChange={(next) => commitField("appearance", next)} options={[
|
||||
{ value: "system", label: "跟随系统" },
|
||||
{ value: "light", label: "浅色" },
|
||||
{ value: "dark", label: "深色(V2)", disabled: true },
|
||||
{ value: "dark", label: "深色" },
|
||||
]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<div className="lbl">语言</div>
|
||||
<div className="val">
|
||||
<CustomSelect fill value={draft.language} onChange={(next) => patchDraft("language", next)} options={[
|
||||
<CustomSelect fill value={draft.language} onChange={(next) => commitField("language", next)} options={[
|
||||
{ value: "zh", label: "简体中文" },
|
||||
{ value: "en", label: "English(V2)", disabled: true },
|
||||
]} />
|
||||
@@ -678,7 +712,7 @@ export function SettingsPage({
|
||||
<div className="form-row">
|
||||
<div className="lbl">表格密度</div>
|
||||
<div className="val">
|
||||
<CustomSelect fill value={draft.density} onChange={(next) => patchDraft("density", next)} options={[
|
||||
<CustomSelect fill value={draft.density} onChange={(next) => commitField("density", next)} options={[
|
||||
{ value: "compact", label: "紧凑" },
|
||||
{ value: "standard", label: "标准" },
|
||||
{ value: "loose", label: "宽松" },
|
||||
@@ -813,7 +847,7 @@ export function SettingsPage({
|
||||
<div className="li">项目、资产、团队成员与余额数据都会保留</div>
|
||||
<div className="li">仅影响当前浏览器会话,不会下线其他设备</div>
|
||||
</div>
|
||||
{isDirty ? <div className="logout-unsaved-note">当前有 {dirtyCount} 项未保存的设置变更,退出后这些变更不会保存。</div> : null}
|
||||
{isDirty ? <div className="logout-unsaved-note">还有 {dirtyCount} 项未落盘的修改(通常是正在编辑的文本),退出后不会保存。</div> : null}
|
||||
</TeamModal>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 210px minmax(0, 1fr);
|
||||
gap: 22px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
@@ -42,9 +42,9 @@
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
align-content: start;
|
||||
padding: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--st-line);
|
||||
border-radius: 13px;
|
||||
border-radius: 20px;
|
||||
background: var(--st-wash);
|
||||
}
|
||||
.settings-nav .nav-h {
|
||||
@@ -104,7 +104,7 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.st-tab.has-changes .nav-dot { display: block; }
|
||||
.st-tab.active .nav-dot { background: #fff; }
|
||||
.st-tab.active .nav-dot { background: var(--surface); }
|
||||
.st-tab:not(:has(.nav-badge)) .nav-dot { margin-left: auto; }
|
||||
|
||||
.logout-pill {
|
||||
@@ -132,24 +132,34 @@
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
padding: 8px 22px;
|
||||
padding: 22px 28px 24px;
|
||||
border: 1px solid var(--st-line);
|
||||
border-radius: 13px;
|
||||
background: rgba(34, 42, 54, 0.04);
|
||||
border-radius: 20px;
|
||||
background: rgba(34, 42, 54, 0.035);
|
||||
box-shadow: var(--st-shadow);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pane { background: transparent; border: 0; border-radius: 0; padding: 0; margin: 0; }
|
||||
/* 内容区本身是卡,pane 不再叠第二层底/阴影 */
|
||||
.pane {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.pane h3 { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
|
||||
.pane .pane-desc { font-size: 12px; color: var(--st-muted); margin-bottom: 18px; }
|
||||
|
||||
.st-row {
|
||||
min-height: 82px;
|
||||
min-height: 76px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid rgba(34, 42, 54, 0.075);
|
||||
}
|
||||
.st-row:last-child { border-bottom: 0; }
|
||||
@@ -196,7 +206,7 @@
|
||||
border: 1px solid rgba(34, 42, 54, 0.11);
|
||||
border-radius: 10px;
|
||||
outline: none;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--st-text);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
@@ -283,7 +293,7 @@
|
||||
.switch { position: relative; width: 46px; height: 26px; flex: 0 0 46px; display: inline-block; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.switch .slider { position: absolute; inset: 0; background: #cfd3da; border-radius: 999px; cursor: pointer; transition: background 170ms ease; }
|
||||
.switch .slider::before { content: ''; position: absolute; left: 4px; top: 4px; width: 18px; height: 18px; background: #fff; border-radius: 50%; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18); transition: transform 170ms ease; }
|
||||
.switch .slider::before { content: ''; position: absolute; left: 4px; top: 4px; width: 18px; height: 18px; background: var(--surface); border-radius: 50%; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18); transition: transform 170ms ease; }
|
||||
.switch input:checked + .slider { background: var(--klein); }
|
||||
.switch input:checked + .slider::before { transform: translateX(20px); }
|
||||
.switch input:disabled + .slider { cursor: not-allowed; opacity: .55; }
|
||||
|
||||
@@ -68,6 +68,31 @@
|
||||
--red-bd: #F2D6CE;
|
||||
}
|
||||
|
||||
|
||||
html[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--bg: #0f1115;
|
||||
--bg-soft: #14171c;
|
||||
--card: #181b21;
|
||||
--border: #2a2f38;
|
||||
--border-soft: #323843;
|
||||
--ink: #e8eaed;
|
||||
--ink-2: rgba(232, 234, 237, .72);
|
||||
--ink-3: rgba(232, 234, 237, .52);
|
||||
--ink-4: rgba(232, 234, 237, .32);
|
||||
--orange: #3d6bff;
|
||||
--orange-soft: rgba(61, 107, 255, .28);
|
||||
--orange-tint: rgba(61, 107, 255, .10);
|
||||
--orange-hover: #5a82ff;
|
||||
--green: #6dd88a;
|
||||
--green-bg: rgba(66, 195, 102, .14);
|
||||
--green-bd: rgba(66, 195, 102, .32);
|
||||
--red: #ff6b5c;
|
||||
--red-bg: rgba(235, 52, 36, .14);
|
||||
--red-bd: rgba(235, 52, 36, .32);
|
||||
}
|
||||
|
||||
|
||||
html, body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
@@ -177,7 +202,7 @@ nav a.disabled:hover { background: transparent; color: var(--ink-4); }
|
||||
.user .em { font-size: 13px; }
|
||||
|
||||
/* ─── Main + grid background ─── */
|
||||
main { position: relative; background: #fff; }
|
||||
main { position: relative; background: var(--surface); }
|
||||
.grid-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -1529,7 +1554,7 @@ table.t tbody tr:hover { background: var(--bg-soft); }
|
||||
.topup-modal .topup-amt { text-align: center; font-size: 34px; font-weight: 600; color: var(--ink); margin: 4px 0; }
|
||||
.topup-note { text-align: center; color: var(--orange); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
.topup-qr { width: 180px; height: 180px; margin: 18px auto; background: repeating-linear-gradient(45deg, var(--ink) 0 4px, #fff 4px 8px); border: 8px solid #fff; display: grid; place-items: center; }
|
||||
.topup-qr .center { background: #fff; padding: 12px; text-align: center; font-weight: 600; }
|
||||
.topup-qr .center { background: var(--surface); padding: 12px; text-align: center; font-weight: 600; }
|
||||
.topup-qr .center span { color: var(--ink-3); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
|
||||
/* 团队页旧全局块已移除 · 详见 src/team-page.css 的 .team-page 作用域。保留通用 .av(app-shell/account/dashboard 在用)。 */
|
||||
@@ -1597,7 +1622,7 @@ table.t tbody tr:hover { background: var(--bg-soft); }
|
||||
.switch { position: relative; display: inline-flex; width: 42px; height: 24px; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; inset: 0; border-radius: 999px; background: var(--border); transition: .15s; }
|
||||
.slider::before { content: ''; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; border-radius: 50%; background: #fff; transition: .15s; }
|
||||
.slider::before { content: ''; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; border-radius: 50%; background: var(--surface); transition: .15s; }
|
||||
.switch input:checked + .slider { background: var(--orange); }
|
||||
.switch input:checked + .slider::before { transform: translateX(18px); }
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
padding: 0 13px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.10);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
color: var(--st-muted);
|
||||
}
|
||||
.tc-search input {
|
||||
@@ -239,7 +239,7 @@
|
||||
line-height: 0;
|
||||
border: 1px solid var(--st-line);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
color: var(--st-muted);
|
||||
padding: 0;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/** 外观主题:浅色 / 深色 / 跟随系统。写到 html[data-theme] + localStorage,避免首屏闪白。 */
|
||||
|
||||
export type Appearance = "light" | "dark" | "system";
|
||||
|
||||
export const THEME_STORAGE_KEY = "yingqing-appearance";
|
||||
|
||||
export function normalizeAppearance(value: unknown): Appearance {
|
||||
if (value === "light" || value === "dark" || value === "system") return value;
|
||||
return "system";
|
||||
}
|
||||
|
||||
export function resolveTheme(appearance: Appearance): "light" | "dark" {
|
||||
if (appearance === "light" || appearance === "dark") return appearance;
|
||||
if (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark";
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
|
||||
export function applyTheme(appearance: Appearance | string | null | undefined) {
|
||||
if (typeof document === "undefined") return;
|
||||
const normalized = normalizeAppearance(appearance);
|
||||
const theme = resolveTheme(normalized);
|
||||
const root = document.documentElement;
|
||||
root.setAttribute("data-theme", theme);
|
||||
root.style.colorScheme = theme;
|
||||
try {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, normalized);
|
||||
} catch {
|
||||
/* ignore quota / private mode */
|
||||
}
|
||||
}
|
||||
|
||||
export function readStoredAppearance(): Appearance {
|
||||
try {
|
||||
return normalizeAppearance(localStorage.getItem(THEME_STORAGE_KEY));
|
||||
} catch {
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
@@ -810,7 +810,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.16);
|
||||
border-radius: 50%;
|
||||
color: #78849a;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
transition: color 180ms ease, border-color 180ms ease, background-color 180ms ease, box-shadow 180ms ease;
|
||||
@@ -903,7 +903,7 @@
|
||||
box-sizing: content-box;
|
||||
border: 1px solid rgba(0, 47, 167, 0.12);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 20px rgba(0, 47, 167, 0.08);
|
||||
}
|
||||
|
||||
@@ -1015,7 +1015,7 @@
|
||||
|
||||
.vr-page .remix-page .remix-prompt-panel .analysis-prompt {
|
||||
border-left: 3px solid var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: inset 0 1px 3px rgba(27, 45, 83, 0.03), 0 8px 22px rgba(22, 45, 92, 0.045);
|
||||
}
|
||||
|
||||
@@ -1245,7 +1245,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.24);
|
||||
border-radius: 9px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
@@ -1312,7 +1312,7 @@
|
||||
border-radius: 9px;
|
||||
outline: none;
|
||||
color: #27334a;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
@@ -1327,7 +1327,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.2);
|
||||
border-radius: 8px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
|
||||
.vrep-page .replace-mode-button.active {
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 7px 18px rgba(22, 45, 92, 0.09), inset 0 0 0 1px rgba(0, 47, 167, 0.11);
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.12);
|
||||
border-radius: 13px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 18px rgba(0, 47, 167, 0.07);
|
||||
}
|
||||
|
||||
@@ -623,7 +623,7 @@
|
||||
padding: 6px;
|
||||
border: 1px solid rgba(34, 42, 54, 0.09);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.vrep-page .replace-temporary-cell {
|
||||
@@ -686,7 +686,7 @@
|
||||
place-items: center;
|
||||
border-radius: 11px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.vrep-page .replace-temporary-more svg {
|
||||
@@ -1130,7 +1130,7 @@
|
||||
border: 1px solid rgba(0, 47, 167, 0.18);
|
||||
border-radius: 8px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
@@ -1232,7 +1232,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: -22px 0 48px rgba(7, 12, 22, 0.2);
|
||||
transform: translateX(100%);
|
||||
transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
@@ -1363,7 +1363,7 @@
|
||||
gap: 10px;
|
||||
padding: 0 24px;
|
||||
border-top: 1px solid rgba(34, 42, 54, 0.09);
|
||||
background: #fff;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 -10px 24px rgba(20, 27, 38, 0.045);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user