fix: 流量保留两位小数

This commit is contained in:
sexygoat
2026-05-08 14:54:36 +08:00
parent f8bfc3ad69
commit b54d8f5b32
14 changed files with 379 additions and 96 deletions

View File

@@ -21,7 +21,7 @@ jobs:
run: | run: |
export PATH="$HOME/.nix-profile/bin:/usr/local/bin:/usr/bin:/bin:$PATH" export PATH="$HOME/.nix-profile/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
export GIT_SSL_NO_VERIFY=1 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 }} git checkout ${{ github.sha }}
- name: 设置镜像标签 - name: 设置镜像标签

View File

@@ -1,5 +1,5 @@
server { server {
server_name cmp-api.boss160.cn; server_name cmp-api.xm-iot.cn;
location / { location / {
proxy_pass http://127.0.0.1:3000; proxy_pass http://127.0.0.1:3000;
@@ -26,8 +26,8 @@ server {
listen 443 ssl; # managed by Certbot listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/cmp-api.boss160.cn/fullchain.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.boss160.cn/privkey.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 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
@@ -171,12 +171,12 @@ server {
} }
server { server {
if ($host = cmp-api.boss160.cn) { if ($host = cmp-api.xm-iot.cn) {
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
} # managed by Certbot } # managed by Certbot
server_name cmp-api.boss160.cn; server_name cmp-api.xm-iot.cn;
listen 80; listen 80;
return 404; # managed by Certbot return 404; # managed by Certbot

View File

@@ -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 - **测试环境**https://cmp-api.xm-iot.cn

View File

@@ -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 - **测试环境**https://cmp-api.xm-iot.cn

View File

@@ -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`

View File

@@ -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,
剩余: 总流量-已使用

2
env/.env vendored
View File

@@ -5,7 +5,7 @@ VITE_APP_TITLE=代理企业管理系统
VITE_APP_ENV=development 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 VITE_APP_PORT=9527

View File

@@ -2,7 +2,7 @@
VITE_APP_ENV=development VITE_APP_ENV=development
# 接口地址 # 接口地址
VITE_API_BASE_URL=https://cmp-api.boss160.cn VITE_API_BASE_URL=https://cmp-api.xm-iot.cn
# 是否使用代理 (H5开发环境使用代理解决跨域) # 是否使用代理 (H5开发环境使用代理解决跨域)
VITE_APP_PROXY=true VITE_APP_PROXY=true

2
env/.env.production vendored
View File

@@ -2,7 +2,7 @@
VITE_APP_ENV=production 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 VITE_APP_PROXY=false

2
env/.env.test vendored
View File

@@ -2,7 +2,7 @@
VITE_APP_ENV=staging 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 VITE_APP_PROXY=false

View File

@@ -70,9 +70,10 @@
}, },
"vueVersion" : "3", "vueVersion" : "3",
"h5" : { "h5" : {
"title" : "企业代理H5",
"template" : "index.html",
"router" : { "router" : {
"mode" : "hash", "mode" : "history"
"base" : "/h5/"
} }
} }
} }

View File

@@ -2,7 +2,8 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopAsset, startAsset, getAssetRealtimeStatus } from '@/api/assets' import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopAsset, startAsset, getAssetRealtimeStatus } from '@/api/assets'
import type { AssetType, CardInfo, DeviceInfo, AssetPackage, HistoryRecord } 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<AssetType>('card') const assetType = ref<AssetType>('card')
@@ -67,25 +68,6 @@ function getPackageStatusColor(status?: number) {
return status !== undefined ? map[status] || { bg: '#f5f5f5', text: '#999' } : { bg: '#f5f5f5', text: '#999' } 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']) { function getPackageTypeText(type?: AssetPackage['package_type']) {
const map: Record<string, string> = { const map: Record<string, string> = {
formal: '正式套餐', formal: '正式套餐',
@@ -94,12 +76,6 @@ function getPackageTypeText(type?: AssetPackage['package_type']) {
return type ? map[type] || type : '-' return type ? map[type] || type : '-'
} }
function formatPackagePrice(amount?: number) {
if (amount === undefined || amount === null)
return '-'
return `¥${(amount / 100).toFixed(2)}`
}
// 页面加载 // 页面加载
onMounted(() => { onMounted(() => {
// 从路由参数获取 assetType 和 assetIdentifier // 从路由参数获取 assetType 和 assetIdentifier
@@ -548,15 +524,15 @@ function viewWallet() {
<view class="grid grid-cols-3 gap-2 mb-3"> <view class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-[#f5f5f5] rounded-8px p-2 text-center"> <view class="bg-[#f5f5f5] rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text> <text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatDataSize(getPackageTotalMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-[#f5f5f5] rounded-8px p-2 text-center"> <view class="bg-[#f5f5f5] rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text> <text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatDataSize(getPackageUsedMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-[#f5f5f5] rounded-8px p-2 text-center"> <view class="bg-[#f5f5f5] rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text> <text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatDataSize(getPackageRemainMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view> </view>
</view> </view>
@@ -564,8 +540,8 @@ function viewWallet() {
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text> <text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]"> <text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} / {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatDataSize(getPackageTotalMb(currentPackage)) }} {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text> </text>
</view> </view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden"> <view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
@@ -652,12 +628,20 @@ function viewWallet() {
</view> </view>
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-12px text-[#666]"> <text class="text-12px text-[#666]">
已用: {{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} 已用: {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
</text> </text>
<text class="text-11px text-[#999]"> <text class="text-11px text-[#999]">
优先级: {{ pkg.priority || '-' }} 优先级: {{ pkg.priority || '-' }}
</text> </text>
</view> </view>
<view class="flex items-center justify-between mb-1 text-12px">
<text class="text-[#666]">剩余</text>
<text class="text-[#212121]">{{ formatPackageDataSize(getPackageRemainMb(pkg)) }}</text>
</view>
<view class="flex items-center justify-between mb-1 text-12px">
<text class="text-[#666]">金额</text>
<text class="text-[#212121]">{{ formatPackagePrice(pkg.paid_amount) }}</text>
</view>
<text class="text-11px text-[#999]"> <text class="text-11px text-[#999]">
{{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }} {{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }}
</text> </text>

View File

@@ -3,6 +3,7 @@
import { resolveAsset, getAssetRealtimeStatus, getCurrentPackage, getAssetPackages } from '@/api/assets' import { resolveAsset, getAssetRealtimeStatus, getCurrentPackage, getAssetPackages } from '@/api/assets'
import type { CardInfo, DeviceInfo, AssetRealtimeStatus, AssetPackage } from '@/api/assets' import type { CardInfo, DeviceInfo, AssetRealtimeStatus, AssetPackage } from '@/api/assets'
import { formatTime, formatDataSize } from '@/utils/format' import { formatTime, formatDataSize } from '@/utils/format'
import { formatPackageDataSize, formatPackagePrice, getPackageRemainMb, getPackageTotalMb, getPackageUsagePercent, getPackageUsedMb } from '../package-display'
// 搜索关键词 // 搜索关键词
const searchKeyword = ref('') const searchKeyword = ref('')
@@ -200,19 +201,6 @@
return map[status] || { bg: '#f5f5f5', text: '#999' } 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']) { function getPackageTypeText(type ?: AssetPackage['package_type']) {
const map : Record<string, string> = { const map : Record<string, string> = {
formal: '正式套餐', formal: '正式套餐',
@@ -221,17 +209,6 @@
return type ? map[type] || type : '-' 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) // 获取信号质量等级 (基于 RSRP)
function getSignalQuality(rsrp ?: number) { function getSignalQuality(rsrp ?: number) {
if (rsrp === undefined) return { level: '未知', text: '无信号', color: '#999' } if (rsrp === undefined) return { level: '未知', text: '无信号', color: '#999' }
@@ -550,15 +527,15 @@
<view class="grid grid-cols-3 gap-2 mb-3"> <view class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text> <text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatDataSize(getPackageTotalMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text> <text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatDataSize(getPackageUsedMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text> <text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatDataSize(getPackageRemainMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view> </view>
</view> </view>
@@ -566,8 +543,8 @@
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text> <text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]"> <text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} / {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatDataSize(getPackageTotalMb(currentPackage)) }} {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text> </text>
</view> </view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden"> <view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
@@ -620,15 +597,15 @@
<view v-if="false" class="grid grid-cols-3 gap-2 mb-3"> <view v-if="false" class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text> <text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatDataSize(getPackageTotalMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text> <text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatDataSize(getPackageUsedMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text> <text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatDataSize(getPackageRemainMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view> </view>
</view> </view>
@@ -636,8 +613,8 @@
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text> <text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]"> <text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} / {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatDataSize(getPackageTotalMb(currentPackage)) }} {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text> </text>
</view> </view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden"> <view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
@@ -690,15 +667,15 @@
<view v-if="false" class="grid grid-cols-3 gap-2 mb-3"> <view v-if="false" class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text> <text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatDataSize(getPackageTotalMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text> <text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatDataSize(getPackageUsedMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text> <text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatDataSize(getPackageRemainMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view> </view>
</view> </view>
@@ -706,8 +683,8 @@
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text> <text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]"> <text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} / {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatDataSize(getPackageTotalMb(currentPackage)) }} {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text> </text>
</view> </view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden"> <view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
@@ -832,9 +809,17 @@
<view class="flex items-center justify-between text-11px"> <view class="flex items-center justify-between text-11px">
<text class="text-[#666]">已使用</text> <text class="text-[#666]">已使用</text>
<text class="text-[#212121]"> <text class="text-[#212121]">
{{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
</text> </text>
</view> </view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">剩余</text>
<text class="text-[#212121]">{{ formatPackageDataSize(getPackageRemainMb(pkg)) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">金额</text>
<text class="text-[#212121]">{{ formatPackagePrice(pkg.paid_amount) }}</text>
</view>
<view v-if="pkg.activated_at" class="flex items-center justify-between text-10px"> <view v-if="pkg.activated_at" class="flex items-center justify-between text-10px">
<text class="text-[#999]">{{ formatTime(pkg.activated_at, 'date') }}</text> <text class="text-[#999]">{{ formatTime(pkg.activated_at, 'date') }}</text>
<text v-if="pkg.expires_at" class="text-[#999]"> {{ formatTime(pkg.expires_at, 'date') }}</text> <text v-if="pkg.expires_at" class="text-[#999]"> {{ formatTime(pkg.expires_at, 'date') }}</text>
@@ -1295,15 +1280,15 @@
<view class="grid grid-cols-3 gap-2 mb-3"> <view class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text> <text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatDataSize(getPackageTotalMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text> <text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatDataSize(getPackageUsedMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view> </view>
<view class="bg-white rounded-8px p-2 text-center"> <view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text> <text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatDataSize(getPackageRemainMb(currentPackage)) }}</text> <text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view> </view>
</view> </view>
@@ -1311,8 +1296,8 @@
<view class="flex items-center justify-between mb-1"> <view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text> <text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]"> <text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} / {{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatDataSize(getPackageTotalMb(currentPackage)) }} {{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text> </text>
</view> </view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden"> <view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
@@ -1438,9 +1423,17 @@
<view class="flex items-center justify-between text-11px"> <view class="flex items-center justify-between text-11px">
<text class="text-[#666]">已使用</text> <text class="text-[#666]">已使用</text>
<text class="text-[#212121]"> <text class="text-[#212121]">
{{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }} {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
</text> </text>
</view> </view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">剩余</text>
<text class="text-[#212121]">{{ formatPackageDataSize(getPackageRemainMb(pkg)) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">金额</text>
<text class="text-[#212121]">{{ formatPackagePrice(pkg.paid_amount) }}</text>
</view>
<view v-if="pkg.activated_at" class="flex items-center justify-between text-10px"> <view v-if="pkg.activated_at" class="flex items-center justify-between text-10px">
<text class="text-[#999]">{{ formatTime(pkg.activated_at, 'date') }}</text> <text class="text-[#999]">{{ formatTime(pkg.activated_at, 'date') }}</text>
<text v-if="pkg.expires_at" class="text-[#999]"> {{ formatTime(pkg.expires_at, 'date') }}</text> <text v-if="pkg.expires_at" class="text-[#999]"> {{ formatTime(pkg.expires_at, 'date') }}</text>

View File

@@ -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)}`
}