fix: 流量保留两位小数
This commit is contained in:
@@ -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: 设置镜像标签
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
227
docs/资产详情流量-spec.md
Normal file
227
docs/资产详情流量-spec.md
Normal 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`
|
||||
34
docs/资产详情流量.md
Normal file
34
docs/资产详情流量.md
Normal 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
2
env/.env
vendored
@@ -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
|
||||
|
||||
2
env/.env.development
vendored
2
env/.env.development
vendored
@@ -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
|
||||
|
||||
2
env/.env.production
vendored
2
env/.env.production
vendored
@@ -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
|
||||
|
||||
2
env/.env.test
vendored
2
env/.env.test
vendored
@@ -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
|
||||
|
||||
@@ -70,9 +70,10 @@
|
||||
},
|
||||
"vueVersion" : "3",
|
||||
"h5" : {
|
||||
"title" : "企业代理H5",
|
||||
"template" : "index.html",
|
||||
"router" : {
|
||||
"mode" : "hash",
|
||||
"base" : "/h5/"
|
||||
"mode" : "history"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AssetType>('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<string, string> = {
|
||||
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() {
|
||||
<view class="grid grid-cols-3 gap-2 mb-3">
|
||||
<view class="bg-[#f5f5f5] rounded-8px p-2 text-center">
|
||||
<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 class="bg-[#f5f5f5] rounded-8px p-2 text-center">
|
||||
<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 class="bg-[#f5f5f5] rounded-8px p-2 text-center">
|
||||
<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>
|
||||
|
||||
@@ -564,8 +540,8 @@ function viewWallet() {
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-11px text-[#666]">已使用</text>
|
||||
<text class="text-11px font-600 text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
|
||||
@@ -652,12 +628,20 @@ function viewWallet() {
|
||||
</view>
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-12px text-[#666]">
|
||||
已用: {{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }}
|
||||
已用: {{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
|
||||
</text>
|
||||
<text class="text-11px text-[#999]">
|
||||
优先级: {{ pkg.priority || '-' }}
|
||||
</text>
|
||||
</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]">
|
||||
{{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }}
|
||||
</text>
|
||||
|
||||
@@ -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<string, string> = {
|
||||
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 @@
|
||||
<view class="grid grid-cols-3 gap-2 mb-3">
|
||||
<view class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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>
|
||||
|
||||
@@ -566,8 +543,8 @@
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-11px text-[#666]">已使用</text>
|
||||
<text class="text-11px font-600 text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
</text>
|
||||
</view>
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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>
|
||||
|
||||
@@ -636,8 +613,8 @@
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-11px text-[#666]">已使用</text>
|
||||
<text class="text-11px font-600 text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
</text>
|
||||
</view>
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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>
|
||||
|
||||
@@ -706,8 +683,8 @@
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-11px text-[#666]">已使用</text>
|
||||
<text class="text-11px font-600 text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
|
||||
@@ -832,9 +809,17 @@
|
||||
<view class="flex items-center justify-between text-11px">
|
||||
<text class="text-[#666]">已使用</text>
|
||||
<text class="text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
|
||||
</text>
|
||||
</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">
|
||||
<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>
|
||||
@@ -1295,15 +1280,15 @@
|
||||
<view class="grid grid-cols-3 gap-2 mb-3">
|
||||
<view class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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 class="bg-white rounded-8px p-2 text-center">
|
||||
<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>
|
||||
|
||||
@@ -1311,8 +1296,8 @@
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-11px text-[#666]">已使用</text>
|
||||
<text class="text-11px font-600 text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
|
||||
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
|
||||
@@ -1438,9 +1423,17 @@
|
||||
<view class="flex items-center justify-between text-11px">
|
||||
<text class="text-[#666]">已使用</text>
|
||||
<text class="text-[#212121]">
|
||||
{{ formatDataSize(getPackageUsedMb(pkg)) }} / {{ formatDataSize(getPackageTotalMb(pkg)) }}
|
||||
{{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
|
||||
</text>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
44
src/pages/agent-system/package-display.ts
Normal file
44
src/pages/agent-system/package-display.ts
Normal 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)}`
|
||||
}
|
||||
Reference in New Issue
Block a user