From 85f53ef2dd3380be76d1a47524bb273a0b382f45 Mon Sep 17 00:00:00 2001 From: ianshaw Date: Sun, 4 Jan 2026 20:22:59 -0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E5=AE=8C=E5=96=84=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=A0=A1=E9=AA=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserEditModal: 添加 email 必填和 concurrency 最小值校验 - UserAttributesConfigModal: 添加 key/name 必填和 options 非空校验 - GroupsView: 添加 name 必填校验 - ProxiesView: 添加 name/host 必填和 port 范围校验 - UserBalanceModal: 添加 amount 有效性和余额充足性校验 - RedeemView: 添加空兑换码错误提示 - i18n: 添加所有新增校验的中英文翻译 --- .../admin/user/UserBalanceModal.vue | 15 ++++++++++-- .../components/admin/user/UserEditModal.vue | 8 +++++++ .../user/UserAttributesConfigModal.vue | 12 ++++++++++ frontend/src/i18n/locales/en.ts | 15 ++++++++++-- frontend/src/i18n/locales/zh.ts | 15 ++++++++++-- frontend/src/views/admin/GroupsView.vue | 8 +++++++ frontend/src/views/admin/ProxiesView.vue | 24 +++++++++++++++++++ frontend/src/views/user/RedeemView.vue | 1 + 8 files changed, 92 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/admin/user/UserBalanceModal.vue b/frontend/src/components/admin/user/UserBalanceModal.vue index 19e9ccab..31e242f2 100644 --- a/frontend/src/components/admin/user/UserBalanceModal.vue +++ b/frontend/src/components/admin/user/UserBalanceModal.vue @@ -37,10 +37,21 @@ watch(() => props.show, (v) => { if(v) { form.amount = 0; form.notes = '' } }) const calculateNewBalance = () => (props.user ? (props.operation === 'add' ? props.user.balance + form.amount : props.user.balance - form.amount) : 0) const handleBalanceSubmit = async () => { - if (!props.user) return; submitting.value = true + if (!props.user) return + if (!form.amount || form.amount <= 0) { + appStore.showError(t('admin.users.amountRequired')) + return + } + if (props.operation === 'subtract' && form.amount > props.user.balance) { + appStore.showError(t('admin.users.insufficientBalance')) + return + } + submitting.value = true try { await adminAPI.users.updateBalance(props.user.id, form.amount, props.operation, form.notes) appStore.showSuccess(t('common.success')); emit('success'); emit('close') - } catch {} finally { submitting.value = false } + } catch (e: any) { + appStore.showError(e.response?.data?.detail || t('common.error')) + } finally { submitting.value = false } } \ No newline at end of file diff --git a/frontend/src/components/admin/user/UserEditModal.vue b/frontend/src/components/admin/user/UserEditModal.vue index 3f6fd206..43f677d3 100644 --- a/frontend/src/components/admin/user/UserEditModal.vue +++ b/frontend/src/components/admin/user/UserEditModal.vue @@ -86,6 +86,14 @@ const copyPassword = async () => { } const handleUpdateUser = async () => { if (!props.user) return + if (!form.email.trim()) { + appStore.showError(t('admin.users.emailRequired')) + return + } + if (form.concurrency < 1) { + appStore.showError(t('admin.users.concurrencyMin')) + return + } submitting.value = true try { const data: any = { email: form.email, username: form.username, notes: form.notes, concurrency: form.concurrency } diff --git a/frontend/src/components/user/UserAttributesConfigModal.vue b/frontend/src/components/user/UserAttributesConfigModal.vue index 07b9816d..3e1186e4 100644 --- a/frontend/src/components/user/UserAttributesConfigModal.vue +++ b/frontend/src/components/user/UserAttributesConfigModal.vue @@ -344,6 +344,18 @@ const removeOption = (index: number) => { } const handleSave = async () => { + if (!form.key.trim()) { + appStore.showError(t('admin.users.attributes.keyRequired')) + return + } + if (!form.name.trim()) { + appStore.showError(t('admin.users.attributes.nameRequired')) + return + } + if ((form.type === 'select' || form.type === 'multi_select') && form.options.length === 0) { + appStore.showError(t('admin.users.attributes.optionsRequired')) + return + } saving.value = true try { const data = { diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 29328fe0..2b309af1 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -462,7 +462,8 @@ export default { days: ' days', codeRedeemSuccess: 'Code redeemed successfully!', failedToRedeem: 'Failed to redeem code. Please check the code and try again.', - subscriptionRefreshFailed: 'Redeemed successfully, but failed to refresh subscription status.' + subscriptionRefreshFailed: 'Redeemed successfully, but failed to refresh subscription status.', + pleaseEnterCode: 'Please enter a redeem code' }, // Profile @@ -658,6 +659,10 @@ export default { failedToDelete: 'Failed to delete user', failedToToggle: 'Failed to update user status', failedToLoadApiKeys: 'Failed to load user API keys', + emailRequired: 'Please enter email', + concurrencyMin: 'Concurrency must be at least 1', + amountRequired: 'Please enter a valid amount', + insufficientBalance: 'Insufficient balance', deleteConfirm: "Are you sure you want to delete '{email}'? This action cannot be undone.", setAllowedGroups: 'Set Allowed Groups', allowedGroupsHint: @@ -689,7 +694,6 @@ export default { failedToDeposit: 'Failed to deposit', failedToWithdraw: 'Failed to withdraw', useDepositWithdrawButtons: 'Please use deposit/withdraw buttons to adjust balance', - insufficientBalance: 'Insufficient balance, balance cannot be negative after withdrawal', roles: { admin: 'Admin', user: 'User' @@ -749,6 +753,9 @@ export default { failedToLoad: 'Failed to load attributes', failedToCreate: 'Failed to create attribute', failedToUpdate: 'Failed to update attribute', + keyRequired: 'Please enter attribute key', + nameRequired: 'Please enter display name', + optionsRequired: 'Please add at least one option', failedToDelete: 'Failed to delete attribute', failedToReorder: 'Failed to update order', keyExists: 'Attribute key already exists', @@ -816,6 +823,7 @@ export default { failedToCreate: 'Failed to create group', failedToUpdate: 'Failed to update group', failedToDelete: 'Failed to delete group', + nameRequired: 'Please enter group name', platforms: { all: 'All Platforms', anthropic: 'Anthropic', @@ -1584,6 +1592,9 @@ export default { failedToUpdate: 'Failed to update proxy', failedToDelete: 'Failed to delete proxy', failedToTest: 'Failed to test proxy', + nameRequired: 'Please enter proxy name', + hostRequired: 'Please enter host address', + portInvalid: 'Port must be between 1-65535', deleteConfirm: "Are you sure you want to delete '{name}'? Accounts using this proxy will have their proxy removed." }, diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index cf4b91fc..25dd929c 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -459,7 +459,8 @@ export default { days: '天', codeRedeemSuccess: '兑换成功!', failedToRedeem: '兑换失败,请检查兑换码后重试。', - subscriptionRefreshFailed: '兑换成功,但订阅状态刷新失败。' + subscriptionRefreshFailed: '兑换成功,但订阅状态刷新失败。', + pleaseEnterCode: '请输入兑换码' }, // Profile @@ -716,6 +717,10 @@ export default { concurrencyAdjustedSuccess: '并发数调整成功', failedToSave: '保存用户失败', failedToAdjust: '调整失败', + emailRequired: '请输入邮箱', + concurrencyMin: '并发数不能小于1', + amountRequired: '请输入有效金额', + insufficientBalance: '余额不足', setAllowedGroups: '设置允许分组', allowedGroupsHint: '选择此用户可以使用的标准分组。订阅类型分组请在订阅管理中配置。', noStandardGroups: '暂无标准分组', @@ -742,7 +747,6 @@ export default { failedToDeposit: '充值失败', failedToWithdraw: '退款失败', useDepositWithdrawButtons: '请使用充值/退款按钮调整余额', - insufficientBalance: '余额不足,退款后余额不能为负数', // Settings Dropdowns filterSettings: '筛选设置', columnSettings: '列设置', @@ -798,6 +802,9 @@ export default { failedToLoad: '加载属性列表失败', failedToCreate: '创建属性失败', failedToUpdate: '更新属性失败', + keyRequired: '请输入属性键', + nameRequired: '请输入显示名称', + optionsRequired: '请至少添加一个选项', failedToDelete: '删除属性失败', failedToReorder: '更新排序失败', keyExists: '属性键已存在', @@ -905,6 +912,7 @@ export default { groupDeleted: '分组删除成功', failedToCreate: '创建分组失败', failedToUpdate: '更新分组失败', + nameRequired: '请输入分组名称', subscription: { title: '订阅设置', type: '计费类型', @@ -1694,6 +1702,9 @@ export default { failedToCreate: '创建代理失败', failedToUpdate: '更新代理失败', failedToTest: '测试代理失败', + nameRequired: '请输入代理名称', + hostRequired: '请输入主机地址', + portInvalid: '端口必须在 1-65535 之间', deleteConfirm: "确定要删除代理 '{name}' 吗?使用此代理的账号将被移除代理设置。" }, diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue index f22d1e0d..95918e6e 100644 --- a/frontend/src/views/admin/GroupsView.vue +++ b/frontend/src/views/admin/GroupsView.vue @@ -871,6 +871,10 @@ const closeCreateModal = () => { } const handleCreateGroup = async () => { + if (!createForm.name.trim()) { + appStore.showError(t('admin.groups.nameRequired')) + return + } submitting.value = true try { await adminAPI.groups.create(createForm) @@ -912,6 +916,10 @@ const closeEditModal = () => { const handleUpdateGroup = async () => { if (!editingGroup.value) return + if (!editForm.name.trim()) { + appStore.showError(t('admin.groups.nameRequired')) + return + } submitting.value = true try { diff --git a/frontend/src/views/admin/ProxiesView.vue b/frontend/src/views/admin/ProxiesView.vue index 9a41e950..47e4f15e 100644 --- a/frontend/src/views/admin/ProxiesView.vue +++ b/frontend/src/views/admin/ProxiesView.vue @@ -887,6 +887,18 @@ const handleBatchCreate = async () => { } const handleCreateProxy = async () => { + if (!createForm.name.trim()) { + appStore.showError(t('admin.proxies.nameRequired')) + return + } + if (!createForm.host.trim()) { + appStore.showError(t('admin.proxies.hostRequired')) + return + } + if (createForm.port < 1 || createForm.port > 65535) { + appStore.showError(t('admin.proxies.portInvalid')) + return + } submitting.value = true try { await adminAPI.proxies.create({ @@ -927,6 +939,18 @@ const closeEditModal = () => { const handleUpdateProxy = async () => { if (!editingProxy.value) return + if (!editForm.name.trim()) { + appStore.showError(t('admin.proxies.nameRequired')) + return + } + if (!editForm.host.trim()) { + appStore.showError(t('admin.proxies.hostRequired')) + return + } + if (editForm.port < 1 || editForm.port > 65535) { + appStore.showError(t('admin.proxies.portInvalid')) + return + } submitting.value = true try { diff --git a/frontend/src/views/user/RedeemView.vue b/frontend/src/views/user/RedeemView.vue index 78650aac..e87720d1 100644 --- a/frontend/src/views/user/RedeemView.vue +++ b/frontend/src/views/user/RedeemView.vue @@ -531,6 +531,7 @@ const fetchHistory = async () => { const handleRedeem = async () => { if (!redeemCode.value.trim()) { + appStore.showError(t('redeem.pleaseEnterCode')) return }