feat: replace quota input with amount-first UI and atomic quota adjustment
- Refactor token, redemption, and user quota inputs to prioritize monetary amount entry, with raw quota input collapsed by default - Add atomic quota adjustment modal for users with add/subtract/override modes, bypassing batch update queue for immediate DB consistency - Make user quota fields readonly in edit form; all modifications go through the dedicated adjust-quota modal via POST /api/user/manage - Add DecreaseUserQuota `db` parameter for direct DB writes, matching IncreaseUserQuota behavior - Support negative quota display in amount conversion helpers - Add i18n keys for all new UI strings across all locales
This commit is contained in:
@@ -25,8 +25,12 @@ import {
|
||||
showError,
|
||||
showSuccess,
|
||||
renderQuota,
|
||||
renderQuotaWithPrompt,
|
||||
getCurrencyConfig,
|
||||
} from '../../../../helpers';
|
||||
import {
|
||||
quotaToDisplayAmount,
|
||||
displayAmountToQuota,
|
||||
} from '../../../../helpers/quota';
|
||||
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
|
||||
import {
|
||||
Button,
|
||||
@@ -41,6 +45,7 @@ import {
|
||||
Avatar,
|
||||
Row,
|
||||
Col,
|
||||
InputNumber,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconCreditCard,
|
||||
@@ -57,10 +62,12 @@ const EditRedemptionModal = (props) => {
|
||||
const [loading, setLoading] = useState(isEdit);
|
||||
const isMobile = useIsMobile();
|
||||
const formApiRef = useRef(null);
|
||||
const [showQuotaInput, setShowQuotaInput] = useState(false);
|
||||
|
||||
const getInitValues = () => ({
|
||||
name: '',
|
||||
quota: 100000,
|
||||
amount: Number(quotaToDisplayAmount(100000).toFixed(6)),
|
||||
count: 1,
|
||||
expired_time: null,
|
||||
});
|
||||
@@ -79,6 +86,7 @@ const EditRedemptionModal = (props) => {
|
||||
} else {
|
||||
data.expired_time = new Date(data.expired_time * 1000);
|
||||
}
|
||||
data.amount = Number(quotaToDisplayAmount(data.quota || 0).toFixed(6));
|
||||
formApiRef.current?.setValues({ ...getInitValues(), ...data });
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -104,7 +112,12 @@ const EditRedemptionModal = (props) => {
|
||||
setLoading(true);
|
||||
let localInputs = { ...values };
|
||||
localInputs.count = parseInt(localInputs.count) || 0;
|
||||
localInputs.quota = parseInt(localInputs.quota) || 0;
|
||||
localInputs.quota = displayAmountToQuota(localInputs.amount);
|
||||
if (localInputs.quota <= 0) {
|
||||
showError(t('请输入金额'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
localInputs.name = name;
|
||||
if (!localInputs.expired_time) {
|
||||
localInputs.expired_time = 0;
|
||||
@@ -285,37 +298,63 @@ const EditRedemptionModal = (props) => {
|
||||
</div>
|
||||
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Form.AutoComplete
|
||||
field='quota'
|
||||
label={t('额度')}
|
||||
placeholder={t('请输入额度')}
|
||||
<Col span={24}>
|
||||
<Form.InputNumber
|
||||
field='amount'
|
||||
label={t('金额')}
|
||||
prefix={getCurrencyConfig().symbol}
|
||||
placeholder={t('输入金额')}
|
||||
precision={6}
|
||||
min={0}
|
||||
step={0.000001}
|
||||
style={{ width: '100%' }}
|
||||
type='number'
|
||||
rules={[
|
||||
{ required: true, message: t('请输入额度') },
|
||||
{
|
||||
validator: (rule, v) => {
|
||||
const num = parseInt(v, 10);
|
||||
return num > 0
|
||||
? Promise.resolve()
|
||||
: Promise.reject(t('额度必须大于0'));
|
||||
},
|
||||
},
|
||||
]}
|
||||
extraText={renderQuotaWithPrompt(
|
||||
Number(values.quota) || 0,
|
||||
)}
|
||||
data={[
|
||||
{ value: 500000, label: '1$' },
|
||||
{ value: 5000000, label: '10$' },
|
||||
{ value: 25000000, label: '50$' },
|
||||
{ value: 50000000, label: '100$' },
|
||||
{ value: 250000000, label: '500$' },
|
||||
{ value: 500000000, label: '1000$' },
|
||||
]}
|
||||
onChange={(val) => {
|
||||
const amount = val === '' || val == null ? 0 : val;
|
||||
formApiRef.current?.setValue('amount', amount);
|
||||
formApiRef.current?.setValue(
|
||||
'quota',
|
||||
displayAmountToQuota(amount),
|
||||
);
|
||||
}}
|
||||
showClear
|
||||
/>
|
||||
<div
|
||||
className='text-xs cursor-pointer mt-1'
|
||||
style={{ color: 'var(--semi-color-text-2)' }}
|
||||
onClick={() => setShowQuotaInput((v) => !v)}
|
||||
>
|
||||
{showQuotaInput
|
||||
? `▾ ${t('收起原生额度输入')}`
|
||||
: `▸ ${t('使用原生额度输入')}`}
|
||||
</div>
|
||||
<div style={{ display: showQuotaInput ? 'block' : 'none' }} className='mt-2'>
|
||||
<Form.InputNumber
|
||||
field='quota'
|
||||
label={t('额度')}
|
||||
placeholder={t('输入额度')}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入额度') },
|
||||
{
|
||||
validator: (rule, v) => {
|
||||
const num = parseInt(v, 10);
|
||||
return num > 0
|
||||
? Promise.resolve()
|
||||
: Promise.reject(t('额度必须大于0'));
|
||||
},
|
||||
},
|
||||
]}
|
||||
onChange={(val) => {
|
||||
const quota = val === '' || val == null ? 0 : val;
|
||||
formApiRef.current?.setValue('quota', quota);
|
||||
formApiRef.current?.setValue(
|
||||
'amount',
|
||||
Number(quotaToDisplayAmount(quota).toFixed(6)),
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
{!isEdit && (
|
||||
<Col span={12}>
|
||||
|
||||
@@ -24,10 +24,14 @@ import {
|
||||
showSuccess,
|
||||
timestamp2string,
|
||||
renderGroupOption,
|
||||
renderQuotaWithPrompt,
|
||||
getCurrencyConfig,
|
||||
getModelCategories,
|
||||
selectFilter,
|
||||
} from '../../../../helpers';
|
||||
import {
|
||||
quotaToDisplayAmount,
|
||||
displayAmountToQuota,
|
||||
} from '../../../../helpers/quota';
|
||||
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
|
||||
import {
|
||||
Button,
|
||||
@@ -41,6 +45,7 @@ import {
|
||||
Form,
|
||||
Col,
|
||||
Row,
|
||||
InputNumber,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconCreditCard,
|
||||
@@ -62,11 +67,13 @@ const EditTokenModal = (props) => {
|
||||
const formApiRef = useRef(null);
|
||||
const [models, setModels] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [showQuotaInput, setShowQuotaInput] = useState(false);
|
||||
const isEdit = props.editingToken.id !== undefined;
|
||||
|
||||
const getInitValues = () => ({
|
||||
name: '',
|
||||
remain_quota: 0,
|
||||
remain_amount: 0,
|
||||
expired_time: -1,
|
||||
unlimited_quota: true,
|
||||
model_limits_enabled: false,
|
||||
@@ -162,6 +169,9 @@ const EditTokenModal = (props) => {
|
||||
} else {
|
||||
data.model_limits = [];
|
||||
}
|
||||
data.remain_amount = Number(
|
||||
quotaToDisplayAmount(data.remain_quota || 0).toFixed(6),
|
||||
);
|
||||
if (formApiRef.current) {
|
||||
formApiRef.current.setValues({ ...getInitValues(), ...data });
|
||||
}
|
||||
@@ -209,7 +219,14 @@ const EditTokenModal = (props) => {
|
||||
setLoading(true);
|
||||
if (isEdit) {
|
||||
let { tokenCount: _tc, ...localInputs } = values;
|
||||
localInputs.remain_quota = parseInt(localInputs.remain_quota);
|
||||
localInputs.remain_quota = localInputs.unlimited_quota
|
||||
? 0
|
||||
: displayAmountToQuota(localInputs.remain_amount);
|
||||
if (!localInputs.unlimited_quota && localInputs.remain_quota <= 0) {
|
||||
showError(t('请输入金额'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (localInputs.expired_time !== -1) {
|
||||
let time = Date.parse(localInputs.expired_time);
|
||||
if (isNaN(time)) {
|
||||
@@ -245,7 +262,14 @@ const EditTokenModal = (props) => {
|
||||
} else {
|
||||
localInputs.name = baseName;
|
||||
}
|
||||
localInputs.remain_quota = parseInt(localInputs.remain_quota);
|
||||
localInputs.remain_quota = localInputs.unlimited_quota
|
||||
? 0
|
||||
: displayAmountToQuota(localInputs.remain_amount);
|
||||
if (!localInputs.unlimited_quota && localInputs.remain_quota <= 0) {
|
||||
showError(t('请输入金额'));
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (localInputs.expired_time !== -1) {
|
||||
let time = Date.parse(localInputs.expired_time);
|
||||
@@ -497,28 +521,63 @@ const EditTokenModal = (props) => {
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
<Col span={24}>
|
||||
<Form.AutoComplete
|
||||
field='remain_quota'
|
||||
label={t('额度')}
|
||||
placeholder={t('请输入额度')}
|
||||
type='number'
|
||||
<Form.InputNumber
|
||||
field='remain_amount'
|
||||
label={t('金额')}
|
||||
prefix={getCurrencyConfig().symbol}
|
||||
placeholder={t('输入金额')}
|
||||
precision={6}
|
||||
disabled={values.unlimited_quota}
|
||||
extraText={renderQuotaWithPrompt(values.remain_quota)}
|
||||
rules={
|
||||
values.unlimited_quota
|
||||
? []
|
||||
: [{ required: true, message: t('请输入额度') }]
|
||||
}
|
||||
data={[
|
||||
{ value: 500000, label: '1$' },
|
||||
{ value: 5000000, label: '10$' },
|
||||
{ value: 25000000, label: '50$' },
|
||||
{ value: 50000000, label: '100$' },
|
||||
{ value: 250000000, label: '500$' },
|
||||
{ value: 500000000, label: '1000$' },
|
||||
]}
|
||||
min={0}
|
||||
step={0.000001}
|
||||
onChange={(val) => {
|
||||
const amount = val === '' || val == null ? 0 : val;
|
||||
formApiRef.current?.setValue('remain_amount', amount);
|
||||
formApiRef.current?.setValue(
|
||||
'remain_quota',
|
||||
displayAmountToQuota(amount),
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
showClear
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div
|
||||
className='text-xs cursor-pointer mt-1'
|
||||
style={{ color: 'var(--semi-color-text-2)' }}
|
||||
onClick={() => setShowQuotaInput((v) => !v)}
|
||||
>
|
||||
{showQuotaInput
|
||||
? `▾ ${t('收起原生额度输入')}`
|
||||
: `▸ ${t('使用原生额度输入')}`}
|
||||
</div>
|
||||
<div style={{ display: showQuotaInput ? 'block' : 'none' }} className='mt-2'>
|
||||
<Form.InputNumber
|
||||
field='remain_quota'
|
||||
label={t('额度')}
|
||||
placeholder={t('输入额度')}
|
||||
disabled={values.unlimited_quota}
|
||||
min={0}
|
||||
step={500000}
|
||||
rules={
|
||||
values.unlimited_quota
|
||||
? []
|
||||
: [{ required: true, message: t('请输入额度') }]
|
||||
}
|
||||
onChange={(val) => {
|
||||
const quota = val === '' || val == null ? 0 : val;
|
||||
formApiRef.current?.setValue('remain_quota', quota);
|
||||
formApiRef.current?.setValue(
|
||||
'remain_amount',
|
||||
Number(quotaToDisplayAmount(quota).toFixed(6)),
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Switch
|
||||
field='unlimited_quota'
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
showError,
|
||||
showSuccess,
|
||||
renderQuota,
|
||||
renderQuotaWithPrompt,
|
||||
getCurrencyConfig,
|
||||
} from '../../../../helpers';
|
||||
import {
|
||||
@@ -46,6 +45,8 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
InputNumber,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconUser,
|
||||
@@ -53,7 +54,7 @@ import {
|
||||
IconClose,
|
||||
IconLink,
|
||||
IconUserGroup,
|
||||
IconPlus,
|
||||
IconEdit,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import UserBindingManagementModal from './UserBindingManagementModal';
|
||||
|
||||
@@ -63,13 +64,18 @@ const EditUserModal = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const userId = props.editingUser.id;
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [addQuotaModalOpen, setIsModalOpen] = useState(false);
|
||||
const [addQuotaLocal, setAddQuotaLocal] = useState('');
|
||||
const [addAmountLocal, setAddAmountLocal] = useState('');
|
||||
const [adjustModalOpen, setAdjustModalOpen] = useState(false);
|
||||
const [adjustQuotaLocal, setAdjustQuotaLocal] = useState('');
|
||||
const [adjustAmountLocal, setAdjustAmountLocal] = useState('');
|
||||
const [adjustMode, setAdjustMode] = useState('add');
|
||||
const [adjustLoading, setAdjustLoading] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [bindingModalVisible, setBindingModalVisible] = useState(false);
|
||||
const formApiRef = useRef(null);
|
||||
const [showAdjustQuotaRaw, setShowAdjustQuotaRaw] = useState(false);
|
||||
const [showQuotaInput, setShowQuotaInput] = useState(false);
|
||||
const [inputs, setInputs] = useState(null);
|
||||
|
||||
const isEdit = Boolean(userId);
|
||||
|
||||
@@ -85,6 +91,7 @@ const EditUserModal = (props) => {
|
||||
linux_do_id: '',
|
||||
email: '',
|
||||
quota: 0,
|
||||
quota_amount: 0,
|
||||
group: 'default',
|
||||
remark: '',
|
||||
});
|
||||
@@ -107,13 +114,22 @@ const EditUserModal = (props) => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
data.password = '';
|
||||
formApiRef.current?.setValues({ ...getInitValues(), ...data });
|
||||
data.quota_amount = Number(
|
||||
quotaToDisplayAmount(data.quota || 0).toFixed(6),
|
||||
);
|
||||
setInputs({ ...getInitValues(), ...data });
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (inputs && formApiRef.current) {
|
||||
formApiRef.current.setValues(inputs);
|
||||
}
|
||||
}, [inputs]);
|
||||
|
||||
useEffect(() => {
|
||||
loadUser();
|
||||
if (userId) fetchGroups();
|
||||
@@ -132,8 +148,8 @@ const EditUserModal = (props) => {
|
||||
const submit = async (values) => {
|
||||
setLoading(true);
|
||||
let payload = { ...values };
|
||||
if (typeof payload.quota === 'string')
|
||||
payload.quota = parseInt(payload.quota) || 0;
|
||||
delete payload.quota;
|
||||
delete payload.quota_amount;
|
||||
if (userId) {
|
||||
payload.id = parseInt(userId);
|
||||
}
|
||||
@@ -150,11 +166,60 @@ const EditUserModal = (props) => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
/* --------------------- quota helper -------------------- */
|
||||
const addLocalQuota = () => {
|
||||
const current = parseInt(formApiRef.current?.getValue('quota') || 0);
|
||||
const delta = parseInt(addQuotaLocal) || 0;
|
||||
formApiRef.current?.setValue('quota', current + delta);
|
||||
/* --------------------- atomic quota adjust -------------------- */
|
||||
const adjustQuota = async () => {
|
||||
const quotaVal = parseInt(adjustQuotaLocal) || 0;
|
||||
if (quotaVal <= 0 && adjustMode !== 'override') return;
|
||||
if (adjustMode === 'override' && (adjustQuotaLocal === '' || adjustQuotaLocal == null)) return;
|
||||
setAdjustLoading(true);
|
||||
try {
|
||||
const res = await API.post('/api/user/manage', {
|
||||
id: parseInt(userId),
|
||||
action: 'add_quota',
|
||||
mode: adjustMode,
|
||||
value: adjustMode === 'override' ? quotaVal : Math.abs(quotaVal),
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('调整额度成功'));
|
||||
setAdjustModalOpen(false);
|
||||
setAdjustQuotaLocal('');
|
||||
setAdjustAmountLocal('');
|
||||
const userRes = await API.get(`/api/user/${userId}`);
|
||||
if (userRes.data.success) {
|
||||
const data = userRes.data.data;
|
||||
data.password = '';
|
||||
data.quota_amount = Number(
|
||||
quotaToDisplayAmount(data.quota || 0).toFixed(6),
|
||||
);
|
||||
setInputs({ ...getInitValues(), ...data });
|
||||
}
|
||||
props.refresh();
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (e) {
|
||||
showError(e.message);
|
||||
}
|
||||
setAdjustLoading(false);
|
||||
};
|
||||
|
||||
const getPreviewText = () => {
|
||||
const current = formApiRef.current?.getValue('quota') || 0;
|
||||
const val = parseInt(adjustQuotaLocal) || 0;
|
||||
let result;
|
||||
switch (adjustMode) {
|
||||
case 'add':
|
||||
result = current + Math.abs(val);
|
||||
return `${t('当前额度')}:${renderQuota(current)},+${renderQuota(Math.abs(val))} = ${renderQuota(result)}`;
|
||||
case 'subtract':
|
||||
result = current - Math.abs(val);
|
||||
return `${t('当前额度')}:${renderQuota(current)},-${renderQuota(Math.abs(val))} = ${renderQuota(result)}`;
|
||||
case 'override':
|
||||
return `${t('当前额度')}:${renderQuota(current)} → ${renderQuota(val)}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/* --------------------------- UI --------------------------- */
|
||||
@@ -305,24 +370,47 @@ const EditUserModal = (props) => {
|
||||
|
||||
<Col span={10}>
|
||||
<Form.InputNumber
|
||||
field='quota'
|
||||
label={t('剩余额度')}
|
||||
placeholder={t('请输入新的剩余额度')}
|
||||
step={500000}
|
||||
extraText={renderQuotaWithPrompt(values.quota || 0)}
|
||||
rules={[{ required: true, message: t('请输入额度') }]}
|
||||
field='quota_amount'
|
||||
label={t('金额')}
|
||||
prefix={getCurrencyConfig().symbol}
|
||||
precision={6}
|
||||
step={0.000001}
|
||||
style={{ width: '100%' }}
|
||||
readonly
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col span={14}>
|
||||
<Form.Slot label={t('添加额度')}>
|
||||
<Form.Slot label={t('调整额度')}>
|
||||
<Button
|
||||
icon={<IconPlus />}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
/>
|
||||
icon={<IconEdit />}
|
||||
onClick={() => setAdjustModalOpen(true)}
|
||||
>
|
||||
{t('调整额度')}
|
||||
</Button>
|
||||
</Form.Slot>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<div
|
||||
className='text-xs cursor-pointer'
|
||||
style={{ color: 'var(--semi-color-text-2)' }}
|
||||
onClick={() => setShowQuotaInput((v) => !v)}
|
||||
>
|
||||
{showQuotaInput
|
||||
? `▾ ${t('收起原生额度输入')}`
|
||||
: `▸ ${t('使用原生额度输入')}`}
|
||||
</div>
|
||||
<div style={{ display: showQuotaInput ? 'block' : 'none' }} className='mt-2'>
|
||||
<Form.InputNumber
|
||||
field='quota'
|
||||
label={t('额度')}
|
||||
placeholder={t('请输入额度')}
|
||||
style={{ width: '100%' }}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)}
|
||||
@@ -372,81 +460,102 @@ const EditUserModal = (props) => {
|
||||
formApiRef={formApiRef}
|
||||
/>
|
||||
|
||||
{/* 添加额度模态框 */}
|
||||
{/* 调整额度模态框 */}
|
||||
<Modal
|
||||
centered
|
||||
visible={addQuotaModalOpen}
|
||||
onOk={() => {
|
||||
addLocalQuota();
|
||||
setIsModalOpen(false);
|
||||
setAddQuotaLocal('');
|
||||
setAddAmountLocal('');
|
||||
}}
|
||||
visible={adjustModalOpen}
|
||||
onOk={adjustQuota}
|
||||
onCancel={() => {
|
||||
setIsModalOpen(false);
|
||||
setAdjustModalOpen(false);
|
||||
setAdjustQuotaLocal('');
|
||||
setAdjustAmountLocal('');
|
||||
setAdjustMode('add');
|
||||
}}
|
||||
confirmLoading={adjustLoading}
|
||||
closable={null}
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<IconPlus className='mr-2' />
|
||||
{t('添加额度')}
|
||||
<IconEdit className='mr-2' />
|
||||
{t('调整额度')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='mb-4'>
|
||||
{(() => {
|
||||
const current = formApiRef.current?.getValue('quota') || 0;
|
||||
return (
|
||||
<Text type='secondary' className='block mb-2'>
|
||||
{`${t('新额度:')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`}
|
||||
</Text>
|
||||
);
|
||||
})()}
|
||||
<Text type='secondary' className='block mb-2'>
|
||||
{getPreviewText()}
|
||||
</Text>
|
||||
</div>
|
||||
{getCurrencyConfig().type !== 'TOKENS' && (
|
||||
<div className='mb-3'>
|
||||
<div className='mb-1'>
|
||||
<Text size='small'>{t('金额')}</Text>
|
||||
<Text size='small' type='tertiary'>
|
||||
{' '}
|
||||
({t('仅用于换算,实际保存的是额度')})
|
||||
</Text>
|
||||
</div>
|
||||
<InputNumber
|
||||
prefix={getCurrencyConfig().symbol}
|
||||
placeholder={t('输入金额')}
|
||||
value={addAmountLocal}
|
||||
precision={2}
|
||||
onChange={(val) => {
|
||||
setAddAmountLocal(val);
|
||||
setAddQuotaLocal(
|
||||
val != null && val !== ''
|
||||
? displayAmountToQuota(Math.abs(val)) * Math.sign(val)
|
||||
: '',
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
showClear
|
||||
/>
|
||||
<div className='mb-3'>
|
||||
<div className='mb-1'>
|
||||
<Text size='small'>{t('操作')}</Text>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<RadioGroup
|
||||
type='button'
|
||||
value={adjustMode}
|
||||
onChange={(e) => {
|
||||
setAdjustMode(e.target.value);
|
||||
setAdjustQuotaLocal('');
|
||||
setAdjustAmountLocal('');
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Radio value='add'>{t('添加')}</Radio>
|
||||
<Radio value='subtract'>{t('减少')}</Radio>
|
||||
<Radio value='override'>{t('覆盖')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<div className='mb-1'>
|
||||
<Text size='small'>{t('金额')}</Text>
|
||||
</div>
|
||||
<InputNumber
|
||||
prefix={getCurrencyConfig().symbol}
|
||||
placeholder={t('输入金额')}
|
||||
value={adjustAmountLocal}
|
||||
precision={6}
|
||||
min={adjustMode === 'override' ? undefined : 0}
|
||||
step={0.000001}
|
||||
onChange={(val) => {
|
||||
const amount = val === '' || val == null ? '' : val;
|
||||
setAdjustAmountLocal(amount);
|
||||
setAdjustQuotaLocal(
|
||||
amount === ''
|
||||
? ''
|
||||
: adjustMode === 'override'
|
||||
? displayAmountToQuota(amount)
|
||||
: displayAmountToQuota(Math.abs(amount)),
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className='text-xs cursor-pointer mt-2'
|
||||
style={{ color: 'var(--semi-color-text-2)' }}
|
||||
onClick={() => setShowAdjustQuotaRaw((v) => !v)}
|
||||
>
|
||||
{showAdjustQuotaRaw
|
||||
? `▾ ${t('收起原生额度输入')}`
|
||||
: `▸ ${t('使用原生额度输入')}`}
|
||||
</div>
|
||||
<div style={{ display: showAdjustQuotaRaw ? 'block' : 'none' }} className='mt-2'>
|
||||
<div className='mb-1'>
|
||||
<Text size='small'>{t('额度')}</Text>
|
||||
</div>
|
||||
<InputNumber
|
||||
placeholder={t('输入额度')}
|
||||
value={addQuotaLocal}
|
||||
value={adjustQuotaLocal}
|
||||
min={adjustMode === 'override' ? undefined : 0}
|
||||
onChange={(val) => {
|
||||
setAddQuotaLocal(val);
|
||||
setAddAmountLocal(
|
||||
val != null && val !== ''
|
||||
? Number(
|
||||
(
|
||||
quotaToDisplayAmount(Math.abs(val)) * Math.sign(val)
|
||||
).toFixed(2),
|
||||
)
|
||||
: '',
|
||||
const quota = val === '' || val == null ? '' : val;
|
||||
setAdjustQuotaLocal(quota);
|
||||
setAdjustAmountLocal(
|
||||
quota === ''
|
||||
? ''
|
||||
: adjustMode === 'override'
|
||||
? Number(quotaToDisplayAmount(quota).toFixed(6))
|
||||
: Number(quotaToDisplayAmount(Math.abs(quota)).toFixed(6)),
|
||||
);
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
|
||||
Reference in New Issue
Block a user