|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useContext, useEffect, useRef, useState } from 'react'; |
|
|
import { |
|
|
Banner, |
|
|
Button, |
|
|
Col, |
|
|
Form, |
|
|
Row, |
|
|
Modal, |
|
|
Space, |
|
|
Card, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { API, showError, showSuccess, timestamp2string } from '../../helpers'; |
|
|
import { marked } from 'marked'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { StatusContext } from '../../context/Status'; |
|
|
import Text from '@douyinfe/semi-ui/lib/es/typography/text'; |
|
|
|
|
|
const LEGAL_USER_AGREEMENT_KEY = 'legal.user_agreement'; |
|
|
const LEGAL_PRIVACY_POLICY_KEY = 'legal.privacy_policy'; |
|
|
|
|
|
const OtherSetting = () => { |
|
|
const { t } = useTranslation(); |
|
|
let [inputs, setInputs] = useState({ |
|
|
Notice: '', |
|
|
[LEGAL_USER_AGREEMENT_KEY]: '', |
|
|
[LEGAL_PRIVACY_POLICY_KEY]: '', |
|
|
SystemName: '', |
|
|
Logo: '', |
|
|
Footer: '', |
|
|
About: '', |
|
|
HomePageContent: '', |
|
|
}); |
|
|
let [loading, setLoading] = useState(false); |
|
|
const [showUpdateModal, setShowUpdateModal] = useState(false); |
|
|
const [statusState, statusDispatch] = useContext(StatusContext); |
|
|
const [updateData, setUpdateData] = useState({ |
|
|
tag_name: '', |
|
|
content: '', |
|
|
}); |
|
|
|
|
|
const updateOption = async (key, value) => { |
|
|
setLoading(true); |
|
|
const res = await API.put('/api/option/', { |
|
|
key, |
|
|
value, |
|
|
}); |
|
|
const { success, message } = res.data; |
|
|
if (success) { |
|
|
setInputs((inputs) => ({ ...inputs, [key]: value })); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
const [loadingInput, setLoadingInput] = useState({ |
|
|
Notice: false, |
|
|
[LEGAL_USER_AGREEMENT_KEY]: false, |
|
|
[LEGAL_PRIVACY_POLICY_KEY]: false, |
|
|
SystemName: false, |
|
|
Logo: false, |
|
|
HomePageContent: false, |
|
|
About: false, |
|
|
Footer: false, |
|
|
CheckUpdate: false, |
|
|
}); |
|
|
const handleInputChange = async (value, e) => { |
|
|
const name = e.target.id; |
|
|
setInputs((inputs) => ({ ...inputs, [name]: value })); |
|
|
}; |
|
|
|
|
|
|
|
|
const formAPISettingGeneral = useRef(); |
|
|
|
|
|
const submitNotice = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true })); |
|
|
await updateOption('Notice', inputs.Notice); |
|
|
showSuccess(t('公告已更新')); |
|
|
} catch (error) { |
|
|
console.error(t('公告更新失败'), error); |
|
|
showError(t('公告更新失败')); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitUserAgreement = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
[LEGAL_USER_AGREEMENT_KEY]: true, |
|
|
})); |
|
|
await updateOption( |
|
|
LEGAL_USER_AGREEMENT_KEY, |
|
|
inputs[LEGAL_USER_AGREEMENT_KEY], |
|
|
); |
|
|
showSuccess(t('用户协议已更新')); |
|
|
} catch (error) { |
|
|
console.error(t('用户协议更新失败'), error); |
|
|
showError(t('用户协议更新失败')); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
[LEGAL_USER_AGREEMENT_KEY]: false, |
|
|
})); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitPrivacyPolicy = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
[LEGAL_PRIVACY_POLICY_KEY]: true, |
|
|
})); |
|
|
await updateOption( |
|
|
LEGAL_PRIVACY_POLICY_KEY, |
|
|
inputs[LEGAL_PRIVACY_POLICY_KEY], |
|
|
); |
|
|
showSuccess(t('隐私政策已更新')); |
|
|
} catch (error) { |
|
|
console.error(t('隐私政策更新失败'), error); |
|
|
showError(t('隐私政策更新失败')); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
[LEGAL_PRIVACY_POLICY_KEY]: false, |
|
|
})); |
|
|
} |
|
|
}; |
|
|
|
|
|
const formAPIPersonalization = useRef(); |
|
|
|
|
|
const submitSystemName = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
SystemName: true, |
|
|
})); |
|
|
await updateOption('SystemName', inputs.SystemName); |
|
|
showSuccess(t('系统名称已更新')); |
|
|
} catch (error) { |
|
|
console.error(t('系统名称更新失败'), error); |
|
|
showError(t('系统名称更新失败')); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
SystemName: false, |
|
|
})); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const submitLogo = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true })); |
|
|
await updateOption('Logo', inputs.Logo); |
|
|
showSuccess('Logo 已更新'); |
|
|
} catch (error) { |
|
|
console.error('Logo 更新失败', error); |
|
|
showError('Logo 更新失败'); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitOption = async (key) => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
HomePageContent: true, |
|
|
})); |
|
|
await updateOption(key, inputs[key]); |
|
|
showSuccess('首页内容已更新'); |
|
|
} catch (error) { |
|
|
console.error('首页内容更新失败', error); |
|
|
showError('首页内容更新失败'); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
HomePageContent: false, |
|
|
})); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitAbout = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, About: true })); |
|
|
await updateOption('About', inputs.About); |
|
|
showSuccess('关于内容已更新'); |
|
|
} catch (error) { |
|
|
console.error('关于内容更新失败', error); |
|
|
showError('关于内容更新失败'); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, About: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitFooter = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true })); |
|
|
await updateOption('Footer', inputs.Footer); |
|
|
showSuccess('页脚内容已更新'); |
|
|
} catch (error) { |
|
|
console.error('页脚内容更新失败', error); |
|
|
showError('页脚内容更新失败'); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
const checkUpdate = async () => { |
|
|
try { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
CheckUpdate: true, |
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const res = await fetch( |
|
|
'https://api.github.com/repos/Calcium-Ion/new-api/releases/latest', |
|
|
{ |
|
|
headers: { |
|
|
Accept: 'application/json', |
|
|
'Content-Type': 'application/json', |
|
|
|
|
|
'User-Agent': 'new-api-update-checker', |
|
|
}, |
|
|
}, |
|
|
).then((response) => response.json()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { tag_name, body } = res; |
|
|
if (tag_name === statusState?.status?.version) { |
|
|
showSuccess(`已是最新版本:${tag_name}`); |
|
|
} else { |
|
|
setUpdateData({ |
|
|
tag_name: tag_name, |
|
|
content: marked.parse(body), |
|
|
}); |
|
|
setShowUpdateModal(true); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Failed to check for updates:', error); |
|
|
showError('检查更新失败,请稍后再试'); |
|
|
} finally { |
|
|
setLoadingInput((loadingInput) => ({ |
|
|
...loadingInput, |
|
|
CheckUpdate: false, |
|
|
})); |
|
|
} |
|
|
}; |
|
|
const getOptions = async () => { |
|
|
const res = await API.get('/api/option/'); |
|
|
const { success, message, data } = res.data; |
|
|
if (success) { |
|
|
let newInputs = {}; |
|
|
data.forEach((item) => { |
|
|
if (item.key in inputs) { |
|
|
newInputs[item.key] = item.value; |
|
|
} |
|
|
}); |
|
|
setInputs(newInputs); |
|
|
formAPISettingGeneral.current.setValues(newInputs); |
|
|
formAPIPersonalization.current.setValues(newInputs); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
}; |
|
|
|
|
|
useEffect(() => { |
|
|
getOptions(); |
|
|
}, []); |
|
|
|
|
|
|
|
|
const openGitHubRelease = () => { |
|
|
window.open( |
|
|
`https://github.com/Calcium-Ion/new-api/releases/tag/${updateData.tag_name}`, |
|
|
'_blank', |
|
|
); |
|
|
}; |
|
|
|
|
|
const getStartTimeString = () => { |
|
|
const timestamp = statusState?.status?.start_time; |
|
|
return statusState.status ? timestamp2string(timestamp) : ''; |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<Row> |
|
|
<Col |
|
|
span={24} |
|
|
style={{ |
|
|
marginTop: '10px', |
|
|
display: 'flex', |
|
|
flexDirection: 'column', |
|
|
gap: '10px', |
|
|
}} |
|
|
> |
|
|
{/* 版本信息 */} |
|
|
<Form> |
|
|
<Card> |
|
|
<Form.Section text={t('系统信息')}> |
|
|
<Row> |
|
|
<Col span={16}> |
|
|
<Space> |
|
|
<Text> |
|
|
{t('当前版本')}: |
|
|
{statusState?.status?.version || t('未知')} |
|
|
</Text> |
|
|
<Button |
|
|
type='primary' |
|
|
onClick={checkUpdate} |
|
|
loading={loadingInput['CheckUpdate']} |
|
|
> |
|
|
{t('检查更新')} |
|
|
</Button> |
|
|
</Space> |
|
|
</Col> |
|
|
</Row> |
|
|
<Row> |
|
|
<Col span={16}> |
|
|
<Text> |
|
|
{t('启动时间')}:{getStartTimeString()} |
|
|
</Text> |
|
|
</Col> |
|
|
</Row> |
|
|
</Form.Section> |
|
|
</Card> |
|
|
</Form> |
|
|
{/* 通用设置 */} |
|
|
<Form |
|
|
values={inputs} |
|
|
getFormApi={(formAPI) => (formAPISettingGeneral.current = formAPI)} |
|
|
> |
|
|
<Card> |
|
|
<Form.Section text={t('通用设置')}> |
|
|
<Form.TextArea |
|
|
label={t('公告')} |
|
|
placeholder={t( |
|
|
'在此输入新的公告内容,支持 Markdown & HTML 代码', |
|
|
)} |
|
|
field={'Notice'} |
|
|
onChange={handleInputChange} |
|
|
style={{ fontFamily: 'JetBrains Mono, Consolas' }} |
|
|
autosize={{ minRows: 6, maxRows: 12 }} |
|
|
/> |
|
|
<Button onClick={submitNotice} loading={loadingInput['Notice']}> |
|
|
{t('设置公告')} |
|
|
</Button> |
|
|
<Form.TextArea |
|
|
label={t('用户协议')} |
|
|
placeholder={t( |
|
|
'在此输入用户协议内容,支持 Markdown & HTML 代码', |
|
|
)} |
|
|
field={LEGAL_USER_AGREEMENT_KEY} |
|
|
onChange={handleInputChange} |
|
|
style={{ fontFamily: 'JetBrains Mono, Consolas' }} |
|
|
autosize={{ minRows: 6, maxRows: 12 }} |
|
|
helpText={t('填写用户协议内容后,用户注册时将被要求勾选已阅读用户协议')} |
|
|
/> |
|
|
<Button |
|
|
onClick={submitUserAgreement} |
|
|
loading={loadingInput[LEGAL_USER_AGREEMENT_KEY]} |
|
|
> |
|
|
{t('设置用户协议')} |
|
|
</Button> |
|
|
<Form.TextArea |
|
|
label={t('隐私政策')} |
|
|
placeholder={t( |
|
|
'在此输入隐私政策内容,支持 Markdown & HTML 代码', |
|
|
)} |
|
|
field={LEGAL_PRIVACY_POLICY_KEY} |
|
|
onChange={handleInputChange} |
|
|
style={{ fontFamily: 'JetBrains Mono, Consolas' }} |
|
|
autosize={{ minRows: 6, maxRows: 12 }} |
|
|
helpText={t('填写隐私政策内容后,用户注册时将被要求勾选已阅读隐私政策')} |
|
|
/> |
|
|
<Button |
|
|
onClick={submitPrivacyPolicy} |
|
|
loading={loadingInput[LEGAL_PRIVACY_POLICY_KEY]} |
|
|
> |
|
|
{t('设置隐私政策')} |
|
|
</Button> |
|
|
</Form.Section> |
|
|
</Card> |
|
|
</Form> |
|
|
{/* 个性化设置 */} |
|
|
<Form |
|
|
values={inputs} |
|
|
getFormApi={(formAPI) => (formAPIPersonalization.current = formAPI)} |
|
|
> |
|
|
<Card> |
|
|
<Form.Section text={t('个性化设置')}> |
|
|
<Form.Input |
|
|
label={t('系统名称')} |
|
|
placeholder={t('在此输入系统名称')} |
|
|
field={'SystemName'} |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Button |
|
|
onClick={submitSystemName} |
|
|
loading={loadingInput['SystemName']} |
|
|
> |
|
|
{t('设置系统名称')} |
|
|
</Button> |
|
|
<Form.Input |
|
|
label={t('Logo 图片地址')} |
|
|
placeholder={t('在此输入 Logo 图片地址')} |
|
|
field={'Logo'} |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Button onClick={submitLogo} loading={loadingInput['Logo']}> |
|
|
{t('设置 Logo')} |
|
|
</Button> |
|
|
<Form.TextArea |
|
|
label={t('首页内容')} |
|
|
placeholder={t( |
|
|
'在此输入首页内容,支持 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']} |
|
|
> |
|
|
{t('设置首页内容')} |
|
|
</Button> |
|
|
<Form.TextArea |
|
|
label={t('关于')} |
|
|
placeholder={t( |
|
|
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面', |
|
|
)} |
|
|
field={'About'} |
|
|
onChange={handleInputChange} |
|
|
style={{ fontFamily: 'JetBrains Mono, Consolas' }} |
|
|
autosize={{ minRows: 6, maxRows: 12 }} |
|
|
/> |
|
|
<Button onClick={submitAbout} loading={loadingInput['About']}> |
|
|
{t('设置关于')} |
|
|
</Button> |
|
|
{/* */} |
|
|
<Banner |
|
|
fullMode={false} |
|
|
type='info' |
|
|
description={t( |
|
|
'移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目', |
|
|
)} |
|
|
closeIcon={null} |
|
|
style={{ marginTop: 15 }} |
|
|
/> |
|
|
<Form.Input |
|
|
label={t('页脚')} |
|
|
placeholder={t( |
|
|
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码', |
|
|
)} |
|
|
field={'Footer'} |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Button onClick={submitFooter} loading={loadingInput['Footer']}> |
|
|
{t('设置页脚')} |
|
|
</Button> |
|
|
</Form.Section> |
|
|
</Card> |
|
|
</Form> |
|
|
</Col> |
|
|
<Modal |
|
|
title={t('新版本') + ':' + updateData.tag_name} |
|
|
visible={showUpdateModal} |
|
|
onCancel={() => setShowUpdateModal(false)} |
|
|
footer={[ |
|
|
<Button |
|
|
key='details' |
|
|
type='primary' |
|
|
onClick={() => { |
|
|
setShowUpdateModal(false); |
|
|
openGitHubRelease(); |
|
|
}} |
|
|
> |
|
|
{t('详情')} |
|
|
</Button>, |
|
|
]} |
|
|
> |
|
|
<div dangerouslySetInnerHTML={{ __html: updateData.content }}></div> |
|
|
</Modal> |
|
|
</Row> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default OtherSetting; |
|
|
|