Jelajahi Sumber

Merge pull request #208 from kahosan/refactor_dark_mode

fix: the dark mode does not work for the `OperationSetting` and `SystemSetting` panels
Calcium-Ion 1 tahun lalu
induk
melakukan
9dfd405ba9

+ 11 - 17
web/src/components/HeaderBar.js

@@ -1,6 +1,7 @@
 import React, { useContext, useEffect, useState } from 'react';
 import { Link, useNavigate } from 'react-router-dom';
 import { UserContext } from '../context/User';
+import { useSetTheme, useTheme } from '../context/Theme';
 
 import { API, getLogo, getSystemName, showSuccess } from '../helpers';
 import '../index.css';
@@ -34,10 +35,8 @@ const HeaderBar = () => {
   let navigate = useNavigate();
 
   const [showSidebar, setShowSidebar] = useState(false);
-  const [dark, setDark] = useState(false);
   const systemName = getSystemName();
   const logo = getLogo();
-  var themeMode = localStorage.getItem('theme-mode');
   const currentDate = new Date();
   // enable fireworks on new year(1.1 and 2.9-2.24)
   const isNewYear =
@@ -66,26 +65,19 @@ const HeaderBar = () => {
     }, 3000);
   };
 
+  const theme = useTheme();
+  const setTheme = useSetTheme();
+
   useEffect(() => {
-    if (themeMode === 'dark') {
-      switchMode(true);
+    if (theme === 'dark') {
+      document.body.setAttribute('theme-mode', 'dark');
     }
+
     if (isNewYear) {
       console.log('Happy New Year!');
     }
   }, []);
 
-  const switchMode = (model) => {
-    const body = document.body;
-    if (!model) {
-      body.removeAttribute('theme-mode');
-      localStorage.setItem('theme-mode', 'light');
-    } else {
-      body.setAttribute('theme-mode', 'dark');
-      localStorage.setItem('theme-mode', 'dark');
-    }
-    setDark(model);
-  };
   return (
     <>
       <Layout>
@@ -132,9 +124,11 @@ const HeaderBar = () => {
                 <Switch
                   checkedText='🌞'
                   size={'large'}
-                  checked={dark}
+                  checked={theme === 'dark'}
                   uncheckedText='🌙'
-                  onChange={switchMode}
+                  onChange={(checked) => {
+                    setTheme(checked);
+                  }}
                 />
                 {userState.user ? (
                   <>

+ 30 - 9
web/src/components/OperationSetting.js

@@ -8,6 +8,8 @@ import {
   verifyJSON,
 } from '../helpers';
 
+import { useTheme } from '../context/Theme';
+
 const OperationSetting = () => {
   let now = new Date();
   let [inputs, setInputs] = useState({
@@ -77,6 +79,9 @@ const OperationSetting = () => {
     }
   };
 
+  const theme = useTheme();
+  const isDark = theme === 'dark';
+
   useEffect(() => {
     getOptions().then();
   }, []);
@@ -219,8 +224,10 @@ const OperationSetting = () => {
   return (
     <Grid columns={1}>
       <Grid.Column>
-        <Form loading={loading}>
-          <Header as='h3'>通用设置</Header>
+        <Form loading={loading} inverted={isDark}>
+          <Header as='h3' inverted={isDark}>
+            通用设置
+          </Header>
           <Form.Group widths={4}>
             <Form.Input
               label='充值链接'
@@ -299,7 +306,9 @@ const OperationSetting = () => {
             保存通用设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>绘图设置</Header>
+          <Header as='h3' inverted={isDark}>
+            绘图设置
+          </Header>
           <Form.Group inline>
             <Form.Checkbox
               checked={inputs.DrawingEnabled === 'true'}
@@ -321,7 +330,9 @@ const OperationSetting = () => {
             />
           </Form.Group>
           <Divider />
-          <Header as='h3'>屏蔽词过滤设置</Header>
+          <Header as='h3' inverted={isDark}>
+            屏蔽词过滤设置
+          </Header>
           <Form.Group inline>
             <Form.Checkbox
               checked={inputs.CheckSensitiveEnabled === 'true'}
@@ -381,7 +392,9 @@ const OperationSetting = () => {
             保存屏蔽词设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>日志设置</Header>
+          <Header as='h3' inverted={isDark}>
+            日志设置
+          </Header>
           <Form.Group inline>
             <Form.Checkbox
               checked={inputs.LogConsumeEnabled === 'true'}
@@ -409,7 +422,9 @@ const OperationSetting = () => {
             清理历史日志
           </Form.Button>
           <Divider />
-          <Header as='h3'>数据看板</Header>
+          <Header as='h3' inverted={isDark}>
+            数据看板
+          </Header>
           <Form.Checkbox
             checked={inputs.DataExportEnabled === 'true'}
             label='启用数据看板(实验性)'
@@ -439,7 +454,9 @@ const OperationSetting = () => {
             />
           </Form.Group>
           <Divider />
-          <Header as='h3'>监控设置</Header>
+          <Header as='h3' inverted={isDark}>
+            监控设置
+          </Header>
           <Form.Group widths={3}>
             <Form.Input
               label='最长响应时间'
@@ -484,7 +501,9 @@ const OperationSetting = () => {
             保存监控设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>额度设置</Header>
+          <Header as='h3' inverted={isDark}>
+            额度设置
+          </Header>
           <Form.Group widths={4}>
             <Form.Input
               label='新用户初始额度'
@@ -535,7 +554,9 @@ const OperationSetting = () => {
             保存额度设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>倍率设置</Header>
+          <Header as='h3' inverted={isDark}>
+            倍率设置
+          </Header>
           <Form.Group widths='equal'>
             <Form.TextArea
               label='模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)'

+ 15 - 10
web/src/components/SystemSetting.js

@@ -10,6 +10,8 @@ import {
 } from 'semantic-ui-react';
 import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
 
+import { useTheme } from '../context/Theme';
+
 const SystemSetting = () => {
   let [inputs, setInputs] = useState({
     PasswordLoginEnabled: '',
@@ -57,6 +59,9 @@ const SystemSetting = () => {
   const [showPasswordWarningModal, setShowPasswordWarningModal] =
     useState(false);
 
+  const theme = useTheme();
+  const isDark = theme === 'dark';
+
   const getOptions = async () => {
     const res = await API.get('/api/option/');
     const { success, message, data } = res.data;
@@ -306,8 +311,8 @@ const SystemSetting = () => {
   return (
     <Grid columns={1}>
       <Grid.Column>
-        <Form loading={loading}>
-          <Header as='h3'>通用设置</Header>
+        <Form loading={loading} inverted={isDark}>
+          <Header as='h3' inverted={isDark}>通用设置</Header>
           <Form.Group widths='equal'>
             <Form.Input
               label='服务器地址'
@@ -321,7 +326,7 @@ const SystemSetting = () => {
             更新服务器地址
           </Form.Button>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
           </Header>
           <Form.Group widths='equal'>
@@ -385,7 +390,7 @@ const SystemSetting = () => {
           </Form.Group>
           <Form.Button onClick={submitPayAddress}>更新支付设置</Form.Button>
           <Divider />
-          <Header as='h3'>配置登录注册</Header>
+          <Header as='h3' inverted={isDark}>配置登录注册</Header>
           <Form.Group inline>
             <Form.Checkbox
               checked={inputs.PasswordLoginEnabled === 'true'}
@@ -468,7 +473,7 @@ const SystemSetting = () => {
             />
           </Form.Group>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             配置邮箱域名白名单
             <Header.Subheader>
               用以防止恶意用户利用临时邮箱批量注册
@@ -533,7 +538,7 @@ const SystemSetting = () => {
             保存邮箱域名白名单设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             配置 SMTP
             <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
           </Header>
@@ -592,7 +597,7 @@ const SystemSetting = () => {
           </Form.Group>
           <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             配置 GitHub OAuth App
             <Header.Subheader>
               用以支持通过 GitHub 进行登录注册,
@@ -634,7 +639,7 @@ const SystemSetting = () => {
             保存 GitHub OAuth 设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             配置 WeChat Server
             <Header.Subheader>
               用以支持通过微信进行登录注册,
@@ -679,7 +684,7 @@ const SystemSetting = () => {
             保存 WeChat Server 设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>配置 Telegram 登录</Header>
+          <Header as='h3' inverted={isDark}>配置 Telegram 登录</Header>
           <Form.Group inline>
             <Form.Input
               label='Telegram Bot Token'
@@ -700,7 +705,7 @@ const SystemSetting = () => {
             保存 Telegram 登录设置
           </Form.Button>
           <Divider />
-          <Header as='h3'>
+          <Header as='h3' inverted={isDark}>
             配置 Turnstile
             <Header.Subheader>
               用以支持用户校验,

+ 36 - 0
web/src/context/Theme/index.js

@@ -0,0 +1,36 @@
+import { createContext, useCallback, useContext, useState } from 'react';
+
+const ThemeContext = createContext(null);
+export const useTheme = () => useContext(ThemeContext);
+
+const SetThemeContext = createContext(null);
+export const useSetTheme = () => useContext(SetThemeContext);
+
+export const ThemeProvider = ({ children }) => {
+  const [theme, _setTheme] = useState(() => {
+    try {
+      return localStorage.getItem('theme-mode') || null;
+    } catch {
+      return null;
+    }
+  });
+
+  const setTheme = useCallback((input) => {
+    _setTheme(input ? 'dark' : 'light');
+
+    const body = document.body;
+    if (!input) {
+      body.removeAttribute('theme-mode');
+      localStorage.setItem('theme-mode', 'light');
+    } else {
+      body.setAttribute('theme-mode', 'dark');
+      localStorage.setItem('theme-mode', 'dark');
+    }
+  }, []);
+
+  return (
+    <SetThemeContext.Provider value={setTheme}>
+      <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
+    </SetThemeContext.Provider>
+  );
+};

+ 22 - 19
web/src/index.js

@@ -12,6 +12,7 @@ import 'react-toastify/dist/ReactToastify.css';
 import { StatusProvider } from './context/Status';
 import { Layout } from '@douyinfe/semi-ui';
 import SiderBar from './components/SiderBar';
+import { ThemeProvider } from './context/Theme';
 
 // initialization
 
@@ -22,27 +23,29 @@ root.render(
     <StatusProvider>
       <UserProvider>
         <BrowserRouter>
-          <Layout>
-            <Sider>
-              <SiderBar />
-            </Sider>
+          <ThemeProvider>
             <Layout>
-              <Header>
-                <HeaderBar />
-              </Header>
-              <Content
-                style={{
-                  padding: '24px',
-                }}
-              >
-                <App />
-              </Content>
-              <Layout.Footer>
-                <Footer></Footer>
-              </Layout.Footer>
+              <Sider>
+                <SiderBar />
+              </Sider>
+              <Layout>
+                <Header>
+                  <HeaderBar />
+                </Header>
+                <Content
+                  style={{
+                    padding: '24px',
+                  }}
+                >
+                  <App />
+                </Content>
+                <Layout.Footer>
+                  <Footer></Footer>
+                </Layout.Footer>
+              </Layout>
+              <ToastContainer />
             </Layout>
-            <ToastContainer />
-          </Layout>
+          </ThemeProvider>
         </BrowserRouter>
       </UserProvider>
     </StatusProvider>