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