Merge pull request #609 from Calcium-Ion/mobile

feat: 界面美化
This commit is contained in:
Calcium-Ion
2024-12-11 17:33:32 +08:00
committed by GitHub
8 changed files with 178 additions and 79 deletions

View File

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

View File

@@ -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;

View File

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

View File

@@ -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>
);
};

View File

@@ -17,6 +17,10 @@ body {
flex-direction: column; 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) { @media only screen and (max-width: 767px) {
.semi-table-tbody, .semi-table-tbody,
.semi-table-row, .semi-table-row,
@@ -39,6 +43,10 @@ body {
row-gap: 3px; row-gap: 3px;
column-gap: 10px; column-gap: 10px;
} }
.semi-navigation-horizontal .semi-navigation-header {
margin-right: 0;
}
} }
.semi-table-tbody > .semi-table-row > .semi-table-row-cell { .semi-table-tbody > .semi-table-row > .semi-table-row-cell {

View File

@@ -13,6 +13,8 @@ import { Layout } from '@douyinfe/semi-ui';
import SiderBar from './components/SiderBar'; import SiderBar from './components/SiderBar';
import { ThemeProvider } from './context/Theme'; import { ThemeProvider } from './context/Theme';
import FooterBar from './components/Footer'; import FooterBar from './components/Footer';
import { StyleProvider } from './context/Style/index.js';
import PageLayout from './components/PageLayout.js';
// initialization // initialization
@@ -24,27 +26,9 @@ root.render(
<UserProvider> <UserProvider>
<BrowserRouter> <BrowserRouter>
<ThemeProvider> <ThemeProvider>
<Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <StyleProvider>
<Header> <PageLayout/>
<HeaderBar /> </StyleProvider>
</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>
</ThemeProvider> </ThemeProvider>
</BrowserRouter> </BrowserRouter>
</UserProvider> </UserProvider>

View File

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

View File

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