Commit eec9046c by CaIon

更换前端组件库

parent f439c4d5
...@@ -54,7 +54,7 @@ func RelayMidjourneyImage(c *gin.Context) { ...@@ -54,7 +54,7 @@ func RelayMidjourneyImage(c *gin.Context) {
return return
} }
c.Header("Content-Type", "image/jpeg") c.Header("Content-Type", "image/jpeg")
//c.Header("Content-Length", string(rune(len(data)))) //c.HeaderBar("Content-Length", string(rune(len(data))))
c.Data(http.StatusOK, "image/jpeg", data) c.Data(http.StatusOK, "image/jpeg", data)
} }
...@@ -275,13 +275,13 @@ func relayMidjourneySubmit(c *gin.Context, relayMode int) *MidjourneyResponse { ...@@ -275,13 +275,13 @@ func relayMidjourneySubmit(c *gin.Context, relayMode int) *MidjourneyResponse {
Description: "create_request_failed", Description: "create_request_failed",
} }
} }
//req.Header.Set("Authorization", c.Request.Header.Get("Authorization")) //req.HeaderBar.Set("Authorization", c.Request.HeaderBar.Get("Authorization"))
req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
req.Header.Set("Accept", c.Request.Header.Get("Accept")) req.Header.Set("Accept", c.Request.Header.Get("Accept"))
//mjToken := "" //mjToken := ""
//if c.Request.Header.Get("Authorization") != "" { //if c.Request.HeaderBar.Get("Authorization") != "" {
// mjToken = strings.Split(c.Request.Header.Get("Authorization"), " ")[1] // mjToken = strings.Split(c.Request.HeaderBar.Get("Authorization"), " ")[1]
//} //}
req.Header.Set("mj-api-secret", strings.Split(c.Request.Header.Get("Authorization"), " ")[1]) req.Header.Set("mj-api-secret", strings.Split(c.Request.Header.Get("Authorization"), " ")[1])
// print request header // print request header
......
...@@ -381,7 +381,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { ...@@ -381,7 +381,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
if isStream && c.Request.Header.Get("Accept") == "" { if isStream && c.Request.Header.Get("Accept") == "" {
req.Header.Set("Accept", "text/event-stream") req.Header.Set("Accept", "text/event-stream")
} }
//req.Header.Set("Connection", c.Request.Header.Get("Connection")) //req.HeaderBar.Set("Connection", c.Request.HeaderBar.Get("Connection"))
resp, err = httpClient.Do(req) resp, err = httpClient.Do(req)
if err != nil { if err != nil {
return errorWrapper(err, "do_request_failed", http.StatusInternalServerError) return errorWrapper(err, "do_request_failed", http.StatusInternalServerError)
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@douyinfe/semi-ui": "^2.45.2",
"axios": "^0.27.2", "axios": "^0.27.2",
"history": "^5.3.0", "history": "^5.3.0",
"marked": "^4.1.1", "marked": "^4.1.1",
...@@ -14,7 +15,8 @@ ...@@ -14,7 +15,8 @@
"react-toastify": "^9.0.8", "react-toastify": "^9.0.8",
"react-turnstile": "^1.0.5", "react-turnstile": "^1.0.5",
"semantic-ui-css": "^2.5.0", "semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.3" "semantic-ui-react": "^2.1.3",
"usehooks-ts": "^2.9.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
...@@ -47,5 +49,5 @@ ...@@ -47,5 +49,5 @@
"singleQuote": true, "singleQuote": true,
"jsxSingleQuote": true "jsxSingleQuote": true
}, },
"proxy": "http://localhost:3000" "proxy": "https://nekoapi.com"
} }
...@@ -24,6 +24,7 @@ import EditRedemption from './pages/Redemption/EditRedemption'; ...@@ -24,6 +24,7 @@ import EditRedemption from './pages/Redemption/EditRedemption';
import TopUp from './pages/TopUp'; import TopUp from './pages/TopUp';
import Log from './pages/Log'; import Log from './pages/Log';
import Chat from './pages/Chat'; import Chat from './pages/Chat';
import {Layout} from "@douyinfe/semi-ui";
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About')); const About = lazy(() => import('./pages/About'));
...@@ -86,6 +87,7 @@ function App() { ...@@ -86,6 +87,7 @@ function App() {
}, []); }, []);
return ( return (
<Layout>
<Routes> <Routes>
<Route <Route
path='/' path='/'
...@@ -287,6 +289,7 @@ function App() { ...@@ -287,6 +289,7 @@ function App() {
<NotFound /> <NotFound />
} /> } />
</Routes> </Routes>
</Layout>
); );
} }
......
...@@ -44,8 +44,8 @@ const Footer = () => { ...@@ -44,8 +44,8 @@ const Footer = () => {
{systemName} {process.env.REACT_APP_VERSION}{' '} {systemName} {process.env.REACT_APP_VERSION}{' '}
</a> </a>
{' '} {' '}
<a href='https://github.com/songquanpeng' target='_blank'> <a href='https://github.com/Calcium-Ion' target='_blank'>
JustSong Calcium-Ion
</a>{' '} </a>{' '}
构建,源代码遵循{' '} 构建,源代码遵循{' '}
<a href='https://opensource.org/licenses/mit-license.php'> <a href='https://opensource.org/licenses/mit-license.php'>
......
import React, { useContext, useState } from 'react'; import React, {useContext, useState} from 'react';
import { Link, useNavigate } from 'react-router-dom'; import {Link, useNavigate} from 'react-router-dom';
import { UserContext } from '../context/User'; import {UserContext} from '../context/User';
import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react'; import {Button, Container, Icon, Menu, Segment} from 'semantic-ui-react';
import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers'; import {API, getLogo, getSystemName, isAdmin, isMobile, showSuccess} from '../helpers';
import '../index.css'; import '../index.css';
// Header Buttons import {
IconAt,
IconHistogram,
IconGift,
IconKey,
IconUser,
IconLayers,
IconHelpCircle,
IconCreditCard,
IconSemiLogo,
IconHome,
IconImage
} from '@douyinfe/semi-icons';
import {Nav, Avatar, Dropdown, Layout, Switch} from '@douyinfe/semi-ui';
import {stringToColor} from "../helpers/render";
// HeaderBar Buttons
let headerButtons = [ let headerButtons = [
{ {
name: '首页', text: '关于',
to: '/', itemKey: 'about',
icon: 'home'
},
{
name: '渠道',
to: '/channel',
icon: 'sitemap',
admin: true
},
{
name: '令牌',
to: '/token',
icon: 'key'
},
{
name: '兑换',
to: '/redemption',
icon: 'dollar sign',
admin: true
},
{
name: '充值',
to: '/topup',
icon: 'cart'
},
{
name: '用户',
to: '/user',
icon: 'user',
admin: true
},
{
name: '日志',
to: '/log',
icon: 'book'
},
{
name: 'Midjourney',
to: '/midjourney',
icon: 'images outline'
},
{
name: '设置',
to: '/setting',
icon: 'setting'
},
{
name: '关于',
to: '/about', to: '/about',
icon: 'info circle' icon: <IconHelpCircle/>
} },
]; ];
if (localStorage.getItem('chat_link')) { if (localStorage.getItem('chat_link')) {
...@@ -71,7 +40,7 @@ if (localStorage.getItem('chat_link')) { ...@@ -71,7 +40,7 @@ if (localStorage.getItem('chat_link')) {
}); });
} }
const Header = () => { const HeaderBar = () => {
const [userState, userDispatch] = useContext(UserContext); const [userState, userDispatch] = useContext(UserContext);
let navigate = useNavigate(); let navigate = useNavigate();
...@@ -83,7 +52,7 @@ const Header = () => { ...@@ -83,7 +52,7 @@ const Header = () => {
setShowSidebar(false); setShowSidebar(false);
await API.get('/api/user/logout'); await API.get('/api/user/logout');
showSuccess('注销成功!'); showSuccess('注销成功!');
userDispatch({ type: 'logout' }); userDispatch({type: 'logout'});
localStorage.removeItem('user'); localStorage.removeItem('user');
navigate('/login'); navigate('/login');
} }
...@@ -109,7 +78,7 @@ const Header = () => { ...@@ -109,7 +78,7 @@ const Header = () => {
} }
return ( return (
<Menu.Item key={button.name} as={Link} to={button.to}> <Menu.Item key={button.name} as={Link} to={button.to}>
<Icon name={button.icon} /> <Icon name={button.icon}/>
{button.name} {button.name}
</Menu.Item> </Menu.Item>
); );
...@@ -130,7 +99,7 @@ const Header = () => { ...@@ -130,7 +99,7 @@ const Header = () => {
borderTop: 'none', borderTop: 'none',
height: '51px' height: '51px'
} }
: { borderTop: 'none', height: '52px' } : {borderTop: 'none', height: '52px'}
} }
> >
<Container> <Container>
...@@ -138,21 +107,22 @@ const Header = () => { ...@@ -138,21 +107,22 @@ const Header = () => {
<img <img
src={logo} src={logo}
alt='logo' alt='logo'
style={{ marginRight: '0.75em' }} style={{marginRight: '0.75em'}}
/> />
<div style={{ fontSize: '20px' }}> <div style={{fontSize: '20px'}}>
<b>{systemName}</b> <b>{systemName}</b>
</div> </div>
</Menu.Item> </Menu.Item>
<Menu.Menu position='right'> <Menu.Menu position='right'>
<Menu.Item onClick={toggleSidebar}> <Menu.Item onClick={toggleSidebar}>
<Icon name={showSidebar ? 'close' : 'sidebar'} /> <Icon name={showSidebar ? 'close' : 'sidebar'}/>
</Menu.Item> </Menu.Item>
</Menu.Menu> </Menu.Menu>
</Container> </Container>
</Menu> </Menu>
{showSidebar ? ( {showSidebar ? (
<Segment style={{ marginTop: 0, borderTop: '0' }}> <Segment style={{marginTop: 0, borderTop: '0'}}>
<Menu secondary vertical style={{ width: '100%', margin: 0 }}> <Menu secondary vertical style={{ width: '100%', margin: 0 }}>
{renderButtons(true)} {renderButtons(true)}
<Menu.Item> <Menu.Item>
...@@ -180,49 +150,81 @@ const Header = () => { ...@@ -180,49 +150,81 @@ const Header = () => {
)} )}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
</Segment> </Segment>
) : ( ) : (
<></> <></>
)} )}
</> </>
); );
} }
const switchMode = (model) => {
const body = document.body;
if (!model) {
body.removeAttribute('theme-mode');
} else {
body.setAttribute('theme-mode', 'dark');
}
};
return ( return (
<> <>
<Menu borderless style={{ borderTop: 'none' }}> <Layout>
<Container> <div style={{width: '100%'}}>
<Menu.Item as={Link} to='/' className={'hide-on-mobile'}> <Nav
<img src={logo} alt='logo' style={{ marginRight: '0.75em' }} /> mode={'horizontal'}
<div style={{ fontSize: '20px' }}> // bodyStyle={{ height: 100 }}
<b>{systemName}</b> renderWrapper={({itemElement, isSubNav, isInSubNav, props}) => {
</div> const routerMap = {
</Menu.Item> about: "/about",
{renderButtons(false)} };
<Menu.Menu position='right'> return (
{userState.user ? ( <Link
<Dropdown style={{textDecoration: "none"}}
text={userState.user.username} to={routerMap[props.itemKey]}
pointing
className='link item'
> >
{itemElement}
</Link>
);
}}
selectedKeys={[]}
// items={headerButtons}
onSelect={key => console.log(key)}
footer={
<>
<Nav.Item itemKey={'about'} icon={<IconHelpCircle />} />
<Switch checkedText="🌞" size={'large'} uncheckedText="🌙" onChange={switchMode} />
{userState.user ?
<>
<Dropdown
position="bottomRight"
render={
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item onClick={logout}>注销</Dropdown.Item> <Dropdown.Item>退出</Dropdown.Item>
</Dropdown.Menu> </Dropdown.Menu>
}
>
<Avatar size="small" color={stringToColor(userState.user.username)} style={{ margin: 4 }}>
{userState.user.username[0]}
</Avatar>
<span>{userState.user.username}</span>
</Dropdown> </Dropdown>
) : ( </>
<Menu.Item :
name='登录' <>
as={Link} <Nav.Item itemKey={'login'} text={'登录'} icon={<IconKey />} />
to='/login' <Nav.Item itemKey={'register'} text={'注册'} icon={<IconUser />} />
className='btn btn-link' </>
/> }
)} </>
</Menu.Menu> }
</Container> >
</Menu> </Nav>
</div>
</Layout>
</> </>
); );
}; };
export default Header; export default HeaderBar;
import React, {useContext, useState} from 'react';
import {Link, useNavigate} from 'react-router-dom';
import {UserContext} from '../context/User';
import {Button, Container, Icon, Menu, Segment} from 'semantic-ui-react';
import {API, getLogo, getSystemName, isAdmin, isMobile, showSuccess} from '../helpers';
import '../index.css';
import {
IconAt,
IconHistogram,
IconGift,
IconKey,
IconUser,
IconLayers,
IconSetting,
IconCreditCard,
IconSemiLogo,
IconHome,
IconImage
} from '@douyinfe/semi-icons';
import {Nav, Avatar, Dropdown, Layout} from '@douyinfe/semi-ui';
// HeaderBar Buttons
let headerButtons = [
{
text: '首页',
itemKey: 'home',
to: '/',
icon: <IconHome/>
},
{
text: '渠道',
itemKey: 'channel',
to: '/channel',
icon: <IconLayers/>,
admin: true
},
{
text: '令牌',
itemKey: 'token',
to: '/token',
icon: <IconKey/>
},
{
text: '兑换码',
itemKey: 'redemption',
to: '/redemption',
icon: <IconGift/>,
admin: true
},
{
text: '钱包',
itemKey: 'topup',
to: '/topup',
icon: <IconCreditCard/>
},
{
text: '用户管理',
itemKey: 'user',
to: '/user',
icon: <IconUser/>,
admin: true
},
{
text: '日志',
itemKey: 'log',
to: '/log',
icon: <IconHistogram/>
},
{
text: '绘图',
itemKey: 'midjourney',
to: '/midjourney',
icon: <IconImage/>
},
{
text: '设置',
itemKey: 'setting',
to: '/setting',
icon: <IconSetting/>
},
// {
// text: '关于',
// itemKey: 'about',
// to: '/about',
// icon: <IconAt/>
// }
];
if (localStorage.getItem('chat_link')) {
headerButtons.splice(1, 0, {
name: '聊天',
to: '/chat',
icon: 'comments'
});
}
const HeaderBar = () => {
const [userState, userDispatch] = useContext(UserContext);
let navigate = useNavigate();
const [selectedKeys, setSelectedKeys] = useState(['home']);
const [showSidebar, setShowSidebar] = useState(false);
const systemName = getSystemName();
const logo = getLogo();
async function logout() {
setShowSidebar(false);
await API.get('/api/user/logout');
showSuccess('注销成功!');
userDispatch({type: 'logout'});
localStorage.removeItem('user');
navigate('/login');
}
const toggleSidebar = () => {
setShowSidebar(!showSidebar);
};
const renderButtons = (isMobile) => {
return headerButtons.map((button) => {
if (button.admin && !isAdmin()) return <></>;
if (isMobile) {
return (
<Menu.Item
onClick={() => {
navigate(button.to);
setShowSidebar(false);
}}
>
{button.name}
</Menu.Item>
);
}
return (
<Menu.Item key={button.name} as={Link} to={button.to}>
<Icon name={button.icon}/>
{button.name}
</Menu.Item>
);
});
};
if (isMobile()) {
return (
<>
<Menu
borderless
size='large'
style={
showSidebar
? {
borderBottom: 'none',
marginBottom: '0',
borderTop: 'none',
height: '51px'
}
: {borderTop: 'none', height: '52px'}
}
>
<Container>
<Menu.Item as={Link} to='/'>
<img
src={logo}
alt='logo'
style={{marginRight: '0.75em'}}
/>
<div style={{fontSize: '20px'}}>
<b>{systemName}</b>
</div>
</Menu.Item>
<Menu.Menu position='right'>
<Menu.Item onClick={toggleSidebar}>
<Icon name={showSidebar ? 'close' : 'sidebar'}/>
</Menu.Item>
</Menu.Menu>
</Container>
</Menu>
{showSidebar ? (
<Segment style={{marginTop: 0, borderTop: '0'}}>
{/*<Menu secondary vertical style={{ width: '100%', margin: 0 }}>*/}
{/* {renderButtons(true)}*/}
{/* <Menu.Item>*/}
{/* {userState.user ? (*/}
{/* <Button onClick={logout}>注销</Button>*/}
{/* ) : (*/}
{/* <>*/}
{/* <Button*/}
{/* onClick={() => {*/}
{/* setShowSidebar(false);*/}
{/* navigate('/login');*/}
{/* }}*/}
{/* >*/}
{/* 登录*/}
{/* </Button>*/}
{/* <Button*/}
{/* onClick={() => {*/}
{/* setShowSidebar(false);*/}
{/* navigate('/register');*/}
{/* }}*/}
{/* >*/}
{/* 注册*/}
{/* </Button>*/}
{/* </>*/}
{/* )}*/}
{/* </Menu.Item>*/}
{/*</Menu>*/}
</Segment>
) : (
<></>
)}
</>
);
}
return (
<>
<Layout>
<div style={{height: '100%'}}>
<Nav
// mode={'horizontal'}
// bodyStyle={{ height: 100 }}
selectedKeys={selectedKeys}
renderWrapper={({itemElement, isSubNav, isInSubNav, props}) => {
const routerMap = {
home: "/",
channel: "/channel",
token: "/token",
redemption: "/redemption",
topup: "/topup",
user: "/user",
log: "/log",
midjourney: "/midjourney",
setting: "/setting",
about: "/about",
};
return (
<Link
style={{textDecoration: "none"}}
to={routerMap[props.itemKey]}
>
{itemElement}
</Link>
);
}}
items={headerButtons}
onSelect={key => {
console.log(key);
setSelectedKeys([key.itemKey]);
}}
header={{
logo: <IconSemiLogo style={{height: '36px', fontSize: 36}}/>,
text: 'NekoAPI'
}}
// footer={{
// text: '© 2021 NekoAPI',
// }}
>
<Nav.Footer collapseButton={true}>
</Nav.Footer>
</Nav>
</div>
</Layout>
</>
);
};
export default HeaderBar;
...@@ -56,3 +56,20 @@ export function renderQuotaWithPrompt(quota, digits) { ...@@ -56,3 +56,20 @@ export function renderQuotaWithPrompt(quota, digits) {
} }
return ''; return '';
} }
const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo',
'light-blue', 'lime', 'orange', 'pink',
'purple', 'red', 'teal', 'violet', 'yellow'
]
export function stringToColor(str) {
let sum = 0;
// 对字符串中的每个字符进行操作
for (let i = 0; i < str.length; i++) {
// 将字符的ASCII值加到sum中
sum += str.charCodeAt(i);
}
// 使用模运算得到个位数
let i = sum % colors.length;
return colors[i];
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ export function isRoot() { ...@@ -22,7 +22,7 @@ export function isRoot() {
export function getSystemName() { export function getSystemName() {
let system_name = localStorage.getItem('system_name'); let system_name = localStorage.getItem('system_name');
if (!system_name) return 'One API Midjourney'; if (!system_name) return 'Neko API';
return system_name; return system_name;
} }
......
...@@ -6,6 +6,8 @@ body { ...@@ -6,6 +6,8 @@ body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
scrollbar-width: none; scrollbar-width: none;
color: var(--semi-color-text-0) !important;
background-color: var( --semi-color-bg-0) !important;
} }
body::-webkit-scrollbar { body::-webkit-scrollbar {
...@@ -16,8 +18,27 @@ code { ...@@ -16,8 +18,27 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
.semi-navigation-vertical {
/*display: flex;*/
/*flex-direction: column;*/
}
.semi-navigation-item {
margin-bottom: 0;
}
.semi-navigation-vertical {
/*flex: 0 0 auto;*/
/*display: flex;*/
/*flex-direction: column;*/
/*width: 100%;*/
height: 100vh;
overflow: hidden;
}
.main-content { .main-content {
padding: 4px; padding: 4px;
height: 100%;
} }
.small-icon .icon { .small-icon .icon {
......
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom'; import {BrowserRouter} from 'react-router-dom';
import { Container } from 'semantic-ui-react'; import {Container} from 'semantic-ui-react';
import App from './App'; import App from './App';
import Header from './components/Header'; import HeaderBar from './components/HeaderBar';
import Footer from './components/Footer'; import Footer from './components/Footer';
import 'semantic-ui-css/semantic.min.css'; import 'semantic-ui-css/semantic.min.css';
import './index.css'; import './index.css';
import { UserProvider } from './context/User'; import {UserProvider} from './context/User';
import { ToastContainer } from 'react-toastify'; import {ToastContainer} from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import { StatusProvider } from './context/Status'; import {StatusProvider} from './context/Status';
import {Layout} from "@douyinfe/semi-ui";
import SiderBar from "./components/SiderBar";
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById('root'));
const {Sider, Content, Header} = Layout;
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<StatusProvider> <StatusProvider>
<UserProvider> <UserProvider>
<BrowserRouter> <BrowserRouter>
<Header /> <Layout>
<Container className={'main-content'}>
<App /> <Sider>
</Container> <SiderBar/>
<ToastContainer /> </Sider>
<Footer /> <Layout>
<Header>
<HeaderBar/>
</Header>
<Content
style={{
padding: '24px',
}}
>
<App/>
</Content>
<Footer></Footer>
</Layout>
</Layout>
<ToastContainer/>
{/*<Footer />*/}
</BrowserRouter> </BrowserRouter>
</UserProvider> </UserProvider>
</StatusProvider> </StatusProvider>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment