Просмотр исходного кода

Merge pull request #609 from Calcium-Ion/mobile

feat: 界面美化
Calcium-Ion 1 год назад
Родитель
Сommit
e5dc21d56b

+ 56 - 41
web/src/components/HeaderBar.js

@@ -9,17 +9,19 @@ import '../index.css';
 import fireworks from 'react-fireworks';
 
 import {
+  IconClose,
   IconHelpCircle,
   IconHome,
-  IconHomeStroked,
-  IconKey,
+  IconHomeStroked, IconIndentLeft,
+  IconKey, IconMenu,
   IconNoteMoneyStroked,
   IconPriceTag,
   IconUser
 } from '@douyinfe/semi-icons';
-import { Avatar, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui';
+import { Avatar, Button, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui';
 import { stringToColor } from '../helpers/render';
 import Text from '@douyinfe/semi-ui/lib/es/typography/text';
+import { StyleContext } from '../context/Style/index.js';
 
 // HeaderBar Buttons
 let headerButtons = [
@@ -31,21 +33,6 @@ let headerButtons = [
   },
 ];
 
-let buttons = [
-  {
-    text: '首页',
-    itemKey: 'home',
-    to: '/',
-    // icon: <IconHomeStroked />,
-  },
-  // {
-  //   text: 'Playground',
-  //   itemKey: 'playground',
-  //   to: '/playground',
-  //   // icon: <IconNoteMoneyStroked />,
-  // },
-];
-
 if (localStorage.getItem('chat_link')) {
   headerButtons.splice(1, 0, {
     name: '聊天',
@@ -56,9 +43,9 @@ if (localStorage.getItem('chat_link')) {
 
 const HeaderBar = () => {
   const [userState, userDispatch] = useContext(UserContext);
+  const [styleState, styleDispatch] = useContext(StyleContext);
   let navigate = useNavigate();
 
-  const [showSidebar, setShowSidebar] = useState(false);
   const systemName = getSystemName();
   const logo = getLogo();
   const currentDate = new Date();
@@ -69,8 +56,20 @@ const HeaderBar = () => {
       currentDate.getDate() >= 9 &&
       currentDate.getDate() <= 24);
 
+  let buttons = [
+    {
+      text: '首页',
+      itemKey: 'home',
+      to: '/',
+    },
+    {
+      text: '控制台',
+      itemKey: 'detail',
+      to: '/',
+    },
+  ];
+
   async function logout() {
-    setShowSidebar(false);
     await API.get('/api/user/logout');
     showSuccess('注销成功!');
     userDispatch({ type: 'logout' });
@@ -108,36 +107,54 @@ const HeaderBar = () => {
         <div style={{ width: '100%' }}>
           <Nav
             mode={'horizontal'}
-            // bodyStyle={{ height: 100 }}
             renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
               const routerMap = {
                 about: '/about',
                 login: '/login',
                 register: '/register',
+                detail: '/detail',
                 home: '/',
               };
               return (
-                <Link
-                  style={{ textDecoration: 'none' }}
-                  to={routerMap[props.itemKey]}
-                >
-                  {itemElement}
-                </Link>
+                <div onClick={(e) => {
+                  if (props.itemKey === 'home') {
+                    styleDispatch({ type: 'SET_SIDER', payload: true });
+                  } else {
+                    styleDispatch({ type: 'SET_SIDER', payload: false });
+                  }
+                }}>
+                  <Link
+                    className="header-bar-text"
+                    style={{ textDecoration: 'none' }}
+                    to={routerMap[props.itemKey]}
+                  >
+                    {itemElement}
+                  </Link>
+                </div>
               );
             }}
             selectedKeys={[]}
             // items={headerButtons}
             onSelect={(key) => {}}
-            header={isMobile()?{
+            header={styleState.isMobile?{
               logo: (
-                <img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
+                <>
+                  {
+                    styleState.showSider ?
+                      <Button icon={<IconMenu />} theme="light" aria-label="展开侧边栏" onClick={
+                        () => styleDispatch({ type: 'SET_SIDER', payload: false })
+                      } />:
+                      <Button icon={<IconIndentLeft />} theme="light" aria-label="关闭侧边栏" onClick={
+                        () => styleDispatch({ type: 'SET_SIDER', payload: true })
+                      } />
+                  }
+                </>
               ),
             }:{
               logo: (
                 <img src={logo} alt='logo' />
               ),
               text: systemName,
-
             }}
             items={buttons}
             footer={
@@ -159,17 +176,15 @@ const HeaderBar = () => {
                 )}
                 <Nav.Item itemKey={'about'} icon={<IconHelpCircle />} />
                 <>
-                {!isMobile() && (
-                    <Switch
-                      checkedText='🌞'
-                      size={'large'}
-                      checked={theme === 'dark'}
-                      uncheckedText='🌙'
-                      onChange={(checked) => {
-                        setTheme(checked);
-                      }}
-                    />
-                  )}
+                  <Switch
+                    checkedText='🌞'
+                    size={'large'}
+                    checked={theme === 'dark'}
+                    uncheckedText='🌙'
+                    onChange={(checked) => {
+                      setTheme(checked);
+                    }}
+                  />
                 </>
                 {userState.user ? (
                   <>

+ 40 - 0
web/src/components/PageLayout.js

@@ -0,0 +1,40 @@
+import HeaderBar from './HeaderBar.js';
+import { Layout } from '@douyinfe/semi-ui';
+import SiderBar from './SiderBar.js';
+import App from '../App.js';
+import FooterBar from './Footer.js';
+import { ToastContainer } from 'react-toastify';
+import React, { useContext } from 'react';
+import { StyleContext } from '../context/Style/index.js';
+const { Sider, Content, Header, Footer } = Layout;
+
+
+const PageLayout = () => {
+  const [styleState, styleDispatch] = useContext(StyleContext);
+
+  return (
+    <Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
+      <Header>
+        <HeaderBar />
+      </Header>
+      <Layout style={{ flex: 1, overflow: 'hidden' }}>
+        <Sider>
+          {styleState.showSider ? null : <SiderBar />}
+        </Sider>
+        <Layout>
+          <Content
+            style={{ overflowY: 'auto', padding: '24px' }}
+          >
+            <App />
+          </Content>
+          <Layout.Footer>
+            <FooterBar></FooterBar>
+          </Layout.Footer>
+        </Layout>
+      </Layout>
+      <ToastContainer />
+    </Layout>
+  )
+}
+
+export default PageLayout;

+ 3 - 15
web/src/components/SiderBar.js

@@ -31,14 +31,15 @@ import { Avatar, Dropdown, Layout, Nav, Switch } from '@douyinfe/semi-ui';
 import { setStatusData } from '../helpers/data.js';
 import { stringToColor } from '../helpers/render.js';
 import { useSetTheme, useTheme } from '../context/Theme/index.js';
+import { StyleContext } from '../context/Style/index.js';
 
 // HeaderBar Buttons
 
 const SiderBar = () => {
-  const [userState, userDispatch] = useContext(UserContext);
+  const [styleState, styleDispatch] = useContext(StyleContext);
   const [statusState, statusDispatch] = useContext(StatusContext);
   const defaultIsCollapsed =
-    isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true';
+    localStorage.getItem('default_collapse_sidebar') === 'true';
 
   const [selectedKeys, setSelectedKeys] = useState(['home']);
   const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed);
@@ -196,7 +197,6 @@ const SiderBar = () => {
   useEffect(() => {
     loadStatus().then(() => {
       setIsCollapsed(
-        isMobile() ||
           localStorage.getItem('default_collapse_sidebar') === 'true',
       );
     });
@@ -239,7 +239,6 @@ const SiderBar = () => {
       <Nav
         style={{ maxWidth: 220, height: '100%' }}
         defaultIsCollapsed={
-          isMobile() ||
           localStorage.getItem('default_collapse_sidebar') === 'true'
         }
         isCollapsed={isCollapsed}
@@ -284,17 +283,6 @@ const SiderBar = () => {
         }}
         footer={
           <>
-            {isMobile() && (
-              <Switch
-                checkedText='🌞'
-                size={'small'}
-                checked={theme === 'dark'}
-                uncheckedText='🌙'
-                onChange={(checked) => {
-                  setTheme(checked);
-                }}
-              />
-            )}
           </>
         }
       >

+ 57 - 0
web/src/context/Style/index.js

@@ -0,0 +1,57 @@
+// contexts/User/index.jsx
+
+import React, { useState, useEffect } from 'react';
+import { isMobile } from '../../helpers/index.js';
+
+export const StyleContext = React.createContext({
+  dispatch: () => null,
+});
+
+export const StyleProvider = ({ children }) => {
+  const [state, setState] = useState({
+    isMobile: false,
+    showSider: false,
+  });
+
+  const dispatch = (action) => {
+    if ('type' in action) {
+      switch (action.type) {
+        case 'TOGGLE_SIDER':
+          setState(prev => ({ ...prev, showSider: !prev.showSider }));
+          break;
+        case 'SET_SIDER':
+          setState(prev => ({ ...prev, showSider: action.payload }));
+          break;
+        case 'SET_MOBILE':
+          setState(prev => ({ ...prev, isMobile: action.payload }));
+          break;
+        default:
+          setState(prev => ({ ...prev, ...action }));
+      }
+    } else {
+      setState(prev => ({ ...prev, ...action }));
+    }
+  };
+
+  useEffect(() => {
+    const updateIsMobile = () => {
+      dispatch({ type: 'SET_MOBILE', payload: isMobile() });
+    };
+
+    updateIsMobile();
+
+    // Optionally, add event listeners to handle window resize
+    window.addEventListener('resize', updateIsMobile);
+
+    // Cleanup event listener on component unmount
+    return () => {
+      window.removeEventListener('resize', updateIsMobile);
+    };
+  }, []);
+
+  return (
+    <StyleContext.Provider value={[state, dispatch]}>
+      {children}
+    </StyleContext.Provider>
+  );
+};

+ 8 - 0
web/src/index.css

@@ -17,6 +17,10 @@ body {
   flex-direction: column;
 }
 
+#root > section > header > section > div > div > div > div.semi-navigation-header-list-outer > div.semi-navigation-list-wrapper > ul > div > a > li > span{
+  font-weight: 600 !important;
+}
+
 @media only screen and (max-width: 767px) {
   .semi-table-tbody,
   .semi-table-row,
@@ -39,6 +43,10 @@ body {
     row-gap: 3px;
     column-gap: 10px;
   }
+
+  .semi-navigation-horizontal .semi-navigation-header {
+    margin-right: 0;
+  }
 }
 
 .semi-table-tbody > .semi-table-row > .semi-table-row-cell {

+ 5 - 21
web/src/index.js

@@ -13,6 +13,8 @@ import { Layout } from '@douyinfe/semi-ui';
 import SiderBar from './components/SiderBar';
 import { ThemeProvider } from './context/Theme';
 import FooterBar from './components/Footer';
+import { StyleProvider } from './context/Style/index.js';
+import PageLayout from './components/PageLayout.js';
 
 // initialization
 
@@ -24,27 +26,9 @@ root.render(
       <UserProvider>
         <BrowserRouter>
           <ThemeProvider>
-            <Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
-              <Header>
-                <HeaderBar />
-              </Header>
-              <Layout style={{ flex: 1, overflow: 'hidden' }}>
-                <Sider>
-                  <SiderBar />
-                </Sider>
-                <Layout>
-                  <Content
-                    style={{ overflowY: 'auto', padding: '24px' }}
-                  >
-                    <App />
-                  </Content>
-                  <Layout.Footer>
-                    <FooterBar></FooterBar>
-                  </Layout.Footer>
-                </Layout>
-              </Layout>
-              <ToastContainer />
-            </Layout>
+            <StyleProvider>
+              <PageLayout/>
+            </StyleProvider>
           </ThemeProvider>
         </BrowserRouter>
       </UserProvider>

+ 2 - 0
web/src/pages/Home/index.js

@@ -3,11 +3,13 @@ import { Card, Col, Row } from '@douyinfe/semi-ui';
 import { API, showError, showNotice, timestamp2string } from '../../helpers';
 import { StatusContext } from '../../context/Status';
 import { marked } from 'marked';
+import { StyleContext } from '../../context/Style/index.js';
 
 const Home = () => {
   const [statusState] = useContext(StatusContext);
   const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
   const [homePageContent, setHomePageContent] = useState('');
+  const [styleState, styleDispatch] = useContext(StyleContext);
 
   const displayNotice = async () => {
     const res = await API.get('/api/notice');

+ 7 - 2
web/src/pages/Redemption/EditRedemption.js

@@ -7,7 +7,7 @@ import {
   showError,
   showSuccess,
 } from '../../helpers';
-import { renderQuotaWithPrompt } from '../../helpers/render';
+import { getQuotaPerUnit, renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
 import {
   AutoComplete,
   Button,
@@ -66,11 +66,16 @@ const EditRedemption = (props) => {
   }, [props.editingRedemption.id]);
 
   const submit = async () => {
-    if (!isEdit && inputs.name === '') return;
+    let name = inputs.name;
+    if (!isEdit && inputs.name === '') {
+      // set default name
+      name = '兑换码-' + renderQuota(quota);
+    }
     setLoading(true);
     let localInputs = inputs;
     localInputs.count = parseInt(localInputs.count);
     localInputs.quota = parseInt(localInputs.quota);
+    localInputs.name = name;
     let res;
     if (isEdit) {
       res = await API.put(`/api/redemption/`, {