| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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;
|
|
|