diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index b7d56fb..2c87156 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -21,7 +21,7 @@ jobs: run: | export PATH="$HOME/.nix-profile/bin:/usr/local/bin:/usr/bin:/bin:$PATH" export GIT_SSL_NO_VERIFY=1 - git clone https://git.boss160.cn/luo/gt-agent-company.git . + git clone https://git.boss160.cn/luo/agent-xm-iot.git . git checkout ${{ github.sha }} - name: 设置镜像标签 diff --git a/docker-services.conf b/docker-services.conf index eee5599..beaedf0 100644 --- a/docker-services.conf +++ b/docker-services.conf @@ -1,5 +1,5 @@ server { - server_name cmp-api.boss160.cn; + server_name cmp-api.xm-iot.cn; location / { proxy_pass http://127.0.0.1:3000; @@ -26,8 +26,8 @@ server { listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/cmp-api.boss160.cn/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/cmp-api.boss160.cn/privkey.pem; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/cmp-api.xm-iot.cn/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/cmp-api.xm-iot.cn/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot @@ -171,12 +171,12 @@ server { } server { - if ($host = cmp-api.boss160.cn) { + if ($host = cmp-api.xm-iot.cn) { return 301 https://$host$request_uri; } # managed by Certbot - server_name cmp-api.boss160.cn; + server_name cmp-api.xm-iot.cn; listen 80; return 404; # managed by Certbot diff --git a/docs/停复机.md b/docs/停复机.md index 0e87c48..931c506 100644 --- a/docs/停复机.md +++ b/docs/停复机.md @@ -132,7 +132,7 @@ POST /api/admin/assets/device/12345/stop ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn +- **测试环境**:https://cmp-api.xm-iot.cn # 设备复机 @@ -232,7 +232,7 @@ POST /api/admin/assets/device/12345/start ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn +- **测试环境**:https://cmp-api.xm-iot.cn # 单卡停机 @@ -332,7 +332,7 @@ POST /api/admin/assets/card/89860123456789012345/stop ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn +- **测试环境**:https://cmp-api.xm-iot.cn # 单卡复机 @@ -424,4 +424,4 @@ POST /api/admin/assets/card/89860123456789012345/start ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn \ No newline at end of file +- **测试环境**:https://cmp-api.xm-iot.cn \ No newline at end of file diff --git a/docs/资产详情.md b/docs/资产详情.md index d40f72e..2553322 100644 --- a/docs/资产详情.md +++ b/docs/资产详情.md @@ -195,7 +195,7 @@ GET /api/admin/assets/card/12345/packages?page=1&page_size=50 ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn +- **测试环境**:https://cmp-api.xm-iot.cn # 当前生效套餐 @@ -330,4 +330,4 @@ GET /api/admin/assets/card/12345/current-package ## 服务器地址 -- **测试环境**:https://cmp-api.boss160.cn \ No newline at end of file +- **测试环境**:https://cmp-api.xm-iot.cn \ No newline at end of file diff --git a/docs/资产详情流量-spec.md b/docs/资产详情流量-spec.md new file mode 100644 index 0000000..4157b1b --- /dev/null +++ b/docs/资产详情流量-spec.md @@ -0,0 +1,227 @@ +# 资产详情流量展示 Spec + +## 1. 背景 + +原始说明见 `docs/资产详情流量.md`。 + +本次需要统一资产详情中套餐流量的展示口径,避免“当前生效套餐”和“套餐列表”出现字段选择不一致、剩余流量计算不一致的问题。 + +本 Spec 仅定义前端展示和计算规则,不涉及后端接口改造。 + +--- + +## 2. 适用范围 + +### 页面范围 + +- 资产详情页:`src/pages/agent-system/asset-detail/index.vue` + +### 联动范围 + +- 资产搜索页中的同类套餐展示:`src/pages/agent-system/asset-search/index.vue` + +### 本次覆盖的 UI 区块 + +- 当前生效套餐卡片 +- 套餐列表中的流量摘要 +- 套餐进度条文案与进度百分比 + +### 不在本次范围内 + +- 后端接口字段定义 +- 其他页面中与套餐无关的流量汇总展示 + +--- + +## 3. 数据来源 + +### 当前生效套餐 + +- 接口:`GET /api/admin/assets/:identifier/current-package` + +### 套餐列表 + +- 接口:`GET /api/admin/assets/:identifier/packages` + +### 本次展示使用的关键字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `real_total_mb` | number | 真流量总量 | +| `real_used_mb` | number | 真流量已使用 | +| `virtual_used_mb` | number | 虚流量已使用 | +| `enable_virtual_data` | boolean | 是否按虚流量口径展示已使用 | +| `paid_amount` | number | 套餐金额,单位分 | + +说明: + +- 本次总流量固定使用 `real_total_mb` +- 本次已使用根据 `enable_virtual_data` 在 `virtual_used_mb` 和 `real_used_mb` 间切换 +- 本次剩余流量以前端计算结果为准,不直接取其他剩余字段 + +--- + +## 4. 统一展示口径 + +| 展示项 | 计算规则 | 备注 | +|------|------|------| +| 总流量 | `real_total_mb` | 固定使用该字段 | +| 已使用 | `enable_virtual_data === true ? virtual_used_mb : real_used_mb` | 仅做字段切换,不做额外换算 | +| 剩余 | `max(总流量 - 已使用, 0)` | 与页面展示的“已使用”保持同口径 | +| 进度百分比 | `total <= 0 ? 0 : min(已使用 / 总流量 * 100, 100)` | 进度条与文案必须一致 | + +### 明确约束 + +1. 不使用 `virtual_total_mb` 作为“总流量”展示值。 +2. 不使用 `real_used_mb` 单独计算“剩余”,必须使用页面实际展示的“已使用”值参与计算。 +3. 不因为字段名差异而在前端自行做虚实流量换算。 + +--- + +## 5. 页面展示要求 + +### 5.1 当前生效套餐 + +当前卡片中的以下区域必须使用统一口径: + +- `总量`:显示 `real_total_mb` +- `已使用`:根据 `enable_virtual_data` 切换展示 +- `剩余`:使用 `总量 - 已使用` +- 进度条文案:`已使用 / 总量` +- 进度条宽度:使用同一组 `已使用`、`总量` 计算 +- `总量`、`已使用`、`剩余` 的展示值统一保留两位小数 + +### 5.2 套餐列表 + +套餐列表每一项中当前展示的 `已用 / 总量` 必须使用相同口径: + +- `已用`:根据 `enable_virtual_data` 切换 +- `总量`:固定使用 `real_total_mb` +- `剩余`:显示 `总量 - 已使用` +- `金额`:显示 `paid_amount / 100` 后的元金额 +- `总量`、`已用`、`剩余` 的展示值统一保留两位小数 + +如果后续套餐列表新增“剩余”字段,必须沿用本 Spec 中的剩余计算规则,不得单独定义。 + +--- + +## 6. 边界处理 + +### 空值处理 + +- `null`、`undefined`、`NaN` 按 `0` 处理 + +### 超限处理 + +- 当 `已使用 > 总流量` 时: + - `剩余` 显示 `0` + - 进度条按 `100%` 展示 + +### 总量为 0 + +- `总量 = 0` 时: + - `已使用` 按规则正常显示 + - `剩余` 显示 `0` + - 进度条显示 `0%` + +--- + +## 7. 实现要求 + +### 统一计算入口 + +建议统一封装以下计算函数,并在相关页面复用: + +- `getPackageTotalMb` +- `getPackageUsedMb` +- `getPackageRemainMb` +- `getPackageUsagePercent` + +### 关键实现要求 + +- `getPackageRemainMb` 必须改为基于“当前展示的已使用值”计算 +- `asset-detail` 与 `asset-search` 不允许维护不同口径的同名逻辑 +- 格式化继续沿用现有 `formatDataSize`,本次不单独引入新的单位换算规则 + +--- + +## 8. 验收标准 + +### 验收用例 1:`enable_virtual_data = true` + +输入: + +```json +{ + "real_total_mb": 1024000, + "real_used_mb": 7848, + "virtual_used_mb": 11211.425208, + "enable_virtual_data": true +} +``` + +期望: + +- 总流量 = `1024000` +- 已使用 = `11211.425208` +- 剩余 = `1012788.574792` +- 进度百分比约 = `1.09%` + +格式化后参考展示: + +- 总量:`1000.00GB` +- 已使用:`10.95GB` +- 剩余:`989.05GB` + +### 验收用例 2:`enable_virtual_data = false` + +输入: + +```json +{ + "real_total_mb": 1024000, + "real_used_mb": 7848, + "virtual_used_mb": 11211.425208, + "enable_virtual_data": false +} +``` + +期望: + +- 总流量 = `1024000` +- 已使用 = `7848` +- 剩余 = `1016152` + +### 验收用例 3:超限 + +输入: + +```json +{ + "real_total_mb": 100, + "real_used_mb": 120, + "virtual_used_mb": 120, + "enable_virtual_data": false +} +``` + +期望: + +- 已使用 = `120` +- 剩余 = `0` +- 进度条 = `100%` + +### 验收用例 4:跨页面一致性 + +- 同一套餐数据在 `asset-detail` 与 `asset-search` 中展示结果一致 +- 当前生效套餐卡片与套餐列表摘要使用同一套字段口径 + +--- + +## 9. 结论 + +本次变更的核心是统一三件事: + +1. 总流量固定取 `real_total_mb` +2. 已使用根据 `enable_virtual_data` 选择 `virtual_used_mb` 或 `real_used_mb` +3. 剩余必须使用 `总流量 - 已使用` 计算,不能再固定减 `real_used_mb` diff --git a/docs/资产详情流量.md b/docs/资产详情流量.md new file mode 100644 index 0000000..d9c0add --- /dev/null +++ b/docs/资产详情流量.md @@ -0,0 +1,34 @@ +/api/admin/assets/862639075995821/current-package +响应: +{ +"code": 0, +"data": { +"package_usage_id": 2, +"package_id": 2, +"package_name": "年套餐", +"package_type": "formal", +"order_id": 2, +"order_no": "ORD20260428105811360190", +"usage_type": "device", +"status": 1, +"status_name": "生效中", +"real_total_mb": 1024000, +"real_used_mb": 7848, +"virtual_total_mb": 716800, +"virtual_used_mb": 11211.425208, +"reduction_pct": 0.42857100000000004, +"enable_virtual_data": true, +"activated_at": "2026-04-28T10:58:11.377734+08:00", +"expires_at": "2027-04-30T23:59:59+08:00", +"priority": 1, +"paid_amount": 19900, +"created_at": "2026-04-28T10:58:11.554516+08:00" +}, +"msg": "success", +"timestamp": "2026-05-08T10:00:55+08:00" +} +资产详情里面的,当前生效套餐和套餐列表里面的流量 + +总流量用的就是real_total_mb, +已使用: 如果 enable_virtual_data: true 显示:virtual_used_mb false:real_used_mb, +剩余: 总流量-已使用 diff --git a/env/.env b/env/.env index 2fd8ecc..3a01f91 100644 --- a/env/.env +++ b/env/.env @@ -5,7 +5,7 @@ VITE_APP_TITLE=代理企业管理系统 VITE_APP_ENV=development # 接口地址 -VITE_API_BASE_URL=https://cmp-api.boss160.cn +VITE_API_BASE_URL=https://cmp-api.xm-iot.cn # 端口号 VITE_APP_PORT=9527 diff --git a/env/.env.development b/env/.env.development index 313d9ce..774460f 100644 --- a/env/.env.development +++ b/env/.env.development @@ -2,7 +2,7 @@ VITE_APP_ENV=development # 接口地址 -VITE_API_BASE_URL=https://cmp-api.boss160.cn +VITE_API_BASE_URL=https://cmp-api.xm-iot.cn # 是否使用代理 (H5开发环境使用代理解决跨域) VITE_APP_PROXY=true diff --git a/env/.env.production b/env/.env.production index 7329d43..0d916b3 100644 --- a/env/.env.production +++ b/env/.env.production @@ -2,7 +2,7 @@ VITE_APP_ENV=production # 接口地址 -VITE_API_BASE_URL=https://cmp-api.boss160.cn +VITE_API_BASE_URL=https://cmp-api.xm-iot.cn # 是否使用代理 (生产环境不使用代理) VITE_APP_PROXY=false diff --git a/env/.env.test b/env/.env.test index 14407bd..9faf8cd 100644 --- a/env/.env.test +++ b/env/.env.test @@ -2,7 +2,7 @@ VITE_APP_ENV=staging # 接口地址 -VITE_API_BASE_URL=https://cmp-api.boss160.cn +VITE_API_BASE_URL=https://cmp-api.xm-iot.cn # 是否使用代理 (测试环境不使用代理) VITE_APP_PROXY=false diff --git a/src/manifest.json b/src/manifest.json index b336a50..db1394e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -70,9 +70,10 @@ }, "vueVersion" : "3", "h5" : { + "title" : "企业代理H5", + "template" : "index.html", "router" : { - "mode" : "hash", - "base" : "/h5/" + "mode" : "history" } } } diff --git a/src/pages/agent-system/asset-detail/index.vue b/src/pages/agent-system/asset-detail/index.vue index b3229ca..f15dbc5 100644 --- a/src/pages/agent-system/asset-detail/index.vue +++ b/src/pages/agent-system/asset-detail/index.vue @@ -2,7 +2,8 @@ import { ref, computed, onMounted } from 'vue' import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopAsset, startAsset, getAssetRealtimeStatus } from '@/api/assets' import type { AssetType, CardInfo, DeviceInfo, AssetPackage, HistoryRecord } from '@/api/assets' -import { formatDataSize, formatTime } from '@/utils/format' +import { formatTime } from '@/utils/format' +import { formatPackageDataSize, formatPackagePrice, getPackageRemainMb, getPackageTotalMb, getPackageUsagePercent, getPackageUsedMb } from '../package-display' // 资产类型 const assetType = ref('card') @@ -67,25 +68,6 @@ function getPackageStatusColor(status?: number) { return status !== undefined ? map[status] || { bg: '#f5f5f5', text: '#999' } : { bg: '#f5f5f5', text: '#999' } } -function getPackageUsedMb(pkg: AssetPackage) { - return Number(pkg.enable_virtual_data ? pkg.virtual_used_mb : pkg.real_used_mb) || 0 -} - -function getPackageTotalMb(pkg: AssetPackage) { - return Number(pkg.real_total_mb) || 0 -} - -function getPackageRemainMb(pkg: AssetPackage) { - return Math.max(0, getPackageTotalMb(pkg) - (Number(pkg.real_used_mb) || 0)) -} - -function getPackageUsagePercent(pkg: AssetPackage) { - const total = getPackageTotalMb(pkg) - if (total <= 0) - return 0 - return Math.min(100, (getPackageUsedMb(pkg) / total) * 100) -} - function getPackageTypeText(type?: AssetPackage['package_type']) { const map: Record = { formal: '正式套餐', @@ -94,12 +76,6 @@ function getPackageTypeText(type?: AssetPackage['package_type']) { return type ? map[type] || type : '-' } -function formatPackagePrice(amount?: number) { - if (amount === undefined || amount === null) - return '-' - return `¥${(amount / 100).toFixed(2)}` -} - // 页面加载 onMounted(() => { // 从路由参数获取 assetType 和 assetIdentifier @@ -548,15 +524,15 @@ function viewWallet() { 总量 - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} 剩余 - {{ formatDataSize(getPackageRemainMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }} @@ -564,8 +540,8 @@ function viewWallet() { 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} / - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} / + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} @@ -652,12 +628,20 @@ function viewWallet() { - 已用: {{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} + 已用: {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }} 优先级: {{ pkg.priority || '-' }} + + 剩余 + {{ formatPackageDataSize(getPackageRemainMb(pkg)) }} + + + 金额 + {{ formatPackagePrice(pkg.paid_amount) }} + {{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }} diff --git a/src/pages/agent-system/asset-search/index.vue b/src/pages/agent-system/asset-search/index.vue index 15ebefe..869160d 100644 --- a/src/pages/agent-system/asset-search/index.vue +++ b/src/pages/agent-system/asset-search/index.vue @@ -3,6 +3,7 @@ import { resolveAsset, getAssetRealtimeStatus, getCurrentPackage, getAssetPackages } from '@/api/assets' import type { CardInfo, DeviceInfo, AssetRealtimeStatus, AssetPackage } from '@/api/assets' import { formatTime, formatDataSize } from '@/utils/format' + import { formatPackageDataSize, formatPackagePrice, getPackageRemainMb, getPackageTotalMb, getPackageUsagePercent, getPackageUsedMb } from '../package-display' // 搜索关键词 const searchKeyword = ref('') @@ -200,19 +201,6 @@ return map[status] || { bg: '#f5f5f5', text: '#999' } } - // 计算套餐使用百分比 - function getPackageUsedMb(pkg : AssetPackage) { - return Number(pkg.enable_virtual_data ? pkg.virtual_used_mb : pkg.real_used_mb) || 0 - } - - function getPackageTotalMb(pkg : AssetPackage) { - return Number(pkg.real_total_mb) || 0 - } - - function getPackageRemainMb(pkg : AssetPackage) { - return Math.max(0, getPackageTotalMb(pkg) - (Number(pkg.real_used_mb) || 0)) - } - function getPackageTypeText(type ?: AssetPackage['package_type']) { const map : Record = { formal: '正式套餐', @@ -221,17 +209,6 @@ return type ? map[type] || type : '-' } - function formatPackagePrice(amount ?: number) { - if (amount === undefined || amount === null) return '-' - return `¥${(amount / 100).toFixed(2)}` - } - - function getPackageUsagePercent(pkg : AssetPackage) { - const total = getPackageTotalMb(pkg) - if (total <= 0) return 0 - return Math.min(100, (getPackageUsedMb(pkg) / total) * 100) - } - // 获取信号质量等级 (基于 RSRP) function getSignalQuality(rsrp ?: number) { if (rsrp === undefined) return { level: '未知', text: '无信号', color: '#999' } @@ -550,15 +527,15 @@ 总量 - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} 剩余 - {{ formatDataSize(getPackageRemainMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }} @@ -566,8 +543,8 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} / - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} / + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} @@ -620,15 +597,15 @@ 总量 - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} 剩余 - {{ formatDataSize(getPackageRemainMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }} @@ -636,8 +613,8 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} / - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} / + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} @@ -690,15 +667,15 @@ 总量 - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} 剩余 - {{ formatDataSize(getPackageRemainMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }} @@ -706,8 +683,8 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} / - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} / + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} @@ -832,9 +809,17 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} + {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }} + + 剩余 + {{ formatPackageDataSize(getPackageRemainMb(pkg)) }} + + + 金额 + {{ formatPackagePrice(pkg.paid_amount) }} + {{ formatTime(pkg.activated_at, 'date') }} 至 {{ formatTime(pkg.expires_at, 'date') }} @@ -1295,15 +1280,15 @@ 总量 - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} 剩余 - {{ formatDataSize(getPackageRemainMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }} @@ -1311,8 +1296,8 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(currentPackage)) }} / - {{ formatDataSize(getPackageTotalMb(currentPackage)) }} + {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} / + {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }} @@ -1438,9 +1423,17 @@ 已使用 - {{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} + {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }} + + 剩余 + {{ formatPackageDataSize(getPackageRemainMb(pkg)) }} + + + 金额 + {{ formatPackagePrice(pkg.paid_amount) }} + {{ formatTime(pkg.activated_at, 'date') }} 至 {{ formatTime(pkg.expires_at, 'date') }} diff --git a/src/pages/agent-system/package-display.ts b/src/pages/agent-system/package-display.ts new file mode 100644 index 0000000..5425bc6 --- /dev/null +++ b/src/pages/agent-system/package-display.ts @@ -0,0 +1,44 @@ +import type { AssetPackage } from '@/api/assets' + +export function getPackageUsedMb(pkg?: AssetPackage | null) { + if (!pkg) + return 0 + + return Number(pkg.enable_virtual_data ? pkg.virtual_used_mb : pkg.real_used_mb) || 0 +} + +export function getPackageTotalMb(pkg?: AssetPackage | null) { + if (!pkg) + return 0 + + return Number(pkg.real_total_mb) || 0 +} + +export function getPackageRemainMb(pkg?: AssetPackage | null) { + return Math.max(0, getPackageTotalMb(pkg) - getPackageUsedMb(pkg)) +} + +export function getPackageUsagePercent(pkg?: AssetPackage | null) { + const total = getPackageTotalMb(pkg) + if (total <= 0) + return 0 + + return Math.min(100, (getPackageUsedMb(pkg) / total) * 100) +} + +export function formatPackageDataSize(mb?: number | null) { + const value = Number(mb) || 0 + + if (value >= 1024) + return `${(value / 1024).toFixed(2)}GB` + + return `${value.toFixed(2)}MB` +} + +export function formatPackagePrice(amount?: number | null) { + if (amount === undefined || amount === null) + return '-' + + const yuan = amount / 100 + return Number.isInteger(yuan) ? `¥${yuan}` : `¥${yuan.toFixed(2)}` +}