Browse Source

Merge pull request #6 from AI-ASS/main

chore: Daily optimization
Calcium-Ion 1 year ago
parent
commit
29f13324d7
11 changed files with 286 additions and 160 deletions
  1. 5 2
      .env.example
  2. 22 14
      README.md
  3. BIN
      public/favicon.ico
  4. 1 1
      public/index.html
  5. BIN
      public/logo192.png
  6. BIN
      public/logo512.png
  7. 30 1
      src/components/HeaderBar.js
  8. 159 125
      src/components/LogsTable.js
  9. 46 0
      src/context/Theme/index.js
  10. 12 9
      src/index.css
  11. 11 8
      src/index.js

+ 5 - 2
.env.example

@@ -4,5 +4,8 @@ REACT_APP_SHOW_DETAIL=true
 # 展示余额
 REACT_APP_SHOW_BALANCE=true
 
-# BaseURL 结尾不要带/
-REACT_APP_BASE_URL=https://nekoapi.com
+# NewAPI的BaseURL
+REACT_APP_BASE_URL={"server1": "https://api.server1.com", "server2": "https://api.server2.com"}
+
+# 是否显示GitHub图标
+REACT_APP_SHOW_ICONGITHUB=true

+ 22 - 14
README.md

@@ -6,7 +6,7 @@
 
 NewAPI 令牌查询页
 
-[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/aiass1s-projects/clone?repository-url=https%3A%2F%2Fgithub.com%2FAI-ASS%2Fneko-api-key-tool&env=REACT_APP_SHOW_DETAIL&env=REACT_APP_SHOW_BALANCE&env=REACT_APP_BASE_URL&project-name=neko-api-key-tool&repository-name=neko-api-key-tool)
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FAI-ASS%2Fneko-api-key-tool&env=REACT_APP_SHOW_DETAIL&env=REACT_APP_SHOW_BALANCE&env=REACT_APP_BASE_URL&env=REACT_APP_SHOW_ICONGITHUB&project-name=neko-api-key-tool&repository-name=neko-api-key-tool)
 
 </div>
 
@@ -17,24 +17,32 @@ NewAPI 令牌查询页
 
 1. 准备好你的 [NewAPI项目](https://github.com/Calcium-Ion/new-api);
 2. 点击右侧按钮开始部署:
-   [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/aiass1s-projects/clone?repository-url=https%3A%2F%2Fgithub.com%2FAI-ASS%2Fneko-api-key-tool&env=REACT_APP_SHOW_DETAIL&env=REACT_APP_SHOW_BALANCE&env=REACT_APP_BASE_URL&project-name=neko-api-key-tool&repository-name=neko-api-key-tool),直接使用 Github 账号登录即可,记得在环境变量页填入 `REACT_APP_SHOW_BALANCE` (是否展示令牌信息,true 或 false) 和 `REACT_APP_SHOW_DETAIL` (是否展示调用详情,true 或 false) 和 `REACT_APP_BASE_URL` (你的NewAPI项目地址);
-3. 部署完毕后,即可开始使用;
-4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
+   [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FAI-ASS%2Fneko-api-key-tool&env=REACT_APP_SHOW_DETAIL&env=REACT_APP_SHOW_BALANCE&env=REACT_APP_BASE_URL&env=REACT_APP_SHOW_ICONGITHUB&project-name=neko-api-key-tool&repository-name=neko-api-key-tool),直接使用 Github 账号登录即可,记得根据自己需求配置环境变量,环境变量如下: 
 
-### 二次开发
-复制.env.example文件为.env
+```   
+REACT_APP_SHOW_BALANCE: 是否展示令牌信息,true 或 false
+REACT_APP_SHOW_DETAIL: 是否展示调用详情,true 或 false
+REACT_APP_BASE_URL: 你的NewAPI项目地址
+REACT_APP_SHOW_ICONGITHUB: 是否展示Github图标,true 或 false
 ```
-cp .env.example .env
-```
-修改.env文件中的配置
+
+例如如下配置:
 ```
-# 展示使用明细
+# 展示令牌信息
+REACT_APP_SHOW_BALANCE=true
+
+# 展示调用详情
 REACT_APP_SHOW_DETAIL=true
 
-# 展示余额
-REACT_APP_SHOW_BALANCE=true
+# NewAPI的BaseURL(支持多个NewAPI站点聚合查询,键值对中的键为站点名称,值为站点的URL)
+REACT_APP_BASE_URL={"server1": "https://api.server1.com", "server2": "https://api.server2.com"}
 
-# BaseURL 结尾不要带/
-REACT_APP_BASE_URL=https://nekoapi.com
+# 展示GitHub图标
+REACT_APP_SHOW_ICONGITHUB=true
 ```
 
+3. 部署完毕后,即可开始使用;
+4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
+
+### 二次开发
+复制.env.example文件为.env,根据自己需求配置env文件中的环境变量。

BIN
public/favicon.ico


+ 1 - 1
public/index.html

@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8" />
     <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="viewport" content="user-scalable=yes">
     <meta name="theme-color" content="#000000" />
     <meta
       name="description"

BIN
public/logo192.png


BIN
public/logo512.png


+ 30 - 1
src/components/HeaderBar.js

@@ -1,7 +1,15 @@
 import { Nav, Typography } from '@douyinfe/semi-ui';
 import { IconTag } from '@douyinfe/semi-icons-lab';
+import {
+  IconGithubLogo,
+  IconMoon,
+  IconSun,
+} from '@douyinfe/semi-icons';
+import { useSetTheme, useTheme } from '../context/Theme';
 
 const HeaderBar = () => {
+  const theme = useTheme();
+  const setTheme = useSetTheme();
 
   return (
     <div
@@ -25,7 +33,28 @@ const HeaderBar = () => {
           }
         }
         footer={
-          <Typography.Text link={{ href: 'https://github.com/Calcium-Ion/neko-api-key-tool' }}>Copyright © 2024 NewAPI. All Rights Reserved.</Typography.Text>
+          <>
+            {theme === 'dark' ? (
+              <IconSun
+                size="large"
+                style={{ cursor: 'pointer', marginRight: '10px', color: 'var(--semi-color-text-2)' }}
+                onClick={() => setTheme(false)}
+              />
+            ) : (
+              <IconMoon
+                size="large"
+                style={{ cursor: 'pointer', marginRight: '10px', color: 'var(--semi-color-text-2)' }}
+                onClick={() => setTheme(true)}
+              />
+            )}
+            {process.env.REACT_APP_SHOW_ICONGITHUB === "true" && (
+              <IconGithubLogo
+                size="large"
+                style={{ cursor: 'pointer', marginRight: '10px', color: 'var(--semi-color-text-2)' }}
+                onClick={() => window.open('https://github.com/Calcium-Ion/neko-api-key-tool', '_blank')}
+              />
+            )}
+          </>
         }
       />
     </div>

+ 159 - 125
src/components/LogsTable.js

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Button, Input, Typography, Table, Tag, Spin, Card, Collapse, Toast, Space } from '@douyinfe/semi-ui';
+import { Button, Input, Typography, Table, Tag, Spin, Card, Collapse, Toast, Space, Tabs } from '@douyinfe/semi-ui';
 import { IconSearch, IconCopy, IconDownload } from '@douyinfe/semi-icons';
 import { API, timestamp2string, copy } from '../helpers';
 import { stringToColor } from '../helpers/render';
@@ -11,6 +11,7 @@ import Papa from 'papaparse';
 
 const { Text } = Typography;
 const { Panel } = Collapse;
+const { TabPane } = Tabs;
 
 function renderTimestamp(timestamp) {
     return timestamp2string(timestamp);
@@ -27,87 +28,87 @@ function renderIsStream(bool) {
 function renderUseTime(type) {
     const time = parseInt(type);
     if (time < 101) {
-        return <Tag color="green" size="large"> {time} s </Tag>;
+        return <Tag color="green" size="large"> {time}  </Tag>;
     } else if (time < 300) {
-        return <Tag color="orange" size="large"> {time} s </Tag>;
+        return <Tag color="orange" size="large"> {time}  </Tag>;
     } else {
-        return <Tag color="red" size="large"> {time} s </Tag>;
+        return <Tag color="red" size="large"> {time}  </Tag>;
     }
 }
 
 const LogsTable = () => {
-    const [key, setKey] = useState('');
-    const [balance, setBalance] = useState(0);
-    const [usage, setUsage] = useState(0);
-    const [accessdate, setAccessDate] = useState(0);
-    const [logs, setLogs] = useState([]);
+    const [apikey, setAPIKey] = useState('');
+    const [activeTabKey, setActiveTabKey] = useState('');
+    const [tabData, setTabData] = useState({});
     const [loading, setLoading] = useState(false);
     const [activeKeys, setActiveKeys] = useState([]);
-    const [tokenValid, setTokenValid] = useState(false);
-    // const [quotaPerUnit, setQuotaPerUnit] = useState('未知');
+    const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
+    const [baseUrl, setBaseUrl] = useState('');
+    const baseUrls = JSON.parse(process.env.REACT_APP_BASE_URL);  // 解析环境变量
 
-    // const fetchQuotaPerUnit = async () => {
-    //     try {
-    //         const res = await API.get(`${process.env.REACT_APP_BASE_URL}/api/status`);
-    //         const { success, data } = res.data;
-    //         if (success) {
-    //             setQuotaPerUnit(data.quota_per_unit);
-    //         } else {
-    //             throw new Error('获取站点汇率失败');
-    //         }
-    //     } catch (e) {
-    //         Toast.error(e.message);
-    //     }
-    // };
+    useEffect(() => {
+        // 默认设置第一个地址为baseUrl
+        const firstKey = Object.keys(baseUrls)[0];
+        setActiveTabKey(firstKey);
+        setBaseUrl(baseUrls[firstKey]);
+    }, []);
 
-    // useEffect(() => {
-    //     fetchQuotaPerUnit();
-    // }, []);
+    const handleTabChange = (key) => {
+        setActiveTabKey(key);
+        setBaseUrl(baseUrls[key]);
+    };
 
-    const resetData = () => {
-        setBalance("未知");
-        setUsage("未知");
-        setAccessDate("未知");
-        setLogs([]);
-        setTokenValid(false);
+    const resetData = (key) => {
+        setTabData((prevData) => ({
+            ...prevData,
+            [key]: {
+                balance: 0,
+                usage: 0,
+                accessdate: "未知",
+                logs: [],
+                tokenValid: false,
+            }
+        }));
     };
 
     const fetchData = async () => {
-        if (key === '') {
+        if (apikey === '') {
             Toast.warning('请先输入令牌,再进行查询');
             return;
         }
         // 检查令牌格式
-        if (!/^sk-[a-zA-Z0-9]{48}$/.test(key)) {
+        if (!/^sk-[a-zA-Z0-9]{48}$/.test(apikey)) {
             Toast.error('令牌格式非法!');
             return;
         }
         setLoading(true);
         try {
+            let newTabData = { ...tabData[activeTabKey], balance: 0, usage: 0, accessdate: 0, logs: [], tokenValid: false };
+
             if (process.env.REACT_APP_SHOW_BALANCE === "true") {
-                const subscription = await API.get(`${process.env.REACT_APP_BASE_URL}/v1/dashboard/billing/subscription`, {
-                    headers: { Authorization: `Bearer ${key}` },
+                const subscription = await API.get(`${baseUrl}/v1/dashboard/billing/subscription`, {
+                    headers: { Authorization: `Bearer ${apikey}` },
                 });
                 const subscriptionData = subscription.data;
-                setBalance(subscriptionData.hard_limit_usd);
-                setTokenValid(true);
+                newTabData.balance = subscriptionData.hard_limit_usd;
+                newTabData.tokenValid = true;
 
                 let now = new Date();
                 let start = new Date(now.getTime() - 100 * 24 * 3600 * 1000);
-                let start_date = start.getFullYear() + '-' + (start.getMonth() + 1) + '-' + start.getDate();
-                let end_date = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate();
-                const res = await API.get(`${process.env.REACT_APP_BASE_URL}/v1/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`, {
-                    headers: { Authorization: `Bearer ${key}` },
+                let start_date = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
+                let end_date = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
+                const res = await API.get(`${baseUrl}/v1/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`, {
+                    headers: { Authorization: `Bearer ${apikey}` },
                 });
                 const data = res.data;
-                setUsage(data.total_usage / 100);
+                newTabData.usage = data.total_usage / 100;
             }
 
             if (process.env.REACT_APP_SHOW_DETAIL === "true") {
-                const logRes = await API.get(`${process.env.REACT_APP_BASE_URL}/api/log/token?key=${key}`);
+                const logRes = await API.get(`${baseUrl}/api/log/token?key=${apikey}`);
                 const { success, message, data: logData } = logRes.data;
                 if (success) {
-                    setLogs(logData.reverse());
+                    newTabData.logs = logData.reverse();
                     let quota = 0;
                     for (let i = 0; i < logData.length; i++) {
                         quota += logData[i].quota;
@@ -117,10 +118,15 @@ const LogsTable = () => {
                     Toast.error('查询调用详情失败,请输入正确的令牌');
                 }
             }
+
+            setTabData((prevData) => ({
+                ...prevData,
+                [activeTabKey]: newTabData,
+            }));
             setLoading(false);
         } catch (e) {
             Toast.error("查询失败,请输入正确的令牌");
-            resetData(); // 如果发生错误,重置所有数据为默认值
+            resetData(activeTabKey); // 如果发生错误,重置所有数据为默认值
             setLoading(false);
             return;
         }
@@ -191,7 +197,7 @@ const LogsTable = () => {
             title: '用时',
             dataIndex: 'use_time',
             render: (text, record, index) => {
-                return (
+                return record.model_name.startsWith('mj_') ? null : (
                     <div>
                         <Space>
                             {renderUseTime(text)}
@@ -206,7 +212,9 @@ const LogsTable = () => {
             title: '提示',
             dataIndex: 'prompt_tokens',
             render: (text, record, index) => {
-                return record.type === 0 || record.type === 2 ? <div>{<span> {text} </span>}</div> : <></>;
+                return record.model_name.startsWith('mj_') ? null : (
+                    record.type === 0 || record.type === 2 ? <div>{<span> {text} </span>}</div> : <></>
+                );
             },
             sorter: (a, b) => a.prompt_tokens - b.prompt_tokens,
         },
@@ -292,15 +300,19 @@ const LogsTable = () => {
 
     const copyTokenInfo = (e) => {
         e.stopPropagation();
-        const info = `令牌总额: ${balance === 100000000 ? '无限' : `$${balance.toFixed(3)}`}
-剩余额度: ${balance === 100000000 ? '无限制' : `$${(balance - usage).toFixed(3)}`}
-已用额度: ${balance === 100000000 ? '不进行计算' : `$${usage.toFixed(3)}`}
+        const activeTabData = tabData[activeTabKey] || {};
+        const { balance, usage, accessdate } = activeTabData;
+        const info = `令牌总额: ${balance === 100000000 ? '无限' : `${balance.toFixed(3)}`}
+剩余额度: ${balance === 100000000 ? '无限制' : `${(balance - usage).toFixed(3)}`}
+已用额度: ${balance === 100000000 ? '不进行计算' : `${usage.toFixed(3)}`}
 有效期至: ${accessdate === 0 ? '永不过期' : renderTimestamp(accessdate)}`;
         copyText(info);
     };
 
     const exportCSV = (e) => {
         e.stopPropagation();
+        const activeTabData = tabData[activeTabKey] || { logs: [] };
+        const { logs } = activeTabData;
         const csvData = logs.map(log => ({
             '时间': renderTimestamp(log.created_at),
             '模型': log.model_name,
@@ -321,19 +333,24 @@ const LogsTable = () => {
         document.body.removeChild(link);
     };
 
-    return (
-        <Card>
-            <div style={{ marginBottom: 16 }}>
+    const activeTabData = tabData[activeTabKey] || { logs: [], balance: 0, usage: 0, accessdate: "未知", tokenValid: false };
+
+    const renderContent = () => (
+        <>
+            <Card style={{ marginTop: 24 }}>
                 <Input
                     showClear
-                    value={key}
-                    onChange={(value) => setKey(value)}
-                    placeholder="请输入要查询的令牌sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+                    value={apikey}
+                    onChange={(value) => setAPIKey(value)}
+                    placeholder="请输入要查询的令牌 sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                     prefix={<IconSearch />}
                     suffix={
                         <Button
+                            type='primary'
+                            theme="solid"
                             onClick={fetchData}
                             loading={loading}
+                            disabled={apikey === ''}
                         >
                             查询
                         </Button>
@@ -344,74 +361,91 @@ const LogsTable = () => {
                         }
                     }}
                 />
-            </div>
-            <Collapse activeKey={activeKeys} onChange={(keys) => {
-                if (keys.length === 0) {
-                    setActiveKeys(['1', '2']);
-                } else {
-                    setActiveKeys(keys);
-                }
-            }}>
-                {process.env.REACT_APP_SHOW_BALANCE === "true" && (
-                    <Panel
-                        header="令牌信息"
-                        itemKey="1"
-                        extra={
-                            <Button icon={<IconCopy />} theme='borderless' type='primary' onClick={(e) => copyTokenInfo(e)} disabled={!tokenValid}>
-                                复制令牌信息
-                            </Button>
-                        }
-                        disabled={!tokenValid}
-                    >
-                        <Spin spinning={loading}>
-                            <div style={{ marginBottom: 16 }}>
-                                <Text type="secondary">
-                                    令牌总额:{balance === 100000000 ? "无限" : balance === "未知" ? "未知" : `$${balance.toFixed(3)}`}
-                                </Text>
-                                <br /><br />
-                                <Text type="secondary">
-                                    剩余额度:{balance === 100000000 ? "无限制" : balance === "未知" || usage === "未知" ? "未知" : `$${(balance - usage).toFixed(3)}`}
-                                </Text>
-                                <br /><br />
-                                <Text type="secondary">
-                                    已用额度:{balance === 100000000 ? "不进行计算" : usage === "未知" ? "未知" : `$${usage.toFixed(3)}`}
-                                </Text>
-                                <br /><br />
-                                <Text type="secondary">
-                                    有效期至:{accessdate === 0 ? '永不过期' : accessdate === "未知" ? '未知' : renderTimestamp(accessdate)}
-                                </Text>
-                            </div>
-                        </Spin>
-                    </Panel>
-                )}
-                {process.env.REACT_APP_SHOW_DETAIL === "true" && (
-                    <Panel
-                        header="调用详情"
-                        itemKey="2"
-                        extra={
-                            <div style={{ display: 'flex', alignItems: 'center' }}>
-                                <Tag color='green' style={{ marginRight: 5 }}>计算汇率:$1 = 50 0000 tokens</Tag>
-                                <Button icon={<IconDownload />} theme='borderless' type='primary' onClick={(e) => exportCSV(e)} disabled={!tokenValid || logs.length === 0}>
-                                    导出为CSV文件
+            </Card>
+            <Card style={{ marginTop: 24 }}>
+                <Collapse activeKey={activeKeys} onChange={(keys) => setActiveKeys(keys)}>
+                    {process.env.REACT_APP_SHOW_BALANCE === "true" && (
+                        <Panel
+                            header="令牌信息"
+                            itemKey="1"
+                            extra={
+                                <Button icon={<IconCopy />} theme='borderless' type='primary' onClick={(e) => copyTokenInfo(e)} disabled={!activeTabData.tokenValid}>
+                                    复制令牌信息
                                 </Button>
-                            </div>
-                        }
-                        disabled={!tokenValid}
-                    >
-                        <Spin spinning={loading}>
-                            <Table
-                                columns={columns}
-                                dataSource={logs}
-                                pagination={{
-                                    pageSize: ITEMS_PER_PAGE,
-                                    hideOnSinglePage: true,
-                                }}
-                            />
-                        </Spin>
-                    </Panel>
-                )}
-            </Collapse>
-        </Card>
+                            }
+                        >
+                            <Spin spinning={loading}>
+                                <div style={{ marginBottom: 16 }}>
+                                    <Text type="secondary">
+                                        令牌总额:{activeTabData.balance === 100000000 ? "无限" : activeTabData.balance === "未知" || activeTabData.balance === undefined ? "未知" : `${activeTabData.balance.toFixed(3)}`}
+                                    </Text>
+                                    <br /><br />
+                                    <Text type="secondary">
+                                        剩余额度:{activeTabData.balance === 100000000 ? "无限制" : activeTabData.balance === "未知" || activeTabData.usage === "未知" || activeTabData.balance === undefined || activeTabData.usage === undefined ? "未知" : `${(activeTabData.balance - activeTabData.usage).toFixed(3)}`}
+                                    </Text>
+                                    <br /><br />
+                                    <Text type="secondary">
+                                        已用额度:{activeTabData.balance === 100000000 ? "不进行计算" : activeTabData.usage === "未知" || activeTabData.usage === undefined ? "未知" : `${activeTabData.usage.toFixed(3)}`}
+                                    </Text>
+                                    <br /><br />
+                                    <Text type="secondary">
+                                        有效期至:{activeTabData.accessdate === 0 ? '永不过期' : activeTabData.accessdate === "未知" ? '未知' : renderTimestamp(activeTabData.accessdate)}
+                                    </Text>
+                                </div>
+                            </Spin>
+                        </Panel>
+                    )}
+                    {process.env.REACT_APP_SHOW_DETAIL === "true" && (
+                        <Panel
+                            header="调用详情"
+                            itemKey="2"
+                            extra={
+                                <div style={{ display: 'flex', alignItems: 'center' }}>
+                                    <Tag shape='circle' color='green' style={{ marginRight: 5 }}>计算汇率:$1 = 50 0000 tokens</Tag>
+                                    <Button icon={<IconDownload />} theme='borderless' type='primary' onClick={(e) => exportCSV(e)} disabled={!activeTabData.tokenValid || activeTabData.logs.length === 0}>
+                                        导出为CSV文件
+                                    </Button>
+                                </div>
+                            }
+                        >
+                            <Spin spinning={loading}>
+                                <Table
+                                    columns={columns}
+                                    dataSource={activeTabData.logs}
+                                    pagination={{
+                                        pageSize: pageSize,
+                                        hideOnSinglePage: true,
+                                        showSizeChanger: true,
+                                        pageSizeOpts: [10, 20, 50, 100],
+                                        onPageSizeChange: (pageSize) => setPageSize(pageSize),
+                                        showTotal: (total) => `共 ${total} 条`,
+                                        showQuickJumper: true,
+                                        total: activeTabData.logs.length,
+                                        style: { marginTop: 12 },
+                                    }}
+                                />
+                            </Spin>
+                        </Panel>
+                    )}
+                </Collapse>
+            </Card>
+        </>
+    );
+
+    return (
+        <>
+            {Object.keys(baseUrls).length > 1 ? (
+                <Tabs type="line" onChange={handleTabChange}>
+                    {Object.entries(baseUrls).map(([key, url]) => (
+                        <TabPane tab={key} itemKey={key} key={key}>
+                            {renderContent()}
+                        </TabPane>
+                    ))}
+                </Tabs>
+            ) : (
+                renderContent()
+            )}
+        </>
     );
 };
 

+ 46 - 0
src/context/Theme/index.js

@@ -0,0 +1,46 @@
+import { createContext, useCallback, useContext, useState, useEffect } 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') || 'light';
+    } catch {
+      return 'light';
+    }
+  });
+
+  const setTheme = useCallback((input) => {
+    const newTheme = input ? 'dark' : 'light';
+    _setTheme(newTheme);
+
+    const body = document.body;
+    if (newTheme === 'light') {
+      body.removeAttribute('theme-mode');
+      localStorage.setItem('theme-mode', 'light');
+    } else {
+      body.setAttribute('theme-mode', 'dark');
+      localStorage.setItem('theme-mode', 'dark');
+    }
+  }, []);
+
+  useEffect(() => {
+    const body = document.body;
+    if (theme === 'dark') {
+      body.setAttribute('theme-mode', 'dark');
+    } else {
+      body.removeAttribute('theme-mode');
+    }
+  }, [theme]);
+
+  return (
+    <SetThemeContext.Provider value={setTheme}>
+      <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
+    </SetThemeContext.Provider>
+  );
+};

+ 12 - 9
src/index.css

@@ -1,14 +1,8 @@
-/* body {
-  margin: 0;
-  padding-top: 55px;
-  overflow-y: scroll;
-  font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  scrollbar-width: none;
+body {
+  background-color: var(--semi-color-bg-0);
 }
 
-body::-webkit-scrollbar {
+/* body::-webkit-scrollbar {
   display: none;
 }
 
@@ -33,3 +27,12 @@ code {
     display: none !important;
   }
 } */
+
+.semi-card {
+  border-radius: 20px !important;
+}
+
+.semi-button,
+.semi-input-wrapper {
+  border-radius: 8px !important;
+}

+ 11 - 8
src/index.js

@@ -6,20 +6,23 @@ import HeaderBar from './components/HeaderBar';
 import reportWebVitals from './reportWebVitals';
 import 'semantic-ui-css/semantic.min.css';
 import './index.css';
+import { ThemeProvider } from './context/Theme';
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
 const { Sider, Content, Header } = Layout;
 root.render(
-  <Layout>
-    <Header>
-      <HeaderBar />
-    </Header>
+  <ThemeProvider>
     <Layout>
-      <Content>
-        <App />
-      </Content>
+      <Header>
+        <HeaderBar />
+      </Header>
+      <Layout>
+        <Content style={{ padding: 24 }}>
+          <App />
+        </Content>
+      </Layout>
     </Layout>
-  </Layout>
+  </ThemeProvider>
 );
 
 // If you want to start measuring performance in your app, pass a function