feat: add dark mod (#134)

This commit is contained in:
Buer 2024-04-01 18:41:38 +08:00 committed by GitHub
parent 6300ce4835
commit 7c2b878892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 242 additions and 75 deletions

View File

@ -1,42 +1,42 @@
{ {
"name": "one_api_web", "name": "one_api_web",
"version": "1.0.0", "version": "1.2.0",
"proxy": "http://127.0.0.1:3000", "proxy": "http://127.0.0.1:3000",
"private": true, "private": true,
"homepage": "", "homepage": "",
"dependencies": { "dependencies": {
"@emotion/cache": "^11.9.3", "@emotion/cache": "^11.11.0",
"@emotion/react": "^11.9.3", "@emotion/react": "^11.11.3",
"@emotion/styled": "^11.9.3", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.8.4", "@mui/icons-material": "^5.15.7",
"@mui/lab": "^5.0.0-alpha.169", "@mui/lab": "^5.0.0-alpha.163",
"@mui/material": "^5.8.6", "@mui/material": "^5.15.7",
"@mui/system": "^5.8.6", "@mui/system": "^5.15.7",
"@mui/utils": "^5.8.6", "@mui/utils": "^5.15.7",
"@mui/x-data-grid": "^6.19.4", "@mui/x-data-grid": "^6.19.4",
"@mui/x-date-pickers": "^6.18.5", "@mui/x-date-pickers": "^6.18.5",
"@tabler/icons-react": "^2.44.0", "@tabler/icons-react": "^2.46.0",
"apexcharts": "^3.35.3", "apexcharts": "^3.45.2",
"axios": "^0.27.2", "axios": "^0.27.2",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"formik": "^2.2.9", "formik": "^2.4.5",
"framer-motion": "^6.3.16", "framer-motion": "^11.0.3",
"history": "^5.3.0", "history": "^5.3.0",
"marked": "^4.1.1", "marked": "^4.1.1",
"material-ui-popup-state": "^4.0.1", "material-ui-popup-state": "^5.0.10",
"notistack": "^3.0.1", "notistack": "^3.0.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-apexcharts": "^1.4.0", "react-apexcharts": "^1.4.1",
"react-device-detect": "^2.2.2", "react-device-detect": "^2.2.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-perfect-scrollbar": "^1.5.8", "react-perfect-scrollbar": "^1.5.8",
"react-redux": "^8.0.2", "react-redux": "^9.1.0",
"react-router": "6.3.0", "react-router": "6.21.3",
"react-router-dom": "6.3.0", "react-router-dom": "6.21.3",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"react-turnstile": "^1.1.2", "react-turnstile": "^1.1.2",
"redux": "^4.2.0", "redux": "^5.0.1",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"scripts": { "scripts": {
@ -67,19 +67,20 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.4", "@babel/core": "^7.23.9",
"@babel/eslint-parser": "^7.21.3", "@babel/eslint-parser": "^7.23.10",
"eslint": "^8.38.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"eslint-config-prettier": "^8.8.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"immutable": "^4.3.0", "immutable": "^4.3.5",
"prettier": "^2.8.7", "prettier": "^3.2.4",
"sass": "^1.53.0" "sass": "^1.70.0"
} }
} }

View File

@ -1,8 +1,9 @@
import { useSelector } from 'react-redux'; import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { ThemeProvider } from '@mui/material/styles'; import { ThemeProvider } from '@mui/material/styles';
import { CssBaseline, StyledEngineProvider } from '@mui/material'; import { CssBaseline, StyledEngineProvider } from '@mui/material';
import { SET_THEME } from 'store/actions';
// routing // routing
import Routes from 'routes'; import Routes from 'routes';
@ -21,8 +22,16 @@ import CopySnackbar from 'ui-component/Snackbar';
// ==============================|| APP ||============================== // // ==============================|| APP ||============================== //
const App = () => { const App = () => {
const dispatch = useDispatch();
const customization = useSelector((state) => state.customization); const customization = useSelector((state) => state.customization);
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
dispatch({ type: SET_THEME, theme: storedTheme });
}
}, [dispatch]);
return ( return (
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<ThemeProvider theme={themes(customization)}> <ThemeProvider theme={themes(customization)}>

View File

@ -46,11 +46,16 @@ $grey600: #4b5565;
$grey700: #364152; $grey700: #364152;
$grey900: #121926; $grey900: #121926;
$tableBackground: #f4f6f8;
$tableBorderBottom: #f1f3f4;
// ==============================|| DARK THEME VARIANTS ||============================== // // ==============================|| DARK THEME VARIANTS ||============================== //
// paper & background // paper & background
$darkBackground: #1a223f; // level 3 $darkBackground: #1a223f; // level 3
$darkPaper: #111936; // level 4 $darkPaper: #111936; // level 4
$darkDivider: rgba(227, 232, 239, 0.2);
$darkSelectedBack : rgba(124, 77, 255, 0.15);
// dark 800 & 900 // dark 800 & 900
$darkLevel1: #29314f; // level 1 $darkLevel1: #29314f; // level 1
@ -154,4 +159,9 @@ $darkTextSecondary: #8492c4;
darkSecondaryDark: $darkSecondaryDark; darkSecondaryDark: $darkSecondaryDark;
darkSecondary200: $darkSecondary200; darkSecondary200: $darkSecondary200;
darkSecondary800: $darkSecondary800; darkSecondary800: $darkSecondary800;
darkDivider: $darkDivider;
darkSelectedBack: $darkSelectedBack;
tableBackground: $tableBackground;
tableBorderBottom: $tableBorderBottom;
} }

View File

@ -71,8 +71,8 @@ const ProfileSection = () => {
alignItems: 'center', alignItems: 'center',
borderRadius: '27px', borderRadius: '27px',
transition: 'all .2s ease-in-out', transition: 'all .2s ease-in-out',
borderColor: theme.palette.primary.light, borderColor: theme.typography.menuChip.background,
backgroundColor: theme.palette.primary.light, backgroundColor: theme.typography.menuChip.background,
'&[aria-controls="menu-list-grow"], &:hover': { '&[aria-controls="menu-list-grow"], &:hover': {
borderColor: theme.palette.primary.main, borderColor: theme.palette.primary.main,
background: `${theme.palette.primary.main}!important`, background: `${theme.palette.primary.main}!important`,

View File

@ -7,6 +7,7 @@ import { Avatar, Box, ButtonBase } from '@mui/material';
// project imports // project imports
import LogoSection from '../LogoSection'; import LogoSection from '../LogoSection';
import ProfileSection from './ProfileSection'; import ProfileSection from './ProfileSection';
import ThemeButton from 'ui-component/ThemeButton';
// assets // assets
import { IconMenu2 } from '@tabler/icons-react'; import { IconMenu2 } from '@tabler/icons-react';
@ -37,9 +38,8 @@ const Header = ({ handleLeftDrawerToggle }) => {
sx={{ sx={{
...theme.typography.commonAvatar, ...theme.typography.commonAvatar,
...theme.typography.mediumAvatar, ...theme.typography.mediumAvatar,
...theme.typography.menuButton,
transition: 'all .2s ease-in-out', transition: 'all .2s ease-in-out',
background: theme.palette.secondary.light,
color: theme.palette.secondary.dark,
'&:hover': { '&:hover': {
background: theme.palette.secondary.dark, background: theme.palette.secondary.dark,
color: theme.palette.secondary.light color: theme.palette.secondary.light
@ -55,7 +55,7 @@ const Header = ({ handleLeftDrawerToggle }) => {
<Box sx={{ flexGrow: 1 }} /> <Box sx={{ flexGrow: 1 }} />
<Box sx={{ flexGrow: 1 }} /> <Box sx={{ flexGrow: 1 }} />
<ThemeButton />
<ProfileSection /> <ProfileSection />
</> </>
); );

View File

@ -36,7 +36,7 @@ import { useNavigate } from 'react-router-dom';
// })); // }));
const CardStyle = styled(Card)(({ theme }) => ({ const CardStyle = styled(Card)(({ theme }) => ({
background: theme.palette.primary.light, background: theme.typography.menuChip.background,
marginBottom: '22px', marginBottom: '22px',
overflow: 'hidden', overflow: 'hidden',
position: 'relative', position: 'relative',

View File

@ -38,6 +38,15 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
> >
<MenuList /> <MenuList />
<MenuCard /> <MenuCard />
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
<Chip
label={process.env.REACT_APP_VERSION || '未知版本'}
disabled
chipcolor="secondary"
size="small"
sx={{ cursor: 'pointer' }}
/>
</Stack>
</PerfectScrollbar> </PerfectScrollbar>
</BrowserView> </BrowserView>
<MobileView> <MobileView>
@ -45,7 +54,13 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
<MenuList /> <MenuList />
<MenuCard /> <MenuCard />
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}> <Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
<Chip label={process.env.REACT_APP_VERSION} disabled chipcolor="secondary" size="small" sx={{ cursor: 'pointer' }} /> <Chip
label={process.env.REACT_APP_VERSION || '未知版本'}
disabled
chipcolor="secondary"
size="small"
sx={{ cursor: 'pointer' }}
/>
</Stack> </Stack>
</Box> </Box>
</MobileView> </MobileView>

View File

@ -5,6 +5,7 @@ import LogoSection from 'layout/MainLayout/LogoSection';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import ThemeButton from 'ui-component/ThemeButton';
// ==============================|| MAIN NAVBAR / HEADER ||============================== // // ==============================|| MAIN NAVBAR / HEADER ||============================== //
@ -38,6 +39,7 @@ const Header = () => {
<Button component={Link} variant="text" to="/about" color={pathname === '/about' ? 'primary' : 'inherit'}> <Button component={Link} variant="text" to="/about" color={pathname === '/about' ? 'primary' : 'inherit'}>
关于 关于
</Button> </Button>
<ThemeButton />
{account.user ? ( {account.user ? (
<Button component={Link} variant="contained" to="/panel" color="primary"> <Button component={Link} variant="contained" to="/panel" color="primary">
控制台 控制台

View File

@ -7,3 +7,4 @@ export const SET_BORDER_RADIUS = '@customization/SET_BORDER_RADIUS';
export const SET_SITE_INFO = '@siteInfo/SET_SITE_INFO'; export const SET_SITE_INFO = '@siteInfo/SET_SITE_INFO';
export const LOGIN = '@account/LOGIN'; export const LOGIN = '@account/LOGIN';
export const LOGOUT = '@account/LOGOUT'; export const LOGOUT = '@account/LOGOUT';
export const SET_THEME = '@customization/SET_THEME';

View File

@ -9,7 +9,8 @@ export const initialState = {
defaultId: 'default', defaultId: 'default',
fontFamily: config.fontFamily, fontFamily: config.fontFamily,
borderRadius: config.borderRadius, borderRadius: config.borderRadius,
opened: true opened: true,
theme: 'light'
}; };
// ==============================|| CUSTOMIZATION REDUCER ||============================== // // ==============================|| CUSTOMIZATION REDUCER ||============================== //
@ -38,6 +39,11 @@ const customizationReducer = (state = initialState, action) => {
...state, ...state,
borderRadius: action.borderRadius borderRadius: action.borderRadius
}; };
case actionTypes.SET_THEME:
return {
...state,
theme: action.theme
};
default: default:
return state; return state;
} }

View File

@ -1,5 +1,5 @@
export default function componentStyleOverrides(theme) { export default function componentStyleOverrides(theme) {
const bgColor = theme.colors?.grey50; const bgColor = theme.mode === 'dark' ? theme.backgroundDefault : theme.colors?.grey50;
return { return {
MuiButton: { MuiButton: {
styleOverrides: { styleOverrides: {
@ -226,12 +226,12 @@ export default function componentStyleOverrides(theme) {
MuiTableCell: { MuiTableCell: {
styleOverrides: { styleOverrides: {
root: { root: {
borderBottom: '1px solid rgb(241, 243, 244)', borderBottom: '1px solid ' + theme.tableBorderBottom,
textAlign: 'center' textAlign: 'center'
}, },
head: { head: {
color: theme.darkTextSecondary, color: theme.darkTextSecondary,
backgroundColor: 'rgb(244, 246, 248)' backgroundColor: theme.headBackgroundColor
} }
} }
}, },
@ -239,7 +239,7 @@ export default function componentStyleOverrides(theme) {
styleOverrides: { styleOverrides: {
root: { root: {
'&:hover': { '&:hover': {
backgroundColor: 'rgb(244, 246, 248)' backgroundColor: theme.headBackgroundColor
} }
} }
} }
@ -251,6 +251,22 @@ export default function componentStyleOverrides(theme) {
background: theme.colors?.grey700 background: theme.colors?.grey700
} }
} }
},
MuiCssBaseline: {
styleOverrides: `
.apexcharts-title-text {
fill: ${theme.textDark} !important
}
.apexcharts-text {
fill: ${theme.textDark} !important
}
.apexcharts-legend-text {
color: ${theme.textDark} !important
}
.apexcharts-menu {
background: ${theme.backgroundDefault} !important
}
`
} }
}; };
} }

View File

@ -15,19 +15,10 @@ import themeTypography from './typography';
export const theme = (customization) => { export const theme = (customization) => {
const color = colors; const color = colors;
const options = customization.theme === 'light' ? GetLightOption() : GetDarkOption();
const themeOption = { const themeOption = {
colors: color, colors: color,
heading: color.grey900, ...options,
paper: color.paper,
backgroundDefault: color.paper,
background: color.primaryLight,
darkTextPrimary: color.grey700,
darkTextSecondary: color.grey500,
textDark: color.grey900,
menuSelected: color.secondaryDark,
menuSelectedBack: color.secondaryLight,
divider: color.grey200,
customization customization
}; };
@ -53,3 +44,49 @@ export const theme = (customization) => {
}; };
export default theme; export default theme;
function GetDarkOption() {
const color = colors;
return {
mode: 'dark',
heading: color.darkTextTitle,
paper: color.darkLevel2,
backgroundDefault: color.darkPaper,
background: color.darkBackground,
darkTextPrimary: color.darkTextPrimary,
darkTextSecondary: color.darkTextSecondary,
textDark: color.darkTextTitle,
menuSelected: color.darkSecondaryMain,
menuSelectedBack: color.darkSelectedBack,
divider: color.darkDivider,
borderColor: color.darkBorderColor,
menuButton: color.darkLevel1,
menuButtonColor: color.darkSecondaryMain,
menuChip: color.darkLevel1,
headBackgroundColor: color.darkBackground,
tableBorderBottom: color.darkDivider
};
}
function GetLightOption() {
const color = colors;
return {
mode: 'light',
heading: color.grey900,
paper: color.paper,
backgroundDefault: color.paper,
background: color.primaryLight,
darkTextPrimary: color.grey700,
darkTextSecondary: color.grey500,
textDark: color.grey900,
menuSelected: color.secondaryDark,
menuSelectedBack: color.secondaryLight,
divider: color.grey200,
borderColor: color.grey300,
menuButton: color.secondaryLight,
menuButtonColor: color.secondaryDark,
menuChip: color.primaryLight,
headBackgroundColor: color.tableBackground,
tableBorderBottom: color.tableBorderBottom
};
}

View File

@ -5,7 +5,7 @@
export default function themePalette(theme) { export default function themePalette(theme) {
return { return {
mode: 'light', mode: theme.mode,
common: { common: {
black: theme.colors?.darkPaper black: theme.colors?.darkPaper
}, },

View File

@ -132,6 +132,19 @@ export default function themeTypography(theme) {
width: '44px', width: '44px',
height: '44px', height: '44px',
fontSize: '1.5rem' fontSize: '1.5rem'
},
menuButton: {
color: theme.menuButtonColor,
background: theme.menuButton
},
menuChip: {
background: theme.menuChip
},
CardWrapper: {
backgroundColor: theme.mode === 'dark' ? theme.colors.darkLevel2 : theme.colors.primaryDark
},
SubCard: {
border: theme.mode === 'dark' ? '1px solid rgba(227, 232, 239, 0.2)' : '1px solid rgb(227, 232, 239)'
} }
}; };
} }

View File

@ -5,11 +5,10 @@ import Loader from './Loader';
// ==============================|| LOADABLE - LAZY LOADING ||============================== // // ==============================|| LOADABLE - LAZY LOADING ||============================== //
const Loadable = (Component) => (props) => const Loadable = (Component) => (props) => (
( <Suspense fallback={<Loader />}>
<Suspense fallback={<Loader />}> <Component {...props} />
<Component {...props} /> </Suspense>
</Suspense> );
);
export default Loadable; export default Loadable;

View File

@ -1,6 +1,8 @@
// material-ui // material-ui
import logo from 'assets/images/logo.svg'; import logoLight from 'assets/images/logo.svg';
import logoDark from 'assets/images/logo-white.svg';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useTheme } from '@mui/material/styles';
/** /**
* if you want to use image instead of <svg> uncomment following. * if you want to use image instead of <svg> uncomment following.
@ -14,6 +16,8 @@ import { useSelector } from 'react-redux';
const Logo = () => { const Logo = () => {
const siteInfo = useSelector((state) => state.siteInfo); const siteInfo = useSelector((state) => state.siteInfo);
const theme = useTheme();
const logo = theme.palette.mode === 'light' ? logoLight : logoDark;
return <img src={siteInfo.logo || logo} alt={siteInfo.system_name} height="50" />; return <img src={siteInfo.logo || logo} alt={siteInfo.system_name} height="50" />;
}; };

View File

@ -0,0 +1,50 @@
import { useDispatch, useSelector } from 'react-redux';
import { SET_THEME } from 'store/actions';
import { useTheme } from '@mui/material/styles';
import { Avatar, Box, ButtonBase } from '@mui/material';
import { IconSun, IconMoon } from '@tabler/icons-react';
export default function ThemeButton() {
const dispatch = useDispatch();
const defaultTheme = useSelector((state) => state.customization.theme);
const theme = useTheme();
return (
<Box
sx={{
ml: 2,
mr: 3,
[theme.breakpoints.down('md')]: {
mr: 2
}
}}
>
<ButtonBase sx={{ borderRadius: '12px' }}>
<Avatar
variant="rounded"
sx={{
...theme.typography.commonAvatar,
...theme.typography.mediumAvatar,
transition: 'all .2s ease-in-out',
borderColor: theme.typography.menuChip.background,
backgroundColor: theme.typography.menuChip.background,
'&[aria-controls="menu-list-grow"],&:hover': {
background: theme.palette.secondary.dark,
color: theme.palette.secondary.light
}
}}
onClick={() => {
let theme = defaultTheme === 'light' ? 'dark' : 'light';
dispatch({ type: SET_THEME, theme: theme });
localStorage.setItem('theme', theme);
}}
color="inherit"
>
{defaultTheme === 'light' ? <IconSun stroke={1.5} size="1.3rem" /> : <IconMoon stroke={1.5} size="1.3rem" />}
</Avatar>
</ButtonBase>
</Box>
);
}

View File

@ -15,7 +15,7 @@ const headerSX = {
const MainCard = forwardRef( const MainCard = forwardRef(
( (
{ {
border = true, border = false,
boxShadow, boxShadow,
children, children,
content = true, content = true,

View File

@ -15,8 +15,7 @@ const SubCard = forwardRef(
<Card <Card
ref={ref} ref={ref}
sx={{ sx={{
border: '1px solid', border: theme.typography.SubCard.border,
borderColor: theme.palette.primary.light,
':hover': { ':hover': {
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)' boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)'
}, },
@ -36,8 +35,8 @@ const SubCard = forwardRef(
{title && ( {title && (
<Divider <Divider
sx={{ sx={{
opacity: 1, opacity: 1
borderColor: theme.palette.primary.light // borderColor: theme.palette.primary.light
}} }}
/> />
)} )}

View File

@ -40,7 +40,8 @@ export function generateLineChartOptions(data, unit) {
chart: { chart: {
sparkline: { sparkline: {
enabled: true enabled: true
} },
background: 'transparent'
}, },
dataLabels: { dataLabels: {
enabled: false enabled: false
@ -139,7 +140,8 @@ export function generateBarChartOptions(xaxis, data, unit = '', decimal = 0) {
}, },
zoom: { zoom: {
enabled: true enabled: true
} },
background: 'transparent'
}, },
responsive: [ responsive: [
{ {

View File

@ -100,7 +100,8 @@ const chartData = {
}, },
zoom: { zoom: {
enabled: true enabled: true
} },
background: 'transparent'
}, },
responsive: [ responsive: [
{ {

View File

@ -229,7 +229,8 @@ function getBarChartOptions(data, dateRange) {
type: 'line', type: 'line',
zoom: { zoom: {
enabled: false enabled: false
} },
background: 'transparent'
}; };
channelData.latency.options.stroke = { channelData.latency.options.stroke = {
curve: 'smooth', curve: 'smooth',
@ -266,7 +267,8 @@ function getRedemptionData(data, dateRange) {
height: 480, height: 480,
options: { options: {
chart: { chart: {
type: 'line' type: 'line',
background: 'transparent'
}, },
stroke: { stroke: {
width: [0, 4] width: [0, 4]

View File

@ -12,7 +12,7 @@ import MainCard from 'ui-component/cards/MainCard';
import SkeletonTotalOrderCard from 'ui-component/cards/Skeleton/EarningCard'; import SkeletonTotalOrderCard from 'ui-component/cards/Skeleton/EarningCard';
const CardWrapper = styled(MainCard)(({ theme }) => ({ const CardWrapper = styled(MainCard)(({ theme }) => ({
backgroundColor: theme.palette.primary.dark, ...theme.typography.CardWrapper,
color: '#fff', color: '#fff',
overflow: 'hidden', overflow: 'hidden',
position: 'relative', position: 'relative',