From 42b4a5a95b8f77de5b25af8a469b11f55f1dcf4d Mon Sep 17 00:00:00 2001 From: xiaosi <2652281683@qq.com> Date: Wed, 2 Sep 2026 11:44:34 +0800 Subject: [PATCH] feat: grant test traffic from TrafficOps dialog --- src/views/TrafficOpsView/TrafficOpsView.vue | 83 ++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/src/views/TrafficOpsView/TrafficOpsView.vue b/src/views/TrafficOpsView/TrafficOpsView.vue index 609cd87..9b1f385 100644 --- a/src/views/TrafficOpsView/TrafficOpsView.vue +++ b/src/views/TrafficOpsView/TrafficOpsView.vue @@ -770,12 +770,89 @@ function onGrantUserCleared() { grantSelectedUserId.value = '' } -async function searchGrantUsers(_keyword) { - // Task 3: remote user search +let grantSearchSeq = 0 +async function searchGrantUsers(keyword) { + const q = String(keyword || '').trim() + if (!q) { + grantUserOptions.value = [] + return + } + const seq = ++grantSearchSeq + grantUserSearching.value = true + try { + const page = await request.get(urls.USERS, { + params: { keyword: q, role: 'user', pageNum: 1, pageSize: 20 } + }) + if (seq !== grantSearchSeq) return + grantUserOptions.value = (page?.records || []).map((u) => ({ + label: `${u.name || '--'} · ${u.phone || '--'} · ${u.id}`, + value: String(u.id) + })) + } catch (e) { + if (seq !== grantSearchSeq) return + grantUserOptions.value = [] + // http 拦截器已 toast;此处不重复 + } finally { + if (seq === grantSearchSeq) grantUserSearching.value = false + } +} + +function parseGrantBalanceText(data) { + if (data == null) return '已发放(未返回余额字段)' + if (typeof data === 'number' || (typeof data === 'string' && data.trim() !== '' && !Number.isNaN(Number(data)))) { + const n = Number(data) + if (!Number.isFinite(n)) return '已发放(未返回余额字段)' + // 很大则按 bytes,否则按 GB + return n >= GB_BYTES ? bytesToGbText(n) : `${n.toFixed(2)} GB` + } + if (typeof data !== 'object') return '已发放(未返回余额字段)' + if (data.trafficBalanceBytes != null) return bytesToGbText(data.trafficBalanceBytes) + if (data.balanceBytes != null) return bytesToGbText(data.balanceBytes) + if (data.trafficBalance != null) { + const n = Number(data.trafficBalance) + if (!Number.isFinite(n)) return '已发放(未返回余额字段)' + return n >= GB_BYTES ? bytesToGbText(n) : `${n.toFixed(2)} GB` + } + if (data.balance != null) { + const n = Number(data.balance) + if (!Number.isFinite(n)) return '已发放(未返回余额字段)' + return n >= GB_BYTES ? bytesToGbText(n) : `${n.toFixed(2)} GB` + } + return '已发放(未返回余额字段)' } async function submitGrant() { - // Task 3: validate + POST test-grants + grantFormError.value = '' + const userIdNum = Number(String(grantForm.userId || '').trim()) + const amountGb = Number(grantForm.amountGb) + const reason = String(grantForm.reason || '').trim() + if (!Number.isInteger(userIdNum) || userIdNum <= 0) { + grantFormError.value = '请填写有效的用户 ID' + return + } + if (!Number.isFinite(amountGb) || amountGb < 1 || !Number.isInteger(amountGb)) { + grantFormError.value = '额度须为不小于 1 的整数 GB' + return + } + if (!reason) { + grantFormError.value = '请填写原因' + return + } + grantSubmitting.value = true + try { + const data = await request.post(urls.ADMIN_TRAFFIC_TEST_GRANTS, { + userId: userIdNum, + amountGb, + reason + }) + grantResultBalanceText.value = parseGrantBalanceText(data) + ui.toast('测试流量已发放') + } catch (e) { + // 拦截器已 toast;保留对话框便于改参重试 + if (!e?.message) ui.toast('发放失败') + } finally { + grantSubmitting.value = false + } }