@@ -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 ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
40
web/src/components/PageLayout.js
Normal file
40
web/src/components/PageLayout.js
Normal 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;
|
||||||
@@ -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);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
57
web/src/context/Style/index.js
Normal file
57
web/src/context/Style/index.js
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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/`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user