refactor(theme): 重构主题配置和管理后台样式
- 将 Ant Design 主题配置提取到独立的 getAntThemeConfig 函数 - 移除 AGENTS.md 中关于状态管理和样式的冗余说明 - 删除 globals.css 中的 admin 相关样式和颜色变量 - 更新管理后台布局使用新的主题配置和样式常量 - 调整代码编辑器样式使用 Ant Design 的 CSS 变量 - 优化管理后台整体视觉风格为中性色调
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { FileTextOutlined, HomeOutlined, LogoutOutlined, PictureOutlined, SettingOutlined } from "@ant-design/icons";
|
||||
import { Button, Flex, Layout, Menu, Typography } from "antd";
|
||||
import { Button, Flex, Layout, Menu, Typography, theme } from "antd";
|
||||
import { LogOut } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
@@ -9,10 +9,12 @@ import type { ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { UserStatusActions } from "@/components/user-status-actions";
|
||||
import { adminLayoutStyle } from "@/lib/app-theme";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
|
||||
export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||
const { token: antToken } = theme.useToken();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const token = useUserStore((state) => state.token);
|
||||
@@ -38,50 +40,50 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
if (!isReady || !token || user?.role !== "admin") {
|
||||
return (
|
||||
<div style={{ display: "flex", minHeight: "100vh", alignItems: "center", justifyContent: "center", background: "var(--background)" }}>
|
||||
<div style={{ display: "flex", minHeight: "100vh", alignItems: "center", justifyContent: "center", background: antToken.colorBgLayout }}>
|
||||
<span />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout className="admin-layout" hasSider style={{ height: "100vh", overflow: "hidden" }}>
|
||||
<Layout.Sider width={232} style={{ height: "100vh", overflow: "hidden" }}>
|
||||
<Flex className="admin-brand" align="center" gap={12} style={{ height: 56, padding: "0 20px" }}>
|
||||
<span className="admin-logo" aria-hidden />
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>无限画布</Typography.Text>
|
||||
<Layout hasSider style={{ height: "100vh", overflow: "hidden", background: antToken.colorBgLayout }}>
|
||||
<Layout.Sider width={adminLayoutStyle.siderWidth} style={{ height: "100vh", overflow: "hidden", background: antToken.colorBgContainer, borderRight: `1px solid ${antToken.colorBorder}` }}>
|
||||
<Flex align="center" gap={12} style={{ height: adminLayoutStyle.brandHeight, padding: "0 20px", borderBottom: `1px solid ${antToken.colorBorderSecondary}` }}>
|
||||
<span aria-hidden style={{ display: "inline-block", width: 30, height: 30, background: antToken.colorText, WebkitMask: "url(/logo.svg) center / contain no-repeat", mask: "url(/logo.svg) center / contain no-repeat" }} />
|
||||
<Typography.Text strong style={{ fontSize: 18, letterSpacing: 0 }}>无限画布</Typography.Text>
|
||||
</Flex>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[activeKey]}
|
||||
style={adminLayoutStyle.menu}
|
||||
items={[
|
||||
{ key: "/admin/prompts", icon: <FileTextOutlined />, label: <Link href="/admin/prompts" style={{ color: "inherit" }}>提示词管理</Link>, style: adminLayoutStyle.menuItem },
|
||||
{ key: "/admin/assets", icon: <PictureOutlined />, label: <Link href="/admin/assets" style={{ color: "inherit" }}>素材库</Link>, style: adminLayoutStyle.menuItem },
|
||||
{ key: "/admin/settings", icon: <SettingOutlined />, label: <Link href="/admin/settings" style={{ color: "inherit" }}>系统设置</Link>, style: adminLayoutStyle.menuItem },
|
||||
]}
|
||||
/>
|
||||
<Flex vertical gap={8} style={{ position: "absolute", bottom: 0, insetInline: 0, padding: 12, borderTop: `1px solid ${antToken.colorBorder}`, background: antToken.colorBgContainer }}>
|
||||
<Button block icon={<HomeOutlined />} href="/canvas" target="_blank" rel="noreferrer">前往画布</Button>
|
||||
<Button block icon={<LogoutOutlined />} onClick={logout}>退出登录</Button>
|
||||
</Flex>
|
||||
</Layout.Sider>
|
||||
<Layout style={{ background: antToken.colorBgLayout }}>
|
||||
<Layout.Header style={{ display: "flex", alignItems: "center", justifyContent: "space-between", height: adminLayoutStyle.headerHeight, padding: "0 24px", background: antToken.colorBgContainer, borderBottom: `1px solid ${antToken.colorBorder}` }}>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>{pageTitle}</Typography.Title>
|
||||
<Flex align="center" gap={4}>
|
||||
<UserStatusActions
|
||||
version={appVersion}
|
||||
theme={colorTheme}
|
||||
onThemeChange={setTheme}
|
||||
showConfig={false}
|
||||
userName={user.username}
|
||||
menuItems={[{ key: "logout", icon: <LogOut className="size-4" />, label: "退出登录", onClick: logout }]}
|
||||
/>
|
||||
</Flex>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[activeKey]}
|
||||
style={{ borderInlineEnd: 0, padding: "12px 8px" }}
|
||||
items={[
|
||||
{ key: "/admin/prompts", icon: <FileTextOutlined />, label: <Link href="/admin/prompts" style={{ color: "inherit" }}>提示词管理</Link> },
|
||||
{ key: "/admin/assets", icon: <PictureOutlined />, label: <Link href="/admin/assets" style={{ color: "inherit" }}>素材库</Link> },
|
||||
{ key: "/admin/settings", icon: <SettingOutlined />, label: <Link href="/admin/settings" style={{ color: "inherit" }}>系统设置</Link> },
|
||||
]}
|
||||
/>
|
||||
<Flex vertical gap={8} style={{ position: "absolute", bottom: 0, insetInline: 0, padding: 12 }}>
|
||||
<Button block icon={<HomeOutlined />} href="/canvas" target="_blank" rel="noreferrer">前往画布</Button>
|
||||
<Button block icon={<LogoutOutlined />} onClick={logout}>退出登录</Button>
|
||||
</Flex>
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<Layout.Header style={{ display: "flex", alignItems: "center", justifyContent: "space-between", height: 56, padding: "0 24px" }}>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>{pageTitle}</Typography.Title>
|
||||
<Flex align="center" gap={4}>
|
||||
<UserStatusActions
|
||||
version={appVersion}
|
||||
theme={colorTheme}
|
||||
onThemeChange={setTheme}
|
||||
showConfig={false}
|
||||
userName={user.username}
|
||||
menuItems={[{ key: "logout", icon: <LogOut className="size-4" />, label: "退出登录", onClick: logout }]}
|
||||
/>
|
||||
</Flex>
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ minHeight: 0, overflow: "auto" }}>{children}</Layout.Content>
|
||||
</Layout>
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ minHeight: 0, overflow: "auto" }}>{children}</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { CheckCircleOutlined, DeleteOutlined, FormatPainterOutlined, PlusOutlined, ReloadOutlined, SaveOutlined } from "@ant-design/icons";
|
||||
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { tags } from "@lezer/highlight";
|
||||
import { App, Button, Card, Col, Drawer, Flex, Form, Input, InputNumber, Modal, Row, Segmented, Select, Space, Switch, Table, Tabs, Tag, Typography } from "antd";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -14,27 +12,17 @@ import { useUserStore } from "@/stores/use-user-store";
|
||||
|
||||
const CodeMirror = dynamic(() => import("@uiw/react-codemirror"), { ssr: false });
|
||||
const jsonEditorTheme = EditorView.theme({
|
||||
"&": { backgroundColor: "var(--card)", color: "var(--foreground)" },
|
||||
".cm-content": { caretColor: "var(--foreground)", padding: "12px 0" },
|
||||
"&": { backgroundColor: "var(--ant-color-bg-container)", color: "var(--ant-color-text)" },
|
||||
".cm-content": { caretColor: "var(--ant-color-text)", padding: "12px 0" },
|
||||
".cm-line": { padding: "0 18px" },
|
||||
".cm-gutters": { backgroundColor: "color-mix(in srgb, var(--card) 94%, var(--foreground))", borderRight: "1px solid var(--border)", color: "var(--muted-foreground)" },
|
||||
".cm-activeLine": { backgroundColor: "color-mix(in srgb, var(--primary) 9%, transparent)" },
|
||||
".cm-activeLineGutter": { backgroundColor: "color-mix(in srgb, var(--primary) 10%, transparent)", color: "var(--foreground)" },
|
||||
".cm-cursor": { borderLeftColor: "var(--foreground)" },
|
||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": { backgroundColor: "color-mix(in srgb, var(--primary) 25%, transparent)" },
|
||||
".cm-foldPlaceholder": { backgroundColor: "var(--muted)", border: "1px solid var(--border)", color: "var(--muted-foreground)" },
|
||||
".cm-gutters": { backgroundColor: "var(--ant-color-fill-quaternary)", borderRight: "1px solid var(--ant-color-border)", color: "var(--ant-color-text-tertiary)" },
|
||||
".cm-activeLine": { backgroundColor: "var(--ant-color-fill-quaternary)" },
|
||||
".cm-activeLineGutter": { backgroundColor: "var(--ant-color-fill-quaternary)", color: "var(--ant-color-text)" },
|
||||
".cm-cursor": { borderLeftColor: "var(--ant-color-text)" },
|
||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": { backgroundColor: "var(--ant-control-item-bg-active)" },
|
||||
".cm-foldPlaceholder": { backgroundColor: "var(--ant-color-fill-quaternary)", border: "1px solid var(--ant-color-border)", color: "var(--ant-color-text-tertiary)" },
|
||||
"&.cm-focused": { outline: "none" },
|
||||
}, { dark: true });
|
||||
const jsonHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.propertyName, color: "#f2a7a7" },
|
||||
{ tag: tags.string, color: "#d6c6a8" },
|
||||
{ tag: tags.number, color: "#ddb27d" },
|
||||
{ tag: tags.bool, color: "#c5b3ff" },
|
||||
{ tag: tags.null, color: "#c5b3ff" },
|
||||
{ tag: tags.punctuation, color: "rgba(250, 250, 249, 0.62)" },
|
||||
{ tag: tags.squareBracket, color: "rgba(250, 250, 249, 0.62)" },
|
||||
{ tag: tags.brace, color: "rgba(250, 250, 249, 0.62)" },
|
||||
]);
|
||||
});
|
||||
|
||||
const emptySettings: AdminSettings = {
|
||||
public: {
|
||||
@@ -291,11 +279,10 @@ export default function AdminSettingsPage() {
|
||||
<CodeMirror
|
||||
value={activeJsonText}
|
||||
height="520px"
|
||||
extensions={[json(), jsonEditorTheme, syntaxHighlighting(jsonHighlightStyle)]}
|
||||
extensions={[json(), jsonEditorTheme]}
|
||||
basicSetup={{ foldGutter: true, lineNumbers: true, highlightActiveLine: true, highlightActiveLineGutter: true }}
|
||||
theme="none"
|
||||
onChange={(value) => setJsonText((current) => ({ ...current, public: value }))}
|
||||
className="admin-json-editor"
|
||||
style={{ fontSize: 13 }}
|
||||
/>
|
||||
</div>
|
||||
@@ -341,11 +328,10 @@ export default function AdminSettingsPage() {
|
||||
<CodeMirror
|
||||
value={activeJsonText}
|
||||
height="520px"
|
||||
extensions={[json(), jsonEditorTheme, syntaxHighlighting(jsonHighlightStyle)]}
|
||||
extensions={[json(), jsonEditorTheme]}
|
||||
basicSetup={{ foldGutter: true, lineNumbers: true, highlightActiveLine: true, highlightActiveLineGutter: true }}
|
||||
theme="none"
|
||||
onChange={(value) => setJsonText((current) => ({ ...current, private: value }))}
|
||||
className="admin-json-editor"
|
||||
style={{ fontSize: 13 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--admin-card-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -115,7 +114,6 @@
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--admin-card-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -370,72 +368,6 @@
|
||||
top: 21px;
|
||||
}
|
||||
|
||||
.admin-layout .ant-layout-sider {
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.admin-layout .ant-layout-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.admin-layout .ant-layout-sider-children > .ant-flex:first-child {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.admin-logo {
|
||||
background: currentColor;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
mask: url(/logo.svg) center / contain no-repeat;
|
||||
width: 32px;
|
||||
-webkit-mask: url(/logo.svg) center / contain no-repeat;
|
||||
}
|
||||
|
||||
.admin-layout .ant-layout-sider-children > .ant-flex:last-child {
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
.admin-layout .ant-card,
|
||||
.admin-layout .ant-pro-card {
|
||||
box-shadow: var(--admin-card-shadow);
|
||||
}
|
||||
|
||||
.admin-layout .ant-table-cell .ant-btn-icon-only.ant-btn-sm {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-editor {
|
||||
background: var(--card);
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-scroller {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-gutters {
|
||||
background: color-mix(in srgb, var(--card) 92%, var(--foreground));
|
||||
border-right: 1px solid var(--border);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-activeLine,
|
||||
.admin-json-editor .cm-activeLineGutter {
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-cursor {
|
||||
border-left-color: var(--foreground);
|
||||
}
|
||||
|
||||
.admin-json-editor .cm-selectionBackground,
|
||||
.admin-json-editor .cm-focused .cm-selectionBackground {
|
||||
background: color-mix(in srgb, var(--primary) 28%, transparent);
|
||||
}
|
||||
|
||||
@keyframes canvas-batch-child-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
Reference in New Issue
Block a user