Antd自定义主题色
(1) 创建主题状态Store
- 文件: src/store/theme.tsx
tsx
import { proxy } from "valtio";
interface ThemeState {
/** 当前主题色 */
colorPrimary: string;
/** 是否为暗黑模式 */
isDark: boolean;
/** 设置主题色 */
setColorPrimary: (color: string) => void;
/** 切换暗黑模式 */
toggleDark: () => void;
}
export const themeStore = proxy<ThemeState>({
colorPrimary: "#1677ff", // 默认主题色:antd 蓝色
isDark: false, // 默认亮色模式
setColorPrimary(color: string) {
this.colorPrimary = color;
},
toggleDark() {
this.isDark = !this.isDark;
},
});(2) 根组件绑定 antd ConfigProvider
src/App.tsx
tsx
import { RouterProvider } from "react-router-dom";
import { ConfigProvider, theme } from "antd";
import zhCN from "antd/locale/zh_CN";
import { useSnapshot } from "valtio";
import router from "@/routes/router";
import { themeStore } from "@/store/theme";
export default function App() {
const { colorPrimary, isDark } = useSnapshot(themeStore);
return (
<ConfigProvider
locale={zhCN}
theme={{
token: {
colorPrimary, // ← 关键:绑定到 store 中的 colorPrimary
},
algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}
>
<RouterProvider router={router} />
</ConfigProvider>
);
}关键机制
useSnapshot(themeStore)让 App 组件"监听" store- 当
themeStore.colorPrimary被修改时,App 重新渲染 - App 重新渲染时,
ConfigProvider的themeprop 更新 - antd 内部检测到
colorPrimary变化, 自动重新计算所有组件的样式
(3) NavHeader 添加颜色选择器和暗黑模式
- 文件: src/components/NavHeader/index.tsx
tsx
import {
MenuFoldOutlined,
DownOutlined,
MenuUnfoldOutlined,
} from "@ant-design/icons";
import {
Breadcrumb,
Switch,
Dropdown,
type MenuProps,
Space,
ColorPicker,
} from "antd";
import { useState } from "react";
import { NavHeaderWrapper, NavLeft, NickName } from "./navheader.css";
import { useSnapshot } from "valtio";
import { collapseStore } from "@/store/collapse";
import { themeStore } from "@/store/theme";
import { useNavigate } from "react-router-dom";
function NavHeader() {
// 组件内订阅状态
const { collapsed } = useSnapshot(collapseStore);
const { isDark, colorPrimary } = useSnapshot(themeStore);
const navigate = useNavigate();
const [breadlist] = useState([
{ title: "首页", key: "1" },
{ title: "欢迎页", key: "2" },
]);
const items: MenuProps["items"] = [
{
key: "1",
label: "邮箱:2993222085@qq.com",
},
{
key: "2",
danger: true,
label: "退出登陆",
},
];
return (
<NavHeaderWrapper>
<NavLeft>
{collapsed ? (
<MenuUnfoldOutlined onClick={() => collapseStore.toggle()} />
) : (
<MenuFoldOutlined onClick={() => collapseStore.toggle()} />
)}
<Breadcrumb items={breadlist} style={{ marginLeft: 12 }} />
</NavLeft>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<ColorPicker
value={colorPrimary}
onChange={(color) => themeStore.setColorPrimary(color.toHexString())}
style={{ marginRight: 12 }}
/>
<Switch
checkedChildren="暗黑"
unCheckedChildren="默认"
style={{ marginRight: 12 }}
checked={isDark}
onChange={() => themeStore.toggleDark()}
/>
<Dropdown
menu={{
items,
onClick: ({ key }) => {
if (key === "2") {
navigate("/login");
localStorage.removeItem("token");
return;
}
console.log(`当前序号:${key},hello world`);
},
}}
trigger={["click"]}
>
<a onClick={(e) => e.preventDefault()}>
<NickName>
<Space>
七宗罪
<DownOutlined />
</Space>
</NickName>
</a>
</Dropdown>
</div>
</NavHeaderWrapper>
);
}
export default NavHeader;(4) NavFooter 使用动态主题色
- 修改 src/components/NavFooter/index.tsx
tsx
import { useSnapshot } from "valtio";
import { FooterLink, FooterWrapper, Gutter } from "./navfooter.css";
import { themeStore } from "@/store/theme";
function NavFooter() {
const { colorPrimary } = useSnapshot(themeStore); // 订阅主题色
return (
<FooterWrapper
style={{ "--theme-color": colorPrimary } as React.CSSProperties}
>
<div>
<FooterLink href="https://www.uiyin.com" target="_blank">
个人博客页面
</FooterLink>
<Gutter>|</Gutter>
<FooterLink href="#" target="_blank">
React后台管理系统
</FooterLink>
<Gutter>|</Gutter>
<FooterLink href="https://demo1.jsopy.com/" target="_blank">
Vue3后台管理系统
</FooterLink>
<Gutter>|</Gutter>
</div>
<div>Copyright @ 2026 七宗罪React测试项目 All Rights Reserved.</div>
</FooterWrapper>
);
}关键代码
tsx
style={{ '--theme-color': colorPrimary } as React.CSSProperties}--theme-color是一个 CSS 自定义属性(CSS 变量)- 通过
style属性设置在FooterWrapper这个 DOM 元素上 as React.CSSProperties是 TypeScript 类型断言,因为 React 默认不认识--xxx这种自定义属性
修改 src/components/NavFooter/navfooter.css.tsx
tsx
import styled from "@emotion/styled";
export const FooterWrapper = styled.div`
text-align: center;
line-height: 30px;
color: #b0aeae;
font-size: 14px;
a {
color: #78b2f8;
}
`;
export const FooterLink = styled.a`
&:hover {
color: var(--theme-color) !important; // ← 关键:使用 CSS 变量
}
`;
export const Gutter = styled.span`
margin-left: 10px;
margin-right: 10px;
`;- 为什么必须用css变量
注意
因为themeColor 是 硬编码的常量 ,在代码打包后就固定了。而var(--theme-color) 是在浏览器运行时,从 DOM 元素的style 属性中动态读取的。当colorPrimary 变化时,FooterWrapper 上的--theme-color 值会更新,FooterLink 的 hover 颜色也随之更新
最后完成流程图
bash
┌─────────────────────────────────────────────────────────────────┐
│ 用户操作层 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ColorPicker │ │ Switch │ │
│ │ 选择颜色 │ │ 切换暗黑 │ │
│ └──────┬──────┘ └──────┬──────┘ │
└─────────┼──────────────────┼────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ 状态管理层 (valtio) │
│ themeStore.setColorPrimary('#xxx') │
│ themeStore.toggleDark() │
│ │ │
│ ┌─────────┴─────────┐ │
│ ▼ ▼ │
│ colorPrimary 变化 isDark 变化 │
│ │ │ │
└──────────────┼───────────────────┼──────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ 组件渲染层 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ App.tsx │ │ NavFooter │ │
│ │ useSnapshot() │ │ useSnapshot() │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ConfigProvider │ │ FooterWrapper │ │
│ │ colorPrimary │ │ --theme-color │ │
│ │ algorithm │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ antd 组件自动 │ │ FooterLink hover │ │
│ │ 更新主色调 │ │ 读取 var(--theme-color) │
│ │ (Button等) │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘总结 每个文件的作用
| 文件 | 职责 |
|---|---|
| store/theme.tsx | 数据中心 :存储colorPrimary 和isDark ,提供修改方法 |
| App.tsx | antd 桥接 :订阅 store,通过ConfigProvider 把颜色传给所有 antd 组件 |
| NavHeader/index.tsx | 用户入口 :提供 ColorPicker 和 Switch,让用户可以修改 store |
| NavFooter/index.tsx | 自定义组件 :订阅 store,通过 CSS 变量把颜色传给 styled-components |
| NavFooter/navfooter.css.tsx | 样式定义 :用var(--theme-color) 读取 CSS 变量,实现动态 hover 颜色 |