一程序员去面试,面试官问:“你毕业才两年,这三年工作经验是怎么来的?!”程序员答:“加班。”
效果图
- 明暗主题
- 切换主题色
安装插件
npm i antd
npm i unocss @iconify/json -D
unocss配置
- 项目根目录创建uno.config.ts配置文件
/*
* @Author: vhen
* @Date: 2024-02-02 19:11:19
* @LastEditTime: 2024-03-05 17:08:08
* @Description: 现在的努力是为了小时候吹过的牛逼!
*
* @FilePath: \react-vhen-blog-admin\uno.config.ts
*
*/
import {
defineConfig,
presetAttributify,
presetIcons,
presetTypography,
presetUno,
transformerCompileClass,
transformerDirectives,
transformerVariantGroup,
} from 'unocss';
import transformerAttributifyJsx from './transformer-attributify-jsx';
export default defineConfig({
// 自定义快捷方式
shortcuts: {
'bg-base': 'bg-[#fff] dark:bg-[#1b1b1f] switch-animation', // 明暗背景色
'text-base': 'text-[#20202a] dark:text-[#f0f0f0] switch-animation', // 明暗字体样式
},
/** 排除 */
exclude: ['node_modules'],
presets: [
presetUno(), // m-10 理解为 margin:10rem 或者 m-10px 理解为 margin:10px
presetAttributify(),
presetIcons({
extraProperties: {
'display': 'inline-block',
'width': '1.2em',
'height': '1.2em',
'vertical-align': 'middle'
}
}),
presetTypography() // 归因模式 bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600" 背景颜色的简写
],
transformers: [
transformerAttributifyJsx(),
transformerDirectives(),
transformerVariantGroup(),
transformerCompileClass()
],
theme: {
colors: {
primary: 'var(--primary-color)',
}
}
});
- base.scss 基础样式
// 变量配置
:root {
/* 默认明亮模式的样式 */
--v-bg: #fff;
--v-scrollbar: #f2f2f2;
--v-scrollbar-hover: #bbb;
--v-text-color: #333;
}
html {
background-color: var(--v-bg);
color: var(--v-text-color);
overflow-x: hidden;
overflow-y: scroll;
}
html.dark {
/* 黑暗模式的样式 */
--v-bg: #222;
--v-scrollbar: #111;
--v-scrollbar-hover: #333;
--v-text-color: #fff;
}
html,body{
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
box-sizing: border-box;
width: 100%;
height: 100%;
}
#root,.ant-app{
width: 100%;
height: 100%
}
// scrollbar
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: var(--v-bg);
}
::-webkit-scrollbar-thumb {
background-color: var(--v-scrollbar);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--v-scrollbar-hover);
}
jotai中定义主题
- 声明ColorMode类型
// '/#/app'
export type ColorMode = "light" | "dark" | "default";
/*
* @Author: vhen
* @Date: 2024-02-25 14:39:50
* @LastEditTime: 2024-02-27 01:16:23
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\store\app.ts
*
*/
import { ColorMode } from '/#/app';
import { atom } from 'jotai';
// 切换菜单折叠状态
const collapseAtom = atom(false);
// 切换主题
const themeAtom = atom<ColorMode>('light');
// 主题色
const primaryColorAtom = atom<string>('1890ff');
export {
collapseAtom, primaryColorAtom, themeAtom
};
封装ThemeMode组件
import { Dropdown } from "antd";
import { useAtom } from 'jotai';
import type { MenuProps } from 'antd';
import { themeAtom } from '@/store/app';
const ThemeMode: React.FC = () => {
const [Theme, setTheme] = useAtom(themeAtom);
const iconMap = {
light: 'i-material-symbols:light-mode-outline',
dark: 'i-tdesign:mode-dark',
default: 'i-material-symbols:desktop-windows-outline-rounded',
};
const items: MenuProps['items'] = [
{
key: "1",
label: <span>light</span>,
onClick: () => setTheme('light'),
},
{
key: "2",
label: <span>dark</span>,
onClick: () => setTheme('dark'),
},
{
key: "3",
label: <span>default</span>,
onClick: () => setTheme('default'),
}
];
return (
<Dropdown menu={{ items }} placement="bottom" trigger={["click"]} arrow>
<i text-lg className={iconMap[Theme]} />
</Dropdown>
)
}
export default ThemeMode;
封装Hook usePreferredDark来监听主题改变
prefers-color-scheme这个CSS3媒体查询特性用来检测用户是否设置亮色(light)或暗色(dark) 的主题色在JavaScript 检测中,最关键的是使用 Window.matchMedia() API. 这个函数检测
document是否匹配对应的媒体查询并返回一个MediaQueryList对象. 通过返回对象, 可以检测document是否匹配媒体查询
import { useState } from 'react'
export function usePreferredDark() {
const [matches, setMatches] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
setMatches(e.matches)
})
return matches
}
封装Hook useDark来判断网页是否为暗黑模式
/*
* @Author: vhen
* @Date: 2024-02-27 01:13:24
* @LastEditTime: 2024-02-27 01:16:43
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\hooks\useDark.ts
*
*/
import { useMemo } from 'react';
import { ColorMode } from '/#/app';
import { usePreferredDark } from './usePreferredDark';
export function useDark(mode: ColorMode) {
const preferredDark = usePreferredDark()
const isDark = useMemo(() => {
return mode === 'dark' || (preferredDark && mode !== 'light')
}, [mode, preferredDark])
return isDark
}
App.tsx 配置主题
DOMTokenList 接口的 toggle() 方法从列表中删除一个给定的标记并返回 false。如果标记不存在,则添加并且函数返回 true。
/* eslint-disable @typescript-eslint/no-unused-vars */
import { App, ConfigProvider, theme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { useAtom } from 'jotai';
import { FC, ReactElement, useEffect } from 'react';
import { HashRouter, useRoutes } from 'react-router-dom';
import type { ThemeConfig } from 'antd';
import { useDark } from '@/hooks/useDark';
import allRoutes from '@/router';
import RouterGuard from '@/router/RouterGuard';
import { primaryColorAtom, themeAtom } from '@/store/app';
const Router = ({ routes }: { routes: any }) => useRoutes(routes);
const AppWrapper: FC = (): ReactElement => {
const [ThemeMode] = useAtom(themeAtom);
const [primaryColor] = useAtom(primaryColorAtom);
const isDark = useDark(ThemeMode!);
useEffect(() => {
document.documentElement.classList.toggle("dark", isDark);
}, [isDark]);
const config: ThemeConfig = {
token: {
colorPrimary: primaryColor
},
algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
};
return (
<ConfigProvider locale={zhCN} theme={config}>
<App>
<HashRouter>
{/* 路由守卫鉴权与拦截器 */}
<RouterGuard key="appraisal">
<Router routes={allRoutes} />
</RouterGuard>
</HashRouter>
</App>
</ConfigProvider>
);
};
export default AppWrapper;
主题色调用
import { Button, ColorPicker } from "antd";
import { useAtom } from "jotai";
import ThemeMode from '@/components/Theme/ThemeMode';
import { primaryColorAtom } from '@/store/app';
const Home: React.FC = () => {
const [primaryColor, setPrimaryColor] = useAtom(primaryColorAtom);
return (
<div>
<div mb-4> Home</div>
<div mb-4 pl-6 dark:text-color>
<ThemeMode />
</div>
<div mb-4>
<ColorPicker
value={primaryColor}
onChange={(_, c) => setPrimaryColor(c)}
/>
</div>
<Button type="primary">primary</Button>
<Button ml-4>default</Button>
</div>
);
};
export default Home;