diff --git a/web/src/pages/Task/index.js b/web/src/pages/Task/index.js
index 05326719..7cceb3f1 100644
--- a/web/src/pages/Task/index.js
+++ b/web/src/pages/Task/index.js
@@ -2,9 +2,9 @@ import React from 'react';
import TaskLogsTable from '../../components/table/TaskLogsTable.js';
const Task = () => (
- <>
+
- >
+
);
export default Task;
diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js
index 71f611bd..fb8badf7 100644
--- a/web/src/pages/Token/EditToken.js
+++ b/web/src/pages/Token/EditToken.js
@@ -1,5 +1,4 @@
-import React, { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
+import React, { useEffect, useState, useContext, useRef } from 'react';
import {
API,
isMobile,
@@ -7,71 +6,53 @@ import {
showSuccess,
timestamp2string,
renderGroupOption,
- renderQuotaWithPrompt
+ renderQuotaWithPrompt,
} from '../../helpers';
import {
- AutoComplete,
- Banner,
Button,
- Checkbox,
- DatePicker,
- Input,
- Select,
SideSheet,
Space,
Spin,
- TextArea,
Typography,
Card,
Tag,
+ Avatar,
+ Form,
+ Col,
+ Row,
} from '@douyinfe/semi-ui';
import {
- IconClock,
- IconCalendar,
IconCreditCard,
IconLink,
- IconServer,
- IconUserGroup,
IconSave,
IconClose,
- IconPlusCircle,
+ IconKey,
} from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next';
+import { StatusContext } from '../../context/Status';
const { Text, Title } = Typography;
const EditToken = (props) => {
const { t } = useTranslation();
- const [isEdit, setIsEdit] = useState(false);
- const [loading, setLoading] = useState(isEdit);
- const originInputs = {
+ const [statusState, statusDispatch] = useContext(StatusContext);
+ const [loading, setLoading] = useState(false);
+ const formApiRef = useRef(null);
+ const [models, setModels] = useState([]);
+ const [groups, setGroups] = useState([]);
+ const isEdit = props.editingToken.id !== undefined;
+
+ const getInitValues = () => ({
name: '',
- remain_quota: isEdit ? 0 : 500000,
+ remain_quota: 500000,
expired_time: -1,
unlimited_quota: false,
model_limits_enabled: false,
model_limits: [],
allow_ips: '',
group: '',
- };
- const [inputs, setInputs] = useState(originInputs);
- const {
- name,
- remain_quota,
- expired_time,
- unlimited_quota,
- model_limits_enabled,
- model_limits,
- allow_ips,
- group,
- } = inputs;
- const [models, setModels] = useState([]);
- const [groups, setGroups] = useState([]);
- const navigate = useNavigate();
-
- const handleInputChange = (name, value) => {
- setInputs((inputs) => ({ ...inputs, [name]: value }));
- };
+ tokenCount: 1,
+ });
const handleCancel = () => {
props.handleClose();
@@ -84,18 +65,15 @@ const EditToken = (props) => {
seconds += day * 24 * 60 * 60;
seconds += hour * 60 * 60;
seconds += minute * 60;
+ if (!formApiRef.current) return;
if (seconds !== 0) {
timestamp += seconds;
- setInputs({ ...inputs, expired_time: timestamp2string(timestamp) });
+ formApiRef.current.setValue('expired_time', timestamp2string(timestamp));
} else {
- setInputs({ ...inputs, expired_time: -1 });
+ formApiRef.current.setValue('expired_time', -1);
}
};
- const setUnlimitedQuota = () => {
- setInputs({ ...inputs, unlimited_quota: !unlimited_quota });
- };
-
const loadModels = async () => {
let res = await API.get(`/api/user/models`);
const { success, message, data } = res.data;
@@ -119,7 +97,17 @@ const EditToken = (props) => {
value: group,
ratio: info.ratio,
}));
+ if (statusState?.status?.default_use_auto_group) {
+ if (localGroupOptions.some((group) => group.value === 'auto')) {
+ localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1));
+ } else {
+ localGroupOptions.unshift({ label: t('自动选择'), value: 'auto' });
+ }
+ }
setGroups(localGroupOptions);
+ if (statusState?.status?.default_use_auto_group && formApiRef.current) {
+ formApiRef.current.setValue('group', 'auto');
+ }
} else {
showError(t(message));
}
@@ -138,7 +126,9 @@ const EditToken = (props) => {
} else {
data.model_limits = [];
}
- setInputs(data);
+ if (formApiRef.current) {
+ formApiRef.current.setValues({ ...getInitValues(), ...data });
+ }
} else {
showError(message);
}
@@ -146,34 +136,27 @@ const EditToken = (props) => {
};
useEffect(() => {
- setIsEdit(props.editingToken.id !== undefined);
- }, [props.editingToken.id]);
-
- useEffect(() => {
- if (!isEdit) {
- setInputs(originInputs);
- } else {
- loadToken().then(() => {
- // console.log(inputs);
- });
+ if (formApiRef.current) {
+ if (!isEdit) {
+ formApiRef.current.setValues(getInitValues());
+ }
}
loadModels();
loadGroups();
- }, [isEdit]);
+ }, [props.editingToken.id]);
- // 新增 state 变量 tokenCount 来记录用户想要创建的令牌数量,默认为 1
- const [tokenCount, setTokenCount] = useState(1);
-
- // 新增处理 tokenCount 变化的函数
- const handleTokenCountChange = (value) => {
- // 确保用户输入的是正整数
- const count = parseInt(value, 10);
- if (!isNaN(count) && count > 0) {
- setTokenCount(count);
+ useEffect(() => {
+ if (props.visiable) {
+ if (isEdit) {
+ loadToken();
+ } else {
+ formApiRef.current?.setValues(getInitValues());
+ }
+ } else {
+ formApiRef.current?.reset();
}
- };
+ }, [props.visiable, props.editingToken.id]);
- // 生成一个随机的四位字母数字字符串
const generateRandomSuffix = () => {
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
@@ -186,11 +169,10 @@ const EditToken = (props) => {
return result;
};
- const submit = async () => {
+ const submit = async (values) => {
setLoading(true);
if (isEdit) {
- // 编辑令牌的逻辑保持不变
- let localInputs = { ...inputs };
+ let { tokenCount: _tc, ...localInputs } = values;
localInputs.remain_quota = parseInt(localInputs.remain_quota);
if (localInputs.expired_time !== -1) {
let time = Date.parse(localInputs.expired_time);
@@ -202,6 +184,7 @@ const EditToken = (props) => {
localInputs.expired_time = Math.ceil(time / 1000);
}
localInputs.model_limits = localInputs.model_limits.join(',');
+ localInputs.model_limits_enabled = localInputs.model_limits.length > 0;
let res = await API.put(`/api/token/`, {
...localInputs,
id: parseInt(props.editingToken.id),
@@ -215,16 +198,12 @@ const EditToken = (props) => {
showError(t(message));
}
} else {
- // 处理新增多个令牌的情况
- let successCount = 0; // 记录成功创建的令牌数量
- for (let i = 0; i < tokenCount; i++) {
- let localInputs = { ...inputs };
-
- // 检查用户是否填写了令牌名称
- const baseName = inputs.name.trim() === '' ? 'default' : inputs.name;
-
- if (i !== 0 || inputs.name.trim() === '') {
- // 如果创建多个令牌(i !== 0)或者用户没有填写名称,则添加随机后缀
+ const count = parseInt(values.tokenCount, 10) || 1;
+ let successCount = 0;
+ for (let i = 0; i < count; i++) {
+ let { tokenCount: _tc, ...localInputs } = values;
+ const baseName = values.name.trim() === '' ? 'default' : values.name.trim();
+ if (i !== 0 || values.name.trim() === '') {
localInputs.name = `${baseName}-${generateRandomSuffix()}`;
} else {
localInputs.name = baseName;
@@ -241,17 +220,16 @@ const EditToken = (props) => {
localInputs.expired_time = Math.ceil(time / 1000);
}
localInputs.model_limits = localInputs.model_limits.join(',');
+ localInputs.model_limits_enabled = localInputs.model_limits.length > 0;
let res = await API.post(`/api/token/`, localInputs);
const { success, message } = res.data;
-
if (success) {
successCount++;
} else {
showError(t(message));
- break; // 如果创建失败,终止循环
+ break;
}
}
-
if (successCount > 0) {
showSuccess(t('令牌创建成功,请在列表页面点击复制获取令牌!'));
props.refresh();
@@ -259,8 +237,7 @@ const EditToken = (props) => {
}
}
setLoading(false);
- setInputs(originInputs); // 重置表单
- setTokenCount(1); // 重置数量为默认值
+ formApiRef.current?.setValues(getInitValues());
};
return (
@@ -268,43 +245,39 @@ const EditToken = (props) => {
placement={isEdit ? 'right' : 'left'}
title={
- {isEdit ?
- {t('更新')} :
- {t('新建')}
- }
-
+ {isEdit ? (
+
+ {t('更新')}
+
+ ) : (
+
+ {t('新建')}
+
+ )}
+
{isEdit ? t('更新令牌信息') : t('创建新的令牌')}
}
- headerStyle={{
- borderBottom: '1px solid var(--semi-color-border)',
- padding: '24px'
- }}
- bodyStyle={{
- backgroundColor: 'var(--semi-color-bg-0)',
- padding: '0'
- }}
+ bodyStyle={{ padding: '0' }}
visible={props.visiable}
width={isMobile() ? '100%' : 600}
footer={
-
+
}
>
@@ -317,285 +290,199 @@ const EditToken = (props) => {
onCancel={() => handleCancel()}
>
-
-
-
-
-
-
-
-
-
{t('基本信息')}
-
{t('设置令牌的基本信息')}
-
-
-
-
-
- {t('名称')}
- handleInputChange('name', value)}
- value={name}
- autoComplete="new-password"
- size="large"
- className="!rounded-lg"
- showClear
- required
- />
-
-
-
-
{t('过期时间')}
-
-
handleInputChange('expired_time', value)}
- value={expired_time}
- autoComplete="new-password"
- type="dateTime"
- className="w-full !rounded-lg"
- size="large"
- prefix={}
- />
+
);
diff --git a/web/src/pages/Token/index.js b/web/src/pages/Token/index.js
index d63247a4..4c03767d 100644
--- a/web/src/pages/Token/index.js
+++ b/web/src/pages/Token/index.js
@@ -3,9 +3,9 @@ import TokensTable from '../../components/table/TokensTable';
const Token = () => {
return (
- <>
+
- >
+
);
};
diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js
index cb32bca2..dc986077 100644
--- a/web/src/pages/TopUp/index.js
+++ b/web/src/pages/TopUp/index.js
@@ -7,7 +7,7 @@ import {
renderQuota,
renderQuotaWithAmount,
copy,
- getQuotaPerUnit
+ getQuotaPerUnit,
} from '../../helpers';
import {
Avatar,
@@ -34,7 +34,7 @@ import {
Copy,
Users,
User,
- Coins
+ Coins,
} from 'lucide-react';
const { Text, Title } = Typography;
@@ -49,9 +49,15 @@ const TopUp = () => {
const [topUpCode, setTopUpCode] = useState('');
const [amount, setAmount] = useState(0.0);
const [minTopUp, setMinTopUp] = useState(statusState?.status?.min_topup || 1);
- const [topUpCount, setTopUpCount] = useState(statusState?.status?.min_topup || 1);
- const [topUpLink, setTopUpLink] = useState(statusState?.status?.top_up_link || '');
- const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(statusState?.status?.enable_online_topup || false);
+ const [topUpCount, setTopUpCount] = useState(
+ statusState?.status?.min_topup || 1,
+ );
+ const [topUpLink, setTopUpLink] = useState(
+ statusState?.status?.top_up_link || '',
+ );
+ const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(
+ statusState?.status?.enable_online_topup || false,
+ );
const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
const [userQuota, setUserQuota] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -61,6 +67,7 @@ const TopUp = () => {
const [amountLoading, setAmountLoading] = useState(false);
const [paymentLoading, setPaymentLoading] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
+ const [payMethods, setPayMethods] = useState([]);
// 邀请相关状态
const [affLink, setAffLink] = useState('');
@@ -76,7 +83,7 @@ const TopUp = () => {
{ value: 100 },
{ value: 300 },
{ value: 500 },
- { value: 1000 }
+ { value: 1000 },
]);
const [selectedPreset, setSelectedPreset] = useState(null);
@@ -126,7 +133,7 @@ const TopUp = () => {
if (userState.user) {
const updatedUser = {
...userState.user,
- quota: userState.user.quota + data
+ quota: userState.user.quota + data,
};
userDispatch({ type: 'login', payload: updatedUser });
}
@@ -283,6 +290,34 @@ const TopUp = () => {
}
getAffLink().then();
setTransferAmount(getQuotaPerUnit());
+
+ let payMethods = localStorage.getItem('pay_methods');
+ try {
+ payMethods = JSON.parse(payMethods);
+ if (payMethods && payMethods.length > 0) {
+ // 检查name和type是否为空
+ payMethods = payMethods.filter((method) => {
+ return method.name && method.type;
+ });
+ // 如果没有color,则设置默认颜色
+ payMethods = payMethods.map((method) => {
+ if (!method.color) {
+ if (method.type === 'zfb') {
+ method.color = 'rgba(var(--semi-blue-5), 1)';
+ } else if (method.type === 'wx') {
+ method.color = 'rgba(var(--semi-green-5), 1)';
+ } else {
+ method.color = 'rgba(var(--semi-primary-5), 1)';
+ }
+ }
+ return method;
+ });
+ setPayMethods(payMethods);
+ }
+ } catch (e) {
+ console.log(e);
+ showError(t('支付方式配置错误, 请联系管理员'));
+ }
}, []);
useEffect(() => {
@@ -347,12 +382,12 @@ const TopUp = () => {
};
return (
-
+
{/* 划转模态框 */}
-
+
+
{t('划转邀请额度')}
}
@@ -360,22 +395,22 @@ const TopUp = () => {
onOk={transfer}
onCancel={handleTransferCancel}
maskClosable={false}
- size="small"
+ size='small'
centered
>
-
+
@@ -393,8 +428,8 @@ const TopUp = () => {
{/* 充值确认模态框 */}
-
+
+
{t('充值确认')}
}
@@ -402,57 +437,80 @@ const TopUp = () => {
onOk={onlineTopUp}
onCancel={handleCancel}
maskClosable={false}
- size="small"
+ size='small'
centered
confirmLoading={confirmLoading}
>
-
-
+
+
{t('充值数量')}:
{renderQuotaWithAmount(topUpCount)}
-
+
{t('实付金额')}:
{amountLoading ? (
) : (
- {renderAmount()}
+
+ {renderAmount()}
+
)}
-
+
{t('支付方式')}:
- {payWay === 'zfb' ? (
-
-
- {t('支付宝')}
-
- ) : (
-
-
- {t('微信')}
-
- )}
+ {(() => {
+ const payMethod = payMethods.find(
+ (method) => method.type === payWay,
+ );
+ if (payMethod) {
+ return (
+
+ {payMethod.type === 'zfb' ? (
+
+ ) : payMethod.type === 'wx' ? (
+
+ ) : (
+
+ )}
+ {payMethod.name}
+
+ );
+ } else {
+ // 默认充值方式
+ return payWay === 'zfb' ? (
+
+
+ {t('支付宝')}
+
+ ) : (
+
+
+ {t('微信')}
+
+ );
+ }
+ })()}
-
+
{/* 左侧充值区域 */}
-
+
{/* 在线充值卡片 */}
-
-
+
+
+
@@ -460,21 +518,23 @@ const TopUp = () => {
{t('在线充值')}
-
+
{t('快速方便的充值方式')}
-
+
{userDataLoading ? (
) : (
-
-
-
-
{getUsername()} ({getUserRole()})
-
{getUsername()}
+
+
+
+
+ {getUsername()} ({getUserRole()})
+
+ {getUsername()}
)}
@@ -483,29 +543,33 @@ const TopUp = () => {
}
>
-
+
{/* 账户余额信息 */}
-
-
-
+
+
+
{t('当前余额')}
{userDataLoading ? (
-
+
) : (
-
+
{renderQuota(userState?.user?.quota || userQuota)}
)}
-
-
+
+
{t('历史消耗')}
{userDataLoading ? (
-
+
) : (
-
+
{renderQuota(userState?.user?.used_quota || 0)}
)}
@@ -516,8 +580,10 @@ const TopUp = () => {
<>
{/* 预设充值额度卡片网格 */}
-
{t('选择充值额度')}
-
+
+ {t('选择充值额度')}
+
+
{presetAmounts.map((preset, index) => (
{
}`}
bodyStyle={{ textAlign: 'center' }}
>
-
-
+
+
{formatLargeNumber(preset.value)}
-
- {t('实付')} ¥{(preset.value * priceRatio).toFixed(2)}
+
+ {t('实付')} ¥
+ {(preset.value * priceRatio).toFixed(2)}
))}
{/* 桌面端显示的自定义金额和支付按钮 */}
-
+
- {t('或输入自定义金额')}
+
+ {t('或输入自定义金额')}
+
-
+
{t('充值数量')}
{amountLoading ? (
-
+
) : (
- {t('实付金额:') + renderAmount()}
+
+ {t('实付金额:') + renderAmount()}
+
)}
{
getAmount(1);
}
}}
- size="large"
- className="w-full"
- formatter={(value) => value ? `${value}` : ''}
- parser={(value) => value ? parseInt(value.replace(/[^\d]/g, '')) : 0}
+ size='large'
+ className='w-full'
+ formatter={(value) => (value ? `${value}` : '')}
+ parser={(value) =>
+ value ? parseInt(value.replace(/[^\d]/g, '')) : 0
+ }
/>
-
-
-
+
+
+ {t('选择支付方式')}
+
+ {payMethods.length === 2 ? (
+
+ {payMethods.map((payMethod) => (
+
+ ))}
+
+ ) : payMethods.length === 3 ? (
+
+ {payMethods.map((payMethod) => (
+
+ ))}
+
+ ) : payMethods.length > 3 ? (
+
+ {payMethods.map((payMethod) => (
+
preTopUp(payMethod.type)}
+ disabled={!enableOnlineTopUp}
+ className={`cursor-pointer !rounded-xl p-0 transition-all hover:shadow-md ${paymentLoading && payWay === payMethod.type
+ ? 'border-blue-400'
+ : 'border-gray-200 hover:border-gray-300'
+ }`}
+ bodyStyle={{
+ padding: '10px',
+ textAlign: 'center',
+ opacity: !enableOnlineTopUp ? 0.5 : 1
+ }}
+ >
+ {paymentLoading && payWay === payMethod.type ? (
+
+ ) : (
+ <>
+
+ {payMethod.type === 'zfb' ? (
+
+ ) : payMethod.type === 'wx' ? (
+
+ ) : (
+
+ )}
+
+ {payMethod.name}
+ >
+ )}
+
+ ))}
+
+ ) : (
+
+ {payMethods.map((payMethod) => (
+
+ ))}
+
+ )}
>
@@ -613,39 +800,41 @@ const TopUp = () => {
{!enableOnlineTopUp && (
)}
- {t('兑换码充值')}
+ {t('兑换码充值')}
-
-
-
+
+
+
{t('使用兑换码快速充值')}
-
+
setRedemptionCode(value)}
- size="large"
+ size='large'
/>
-
+
{topUpLink && (
}
style={{ height: '40px' }}
>
@@ -653,12 +842,12 @@ const TopUp = () => {
)}
{/* 右侧邀请信息卡片 */}
-
+
-
-
+
+
+
@@ -689,7 +878,7 @@ const TopUp = () => {
{t('邀请奖励')}
-
+
{t('邀请好友获得额外奖励')}
@@ -698,53 +887,56 @@ const TopUp = () => {
}
>
-
-
-
-
-
{t('待使用收益')}
+
+
+
+
+ {t('待使用收益')}
-
+
{renderQuota(userState?.user?.aff_quota || 0)}
-
-
- {t('总收益')}
-
+
+
+ {t('总收益')}
+
{renderQuota(userState?.user?.aff_history_quota || 0)}
-
- {t('邀请人数')}
-
-
+
+ {t('邀请人数')}
+
+
{userState?.user?.aff_count || 0}
-
+
{t('邀请链接')}
}
>
@@ -753,24 +945,24 @@ const TopUp = () => {
}
/>
-
-
-
-
-
-
+
+
+
+
+
+
{t('邀请好友注册,好友充值后您可获得相应奖励')}
-
-
-
+
+
+
{t('通过划转功能将奖励额度转入到您的账户余额中')}
-
-
-
+
+
+
{t('邀请的好友越多,获得的奖励越多')}
@@ -785,20 +977,27 @@ const TopUp = () => {
{/* 移动端底部固定的自定义金额和支付区域 */}
{enableOnlineTopUp && (
-