Commit eb955ae4 by CaIon

chore: lint fix

parent d572a43f
......@@ -18,4 +18,4 @@ Before you start editing, make sure your `Actions on Save` options have `Optimiz
## Reference
1. https://github.com/OIerDb-ng/OIerDb
2. https://github.com/cornflourblue/react-hooks-redux-registration-login-example
\ No newline at end of file
2. https://github.com/cornflourblue/react-hooks-redux-registration-login-example
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
......
......@@ -48,7 +48,7 @@ function App() {
}
let logo = getLogo();
if (logo) {
let linkElement = document.querySelector('link[rel~=\'icon\']');
let linkElement = document.querySelector("link[rel~='icon']");
if (linkElement) {
linkElement.href = logo;
}
......@@ -60,7 +60,7 @@ function App() {
<Layout.Content>
<Routes>
<Route
path="/"
path='/'
element={
<Suspense fallback={<Loading></Loading>}>
<Home />
......@@ -68,7 +68,7 @@ function App() {
}
/>
<Route
path="/channel"
path='/channel'
element={
<PrivateRoute>
<Channel />
......@@ -76,7 +76,7 @@ function App() {
}
/>
<Route
path="/channel/edit/:id"
path='/channel/edit/:id'
element={
<Suspense fallback={<Loading></Loading>}>
<EditChannel />
......@@ -84,7 +84,7 @@ function App() {
}
/>
<Route
path="/channel/add"
path='/channel/add'
element={
<Suspense fallback={<Loading></Loading>}>
<EditChannel />
......@@ -92,7 +92,7 @@ function App() {
}
/>
<Route
path="/token"
path='/token'
element={
<PrivateRoute>
<Token />
......@@ -100,7 +100,7 @@ function App() {
}
/>
<Route
path="/redemption"
path='/redemption'
element={
<PrivateRoute>
<Redemption />
......@@ -108,7 +108,7 @@ function App() {
}
/>
<Route
path="/user"
path='/user'
element={
<PrivateRoute>
<User />
......@@ -116,7 +116,7 @@ function App() {
}
/>
<Route
path="/user/edit/:id"
path='/user/edit/:id'
element={
<Suspense fallback={<Loading></Loading>}>
<EditUser />
......@@ -124,7 +124,7 @@ function App() {
}
/>
<Route
path="/user/edit"
path='/user/edit'
element={
<Suspense fallback={<Loading></Loading>}>
<EditUser />
......@@ -132,7 +132,7 @@ function App() {
}
/>
<Route
path="/user/reset"
path='/user/reset'
element={
<Suspense fallback={<Loading></Loading>}>
<PasswordResetConfirm />
......@@ -140,7 +140,7 @@ function App() {
}
/>
<Route
path="/login"
path='/login'
element={
<Suspense fallback={<Loading></Loading>}>
<LoginForm />
......@@ -148,7 +148,7 @@ function App() {
}
/>
<Route
path="/register"
path='/register'
element={
<Suspense fallback={<Loading></Loading>}>
<RegisterForm />
......@@ -156,7 +156,7 @@ function App() {
}
/>
<Route
path="/reset"
path='/reset'
element={
<Suspense fallback={<Loading></Loading>}>
<PasswordResetForm />
......@@ -164,7 +164,7 @@ function App() {
}
/>
<Route
path="/oauth/github"
path='/oauth/github'
element={
<Suspense fallback={<Loading></Loading>}>
<GitHubOAuth />
......@@ -172,7 +172,7 @@ function App() {
}
/>
<Route
path="/setting"
path='/setting'
element={
<PrivateRoute>
<Suspense fallback={<Loading></Loading>}>
......@@ -182,7 +182,7 @@ function App() {
}
/>
<Route
path="/topup"
path='/topup'
element={
<PrivateRoute>
<Suspense fallback={<Loading></Loading>}>
......@@ -192,7 +192,7 @@ function App() {
}
/>
<Route
path="/log"
path='/log'
element={
<PrivateRoute>
<Log />
......@@ -200,7 +200,7 @@ function App() {
}
/>
<Route
path="/detail"
path='/detail'
element={
<PrivateRoute>
<Suspense fallback={<Loading></Loading>}>
......@@ -210,7 +210,7 @@ function App() {
}
/>
<Route
path="/midjourney"
path='/midjourney'
element={
<PrivateRoute>
<Suspense fallback={<Loading></Loading>}>
......@@ -220,7 +220,7 @@ function App() {
}
/>
<Route
path="/about"
path='/about'
element={
<Suspense fallback={<Loading></Loading>}>
<About />
......@@ -228,16 +228,14 @@ function App() {
}
/>
<Route
path="/chat"
path='/chat'
element={
<Suspense fallback={<Loading></Loading>}>
<Chat />
</Suspense>
}
/>
<Route path="*" element={
<NotFound />
} />
<Route path='*' element={<NotFound />} />
</Routes>
</Layout.Content>
</Layout>
......
......@@ -32,27 +32,36 @@ const Footer = () => {
<Layout.Content style={{ textAlign: 'center' }}>
{footer ? (
<div
className="custom-footer"
className='custom-footer'
dangerouslySetInnerHTML={{ __html: footer }}
></div>
) : (
<div className="custom-footer">
<div className='custom-footer'>
<a
href="https://github.com/Calcium-Ion/new-api"
target="_blank" rel="noreferrer"
href='https://github.com/Calcium-Ion/new-api'
target='_blank'
rel='noreferrer'
>
New API {import.meta.env.VITE_REACT_APP_VERSION}{' '}
</a>
{' '}
<a href="https://github.com/Calcium-Ion" target="_blank" rel="noreferrer">
<a
href='https://github.com/Calcium-Ion'
target='_blank'
rel='noreferrer'
>
Calcium-Ion
</a>{' '}
开发,基于{' '}
<a href="https://github.com/songquanpeng/one-api" target="_blank" rel="noreferrer">
<a
href='https://github.com/songquanpeng/one-api'
target='_blank'
rel='noreferrer'
>
One API v0.5.4
</a>{' '}
,本项目根据{' '}
<a href="https://opensource.org/licenses/mit-license.php">
<a href='https://opensource.org/licenses/mit-license.php'>
MIT 许可证
</a>{' '}
授权
......
......@@ -49,7 +49,7 @@ const GitHubOAuth = () => {
return (
<Segment style={{ minHeight: '300px' }}>
<Dimmer active inverted>
<Loader size="large">{prompt}</Loader>
<Loader size='large'>{prompt}</Loader>
</Dimmer>
</Segment>
);
......
......@@ -17,15 +17,15 @@ let headerButtons = [
text: '关于',
itemKey: 'about',
to: '/about',
icon: <IconHelpCircle />
}
icon: <IconHelpCircle />,
},
];
if (localStorage.getItem('chat_link')) {
headerButtons.splice(1, 0, {
name: '聊天',
to: '/chat',
icon: 'comments'
icon: 'comments',
});
}
......@@ -40,7 +40,11 @@ const HeaderBar = () => {
var themeMode = localStorage.getItem('theme-mode');
const currentDate = new Date();
// enable fireworks on new year(1.1 and 2.9-2.24)
const isNewYear = (currentDate.getMonth() === 0 && currentDate.getDate() === 1) || (currentDate.getMonth() === 1 && currentDate.getDate() >= 9 && currentDate.getDate() <= 24);
const isNewYear =
(currentDate.getMonth() === 0 && currentDate.getDate() === 1) ||
(currentDate.getMonth() === 1 &&
currentDate.getDate() >= 9 &&
currentDate.getDate() <= 24);
async function logout() {
setShowSidebar(false);
......@@ -93,7 +97,7 @@ const HeaderBar = () => {
const routerMap = {
about: '/about',
login: '/login',
register: '/register'
register: '/register',
};
return (
<Link
......@@ -106,52 +110,69 @@ const HeaderBar = () => {
}}
selectedKeys={[]}
// items={headerButtons}
onSelect={key => {
}}
onSelect={(key) => {}}
footer={
<>
{isNewYear &&
{isNewYear && (
// happy new year
<Dropdown
position="bottomRight"
position='bottomRight'
render={
<Dropdown.Menu>
<Dropdown.Item onClick={handleNewYearClick}>Happy New Year!!!</Dropdown.Item>
<Dropdown.Item onClick={handleNewYearClick}>
Happy New Year!!!
</Dropdown.Item>
</Dropdown.Menu>
}
>
<Nav.Item itemKey={'new-year'} text={'🏮'} />
</Dropdown>
}
)}
<Nav.Item itemKey={'about'} icon={<IconHelpCircle />} />
<Switch checkedText="🌞" size={'large'} checked={dark} uncheckedText="🌙" onChange={switchMode} />
{userState.user ?
<Switch
checkedText='🌞'
size={'large'}
checked={dark}
uncheckedText='🌙'
onChange={switchMode}
/>
{userState.user ? (
<>
<Dropdown
position="bottomRight"
position='bottomRight'
render={
<Dropdown.Menu>
<Dropdown.Item onClick={logout}>退出</Dropdown.Item>
</Dropdown.Menu>
}
>
<Avatar size="small" color={stringToColor(userState.user.username)} style={{ margin: 4 }}>
<Avatar
size='small'
color={stringToColor(userState.user.username)}
style={{ margin: 4 }}
>
{userState.user.username[0]}
</Avatar>
<span>{userState.user.username}</span>
</Dropdown>
</>
:
) : (
<>
<Nav.Item itemKey={'login'} text={'登录'} icon={<IconKey />} />
<Nav.Item itemKey={'register'} text={'注册'} icon={<IconUser />} />
<Nav.Item
itemKey={'login'}
text={'登录'}
icon={<IconKey />}
/>
<Nav.Item
itemKey={'register'}
text={'注册'}
icon={<IconUser />}
/>
</>
}
)}
</>
}
>
</Nav>
></Nav>
</div>
</Layout>
</>
......
......@@ -3,7 +3,9 @@ import { Spin } from '@douyinfe/semi-ui';
const Loading = ({ prompt: name = 'page' }) => {
return (
<Spin style={{height: 100}} spinning={true}>加载{name}...</Spin>
<Spin style={{ height: 100 }} spinning={true}>
加载{name}...
</Spin>
);
};
......
......@@ -10,21 +10,20 @@ const OtherSetting = () => {
Logo: '',
Footer: '',
About: '',
HomePageContent: ''
HomePageContent: '',
});
let [loading, setLoading] = useState(false);
const [showUpdateModal, setShowUpdateModal] = useState(false);
const [updateData, setUpdateData] = useState({
tag_name: '',
content: ''
content: '',
});
const updateOption = async (key, value) => {
setLoading(true);
const res = await API.put('/api/option/', {
key,
value
value,
});
const { success, message } = res.data;
if (success) {
......@@ -41,7 +40,7 @@ const OtherSetting = () => {
Logo: false,
HomePageContent: false,
About: false,
Footer: false
Footer: false,
});
const handleInputChange = async (value, e) => {
const name = e.target.id;
......@@ -68,14 +67,20 @@ const OtherSetting = () => {
// 个性化设置 - SystemName
const submitSystemName = async () => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: true }));
setLoadingInput((loadingInput) => ({
...loadingInput,
SystemName: true,
}));
await updateOption('SystemName', inputs.SystemName);
showSuccess('系统名称已更新');
} catch (error) {
console.error('系统名称更新失败', error);
showError('系统名称更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: false }));
setLoadingInput((loadingInput) => ({
...loadingInput,
SystemName: false,
}));
}
};
......@@ -95,14 +100,20 @@ const OtherSetting = () => {
// 个性化设置 - 首页内容
const submitOption = async (key) => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: true }));
setLoadingInput((loadingInput) => ({
...loadingInput,
HomePageContent: true,
}));
await updateOption(key, inputs[key]);
showSuccess('首页内容已更新');
} catch (error) {
console.error('首页内容更新失败', error);
showError('首页内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: false }));
setLoadingInput((loadingInput) => ({
...loadingInput,
HomePageContent: false,
}));
}
};
// 个性化设置 - 关于
......@@ -132,15 +143,13 @@ const OtherSetting = () => {
}
};
const openGitHubRelease = () => {
window.location =
'https://github.com/songquanpeng/one-api/releases/latest';
window.location = 'https://github.com/songquanpeng/one-api/releases/latest';
};
const checkUpdate = async () => {
const res = await API.get(
'https://api.github.com/repos/songquanpeng/one-api/releases/latest'
'https://api.github.com/repos/songquanpeng/one-api/releases/latest',
);
const { tag_name, body } = res.data;
if (tag_name === process.env.REACT_APP_VERSION) {
......@@ -148,7 +157,7 @@ const OtherSetting = () => {
} else {
setUpdateData({
tag_name: tag_name,
content: marked.parse(body)
content: marked.parse(body),
});
setShowUpdateModal(true);
}
......@@ -175,13 +184,15 @@ const OtherSetting = () => {
getOptions();
}, []);
return (
<Row>
<Col span={24}>
{/* 通用设置 */}
<Form values={inputs} getFormApi={formAPI => formAPISettingGeneral.current = formAPI}
style={{ marginBottom: 15 }}>
<Form
values={inputs}
getFormApi={(formAPI) => (formAPISettingGeneral.current = formAPI)}
style={{ marginBottom: 15 }}
>
<Form.Section text={'通用设置'}>
<Form.TextArea
label={'公告'}
......@@ -191,12 +202,17 @@ const OtherSetting = () => {
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button onClick={submitNotice} loading={loadingInput['Notice']}>设置公告</Button>
<Button onClick={submitNotice} loading={loadingInput['Notice']}>
设置公告
</Button>
</Form.Section>
</Form>
{/* 个性化设置 */}
<Form values={inputs} getFormApi={formAPI => formAPIPersonalization.current = formAPI}
style={{ marginBottom: 15 }}>
<Form
values={inputs}
getFormApi={(formAPI) => (formAPIPersonalization.current = formAPI)}
style={{ marginBottom: 15 }}
>
<Form.Section text={'个性化设置'}>
<Form.Input
label={'系统名称'}
......@@ -204,48 +220,69 @@ const OtherSetting = () => {
field={'SystemName'}
onChange={handleInputChange}
/>
<Button onClick={submitSystemName} loading={loadingInput['SystemName']}>设置系统名称</Button>
<Button
onClick={submitSystemName}
loading={loadingInput['SystemName']}
>
设置系统名称
</Button>
<Form.Input
label={'Logo 图片地址'}
placeholder={'在此输入 Logo 图片地址'}
field={'Logo'}
onChange={handleInputChange}
/>
<Button onClick={submitLogo} loading={loadingInput['Logo']}>设置 Logo</Button>
<Button onClick={submitLogo} loading={loadingInput['Logo']}>
设置 Logo
</Button>
<Form.TextArea
label={'首页内容'}
placeholder={'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'}
placeholder={
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
}
field={'HomePageContent'}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button onClick={() => submitOption('HomePageContent')}
loading={loadingInput['HomePageContent']}>设置首页内容</Button>
<Button
onClick={() => submitOption('HomePageContent')}
loading={loadingInput['HomePageContent']}
>
设置首页内容
</Button>
<Form.TextArea
label={'关于'}
placeholder={'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'}
placeholder={
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
}
field={'About'}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button onClick={submitAbout} loading={loadingInput['About']}>设置关于</Button>
<Button onClick={submitAbout} loading={loadingInput['About']}>
设置关于
</Button>
{/* */}
<Banner
fullMode={false}
type="info"
description="移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。"
type='info'
description='移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。'
closeIcon={null}
style={{ marginTop: 15 }}
/>
<Form.Input
label={'页脚'}
placeholder={'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'}
placeholder={
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
}
field={'Footer'}
onChange={handleInputChange}
/>
<Button onClick={submitFooter} loading={loadingInput['Footer']}>设置页脚</Button>
<Button onClick={submitFooter} loading={loadingInput['Footer']}>
设置页脚
</Button>
</Form.Section>
</Form>
</Col>
......
......@@ -6,7 +6,7 @@ import { useSearchParams } from 'react-router-dom';
const PasswordResetConfirm = () => {
const [inputs, setInputs] = useState({
email: '',
token: ''
token: '',
});
const { email, token } = inputs;
......@@ -23,7 +23,7 @@ const PasswordResetConfirm = () => {
let email = searchParams.get('email');
setInputs({
token,
email
email,
});
}, []);
......@@ -46,7 +46,7 @@ const PasswordResetConfirm = () => {
setLoading(true);
const res = await API.post(`/api/user/reset`, {
email,
token
token,
});
const { success, message } = res.data;
if (success) {
......@@ -61,29 +61,29 @@ const PasswordResetConfirm = () => {
}
return (
<Grid textAlign="center" style={{ marginTop: '48px' }}>
<Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="" textAlign="center">
<Image src="/logo.png" /> 密码重置确认
<Header as='h2' color='' textAlign='center'>
<Image src='/logo.png' /> 密码重置确认
</Header>
<Form size="large">
<Form size='large'>
<Segment>
<Form.Input
fluid
icon="mail"
iconPosition="left"
placeholder="邮箱地址"
name="email"
icon='mail'
iconPosition='left'
placeholder='邮箱地址'
name='email'
value={email}
readOnly
/>
{newPassword && (
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="新密码"
name="newPassword"
icon='lock'
iconPosition='left'
placeholder='新密码'
name='newPassword'
value={newPassword}
readOnly
onClick={(e) => {
......@@ -94,9 +94,9 @@ const PasswordResetConfirm = () => {
/>
)}
<Button
color="green"
color='green'
fluid
size="large"
size='large'
onClick={handleSubmit}
loading={loading}
disabled={disableButton}
......
......@@ -5,7 +5,7 @@ import Turnstile from 'react-turnstile';
const PasswordResetForm = () => {
const [inputs, setInputs] = useState({
email: ''
email: '',
});
const { email } = inputs;
......@@ -31,7 +31,7 @@ const PasswordResetForm = () => {
function handleChange(e) {
const { name, value } = e.target;
setInputs(inputs => ({ ...inputs, [name]: value }));
setInputs((inputs) => ({ ...inputs, [name]: value }));
}
async function handleSubmit(e) {
......@@ -43,7 +43,7 @@ const PasswordResetForm = () => {
}
setLoading(true);
const res = await API.get(
`/api/reset_password?email=${email}&turnstile=${turnstileToken}`
`/api/reset_password?email=${email}&turnstile=${turnstileToken}`,
);
const { success, message } = res.data;
if (success) {
......@@ -56,19 +56,19 @@ const PasswordResetForm = () => {
}
return (
<Grid textAlign="center" style={{ marginTop: '48px' }}>
<Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="" textAlign="center">
<Image src="/logo.png" /> 密码重置
<Header as='h2' color='' textAlign='center'>
<Image src='/logo.png' /> 密码重置
</Header>
<Form size="large">
<Form size='large'>
<Segment>
<Form.Input
fluid
icon="mail"
iconPosition="left"
placeholder="邮箱地址"
name="email"
icon='mail'
iconPosition='left'
placeholder='邮箱地址'
name='email'
value={email}
onChange={handleChange}
/>
......@@ -83,9 +83,9 @@ const PasswordResetForm = () => {
<></>
)}
<Button
color="green"
color='green'
fluid
size="large"
size='large'
onClick={handleSubmit}
loading={loading}
disabled={disableButton}
......
......@@ -2,12 +2,11 @@ import { Navigate } from 'react-router-dom';
import { history } from '../helpers';
function PrivateRoute({ children }) {
if (!localStorage.getItem('user')) {
return <Navigate to="/login" state={{ from: history.location }} />;
return <Navigate to='/login' state={{ from: history.location }} />;
}
return children;
}
export { PrivateRoute };
\ No newline at end of file
export { PrivateRoute };
import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Message, Segment } from 'semantic-ui-react';
import {
Button,
Form,
Grid,
Header,
Image,
Message,
Segment,
} from 'semantic-ui-react';
import { Link, useNavigate } from 'react-router-dom';
import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import Turnstile from 'react-turnstile';
......@@ -10,7 +18,7 @@ const RegisterForm = () => {
password: '',
password2: '',
email: '',
verification_code: ''
verification_code: '',
});
const { username, password, password2 } = inputs;
const [showEmailVerification, setShowEmailVerification] = useState(false);
......@@ -65,7 +73,7 @@ const RegisterForm = () => {
inputs.aff_code = affCode;
const res = await API.post(
`/api/user/register?turnstile=${turnstileToken}`,
inputs
inputs,
);
const { success, message } = res.data;
if (success) {
......@@ -86,7 +94,7 @@ const RegisterForm = () => {
}
setLoading(true);
const res = await API.get(
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
);
const { success, message } = res.data;
if (success) {
......@@ -98,49 +106,49 @@ const RegisterForm = () => {
};
return (
<Grid textAlign="center" style={{ marginTop: '48px' }}>
<Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="" textAlign="center">
<Header as='h2' color='' textAlign='center'>
<Image src={logo} /> 新用户注
</Header>
<Form size="large">
<Form size='large'>
<Segment>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="输入用户名,最长 12 位"
icon='user'
iconPosition='left'
placeholder='输入用户名,最长 12 位'
onChange={handleChange}
name="username"
name='username'
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="输入密码,最短 8 位,最长 20 位"
icon='lock'
iconPosition='left'
placeholder='输入密码,最短 8 位,最长 20 位'
onChange={handleChange}
name="password"
type="password"
name='password'
type='password'
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="输入密码,最短 8 位,最长 20 位"
icon='lock'
iconPosition='left'
placeholder='输入密码,最短 8 位,最长 20 位'
onChange={handleChange}
name="password2"
type="password"
name='password2'
type='password'
/>
{showEmailVerification ? (
<>
<Form.Input
fluid
icon="mail"
iconPosition="left"
placeholder="输入邮箱地址"
icon='mail'
iconPosition='left'
placeholder='输入邮箱地址'
onChange={handleChange}
name="email"
type="email"
name='email'
type='email'
action={
<Button onClick={sendVerificationCode} disabled={loading}>
获取验证码
......@@ -149,11 +157,11 @@ const RegisterForm = () => {
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="输入验证码"
icon='lock'
iconPosition='left'
placeholder='输入验证码'
onChange={handleChange}
name="verification_code"
name='verification_code'
/>
</>
) : (
......@@ -170,9 +178,9 @@ const RegisterForm = () => {
<></>
)}
<Button
color="green"
color='green'
fluid
size="large"
size='large'
onClick={handleSubmit}
loading={loading}
>
......@@ -182,7 +190,7 @@ const RegisterForm = () => {
</Form>
<Message>
已有账户?
<Link to="/login" className="btn btn-link">
<Link to='/login' className='btn btn-link'>
点击登录
</Link>
</Message>
......
......@@ -3,7 +3,14 @@ import { Link, useNavigate } from 'react-router-dom';
import { UserContext } from '../context/User';
import { StatusContext } from '../context/Status';
import { API, getLogo, getSystemName, isAdmin, isMobile, showError } from '../helpers';
import {
API,
getLogo,
getSystemName,
isAdmin,
isMobile,
showError,
} from '../helpers';
import '../index.css';
import {
......@@ -17,7 +24,7 @@ import {
IconKey,
IconLayers,
IconSetting,
IconUser
IconUser,
} from '@douyinfe/semi-icons';
import { Layout, Nav } from '@douyinfe/semi-ui';
......@@ -26,7 +33,8 @@ import { Layout, Nav } from '@douyinfe/semi-ui';
const SiderBar = () => {
const [userState, userDispatch] = useContext(UserContext);
const [statusState, statusDispatch] = useContext(StatusContext);
const defaultIsCollapsed = isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true';
const defaultIsCollapsed =
isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true';
let navigate = useNavigate();
const [selectedKeys, setSelectedKeys] = useState(['home']);
......@@ -46,89 +54,105 @@ const SiderBar = () => {
setting: '/setting',
about: '/about',
chat: '/chat',
detail: '/detail'
detail: '/detail',
};
const headerButtons = useMemo(() => [
{
text: '首页',
itemKey: 'home',
to: '/',
icon: <IconHome />
},
{
text: '渠道',
itemKey: 'channel',
to: '/channel',
icon: <IconLayers />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '聊天',
itemKey: 'chat',
to: '/chat',
icon: <IconComment />,
className: localStorage.getItem('chat_link') ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '令牌',
itemKey: 'token',
to: '/token',
icon: <IconKey />
},
{
text: '兑换码',
itemKey: 'redemption',
to: '/redemption',
icon: <IconGift />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '钱包',
itemKey: 'topup',
to: '/topup',
icon: <IconCreditCard />
},
{
text: '用户管理',
itemKey: 'user',
to: '/user',
icon: <IconUser />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '日志',
itemKey: 'log',
to: '/log',
icon: <IconHistogram />
},
{
text: '数据看板',
itemKey: 'detail',
to: '/detail',
icon: <IconCalendarClock />,
className: localStorage.getItem('enable_data_export') === 'true' ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '绘图',
itemKey: 'midjourney',
to: '/midjourney',
icon: <IconImage />,
className: localStorage.getItem('enable_drawing') === 'true' ? 'semi-navigation-item-normal' : 'tableHiddle'
},
{
text: '设置',
itemKey: 'setting',
to: '/setting',
icon: <IconSetting />
}
// {
// text: '关于',
// itemKey: 'about',
// to: '/about',
// icon: <IconAt/>
// }
], [localStorage.getItem('enable_data_export'), localStorage.getItem('enable_drawing'), localStorage.getItem('chat_link'), isAdmin()]);
const headerButtons = useMemo(
() => [
{
text: '首页',
itemKey: 'home',
to: '/',
icon: <IconHome />,
},
{
text: '渠道',
itemKey: 'channel',
to: '/channel',
icon: <IconLayers />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
},
{
text: '聊天',
itemKey: 'chat',
to: '/chat',
icon: <IconComment />,
className: localStorage.getItem('chat_link')
? 'semi-navigation-item-normal'
: 'tableHiddle',
},
{
text: '令牌',
itemKey: 'token',
to: '/token',
icon: <IconKey />,
},
{
text: '兑换码',
itemKey: 'redemption',
to: '/redemption',
icon: <IconGift />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
},
{
text: '钱包',
itemKey: 'topup',
to: '/topup',
icon: <IconCreditCard />,
},
{
text: '用户管理',
itemKey: 'user',
to: '/user',
icon: <IconUser />,
className: isAdmin() ? 'semi-navigation-item-normal' : 'tableHiddle',
},
{
text: '日志',
itemKey: 'log',
to: '/log',
icon: <IconHistogram />,
},
{
text: '数据看板',
itemKey: 'detail',
to: '/detail',
icon: <IconCalendarClock />,
className:
localStorage.getItem('enable_data_export') === 'true'
? 'semi-navigation-item-normal'
: 'tableHiddle',
},
{
text: '绘图',
itemKey: 'midjourney',
to: '/midjourney',
icon: <IconImage />,
className:
localStorage.getItem('enable_drawing') === 'true'
? 'semi-navigation-item-normal'
: 'tableHiddle',
},
{
text: '设置',
itemKey: 'setting',
to: '/setting',
icon: <IconSetting />,
},
// {
// text: '关于',
// itemKey: 'about',
// to: '/about',
// icon: <IconAt/>
// }
],
[
localStorage.getItem('enable_data_export'),
localStorage.getItem('enable_drawing'),
localStorage.getItem('chat_link'),
isAdmin(),
],
);
const loadStatus = async () => {
const res = await API.get('/api/status');
......@@ -143,8 +167,14 @@ const SiderBar = () => {
localStorage.setItem('display_in_currency', data.display_in_currency);
localStorage.setItem('enable_drawing', data.enable_drawing);
localStorage.setItem('enable_data_export', data.enable_data_export);
localStorage.setItem('data_export_default_time', data.data_export_default_time);
localStorage.setItem('default_collapse_sidebar', data.default_collapse_sidebar);
localStorage.setItem(
'data_export_default_time',
data.data_export_default_time,
);
localStorage.setItem(
'default_collapse_sidebar',
data.default_collapse_sidebar,
);
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
if (data.chat_link) {
localStorage.setItem('chat_link', data.chat_link);
......@@ -163,11 +193,14 @@ const SiderBar = () => {
useEffect(() => {
loadStatus().then(() => {
setIsCollapsed(isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true');
setIsCollapsed(
isMobile() ||
localStorage.getItem('default_collapse_sidebar') === 'true',
);
});
let localKey = window.location.pathname.split('/')[1]
let localKey = window.location.pathname.split('/')[1];
if (localKey === '') {
localKey = 'home'
localKey = 'home';
}
setSelectedKeys([localKey]);
}, []);
......@@ -179,9 +212,12 @@ const SiderBar = () => {
<Nav
// bodyStyle={{ maxWidth: 200 }}
style={{ maxWidth: 200 }}
defaultIsCollapsed={isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true'}
defaultIsCollapsed={
isMobile() ||
localStorage.getItem('default_collapse_sidebar') === 'true'
}
isCollapsed={isCollapsed}
onCollapseChange={collapsed => {
onCollapseChange={(collapsed) => {
setIsCollapsed(collapsed);
}}
selectedKeys={selectedKeys}
......@@ -196,20 +232,20 @@ const SiderBar = () => {
);
}}
items={headerButtons}
onSelect={key => {
onSelect={(key) => {
setSelectedKeys([key.itemKey]);
}}
header={{
logo: <img src={logo} alt="logo" style={{ marginRight: '0.75em' }} />,
text: systemName
logo: (
<img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
),
text: systemName,
}}
// footer={{
// text: '© 2021 NekoAPI',
// }}
>
<Nav.Footer collapseButton={true}>
</Nav.Footer>
<Nav.Footer collapseButton={true}></Nav.Footer>
</Nav>
</div>
</Layout>
......
......@@ -3,15 +3,27 @@ import { Icon } from '@douyinfe/semi-ui';
const WeChatIcon = () => {
function CustomIcon() {
return <svg t="1709714447384" className="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5091" width="16" height="16">
<path
d="M690.1 377.4c5.9 0 11.8 0.2 17.6 0.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6 5.5 3.9 9.1 10.3 9.1 17.6 0 2.4-0.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-0.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-0.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4 0.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-0.1 17.8-0.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8z m-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1z m-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1z"
p-id="5092"></path>
<path
d="M866.7 792.7c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-0.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7 2.4 0 4.7-0.9 6.4-2.6 1.7-1.7 2.6-4 2.6-6.4 0-2.2-0.9-4.4-1.4-6.6-0.3-1.2-7.6-28.3-12.2-45.3-0.5-1.9-0.9-3.8-0.9-5.7 0.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9z m179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c-0.1 19.8-16.2 35.9-36 35.9z"
p-id="5093"></path>
</svg>;
return (
<svg
t='1709714447384'
className='icon'
viewBox='0 0 1024 1024'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
p-id='5091'
width='16'
height='16'
>
<path
d='M690.1 377.4c5.9 0 11.8 0.2 17.6 0.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6 5.5 3.9 9.1 10.3 9.1 17.6 0 2.4-0.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-0.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-0.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4 0.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-0.1 17.8-0.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8z m-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1z m-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1z'
p-id='5092'
></path>
<path
d='M866.7 792.7c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-0.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7 2.4 0 4.7-0.9 6.4-2.6 1.7-1.7 2.6-4 2.6-6.4 0-2.2-0.9-4.4-1.4-6.6-0.3-1.2-7.6-28.3-12.2-45.3-0.5-1.9-0.9-3.8-0.9-5.7 0.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9z m179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c-0.1 19.8-16.2 35.9-36 35.9z'
p-id='5093'
></path>
</svg>
);
}
return (
......
......@@ -15,6 +15,6 @@ export async function onGitHubOAuthClicked(github_client_id) {
const state = await getOAuthState();
if (!state) return;
window.open(
`https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`
`https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`,
);
}
\ No newline at end of file
}
export const CHANNEL_OPTIONS = [
{key: 1, text: 'OpenAI', value: 1, color: 'green', label: 'OpenAI'},
{key: 2, text: 'Midjourney Proxy', value: 2, color: 'light-blue', label: 'Midjourney Proxy'},
{key: 5, text: 'Midjourney Proxy Plus', value: 5, color: 'blue', label: 'Midjourney Proxy Plus'},
{key: 4, text: 'Ollama', value: 4, color: 'grey', label: 'Ollama'},
{key: 14, text: 'Anthropic Claude', value: 14, color: 'indigo', label: 'Anthropic Claude'},
{key: 3, text: 'Azure OpenAI', value: 3, color: 'teal', label: 'Azure OpenAI'},
{key: 11, text: 'Google PaLM2', value: 11, color: 'orange', label: 'Google PaLM2'},
{key: 24, text: 'Google Gemini', value: 24, color: 'orange', label: 'Google Gemini'},
{key: 15, text: '百度文心千帆', value: 15, color: 'blue', label: '百度文心千帆'},
{key: 17, text: '阿里通义千问', value: 17, color: 'orange', label: '阿里通义千问'},
{key: 18, text: '讯飞星火认知', value: 18, color: 'blue', label: '讯飞星火认知'},
{key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet', label: '智谱 ChatGLM'},
{key: 16, text: '智谱 GLM-4V', value: 26, color: 'purple', label: '智谱 GLM-4V'},
{key: 16, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot'},
{key: 19, text: '360 智脑', value: 19, color: 'blue', label: '360 智脑'},
{key: 23, text: '腾讯混元', value: 23, color: 'teal', label: '腾讯混元'},
{key: 31, text: '零一万物', value: 31, color: 'green', label: '零一万物'},
{key: 8, text: '自定义渠道', value: 8, color: 'pink', label: '自定义渠道'},
{key: 22, text: '知识库:FastGPT', value: 22, color: 'blue', label: '知识库:FastGPT'},
{key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple', label: '知识库:AI Proxy'},
{ key: 1, text: 'OpenAI', value: 1, color: 'green', label: 'OpenAI' },
{
key: 2,
text: 'Midjourney Proxy',
value: 2,
color: 'light-blue',
label: 'Midjourney Proxy',
},
{
key: 5,
text: 'Midjourney Proxy Plus',
value: 5,
color: 'blue',
label: 'Midjourney Proxy Plus',
},
{ key: 4, text: 'Ollama', value: 4, color: 'grey', label: 'Ollama' },
{
key: 14,
text: 'Anthropic Claude',
value: 14,
color: 'indigo',
label: 'Anthropic Claude',
},
{
key: 3,
text: 'Azure OpenAI',
value: 3,
color: 'teal',
label: 'Azure OpenAI',
},
{
key: 11,
text: 'Google PaLM2',
value: 11,
color: 'orange',
label: 'Google PaLM2',
},
{
key: 24,
text: 'Google Gemini',
value: 24,
color: 'orange',
label: 'Google Gemini',
},
{
key: 15,
text: '百度文心千帆',
value: 15,
color: 'blue',
label: '百度文心千帆',
},
{
key: 17,
text: '阿里通义千问',
value: 17,
color: 'orange',
label: '阿里通义千问',
},
{
key: 18,
text: '讯飞星火认知',
value: 18,
color: 'blue',
label: '讯飞星火认知',
},
{
key: 16,
text: '智谱 ChatGLM',
value: 16,
color: 'violet',
label: '智谱 ChatGLM',
},
{
key: 16,
text: '智谱 GLM-4V',
value: 26,
color: 'purple',
label: '智谱 GLM-4V',
},
{ key: 16, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' },
{ key: 19, text: '360 智脑', value: 19, color: 'blue', label: '360 智脑' },
{ key: 23, text: '腾讯混元', value: 23, color: 'teal', label: '腾讯混元' },
{ key: 31, text: '零一万物', value: 31, color: 'green', label: '零一万物' },
{ key: 8, text: '自定义渠道', value: 8, color: 'pink', label: '自定义渠道' },
{
key: 22,
text: '知识库:FastGPT',
value: 22,
color: 'blue',
label: '知识库:FastGPT',
},
{
key: 21,
text: '知识库:AI Proxy',
value: 21,
color: 'purple',
label: '知识库:AI Proxy',
},
];
export * from './toast.constants';
export * from './user.constants';
export * from './common.constant';
export * from './channel.constants';
\ No newline at end of file
export * from './channel.constants';
......@@ -3,5 +3,5 @@ export const toastConstants = {
INFO_TIMEOUT: 3000,
ERROR_TIMEOUT: 5000,
WARNING_TIMEOUT: 10000,
NOTICE_TIMEOUT: 20000
NOTICE_TIMEOUT: 20000,
};
export const userConstants = {
REGISTER_REQUEST: 'USERS_REGISTER_REQUEST',
REGISTER_SUCCESS: 'USERS_REGISTER_SUCCESS',
REGISTER_FAILURE: 'USERS_REGISTER_FAILURE',
REGISTER_REQUEST: 'USERS_REGISTER_REQUEST',
REGISTER_SUCCESS: 'USERS_REGISTER_SUCCESS',
REGISTER_FAILURE: 'USERS_REGISTER_FAILURE',
LOGIN_REQUEST: 'USERS_LOGIN_REQUEST',
LOGIN_SUCCESS: 'USERS_LOGIN_SUCCESS',
LOGIN_FAILURE: 'USERS_LOGIN_FAILURE',
LOGOUT: 'USERS_LOGOUT',
LOGIN_REQUEST: 'USERS_LOGIN_REQUEST',
LOGIN_SUCCESS: 'USERS_LOGIN_SUCCESS',
LOGIN_FAILURE: 'USERS_LOGIN_FAILURE',
GETALL_REQUEST: 'USERS_GETALL_REQUEST',
GETALL_SUCCESS: 'USERS_GETALL_SUCCESS',
GETALL_FAILURE: 'USERS_GETALL_FAILURE',
LOGOUT: 'USERS_LOGOUT',
DELETE_REQUEST: 'USERS_DELETE_REQUEST',
DELETE_SUCCESS: 'USERS_DELETE_SUCCESS',
DELETE_FAILURE: 'USERS_DELETE_FAILURE'
GETALL_REQUEST: 'USERS_GETALL_REQUEST',
GETALL_SUCCESS: 'USERS_GETALL_SUCCESS',
GETALL_FAILURE: 'USERS_GETALL_FAILURE',
DELETE_REQUEST: 'USERS_DELETE_REQUEST',
DELETE_SUCCESS: 'USERS_DELETE_SUCCESS',
DELETE_FAILURE: 'USERS_DELETE_FAILURE',
};
......@@ -16,4 +16,4 @@ export const StatusProvider = ({ children }) => {
{children}
</StatusContext.Provider>
);
};
\ No newline at end of file
};
// contexts/User/index.jsx
import React from "react"
import { reducer, initialState } from "./reducer"
import React from 'react';
import { reducer, initialState } from './reducer';
export const UserContext = React.createContext({
state: initialState,
dispatch: () => null
})
dispatch: () => null,
});
export const UserProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState)
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<UserContext.Provider value={[ state, dispatch ]}>
{ children }
<UserContext.Provider value={[state, dispatch]}>
{children}
</UserContext.Provider>
)
}
\ No newline at end of file
);
};
......@@ -3,12 +3,12 @@ export const reducer = (state, action) => {
case 'login':
return {
...state,
user: action.payload
user: action.payload,
};
case 'logout':
return {
...state,
user: undefined
user: undefined,
};
default:
......@@ -17,5 +17,5 @@ export const reducer = (state, action) => {
};
export const initialState = {
user: undefined
};
\ No newline at end of file
user: undefined,
};
......@@ -2,12 +2,14 @@ import { showError } from './utils';
import axios from 'axios';
export const API = axios.create({
baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL ? import.meta.env.VITE_REACT_APP_SERVER_URL : '',
baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
? import.meta.env.VITE_REACT_APP_SERVER_URL
: '',
});
API.interceptors.response.use(
(response) => response,
(error) => {
showError(error);
}
},
);
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
\ No newline at end of file
if (user && user.token) {
return { Authorization: 'Bearer ' + user.token };
} else {
return {};
}
}
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
\ No newline at end of file
export const history = createBrowserHistory();
export * from './history';
export * from './auth-header';
export * from './utils';
export * from './api';
\ No newline at end of file
export * from './api';
import { Toast } from '@douyinfe/semi-ui';
import { toastConstants } from '../constants';
import React from 'react';
import {toast} from "react-toastify";
import { toast } from 'react-toastify';
const HTMLToastContent = ({ htmlContent }) => {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
......@@ -30,7 +30,7 @@ export function getSystemName() {
export function getLogo() {
let logo = localStorage.getItem('logo');
if (!logo) return '/logo.png';
return logo
return logo;
}
export function getFooterHTML() {
......@@ -157,17 +157,7 @@ export function timestamp2string(timestamp) {
second = '0' + second;
}
return (
year +
'-' +
month +
'-' +
day +
' ' +
hour +
':' +
minute +
':' +
second
year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
);
}
......@@ -186,20 +176,20 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
if (hour.length === 1) {
hour = '0' + hour;
}
let str = month + '-' + day
let str = month + '-' + day;
if (dataExportDefaultTime === 'hour') {
str += ' ' + hour + ":00"
str += ' ' + hour + ':00';
} else if (dataExportDefaultTime === 'week') {
let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000);
let nextMonth = (nextWeek.getMonth() + 1).toString();
let nextDay = nextWeek.getDate().toString();
if (nextMonth.length === 1) {
nextMonth = '0' + nextMonth;
nextMonth = '0' + nextMonth;
}
if (nextDay.length === 1) {
nextDay = '0' + nextDay;
nextDay = '0' + nextDay;
}
str += ' - ' + nextMonth + '-' + nextDay
str += ' - ' + nextMonth + '-' + nextDay;
}
return str;
}
......@@ -225,9 +215,8 @@ export const verifyJSON = (str) => {
export function shouldShowPrompt(id) {
let prompt = localStorage.getItem(`prompt-${id}`);
return !prompt;
}
export function setPromptShown(id) {
localStorage.setItem(`prompt-${id}`, 'true');
}
\ No newline at end of file
}
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;
color: var(--semi-color-text-0) !important;
background-color: var( --semi-color-bg-0) !important;
height: 100%;
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;
color: var(--semi-color-text-0) !important;
background-color: var(--semi-color-bg-0) !important;
height: 100%;
}
#root {
height: 100%;
height: 100%;
}
@media only screen and (max-width: 767px) {
.semi-table-tbody, .semi-table-row, .semi-table-row-cell {
display: block!important;
width: auto!important;
padding: 2px!important;
}
.semi-table-row-cell {
border-bottom: 0!important;
}
.semi-table-tbody>.semi-table-row {
border-bottom: 1px solid rgba(0,0,0,.1);
}
.semi-space {
/*display: block!important;*/
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 3px;
column-gap: 10px;
}
.semi-table-tbody,
.semi-table-row,
.semi-table-row-cell {
display: block !important;
width: auto !important;
padding: 2px !important;
}
.semi-table-row-cell {
border-bottom: 0 !important;
}
.semi-table-tbody > .semi-table-row {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.semi-space {
/*display: block!important;*/
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 3px;
column-gap: 10px;
}
}
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
padding: 16px 14px;
padding: 16px 14px;
}
.channel-table {
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
padding: 16px 8px;
}
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
padding: 16px 8px;
}
}
.semi-layout {
height: 100%;
height: 100%;
}
.tableShow {
display: revert;
display: revert;
}
.tableHiddle {
display: none !important;
display: none !important;
}
body::-webkit-scrollbar {
display: none;
display: none;
}
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;*/
/*display: flex;*/
/*flex-direction: column;*/
}
.semi-navigation-item {
margin-bottom: 0;
margin-bottom: 0;
}
.semi-navigation-vertical {
/*flex: 0 0 auto;*/
/*display: flex;*/
/*flex-direction: column;*/
/*width: 100%;*/
height: 100%;
overflow: hidden;
/*flex: 0 0 auto;*/
/*display: flex;*/
/*flex-direction: column;*/
/*width: 100%;*/
height: 100%;
overflow: hidden;
}
.main-content {
padding: 4px;
height: 100%;
padding: 4px;
height: 100%;
}
.small-icon .icon {
font-size: 1em !important;
font-size: 1em !important;
}
.custom-footer {
font-size: 1.1em;
font-size: 1.1em;
}
@media only screen and (max-width: 600px) {
.hide-on-mobile {
display: none !important;
}
.hide-on-mobile {
display: none !important;
}
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import HeaderBar from './components/HeaderBar';
import Footer from './components/Footer';
import 'semantic-ui-offline/semantic.min.css';
import './index.css';
import {UserProvider} from './context/User';
import {ToastContainer} from 'react-toastify';
import { UserProvider } from './context/User';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {StatusProvider} from './context/Status';
import {Layout} from "@douyinfe/semi-ui";
import SiderBar from "./components/SiderBar";
import { StatusProvider } from './context/Status';
import { Layout } from '@douyinfe/semi-ui';
import SiderBar from './components/SiderBar';
// initialization
const root = ReactDOM.createRoot(document.getElementById('root'));
const {Sider, Content, Header} = Layout;
const { Sider, Content, Header } = Layout;
root.render(
<React.StrictMode>
<StatusProvider>
<UserProvider>
<BrowserRouter>
<Layout>
<Sider>
<SiderBar/>
</Sider>
<Layout>
<Header>
<HeaderBar/>
</Header>
<Content
style={{
padding: '24px',
}}
>
<App/>
</Content>
<Layout.Footer>
<Footer></Footer>
</Layout.Footer>
</Layout>
<ToastContainer/>
</Layout>
</BrowserRouter>
</UserProvider>
</StatusProvider>
</React.StrictMode>
<React.StrictMode>
<StatusProvider>
<UserProvider>
<BrowserRouter>
<Layout>
<Sider>
<SiderBar />
</Sider>
<Layout>
<Header>
<HeaderBar />
</Header>
<Content
style={{
padding: '24px',
}}
>
<App />
</Content>
<Layout.Footer>
<Footer></Footer>
</Layout.Footer>
</Layout>
<ToastContainer />
</Layout>
</BrowserRouter>
</UserProvider>
</StatusProvider>
</React.StrictMode>,
);
import React, { useEffect, useState } from 'react';
import { API, showError } from '../../helpers';
import { marked } from 'marked';
import {Layout} from "@douyinfe/semi-ui";
import { Layout } from '@douyinfe/semi-ui';
const About = () => {
const [about, setAbout] = useState('');
......@@ -31,37 +31,42 @@ const About = () => {
return (
<>
{
aboutLoaded && about === '' ? <>
{aboutLoaded && about === '' ? (
<>
<Layout>
<Layout.Header>
<h3>关于</h3>
</Layout.Header>
<Layout.Content>
<p>
可在设置页面设置关于内容,支持 HTML & Markdown
</p>
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
new-api项目仓库地址:
<a href='https://github.com/Calcium-Ion/new-api'>
https://github.com/Calcium-Ion/new-api
</a>
<p>
NewAPI © 2023 CalciumIon | 基于 One API v0.5.4 © 2023 JustSong。本项目根据MIT许可证授权。
NewAPI © 2023 CalciumIon | 基于 One API v0.5.4 © 2023
JustSong。本项目根据MIT许可证授权。
</p>
</Layout.Content>
</Layout>
</> : <>
{
about.startsWith('https://') ? <iframe
</>
) : (
<>
{about.startsWith('https://') ? (
<iframe
src={about}
style={{ width: '100%', height: '100vh', border: 'none' }}
/> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: about }}></div>
}
/>
) : (
<div
style={{ fontSize: 'larger' }}
dangerouslySetInnerHTML={{ __html: about }}
></div>
)}
</>
}
)}
</>
);
};
export default About;
import React from 'react';
import ChannelsTable from '../../components/ChannelsTable';
import {Layout} from "@douyinfe/semi-ui";
import { Layout } from '@douyinfe/semi-ui';
const File = () => (
<>
<Layout>
<Layout.Header>
<h3>管理渠道</h3>
</Layout.Header>
<Layout.Content>
<ChannelsTable/>
</Layout.Content>
</Layout>
</>
<>
<Layout>
<Layout.Header>
<h3>管理渠道</h3>
</Layout.Header>
<Layout.Content>
<ChannelsTable />
</Layout.Content>
</Layout>
</>
);
export default File;
......@@ -11,5 +11,4 @@ const Chat = () => {
);
};
export default Chat;
......@@ -53,78 +53,115 @@ const Home = () => {
}, []);
return (
<>
{
homePageContentLoaded && homePageContent === '' ?
<>
<Card
bordered={false}
headerLine={false}
title='系统状况'
bodyStyle={{ padding: '10px 20px' }}
>
<Row gutter={16}>
<Col span={12}>
<Card
title='系统信息'
headerExtraContent={<span
style={{ fontSize: '12px', color: 'var(--semi-color-text-1)' }}>系统信息总览</span>}>
<p>名称:{statusState?.status?.system_name}</p>
<p>版本:{statusState?.status?.version ? statusState?.status?.version : 'unknown'}</p>
<p>
源码:
<a
href='https://github.com/songquanpeng/one-api'
target='_blank' rel='noreferrer'
>
https://github.com/songquanpeng/one-api
</a>
</p>
<p>启动时间:{getStartTimeString()}</p>
</Card>
</Col>
<Col span={12}>
<Card
title='系统配置'
headerExtraContent={<span
style={{ fontSize: '12px', color: 'var(--semi-color-text-1)' }}>系统配置总览</span>}>
<p>
邮箱验证:
{statusState?.status?.email_verification === true ? '已启用' : '未启用'}
</p>
<p>
GitHub 身份验证:
{statusState?.status?.github_oauth === true ? '已启用' : '未启用'}
</p>
<p>
微信身份验证:
{statusState?.status?.wechat_login === true ? '已启用' : '未启用'}
</p>
<p>
Turnstile 用户校验:
{statusState?.status?.turnstile_check === true ? '已启用' : '未启用'}
</p>
<p>
Telegram 身份验证:
{statusState?.status?.telegram_oauth === true
? '已启用' : '未启用'}
</p>
</Card>
</Col>
</Row>
</Card>
</>
: <>
{
homePageContent.startsWith('https://') ?
<iframe src={homePageContent} style={{ width: '100%', height: '100vh', border: 'none' }} /> :
<div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
}
</>
}
{homePageContentLoaded && homePageContent === '' ? (
<>
<Card
bordered={false}
headerLine={false}
title='系统状况'
bodyStyle={{ padding: '10px 20px' }}
>
<Row gutter={16}>
<Col span={12}>
<Card
title='系统信息'
headerExtraContent={
<span
style={{
fontSize: '12px',
color: 'var(--semi-color-text-1)',
}}
>
系统信息总览
</span>
}
>
<p>名称:{statusState?.status?.system_name}</p>
<p>
版本:
{statusState?.status?.version
? statusState?.status?.version
: 'unknown'}
</p>
<p>
源码:
<a
href='https://github.com/songquanpeng/one-api'
target='_blank'
rel='noreferrer'
>
https://github.com/songquanpeng/one-api
</a>
</p>
<p>启动时间:{getStartTimeString()}</p>
</Card>
</Col>
<Col span={12}>
<Card
title='系统配置'
headerExtraContent={
<span
style={{
fontSize: '12px',
color: 'var(--semi-color-text-1)',
}}
>
系统配置总览
</span>
}
>
<p>
邮箱验证:
{statusState?.status?.email_verification === true
? '已启用'
: '未启用'}
</p>
<p>
GitHub 身份验证:
{statusState?.status?.github_oauth === true
? '已启用'
: '未启用'}
</p>
<p>
微信身份验证:
{statusState?.status?.wechat_login === true
? '已启用'
: '未启用'}
</p>
<p>
Turnstile 用户校验:
{statusState?.status?.turnstile_check === true
? '已启用'
: '未启用'}
</p>
<p>
Telegram 身份验证:
{statusState?.status?.telegram_oauth === true
? '已启用'
: '未启用'}
</p>
</Card>
</Col>
</Row>
</Card>
</>
) : (
<>
{homePageContent.startsWith('https://') ? (
<iframe
src={homePageContent}
style={{ width: '100%', height: '100vh', border: 'none' }}
/>
) : (
<div
style={{ fontSize: 'larger' }}
dangerouslySetInnerHTML={{ __html: homePageContent }}
></div>
)}
</>
)}
</>
);
};
export default Home;
\ No newline at end of file
export default Home;
import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { API, downloadTextAsFile, isMobile, showError, showSuccess } from '../../helpers';
import {
API,
downloadTextAsFile,
isMobile,
showError,
showSuccess,
} from '../../helpers';
import { renderQuotaWithPrompt } from '../../helpers/render';
import { AutoComplete, Button, Input, Modal, SideSheet, Space, Spin, Typography } from '@douyinfe/semi-ui';
import {
AutoComplete,
Button,
Input,
Modal,
SideSheet,
Space,
Spin,
Typography,
} from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import { Divider } from 'semantic-ui-react';
......@@ -15,7 +30,7 @@ const EditRedemption = (props) => {
const originInputs = {
name: '',
quota: 100000,
count: 1
count: 1,
};
const [inputs, setInputs] = useState(originInputs);
const { name, quota, count } = inputs;
......@@ -42,11 +57,9 @@ const EditRedemption = (props) => {
useEffect(() => {
if (isEdit) {
loadRedemption().then(
() => {
// console.log(inputs);
}
);
loadRedemption().then(() => {
// console.log(inputs);
});
} else {
setInputs(originInputs);
}
......@@ -60,10 +73,13 @@ const EditRedemption = (props) => {
localInputs.quota = parseInt(localInputs.quota);
let res;
if (isEdit) {
res = await API.put(`/api/redemption/`, { ...localInputs, id: parseInt(props.editingRedemption.id) });
res = await API.put(`/api/redemption/`, {
...localInputs,
id: parseInt(props.editingRedemption.id),
});
} else {
res = await API.post(`/api/redemption/`, {
...localInputs
...localInputs,
});
}
const { success, message, data } = res.data;
......@@ -97,7 +113,7 @@ const EditRedemption = (props) => {
),
onOk: () => {
downloadTextAsFile(text, `${inputs.name}.txt`);
}
},
});
}
setLoading(false);
......@@ -107,15 +123,28 @@ const EditRedemption = (props) => {
<>
<SideSheet
placement={isEdit ? 'right' : 'left'}
title={<Title level={3}>{isEdit ? '更新兑换码信息' : '创建新的兑换码'}</Title>}
title={
<Title level={3}>
{isEdit ? '更新兑换码信息' : '创建新的兑换码'}
</Title>
}
headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
visible={props.visiable}
footer={
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space>
<Button theme="solid" size={'large'} onClick={submit}>提交</Button>
<Button theme="solid" size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
<Button theme='solid' size={'large'} onClick={submit}>
提交
</Button>
<Button
theme='solid'
size={'large'}
type={'tertiary'}
onClick={handleCancel}
>
取消
</Button>
</Space>
</div>
}
......@@ -126,12 +155,12 @@ const EditRedemption = (props) => {
<Spin spinning={loading}>
<Input
style={{ marginTop: 20 }}
label="名称"
name="name"
label='名称'
name='name'
placeholder={'请输入名称'}
onChange={value => handleInputChange('name', value)}
onChange={(value) => handleInputChange('name', value)}
value={name}
autoComplete="new-password"
autoComplete='new-password'
required={!isEdit}
/>
<Divider />
......@@ -140,12 +169,12 @@ const EditRedemption = (props) => {
</div>
<AutoComplete
style={{ marginTop: 8 }}
name="quota"
name='quota'
placeholder={'请输入额度'}
onChange={(value) => handleInputChange('quota', value)}
value={quota}
autoComplete="new-password"
type="number"
autoComplete='new-password'
type='number'
position={'bottom'}
data={[
{ value: 500000, label: '1$' },
......@@ -153,25 +182,25 @@ const EditRedemption = (props) => {
{ value: 25000000, label: '50$' },
{ value: 50000000, label: '100$' },
{ value: 250000000, label: '500$' },
{ value: 500000000, label: '1000$' }
{ value: 500000000, label: '1000$' },
]}
/>
{
!isEdit && <>
{!isEdit && (
<>
<Divider />
<Typography.Text>生成数量</Typography.Text>
<Input
style={{ marginTop: 8 }}
label="生成数量"
name="count"
label='生成数量'
name='count'
placeholder={'请输入生成数量'}
onChange={value => handleInputChange('count', value)}
onChange={(value) => handleInputChange('count', value)}
value={count}
autoComplete="new-password"
type="number"
autoComplete='new-password'
type='number'
/>
</>
}
)}
</Spin>
</SideSheet>
</>
......
import React from 'react';
import RedemptionsTable from '../../components/RedemptionsTable';
import {Layout} from "@douyinfe/semi-ui";
import { Layout } from '@douyinfe/semi-ui';
const Redemption = () => (
<>
<Layout>
<Layout.Header>
<h3>管理兑换码</h3>
</Layout.Header>
<Layout.Content>
<RedemptionsTable/>
</Layout.Content>
</Layout>
<Layout>
<Layout.Header>
<h3>管理兑换码</h3>
</Layout.Header>
<Layout.Content>
<RedemptionsTable />
</Layout.Content>
</Layout>
</>
);
......
import React from 'react';
import SystemSetting from '../../components/SystemSetting';
import {isRoot} from '../../helpers';
import { isRoot } from '../../helpers';
import OtherSetting from '../../components/OtherSetting';
import PersonalSetting from '../../components/PersonalSetting';
import OperationSetting from '../../components/OperationSetting';
import {Layout, TabPane, Tabs} from "@douyinfe/semi-ui";
import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui';
const Setting = () => {
let panes = [
{
tab: '个人设置',
content: <PersonalSetting/>,
itemKey: '1'
}
];
let panes = [
{
tab: '个人设置',
content: <PersonalSetting />,
itemKey: '1',
},
];
if (isRoot()) {
panes.push({
tab: '运营设置',
content: <OperationSetting/>,
itemKey: '2'
});
panes.push({
tab: '系统设置',
content: <SystemSetting/>,
itemKey: '3'
});
panes.push({
tab: '其他设置',
content: <OtherSetting/>,
itemKey: '4'
});
}
if (isRoot()) {
panes.push({
tab: '运营设置',
content: <OperationSetting />,
itemKey: '2',
});
panes.push({
tab: '系统设置',
content: <SystemSetting />,
itemKey: '3',
});
panes.push({
tab: '其他设置',
content: <OtherSetting />,
itemKey: '4',
});
}
return (
<div>
<Layout>
<Layout.Content>
<Tabs type="line" defaultActiveKey="1">
{panes.map(pane => (
<TabPane itemKey={pane.itemKey} tab={pane.tab}>
{pane.content}
</TabPane>
))}
</Tabs>
</Layout.Content>
</Layout>
</div>
);
return (
<div>
<Layout>
<Layout.Content>
<Tabs type='line' defaultActiveKey='1'>
{panes.map((pane) => (
<TabPane itemKey={pane.itemKey} tab={pane.tab}>
{pane.content}
</TabPane>
))}
</Tabs>
</Layout.Content>
</Layout>
</div>
);
};
export default Setting;
import React from 'react';
import TokensTable from '../../components/TokensTable';
import {Layout} from "@douyinfe/semi-ui";
import { Layout } from '@douyinfe/semi-ui';
const Token = () => (
<>
<Layout>
<Layout.Header>
<h3>我的令牌</h3>
<h3>我的令牌</h3>
</Layout.Header>
<Layout.Content>
<TokensTable/>
<TokensTable />
</Layout.Content>
</Layout>
</>
......
......@@ -7,7 +7,7 @@ const AddUser = (props) => {
const originInputs = {
username: '',
display_name: '',
password: ''
password: '',
};
const [inputs, setInputs] = useState(originInputs);
const [loading, setLoading] = useState(false);
......@@ -48,8 +48,17 @@ const AddUser = (props) => {
footer={
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space>
<Button theme="solid" size={'large'} onClick={submit}>提交</Button>
<Button theme="solid" size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
<Button theme='solid' size={'large'} onClick={submit}>
提交
</Button>
<Button
theme='solid'
size={'large'}
type={'tertiary'}
onClick={handleCancel}
>
取消
</Button>
</Space>
</div>
}
......@@ -60,34 +69,34 @@ const AddUser = (props) => {
<Spin spinning={loading}>
<Input
style={{ marginTop: 20 }}
label="用户名"
name="username"
label='用户名'
name='username'
addonBefore={'用户名'}
placeholder={'请输入用户名'}
onChange={value => handleInputChange('username', value)}
onChange={(value) => handleInputChange('username', value)}
value={username}
autoComplete="off"
autoComplete='off'
/>
<Input
style={{ marginTop: 20 }}
addonBefore={'显示名'}
label="显示名称"
name="display_name"
autoComplete="off"
label='显示名称'
name='display_name'
autoComplete='off'
placeholder={'请输入显示名称'}
onChange={value => handleInputChange('display_name', value)}
onChange={(value) => handleInputChange('display_name', value)}
value={display_name}
/>
<Input
style={{ marginTop: 20 }}
label="密 码"
name="password"
label='密 码'
name='password'
type={'password'}
addonBefore={'密码'}
placeholder={'请输入密码'}
onChange={value => handleInputChange('password', value)}
onChange={(value) => handleInputChange('password', value)}
value={password}
autoComplete="off"
autoComplete='off'
/>
</Spin>
</SideSheet>
......
......@@ -3,7 +3,16 @@ import { useNavigate } from 'react-router-dom';
import { API, isMobile, showError, showSuccess } from '../../helpers';
import { renderQuotaWithPrompt } from '../../helpers/render';
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import { Button, Divider, Input, Select, SideSheet, Space, Spin, Typography } from '@douyinfe/semi-ui';
import {
Button,
Divider,
Input,
Select,
SideSheet,
Space,
Spin,
Typography,
} from '@douyinfe/semi-ui';
const EditUser = (props) => {
const userId = props.editingUser.id;
......@@ -16,21 +25,32 @@ const EditUser = (props) => {
wechat_id: '',
email: '',
quota: 0,
group: 'default'
group: 'default',
});
const [groupOptions, setGroupOptions] = useState([]);
const { username, display_name, password, github_id, wechat_id, telegram_id, email, quota, group } =
inputs;
const {
username,
display_name,
password,
github_id,
wechat_id,
telegram_id,
email,
quota,
group,
} = inputs;
const handleInputChange = (name, value) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const fetchGroups = async () => {
try {
let res = await API.get(`/api/group/`);
setGroupOptions(res.data.data.map((group) => ({
label: group,
value: group
})));
setGroupOptions(
res.data.data.map((group) => ({
label: group,
value: group,
})),
);
} catch (error) {
showError(error.message);
}
......@@ -98,8 +118,17 @@ const EditUser = (props) => {
footer={
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space>
<Button theme="solid" size={'large'} onClick={submit}>提交</Button>
<Button theme="solid" size={'large'} type={'tertiary'} onClick={handleCancel}>取消</Button>
<Button theme='solid' size={'large'} onClick={submit}>
提交
</Button>
<Button
theme='solid'
size={'large'}
type={'tertiary'}
onClick={handleCancel}
>
取消
</Button>
</Space>
</div>
}
......@@ -112,103 +141,103 @@ const EditUser = (props) => {
<Typography.Text>用户名</Typography.Text>
</div>
<Input
label="用户名"
name="username"
label='用户名'
name='username'
placeholder={'请输入新的用户名'}
onChange={value => handleInputChange('username', value)}
onChange={(value) => handleInputChange('username', value)}
value={username}
autoComplete="new-password"
autoComplete='new-password'
/>
<div style={{ marginTop: 20 }}>
<Typography.Text>密码</Typography.Text>
</div>
<Input
label="密码"
name="password"
label='密码'
name='password'
type={'password'}
placeholder={'请输入新的密码,最短 8 位'}
onChange={value => handleInputChange('password', value)}
onChange={(value) => handleInputChange('password', value)}
value={password}
autoComplete="new-password"
autoComplete='new-password'
/>
<div style={{ marginTop: 20 }}>
<Typography.Text>显示名称</Typography.Text>
</div>
<Input
label="显示名称"
name="display_name"
label='显示名称'
name='display_name'
placeholder={'请输入新的显示名称'}
onChange={value => handleInputChange('display_name', value)}
onChange={(value) => handleInputChange('display_name', value)}
value={display_name}
autoComplete="new-password"
autoComplete='new-password'
/>
{
userId && <>
{userId && (
<>
<div style={{ marginTop: 20 }}>
<Typography.Text>分组</Typography.Text>
</div>
<Select
placeholder={'请选择分组'}
name="group"
name='group'
fluid
search
selection
allowAdditions
additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
onChange={value => handleInputChange('group', value)}
onChange={(value) => handleInputChange('group', value)}
value={inputs.group}
autoComplete="new-password"
autoComplete='new-password'
optionList={groupOptions}
/>
<div style={{ marginTop: 20 }}>
<Typography.Text>{`剩余额度${renderQuotaWithPrompt(quota)}`}</Typography.Text>
</div>
<Input
name="quota"
name='quota'
placeholder={'请输入新的剩余额度'}
onChange={value => handleInputChange('quota', value)}
onChange={(value) => handleInputChange('quota', value)}
value={quota}
type={'number'}
autoComplete="new-password"
autoComplete='new-password'
/>
</>
}
)}
<Divider style={{ marginTop: 20 }}>以下信息不可修改</Divider>
<div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的 GitHub 账户</Typography.Text>
</div>
<Input
name="github_id"
name='github_id'
value={github_id}
autoComplete="new-password"
placeholder="此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
/>
<div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的微信账户</Typography.Text>
</div>
<Input
name="wechat_id"
name='wechat_id'
value={wechat_id}
autoComplete="new-password"
placeholder="此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
/>
<Input
name="telegram_id"
name='telegram_id'
value={telegram_id}
autoComplete="new-password"
placeholder="此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
/>
<div style={{ marginTop: 20 }}>
<Typography.Text>已绑定的邮箱账户</Typography.Text>
</div>
<Input
name="email"
name='email'
value={email}
autoComplete="new-password"
placeholder="此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
autoComplete='new-password'
placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
/>
</Spin>
......
import React from 'react';
import UsersTable from '../../components/UsersTable';
import {Layout} from "@douyinfe/semi-ui";
import { Layout } from '@douyinfe/semi-ui';
const User = () => (
<>
<Layout>
<Layout.Header>
<h3>管理用户</h3>
</Layout.Header>
<Layout.Content>
<UsersTable/>
</Layout.Content>
<Layout.Header>
<h3>管理用户</h3>
</Layout.Header>
<Layout.Content>
<UsersTable />
</Layout.Content>
</Layout>
</>
);
......
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