fix: 优化界面以及更新接口
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m27s
@@ -39,54 +39,57 @@ Based on analysis: **SaaS (General) + B2B Service**
|
|||||||
|
|
||||||
## 🌈 Color Palette
|
## 🌈 Color Palette
|
||||||
|
|
||||||
### Brand Colors (Xiaomi-Inspired)
|
### Brand Colors (Blue Theme)
|
||||||
|
|
||||||
```scss
|
```scss
|
||||||
// Primary Brand Colors
|
// Primary Brand Colors
|
||||||
--brand-orange: #ff6700; // Primary CTA, hover states
|
--brand-primary: #1677ff; // Primary CTA, hover states
|
||||||
--brand-orange-light: #ff8533; // Lighter variant
|
--brand-primary-light: #4096ff; // Lighter variant
|
||||||
--brand-orange-dark: #e65a00; // Pressed states
|
--brand-primary-dark: #0958d9; // Pressed states
|
||||||
--brand-gray: #333333; // Headings, important text
|
--brand-secondary: #1e293b; // Dark Gray (headings)
|
||||||
--brand-gray-light: #757575; // Secondary text
|
--brand-accent: #ffffff; // White (emphasis)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Semantic Colors (Light Mode)
|
### Semantic Colors (Light Mode)
|
||||||
|
|
||||||
```scss
|
```scss
|
||||||
// Primary Theme Colors
|
// Primary Theme Colors
|
||||||
--theme-primary: #ff6700; // Orange (Trust + Energy)
|
--theme-primary: #1677ff; // Blue (Trust + Energy)
|
||||||
--theme-secondary: #ff8533; // Lighter Orange
|
--theme-secondary: #1e293b; // Dark Gray
|
||||||
--theme-accent: #333333; // Dark Gray
|
--theme-accent: #ffffff; // White
|
||||||
|
|
||||||
// Status Colors
|
// Status Colors
|
||||||
--theme-success: #3ed268; // Green (Success states)
|
--theme-success: #52c41a; // Green (Success states)
|
||||||
--theme-warning: #e68815; // Amber (Warnings)
|
--theme-warning: #faad14; // Amber (Warnings)
|
||||||
--theme-error: #d31638; // Red (Errors)
|
--theme-error: #ff4d4f; // Red (Errors)
|
||||||
|
--theme-info: #1677ff; // Info (same as primary)
|
||||||
|
|
||||||
// Text Hierarchy
|
// Text Hierarchy
|
||||||
--theme-main-color: #4c484f; // Primary text (slate-900 equivalent)
|
--theme-main-color: #212121; // Primary text
|
||||||
--theme-content-color: #6b6569; // Body text (slate-700)
|
--theme-content-color: #666666; // Body text
|
||||||
--theme-tips-color: #8a8486; // Secondary text (slate-500)
|
--theme-tips-color: #999999; // Secondary text
|
||||||
--theme-light-color: #bd9e84; // Muted text (beige)
|
--theme-light-color: #cccccc; // Muted text
|
||||||
--theme-disabled-color: #dce0eb; // Disabled states
|
--theme-disabled-color: #f5f5f5; // Disabled states
|
||||||
|
|
||||||
// Backgrounds
|
// Backgrounds
|
||||||
--theme-bg-color: #ffffff; // Main background
|
--theme-bg-color: #fafafa; // Main background
|
||||||
--theme-bg-color-secondary: #faf8f7; // Card backgrounds
|
--theme-bg-color-secondary: #ffffff; // Card backgrounds
|
||||||
--theme-bg-gradient-start: #c5dfdf;
|
--theme-bg-gradient-start: #1677ff;
|
||||||
--theme-bg-gradient-end: #bd9e84;
|
--theme-bg-gradient-end: #4096ff;
|
||||||
|
|
||||||
// Borders
|
// Borders
|
||||||
--theme-border-color: #c5dfdf; // Borders, dividers
|
--theme-border-color: #e8e8e8; // Borders, dividers
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dark Mode Colors
|
### Dark Mode Colors
|
||||||
|
|
||||||
```scss
|
```scss
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
--theme-primary: #3c9cff; // Blue (better for dark mode)
|
--brand-primary: #4b9cff; // Blue (lighter for dark mode)
|
||||||
--theme-success: #5ac725;
|
--brand-primary-light: #69b1ff;
|
||||||
--theme-warning: #f9ae3d;
|
--brand-primary-dark: #1668dc;
|
||||||
|
--theme-success: #73d13d;
|
||||||
|
--theme-warning: #ffc53d;
|
||||||
--theme-error: #f56c6c;
|
--theme-error: #f56c6c;
|
||||||
|
|
||||||
// Text (High Contrast Required)
|
// Text (High Contrast Required)
|
||||||
|
|||||||
265
docs/BREAKING_CHANGES_asset_identifier(1).md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# 接口变化说明:资产标识符规范化 + 历史订单 + 导入必填
|
||||||
|
|
||||||
|
> **涉及提案**:
|
||||||
|
> - `asset-identifier-standardization`(BREAKING)
|
||||||
|
> - `asset-historical-orders`(新增接口)
|
||||||
|
> - `import-mandatory-virtual-no`(行为变更)
|
||||||
|
>
|
||||||
|
> **影响范围**:B 端资产操作、设备管理、订单创建、IoT 卡 / 设备导入
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 资产操作接口路由变更
|
||||||
|
|
||||||
|
**变更前**:路径参数为数据库主键 ID,需同时传 `asset_type`
|
||||||
|
**变更后**:路径参数统一为资产标识符(ICCID 或 VirtualNo),系统内部自动识别资产类型
|
||||||
|
|
||||||
|
| 旧路由 | 新路由 |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/admin/assets/:asset_type/:id/realtime-status` | `GET /api/admin/assets/:identifier/realtime-status` |
|
||||||
|
| `POST /api/admin/assets/:asset_type/:id/refresh` | `POST /api/admin/assets/:identifier/refresh` |
|
||||||
|
| `GET /api/admin/assets/:asset_type/:id/packages` | `GET /api/admin/assets/:identifier/packages` |
|
||||||
|
| `GET /api/admin/assets/:asset_type/:id/current-package` | `GET /api/admin/assets/:identifier/current-package` |
|
||||||
|
| `GET /api/admin/assets/:asset_type/:id/wallet` | `GET /api/admin/assets/:identifier/wallet` |
|
||||||
|
| `GET /api/admin/assets/:asset_type/:id/wallet/transactions` | `GET /api/admin/assets/:identifier/wallet/transactions` |
|
||||||
|
| `PATCH /api/admin/assets/:asset_type/:id/polling-status` | `PATCH /api/admin/assets/:identifier/polling-status` |
|
||||||
|
| `POST /api/admin/assets/device/:device_id/stop` | `POST /api/admin/assets/:identifier/stop` |
|
||||||
|
| `POST /api/admin/assets/card/:iccid/stop` | `POST /api/admin/assets/:identifier/stop`(已合并) |
|
||||||
|
| `POST /api/admin/assets/device/:device_id/start` | `POST /api/admin/assets/:identifier/start` |
|
||||||
|
| `POST /api/admin/assets/card/:iccid/start` | `POST /api/admin/assets/:identifier/start`(已合并) |
|
||||||
|
| `PATCH /api/admin/devices/:id/deactivate` | `PATCH /api/admin/assets/:identifier/deactivate` |
|
||||||
|
| `PATCH /api/admin/iot-cards/:id/deactivate` | `PATCH /api/admin/assets/:identifier/deactivate`(已合并) |
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- `:identifier` 只接受 ICCID 或 VirtualNo(全局唯一标识符)
|
||||||
|
- `stop` / `start` / `deactivate` 已合并为统一接口,系统内部按资产类型自动分支处理,前端无需区分卡和设备
|
||||||
|
- IMEI / SN / MSISDN 仍可用于 `GET /api/admin/assets/resolve/:identifier` 搜索,但不适用于操作接口
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 设备管理接口路由变更
|
||||||
|
|
||||||
|
**变更前**:路径参数为数据库主键 ID
|
||||||
|
**变更后**:路径参数改为 VirtualNo(虚拟号)
|
||||||
|
|
||||||
|
| 旧路由 | 新路由 |
|
||||||
|
|---|---|
|
||||||
|
| `DELETE /api/admin/devices/:id` | `DELETE /api/admin/devices/:virtual_no` |
|
||||||
|
| `GET /api/admin/devices/:id/cards` | `GET /api/admin/devices/:virtual_no/cards` |
|
||||||
|
| `POST /api/admin/devices/:id/cards` | `POST /api/admin/devices/:virtual_no/cards` |
|
||||||
|
| `DELETE /api/admin/devices/:id/cards/:cardId` | `DELETE /api/admin/devices/:virtual_no/cards/:iccid` |
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- 解绑卡接口第二段路径参数从 `cardId`(数据库 ID)改为 `iccid`(ICCID 字符串)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 创建订单接口请求体变更
|
||||||
|
|
||||||
|
### B 端:`POST /api/admin/orders`
|
||||||
|
|
||||||
|
**请求体字段变化:**
|
||||||
|
|
||||||
|
| 字段 | 旧 | 新 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `order_type` | 必填 | **废弃** | 系统根据 `identifier` 解析结果自动填入 |
|
||||||
|
| `iot_card_id` | 单卡购买必填 | **废弃** | 改用 `identifier` |
|
||||||
|
| `device_id` | 设备购买必填 | **废弃** | 改用 `identifier` |
|
||||||
|
| `identifier` | — | **必填** | ICCID 或 VirtualNo,最长 100 字符 |
|
||||||
|
| `package_ids` | 必填 | 不变 | — |
|
||||||
|
| `payment_method` | 必填 | 不变 | — |
|
||||||
|
|
||||||
|
**变更后请求体示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"identifier": "8986XXXXXXXXXXXXXXXX",[前端接口变更通知.md](%E5%89%8D%E7%AB%AF%E6%8E%A5%E5%8F%A3%E5%8F%98%E6%9B%B4%E9%80%9A%E7%9F%A5.md)
|
||||||
|
"package_ids": [5],
|
||||||
|
"payment_method": "wallet"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### C 端:`POST /api/c/v1/orders/create`
|
||||||
|
|
||||||
|
同 B 端,废弃 `order_type` / `iot_card_id` / `device_id`,统一使用 `identifier`。C 端原本已有此字段,行为不变。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 订单列表接口新增过滤参数
|
||||||
|
|
||||||
|
### `GET /api/admin/orders`
|
||||||
|
|
||||||
|
新增可选查询参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `identifier` | string | 按资产标识符精确过滤,匹配订单的 `asset_identifier` 快照字段 |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/admin/orders?identifier=8986XXXXXXXXXXXXXXXX
|
||||||
|
GET /api/admin/orders?identifier=DEV-001
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 响应结构新增字段
|
||||||
|
|
||||||
|
### `OrderResponse`(所有订单相关接口响应)
|
||||||
|
|
||||||
|
| 新增字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `asset_identifier` | string | 下单时资产的标识符快照,资产删除后仍可读 |
|
||||||
|
| `asset_type` | string | 资产类型:`single_card` 或 `device` |
|
||||||
|
|
||||||
|
### `AssetResolveResponse`(`GET /api/admin/assets/resolve/:identifier`)
|
||||||
|
|
||||||
|
| 新增字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `identifier` | string | 原样回传本次查询所用的标识符 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 新增业务规则
|
||||||
|
|
||||||
|
### 已绑设备的卡不可单独购买套餐
|
||||||
|
|
||||||
|
- **触发条件**:IoT 卡的 `is_standalone = false`(已绑定设备)
|
||||||
|
- **影响接口**:所有套餐购买入口(B 端后台、C 端 H5)
|
||||||
|
- **返回错误**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 40001,
|
||||||
|
"msg": "该卡已绑定设备,请前往设备页面购买套餐"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 新增接口:资产历史订单
|
||||||
|
|
||||||
|
> **来源提案**:`asset-historical-orders`
|
||||||
|
|
||||||
|
### `GET /api/admin/assets/:identifier/orders`
|
||||||
|
|
||||||
|
查询某资产本代历史订单,可选通过换货链追溯前代(最多10代)。
|
||||||
|
|
||||||
|
**路径参数:**
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `identifier` | 资产标识符(ICCID 或 VirtualNo) |
|
||||||
|
|
||||||
|
**查询参数:**
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `page` | int | 否 | 页码,默认 1 |
|
||||||
|
| `page_size` | int | 否 | 每页条数,默认 20,最大 100 |
|
||||||
|
| `include_previous` | bool | 否 | 是否追溯换货前代,默认 false |
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"current_generation": {
|
||||||
|
"generation": 2,
|
||||||
|
"identifier": "VNO-001",
|
||||||
|
"asset_type": "card",
|
||||||
|
"total": 3,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"order_no": "ORD-20250101-001",
|
||||||
|
"order_type": "single_card",
|
||||||
|
"payment_status": 2,
|
||||||
|
"payment_status_text": "已支付",
|
||||||
|
"total_amount": 9900,
|
||||||
|
"payment_method": "wallet",
|
||||||
|
"paid_at": "2025-01-01T10:00:00Z",
|
||||||
|
"generation": 2,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"package_name": "30天1GB套餐",
|
||||||
|
"quantity": 1,
|
||||||
|
"unit_price": 9900,
|
||||||
|
"amount": 9900
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at": "2025-01-01T09:58:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"previous_generations": [
|
||||||
|
{
|
||||||
|
"generation": 1,
|
||||||
|
"identifier": "VNO-000",
|
||||||
|
"asset_type": "card",
|
||||||
|
"exchange_no": "EX-20241201-001",
|
||||||
|
"exchanged_at": "2024-12-01T10:00:00Z",
|
||||||
|
"total": 1,
|
||||||
|
"items": [...]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"truncated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- `previous_generations` 仅在 `include_previous=true` 时返回,否则字段不存在
|
||||||
|
- `truncated: true` 表示换货链超过 10 代,已截断,前代数据不完整
|
||||||
|
- 前代订单每代最多返回 20 条(不分页)
|
||||||
|
- 依赖提案一(`asset-identifier-standardization`)中的 `Order.asset_identifier` 快照字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 行为变更:导入接口 virtual_no 必填
|
||||||
|
|
||||||
|
> **来源提案**:`import-mandatory-virtual-no`(无 Schema 结构变化,仅行为变更)
|
||||||
|
|
||||||
|
### `POST /api/admin/iot-cards/import` — BREAKING
|
||||||
|
|
||||||
|
| 场景 | 旧行为 | 新行为 |
|
||||||
|
|---|---|---|
|
||||||
|
| Excel 缺少 `virtual_no` 列 | 正常导入,卡的 `virtual_no` 存空字符串 | **整批失败**,任务状态=失败,错误:"Excel 文件缺少 virtual_no 列,请使用最新模板" |
|
||||||
|
| 某行 `virtual_no` 为空 | 该行导入成功 | 该行计入 `fail_count`,`failed_items` 含原因:"虚拟号(virtual_no)不能为空" |
|
||||||
|
| `virtual_no` 重复 | 无变化 | 无变化 |
|
||||||
|
|
||||||
|
> IoT 卡导入 Excel 模板**必须新增 `virtual_no` 列**,否则全批失败。
|
||||||
|
|
||||||
|
### `POST /api/admin/devices/import` — 统计语义变更
|
||||||
|
|
||||||
|
| 场景 | 旧行为 | 新行为 |
|
||||||
|
|---|---|---|
|
||||||
|
| 某行 `virtual_no` 为空 | 计入 `skip_count`,无失败详情 | 计入 `fail_count`,`failed_items` 含原因:"设备虚拟号(virtual_no)不能为空" |
|
||||||
|
|
||||||
|
> 前端若有"跳过了 N 行设备"的提示文案,需同步调整为失败处理逻辑。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端迁移清单
|
||||||
|
|
||||||
|
### asset-identifier-standardization(必须改)
|
||||||
|
- [ ] 将 `/assets/:asset_type/:id/` 系列 URL 改为 `/assets/:identifier/`,传 ICCID 或 VirtualNo
|
||||||
|
- [ ] 停复机不再区分卡/设备,统一调 `/assets/:identifier/stop` 和 `/assets/:identifier/start`
|
||||||
|
- [ ] 停用不再区分 `/iot-cards/:id/deactivate` 和 `/devices/:id/deactivate`,统一调 `/assets/:identifier/deactivate`
|
||||||
|
- [ ] 设备管理接口将 `/:id` 改为 `/:virtual_no`,解绑卡将 `/:cardId` 改为 `/:iccid`
|
||||||
|
- [ ] 创建订单请求体移除 `order_type`、`iot_card_id`、`device_id`,改传 `identifier`
|
||||||
|
- [ ] 订单列表可通过 `?identifier=xxx` 过滤指定资产的历史订单
|
||||||
|
- [ ] 展示订单详情时可直接读取 `asset_identifier` 字段,无需额外查询资产
|
||||||
|
|
||||||
|
### asset-historical-orders(新功能)
|
||||||
|
- [ ] 资产详情页接入 `GET /api/admin/assets/:identifier/orders` 展示历史订单
|
||||||
|
- [ ] 换货资产可通过 `?include_previous=true` 追溯前代购买记录
|
||||||
|
|
||||||
|
### import-mandatory-virtual-no(需同步)
|
||||||
|
- [ ] 更新 IoT 卡导入 Excel 模板,必须包含 `virtual_no` 列
|
||||||
|
- [ ] 导入结果展示逻辑:设备导入"跳过行"统计移至"失败行",并展示原因
|
||||||
129
docs/前端接口变更通知.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
前端接口变更通知
|
||||||
|
|
||||||
|
背景 & 原因
|
||||||
|
|
||||||
|
平台需要展示代理的\*\*预充值钱包(主钱包)\*\*余额与流水,原来这部分数据完全不可见。同时,代理的佣金相关接口散落在 /my/ 路径下,与平台统一的 /shops/:id/ 资源路径存在职责重叠,本次统一收拢到 /shops/ 下。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
一、BREAKING CHANGE — 必须迁移
|
||||||
|
|
||||||
|
|
||||||
|
1. 概况列表接口改造
|
||||||
|
|
||||||
|
┌──────────────┬──────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────┐ │ │ 旧 │ 新 │ ├──────────────┼──────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ 路径 │ GET /api/admin/shops/commission-summary │ GET /api/admin/shops/fund-summary │ ├──────────────┼──────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ 入参 │ 完全相同(page, page_size, shop_name, username) │ 无变化 │ ├──────────────┼──────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ 响应新增字段 │ — │ main_balance(预充值余额,分)main_frozen_balance(预充值冻结余额,分) │ ├──────────────┼──────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤ │ 响应删除字段 │ — │ 无 │ └──────────────┴──────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
旧响应 items 字段(已废弃 DTO ShopCommissionSummaryItem)→ 新 DTO ShopFundSummaryItem:
|
||||||
|
|
||||||
|
{ "shop_id": 1, "shop_name": "代理A", "shop_code": "A001", "username": "agent_a", "phone": "138xxxx",
|
||||||
|
|
||||||
|
* "main_balance": 200000, // 新增:预充值钱包余额(分)
|
||||||
|
* "main_frozen_balance": 0, // 新增:预充值冻结余额(分,当前均为0) "total_commission": 50000, "withdrawn_commission": 10000, "unwithdraw_commission": 30000, "frozen_commission": 5000, "withdrawing_commission": 5000, "available_commission": 30000, "created_at": "2025-01-01T00:00:00Z" }
|
||||||
|
|
||||||
|
▎ 操作:把所有调用 /commission-summary 的地方改为 /fund-summary,同时在 UI 上新增"预充值余额"列。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
2. 废弃的 /my/ 接口 — 全部删除,必须切换
|
||||||
|
|
||||||
|
┌────────────────────────────────┬─────────────────────────────────────────────┬─────────────────────────────────────────────┐ │ 废弃路由 │ 替代路由 │ 变化 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ GET /my/commission-summary │ GET /shops/fund-summary │ 见上方,返回结构有变化 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ GET /my/withdrawal-requests │ GET /shops/{shop_id}/withdrawal-requests │ 入参、响应完全一致,只需把 shop_id 放进路径 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ GET /my/commission-records │ GET /shops/{shop_id}/commission-records │ 入参、响应完全一致,只需把 shop_id 放进路径 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ GET /my/commission-stats │ GET /shops/{shop_id}/commission-stats │ 见下方详情 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ GET /my/commission-daily-stats │ GET /shops/{shop_id}/commission-daily-stats │ 见下方详情 │ ├────────────────────────────────┼─────────────────────────────────────────────┼─────────────────────────────────────────────┤ │ POST /my/withdrawal-requests │ POST /shops/{shop_id}/withdrawal-requests │ 见下方详情 │ └────────────────────────────────┴─────────────────────────────────────────────┴─────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
▎ 旧 /my/ 接口已从服务端完全删除,调用会 404。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
二、新增接口详情
|
||||||
|
|
||||||
|
GET /api/admin/shops/{shop_id}/commission-stats
|
||||||
|
|
||||||
|
替代 GET /my/commission-stats
|
||||||
|
|
||||||
|
入参(Query):
|
||||||
|
|
||||||
|
┌────────────┬────────┬──────────────────┐ │ 参数 │ 类型 │ 说明 │ ├────────────┼────────┼──────────────────┤ │ start_time │ string │ 开始时间(可选) │ ├────────────┼────────┼──────────────────┤ │ end_time │ string │ 结束时间(可选) │ └────────────┴────────┴──────────────────┘
|
||||||
|
|
||||||
|
▎ 旧接口的 shop_id query 参数已去掉,改为路径参数 {shop_id}
|
||||||
|
|
||||||
|
响应(无变化): { "total_amount": 50000, "cost_diff_amount": 30000, "one_time_amount": 20000, "cost_diff_percent": 600, "one_time_percent": 400, "total_count": 15, "cost_diff_count": 10, "one_time_count": 5 }
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
GET /api/admin/shops/{shop_id}/commission-daily-stats
|
||||||
|
|
||||||
|
替代 GET /my/commission-daily-stats
|
||||||
|
|
||||||
|
入参(Query):
|
||||||
|
|
||||||
|
┌────────────┬────────┬───────────────────────────────────┐ │ 参数 │ 类型 │ 说明 │ ├────────────┼────────┼───────────────────────────────────┤ │ start_date │ string │ 开始日期 YYYY-MM-DD(可选) │ ├────────────┼────────┼───────────────────────────────────┤ │ end_date │ string │ 结束日期 YYYY-MM-DD(可选) │ ├────────────┼────────┼───────────────────────────────────┤ │ days │ int │ 查询天数,默认30,最大365(可选) │ └────────────┴────────┴───────────────────────────────────┘
|
||||||
|
|
||||||
|
响应(无变化): \[ { "date": "2025-04-08", "total_amount": 10000, "total_count": 3 }, { "date": "2025-04-07", "total_amount": 5000, "total_count": 2 } \]
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
POST /api/admin/shops/{shop_id}/withdrawal-requests
|
||||||
|
|
||||||
|
替代 POST /my/withdrawal-requests
|
||||||
|
|
||||||
|
Body(无变化): { "amount": 10000, "withdrawal_method": "alipay", "account_name": "张三", "account_number": "138xxxx" }
|
||||||
|
|
||||||
|
权限变化(重要):
|
||||||
|
|
||||||
|
* 仅代理账号可调用,平台账号调用返回 403 仅代理商用户可发起提现
|
||||||
|
* 只能为自己的 shop_id 提现,topLevel 代理不能替下级提现,否则返回 403 仅可为本人店铺发起提现
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
GET /api/admin/shops/{shop_id}/main-wallet/transactions ✨ 全新
|
||||||
|
|
||||||
|
预充值钱包流水列表(此前无此功能)
|
||||||
|
|
||||||
|
入参(Query):
|
||||||
|
|
||||||
|
┌──────────────────┬────────┬────────────────────────────────────────────────────────────────────────┐ │ 参数 │ 类型 │ 说明 │ ├──────────────────┼────────┼────────────────────────────────────────────────────────────────────────┤ │ page │ int │ 页码,默认1 │ ├──────────────────┼────────┼────────────────────────────────────────────────────────────────────────┤ │ page_size │ int │ 每页数量,默认20,最大100 │ ├──────────────────┼────────┼────────────────────────────────────────────────────────────────────────┤ │ transaction_type │ string │ 交易类型过滤:recharge(充值入账)/ deduct(套餐扣款)/ refund(退款) │ ├──────────────────┼────────┼────────────────────────────────────────────────────────────────────────┤ │ start_date │ string │ 开始日期 YYYY-MM-DD │ ├──────────────────┼────────┼────────────────────────────────────────────────────────────────────────┤ │ end_date │ string │ 结束日期 YYYY-MM-DD │ └──────────────────┴────────┴────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
响应: { "items": \[ { "id": 1, "transaction_type": "recharge", "transaction_subtype": "", "amount": 50000, "balance_before": 150000, "balance_after": 200000, "remark": "线下充值", "created_at": "2025-04-08T10:00:00Z" } \], "total": 3, "page": 1, "size": 20 }
|
||||||
|
|
||||||
|
▎ amount 正数为入账,负数为扣款,单位:分
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
三、数据权限说明(前端需了解)
|
||||||
|
|
||||||
|
两种角色访问同一套接口,后端自动过滤数据:
|
||||||
|
|
||||||
|
┌──────────┬─────────────────────────┬──────────────────────────────────────────┐ │ 角色 │ /shops/fund-summary │ /shops/{shop_id}/... │ ├──────────┼─────────────────────────┼──────────────────────────────────────────┤ │ 平台账号 │ 返回所有代理店铺 │ 可查任意 shop_id │ ├──────────┼─────────────────────────┼──────────────────────────────────────────┤ │ 代理账号 │ 返回自己 + 所有下级店铺 │ 只能查自己和下级的 shop_id,其他返回 403 │ └──────────┴─────────────────────────┴──────────────────────────────────────────┘
|
||||||
|
|
||||||
|
提现操作额外限制:代理账号只能为自己的 shop_id 发起提现,即使是上级代理也不能替下级操作。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
四、变更全景图
|
||||||
|
|
||||||
|
变更前 变更后 ────────────────────────────────────────────────────────────────────── GET /shops/commission-summary → GET /shops/fund-summary ★ 改路径 + 新增字段 GET /shops/:id/withdrawal-requests GET /shops/:id/withdrawal-requests (不变) GET /shops/:id/commission-records GET /shops/:id/commission-records (不变) → GET /shops/:id/commission-stats ★ 新增 → GET /shops/:id/commission-daily-stats ★ 新增 → POST /shops/:id/withdrawal-requests ★ 新增 → GET /shops/:id/main-wallet/transactions ★ 新增(全新功能)
|
||||||
|
|
||||||
|
GET /my/commission-summary × 已删除 GET /my/withdrawal-requests × 已删除 GET /my/commission-records × 已删除 GET /my/commission-stats × 已删除 GET /my/commission-daily-stats × 已删除 POST /my/withdrawal-requests × 已删除
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
五、前端改造核实清单
|
||||||
|
|
||||||
|
* 将 GET /shops/commission-summary → 改为 GET /shops/fund-summary,UI 新增"预充值余额"列
|
||||||
|
* 代理侧"我的余额概览"调用改为 GET /shops/fund-summary(不再有独立的 /my/commission-summary)
|
||||||
|
* 代理侧"提现记录"改为 GET /shops/{自己shopID}/withdrawal-requests
|
||||||
|
* 代理侧"佣金明细"改为 GET /shops/{自己shopID}/commission-records
|
||||||
|
* 代理侧"佣金统计"改为 GET /shops/{自己shopID}/commission-stats(去掉 query 里的 shop_id)
|
||||||
|
* 代理侧"每日统计"改为 GET /shops/{自己shopID}/commission-daily-stats(去掉 query 里的 shop_id)
|
||||||
|
* 代理侧"发起提现"改为 POST /shops/{自己shopID}/withdrawal-requests(shop_id 进路径,body 不变)
|
||||||
|
* 新建"预充值钱包流水"页面,调用 GET /shops/{shopID}/main-wallet/transactions
|
||||||
|
|
||||||
|
\
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 资产模块 API
|
* 资产模块 API
|
||||||
*/
|
*/
|
||||||
import { get, post } from '@/utils/request'
|
import { get, post, patch } from '@/utils/request'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资产类型
|
* 资产类型
|
||||||
@@ -139,13 +139,22 @@ export interface HistoryRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作参数
|
* 操作参数 (旧版,使用 ID)
|
||||||
|
* @deprecated 使用 AssetOperationParams 代替
|
||||||
*/
|
*/
|
||||||
export interface OperationParams {
|
export interface OperationParams {
|
||||||
id: string
|
id: string
|
||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作参数 (新版,使用 identifier)
|
||||||
|
*/
|
||||||
|
export interface AssetOperationParams {
|
||||||
|
identifier: string
|
||||||
|
reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资产解析 (智能识别)
|
* 资产解析 (智能识别)
|
||||||
* GET /api/admin/assets/resolve/:identifier
|
* GET /api/admin/assets/resolve/:identifier
|
||||||
@@ -156,7 +165,8 @@ export function resolveAsset(identifier: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资产详情
|
* 获取资产详情
|
||||||
* GET /api/admin/assets/:type/:id
|
* GET /api/admin/assets/:identifier (已废弃 type/id 格式)
|
||||||
|
* @deprecated 使用 identifier 代替,路由: GET /api/admin/assets/:identifier
|
||||||
*/
|
*/
|
||||||
export function getAssetDetail(type: AssetType, id: string) {
|
export function getAssetDetail(type: AssetType, id: string) {
|
||||||
return get<AssetDetailResponse>(`/api/admin/assets/${type}/${id}`)
|
return get<AssetDetailResponse>(`/api/admin/assets/${type}/${id}`)
|
||||||
@@ -164,7 +174,7 @@ export function getAssetDetail(type: AssetType, id: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取卡片详情
|
* 获取卡片详情
|
||||||
* GET /api/admin/cards/:id
|
* @deprecated 请使用 resolveAsset 通过 ICCID 查询
|
||||||
*/
|
*/
|
||||||
export function getCardDetail(id: string) {
|
export function getCardDetail(id: string) {
|
||||||
return get<CardInfo>(`/api/admin/cards/${id}`)
|
return get<CardInfo>(`/api/admin/cards/${id}`)
|
||||||
@@ -172,10 +182,11 @@ export function getCardDetail(id: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设备详情
|
* 获取设备详情
|
||||||
* GET /api/admin/devices/:id
|
* GET /api/admin/devices/:virtual_no
|
||||||
|
* @param virtualNo 设备虚拟号
|
||||||
*/
|
*/
|
||||||
export function getDeviceDetail(id: string) {
|
export function getDeviceDetail(virtualNo: string) {
|
||||||
return get<DeviceInfo>(`/api/admin/devices/${id}`)
|
return get<DeviceInfo>(`/api/admin/devices/${virtualNo}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,10 +254,11 @@ export interface AssetRealtimeStatus {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资产实时状态
|
* 获取资产实时状态
|
||||||
* GET /api/admin/assets/:asset_type/:id/realtime-status
|
* GET /api/admin/assets/:identifier/realtime-status
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
*/
|
*/
|
||||||
export function getAssetRealtimeStatus(assetType: AssetType, id: number) {
|
export function getAssetRealtimeStatus(identifier: string) {
|
||||||
return get<AssetRealtimeStatus>(`/api/admin/assets/${assetType}/${id}/realtime-status`)
|
return get<AssetRealtimeStatus>(`/api/admin/assets/${identifier}/realtime-status`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,41 +287,44 @@ export interface AssetPackage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资产套餐列表
|
* 获取资产套餐列表
|
||||||
* GET /api/admin/assets/:asset_type/:id/packages
|
* GET /api/admin/assets/:identifier/packages
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
*/
|
*/
|
||||||
export function getAssetPackages(assetType: AssetType, id: number, params?: { page?: number, page_size?: number }) {
|
export function getAssetPackages(identifier: string, params?: { page?: number, page_size?: number }) {
|
||||||
return get<{
|
return get<{
|
||||||
items: AssetPackage[]
|
items: AssetPackage[]
|
||||||
page: number
|
page: number
|
||||||
page_size: number
|
page_size: number
|
||||||
total: number
|
total: number
|
||||||
}>(`/api/admin/assets/${assetType}/${id}/packages`, { params })
|
}>(`/api/admin/assets/${identifier}/packages`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前生效套餐
|
* 获取当前生效套餐
|
||||||
* GET /api/admin/assets/:asset_type/:id/current-package
|
* GET /api/admin/assets/:identifier/current-package
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
*/
|
*/
|
||||||
export function getCurrentPackage(assetType: AssetType, id: number) {
|
export function getCurrentPackage(identifier: string) {
|
||||||
return get<AssetPackage>(`/api/admin/assets/${assetType}/${id}/current-package`)
|
return get<AssetPackage>(`/api/admin/assets/${identifier}/current-package`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资产历史记录
|
* 获取资产历史记录
|
||||||
* GET /api/admin/assets/:type/:id/history
|
* GET /api/admin/assets/:identifier/history
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
*/
|
*/
|
||||||
export function getAssetHistory(type: AssetType, id: string, params?: { page?: number, pageSize?: number }) {
|
export function getAssetHistory(identifier: string, params?: { page?: number, pageSize?: number }) {
|
||||||
return get<{
|
return get<{
|
||||||
list: HistoryRecord[]
|
list: HistoryRecord[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
}>(`/api/admin/assets/${type}/${id}/history`, { params })
|
}>(`/api/admin/assets/${identifier}/history`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停用卡片
|
* 停用卡片
|
||||||
* POST /api/admin/cards/:id/stop
|
* @deprecated 使用 stopAsset 代替,路由: POST /api/admin/assets/:identifier/stop
|
||||||
*/
|
*/
|
||||||
export function stopCard(data: OperationParams) {
|
export function stopCard(data: OperationParams) {
|
||||||
return post<void>(`/api/admin/cards/${data.id}/stop`, {
|
return post<void>(`/api/admin/cards/${data.id}/stop`, {
|
||||||
@@ -319,7 +334,7 @@ export function stopCard(data: OperationParams) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用卡片
|
* 启用卡片
|
||||||
* POST /api/admin/cards/:id/start
|
* @deprecated 使用 startAsset 代替,路由: POST /api/admin/assets/:identifier/start
|
||||||
*/
|
*/
|
||||||
export function startCard(data: OperationParams) {
|
export function startCard(data: OperationParams) {
|
||||||
return post<void>(`/api/admin/cards/${data.id}/start`, {
|
return post<void>(`/api/admin/cards/${data.id}/start`, {
|
||||||
@@ -329,21 +344,23 @@ export function startCard(data: OperationParams) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 停用设备
|
* 停用设备
|
||||||
* POST /api/admin/devices/:id/stop
|
* POST /api/admin/devices/:virtual_no/stop
|
||||||
|
* @param virtualNo 设备虚拟号
|
||||||
*/
|
*/
|
||||||
export function stopDevice(data: OperationParams) {
|
export function stopDevice(virtualNo: string, reason?: string) {
|
||||||
return post<void>(`/api/admin/devices/${data.id}/stop`, {
|
return post<void>(`/api/admin/devices/${virtualNo}/stop`, {
|
||||||
data: { reason: data.reason },
|
data: { reason },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用设备
|
* 启用设备
|
||||||
* POST /api/admin/devices/:id/start
|
* POST /api/admin/devices/:virtual_no/start
|
||||||
|
* @param virtualNo 设备虚拟号
|
||||||
*/
|
*/
|
||||||
export function startDevice(data: OperationParams) {
|
export function startDevice(virtualNo: string, reason?: string) {
|
||||||
return post<void>(`/api/admin/devices/${data.id}/start`, {
|
return post<void>(`/api/admin/devices/${virtualNo}/start`, {
|
||||||
data: { reason: data.reason },
|
data: { reason },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +384,7 @@ export interface DeviceStopResponse {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备停机 (批量停机设备下所有已实名卡)
|
* 设备停机 (批量停机设备下所有已实名卡)
|
||||||
* POST /api/admin/assets/device/:device_id/stop
|
* @deprecated 使用 stopAsset 代替,路由: POST /api/admin/assets/:identifier/stop
|
||||||
*/
|
*/
|
||||||
export function stopDeviceAsset(deviceId: number) {
|
export function stopDeviceAsset(deviceId: number) {
|
||||||
return post<DeviceStopResponse>(`/api/admin/assets/device/${deviceId}/stop`)
|
return post<DeviceStopResponse>(`/api/admin/assets/device/${deviceId}/stop`)
|
||||||
@@ -375,7 +392,7 @@ export function stopDeviceAsset(deviceId: number) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备复机 (批量复机设备下所有已实名卡)
|
* 设备复机 (批量复机设备下所有已实名卡)
|
||||||
* POST /api/admin/assets/device/:device_id/start
|
* @deprecated 使用 startAsset 代替,路由: POST /api/admin/assets/:identifier/start
|
||||||
*/
|
*/
|
||||||
export function startDeviceAsset(deviceId: number) {
|
export function startDeviceAsset(deviceId: number) {
|
||||||
return post<void>(`/api/admin/assets/device/${deviceId}/start`)
|
return post<void>(`/api/admin/assets/device/${deviceId}/start`)
|
||||||
@@ -383,7 +400,7 @@ export function startDeviceAsset(deviceId: number) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单卡停机 (通过ICCID)
|
* 单卡停机 (通过ICCID)
|
||||||
* POST /api/admin/assets/card/:iccid/stop
|
* @deprecated 使用 stopAsset 代替,路由: POST /api/admin/assets/:identifier/stop
|
||||||
*/
|
*/
|
||||||
export function stopCardByIccid(iccid: string) {
|
export function stopCardByIccid(iccid: string) {
|
||||||
return post<void>(`/api/admin/assets/card/${iccid}/stop`)
|
return post<void>(`/api/admin/assets/card/${iccid}/stop`)
|
||||||
@@ -391,8 +408,135 @@ export function stopCardByIccid(iccid: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单卡复机 (通过ICCID)
|
* 单卡复机 (通过ICCID)
|
||||||
* POST /api/admin/assets/card/:iccid/start
|
* @deprecated 使用 startAsset 代替,路由: POST /api/admin/assets/:identifier/start
|
||||||
*/
|
*/
|
||||||
export function startCardByIccid(iccid: string) {
|
export function startCardByIccid(iccid: string) {
|
||||||
return post<void>(`/api/admin/assets/card/${iccid}/start`)
|
return post<void>(`/api/admin/assets/card/${iccid}/start`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一停机接口 (卡/设备)
|
||||||
|
* POST /api/admin/assets/:identifier/stop
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
* @param reason 停机原因
|
||||||
|
*/
|
||||||
|
export function stopAsset(identifier: string, reason?: string) {
|
||||||
|
return post<void>(`/api/admin/assets/${identifier}/stop`, {
|
||||||
|
data: { reason },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一复机接口 (卡/设备)
|
||||||
|
* POST /api/admin/assets/:identifier/start
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
* @param reason 复机原因
|
||||||
|
*/
|
||||||
|
export function startAsset(identifier: string, reason?: string) {
|
||||||
|
return post<void>(`/api/admin/assets/${identifier}/start`, {
|
||||||
|
data: { reason },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一停用接口 (卡/设备)
|
||||||
|
* POST /api/admin/assets/:identifier/deactivate
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
*/
|
||||||
|
export function deactivateAsset(identifier: string) {
|
||||||
|
return post<void>(`/api/admin/assets/${identifier}/deactivate`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资产钱包信息
|
||||||
|
* GET /api/admin/assets/:identifier/wallet
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
*/
|
||||||
|
export function getAssetWallet(identifier: string) {
|
||||||
|
return get<{
|
||||||
|
available_balance: number
|
||||||
|
frozen_balance: number
|
||||||
|
total_balance: number
|
||||||
|
}>(`/api/admin/assets/${identifier}/wallet`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资产钱包交易记录
|
||||||
|
* GET /api/admin/assets/:identifier/wallet/transactions
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
*/
|
||||||
|
export function getAssetWalletTransactions(identifier: string, params?: { page?: number, page_size?: number }) {
|
||||||
|
return get<{
|
||||||
|
items: Array<{
|
||||||
|
id: number
|
||||||
|
type: string
|
||||||
|
amount: number
|
||||||
|
balance_after: number
|
||||||
|
remark?: string
|
||||||
|
created_at: string
|
||||||
|
}>
|
||||||
|
page: number
|
||||||
|
page_size: number
|
||||||
|
total: number
|
||||||
|
}>(`/api/admin/assets/${identifier}/wallet/transactions`, { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新资产轮询状态
|
||||||
|
* PATCH /api/admin/assets/:identifier/polling-status
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
*/
|
||||||
|
export function updatePollingStatus(identifier: string, enable: boolean) {
|
||||||
|
return patch<void>(`/api/admin/assets/${identifier}/polling-status`, {
|
||||||
|
data: { enable_polling: enable },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资产历史订单
|
||||||
|
* GET /api/admin/assets/:identifier/orders
|
||||||
|
* @param identifier ICCID 或 VirtualNo
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
export function getAssetOrders(identifier: string, params?: {
|
||||||
|
page?: number
|
||||||
|
page_size?: number
|
||||||
|
include_previous?: boolean
|
||||||
|
}) {
|
||||||
|
return get<{
|
||||||
|
current_generation: {
|
||||||
|
generation: number
|
||||||
|
identifier: string
|
||||||
|
asset_type: string
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
page_size: number
|
||||||
|
items: Array<{
|
||||||
|
order_no: string
|
||||||
|
order_type: string
|
||||||
|
payment_status: number
|
||||||
|
payment_status_text: string
|
||||||
|
total_amount: number
|
||||||
|
payment_method: string
|
||||||
|
paid_at?: string
|
||||||
|
generation: number
|
||||||
|
items: Array<{
|
||||||
|
package_name: string
|
||||||
|
quantity: number
|
||||||
|
unit_price: number
|
||||||
|
amount: number
|
||||||
|
}>
|
||||||
|
created_at: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
previous_generations?: Array<{
|
||||||
|
generation: number
|
||||||
|
identifier: string
|
||||||
|
asset_type: string
|
||||||
|
exchange_no: string
|
||||||
|
exchanged_at: string
|
||||||
|
total: number
|
||||||
|
}>
|
||||||
|
truncated?: boolean
|
||||||
|
}>(`/api/admin/assets/${identifier}/orders`, { params })
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,29 @@
|
|||||||
import { get, post } from '@/utils/request'
|
import { get, post } from '@/utils/request'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 佣金概览
|
* 资金概览 (代理端主页用)
|
||||||
|
* GET /api/admin/shops/fund-summary
|
||||||
|
*/
|
||||||
|
export interface FundSummary {
|
||||||
|
shop_id: number
|
||||||
|
shop_name: string
|
||||||
|
shop_code: string
|
||||||
|
username: string
|
||||||
|
phone: string
|
||||||
|
main_balance: number // 预充值钱包余额(分)
|
||||||
|
main_frozen_balance: number // 预充值冻结余额(分)
|
||||||
|
total_commission: number // 累计佣金(分)
|
||||||
|
available_commission: number // 可提现佣金(分)
|
||||||
|
frozen_commission: number // 冻结佣金(分)
|
||||||
|
unwithdraw_commission: number // 未提现佣金(分)
|
||||||
|
withdrawing_commission: number // 提现中金额(分)
|
||||||
|
withdrawn_commission: number // 已提现佣金(分)
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 佣金概览 (旧接口,仅保留类型定义)
|
||||||
|
* @deprecated 使用 FundSummary 代替
|
||||||
*/
|
*/
|
||||||
export interface CommissionSummary {
|
export interface CommissionSummary {
|
||||||
shop_id: number
|
shop_id: number
|
||||||
@@ -67,24 +89,47 @@ export interface CommissionRecordsParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 佣金统计查询参数
|
* 佣金统计查询参数 (新接口,shop_id 在路径中)
|
||||||
*/
|
*/
|
||||||
export interface CommissionStatsParams {
|
export interface CommissionStatsParams {
|
||||||
shop_id?: number
|
|
||||||
start_time?: string
|
start_time?: string
|
||||||
end_time?: string
|
end_time?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每日佣金统计查询参数
|
* 每日佣金统计查询参数 (新接口,shop_id 在路径中)
|
||||||
*/
|
*/
|
||||||
export interface DailyCommissionStatsParams {
|
export interface DailyCommissionStatsParams {
|
||||||
shop_id?: number
|
|
||||||
start_date?: string // YYYY-MM-DD
|
start_date?: string // YYYY-MM-DD
|
||||||
end_date?: string // YYYY-MM-DD
|
end_date?: string // YYYY-MM-DD
|
||||||
days?: number // 查询天数(默认30,最大365)
|
days?: number // 查询天数(默认30,最大365)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预充值钱包交易记录
|
||||||
|
*/
|
||||||
|
export interface WalletTransaction {
|
||||||
|
id: number
|
||||||
|
transaction_type: string // 交易类型: recharge | deduct | refund
|
||||||
|
transaction_subtype: string
|
||||||
|
amount: number // 金额(分),正数为入账,负数为扣款
|
||||||
|
balance_before: number // 余额变动前(分)
|
||||||
|
balance_after: number // 余额变动后(分)
|
||||||
|
remark?: string // 备注
|
||||||
|
created_at: string // 创建时间
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预充值钱包交易列表参数
|
||||||
|
*/
|
||||||
|
export interface WalletTransactionsParams {
|
||||||
|
page?: number
|
||||||
|
page_size?: number
|
||||||
|
transaction_type?: string // 交易类型: recharge | deduct | refund
|
||||||
|
start_date?: string // YYYY-MM-DD
|
||||||
|
end_date?: string // YYYY-MM-DD
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列表响应
|
* 列表响应
|
||||||
*/
|
*/
|
||||||
@@ -95,34 +140,63 @@ export interface ListResponse<T> {
|
|||||||
total: number
|
total: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资金概览 (代理端主页用)
|
||||||
|
* GET /api/admin/shops/fund-summary
|
||||||
|
*/
|
||||||
|
export function getFundSummary() {
|
||||||
|
return get<{ items: FundSummary[] }>('/api/admin/shops/fund-summary').then(res => res.items?.[0])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的佣金概览
|
* 获取我的佣金概览
|
||||||
* GET /api/admin/my/commission-summary
|
* @deprecated 使用 getFundSummary 代替
|
||||||
|
* GET /api/admin/shops/{shop_id}/commission-summary
|
||||||
*/
|
*/
|
||||||
export function getCommissionSummary() {
|
export function getCommissionSummary() {
|
||||||
return get<CommissionSummary>('/api/admin/my/commission-summary')
|
return get<CommissionSummary>('/api/admin/shops/1/commission-summary')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的佣金明细
|
* 获取我的佣金明细
|
||||||
* GET /api/admin/my/commission-records
|
* GET /api/admin/shops/{shop_id}/commission-records
|
||||||
|
* @param shopId 店铺ID
|
||||||
*/
|
*/
|
||||||
export function getCommissionRecords(params?: CommissionRecordsParams) {
|
export function getCommissionRecords(shopId: number, params?: CommissionRecordsParams) {
|
||||||
return get<ListResponse<CommissionRecord>>('/api/admin/my/commission-records', { params })
|
return get<ListResponse<CommissionRecord>>(`/api/admin/shops/${shopId}/commission-records`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的佣金统计
|
* 获取我的佣金统计
|
||||||
* GET /api/admin/my/commission-stats
|
* GET /api/admin/shops/{shop_id}/commission-stats
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @param params 查询参数
|
||||||
*/
|
*/
|
||||||
export function getCommissionStats(params?: CommissionStatsParams) {
|
export function getCommissionStats(shopId: number, params?: CommissionStatsParams) {
|
||||||
return get<CommissionStats>('/api/admin/my/commission-stats', { params })
|
return get<CommissionStats>(`/api/admin/shops/${shopId}/commission-stats`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的每日佣金统计
|
* 获取我的每日佣金统计
|
||||||
* GET /api/admin/my/commission-daily-stats
|
* GET /api/admin/shops/{shop_id}/commission-daily-stats
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @param params 查询参数
|
||||||
*/
|
*/
|
||||||
export function getCommissionDailyStats(params?: DailyCommissionStatsParams) {
|
export function getCommissionDailyStats(shopId: number, params?: DailyCommissionStatsParams) {
|
||||||
return get<DailyCommissionStats[]>('/api/admin/my/commission-daily-stats', { params })
|
return get<DailyCommissionStats[]>(`/api/admin/shops/${shopId}/commission-daily-stats`, { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取预充值钱包流水
|
||||||
|
* GET /api/admin/shops/{shop_id}/main-wallet/transactions
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
export function getMainWalletTransactions(shopId: number, params?: WalletTransactionsParams) {
|
||||||
|
return get<{
|
||||||
|
items: WalletTransaction[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
}>(`/api/admin/shops/${shopId}/main-wallet/transactions`, { params })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,16 +74,20 @@ export interface ListResponse<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起提现申请
|
* 发起提现申请
|
||||||
* POST /api/admin/my/withdrawal-requests
|
* POST /api/admin/shops/{shop_id}/withdrawal-requests
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @param data 提现申请参数
|
||||||
*/
|
*/
|
||||||
export function createWithdrawal(data: WithdrawalCreateParams) {
|
export function createWithdrawal(shopId: number, data: WithdrawalCreateParams) {
|
||||||
return post<WithdrawalCreateResponse>('/api/admin/my/withdrawal-requests', { data })
|
return post<WithdrawalCreateResponse>(`/api/admin/shops/${shopId}/withdrawal-requests`, { data })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的提现记录
|
* 获取提现记录
|
||||||
* GET /api/admin/my/withdrawal-requests
|
* GET /api/admin/shops/{shop_id}/withdrawal-requests
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @param params 查询参数
|
||||||
*/
|
*/
|
||||||
export function getWithdrawalRecords(params?: WithdrawalListParams) {
|
export function getWithdrawalRecords(shopId: number, params?: WithdrawalListParams) {
|
||||||
return get<ListResponse<WithdrawalRecord>>('/api/admin/my/withdrawal-requests', { params })
|
return get<ListResponse<WithdrawalRecord>>(`/api/admin/shops/${shopId}/withdrawal-requests`, { params })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ function selectOption(index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.simple-picker-item-active .simple-picker-item-text {
|
.simple-picker-item-active .simple-picker-item-text {
|
||||||
color: var(--brand-color, #ff6700);
|
color: var(--brand-primary, #1677ff);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,27 +2,35 @@
|
|||||||
<view class="skeleton">
|
<view class="skeleton">
|
||||||
<view v-if="type === 'home'" class="skeleton-home">
|
<view v-if="type === 'home'" class="skeleton-home">
|
||||||
<!-- 顶部用户信息骨架 -->
|
<!-- 顶部用户信息骨架 -->
|
||||||
<view class="safe-area-top bg-white px-4 pt-4 pb-4">
|
<view class="px-4 pt-4">
|
||||||
<view class="flex items-center justify-between">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] px-4 py-4">
|
||||||
<view>
|
<view class="flex items-center">
|
||||||
<view class="skeleton-block h-6 w-32 mb-2" />
|
<view class="skeleton-circle w-56px h-56px mr-4" />
|
||||||
<view class="skeleton-block h-4 w-24" />
|
<view class="flex-1">
|
||||||
|
<view class="skeleton-block h-5 w-32 mb-2" />
|
||||||
|
<view class="skeleton-block h-3 w-16 mb-2" />
|
||||||
|
<view class="skeleton-block h-3 w-24" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="skeleton-circle w-12 h-12" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 数据卡片骨架 -->
|
<!-- 数据卡片骨架 -->
|
||||||
<view class="px-4 pt-4">
|
<view class="px-4 pt-4">
|
||||||
<view class="bg-white rounded-2xl p-5">
|
<view class="skeleton-block h-5 w-24 mb-3" />
|
||||||
<view class="grid grid-cols-2 gap-6">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4">
|
||||||
|
<view class="flex justify-between">
|
||||||
<view>
|
<view>
|
||||||
<view class="skeleton-block h-3 w-16 mb-2" />
|
<view class="skeleton-block h-3 w-20 mb-1" />
|
||||||
<view class="skeleton-block h-8 w-24" />
|
<view class="skeleton-block h-5 w-24" />
|
||||||
</view>
|
</view>
|
||||||
<view>
|
<view>
|
||||||
<view class="skeleton-block h-3 w-16 mb-2" />
|
<view class="skeleton-block h-3 w-16 mb-1" />
|
||||||
<view class="skeleton-block h-8 w-24" />
|
<view class="skeleton-block h-5 w-20" />
|
||||||
|
</view>
|
||||||
|
<view class="text-right">
|
||||||
|
<view class="skeleton-block h-3 w-20 mb-1" />
|
||||||
|
<view class="skeleton-block h-5 w-24" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -30,8 +38,8 @@
|
|||||||
|
|
||||||
<!-- 快捷功能骨架 -->
|
<!-- 快捷功能骨架 -->
|
||||||
<view class="px-4 pt-6 pb-4">
|
<view class="px-4 pt-6 pb-4">
|
||||||
<view class="skeleton-block h-4 w-16 mb-3" />
|
<view class="skeleton-block h-5 w-24 mb-3" />
|
||||||
<view class="bg-white rounded-2xl overflow-hidden">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||||
<view v-for="i in 5" :key="i" class="flex items-center px-4 py-4" :class="{ 'border-t border-[#f1f5f9]': i > 1 }">
|
<view v-for="i in 5" :key="i" class="flex items-center px-4 py-4" :class="{ 'border-t border-[#f1f5f9]': i > 1 }">
|
||||||
<view class="skeleton-circle w-10 h-10 mr-3" />
|
<view class="skeleton-circle w-10 h-10 mr-3" />
|
||||||
<view class="flex-1">
|
<view class="flex-1">
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopCard, startCard, stopDevice, startDevice } from '@/api/assets'
|
import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopAsset, startAsset } from '@/api/assets'
|
||||||
import type { AssetType, CardInfo, DeviceInfo, AssetPackage, HistoryRecord } from '@/api/assets'
|
import type { AssetType, CardInfo, DeviceInfo, AssetPackage, HistoryRecord } from '@/api/assets'
|
||||||
|
|
||||||
// 资产类型
|
// 资产类型
|
||||||
const assetType = ref<AssetType>('card')
|
const assetType = ref<AssetType>('card')
|
||||||
|
|
||||||
// 资产ID
|
// 资产标识符 (ICCID 或 VirtualNo)
|
||||||
const assetId = ref('')
|
const assetIdentifier = ref('')
|
||||||
|
|
||||||
// 资产详情数据
|
// 资产详情数据
|
||||||
const assetDetail = ref<CardInfo | DeviceInfo | null>(null)
|
const assetDetail = ref<CardInfo | DeviceInfo | null>(null)
|
||||||
@@ -57,11 +57,11 @@ const statusColor = computed(() => {
|
|||||||
|
|
||||||
// 页面加载
|
// 页面加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 从路由参数获取 assetType 和 assetId
|
// 从路由参数获取 assetType 和 assetIdentifier
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
const currentPage = pages[pages.length - 1] as any
|
const currentPage = pages[pages.length - 1] as any
|
||||||
assetType.value = currentPage.options.type as AssetType
|
assetType.value = currentPage.options.type as AssetType
|
||||||
assetId.value = currentPage.options.id
|
assetIdentifier.value = currentPage.options.id
|
||||||
|
|
||||||
loadAssetDetail()
|
loadAssetDetail()
|
||||||
})
|
})
|
||||||
@@ -120,7 +120,7 @@ async function loadPackages() {
|
|||||||
try {
|
try {
|
||||||
// 1. 调用当前生效套餐接口
|
// 1. 调用当前生效套餐接口
|
||||||
try {
|
try {
|
||||||
const current = await getCurrentPackage(assetType.value, Number(assetId.value))
|
const current = await getCurrentPackage(assetIdentifier.value)
|
||||||
currentPackage.value = current
|
currentPackage.value = current
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('获取当前套餐失败:', error)
|
console.warn('获取当前套餐失败:', error)
|
||||||
@@ -128,7 +128,7 @@ async function loadPackages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 调用套餐列表接口
|
// 2. 调用套餐列表接口
|
||||||
const packagesData = await getAssetPackages(assetType.value, Number(assetId.value), {
|
const packagesData = await getAssetPackages(assetIdentifier.value, {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 50
|
page_size: 50
|
||||||
})
|
})
|
||||||
@@ -147,7 +147,7 @@ async function loadPackages() {
|
|||||||
// 加载历史记录
|
// 加载历史记录
|
||||||
async function loadHistory() {
|
async function loadHistory() {
|
||||||
try {
|
try {
|
||||||
const historyData = await getAssetHistory(assetType.value, assetId.value, {
|
const historyData = await getAssetHistory(assetIdentifier.value, {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
})
|
})
|
||||||
@@ -193,7 +193,7 @@ async function handleTabChange(tab: 'info' | 'package' | 'history') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 停机
|
// 停机
|
||||||
async function stopAsset() {
|
async function handleStopAsset() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '确认停机',
|
title: '确认停机',
|
||||||
content: '停机后将无法使用网络服务,确定要停机吗?',
|
content: '停机后将无法使用网络服务,确定要停机吗?',
|
||||||
@@ -201,13 +201,8 @@ async function stopAsset() {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
operating.value = true
|
operating.value = true
|
||||||
try {
|
try {
|
||||||
// 调用停机接口
|
// 调用统一停机接口
|
||||||
if (assetType.value === 'card') {
|
await stopAsset(assetIdentifier.value)
|
||||||
await stopCard({ id: assetId.value })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await stopDevice({ id: assetId.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '停机成功',
|
title: '停机成功',
|
||||||
@@ -232,7 +227,7 @@ async function stopAsset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 复机
|
// 复机
|
||||||
async function startAsset() {
|
async function handleStartAsset() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '确认复机',
|
title: '确认复机',
|
||||||
content: '复机后将恢复网络服务,确定要复机吗?',
|
content: '复机后将恢复网络服务,确定要复机吗?',
|
||||||
@@ -240,13 +235,8 @@ async function startAsset() {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
operating.value = true
|
operating.value = true
|
||||||
try {
|
try {
|
||||||
// 调用复机接口
|
// 调用统一复机接口
|
||||||
if (assetType.value === 'card') {
|
await startAsset(assetIdentifier.value)
|
||||||
await startCard({ id: assetId.value })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await startDevice({ id: assetId.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '复机成功',
|
title: '复机成功',
|
||||||
@@ -274,7 +264,7 @@ async function startAsset() {
|
|||||||
function viewWallet() {
|
function viewWallet() {
|
||||||
// 跳转到钱包页面
|
// 跳转到钱包页面
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/agent-system/asset-wallet/index?type=${assetType.value}&id=${assetId.value}`,
|
url: `/pages/agent-system/asset-wallet/index?type=${assetType.value}&id=${assetIdentifier.value}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -459,7 +449,7 @@ function viewWallet() {
|
|||||||
<text class="text-16px font-700 text-[#212121] block mb-2">
|
<text class="text-16px font-700 text-[#212121] block mb-2">
|
||||||
{{ currentPackage.package_name || '-' }}
|
{{ currentPackage.package_name || '-' }}
|
||||||
</text>
|
</text>
|
||||||
<text class="text-14px text-[#ff6700] font-600 block mb-3">
|
<text class="text-14px text-[var(--brand-primary)] font-600 block mb-3">
|
||||||
已用: {{ currentPackage.virtual_used_mb || 0 }}MB / 总量: {{ currentPackage.virtual_limit_mb || 0 }}MB
|
已用: {{ currentPackage.virtual_used_mb || 0 }}MB / 总量: {{ currentPackage.virtual_limit_mb || 0 }}MB
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
@@ -546,7 +536,7 @@ function viewWallet() {
|
|||||||
transition-all duration-200
|
transition-all duration-200
|
||||||
active:opacity-80
|
active:opacity-80
|
||||||
"
|
"
|
||||||
@click="stopAsset"
|
@click="handleStopAsset"
|
||||||
>
|
>
|
||||||
<i class="i-mdi-pause-circle text-18px text-[#c62828] mr-1" />
|
<i class="i-mdi-pause-circle text-18px text-[#c62828] mr-1" />
|
||||||
<text class="text-15px font-600 text-[#c62828]">
|
<text class="text-15px font-600 text-[#c62828]">
|
||||||
@@ -562,7 +552,7 @@ function viewWallet() {
|
|||||||
transition-all duration-200
|
transition-all duration-200
|
||||||
active:opacity-80
|
active:opacity-80
|
||||||
"
|
"
|
||||||
@click="startAsset"
|
@click="handleStartAsset"
|
||||||
>
|
>
|
||||||
<i class="i-mdi-play-circle text-18px text-[#2e7d32] mr-1" />
|
<i class="i-mdi-play-circle text-18px text-[#2e7d32] mr-1" />
|
||||||
<text class="text-15px font-600 text-[#2e7d32]">
|
<text class="text-15px font-600 text-[#2e7d32]">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
// 套餐列表
|
// 套餐列表
|
||||||
const packageList = ref<AssetPackage[]>([])
|
const packageList = ref<AssetPackage[]>([])
|
||||||
// 是否展开套餐列表
|
// 是否展开套餐列表
|
||||||
const showPackageList = ref(false)
|
const showPackageList = ref(true)
|
||||||
|
|
||||||
// 是否为卡片
|
// 是否为卡片
|
||||||
const isCard = computed(() => assetType.value === 'card')
|
const isCard = computed(() => assetType.value === 'card')
|
||||||
@@ -39,10 +39,11 @@
|
|||||||
assetType.value = result.asset_type
|
assetType.value = result.asset_type
|
||||||
assetInfo.value = result
|
assetInfo.value = result
|
||||||
|
|
||||||
// 加载实时状态和当前套餐
|
// 加载实时状态和当前套餐 (使用 identifier)
|
||||||
if (result.asset_type && result.asset_id) {
|
if (searchKeyword.value.trim()) {
|
||||||
loadRealtimeStatus(result.asset_type, result.asset_id)
|
loadRealtimeStatus(searchKeyword.value.trim())
|
||||||
loadCurrentPackage(result.asset_type, result.asset_id)
|
loadCurrentPackage(searchKeyword.value.trim())
|
||||||
|
loadPackageList(searchKeyword.value.trim())
|
||||||
}
|
}
|
||||||
} catch (error : any) {
|
} catch (error : any) {
|
||||||
console.error('查询失败:', error)
|
console.error('查询失败:', error)
|
||||||
@@ -53,9 +54,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载实时状态
|
// 加载实时状态
|
||||||
async function loadRealtimeStatus(type : 'card' | 'device', id : number) {
|
async function loadRealtimeStatus(identifier : string) {
|
||||||
try {
|
try {
|
||||||
const status = await getAssetRealtimeStatus(type, id)
|
const status = await getAssetRealtimeStatus(identifier)
|
||||||
realtimeStatus.value = status
|
realtimeStatus.value = status
|
||||||
} catch (error : any) {
|
} catch (error : any) {
|
||||||
console.error('加载实时状态失败:', error)
|
console.error('加载实时状态失败:', error)
|
||||||
@@ -63,10 +64,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载当前套餐
|
// 加载当前套餐
|
||||||
async function loadCurrentPackage(type : 'card' | 'device', id : number) {
|
async function loadCurrentPackage(identifier : string) {
|
||||||
try {
|
try {
|
||||||
console.log('开始加载当前套餐:', type, id)
|
console.log('开始加载当前套餐:', identifier)
|
||||||
const pkg = await getCurrentPackage(type, id)
|
const pkg = await getCurrentPackage(identifier)
|
||||||
console.log('当前套餐数据:', pkg)
|
console.log('当前套餐数据:', pkg)
|
||||||
currentPackage.value = pkg
|
currentPackage.value = pkg
|
||||||
} catch (error : any) {
|
} catch (error : any) {
|
||||||
@@ -77,10 +78,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载套餐列表
|
// 加载套餐列表
|
||||||
async function loadPackageList(type : 'card' | 'device', id : number) {
|
async function loadPackageList(identifier : string) {
|
||||||
try {
|
try {
|
||||||
console.log('开始加载套餐列表:', type, id)
|
console.log('开始加载套餐列表:', identifier)
|
||||||
const response = await getAssetPackages(type, id, { page: 1, page_size: 50 })
|
const response = await getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||||
console.log('套餐列表数据:', response)
|
console.log('套餐列表数据:', response)
|
||||||
packageList.value = response?.items || []
|
packageList.value = response?.items || []
|
||||||
} catch (error : any) {
|
} catch (error : any) {
|
||||||
@@ -93,8 +94,8 @@
|
|||||||
async function togglePackageList() {
|
async function togglePackageList() {
|
||||||
showPackageList.value = !showPackageList.value
|
showPackageList.value = !showPackageList.value
|
||||||
// 第一次展开时加载套餐列表
|
// 第一次展开时加载套餐列表
|
||||||
if (showPackageList.value && packageList.value.length === 0 && assetType.value && assetInfo.value?.asset_id) {
|
if (showPackageList.value && packageList.value.length === 0 && searchKeyword.value.trim()) {
|
||||||
await loadPackageList(assetType.value, assetInfo.value.asset_id)
|
await loadPackageList(searchKeyword.value.trim())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,9 +326,9 @@
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 电话号码 -->
|
<!-- msisdn -->
|
||||||
<view v-if="cardInfo.msisdn" class="flex items-start justify-between">
|
<view v-if="cardInfo.msisdn" class="flex items-start justify-between">
|
||||||
<text class="text-12px text-[#999] w-70px">电话号码</text>
|
<text class="text-12px text-[#999] w-70px">msisdn</text>
|
||||||
<text class="flex-1 text-13px text-[#212121] text-right">
|
<text class="flex-1 text-13px text-[#212121] text-right">
|
||||||
{{ cardInfo.msisdn }}
|
{{ cardInfo.msisdn }}
|
||||||
</text>
|
</text>
|
||||||
@@ -376,7 +377,7 @@
|
|||||||
<!-- 累计充值 -->
|
<!-- 累计充值 -->
|
||||||
<view v-if="cardInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
|
<view v-if="cardInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
|
||||||
<text class="text-12px text-[#999] w-70px">累计充值</text>
|
<text class="text-12px text-[#999] w-70px">累计充值</text>
|
||||||
<text class="flex-1 text-13px font-600 text-[#ff6700] text-right">
|
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
|
||||||
¥{{ cardInfo.accumulated_recharge }}
|
¥{{ cardInfo.accumulated_recharge }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -424,7 +425,7 @@
|
|||||||
<!-- 本月用量 -->
|
<!-- 本月用量 -->
|
||||||
<view class="flex items-center justify-between">
|
<view class="flex items-center justify-between">
|
||||||
<text class="text-12px text-[#666]">本月用量</text>
|
<text class="text-12px text-[#666]">本月用量</text>
|
||||||
<text class="text-13px font-600 text-[#ff6700]">
|
<text class="text-13px font-600 text-[var(--brand-primary)]">
|
||||||
{{
|
{{
|
||||||
realtimeStatus.current_month_usage_mb !== null && realtimeStatus.current_month_usage_mb !== undefined
|
realtimeStatus.current_month_usage_mb !== null && realtimeStatus.current_month_usage_mb !== undefined
|
||||||
? formatDataSize(realtimeStatus.current_month_usage_mb)
|
? formatDataSize(realtimeStatus.current_month_usage_mb)
|
||||||
@@ -495,7 +496,7 @@
|
|||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<i :class="[
|
<i :class="[
|
||||||
'text-16px',
|
'text-16px',
|
||||||
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[#ff6700]' : 'i-mdi-gas-station text-[#2e7d32]',
|
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
|
||||||
]" />
|
]" />
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
{{ currentPackage.package_name }}
|
{{ currentPackage.package_name }}
|
||||||
@@ -564,7 +565,7 @@
|
|||||||
<view class="flex items-center gap-1.5">
|
<view class="flex items-center gap-1.5">
|
||||||
<i :class="[
|
<i :class="[
|
||||||
'text-14px',
|
'text-14px',
|
||||||
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[#ff6700]' : 'i-mdi-gas-station text-[#2e7d32]',
|
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
|
||||||
]" />
|
]" />
|
||||||
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
|
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
|
||||||
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
|
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
|
||||||
@@ -697,7 +698,7 @@
|
|||||||
<!-- 绑定卡数 -->
|
<!-- 绑定卡数 -->
|
||||||
<view v-if="deviceInfo.bound_card_count !== undefined" class="flex items-start justify-between">
|
<view v-if="deviceInfo.bound_card_count !== undefined" class="flex items-start justify-between">
|
||||||
<text class="text-12px text-[#999] w-80px">绑定卡数</text>
|
<text class="text-12px text-[#999] w-80px">绑定卡数</text>
|
||||||
<text class="flex-1 text-13px font-600 text-[#ff6700] text-right">
|
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
|
||||||
{{ deviceInfo.bound_card_count }} 张
|
{{ deviceInfo.bound_card_count }} 张
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -753,7 +754,7 @@
|
|||||||
<!-- 累计充值 -->
|
<!-- 累计充值 -->
|
||||||
<view v-if="deviceInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
|
<view v-if="deviceInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
|
||||||
<text class="text-12px text-[#999] w-80px">累计充值</text>
|
<text class="text-12px text-[#999] w-80px">累计充值</text>
|
||||||
<text class="flex-1 text-13px font-600 text-[#ff6700] text-right">
|
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
|
||||||
¥{{ deviceInfo.accumulated_recharge }}
|
¥{{ deviceInfo.accumulated_recharge }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -876,7 +877,7 @@
|
|||||||
<!-- 今日用量 -->
|
<!-- 今日用量 -->
|
||||||
<view v-if="realtimeStatus.device_realtime.daily_usage" class="flex items-center justify-between text-11px">
|
<view v-if="realtimeStatus.device_realtime.daily_usage" class="flex items-center justify-between text-11px">
|
||||||
<text class="text-[#666]">今日用量</text>
|
<text class="text-[#666]">今日用量</text>
|
||||||
<text class="text-[#ff6700] font-600">{{ formatBytes(realtimeStatus.device_realtime.daily_usage) }}</text>
|
<text class="text-[var(--brand-primary)] font-600">{{ formatBytes(realtimeStatus.device_realtime.daily_usage) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 下载流量 -->
|
<!-- 下载流量 -->
|
||||||
@@ -1017,7 +1018,7 @@
|
|||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<i :class="[
|
<i :class="[
|
||||||
'text-16px',
|
'text-16px',
|
||||||
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[#ff6700]' : 'i-mdi-gas-station text-[#2e7d32]',
|
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
|
||||||
]" />
|
]" />
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
{{ currentPackage.package_name }}
|
{{ currentPackage.package_name }}
|
||||||
@@ -1086,7 +1087,7 @@
|
|||||||
<view class="flex items-center gap-1.5">
|
<view class="flex items-center gap-1.5">
|
||||||
<i :class="[
|
<i :class="[
|
||||||
'text-14px',
|
'text-14px',
|
||||||
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[#ff6700]' : 'i-mdi-gas-station text-[#2e7d32]',
|
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
|
||||||
]" />
|
]" />
|
||||||
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
|
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
|
||||||
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
|
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
|
||||||
|
|||||||
@@ -1,99 +1,99 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { getAssetWallet } from '@/api/wallet'
|
import { getAssetWallet } from '@/api/wallet'
|
||||||
import type { AssetWalletInfo, AssetType } from '@/api/wallet'
|
import type { AssetWalletInfo, AssetType } from '@/api/wallet'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
// 资产类型和ID (从URL参数获取)
|
// 资产类型和ID (从URL参数获取)
|
||||||
const assetType = ref<AssetType>('card')
|
const assetType = ref<AssetType>('card')
|
||||||
const assetId = ref<number>(0)
|
const assetId = ref<number>(0)
|
||||||
|
|
||||||
// 钱包信息
|
// 钱包信息
|
||||||
const walletInfo = ref<AssetWalletInfo | null>(null)
|
const walletInfo = ref<AssetWalletInfo | null>(null)
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 格式化金额(分转元)
|
// 格式化金额(分转元)
|
||||||
function formatAmount(amount: number) {
|
function formatAmount(amount : number) {
|
||||||
return `¥${(amount / 100).toFixed(2)}`
|
return `¥${(amount / 100).toFixed(2)}`
|
||||||
}
|
|
||||||
|
|
||||||
// 获取钱包状态文本
|
|
||||||
function getStatusText(status: number, statusText?: string) {
|
|
||||||
if (statusText) return statusText
|
|
||||||
const map: Record<number, string> = {
|
|
||||||
1: '正常',
|
|
||||||
2: '冻结',
|
|
||||||
3: '已停用',
|
|
||||||
}
|
|
||||||
return map[status] || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取钱包状态颜色
|
|
||||||
function getStatusColor(status: number) {
|
|
||||||
const map: Record<number, { bg: string, text: string }> = {
|
|
||||||
1: { bg: '#e8f5e9', text: '#2e7d32' }, // 正常
|
|
||||||
2: { bg: '#fff3e0', text: '#e65100' }, // 冻结
|
|
||||||
3: { bg: '#f5f5f5', text: '#999' }, // 已停用
|
|
||||||
}
|
|
||||||
return map[status] || { bg: '#f5f5f5', text: '#999' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
function formatTime(time: string) {
|
|
||||||
if (!time) return '-'
|
|
||||||
const date = new Date(time)
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
const hour = String(date.getHours()).padStart(2, '0')
|
|
||||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hour}:${minute}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时获取参数
|
|
||||||
onLoad((options) => {
|
|
||||||
if (options.asset_type) {
|
|
||||||
assetType.value = options.asset_type as AssetType
|
|
||||||
}
|
|
||||||
if (options.asset_id) {
|
|
||||||
assetId.value = Number(options.asset_id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 页面挂载
|
|
||||||
onMounted(() => {
|
|
||||||
if (assetId.value) {
|
|
||||||
loadWalletInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载钱包信息
|
|
||||||
async function loadWalletInfo() {
|
|
||||||
if (!assetId.value) {
|
|
||||||
uni.$u.toast('缺少资产ID')
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
// 获取钱包状态文本
|
||||||
|
function getStatusText(status : number, statusText ?: string) {
|
||||||
|
if (statusText) return statusText
|
||||||
|
const map : Record<number, string> = {
|
||||||
|
1: '正常',
|
||||||
|
2: '冻结',
|
||||||
|
3: '已停用',
|
||||||
|
}
|
||||||
|
return map[status] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
// 获取钱包状态颜色
|
||||||
const wallet = await getAssetWallet(assetType.value, assetId.value)
|
function getStatusColor(status : number) {
|
||||||
walletInfo.value = wallet
|
const map : Record<number, { bg : string, text : string }> = {
|
||||||
|
1: { bg: '#e8f5e9', text: '#2e7d32' }, // 正常
|
||||||
|
2: { bg: '#fff3e0', text: '#e65100' }, // 冻结
|
||||||
|
3: { bg: '#f5f5f5', text: '#999' }, // 已停用
|
||||||
|
}
|
||||||
|
return map[status] || { bg: '#f5f5f5', text: '#999' }
|
||||||
}
|
}
|
||||||
catch (error) {
|
|
||||||
console.error('加载资产钱包信息失败:', error)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回上一页
|
// 格式化时间
|
||||||
function goBack() {
|
function formatTime(time : string) {
|
||||||
uni.navigateBack()
|
if (!time) return '-'
|
||||||
}
|
const date = new Date(time)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取参数
|
||||||
|
onLoad((options) => {
|
||||||
|
if (options.asset_type) {
|
||||||
|
assetType.value = options.asset_type as AssetType
|
||||||
|
}
|
||||||
|
if (options.asset_id) {
|
||||||
|
assetId.value = Number(options.asset_id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面挂载
|
||||||
|
onMounted(() => {
|
||||||
|
if (assetId.value) {
|
||||||
|
loadWalletInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载钱包信息
|
||||||
|
async function loadWalletInfo() {
|
||||||
|
if (!assetId.value) {
|
||||||
|
uni.$u.toast('缺少资产ID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const wallet = await getAssetWallet(assetType.value, assetId.value)
|
||||||
|
walletInfo.value = wallet
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('加载资产钱包信息失败:', error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -106,7 +106,8 @@ function goBack() {
|
|||||||
<!-- 钱包信息 -->
|
<!-- 钱包信息 -->
|
||||||
<view v-else-if="walletInfo" class="px-4 pt-4">
|
<view v-else-if="walletInfo" class="px-4 pt-4">
|
||||||
<!-- 余额卡片 -->
|
<!-- 余额卡片 -->
|
||||||
<view class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-16px shadow-[0_4px_16px_rgba(255,103,0,0.3)] p-4 mb-3">
|
<view
|
||||||
|
class="bg-gradient-to-r from-[var(--brand-primary)] to-[var(--brand-primary-light)] rounded-16px shadow-brand-colored p-4 mb-3">
|
||||||
<view class="flex items-center justify-between mb-3">
|
<view class="flex items-center justify-between mb-3">
|
||||||
<view>
|
<view>
|
||||||
<text class="text-13px text-white/70 block mb-1">
|
<text class="text-13px text-white/70 block mb-1">
|
||||||
@@ -116,13 +117,10 @@ function goBack() {
|
|||||||
{{ formatAmount(walletInfo.available_balance) }}
|
{{ formatAmount(walletInfo.available_balance) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view :style="{
|
||||||
:style="{
|
|
||||||
backgroundColor: getStatusColor(walletInfo.status).bg,
|
backgroundColor: getStatusColor(walletInfo.status).bg,
|
||||||
color: getStatusColor(walletInfo.status).text,
|
color: getStatusColor(walletInfo.status).text,
|
||||||
}"
|
}" class="px-3 py-1 rounded-8px text-12px font-600">
|
||||||
class="px-3 py-1 rounded-8px text-12px font-600"
|
|
||||||
>
|
|
||||||
{{ getStatusText(walletInfo.status, walletInfo.status_text) }}
|
{{ getStatusText(walletInfo.status, walletInfo.status_text) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -146,7 +144,7 @@ function goBack() {
|
|||||||
<!-- 钱包详情 -->
|
<!-- 钱包详情 -->
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||||
<view class="flex items-center gap-2 mb-3">
|
<view class="flex items-center gap-2 mb-3">
|
||||||
<i class="i-mdi-information-outline text-20px text-[#ff6700]" />
|
<i class="i-mdi-information-outline text-20px text-[var(--brand-primary)]" />
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
<text class="text-15px font-600 text-[#212121]">
|
||||||
钱包详情
|
钱包详情
|
||||||
</text>
|
</text>
|
||||||
@@ -227,26 +225,27 @@ function goBack() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.center {
|
.center {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
.space-y-2 > view:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-spin {
|
.space-y-2>view:not(:last-child) {
|
||||||
animation: spin 1s linear infinite;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import type { UserInfo } from '@/api/auth'
|
|||||||
import { getEnterpriseCards, getEnterpriseDevices } from '@/api/enterprise'
|
import { getEnterpriseCards, getEnterpriseDevices } from '@/api/enterprise'
|
||||||
import { getCarriers } from '@/api/carrier'
|
import { getCarriers } from '@/api/carrier'
|
||||||
import type { CarrierInfo } from '@/api/carrier'
|
import type { CarrierInfo } from '@/api/carrier'
|
||||||
import { stopDeviceAsset, startDeviceAsset, stopCardByIccid, startCardByIccid } from '@/api/assets'
|
import { stopAsset, startAsset } from '@/api/assets'
|
||||||
import type { DeviceStopResponse } from '@/api/assets'
|
|
||||||
import Skeleton from '@/components/Skeleton.vue'
|
import Skeleton from '@/components/Skeleton.vue'
|
||||||
import SimplePicker from '@/components/SimplePicker.vue'
|
import SimplePicker from '@/components/SimplePicker.vue'
|
||||||
import { formatTime, formatDataSize } from '@/utils/format'
|
import { formatTime, formatDataSize } from '@/utils/format'
|
||||||
@@ -404,10 +403,9 @@ async function handleStopDevice(asset: AssetItem, event: Event) {
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const device = asset.raw as DeviceInfo
|
const device = asset.raw as DeviceInfo
|
||||||
const deviceId = device.id || device.device_id
|
|
||||||
|
|
||||||
if (!deviceId) {
|
if (!device.virtual_no) {
|
||||||
uni.$u.toast('设备ID不存在')
|
uni.$u.toast('设备虚拟号不存在')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,14 +416,13 @@ async function handleStopDevice(asset: AssetItem, event: Event) {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showLoading({ title: '停机中...' })
|
uni.showLoading({ title: '停机中...' })
|
||||||
try {
|
try {
|
||||||
const result = await stopDeviceAsset(Number(deviceId))
|
const result = await stopAsset(device.virtual_no)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 显示停机结果
|
// 显示停机结果 (统一接口不返回详情,仅提示成功)
|
||||||
const msg = `停机完成\n成功: ${result.success_count}张\n失败: ${result.fail_count}张\n跳过: ${result.skip_count}张`
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '停机结果',
|
title: '停机成功',
|
||||||
content: msg,
|
content: '设备已停机',
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
success: () => {
|
success: () => {
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
@@ -447,10 +444,9 @@ async function handleStartDevice(asset: AssetItem, event: Event) {
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const device = asset.raw as DeviceInfo
|
const device = asset.raw as DeviceInfo
|
||||||
const deviceId = device.id || device.device_id
|
|
||||||
|
|
||||||
if (!deviceId) {
|
if (!device.virtual_no) {
|
||||||
uni.$u.toast('设备ID不存在')
|
uni.$u.toast('设备虚拟号不存在')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +457,7 @@ async function handleStartDevice(asset: AssetItem, event: Event) {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showLoading({ title: '复机中...' })
|
uni.showLoading({ title: '复机中...' })
|
||||||
try {
|
try {
|
||||||
await startDeviceAsset(Number(deviceId))
|
await startAsset(device.virtual_no)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.$u.toast('复机成功')
|
uni.$u.toast('复机成功')
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
@@ -494,7 +490,7 @@ async function handleStopCard(asset: AssetItem, event: Event) {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showLoading({ title: '停机中...' })
|
uni.showLoading({ title: '停机中...' })
|
||||||
try {
|
try {
|
||||||
await stopCardByIccid(card.iccid)
|
await stopAsset(card.iccid)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.$u.toast('停机成功')
|
uni.$u.toast('停机成功')
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
@@ -527,7 +523,7 @@ async function handleStartCard(asset: AssetItem, event: Event) {
|
|||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showLoading({ title: '复机中...' })
|
uni.showLoading({ title: '复机中...' })
|
||||||
try {
|
try {
|
||||||
await startCardByIccid(card.iccid)
|
await startAsset(card.iccid)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.$u.toast('复机成功')
|
uni.$u.toast('复机成功')
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
@@ -682,135 +678,124 @@ onMounted(async () => {
|
|||||||
@scroll="onScroll"
|
@scroll="onScroll"
|
||||||
>
|
>
|
||||||
<view v-if="assetList.length > 0" class="px-4 pb-4">
|
<view v-if="assetList.length > 0" class="px-4 pb-4">
|
||||||
<view
|
<view
|
||||||
v-for="asset in assetList"
|
v-for="asset in assetList"
|
||||||
:key="`${asset.type}-${asset.id}`"
|
:key="`${asset.type}-${asset.id}`"
|
||||||
class="card shadow-sm mb-3 cursor-pointer transition-fast hover:shadow-md active:scale-98"
|
class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3 cursor-pointer"
|
||||||
@click="viewAssetDetail(asset)"
|
@click="viewAssetDetail(asset)"
|
||||||
>
|
>
|
||||||
<!-- 头部区域 -->
|
<!-- 顶部区域:左侧状态+名称,右侧类型标签 -->
|
||||||
<view class="flex items-start justify-between mb-3">
|
<view class="flex items-start justify-between mb-3">
|
||||||
<!-- 左侧信息 -->
|
<view class="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<view class="flex items-start gap-2 flex-1 min-w-0">
|
<view
|
||||||
<!-- 状态指示点 -->
|
class="w-2 h-2 rounded-full mt-1 flex-shrink-0"
|
||||||
<view
|
:style="{ backgroundColor: asset.statusColor }"
|
||||||
class="w-2 h-2 rounded-full mt-1 flex-shrink-0"
|
/>
|
||||||
:style="{ backgroundColor: asset.statusColor }"
|
<text class="text-15px font-600 text-[#212121] truncate">
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 资产名称 -->
|
|
||||||
<view class="flex-1 min-w-0">
|
|
||||||
<text class="text-base font-600 text-primary block truncate">
|
|
||||||
{{ asset.name }}
|
{{ asset.name }}
|
||||||
</text>
|
</text>
|
||||||
<text class="text-xs text-tertiary mt-1 block">
|
</view>
|
||||||
{{ asset.detail }}
|
<view
|
||||||
</text>
|
class="px-2 py-1 rounded-6px text-11px font-500 flex-shrink-0 ml-2"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: asset.type === 'card' ? '#e6f4ff' : '#f5f5f5',
|
||||||
|
color: asset.type === 'card' ? 'var(--brand-primary)' : '#666',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ asset.type === 'card' ? 'IoT卡' : '设备' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 类型徽章 -->
|
<!-- 详情信息 -->
|
||||||
<view
|
<view class="mb-3">
|
||||||
class="px-2 py-1 radius-sm text-xs font-500 ml-2 flex-shrink-0"
|
<text class="text-12px text-[#999]">
|
||||||
:style="{
|
{{ asset.detail }}
|
||||||
backgroundColor: asset.type === 'card' ? 'rgba(255, 103, 0, 0.1)' : 'rgba(51, 51, 51, 0.1)',
|
|
||||||
color: asset.type === 'card' ? 'var(--brand-primary)' : 'var(--brand-secondary)',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ asset.type === 'card' ? 'IoT卡' : '设备' }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部状态栏 -->
|
|
||||||
<view class="flex items-center justify-between pt-3 border-t border-border-secondary">
|
|
||||||
<!-- 状态标签 -->
|
|
||||||
<view class="flex items-center gap-1">
|
|
||||||
<view
|
|
||||||
class="w-1.5 h-1.5 rounded-full"
|
|
||||||
:style="{ backgroundColor: asset.statusColor }"
|
|
||||||
/>
|
|
||||||
<text class="text-sm font-500" :style="{ color: asset.statusColor }">
|
|
||||||
{{ asset.status }}
|
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 底部区域:状态标签 + 操作按钮 -->
|
||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center justify-between pt-3 border-t border-[#f5f5f5]">
|
||||||
<!-- IoT卡操作按钮 -->
|
<view class="flex items-center gap-1">
|
||||||
<template v-if="asset.type === 'card'">
|
<view
|
||||||
<!-- 根据网络状态显示对应按钮 -->
|
class="w-1.5 h-1.5 rounded-full"
|
||||||
<template v-if="(asset.raw as CardInfo).network_status === 0">
|
:style="{ backgroundColor: asset.statusColor }"
|
||||||
<!-- 已停机,只显示复机按钮 -->
|
/>
|
||||||
<view
|
<text class="text-13px font-500" :style="{ color: asset.statusColor }">
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
{{ asset.status }}
|
||||||
style="background: #52c41a; color: white;"
|
</text>
|
||||||
@click="handleStartCard(asset, $event)"
|
</view>
|
||||||
>
|
|
||||||
<text class="text-xs font-500">复机</text>
|
<view class="flex items-center gap-2">
|
||||||
</view>
|
<!-- IoT卡操作按钮 -->
|
||||||
</template>
|
<template v-if="asset.type === 'card'">
|
||||||
<template v-else-if="(asset.raw as CardInfo).network_status === 1">
|
<template v-if="(asset.raw as CardInfo).network_status === 0">
|
||||||
<!-- 正常运行,只显示停机按钮 -->
|
<view
|
||||||
<view
|
class="px-3 py-1 rounded-lg"
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
style="background: #52c41a; color: white;"
|
||||||
style="background: #ff4d4f; color: white;"
|
@click="handleStartCard(asset, $event)"
|
||||||
@click="handleStopCard(asset, $event)"
|
>
|
||||||
>
|
<text class="text-12px font-500">复机</text>
|
||||||
<text class="text-xs font-500">停机</text>
|
</view>
|
||||||
</view>
|
</template>
|
||||||
|
<template v-else-if="(asset.raw as CardInfo).network_status === 1">
|
||||||
|
<view
|
||||||
|
class="px-3 py-1 rounded-lg"
|
||||||
|
style="background: #ff4d4f; color: white;"
|
||||||
|
@click="handleStopCard(asset, $event)"
|
||||||
|
>
|
||||||
|
<text class="text-12px font-500">停机</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view
|
||||||
|
class="px-3 py-1 rounded-lg"
|
||||||
|
style="background: #ff4d4f; color: white;"
|
||||||
|
@click="handleStopCard(asset, $event)"
|
||||||
|
>
|
||||||
|
<text class="text-12px font-500">停机</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="px-3 py-1 rounded-lg"
|
||||||
|
style="background: #52c41a; color: white;"
|
||||||
|
@click="handleStartCard(asset, $event)"
|
||||||
|
>
|
||||||
|
<text class="text-12px font-500">复机</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 设备操作按钮 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- 其他状态,显示两个按钮 -->
|
|
||||||
<view
|
<view
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
class="px-3 py-1 rounded-lg"
|
||||||
style="background: #ff4d4f; color: white;"
|
style="background: #ff4d4f; color: white;"
|
||||||
@click="handleStopCard(asset, $event)"
|
@click="handleStopDevice(asset, $event)"
|
||||||
>
|
>
|
||||||
<text class="text-xs font-500">停机</text>
|
<text class="text-12px font-500">停机</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
class="px-3 py-1 rounded-lg"
|
||||||
style="background: #52c41a; color: white;"
|
style="background: #52c41a; color: white;"
|
||||||
@click="handleStartCard(asset, $event)"
|
@click="handleStartDevice(asset, $event)"
|
||||||
>
|
>
|
||||||
<text class="text-xs font-500">复机</text>
|
<text class="text-12px font-500">复机</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 设备操作按钮 - 始终显示两个按钮 -->
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
<template v-else>
|
</view>
|
||||||
<view
|
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
|
||||||
style="background: #ff4d4f; color: white;"
|
|
||||||
@click="handleStopDevice(asset, $event)"
|
|
||||||
>
|
|
||||||
<text class="text-xs font-500">停机</text>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
|
||||||
style="background: #52c41a; color: white;"
|
|
||||||
@click="handleStartDevice(asset, $event)"
|
|
||||||
>
|
|
||||||
<text class="text-xs font-500">复机</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 箭头 -->
|
|
||||||
<i class="i-mdi-chevron-right text-xl text-quaternary" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 加载更多提示 -->
|
<!-- 加载更多提示 -->
|
||||||
<view v-if="loading" class="py-4 text-center">
|
<view v-if="loading" class="py-4 text-center">
|
||||||
<i class="i-mdi-loading animate-spin text-xl text-brand mr-2" />
|
<i class="i-mdi-loading animate-spin text-xl text-brand mr-2" />
|
||||||
<text class="text-sm text-[#64748b]">加载中...</text>
|
<text class="text-sm text-[#64748b]">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="!((activeTab === 'card' && cardHasMore) || (activeTab === 'device' && deviceHasMore))" class="py-4 text-center">
|
||||||
|
<text class="text-sm text-[#94a3b8]">没有更多了</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="!((activeTab === 'card' && cardHasMore) || (activeTab === 'device' && deviceHasMore))" class="py-4 text-center">
|
|
||||||
<text class="text-sm text-[#94a3b8]">没有更多了</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<view v-else class="px-4">
|
<view v-else class="px-4">
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const isFormValid = computed(() => {
|
|||||||
const { old_password, new_password, confirm_password } = formData.value
|
const { old_password, new_password, confirm_password } = formData.value
|
||||||
return (
|
return (
|
||||||
old_password.length > 0
|
old_password.length > 0
|
||||||
&& new_password.length >= 6
|
&& new_password.length >= 8
|
||||||
&& new_password === confirm_password
|
&& new_password === confirm_password
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -74,9 +74,9 @@ async function submitChange() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.value.new_password.length < 6) {
|
if (formData.value.new_password.length < 8) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '新密码至少6位',
|
title: '新密码至少8位',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -149,256 +149,126 @@ function clearForm() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||||
<!-- 内容区域 -->
|
|
||||||
<view class="px-4 pt-5">
|
<view class="px-4 pt-5">
|
||||||
<!-- 提示信息 -->
|
<view class="bg-white rounded-12px p-4 mb-3">
|
||||||
<view class="bg-[#e3f2fd] rounded-12px p-4 mb-4 flex items-start gap-3">
|
<text class="text-12px text-[#999] block mb-2">当前密码</text>
|
||||||
<i class="i-mdi-information text-20px text-[#1976d2] mt-0.5" />
|
<view class="flex items-center">
|
||||||
<view class="flex-1">
|
<input
|
||||||
<text class="text-13px text-[#1976d2] block mb-1 font-600">
|
v-model="formData.old_password"
|
||||||
密码安全提示
|
:password="!passwordVisible.old"
|
||||||
</text>
|
class="flex-1 text-16px text-[#212121]"
|
||||||
<text class="text-12px text-[#1976d2] leading-relaxed">
|
placeholder="请输入当前密码"
|
||||||
为了您的账号安全,建议密码长度至少8位,包含大小写字母、数字和特殊字符。
|
placeholder-class="text-[#ccc]"
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 表单卡片 -->
|
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-4">
|
|
||||||
<!-- 当前密码 -->
|
|
||||||
<view class="mb-4">
|
|
||||||
<text class="text-13px text-[#999] block mb-2">
|
|
||||||
当前密码 <text class="text-[#ff6b6b]">*</text>
|
|
||||||
</text>
|
|
||||||
<view class="flex items-center gap-2 px-4 py-3 rounded-12px bg-[#f5f5f5]">
|
|
||||||
<i class="i-mdi-lock text-20px text-[#999]" />
|
|
||||||
<input
|
|
||||||
v-model="formData.old_password"
|
|
||||||
:password="!passwordVisible.old"
|
|
||||||
class="flex-1 text-15px text-[#212121] placeholder:text-[#ccc]"
|
|
||||||
placeholder="请输入当前密码"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-20px text-[#999]',
|
|
||||||
passwordVisible.old ? 'i-mdi-eye-off' : 'i-mdi-eye',
|
|
||||||
]"
|
|
||||||
@click="togglePasswordVisible('old')"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 新密码 -->
|
|
||||||
<view class="mb-4">
|
|
||||||
<view class="flex items-center justify-between mb-2">
|
|
||||||
<text class="text-13px text-[#999]">
|
|
||||||
新密码 <text class="text-[#ff6b6b]">*</text>
|
|
||||||
</text>
|
|
||||||
<view
|
|
||||||
v-if="formData.new_password"
|
|
||||||
class="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<text class="text-11px text-[#999]">
|
|
||||||
强度:
|
|
||||||
</text>
|
|
||||||
<text
|
|
||||||
:style="{ color: passwordStrength.color }"
|
|
||||||
class="text-11px font-600"
|
|
||||||
>
|
|
||||||
{{ passwordStrength.text }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="flex items-center gap-2 px-4 py-3 rounded-12px bg-[#f5f5f5]">
|
|
||||||
<i class="i-mdi-lock-outline text-20px text-[#999]" />
|
|
||||||
<input
|
|
||||||
v-model="formData.new_password"
|
|
||||||
:password="!passwordVisible.new"
|
|
||||||
class="flex-1 text-15px text-[#212121] placeholder:text-[#ccc]"
|
|
||||||
placeholder="请输入新密码(至少6位)"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-20px text-[#999]',
|
|
||||||
passwordVisible.new ? 'i-mdi-eye-off' : 'i-mdi-eye',
|
|
||||||
]"
|
|
||||||
@click="togglePasswordVisible('new')"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 密码强度指示器 -->
|
|
||||||
<view
|
|
||||||
v-if="formData.new_password"
|
|
||||||
class="flex items-center gap-1 mt-2"
|
|
||||||
>
|
>
|
||||||
<view
|
<image
|
||||||
v-for="i in 3"
|
:src="passwordVisible.old ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
|
||||||
:key="i"
|
class="w-20px h-20px"
|
||||||
:class="[
|
mode="aspectFit"
|
||||||
'flex-1 h-2px rounded-full',
|
@click="togglePasswordVisible('old')"
|
||||||
i <= passwordStrength.level ? 'opacity-100' : 'opacity-20',
|
|
||||||
]"
|
|
||||||
:style="{ backgroundColor: passwordStrength.color }"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 确认密码 -->
|
|
||||||
<view>
|
|
||||||
<text class="text-13px text-[#999] block mb-2">
|
|
||||||
确认密码 <text class="text-[#ff6b6b]">*</text>
|
|
||||||
</text>
|
|
||||||
<view class="flex items-center gap-2 px-4 py-3 rounded-12px bg-[#f5f5f5]">
|
|
||||||
<i class="i-mdi-lock-check text-20px text-[#999]" />
|
|
||||||
<input
|
|
||||||
v-model="formData.confirm_password"
|
|
||||||
:password="!passwordVisible.confirm"
|
|
||||||
class="flex-1 text-15px text-[#212121] placeholder:text-[#ccc]"
|
|
||||||
placeholder="请再次输入新密码"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-20px text-[#999]',
|
|
||||||
passwordVisible.confirm ? 'i-mdi-eye-off' : 'i-mdi-eye',
|
|
||||||
]"
|
|
||||||
@click="togglePasswordVisible('confirm')"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 密码匹配提示 -->
|
|
||||||
<view
|
|
||||||
v-if="formData.confirm_password"
|
|
||||||
class="flex items-center gap-1 mt-2"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-14px',
|
|
||||||
formData.new_password === formData.confirm_password
|
|
||||||
? 'i-mdi-check-circle text-[#3ed268]'
|
|
||||||
: 'i-mdi-close-circle text-[#ff6b6b]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<text
|
|
||||||
:class="[
|
|
||||||
'text-12px',
|
|
||||||
formData.new_password === formData.confirm_password
|
|
||||||
? 'text-[#3ed268]'
|
|
||||||
: 'text-[#ff6b6b]',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ formData.new_password === formData.confirm_password ? '密码一致' : '密码不一致' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 密码要求说明 -->
|
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-4">
|
|
||||||
<text class="text-14px font-600 text-[#212121] block mb-3">
|
|
||||||
密码要求
|
|
||||||
</text>
|
|
||||||
|
|
||||||
<view class="space-y-2">
|
|
||||||
<view class="flex items-start gap-2">
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-14px mt-0.5',
|
|
||||||
formData.new_password.length >= 6
|
|
||||||
? 'i-mdi-check-circle text-[#3ed268]'
|
|
||||||
: 'i-mdi-checkbox-blank-circle-outline text-[#ccc]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<text class="flex-1 text-13px text-[#666]">
|
|
||||||
至少6个字符(建议8个以上)
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-start gap-2">
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-14px mt-0.5',
|
|
||||||
/[a-zA-Z]/.test(formData.new_password)
|
|
||||||
? 'i-mdi-check-circle text-[#3ed268]'
|
|
||||||
: 'i-mdi-checkbox-blank-circle-outline text-[#ccc]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<text class="flex-1 text-13px text-[#666]">
|
|
||||||
包含字母
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-start gap-2">
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-14px mt-0.5',
|
|
||||||
/\d/.test(formData.new_password)
|
|
||||||
? 'i-mdi-check-circle text-[#3ed268]'
|
|
||||||
: 'i-mdi-checkbox-blank-circle-outline text-[#ccc]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<text class="flex-1 text-13px text-[#666]">
|
|
||||||
包含数字
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-start gap-2">
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
'text-14px mt-0.5',
|
|
||||||
/[^a-zA-Z\d]/.test(formData.new_password)
|
|
||||||
? 'i-mdi-check-circle text-[#3ed268]'
|
|
||||||
: 'i-mdi-checkbox-blank-circle-outline text-[#ccc]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<text class="flex-1 text-13px text-[#666]">
|
|
||||||
包含特殊字符(推荐)
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<view class="space-y-3">
|
|
||||||
<!-- 确认修改 -->
|
|
||||||
<view
|
|
||||||
:class="[
|
|
||||||
'py-3 rounded-12px center',
|
|
||||||
'transition-all duration-200',
|
|
||||||
isFormValid && !submitting
|
|
||||||
? 'bg-[#212121] active:bg-[#000]'
|
|
||||||
: 'bg-[#ccc]',
|
|
||||||
]"
|
|
||||||
@click="submitChange"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
v-if="submitting"
|
|
||||||
class="i-mdi-loading animate-spin text-18px text-white mr-2"
|
|
||||||
/>
|
/>
|
||||||
<text class="text-15px font-600 text-white">
|
</view>
|
||||||
{{ submitting ? '修改中...' : '确认修改' }}
|
</view>
|
||||||
|
|
||||||
|
<view class="bg-white rounded-12px p-4 mb-3">
|
||||||
|
<view class="flex items-center justify-between mb-2">
|
||||||
|
<text class="text-12px text-[#999]">新密码</text>
|
||||||
|
<text
|
||||||
|
v-if="formData.new_password"
|
||||||
|
:style="{ color: passwordStrength.color }"
|
||||||
|
class="text-12px font-600"
|
||||||
|
>
|
||||||
|
{{ passwordStrength.text }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<input
|
||||||
|
v-model="formData.new_password"
|
||||||
|
:password="!passwordVisible.new"
|
||||||
|
class="flex-1 text-16px text-[#212121]"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
placeholder-class="text-[#ccc]"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="passwordVisible.new ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
|
||||||
|
class="w-20px h-20px"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click="togglePasswordVisible('new')"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view v-if="formData.new_password" class="flex items-center gap-1 mt-2">
|
||||||
|
<view
|
||||||
|
v-for="i in 3"
|
||||||
|
:key="i"
|
||||||
|
class="flex-1 h-2px rounded-full"
|
||||||
|
:style="{ backgroundColor: i <= passwordStrength.level ? passwordStrength.color : '#e0e0e0' }"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 清空表单 -->
|
<view class="bg-white rounded-12px p-4 mb-3">
|
||||||
<view
|
<text class="text-12px text-[#999] block mb-2">确认密码</text>
|
||||||
class="
|
<view class="flex items-center">
|
||||||
py-3 rounded-12px center
|
<input
|
||||||
bg-white shadow-[0_2px_12px_rgba(0,0,0,0.06)]
|
v-model="formData.confirm_password"
|
||||||
transition-all duration-200
|
:password="!passwordVisible.confirm"
|
||||||
active:bg-[#f5f5f5]
|
class="flex-1 text-16px text-[#212121]"
|
||||||
"
|
placeholder="请再次输入新密码"
|
||||||
@click="clearForm"
|
placeholder-class="text-[#ccc]"
|
||||||
>
|
>
|
||||||
<i class="i-mdi-refresh text-18px text-[#666] mr-2" />
|
<image
|
||||||
<text class="text-15px font-600 text-[#666]">
|
:src="passwordVisible.confirm ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
|
||||||
清空
|
class="w-20px h-20px"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click="togglePasswordVisible('confirm')"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view v-if="formData.confirm_password" class="mt-2">
|
||||||
|
<text
|
||||||
|
:class="[
|
||||||
|
'text-12px',
|
||||||
|
formData.new_password === formData.confirm_password ? 'text-[#52c41a]' : 'text-[#ff4d4f]',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ formData.new_password === formData.confirm_password ? '密码一致' : '密码不一致' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部留白 -->
|
<view
|
||||||
|
:class="[
|
||||||
|
'py-4 rounded-12px text-center text-16px font-600',
|
||||||
|
isFormValid && !submitting
|
||||||
|
? 'bg-brand text-white'
|
||||||
|
: 'bg-[#f5f5f5] text-[#ccc]',
|
||||||
|
]"
|
||||||
|
@click="submitChange"
|
||||||
|
>
|
||||||
|
{{ submitting ? '修改中...' : '确认修改' }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="bg-white rounded-12px p-4 mt-4">
|
||||||
|
<text class="text-13px font-600 text-[#212121] block mb-3">密码要求</text>
|
||||||
|
<view class="space-y-2">
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text :class="formData.new_password.length >= 8 ? 'text-[#52c41a]' : 'text-[#ccc]'">●</text>
|
||||||
|
<text class="text-13px text-[#666]">至少8个字符</text>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text :class="/[a-zA-Z]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'">●</text>
|
||||||
|
<text class="text-13px text-[#666]">包含字母</text>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text :class="/\d/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'">●</text>
|
||||||
|
<text class="text-13px text-[#666]">包含数字</text>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text :class="/[^a-zA-Z\d]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'">●</text>
|
||||||
|
<text class="text-13px text-[#666]">包含特殊字符</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="h-8" />
|
<view class="h-8" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -423,6 +293,10 @@ function clearForm() {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-y-2 > view:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { getCommissionSummary, getCommissionRecords, getCommissionStats } from '@/api/commission'
|
import { getFundSummary, getCommissionRecords, getCommissionStats } from '@/api/commission'
|
||||||
import type { CommissionSummary, CommissionRecord, CommissionStats } from '@/api/commission'
|
import type { FundSummary, CommissionRecord, CommissionStats } from '@/api/commission'
|
||||||
import SimplePicker from '@/components/SimplePicker.vue'
|
import SimplePicker from '@/components/SimplePicker.vue'
|
||||||
|
|
||||||
// 佣金概览
|
// 佣金概览
|
||||||
const commissionSummary = ref<CommissionSummary | null>(null)
|
const commissionSummary = ref<FundSummary | null>(null)
|
||||||
|
|
||||||
|
// 店铺ID (从登录信息获取)
|
||||||
|
const shopId = ref<number>(0)
|
||||||
|
|
||||||
|
// 从本地存储获取用户信息
|
||||||
|
function getUserInfo() {
|
||||||
|
const userInfo = uni.getStorageSync('user_info')
|
||||||
|
return userInfo || null
|
||||||
|
}
|
||||||
|
|
||||||
// 佣金明细列表
|
// 佣金明细列表
|
||||||
const commissionRecords = ref<CommissionRecord[]>([])
|
const commissionRecords = ref<CommissionRecord[]>([])
|
||||||
@@ -92,6 +101,12 @@ function formatTime(time: string) {
|
|||||||
|
|
||||||
// 页面加载
|
// 页面加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 从登录信息获取 shop_id
|
||||||
|
const userInfo = getUserInfo()
|
||||||
|
if (userInfo?.shop_id) {
|
||||||
|
shopId.value = userInfo.shop_id
|
||||||
|
}
|
||||||
|
|
||||||
loadCommissionSummary()
|
loadCommissionSummary()
|
||||||
loadCommissionStats()
|
loadCommissionStats()
|
||||||
loadCommissionRecords()
|
loadCommissionRecords()
|
||||||
@@ -100,7 +115,7 @@ onMounted(() => {
|
|||||||
// 加载佣金概览
|
// 加载佣金概览
|
||||||
async function loadCommissionSummary() {
|
async function loadCommissionSummary() {
|
||||||
try {
|
try {
|
||||||
const summary = await getCommissionSummary()
|
const summary = await getFundSummary()
|
||||||
commissionSummary.value = summary
|
commissionSummary.value = summary
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -110,8 +125,10 @@ async function loadCommissionSummary() {
|
|||||||
|
|
||||||
// 加载佣金统计
|
// 加载佣金统计
|
||||||
async function loadCommissionStats() {
|
async function loadCommissionStats() {
|
||||||
|
if (!shopId.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stats = await getCommissionStats()
|
const stats = await getCommissionStats(shopId.value)
|
||||||
commissionStats.value = stats
|
commissionStats.value = stats
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -121,6 +138,8 @@ async function loadCommissionStats() {
|
|||||||
|
|
||||||
// 加载佣金明细
|
// 加载佣金明细
|
||||||
async function loadCommissionRecords() {
|
async function loadCommissionRecords() {
|
||||||
|
if (!shopId.value) return
|
||||||
|
|
||||||
loadingRecords.value = true
|
loadingRecords.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -152,7 +171,7 @@ async function loadCommissionRecords() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getCommissionRecords(params)
|
const response = await getCommissionRecords(shopId.value, params)
|
||||||
|
|
||||||
if (page.value === 1) {
|
if (page.value === 1) {
|
||||||
commissionRecords.value = response.items || []
|
commissionRecords.value = response.items || []
|
||||||
@@ -214,217 +233,155 @@ function refreshList() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||||
<!-- 加载中 -->
|
|
||||||
<view v-if="loading" class="pt-20 center">
|
<view v-if="loading" class="pt-20 center">
|
||||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<view v-else>
|
||||||
<view v-else class="px-4 pt-4">
|
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||||
<!-- 佣金概览卡片 -->
|
<text class="text-11px text-white/60">可提现佣金</text>
|
||||||
<view v-if="commissionSummary" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<text class="text-24px font-700 text-white block mt-1">
|
||||||
<view class="flex items-center justify-between mb-3">
|
{{ commissionSummary ? formatAmount(commissionSummary.available_commission) : '--' }}
|
||||||
<view class="flex items-center gap-2">
|
</text>
|
||||||
<i class="i-mdi-currency-cny text-24px text-[#ff6700]" />
|
</view>
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
|
||||||
佣金概览
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="grid grid-cols-2 gap-3">
|
<view class="bg-[var(--bg-container)] rounded-12px p-4 mx-4 mt-3">
|
||||||
<view class="text-center p-3 bg-[#f5f5f5] rounded-12px">
|
<view class="grid grid-cols-3 gap-4">
|
||||||
<text class="text-11px text-[#999] block mb-1">可提现</text>
|
<view class="text-center">
|
||||||
<text class="text-18px font-700 text-[#ff6700]">
|
<text class="text-11px text-[var(--text-tertiary)] block">累计佣金</text>
|
||||||
{{ formatAmount(commissionSummary.available_commission) }}
|
<text class="text-16px font-700 text-[var(--text-primary)] mt-1">
|
||||||
|
{{ commissionSummary ? formatAmount(commissionSummary.total_commission) : '--' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center p-3 bg-[#f5f5f5] rounded-12px">
|
<view class="text-center">
|
||||||
<text class="text-11px text-[#999] block mb-1">累计佣金</text>
|
<text class="text-11px text-[var(--text-tertiary)] block">冻结</text>
|
||||||
<text class="text-18px font-700 text-[#212121]">
|
<text class="text-16px font-700 text-[var(--text-tertiary)] mt-1">
|
||||||
{{ formatAmount(commissionSummary.total_commission) }}
|
{{ commissionSummary ? formatAmount(commissionSummary.frozen_commission) : '--' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center p-3 bg-[#f5f5f5] rounded-12px">
|
<view class="text-center">
|
||||||
<text class="text-11px text-[#999] block mb-1">冻结</text>
|
<text class="text-11px text-[var(--text-tertiary)] block">已提现</text>
|
||||||
<text class="text-18px font-700 text-[#999]">
|
<text class="text-16px font-700 text-[#52c41a] mt-1">
|
||||||
{{ formatAmount(commissionSummary.frozen_commission) }}
|
{{ commissionSummary ? formatAmount(commissionSummary.withdrawn_commission) : '--' }}
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="text-center p-3 bg-[#f5f5f5] rounded-12px">
|
|
||||||
<text class="text-11px text-[#999] block mb-1">已提现</text>
|
|
||||||
<text class="text-18px font-700 text-[#52c41a]">
|
|
||||||
{{ formatAmount(commissionSummary.withdrawn_commission) }}
|
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 佣金统计卡片 -->
|
<view class="px-4 mt-4">
|
||||||
<view v-if="commissionStats" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<text class="text-14px font-600 text-[var(--text-primary)] block mb-2">佣金统计</text>
|
||||||
|
<view class="bg-[var(--bg-container)] rounded-12px p-4">
|
||||||
|
<view class="flex items-center justify-between">
|
||||||
|
<text class="text-13px text-[var(--text-secondary)]">成本价差</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||||||
|
{{ commissionStats ? formatAmount(commissionStats.cost_diff_amount) : '--' }}
|
||||||
|
</text>
|
||||||
|
<text class="text-11px text-[var(--text-tertiary)]">
|
||||||
|
{{ commissionStats ? (commissionStats.cost_diff_percent / 10).toFixed(1) + '%' : '--' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center justify-between mt-3">
|
||||||
|
<text class="text-13px text-[var(--text-secondary)]">一次性佣金</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||||||
|
{{ commissionStats ? formatAmount(commissionStats.one_time_amount) : '--' }}
|
||||||
|
</text>
|
||||||
|
<text class="text-11px text-[var(--text-tertiary)]">
|
||||||
|
{{ commissionStats ? (commissionStats.one_time_percent / 10).toFixed(1) + '%' : '--' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center justify-between mt-3 pt-3 border-t border-[var(--border-secondary)]">
|
||||||
|
<text class="text-13px font-600 text-[var(--text-primary)]">总计</text>
|
||||||
|
<text class="text-16px font-700 text-brand">
|
||||||
|
{{ commissionStats ? formatAmount(commissionStats.total_amount) : '--' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="px-4 mt-4">
|
||||||
|
<text class="text-14px font-600 text-[var(--text-primary)] block mb-2">佣金明细</text>
|
||||||
|
|
||||||
<view class="flex items-center gap-2 mb-3">
|
<view class="flex items-center gap-2 mb-3">
|
||||||
<i class="i-mdi-chart-pie text-20px text-[#ff6700]" />
|
<input
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
v-model="searchKeyword"
|
||||||
佣金统计
|
class="flex-1 h-40px bg-[var(--bg-container)] rounded-8px px-3 text-14px text-[var(--text-primary)]"
|
||||||
</text>
|
placeholder="搜索 ICCID / 虚拟号 / 订单号"
|
||||||
</view>
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
|
@confirm="handleSearch"
|
||||||
<view class="space-y-2">
|
>
|
||||||
<view class="flex items-center justify-between">
|
<view
|
||||||
<text class="text-13px text-[#666]">成本价差</text>
|
class="px-4 h-40px bg-brand text-white text-14px rounded-8px flex items-center justify-center"
|
||||||
<view class="flex items-center gap-2">
|
@click="handleSearch"
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
>
|
||||||
{{ formatAmount(commissionStats.cost_diff_amount) }}
|
搜索
|
||||||
</text>
|
|
||||||
<text class="text-11px text-[#999]">
|
|
||||||
{{ (commissionStats.cost_diff_percent / 10).toFixed(1) }}%
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<text class="text-13px text-[#666]">一次性佣金</text>
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
|
||||||
{{ formatAmount(commissionStats.one_time_amount) }}
|
|
||||||
</text>
|
|
||||||
<text class="text-11px text-[#999]">
|
|
||||||
{{ (commissionStats.one_time_percent / 10).toFixed(1) }}%
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="pt-2 border-t-1px border-[#f5f5f5]">
|
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<text class="text-13px font-600 text-[#212121]">总计</text>
|
|
||||||
<text class="text-16px font-700 text-[#ff6700]">
|
|
||||||
{{ formatAmount(commissionStats.total_amount) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<text class="text-11px text-[#999] mt-1 block text-right">
|
|
||||||
共 {{ commissionStats.total_count }} 笔
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 搜索框 -->
|
<view class="flex items-center gap-2 mb-3">
|
||||||
<view class="mb-3">
|
<view class="flex-1 bg-[var(--bg-container)] rounded-8px px-3 h-40px flex items-center" @click="showSourcePicker = true">
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
<text class="flex-1 text-14px text-[var(--text-primary)]">{{ selectedSourceName }}</text>
|
||||||
<view class="flex items-center px-4 py-3 gap-2">
|
<text class="text-12px text-[var(--text-tertiary)]">▼</text>
|
||||||
<i class="i-mdi-magnify text-20px text-[#999]" />
|
|
||||||
<input
|
|
||||||
v-model="searchKeyword"
|
|
||||||
class="flex-1 text-14px text-[#212121] placeholder:text-[#999]"
|
|
||||||
placeholder="搜索 ICCID / 虚拟号 / 订单号"
|
|
||||||
@confirm="handleSearch"
|
|
||||||
>
|
|
||||||
<view
|
|
||||||
v-if="searchKeyword"
|
|
||||||
class="ml-1 min-w-32px min-h-32px flex items-center justify-center"
|
|
||||||
@click="clearSearch"
|
|
||||||
>
|
|
||||||
<i class="i-mdi-close-circle text-18px text-[#999]" />
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
class="px-4 py-2 bg-[#ff6700] text-white text-13px font-600 rounded-8px min-h-32px flex items-center justify-center"
|
|
||||||
@click="handleSearch"
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 筛选条件 -->
|
<view class="flex items-center justify-between mb-3">
|
||||||
<view class="flex items-center gap-2 mb-3">
|
<text class="text-12px text-[var(--text-secondary)]">共 {{ total }} 条记录</text>
|
||||||
<!-- 佣金来源筛选 -->
|
|
||||||
<view class="flex-1 bg-white rounded-full px-4 py-2 flex items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)]" @click="showSourcePicker = true">
|
|
||||||
<text class="flex-1 text-14px text-[#212121]">{{ selectedSourceName }}</text>
|
|
||||||
<i class="i-mdi-chevron-down text-18px text-[#999]" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 佣金明细列表 -->
|
|
||||||
<view class="mb-3">
|
|
||||||
<view class="flex items-center justify-between mb-2 px-2">
|
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
|
||||||
佣金明细
|
|
||||||
</text>
|
|
||||||
<text class="text-12px text-[#999]">
|
|
||||||
共 {{ total }} 条记录
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="loadingRecords && page === 1" class="pt-10 center">
|
<view v-if="loadingRecords && page === 1" class="pt-10 center">
|
||||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="commissionRecords.length > 0" class="space-y-2">
|
<view v-else-if="commissionRecords.length > 0" class="space-y-2">
|
||||||
<view
|
<view
|
||||||
v-for="record in commissionRecords"
|
v-for="record in commissionRecords"
|
||||||
:key="record.id"
|
:key="record.id"
|
||||||
class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4"
|
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||||||
>
|
>
|
||||||
<view class="flex items-center justify-between mb-2">
|
<view class="flex items-center justify-between">
|
||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<i class="i-mdi-cash-plus text-20px text-[#ff6700]" />
|
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
|
||||||
{{ getSourceText(record.commission_source) }}
|
{{ getSourceText(record.commission_source) }}
|
||||||
</text>
|
</text>
|
||||||
|
<view
|
||||||
|
:style="{
|
||||||
|
backgroundColor: getStatusColor(record.status).bg,
|
||||||
|
color: getStatusColor(record.status).text,
|
||||||
|
}"
|
||||||
|
class="px-2 py-0.5 rounded text-11px font-600"
|
||||||
|
>
|
||||||
|
{{ getStatusText(record.status) }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<text class="text-18px font-700 text-brand">
|
||||||
:style="{
|
|
||||||
backgroundColor: getStatusColor(record.status).bg,
|
|
||||||
color: getStatusColor(record.status).text,
|
|
||||||
}"
|
|
||||||
class="px-2 py-1 rounded-6px text-11px font-600"
|
|
||||||
>
|
|
||||||
{{ getStatusText(record.status) }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<text class="text-12px text-[#999]">
|
|
||||||
{{ formatTime(record.created_at) }}
|
|
||||||
</text>
|
|
||||||
<text class="text-18px font-700 text-[#ff6700]">
|
|
||||||
+{{ formatAmount(record.amount) }}
|
+{{ formatAmount(record.amount) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex items-center justify-between mt-2">
|
||||||
<view v-if="record.order_id" class="mt-2 pt-2 border-t-1px border-[#f5f5f5]">
|
<text class="text-12px text-[var(--text-tertiary)]">{{ formatTime(record.created_at) }}</text>
|
||||||
<text class="text-11px text-[#999]">
|
<text v-if="record.order_id" class="text-11px text-[var(--text-tertiary)]">订单ID: {{ record.order_id }}</text>
|
||||||
订单ID: {{ record.order_id }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载更多 -->
|
|
||||||
<view v-if="commissionRecords.length < total" class="py-3 center">
|
<view v-if="commissionRecords.length < total" class="py-3 center">
|
||||||
<text v-if="loadingRecords" class="text-12px text-[#999]">
|
<text v-if="loadingRecords" class="text-12px text-[var(--text-secondary)]">加载中...</text>
|
||||||
加载中...
|
<text v-else class="text-12px text-brand" @click="loadMore">加载更多</text>
|
||||||
</text>
|
|
||||||
<text v-else class="text-12px text-[#ff6700]" @click="loadMore">
|
|
||||||
加载更多
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="pt-10 center flex-col">
|
<view v-else class="pt-10 center">
|
||||||
<i class="i-mdi-cash-remove text-60px text-[#ddd] mb-3" />
|
<text class="text-14px text-[var(--text-secondary)]">暂无佣金记录</text>
|
||||||
<text class="text-14px text-[#999]">
|
|
||||||
暂无佣金记录
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部留白 -->
|
<view class="h-16" />
|
||||||
<view class="h-8" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 佣金来源选择器 -->
|
|
||||||
<SimplePicker
|
<SimplePicker
|
||||||
v-model:show="showSourcePicker"
|
v-model:show="showSourcePicker"
|
||||||
:options="sourceOptions"
|
:options="sourceOptions"
|
||||||
|
|||||||
@@ -202,16 +202,6 @@ function getCardStatusText(status: number, statusName?: string) {
|
|||||||
return map[status] ?? '未知'
|
return map[status] ?? '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取运营商图标
|
|
||||||
function getCarrierIcon(carrierName: string) {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
'中国移动': 'i-mdi-cellphone',
|
|
||||||
'中国联通': 'i-mdi-signal-variant',
|
|
||||||
'中国电信': 'i-mdi-wifi',
|
|
||||||
}
|
|
||||||
return map[carrierName] || 'i-mdi-sim'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空搜索
|
// 清空搜索
|
||||||
function clearSearch() {
|
function clearSearch() {
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
@@ -257,204 +247,120 @@ function onCarrierConfirm(option: { label: string, value: any }) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||||
<!-- 加载中 -->
|
|
||||||
<view v-if="loading" class="pt-20 center">
|
<view v-if="loading" class="pt-20 center">
|
||||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<view v-else>
|
||||||
<view v-else class="px-4 pt-4">
|
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||||
<!-- 统计卡片 -->
|
<text class="text-11px text-white/60">企业授权IoT卡</text>
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<text class="text-24px font-700 text-white block mt-1">{{ stats.total }}</text>
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<i class="i-mdi-sim text-24px text-[#ff6700]" />
|
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
|
||||||
企业授权IoT卡
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="text-right">
|
|
||||||
<text class="text-12px text-[#999] block">
|
|
||||||
总计
|
|
||||||
</text>
|
|
||||||
<text class="text-24px font-700 text-[#ff6700]">
|
|
||||||
{{ stats.total }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 搜索框 -->
|
<view class="px-4 mt-4">
|
||||||
<view class="mb-3">
|
<view class="flex items-center gap-2 mb-3">
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
<input
|
||||||
<view class="flex items-center px-4 py-3 gap-2">
|
v-model="searchKeyword"
|
||||||
<i class="i-mdi-magnify text-20px text-[#999]" />
|
class="flex-1 h-40px bg-[var(--bg-container)] rounded-full px-4 text-14px text-[var(--text-primary)]"
|
||||||
<input
|
placeholder="搜索 ICCID / 虚拟号 / 手机号"
|
||||||
v-model="searchKeyword"
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
class="flex-1 text-14px text-[#212121] placeholder:text-[#999]"
|
@confirm="handleSearch"
|
||||||
placeholder="搜索 ICCID / 虚拟号 / 手机号"
|
|
||||||
@confirm="handleSearch"
|
|
||||||
>
|
|
||||||
<view
|
|
||||||
v-if="searchKeyword"
|
|
||||||
class="ml-1 min-w-32px min-h-32px flex items-center justify-center"
|
|
||||||
@click="clearSearch"
|
|
||||||
>
|
|
||||||
<i class="i-mdi-close-circle text-18px text-[#999]" />
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
class="px-4 py-2 bg-[#ff6700] text-white text-13px font-600 rounded-8px min-h-32px flex items-center justify-center"
|
|
||||||
@click="handleSearch"
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 筛选条件 -->
|
|
||||||
<view class="flex items-center gap-2 mb-3">
|
|
||||||
<!-- 运营商筛选 -->
|
|
||||||
<view class="flex-1 bg-white rounded-full px-4 py-2 flex items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)]" @click="showCarrierPicker = true">
|
|
||||||
<text class="flex-1 text-14px text-[#212121]">{{ selectedCarrierName }}</text>
|
|
||||||
<i class="i-mdi-chevron-down text-18px text-[#999]" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 状态统计和筛选 -->
|
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
|
||||||
<view class="grid grid-cols-3 gap-3 mb-3">
|
|
||||||
<view class="text-center">
|
|
||||||
<text class="text-11px text-[#999] block mb-1">
|
|
||||||
正常
|
|
||||||
</text>
|
|
||||||
<text class="text-20px font-700 text-[#3ed268]">
|
|
||||||
{{ stats.active }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="text-center">
|
|
||||||
<text class="text-11px text-[#999] block mb-1">
|
|
||||||
停机
|
|
||||||
</text>
|
|
||||||
<text class="text-20px font-700 text-[#ff6b6b]">
|
|
||||||
{{ stats.inactive }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="text-center">
|
|
||||||
<text class="text-11px text-[#999] block mb-1">
|
|
||||||
已分销
|
|
||||||
</text>
|
|
||||||
<text class="text-20px font-700 text-[#ff6700]">
|
|
||||||
{{ stats.distributed }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 状态筛选 -->
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<view
|
|
||||||
v-for="option in statusOptions"
|
|
||||||
:key="option.value"
|
|
||||||
:class="[
|
|
||||||
'flex-1 text-center py-2 rounded-8px text-13px transition-all duration-200',
|
|
||||||
statusFilter === option.value
|
|
||||||
? 'bg-[#ff6700] text-white font-600'
|
|
||||||
: 'bg-[#f5f5f5] text-[#666]',
|
|
||||||
]"
|
|
||||||
@click="handleStatusChange(option.value)"
|
|
||||||
>
|
>
|
||||||
{{ option.label }}
|
<view
|
||||||
|
class="px-4 h-40px bg-brand text-white text-14px rounded-full flex items-center justify-center"
|
||||||
|
@click="handleSearch"
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="flex items-center gap-2 mb-3">
|
||||||
|
<view class="flex-1 bg-[var(--bg-container)] rounded-8px px-3 h-40px flex items-center" @click="showCarrierPicker = true">
|
||||||
|
<text class="flex-1 text-14px text-[var(--text-primary)]">{{ selectedCarrierName }}</text>
|
||||||
|
<text class="text-12px text-[var(--text-tertiary)]">▼</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
<view class="px-4">
|
||||||
<view v-if="filteredCards.length > 0" class="space-y-3 pb-4">
|
<view class="bg-[var(--bg-container)] rounded-12px p-4 mb-3">
|
||||||
<view
|
<view class="grid grid-cols-3 gap-3">
|
||||||
v-for="card in filteredCards"
|
<view class="text-center">
|
||||||
:key="card.id"
|
<text class="text-11px text-[var(--text-tertiary)] block">正常</text>
|
||||||
class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
<text class="text-18px font-700 text-[#52c41a] mt-1">{{ stats.active }}</text>
|
||||||
@click="viewCardDetail(card)"
|
</view>
|
||||||
>
|
<view class="text-center">
|
||||||
<!-- 顶部状态栏 -->
|
<text class="text-11px text-[var(--text-tertiary)] block">停机</text>
|
||||||
<view class="px-4 pt-4 pb-3 border-b-1px border-[#f5f5f5]">
|
<text class="text-18px font-700 text-[#ff4d4f] mt-1">{{ stats.inactive }}</text>
|
||||||
<view class="flex items-center justify-between mb-2">
|
</view>
|
||||||
<view class="flex items-center gap-2">
|
<view class="text-center">
|
||||||
<i
|
<text class="text-11px text-[var(--text-tertiary)] block">已分销</text>
|
||||||
:class="[
|
<text class="text-18px font-700 text-brand mt-1">{{ stats.distributed }}</text>
|
||||||
getCarrierIcon(card.carrier_name),
|
</view>
|
||||||
'text-24px text-[#212121]',
|
</view>
|
||||||
]"
|
|
||||||
/>
|
<view class="flex items-center gap-2 mt-4">
|
||||||
<view>
|
<view
|
||||||
<text class="text-16px font-700 text-[#212121] block">
|
v-for="option in statusOptions"
|
||||||
{{ card.carrier_name || '-' }}
|
:key="option.value"
|
||||||
</text>
|
:class="[
|
||||||
<text class="text-11px text-[#999]">
|
'flex-1 text-center py-2 rounded-full text-13px transition-all',
|
||||||
{{ getCardStatusText(card.status, card.status_name) }}
|
statusFilter === option.value
|
||||||
</text>
|
? 'bg-brand text-white font-600'
|
||||||
</view>
|
: 'bg-[var(--bg-muted)] text-[var(--text-secondary)]',
|
||||||
|
]"
|
||||||
|
@click="handleStatusChange(option.value)"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="filteredCards.length > 0" class="space-y-3 pb-4">
|
||||||
|
<view
|
||||||
|
v-for="card in filteredCards"
|
||||||
|
:key="card.id"
|
||||||
|
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||||||
|
@click="viewCardDetail(card)"
|
||||||
|
>
|
||||||
|
<view class="flex items-center justify-between">
|
||||||
|
<view>
|
||||||
|
<text class="text-15px font-600 text-[var(--text-primary)]">{{ card.carrier_name || '-' }}</text>
|
||||||
|
<text class="text-12px text-[var(--text-tertiary)] ml-2">{{ getCardStatusText(card.status, card.status_name) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
:style="{
|
:style="{
|
||||||
backgroundColor: getNetworkStatusColor(card.network_status).bg,
|
backgroundColor: getNetworkStatusColor(card.network_status).bg,
|
||||||
color: getNetworkStatusColor(card.network_status).text,
|
color: getNetworkStatusColor(card.network_status).text,
|
||||||
}"
|
}"
|
||||||
class="px-3 py-1 rounded-8px text-12px font-600"
|
class="px-3 py-1 rounded-full text-12px font-600"
|
||||||
>
|
>
|
||||||
{{ getNetworkStatusText(card.network_status, card.network_status_name) }}
|
{{ getNetworkStatusText(card.network_status, card.network_status_name) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<text class="text-12px text-[#999]">
|
<view class="mt-3 text-13px text-[var(--text-secondary)]">
|
||||||
{{ card.package_name || '暂无套餐' }}
|
{{ card.package_name || '暂无套餐' }}
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 卡片信息 -->
|
|
||||||
<view class="px-4 py-3">
|
|
||||||
<view class="space-y-2">
|
|
||||||
<!-- ICCID -->
|
|
||||||
<view class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">ICCID</text>
|
|
||||||
<text class="flex-1 text-13px text-[#212121] text-right break-all">
|
|
||||||
{{ card.iccid || '-' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 手机号 (MSISDN) -->
|
|
||||||
<view class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">手机号</text>
|
|
||||||
<text class="flex-1 text-13px font-600 text-[#212121] text-right">
|
|
||||||
{{ card.msisdn || '-' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 虚拟号 -->
|
|
||||||
<view v-if="card.virtual_no" class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">虚拟号</text>
|
|
||||||
<text class="flex-1 text-13px text-[#212121] text-right">
|
|
||||||
{{ card.virtual_no }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 使用情况 -->
|
<view class="mt-3 flex items-center justify-between text-12px text-[var(--text-tertiary)]">
|
||||||
<view class="px-4 pb-4">
|
<text>{{ card.iccid || '-' }}</text>
|
||||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
<text>{{ card.msisdn || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="card.virtual_no" class="mt-1 text-12px text-[var(--text-tertiary)]">
|
||||||
|
虚拟号: {{ card.virtual_no }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="mt-3 bg-[var(--bg-muted)] rounded-8px p-3">
|
||||||
<view class="flex items-center justify-between mb-2">
|
<view class="flex items-center justify-between mb-2">
|
||||||
<text class="text-12px text-[#999]">
|
<text class="text-12px text-[var(--text-secondary)]">流量</text>
|
||||||
流量使用
|
<text class="text-12px font-600 text-[var(--text-primary)]">
|
||||||
</text>
|
|
||||||
<text class="text-12px font-600 text-[#212121]">
|
|
||||||
{{ card.data_usage || '0' }} / {{ card.data_total || '0' }}
|
{{ card.data_usage || '0' }} / {{ card.data_total || '0' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="h-6px bg-[var(--border-primary)] rounded-full overflow-hidden">
|
||||||
<!-- 进度条 -->
|
|
||||||
<view class="h-6px bg-[#e0e0e0] rounded-full overflow-hidden">
|
|
||||||
<view
|
<view
|
||||||
:style="{
|
:style="{
|
||||||
width: `${(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) * 100}%`,
|
width: `${(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) * 100}%`,
|
||||||
@@ -462,37 +368,28 @@ function onCarrierConfirm(option: { label: string, value: any }) {
|
|||||||
:class="[
|
:class="[
|
||||||
'h-full rounded-full transition-all duration-300',
|
'h-full rounded-full transition-all duration-300',
|
||||||
(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) > 0.9
|
(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) > 0.9
|
||||||
? 'bg-[#ff6b6b]'
|
? 'bg-[#ff4d4f]'
|
||||||
: 'bg-[#3ed268]',
|
: 'bg-[#52c41a]',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="flex items-center justify-between mt-2 text-11px text-[#999]">
|
<view class="mt-2 flex items-center justify-between text-11px text-[var(--text-tertiary)]">
|
||||||
<text>激活: {{ card.activate_time || '-' }}</text>
|
<text>激活: {{ card.activate_time || '-' }}</text>
|
||||||
<text>到期: {{ card.expire_date || '-' }}</text>
|
<text>到期: {{ card.expire_date || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="pt-16 center">
|
||||||
|
<text class="text-14px text-[var(--text-secondary)]">{{ searchKeyword ? '未找到匹配的卡' : '暂无卡数据' }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<view class="h-16" />
|
||||||
<view v-else class="pt-20 center flex-col">
|
|
||||||
<i class="i-mdi-sim-off text-80px text-[#ddd] mb-4" />
|
|
||||||
<text class="text-15px text-[#999] mb-2">
|
|
||||||
{{ searchKeyword ? '未找到匹配的卡片' : '暂无卡片数据' }}
|
|
||||||
</text>
|
|
||||||
<text class="text-12px text-[#ccc]">
|
|
||||||
{{ searchKeyword ? '请尝试其他搜索关键词' : '请联系管理员添加卡片' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部留白 -->
|
|
||||||
<view class="h-8" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 运营商选择器 -->
|
|
||||||
<SimplePicker
|
<SimplePicker
|
||||||
v-model:show="showCarrierPicker"
|
v-model:show="showCarrierPicker"
|
||||||
:options="carrierOptions"
|
:options="carrierOptions"
|
||||||
|
|||||||
@@ -4,48 +4,33 @@ import { getUserInfo } from '@/api/auth'
|
|||||||
import { getEnterpriseDevices } from '@/api/enterprise'
|
import { getEnterpriseDevices } from '@/api/enterprise'
|
||||||
import type { DeviceInfo } from '@/api/enterprise'
|
import type { DeviceInfo } from '@/api/enterprise'
|
||||||
|
|
||||||
// 企业ID (用于调用企业接口)
|
|
||||||
const enterpriseId = ref<number>(0)
|
const enterpriseId = ref<number>(0)
|
||||||
|
|
||||||
// 设备列表
|
|
||||||
const deviceList = ref<DeviceInfo[]>([])
|
const deviceList = ref<DeviceInfo[]>([])
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 搜索关键词
|
|
||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
// 不再需要状态筛选
|
|
||||||
|
|
||||||
// 直接显示设备列表 (搜索由后端处理)
|
|
||||||
const filteredDevices = computed(() => {
|
const filteredDevices = computed(() => {
|
||||||
return deviceList.value
|
return deviceList.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 统计数据
|
|
||||||
const stats = computed(() => {
|
const stats = computed(() => {
|
||||||
const total = deviceList.value.length
|
const total = deviceList.value.length
|
||||||
return { total }
|
return { total }
|
||||||
})
|
})
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadEnterpriseDevices()
|
loadEnterpriseDevices()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载企业设备列表
|
|
||||||
async function loadEnterpriseDevices() {
|
async function loadEnterpriseDevices() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 获取企业ID (优先从本地存储获取)
|
|
||||||
if (!enterpriseId.value) {
|
if (!enterpriseId.value) {
|
||||||
const userInfo = uni.getStorageSync('user_info')
|
const userInfo = uni.getStorageSync('user_info')
|
||||||
if (userInfo?.enterprise_id) {
|
if (userInfo?.enterprise_id) {
|
||||||
enterpriseId.value = userInfo.enterprise_id
|
enterpriseId.value = userInfo.enterprise_id
|
||||||
} else {
|
} else {
|
||||||
// 如果本地存储没有,则调用接口获取
|
|
||||||
const apiUserInfo = await getUserInfo()
|
const apiUserInfo = await getUserInfo()
|
||||||
if (!apiUserInfo.enterprise_id) {
|
if (!apiUserInfo.enterprise_id) {
|
||||||
throw new Error('未找到企业ID')
|
throw new Error('未找到企业ID')
|
||||||
@@ -54,12 +39,10 @@ async function loadEnterpriseDevices() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取企业设备列表 (支持分页和虚拟号搜索)
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 100, // 暂时获取更多数据,因为没有分页加载
|
page_size: 100,
|
||||||
}
|
}
|
||||||
// 添加虚拟号搜索支持
|
|
||||||
if (searchKeyword.value) {
|
if (searchKeyword.value) {
|
||||||
params.virtual_no = searchKeyword.value
|
params.virtual_no = searchKeyword.value
|
||||||
}
|
}
|
||||||
@@ -69,35 +52,21 @@ async function loadEnterpriseDevices() {
|
|||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
console.error('加载企业设备列表失败:', error)
|
console.error('加载企业设备列表失败:', error)
|
||||||
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
|
||||||
if (error instanceof Error && error.message && !error.statusCode) {
|
if (error instanceof Error && error.message && !error.statusCode) {
|
||||||
uni.$u.toast(error.message)
|
uni.$u.toast(error.message)
|
||||||
}
|
}
|
||||||
// API 请求错误已由拦截器统一处理
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看设备详情
|
|
||||||
function viewDeviceDetail(device: any) {
|
function viewDeviceDetail(device: any) {
|
||||||
// 跳转到资产详情页
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/agent-system/asset-search/index?virtual_no=${device.virtual_no}`,
|
url: `/pages/agent-system/asset-search/index?virtual_no=${device.virtual_no}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取设备型号图标
|
|
||||||
function getDeviceModelIcon(model: string) {
|
|
||||||
// 根据设备型号返回不同图标
|
|
||||||
if (model?.includes('WM')) {
|
|
||||||
return 'i-mdi-router-wireless'
|
|
||||||
}
|
|
||||||
return 'i-mdi-devices'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化授权时间
|
|
||||||
function formatAuthorizedTime(time: string) {
|
function formatAuthorizedTime(time: string) {
|
||||||
if (!time) return '-'
|
if (!time) return '-'
|
||||||
try {
|
try {
|
||||||
@@ -111,178 +80,85 @@ function formatAuthorizedTime(time: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空搜索
|
|
||||||
function clearSearch() {
|
|
||||||
searchKeyword.value = ''
|
|
||||||
deviceList.value = []
|
|
||||||
loadEnterpriseDevices()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行搜索
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
deviceList.value = []
|
deviceList.value = []
|
||||||
loadEnterpriseDevices()
|
loadEnterpriseDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
function refreshList() {
|
|
||||||
deviceList.value = []
|
|
||||||
loadEnterpriseDevices()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||||
<!-- 加载中 -->
|
|
||||||
<view v-if="loading" class="pt-20 center">
|
<view v-if="loading" class="pt-20 center">
|
||||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<view v-else>
|
||||||
<view v-else class="px-4 pt-4">
|
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||||
<!-- 搜索框 -->
|
<text class="text-11px text-white/60">企业授权设备</text>
|
||||||
<view class="mb-3">
|
<text class="text-24px font-700 text-white block mt-1">{{ stats.total }}</text>
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
</view>
|
||||||
<view class="flex items-center px-4 py-3 gap-2">
|
|
||||||
<i class="i-mdi-magnify text-20px text-[#999]" />
|
<view class="px-4 mt-4">
|
||||||
<input
|
<view class="flex items-center gap-2 mb-3">
|
||||||
v-model="searchKeyword"
|
<input
|
||||||
class="flex-1 text-14px text-[#212121] placeholder:text-[#999]"
|
v-model="searchKeyword"
|
||||||
placeholder="搜索设备名称 / 设备型号 / 虚拟号"
|
class="flex-1 h-40px bg-[var(--bg-container)] rounded-full px-4 text-14px text-[var(--text-primary)]"
|
||||||
@confirm="handleSearch"
|
placeholder="搜索设备名称 / 设备型号 / 虚拟号"
|
||||||
>
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
<view
|
@confirm="handleSearch"
|
||||||
v-if="searchKeyword"
|
>
|
||||||
class="ml-1 min-w-32px min-h-32px flex items-center justify-center"
|
<view
|
||||||
@click="clearSearch"
|
class="px-4 h-40px bg-[var(--brand-primary)] text-white text-14px rounded-full flex items-center justify-center"
|
||||||
>
|
@click="handleSearch"
|
||||||
<i class="i-mdi-close-circle text-18px text-[#999]" />
|
>
|
||||||
</view>
|
搜索
|
||||||
<view
|
|
||||||
class="px-4 py-2 bg-[#ff6700] text-white text-13px font-600 rounded-8px min-h-32px flex items-center justify-center"
|
|
||||||
@click="handleSearch"
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<view class="px-4">
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<view v-if="filteredDevices.length > 0" class="space-y-3 pb-4">
|
||||||
<view class="flex items-center justify-between">
|
<view
|
||||||
<view class="flex items-center gap-2">
|
v-for="device in filteredDevices"
|
||||||
<i class="i-mdi-devices text-24px text-[#ff6700]" />
|
:key="device.virtual_no"
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||||||
企业授权设备
|
@click="viewDeviceDetail(device)"
|
||||||
</text>
|
>
|
||||||
</view>
|
<view class="flex items-center justify-between">
|
||||||
<view class="text-right">
|
<view>
|
||||||
<text class="text-12px text-[#999] block">
|
<text class="text-15px font-600 text-[var(--text-primary)]">{{ device.device_name || '-' }}</text>
|
||||||
总计
|
<text class="text-12px text-[var(--text-tertiary)] ml-2">{{ device.device_model || '-' }}</text>
|
||||||
</text>
|
|
||||||
<text class="text-24px font-700 text-[#ff6700]">
|
|
||||||
{{ stats.total }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 设备列表 -->
|
|
||||||
<view v-if="filteredDevices.length > 0" class="space-y-3 pb-4">
|
|
||||||
<view
|
|
||||||
v-for="device in filteredDevices"
|
|
||||||
:key="device.virtual_no"
|
|
||||||
class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
|
||||||
@click="viewDeviceDetail(device)"
|
|
||||||
>
|
|
||||||
<!-- 顶部状态栏 -->
|
|
||||||
<view class="px-4 pt-4 pb-3 border-b-1px border-[#f5f5f5]">
|
|
||||||
<view class="flex items-center justify-between mb-2">
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<i
|
|
||||||
:class="[
|
|
||||||
getDeviceModelIcon(device.device_model),
|
|
||||||
'text-24px text-[#212121]',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<view>
|
|
||||||
<text class="text-16px font-700 text-[#212121] block">
|
|
||||||
{{ device.device_name || '-' }}
|
|
||||||
</text>
|
|
||||||
<text class="text-11px text-[#999]">
|
|
||||||
{{ device.device_model || '-' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="px-3 py-1 rounded-8px text-12px font-600 bg-[#e8f5e9] text-[#2e7d32]">
|
<view class="px-3 py-1 rounded-full text-12px font-600 bg-[#e8f5e9] text-[#52c41a]">
|
||||||
已授权
|
已授权
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<text class="text-12px text-[#999]">
|
<view class="mt-3 text-12px text-[var(--text-tertiary)]">
|
||||||
绑定卡数: {{ device.card_count || 0 }} 张
|
绑定卡数: {{ device.card_count || 0 }} 张
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 设备信息 -->
|
|
||||||
<view class="px-4 py-3">
|
|
||||||
<view class="space-y-2">
|
|
||||||
<!-- 虚拟号 -->
|
|
||||||
<view class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">虚拟号</text>
|
|
||||||
<text class="flex-1 text-13px font-600 text-[#212121] text-right break-all">
|
|
||||||
{{ device.virtual_no || '-' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 设备型号 -->
|
|
||||||
<view class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">设备型号</text>
|
|
||||||
<text class="flex-1 text-13px text-[#212121] text-right">
|
|
||||||
{{ device.device_model || '-' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 绑定卡数 -->
|
|
||||||
<view class="flex items-start justify-between">
|
|
||||||
<text class="text-12px text-[#999] w-70px">绑定卡数</text>
|
|
||||||
<text class="flex-1 text-13px font-600 text-[#ff6700] text-right">
|
|
||||||
{{ device.card_count || 0 }} 张
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 授权时间 -->
|
<view class="mt-3 text-13px text-[var(--text-secondary)]">
|
||||||
<view class="px-4 pb-4">
|
<text class="mr-4">虚拟号: {{ device.virtual_no || '-' }}</text>
|
||||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
</view>
|
||||||
|
|
||||||
|
<view class="mt-3 bg-[var(--bg-muted)] rounded-8px p-3">
|
||||||
<view class="flex items-center justify-between">
|
<view class="flex items-center justify-between">
|
||||||
<text class="text-11px text-[#999]">
|
<text class="text-12px text-[var(--text-secondary)]">授权时间</text>
|
||||||
授权时间
|
<text class="text-12px font-600 text-[var(--text-primary)]">
|
||||||
</text>
|
|
||||||
<text class="text-12px font-600 text-[#212121]">
|
|
||||||
{{ formatAuthorizedTime(device.authorized_at) }}
|
{{ formatAuthorizedTime(device.authorized_at) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="pt-16 center">
|
||||||
|
<text class="text-14px text-[var(--text-secondary)]">{{ searchKeyword ? '未找到匹配的设备' : '暂无设备数据' }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<view class="h-16" />
|
||||||
<view v-else class="pt-20 center flex-col">
|
|
||||||
<i class="i-mdi-devices-off text-80px text-[#ddd] mb-4" />
|
|
||||||
<text class="text-15px text-[#999] mb-2">
|
|
||||||
{{ searchKeyword ? '未找到匹配的设备' : '暂无设备数据' }}
|
|
||||||
</text>
|
|
||||||
<text class="text-12px text-[#ccc]">
|
|
||||||
{{ searchKeyword ? '请尝试其他搜索关键词' : '请联系管理员添加设备' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部留白 -->
|
|
||||||
<view class="h-8" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -297,21 +173,4 @@ function refreshList() {
|
|||||||
.space-y-3 > view:not(:last-child) {
|
.space-y-3 > view:not(:last-child) {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-y-2 > view:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-spin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,301 +1,283 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { getUserInfo, logout as logoutApi } from '@/api/auth'
|
import { getUserInfo, logout as logoutApi } from '@/api/auth'
|
||||||
import { clearToken } from '@/utils/auth'
|
import { clearToken } from '@/utils/auth'
|
||||||
import type { UserInfo } from '@/api/auth'
|
import type { UserInfo } from '@/api/auth'
|
||||||
import { getCommissionSummary, getCommissionDailyStats } from '@/api/commission'
|
import { getFundSummary, getCommissionDailyStats } from '@/api/commission'
|
||||||
import type { CommissionSummary, DailyCommissionStats } from '@/api/commission'
|
import type { FundSummary, DailyCommissionStats } from '@/api/commission'
|
||||||
import Skeleton from '@/components/Skeleton.vue'
|
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const userInfo = ref<UserInfo | null>(null)
|
const userInfo = ref<UserInfo | null>(null)
|
||||||
// 佣金概览
|
// 佣金概览
|
||||||
const commissionSummary = ref<CommissionSummary | null>(null)
|
const commissionSummary = ref<FundSummary | null>(null)
|
||||||
// 每日佣金统计 (最近7天)
|
// 每日佣金统计 (最近7天)
|
||||||
const dailyStats = ref<DailyCommissionStats[]>([])
|
const dailyStats = ref<DailyCommissionStats[]>([])
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const loadingCommission = ref(false)
|
const loadingCommission = ref(false)
|
||||||
|
|
||||||
// 是否为代理账号
|
// 店铺ID
|
||||||
const isAgent = computed(() => userInfo.value?.user_type === 3)
|
const shopId = ref<number>(0)
|
||||||
|
|
||||||
// 格式化金额(分转元,带千分号)
|
// 是否为代理账号
|
||||||
function formatAmount(amount: number) {
|
const isAgent = computed(() => userInfo.value?.user_type === 3)
|
||||||
const yuan = (amount / 100).toFixed(2)
|
|
||||||
const parts = yuan.split('.')
|
|
||||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
||||||
return `¥${parts.join('.')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化日期 (MM-DD)
|
// 格式化金额(分转元,带千分号)
|
||||||
function formatDate(dateStr: string) {
|
function formatAmount(amount : number) {
|
||||||
const date = new Date(dateStr)
|
const yuan = (amount / 100).toFixed(2)
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
const parts = yuan.split('.')
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
return `${month}-${day}`
|
return `¥${parts.join('.')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据用户类型动态生成菜单
|
// 格式化日期 (MM-DD)
|
||||||
const menuItems = computed(() => {
|
function formatDate(dateStr : string) {
|
||||||
const menus = [
|
const date = new Date(dateStr)
|
||||||
{
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
title: '资产列表',
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
desc: userInfo.value?.user_type === 3 ? '代理资产列表' : '企业资产列表',
|
return `${month}-${day}`
|
||||||
icon: 'i-mdi-view-list',
|
}
|
||||||
path: '/pages/agent-system/assets/index',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 企业端显示授权卡和授权设备 (仅 user_type === 4)
|
// 根据用户类型动态生成菜单
|
||||||
if (userInfo.value?.user_type === 4) {
|
const menuItems = computed(() => {
|
||||||
menus.push(
|
const menus = [
|
||||||
{
|
{
|
||||||
title: '授权卡列表',
|
title: '资产列表',
|
||||||
desc: '企业授权IoT卡',
|
desc: userInfo.value?.user_type === 3 ? '代理资产列表' : '企业资产列表',
|
||||||
icon: 'i-mdi-sim',
|
icon: '/static/icons/资产列表.png',
|
||||||
path: '/pages/agent-system/enterprise-cards/index',
|
path: '/pages/agent-system/assets/index',
|
||||||
},
|
},
|
||||||
{
|
]
|
||||||
title: '授权设备列表',
|
|
||||||
desc: '企业授权设备',
|
|
||||||
icon: 'i-mdi-devices',
|
|
||||||
path: '/pages/agent-system/enterprise-devices/index',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仅代理端显示佣金和提现
|
// 企业端显示授权卡和授权设备 (仅 user_type === 4)
|
||||||
if (userInfo.value?.user_type === 3) {
|
if (userInfo.value?.user_type === 4) {
|
||||||
menus.push(
|
menus.push(
|
||||||
{
|
{
|
||||||
title: '佣金中心',
|
title: '授权卡列表',
|
||||||
desc: '收益统计',
|
desc: '企业授权IoT卡',
|
||||||
icon: 'i-mdi-wallet',
|
icon: '/static/icons/授权卡列表.png',
|
||||||
path: '/pages/agent-system/commission-center/index',
|
path: '/pages/agent-system/enterprise-cards/index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '提现管理',
|
title: '授权设备列表',
|
||||||
desc: '申请提现',
|
desc: '企业授权设备',
|
||||||
icon: 'i-mdi-cash',
|
icon: '/static/icons/授权设备列表.png',
|
||||||
path: '/pages/agent-system/withdraw/index',
|
path: '/pages/agent-system/enterprise-devices/index',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return menus
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载佣金数据
|
|
||||||
async function loadCommissionData() {
|
|
||||||
if (!isAgent.value) return
|
|
||||||
|
|
||||||
loadingCommission.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 加载佣金概览
|
|
||||||
const summary = await getCommissionSummary()
|
|
||||||
commissionSummary.value = summary
|
|
||||||
|
|
||||||
// 2. 加载最近7天佣金统计
|
|
||||||
const stats = await getCommissionDailyStats({ days: 7 })
|
|
||||||
dailyStats.value = stats || []
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('加载佣金数据失败:', error)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loadingCommission.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载首页数据
|
|
||||||
async function loadHomeData() {
|
|
||||||
loading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 加载用户信息
|
|
||||||
const user = await getUserInfo()
|
|
||||||
userInfo.value = user
|
|
||||||
|
|
||||||
console.log('首页数据加载成功:', { user })
|
|
||||||
|
|
||||||
// 如果是代理账号,加载佣金数据
|
|
||||||
if (user.user_type === 3) {
|
|
||||||
await loadCommissionData()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (error: any) {
|
// 仅代理端显示佣金和提现
|
||||||
console.error('加载首页数据失败:', error)
|
if (userInfo.value?.user_type === 3) {
|
||||||
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
menus.push(
|
||||||
if (error instanceof Error && error.message && !error.statusCode) {
|
{
|
||||||
uni.$u.toast(error.message)
|
title: '佣金中心',
|
||||||
|
desc: '收益统计',
|
||||||
|
icon: '/static/icons/佣金中心.png',
|
||||||
|
path: '/pages/agent-system/commission-center/index',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提现管理',
|
||||||
|
desc: '申请提现',
|
||||||
|
icon: '/static/icons/提现管理.png',
|
||||||
|
path: '/pages/agent-system/withdraw/index',
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// API 请求错误已由拦截器统一处理
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳转页面
|
return menus
|
||||||
function navigateTo(path: string) {
|
|
||||||
uni.navigateTo({ url: path })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
function logout() {
|
|
||||||
uni.showModal({
|
|
||||||
title: '确认退出',
|
|
||||||
content: '确定要退出登录吗?',
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
try {
|
|
||||||
// 调用登出接口
|
|
||||||
await logoutApi()
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('登出接口调用失败:', error)
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// 清除本地登录状态
|
|
||||||
clearToken()
|
|
||||||
uni.removeStorageSync('refresh_token')
|
|
||||||
uni.removeStorageSync('user_info')
|
|
||||||
|
|
||||||
// 跳转到登录页
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/common/login/index',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
// 加载佣金数据
|
||||||
loadHomeData()
|
async function loadCommissionData() {
|
||||||
})
|
if (!isAgent.value || !shopId.value) return
|
||||||
|
|
||||||
|
loadingCommission.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 加载佣金概览 (新接口 /shops/fund-summary)
|
||||||
|
const summary = await getFundSummary()
|
||||||
|
commissionSummary.value = summary
|
||||||
|
|
||||||
|
// 2. 加载最近7天佣金统计
|
||||||
|
const stats = await getCommissionDailyStats(shopId.value, { days: 30 })
|
||||||
|
dailyStats.value = stats || []
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('加载佣金数据失败:', error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loadingCommission.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载首页数据
|
||||||
|
async function loadHomeData() {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 加载用户信息
|
||||||
|
const user = await getUserInfo()
|
||||||
|
userInfo.value = user
|
||||||
|
|
||||||
|
// 设置店铺ID
|
||||||
|
if (user.shop_id) {
|
||||||
|
shopId.value = user.shop_id
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('首页数据加载成功:', { user })
|
||||||
|
|
||||||
|
// 如果是代理账号,加载佣金数据
|
||||||
|
if (user.user_type === 3) {
|
||||||
|
await loadCommissionData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error : any) {
|
||||||
|
console.error('加载首页数据失败:', error)
|
||||||
|
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
||||||
|
if (error instanceof Error && error.message && !error.statusCode) {
|
||||||
|
uni.$u.toast(error.message)
|
||||||
|
}
|
||||||
|
// API 请求错误已由拦截器统一处理
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转页面
|
||||||
|
function navigateTo(path : string) {
|
||||||
|
uni.navigateTo({ url: path })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
function logout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认退出',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
// 调用登出接口
|
||||||
|
await logoutApi()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('登出接口调用失败:', error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// 清除本地登录状态
|
||||||
|
clearToken()
|
||||||
|
uni.removeStorageSync('refresh_token')
|
||||||
|
uni.removeStorageSync('user_info')
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/common/login/index',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadHomeData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
||||||
<!-- 加载骨架屏 -->
|
<!-- 加载中 -->
|
||||||
<Skeleton v-if="loading" type="home" />
|
<view v-if="loading" class="pt-20 center">
|
||||||
|
<text class="text-14px text-[#999]">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 实际内容 -->
|
<!-- 实际内容 -->
|
||||||
<view v-else>
|
<view v-else>
|
||||||
<!-- 顶部用户信息 -->
|
<!-- 顶部用户信息卡片 -->
|
||||||
<view class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] px-6 pt-6 pb-5">
|
<view class="px-4 pt-4">
|
||||||
<view class="flex items-center justify-between">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] px-4 py-4">
|
||||||
<!-- 左侧用户信息 -->
|
<view class="flex items-center">
|
||||||
<view class="flex-1">
|
<!-- 左侧头像 -->
|
||||||
<view class="flex items-center gap-2 mb-1">
|
<view class="w-56px h-56px rounded-full bg-[#f5f5f5] center mr-4"
|
||||||
<text class="text-22px font-700 text-white">
|
@click="navigateTo('/pages/agent-system/user-info/index')">
|
||||||
|
<text class="text-14px text-[#666] font-700">
|
||||||
|
{{ userInfo?.user_type === 3 ? '代理' : '企业' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧用户信息 -->
|
||||||
|
<view class="flex-1">
|
||||||
|
<text class="text-18px font-700 text-[#212121] block mb-1">
|
||||||
{{ userInfo?.username || '欢迎回来' }}
|
{{ userInfo?.username || '欢迎回来' }}
|
||||||
</text>
|
</text>
|
||||||
<view
|
<text class="text-13px text-[#999]">
|
||||||
v-if="userInfo?.user_type === 3"
|
|
||||||
class="px-2 py-0.5 bg-white/20 rounded-6px"
|
|
||||||
>
|
|
||||||
<text class="text-11px text-white font-600">代理</text>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
v-else-if="userInfo?.user_type === 4"
|
|
||||||
class="px-2 py-0.5 bg-white/20 rounded-6px"
|
|
||||||
>
|
|
||||||
<text class="text-11px text-white font-600">企业</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<i class="i-mdi-phone text-14px text-white/70" />
|
|
||||||
<text class="text-13px text-white/90">
|
|
||||||
{{ userInfo?.phone || '-' }}
|
{{ userInfo?.phone || '-' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 右侧头像 -->
|
|
||||||
<view
|
|
||||||
class="w-56px h-56px rounded-full bg-white/20 backdrop-blur center border-2px border-white/30"
|
|
||||||
@click="navigateTo('/pages/agent-system/user-info/index')"
|
|
||||||
>
|
|
||||||
<text class="text-24px text-white font-700">
|
|
||||||
{{ userInfo?.username?.substring(0, 1) || 'U' }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 代理端: 佣金概览 -->
|
<!-- 代理端: 佣金概览 -->
|
||||||
<view v-if="isAgent && commissionSummary" class="px-4 pt-4">
|
<view v-if="isAgent && commissionSummary" class="px-4 pt-4">
|
||||||
<view class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-16px shadow-[0_4px_16px_rgba(255,103,0,0.3)] p-4 mb-3">
|
<text class="text-16px font-700 text-[#212121] block mb-3">可提现佣金</text>
|
||||||
<view class="flex items-center justify-between mb-3">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||||
<view>
|
<view class="grid grid-cols-4 gap-3">
|
||||||
<text class="text-13px text-white/70 block mb-1">
|
|
||||||
可提现佣金
|
|
||||||
</text>
|
|
||||||
<text class="text-32px font-700 text-white block">
|
|
||||||
{{ formatAmount(commissionSummary.available_commission) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<i class="i-mdi-cash-multiple text-48px text-white/20" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="grid grid-cols-3 gap-3 pt-3 border-t-1px border-white/20">
|
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
<text class="text-11px text-white/70 block mb-1">累计</text>
|
<text class="text-11px text-[#999] block mb-1">累计</text>
|
||||||
<text class="text-14px font-600 text-white">
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
{{ formatAmount(commissionSummary.total_commission) }}
|
{{ formatAmount(commissionSummary.total_commission) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
<text class="text-11px text-white/70 block mb-1">冻结</text>
|
<text class="text-11px text-[#999] block mb-1">冻结</text>
|
||||||
<text class="text-14px font-600 text-white">
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
{{ formatAmount(commissionSummary.frozen_commission) }}
|
{{ formatAmount(commissionSummary.frozen_commission) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
<text class="text-11px text-white/70 block mb-1">已提现</text>
|
<text class="text-11px text-[#999] block mb-1">已提现</text>
|
||||||
<text class="text-14px font-600 text-white">
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
{{ formatAmount(commissionSummary.withdrawn_commission) }}
|
{{ formatAmount(commissionSummary.withdrawn_commission) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="text-center">
|
||||||
|
<text class="text-11px text-[#999] block mb-1">可提现</text>
|
||||||
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
|
{{ formatAmount(commissionSummary.available_commission) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 每日佣金趋势 -->
|
<!-- 今日佣金 -->
|
||||||
<view v-if="dailyStats.length > 0" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<view v-if="dailyStats.length > 0">
|
||||||
<view class="flex items-center justify-between mb-3">
|
<view class="flex items-center justify-between mb-3">
|
||||||
<view class="flex items-center gap-2">
|
<text class="text-16px font-700 text-[#212121]">今日佣金</text>
|
||||||
<i class="i-mdi-chart-line text-20px text-[#ff6700]" />
|
<text class="text-13px text-[#999]" @click="navigateTo('/pages/agent-system/commission-center/index')">更多</text>
|
||||||
<text class="text-15px font-600 text-[#212121]">
|
|
||||||
近7日佣金
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4">
|
||||||
<!-- 简单的趋势条形图 -->
|
<view class="grid grid-cols-3 gap-3">
|
||||||
<view class="space-y-2">
|
<view class="text-center">
|
||||||
<view
|
<text class="text-12px text-[#999] block mb-1">日期</text>
|
||||||
v-for="stat in dailyStats"
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
:key="stat.date"
|
{{ formatDate(dailyStats[0].date) }}
|
||||||
class="flex items-center gap-2"
|
</text>
|
||||||
>
|
</view>
|
||||||
<text class="text-11px text-[#999] w-40px">
|
<view class="text-center">
|
||||||
{{ formatDate(stat.date) }}
|
<text class="text-12px text-[#999] block mb-1">佣金金额</text>
|
||||||
</text>
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
<view class="flex-1 h-20px bg-[#f5f5f5] rounded-full overflow-hidden relative">
|
{{ formatAmount(dailyStats[0].total_amount) }}
|
||||||
<view
|
</text>
|
||||||
:style="{
|
</view>
|
||||||
width: `${Math.min(100, (stat.total_amount / Math.max(...dailyStats.map(s => s.total_amount))) * 100)}%`,
|
<view class="text-center">
|
||||||
}"
|
<text class="text-12px text-[#999] block mb-1">订单笔数</text>
|
||||||
class="h-full bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-full"
|
<text class="text-14px font-600 text-[#212121]">
|
||||||
/>
|
{{ dailyStats[0].total_count }}
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="text-11px font-600 text-[#212121] w-60px text-right">
|
|
||||||
{{ formatAmount(stat.total_amount) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -303,23 +285,55 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 快捷功能区 -->
|
<!-- 快捷功能区 -->
|
||||||
<view class="px-4 pt-4">
|
<view class="px-4 pt-4">
|
||||||
<text class="text-13px text-[#999] block mb-3">快捷功能</text>
|
<text class="text-16px font-700 text-[#212121] block mb-3">快捷功能</text>
|
||||||
|
|
||||||
<!-- 功能列表 -->
|
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-3">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-3">
|
||||||
<view
|
<!-- 资产列表 -->
|
||||||
v-for="(item, index) in menuItems"
|
<view class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/assets/index')">
|
||||||
:key="item.path"
|
<image src="@/static/icons/资产列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200"
|
|
||||||
:class="{ 'border-t-1px border-[#f5f5f5]': index > 0 }"
|
|
||||||
@click="navigateTo(item.path)"
|
|
||||||
>
|
|
||||||
<view class="w-40px h-40px rounded-12px bg-[#fff3e0] center mr-3">
|
|
||||||
<i :class="item.icon" class="text-20px text-[#ff6700]" />
|
|
||||||
</view>
|
|
||||||
<view class="flex-1">
|
<view class="flex-1">
|
||||||
<text class="text-15px text-[#212121] font-600 block">{{ item.title }}</text>
|
<text class="text-15px text-[#212121] font-600 block">资产列表</text>
|
||||||
<text class="text-12px text-[#999] block mt-0.5">{{ item.desc }}</text>
|
<text class="text-12px text-[#999] block mt-0.5">{{ userInfo?.user_type === 3 ? '代理资产列表' : '企业资产列表' }}</text>
|
||||||
|
</view>
|
||||||
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 企业端: 授权卡列表 -->
|
||||||
|
<view v-if="userInfo?.user_type === 4" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/enterprise-cards/index')">
|
||||||
|
<image src="@/static/icons/授权卡列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
|
<view class="flex-1">
|
||||||
|
<text class="text-15px text-[#212121] font-600 block">授权卡列表</text>
|
||||||
|
<text class="text-12px text-[#999] block mt-0.5">企业授权IoT卡</text>
|
||||||
|
</view>
|
||||||
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 企业端: 授权设备列表 -->
|
||||||
|
<view v-if="userInfo?.user_type === 4" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/enterprise-devices/index')">
|
||||||
|
<image src="@/static/icons/授权设备列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
|
<view class="flex-1">
|
||||||
|
<text class="text-15px text-[#212121] font-600 block">授权设备列表</text>
|
||||||
|
<text class="text-12px text-[#999] block mt-0.5">企业授权设备</text>
|
||||||
|
</view>
|
||||||
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 代理端: 佣金中心 -->
|
||||||
|
<view v-if="userInfo?.user_type === 3" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/commission-center/index')">
|
||||||
|
<image src="@/static/icons/佣金中心.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
|
<view class="flex-1">
|
||||||
|
<text class="text-15px text-[#212121] font-600 block">佣金中心</text>
|
||||||
|
<text class="text-12px text-[#999] block mt-0.5">收益统计</text>
|
||||||
|
</view>
|
||||||
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 代理端: 提现管理 -->
|
||||||
|
<view v-if="userInfo?.user_type === 3" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/withdraw/index')">
|
||||||
|
<image src="@/static/icons/提现管理.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
|
<view class="flex-1">
|
||||||
|
<text class="text-15px text-[#212121] font-600 block">提现管理</text>
|
||||||
|
<text class="text-12px text-[#999] block mt-0.5">申请提现</text>
|
||||||
</view>
|
</view>
|
||||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
</view>
|
</view>
|
||||||
@@ -328,35 +342,19 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 其他操作区 -->
|
<!-- 其他操作区 -->
|
||||||
<view class="px-4 pt-4 pb-6">
|
<view class="px-4 pt-4 pb-6">
|
||||||
<text class="text-13px text-[#999] block mb-3">设置</text>
|
<text class="text-16px font-700 text-[#212121] block mb-3">设置</text>
|
||||||
|
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||||
<!-- 个人信息 -->
|
|
||||||
<view
|
|
||||||
class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200"
|
|
||||||
@click="navigateTo('/pages/agent-system/user-info/index')"
|
|
||||||
>
|
|
||||||
<i class="i-mdi-account-circle text-20px text-[#ff6700] mr-3" />
|
|
||||||
<text class="flex-1 text-15px text-[#212121]">个人信息</text>
|
|
||||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 修改密码 -->
|
<!-- 修改密码 -->
|
||||||
<view
|
<view class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/change-password/index')">
|
||||||
class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200"
|
<image src="@/static/icons/修改密码.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
@click="navigateTo('/pages/agent-system/change-password/index')"
|
|
||||||
>
|
|
||||||
<i class="i-mdi-lock-reset text-20px text-[#ff6700] mr-3" />
|
|
||||||
<text class="flex-1 text-15px text-[#212121]">修改密码</text>
|
<text class="flex-1 text-15px text-[#212121]">修改密码</text>
|
||||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 退出登录 -->
|
<!-- 退出登录 -->
|
||||||
<view
|
<view class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="logout">
|
||||||
class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200"
|
<image src="@/static/icons/退出登录.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||||
@click="logout"
|
|
||||||
>
|
|
||||||
<i class="i-mdi-logout text-20px text-[#999] mr-3" />
|
|
||||||
<text class="flex-1 text-15px text-[#212121]">退出登录</text>
|
<text class="flex-1 text-15px text-[#212121]">退出登录</text>
|
||||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||||
</view>
|
</view>
|
||||||
@@ -367,26 +365,27 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.center {
|
.center {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
.space-y-2 > view:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-spin {
|
.space-y-2>view:not(:last-child) {
|
||||||
animation: spin 1s linear infinite;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ const filteredPackages = computed(() => {
|
|||||||
<view class="bg-white rounded-12px p-4 shadow-[0_2px_8px_rgba(0,0,0,0.04)]">
|
<view class="bg-white rounded-12px p-4 shadow-[0_2px_8px_rgba(0,0,0,0.04)]">
|
||||||
<view class="flex items-center justify-between mb-3">
|
<view class="flex items-center justify-between mb-3">
|
||||||
<text class="text-16px font-600 text-[#212121]">佣金明细</text>
|
<text class="text-16px font-600 text-[#212121]">佣金明细</text>
|
||||||
<button class="text-13px text-[#ff6700]">查看全部 →</button>
|
<button class="text-13px text-[var(--brand-primary)]">查看全部 →</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="space-y-3">
|
<view class="space-y-3">
|
||||||
@@ -660,9 +660,9 @@ const filteredPackages = computed(() => {
|
|||||||
<view v-if="currentTab === 'my-packages'">
|
<view v-if="currentTab === 'my-packages'">
|
||||||
<!-- 总计卡片 (橙色渐变) -->
|
<!-- 总计卡片 (橙色渐变) -->
|
||||||
<view class="
|
<view class="
|
||||||
bg-gradient-to-br from-[#ff6700] to-[#ff8533]
|
bg-gradient-to-br from-[var(--brand-primary)] to-[var(--brand-primary-light)]
|
||||||
rounded-16px p-6 mb-4
|
rounded-16px p-6 mb-4
|
||||||
shadow-[0_8px_24px_rgba(255,103,0,0.2)]
|
shadow-brand-colored
|
||||||
">
|
">
|
||||||
<text class="text-13px text-white/70 block mb-2">套餐总计</text>
|
<text class="text-13px text-white/70 block mb-2">套餐总计</text>
|
||||||
|
|
||||||
@@ -699,7 +699,7 @@ const filteredPackages = computed(() => {
|
|||||||
<view class="
|
<view class="
|
||||||
bg-white rounded-12px p-4
|
bg-white rounded-12px p-4
|
||||||
shadow-[0_2px_8px_rgba(0,0,0,0.04)]
|
shadow-[0_2px_8px_rgba(0,0,0,0.04)]
|
||||||
border-2 border-[#ff6700]/20
|
border-2 border-[var(--brand-primary)]/20
|
||||||
">
|
">
|
||||||
<view class="flex items-start justify-between mb-3">
|
<view class="flex items-start justify-between mb-3">
|
||||||
<text class="text-16px font-600 text-[#212121]">
|
<text class="text-16px font-600 text-[#212121]">
|
||||||
@@ -833,7 +833,7 @@ const filteredPackages = computed(() => {
|
|||||||
<i class="i-mdi-gift-outline w-48px h-48px text-[#e0e0e0] mb-2" />
|
<i class="i-mdi-gift-outline w-48px h-48px text-[#e0e0e0] mb-2" />
|
||||||
<text class="text-13px text-[#9e9e9e] block mb-3">暂无加油包</text>
|
<text class="text-13px text-[#9e9e9e] block mb-3">暂无加油包</text>
|
||||||
<button class="
|
<button class="
|
||||||
bg-[#ff6700] text-white
|
bg-[var(--brand-primary)] text-white
|
||||||
px-5 py-2 rounded-8px
|
px-5 py-2 rounded-8px
|
||||||
text-13px font-500
|
text-13px font-500
|
||||||
">
|
">
|
||||||
@@ -872,7 +872,7 @@ const filteredPackages = computed(() => {
|
|||||||
<view class="flex gap-2 mb-3 overflow-x-auto pb-2">
|
<view class="flex gap-2 mb-3 overflow-x-auto pb-2">
|
||||||
<button
|
<button
|
||||||
:class="{
|
:class="{
|
||||||
'bg-[#fff7f0] text-[#ff6700]': filterStatus === null,
|
'bg-[var(--brand-primary-light)] text-[var(--brand-primary)]': filterStatus === null,
|
||||||
'text-[#9e9e9e]': filterStatus !== null
|
'text-[#9e9e9e]': filterStatus !== null
|
||||||
}"
|
}"
|
||||||
class="px-4 py-1.5 rounded-full text-13px font-500 whitespace-nowrap"
|
class="px-4 py-1.5 rounded-full text-13px font-500 whitespace-nowrap"
|
||||||
@@ -1076,7 +1076,7 @@ const filteredPackages = computed(() => {
|
|||||||
<span
|
<span
|
||||||
v-for="tag in asset.tags"
|
v-for="tag in asset.tags"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
class="px-2 py-0.5 bg-[#fff7f0] text-[#ff6700] text-11px rounded-full"
|
class="px-2 py-0.5 bg-[var(--brand-primary-light)] text-[var(--brand-primary)] text-11px rounded-full"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</span>
|
</span>
|
||||||
@@ -1131,7 +1131,7 @@ const filteredPackages = computed(() => {
|
|||||||
<text class="text-11px text-[#9e9e9e]">标签总数</text>
|
<text class="text-11px text-[#9e9e9e]">标签总数</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-1 text-center">
|
<view class="flex-1 text-center">
|
||||||
<text class="text-24px font-700 text-[#ff6700] block">
|
<text class="text-24px font-700 text-[var(--brand-primary)] block">
|
||||||
{{ tagList.reduce((sum, t) => sum + t.asset_count, 0) }}
|
{{ tagList.reduce((sum, t) => sum + t.asset_count, 0) }}
|
||||||
</text>
|
</text>
|
||||||
<text class="text-11px text-[#9e9e9e]">已分配资产</text>
|
<text class="text-11px text-[#9e9e9e]">已分配资产</text>
|
||||||
@@ -1179,7 +1179,7 @@ const filteredPackages = computed(() => {
|
|||||||
已分配 {{ tag.asset_count }} 个资产
|
已分配 {{ tag.asset_count }} 个资产
|
||||||
</text>
|
</text>
|
||||||
<button
|
<button
|
||||||
class="text-12px text-[#ff6700] font-500"
|
class="text-12px text-[var(--brand-primary)] font-500"
|
||||||
@click="enterAssignMode(tag)"
|
@click="enterAssignMode(tag)"
|
||||||
>
|
>
|
||||||
分配 →
|
分配 →
|
||||||
@@ -1192,11 +1192,11 @@ const filteredPackages = computed(() => {
|
|||||||
<button class="
|
<button class="
|
||||||
w-full
|
w-full
|
||||||
mt-4
|
mt-4
|
||||||
bg-[#ff6700] text-white
|
bg-[var(--brand-primary)] text-white
|
||||||
py-3 rounded-12px
|
py-3 rounded-12px
|
||||||
text-14px font-500
|
text-14px font-500
|
||||||
flex items-center justify-center gap-2
|
flex items-center justify-center gap-2
|
||||||
shadow-[0_2px_8px_rgba(255,103,0,0.2)]
|
shadow-brand-xs
|
||||||
">
|
">
|
||||||
<i class="i-mdi-plus w-18px h-18px" />
|
<i class="i-mdi-plus w-18px h-18px" />
|
||||||
创建新标签
|
创建新标签
|
||||||
@@ -1255,7 +1255,7 @@ const filteredPackages = computed(() => {
|
|||||||
<!-- 勾选框 -->
|
<!-- 勾选框 -->
|
||||||
<view
|
<view
|
||||||
:class="{
|
:class="{
|
||||||
'bg-[#ff6700] border-[#ff6700]': asset.tags.includes(selectedTag.name),
|
'bg-[var(--brand-primary)] border-[var(--brand-primary)]': asset.tags.includes(selectedTag.name),
|
||||||
'bg-white border-[#e0e0e0]': !asset.tags.includes(selectedTag.name)
|
'bg-white border-[#e0e0e0]': !asset.tags.includes(selectedTag.name)
|
||||||
}"
|
}"
|
||||||
class="
|
class="
|
||||||
@@ -1296,10 +1296,10 @@ const filteredPackages = computed(() => {
|
|||||||
<view class="fixed bottom-0 left-0 right-0 bg-white p-4 pb-safe border-t border-[#eeeeee]">
|
<view class="fixed bottom-0 left-0 right-0 bg-white p-4 pb-safe border-t border-[#eeeeee]">
|
||||||
<button class="
|
<button class="
|
||||||
w-full
|
w-full
|
||||||
bg-[#ff6700] text-white
|
bg-[var(--brand-primary)] text-white
|
||||||
py-3 rounded-8px
|
py-3 rounded-8px
|
||||||
text-15px font-600
|
text-15px font-600
|
||||||
shadow-[0_2px_8px_rgba(255,103,0,0.2)]
|
shadow-brand-xs
|
||||||
">
|
">
|
||||||
保存分配
|
保存分配
|
||||||
</button>
|
</button>
|
||||||
@@ -1418,10 +1418,10 @@ const filteredPackages = computed(() => {
|
|||||||
<!-- 虚流量比例 -->
|
<!-- 虚流量比例 -->
|
||||||
<view class="flex items-center justify-between p-3 bg-[#fff7f0] rounded-8px">
|
<view class="flex items-center justify-between p-3 bg-[#fff7f0] rounded-8px">
|
||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<i class="i-mdi-calculator w-18px h-18px text-[#ff6700]" />
|
<i class="i-mdi-calculator w-18px h-18px text-[var(--brand-primary)]" />
|
||||||
<text class="text-13px text-[#757575]">虚流量比例</text>
|
<text class="text-13px text-[#757575]">虚流量比例</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="text-16px font-700 text-[#ff6700]">
|
<text class="text-16px font-700 text-[var(--brand-primary)]">
|
||||||
1 : {{ (1 / selectedPackage.virtual_ratio).toFixed(1) }}
|
1 : {{ (1 / selectedPackage.virtual_ratio).toFixed(1) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -1488,10 +1488,10 @@ const filteredPackages = computed(() => {
|
|||||||
续费套餐
|
续费套餐
|
||||||
</button>
|
</button>
|
||||||
<button class="
|
<button class="
|
||||||
bg-[#ff6700] text-white
|
bg-[var(--brand-primary)] text-white
|
||||||
py-3 rounded-8px
|
py-3 rounded-8px
|
||||||
text-14px font-500
|
text-14px font-500
|
||||||
shadow-[0_2px_8px_rgba(255,103,0,0.2)]
|
shadow-brand-xs
|
||||||
">
|
">
|
||||||
购买加油包
|
购买加油包
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ onMounted(() => {
|
|||||||
<text class="text-11px text-[#999] mt-1">总IoT卡</text>
|
<text class="text-11px text-[#999] mt-1">总IoT卡</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
<text class="text-24px font-700 text-[#ff6700] block">{{ packageStats.hasPackage }}</text>
|
<text class="text-24px font-700 text-[var(--brand-primary)] block">{{ packageStats.hasPackage }}</text>
|
||||||
<text class="text-11px text-[#999] mt-1">已订购</text>
|
<text class="text-11px text-[#999] mt-1">已订购</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
@@ -153,7 +153,7 @@ onMounted(() => {
|
|||||||
<view class="px-4 py-3 bg-[#f8fafc] border-b-1px border-[#f1f5f9]">
|
<view class="px-4 py-3 bg-[#f8fafc] border-b-1px border-[#f1f5f9]">
|
||||||
<view class="flex items-center justify-between">
|
<view class="flex items-center justify-between">
|
||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<i class="i-mdi-package-variant text-20px text-[#ff6700]" />
|
<i class="i-mdi-package-variant text-20px text-[var(--brand-primary)]" />
|
||||||
<text class="text-15px font-600 text-[#212121]">{{ group.name }}</text>
|
<text class="text-15px font-600 text-[#212121]">{{ group.name }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="text-12px text-[#999]">{{ group.count }} 张</text>
|
<text class="text-12px text-[#999]">{{ group.count }} 张</text>
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { getCommissionSummary } from '@/api/commission'
|
import { getFundSummary } from '@/api/commission'
|
||||||
import type { CommissionSummary } from '@/api/commission'
|
import type { FundSummary } from '@/api/commission'
|
||||||
import { createWithdrawal, getWithdrawalRecords } from '@/api/withdrawal'
|
import { createWithdrawal, getWithdrawalRecords } from '@/api/withdrawal'
|
||||||
import type { WithdrawalRecord } from '@/api/withdrawal'
|
import type { WithdrawalRecord } from '@/api/withdrawal'
|
||||||
import SimplePicker from '@/components/SimplePicker.vue'
|
import SimplePicker from '@/components/SimplePicker.vue'
|
||||||
|
|
||||||
// 佣金概览
|
// 佣金概览
|
||||||
const commissionSummary = ref<CommissionSummary | null>(null)
|
const commissionSummary = ref<FundSummary | null>(null)
|
||||||
|
|
||||||
|
// 店铺ID (从登录信息获取)
|
||||||
|
const shopId = ref<number>(0)
|
||||||
|
|
||||||
|
// 从本地存储获取用户信息
|
||||||
|
function getUserInfo() {
|
||||||
|
const userInfo = uni.getStorageSync('user_info')
|
||||||
|
return userInfo || null
|
||||||
|
}
|
||||||
|
|
||||||
// 提现金额(元)
|
// 提现金额(元)
|
||||||
const withdrawAmount = ref('')
|
const withdrawAmount = ref('')
|
||||||
@@ -47,9 +56,9 @@ const statusOptions = [
|
|||||||
|
|
||||||
// 获取当前选中的状态名称
|
// 获取当前选中的状态名称
|
||||||
const selectedStatusName = computed(() => {
|
const selectedStatusName = computed(() => {
|
||||||
if (statusFilter.value === undefined) return '提现状态'
|
if (statusFilter.value === undefined) return '全部状态'
|
||||||
const option = statusOptions.find(o => o.value === statusFilter.value)
|
const option = statusOptions.find(o => o.value === statusFilter.value)
|
||||||
return option?.label || '提现状态'
|
return option?.label || '全部状态'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Tab选项
|
// Tab选项
|
||||||
@@ -136,6 +145,12 @@ function formatTime(time: string) {
|
|||||||
|
|
||||||
// 页面加载
|
// 页面加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 从登录信息获取 shop_id
|
||||||
|
const userInfo = getUserInfo()
|
||||||
|
if (userInfo?.shop_id) {
|
||||||
|
shopId.value = userInfo.shop_id
|
||||||
|
}
|
||||||
|
|
||||||
loadCommissionSummary()
|
loadCommissionSummary()
|
||||||
loadWithdrawRecords()
|
loadWithdrawRecords()
|
||||||
})
|
})
|
||||||
@@ -144,8 +159,9 @@ onMounted(() => {
|
|||||||
async function loadCommissionSummary() {
|
async function loadCommissionSummary() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const summary = await getCommissionSummary()
|
const summary = await getFundSummary()
|
||||||
commissionSummary.value = summary
|
commissionSummary.value = summary
|
||||||
|
withdrawAmount.value = availableAmount.value.toFixed(2)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('加载佣金概览失败:', error)
|
console.error('加载佣金概览失败:', error)
|
||||||
@@ -157,6 +173,8 @@ async function loadCommissionSummary() {
|
|||||||
|
|
||||||
// 加载提现记录
|
// 加载提现记录
|
||||||
async function loadWithdrawRecords() {
|
async function loadWithdrawRecords() {
|
||||||
|
if (!shopId.value) return
|
||||||
|
|
||||||
loadingRecords.value = true
|
loadingRecords.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -170,7 +188,7 @@ async function loadWithdrawRecords() {
|
|||||||
params.status = statusFilter.value
|
params.status = statusFilter.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getWithdrawalRecords(params)
|
const response = await getWithdrawalRecords(shopId.value, params)
|
||||||
|
|
||||||
if (page.value === 1) {
|
if (page.value === 1) {
|
||||||
withdrawRecords.value = response.items || []
|
withdrawRecords.value = response.items || []
|
||||||
@@ -213,39 +231,48 @@ async function handleSubmit() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accountName.value.trim()) {
|
const name = accountName.value.trim()
|
||||||
|
if (!name) {
|
||||||
uni.$u.toast('请输入收款人姓名')
|
uni.$u.toast('请输入收款人姓名')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!/^[\u4e00-\u9fa5]{2,20}$|^[a-zA-Z]{2,20}$/.test(name)) {
|
||||||
|
uni.$u.toast('姓名格式错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!accountNumber.value.trim()) {
|
const account = accountNumber.value.trim()
|
||||||
|
if (!account) {
|
||||||
uni.$u.toast('请输入支付宝账号')
|
uni.$u.toast('请输入支付宝账号')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||||
|
if (!phoneRegex.test(account) && !emailRegex.test(account)) {
|
||||||
|
uni.$u.toast('支付宝账号格式错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await createWithdrawal({
|
const result = await createWithdrawal(shopId.value, {
|
||||||
account_name: accountName.value.trim(),
|
account_name: name,
|
||||||
account_number: accountNumber.value.trim(),
|
account_number: account,
|
||||||
amount: Math.round(amount * 100), // 元转分
|
amount: Math.round(amount * 100),
|
||||||
withdrawal_method: 'alipay',
|
withdrawal_method: 'alipay',
|
||||||
})
|
})
|
||||||
|
|
||||||
uni.$u.toast('提现申请已提交')
|
uni.$u.toast('提现申请已提交')
|
||||||
|
|
||||||
// 清空表单
|
|
||||||
withdrawAmount.value = ''
|
withdrawAmount.value = ''
|
||||||
accountName.value = ''
|
accountName.value = ''
|
||||||
accountNumber.value = ''
|
accountNumber.value = ''
|
||||||
|
|
||||||
// 刷新数据
|
|
||||||
await loadCommissionSummary()
|
await loadCommissionSummary()
|
||||||
page.value = 1
|
page.value = 1
|
||||||
await loadWithdrawRecords()
|
await loadWithdrawRecords()
|
||||||
|
|
||||||
// 切换到记录Tab
|
|
||||||
activeTab.value = 'records'
|
activeTab.value = 'records'
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -284,35 +311,22 @@ function loadMore() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||||
<!-- 可提现金额卡片 -->
|
<view class="bg-brand rounded-12px px-4 py-3 mx-4">
|
||||||
<view class="px-4 pt-4 pb-3">
|
<text class="text-11px text-white/60">可提现金额</text>
|
||||||
<view class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-16px shadow-[0_4px_16px_rgba(255,103,0,0.3)] p-4">
|
<text class="text-24px font-700 text-white block mt-1">¥{{ formattedAvailableAmount }}</text>
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<view>
|
|
||||||
<text class="text-13px text-white/70 block mb-1">
|
|
||||||
可提现金额
|
|
||||||
</text>
|
|
||||||
<text class="text-32px font-700 text-white block">
|
|
||||||
¥{{ formattedAvailableAmount }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<i class="i-mdi-cash-multiple text-48px text-white/20" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- Tab切换 -->
|
<view class="px-4 mt-4">
|
||||||
<view class="px-4 mb-3">
|
<view class="flex rounded-full bg-[var(--bg-container)] p-1">
|
||||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-1 flex">
|
|
||||||
<view
|
<view
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.key"
|
:key="tab.key"
|
||||||
:class="[
|
:class="[
|
||||||
'flex-1 text-center py-2 rounded-8px text-14px transition-all duration-200',
|
'flex-1 text-center py-2 rounded-full text-14px transition-all',
|
||||||
activeTab === tab.key
|
activeTab === tab.key
|
||||||
? 'bg-[#ff6700] text-white font-600'
|
? 'bg-brand text-white font-600'
|
||||||
: 'text-[#666]',
|
: 'text-[var(--text-secondary)]',
|
||||||
]"
|
]"
|
||||||
@click="handleTabChange(tab.key)"
|
@click="handleTabChange(tab.key)"
|
||||||
>
|
>
|
||||||
@@ -321,237 +335,136 @@ function loadMore() {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 申请提现 -->
|
<view v-if="activeTab === 'apply'" class="px-4 mt-4">
|
||||||
<view v-if="activeTab === 'apply'" class="px-4">
|
<view class="bg-[var(--bg-container)] rounded-12px p-4">
|
||||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
<text class="text-12px text-[var(--text-tertiary)]">提现金额</text>
|
||||||
<!-- 提现金额 -->
|
<view class="flex items-center mt-1">
|
||||||
<view class="mb-4">
|
<text class="text-24px text-[var(--text-secondary)] mr-1">¥</text>
|
||||||
<text class="text-13px text-[#666] block mb-2">提现金额</text>
|
|
||||||
<view class="flex items-center border-1px border-[#e0e0e0] rounded-8px px-3 py-2">
|
|
||||||
<text class="text-20px text-[#666] mr-1">¥</text>
|
|
||||||
<input
|
|
||||||
v-model="withdrawAmount"
|
|
||||||
type="digit"
|
|
||||||
class="flex-1 text-20px text-[#212121]"
|
|
||||||
placeholder="0.00"
|
|
||||||
>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 快捷金额 -->
|
|
||||||
<view class="flex items-center gap-2 mt-2">
|
|
||||||
<view
|
|
||||||
v-for="amount in [100, 500, 1000]"
|
|
||||||
:key="amount"
|
|
||||||
class="px-3 py-1 bg-[#f5f5f5] text-[#666] text-12px rounded-6px"
|
|
||||||
@click="setQuickAmount(amount)"
|
|
||||||
>
|
|
||||||
{{ amount }}元
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
class="px-3 py-1 bg-[#fff3e0] text-[#ff6700] text-12px rounded-6px font-600"
|
|
||||||
@click="setAllAmount"
|
|
||||||
>
|
|
||||||
全部提现
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 收款人姓名 -->
|
|
||||||
<view class="mb-4 border-2px border-[#ff6700] rounded-16px p-4 bg-white shadow-[0_2px_12px_rgba(255,103,0,0.15)]">
|
|
||||||
<view class="flex items-center mb-3">
|
|
||||||
<text class="text-14px font-600 text-[#212121] mr-1">收款人姓名</text>
|
|
||||||
<text class="text-12px text-[#ff6700]">*</text>
|
|
||||||
</view>
|
|
||||||
<input
|
<input
|
||||||
v-model="accountName"
|
v-model="withdrawAmount"
|
||||||
class="w-full text-16px text-[#212121] font-500"
|
type="digit"
|
||||||
placeholder="请输入支付宝实名认证的真实姓名"
|
class="flex-1 text-24px text-[var(--text-primary)]"
|
||||||
placeholder-style="color: #ccc"
|
placeholder="0.00"
|
||||||
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex items-center gap-2 mt-3">
|
||||||
<!-- 支付宝账号 -->
|
<view
|
||||||
<view class="mb-4 border-2px border-[#ff6700] rounded-16px p-4 bg-white shadow-[0_2px_12px_rgba(255,103,0,0.15)]">
|
v-for="amount in [100, 500, 1000]"
|
||||||
<view class="flex items-center mb-3">
|
:key="amount"
|
||||||
<text class="text-14px font-600 text-[#212121] mr-1">支付宝账号</text>
|
:class="[
|
||||||
<text class="text-12px text-[#ff6700]">*</text>
|
'px-4 py-1.5 text-13px rounded-full transition-all',
|
||||||
</view>
|
withdrawAmount == amount.toString()
|
||||||
<input
|
? 'bg-brand text-white font-600'
|
||||||
v-model="accountNumber"
|
: 'bg-[var(--bg-muted)] text-[var(--text-secondary)]',
|
||||||
class="w-full text-16px text-[#212121] font-500"
|
]"
|
||||||
placeholder="请输入支付宝账号/手机号/邮箱"
|
@click="setQuickAmount(amount)"
|
||||||
placeholder-style="color: #ccc"
|
|
||||||
>
|
>
|
||||||
</view>
|
{{ amount }}
|
||||||
|
|
||||||
<!-- 费用说明 -->
|
|
||||||
<view class="bg-gradient-to-r from-[#f5f5f5] to-[#fafafa] rounded-12px p-4 mb-4 border-1px border-[#e0e0e0]">
|
|
||||||
<view class="flex items-center justify-between mb-3">
|
|
||||||
<text class="text-13px text-[#666]">提现金额</text>
|
|
||||||
<text class="text-16px font-600 text-[#212121]">¥{{ formattedWithdrawAmount }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="pt-3 border-t-2px border-[#ff6700]">
|
<view
|
||||||
<view class="flex items-center justify-between">
|
:class="[
|
||||||
<view>
|
'px-4 py-1.5 text-13px rounded-full transition-all font-500',
|
||||||
<text class="text-14px font-600 text-[#212121] block">实际到账</text>
|
withdrawAmount == availableAmount.toFixed(2)
|
||||||
<text class="text-11px text-[#52c41a] block mt-0.5">
|
? 'bg-brand text-white'
|
||||||
✓ 无手续费,全额到账
|
: 'bg-[var(--bg-muted)] text-[var(--text-secondary)]',
|
||||||
</text>
|
]"
|
||||||
</view>
|
@click="setAllAmount"
|
||||||
<text class="text-24px font-700 text-[#52c41a]">¥{{ actualAmount }}</text>
|
>
|
||||||
</view>
|
全部
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||||
<view
|
<text class="text-12px text-[var(--text-tertiary)]">收款人姓名</text>
|
||||||
:class="[
|
<input
|
||||||
'w-full py-3 rounded-8px text-center text-15px font-600 transition-all duration-200',
|
v-model="accountName"
|
||||||
submitting ? 'bg-[#ccc] text-white' : 'bg-[#ff6700] text-white',
|
class="w-full text-16px text-[var(--text-primary)] mt-1"
|
||||||
]"
|
placeholder="请输入姓名"
|
||||||
@click="handleSubmit"
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
>
|
>
|
||||||
{{ submitting ? '提交中...' : '提交申请' }}
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 温馨提示 -->
|
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||||
<view class="mt-3 p-4 bg-[#fff3e0] rounded-12px">
|
<text class="text-12px text-[var(--text-tertiary)]">支付宝账号</text>
|
||||||
<view class="flex items-start gap-2">
|
<input
|
||||||
<i class="i-mdi-information text-16px text-[#e65100] mt-0.5" />
|
v-model="accountNumber"
|
||||||
<view class="flex-1">
|
class="w-full text-16px text-[var(--text-primary)] mt-1"
|
||||||
<text class="text-12px font-600 text-[#e65100] block mb-2">温馨提示</text>
|
placeholder="请输入账号"
|
||||||
<text class="text-12px text-[#666] block leading-relaxed">
|
placeholder-class="text-[var(--text-quaternary)]"
|
||||||
1. 目前仅支持支付宝提现<br>
|
>
|
||||||
2. 无手续费,全额到账<br>
|
</view>
|
||||||
3. 收款人姓名需与支付宝实名一致<br>
|
|
||||||
4. 提现申请提交后将在1-3个工作日内处理
|
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||||
</text>
|
<view class="flex items-center justify-between">
|
||||||
</view>
|
<text class="text-14px text-[var(--text-secondary)]">提现金额</text>
|
||||||
</view>
|
<text class="text-20px font-600 text-brand">¥{{ formattedWithdrawAmount }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
:class="[
|
||||||
|
'w-full py-4 rounded-12px text-center text-16px font-600 mt-4',
|
||||||
|
submitting ? 'bg-[var(--bg-muted)] text-[var(--text-quaternary)]' : 'bg-brand text-white',
|
||||||
|
]"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
{{ submitting ? '提交中...' : '确认提现' }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 提现记录 -->
|
<view v-else class="px-4 mt-4">
|
||||||
<view v-else class="px-4">
|
|
||||||
<!-- 筛选条件 -->
|
|
||||||
<view class="flex items-center gap-2 mb-3">
|
<view class="flex items-center gap-2 mb-3">
|
||||||
<!-- 状态筛选 -->
|
<view class="flex-1 bg-[var(--bg-container)] rounded-12px px-4 py-3" @click="showStatusPicker = true">
|
||||||
<view class="flex-1 bg-white rounded-full px-4 py-2 flex items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)]" @click="showStatusPicker = true">
|
<text class="text-14px text-[var(--text-primary)]">{{ selectedStatusName }}</text>
|
||||||
<text class="flex-1 text-14px text-[#212121]">{{ selectedStatusName }}</text>
|
|
||||||
<i class="i-mdi-chevron-down text-18px text-[#999]" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 记录列表 -->
|
|
||||||
<view v-if="loadingRecords && page === 1" class="pt-10 center">
|
<view v-if="loadingRecords && page === 1" class="pt-10 center">
|
||||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="withdrawRecords.length > 0" class="space-y-2 mb-3">
|
<view v-else-if="withdrawRecords.length > 0" class="space-y-3">
|
||||||
<view
|
<view
|
||||||
v-for="record in withdrawRecords"
|
v-for="record in withdrawRecords"
|
||||||
:key="record.id"
|
:key="record.id"
|
||||||
class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4"
|
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||||||
>
|
>
|
||||||
<view class="flex items-center justify-between mb-3">
|
<view class="flex items-center justify-between">
|
||||||
<view class="flex items-center gap-2">
|
<text class="text-13px text-[var(--text-tertiary)]">{{ formatTime(record.created_at) }}</text>
|
||||||
<i class="i-mdi-bank-transfer text-20px text-[#ff6700]" />
|
|
||||||
<text class="text-14px font-600 text-[#212121]">
|
|
||||||
{{ record.withdrawal_no }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view
|
<view
|
||||||
:style="{
|
:style="{
|
||||||
backgroundColor: getStatusColor(record.status).bg,
|
backgroundColor: getStatusColor(record.status).bg,
|
||||||
color: getStatusColor(record.status).text,
|
color: getStatusColor(record.status).text,
|
||||||
}"
|
}"
|
||||||
class="px-2 py-1 rounded-6px text-11px font-600"
|
class="px-3 py-1 rounded-full text-12px font-600"
|
||||||
>
|
>
|
||||||
{{ getStatusText(record.status, record.status_name) }}
|
{{ getStatusText(record.status, record.status_name) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex items-center justify-between mt-3">
|
||||||
<view class="space-y-2">
|
<text class="text-14px text-[var(--text-secondary)]">{{ record.account_name }}</text>
|
||||||
<view class="flex items-center justify-between mb-2">
|
<text class="text-20px font-700 text-brand">-{{ formatAmount(record.amount) }}</text>
|
||||||
<text class="text-13px text-[#666]">提现金额</text>
|
</view>
|
||||||
<text class="text-18px font-700 text-[#212121]">
|
<view v-if="record.status === 3 && record.reject_reason" class="mt-2 pt-2 border-t border-[var(--border-secondary)]">
|
||||||
{{ formatAmount(record.amount) }}
|
<text class="text-12px text-[var(--color-error)]">{{ record.reject_reason }}</text>
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="record.fee > 0" class="flex items-center justify-between">
|
|
||||||
<text class="text-12px text-[#999]">手续费</text>
|
|
||||||
<text class="text-13px text-[#ff6700]">
|
|
||||||
-{{ formatAmount(record.fee) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="flex items-center justify-between pt-2 border-t-1px border-[#f5f5f5]">
|
|
||||||
<view>
|
|
||||||
<text class="text-12px text-[#999] block">实际到账</text>
|
|
||||||
<text v-if="record.fee === 0" class="text-10px text-[#52c41a] block mt-0.5">
|
|
||||||
无手续费
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<text class="text-18px font-700 text-[#52c41a]">
|
|
||||||
{{ formatAmount(record.actual_amount) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="pt-2 border-t-1px border-[#f5f5f5]">
|
|
||||||
<view class="flex items-center justify-between mb-1">
|
|
||||||
<text class="text-11px text-[#999]">收款人</text>
|
|
||||||
<text class="text-12px text-[#212121]">
|
|
||||||
{{ record.account_name }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="flex items-center justify-between mb-1">
|
|
||||||
<text class="text-11px text-[#999]">支付宝账号</text>
|
|
||||||
<text class="text-12px text-[#212121]">
|
|
||||||
{{ record.account_number }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="flex items-center justify-between">
|
|
||||||
<text class="text-11px text-[#999]">申请时间</text>
|
|
||||||
<text class="text-11px text-[#999]">
|
|
||||||
{{ formatTime(record.created_at) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 拒绝原因 -->
|
|
||||||
<view v-if="record.status === 3 && record.reject_reason" class="pt-2 border-t-1px border-[#f5f5f5]">
|
|
||||||
<text class="text-11px text-[#c62828] block">
|
|
||||||
拒绝原因: {{ record.reject_reason }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载更多 -->
|
<view v-if="withdrawRecords.length < total" class="py-4 center">
|
||||||
<view v-if="withdrawRecords.length < total" class="py-3 center">
|
<text v-if="loadingRecords" class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||||
<text v-if="loadingRecords" class="text-12px text-[#999]">
|
<text v-else class="text-14px text-[var(--brand-primary)]" @click="loadMore">加载更多</text>
|
||||||
加载中...
|
|
||||||
</text>
|
|
||||||
<text v-else class="text-12px text-[#ff6700]" @click="loadMore">
|
|
||||||
加载更多
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="pt-10 center flex-col">
|
<view v-else class="pt-16 center">
|
||||||
<i class="i-mdi-file-document-outline text-60px text-[#ddd] mb-3" />
|
<text class="text-14px text-[var(--text-secondary)]">暂无记录</text>
|
||||||
<text class="text-14px text-[#999]">
|
|
||||||
暂无提现记录
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部留白 -->
|
<view class="h-16" />
|
||||||
<view class="h-8" />
|
|
||||||
|
|
||||||
<!-- 状态选择器 -->
|
|
||||||
<SimplePicker
|
<SimplePicker
|
||||||
v-model:show="showStatusPicker"
|
v-model:show="showStatusPicker"
|
||||||
:options="statusOptions"
|
:options="statusOptions"
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ onLoad((options: any) => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background:
|
background:
|
||||||
linear-gradient(90deg, transparent 0%, rgba(255, 103, 0, 0.02) 50%, transparent 100%);
|
linear-gradient(90deg, transparent 0%, rgba(22, 119, 255, 0.02) 50%, transparent 100%);
|
||||||
animation: scanline 8s linear infinite;
|
animation: scanline 8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ onLoad((options: any) => {
|
|||||||
height: 800rpx;
|
height: 800rpx;
|
||||||
top: -300rpx;
|
top: -300rpx;
|
||||||
right: -200rpx;
|
right: -200rpx;
|
||||||
background: radial-gradient(circle, rgba(255, 103, 0, 0.08), transparent 60%);
|
background: radial-gradient(circle, rgba(22, 119, 255, 0.08), transparent 60%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: pulse1 10s infinite ease-in-out;
|
animation: pulse1 10s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ onLoad((options: any) => {
|
|||||||
height: 600rpx;
|
height: 600rpx;
|
||||||
bottom: -200rpx;
|
bottom: -200rpx;
|
||||||
left: -150rpx;
|
left: -150rpx;
|
||||||
background: radial-gradient(circle, rgba(255, 103, 0, 0.06), transparent 60%);
|
background: radial-gradient(circle, rgba(22, 119, 255, 0.06), transparent 60%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: pulse2 12s infinite ease-in-out;
|
animation: pulse2 12s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ onLoad((options: any) => {
|
|||||||
height: 400rpx;
|
height: 400rpx;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: -100rpx;
|
right: -100rpx;
|
||||||
background: radial-gradient(circle, rgba(255, 103, 0, 0.04), transparent 60%);
|
background: radial-gradient(circle, rgba(22, 119, 255, 0.04), transparent 60%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: pulse3 14s infinite ease-in-out;
|
animation: pulse3 14s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
@@ -384,8 +384,8 @@ onLoad((options: any) => {
|
|||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-color: #d4af37;
|
border-color: var(--brand-primary);
|
||||||
box-shadow: 0 0 0 4rpx rgba(212, 175, 55, 0.08);
|
box-shadow: 0 0 0 4rpx rgba(22, 119, 255, 0.08);
|
||||||
transform: translateY(-2rpx);
|
transform: translateY(-2rpx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/static/icons/佣金中心.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
src/static/icons/修改密码.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
src/static/icons/授权卡列表.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
src/static/icons/授权设备列表.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
src/static/icons/提现管理.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
src/static/icons/资产列表.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
src/static/icons/退出登录.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
@@ -5,8 +5,10 @@
|
|||||||
|
|
||||||
:root,
|
:root,
|
||||||
page {
|
page {
|
||||||
/* === 品牌色 Brand Colors - 只保留3个核心颜色 === */
|
/* === 品牌色 Brand Colors === */
|
||||||
--brand-primary: #d4af37; /* 主色 - 金色 */
|
--brand-primary: #1677ff; /* 主色 - 蓝色 */
|
||||||
|
--brand-primary-light: #4096ff; /* 主色浅 */
|
||||||
|
--brand-primary-dark: #0958d9; /* 主色深 */
|
||||||
--brand-secondary: #1e293b; /* 辅色 - 深灰/黑色 */
|
--brand-secondary: #1e293b; /* 辅色 - 深灰/黑色 */
|
||||||
--brand-accent: #ffffff; /* 强调 - 白色 */
|
--brand-accent: #ffffff; /* 强调 - 白色 */
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ page {
|
|||||||
--color-warning-bg: #fffbe6; /* 警告背景 */
|
--color-warning-bg: #fffbe6; /* 警告背景 */
|
||||||
--color-error: #ff4d4f; /* 错误 - Red */
|
--color-error: #ff4d4f; /* 错误 - Red */
|
||||||
--color-error-bg: #fff2f0; /* 错误背景 */
|
--color-error-bg: #fff2f0; /* 错误背景 */
|
||||||
--color-info: #1890ff; /* 信息 - Blue */
|
--color-info: #1677ff; /* 信息 - Blue(同主色) */
|
||||||
--color-info-bg: #e6f7ff; /* 信息背景 */
|
--color-info-bg: #e6f7ff; /* 信息背景 */
|
||||||
|
|
||||||
/* === 文字色阶 Text Colors === */
|
/* === 文字色阶 Text Colors === */
|
||||||
@@ -44,7 +46,7 @@ page {
|
|||||||
--shadow-md: 0 2px 8px 0 rgba(0, 0, 0, 0.08);
|
--shadow-md: 0 2px 8px 0 rgba(0, 0, 0, 0.08);
|
||||||
--shadow-lg: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
|
--shadow-lg: 0 4px 16px 0 rgba(0, 0, 0, 0.12);
|
||||||
--shadow-xl: 0 8px 24px 0 rgba(0, 0, 0, 0.15);
|
--shadow-xl: 0 8px 24px 0 rgba(0, 0, 0, 0.15);
|
||||||
--shadow-brand: 0 4px 12px 0 rgba(255, 103, 0, 0.2);
|
--shadow-brand: 0 4px 12px 0 rgba(22, 119, 255, 0.2);
|
||||||
|
|
||||||
/* === 圆角 Radius === */
|
/* === 圆角 Radius === */
|
||||||
--radius-xs: 4px;
|
--radius-xs: 4px;
|
||||||
@@ -98,11 +100,13 @@ page {
|
|||||||
|
|
||||||
/* === 暗色主题 Dark Theme === */
|
/* === 暗色主题 Dark Theme === */
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
--brand-primary: #ff8533; /* 暗色下橙色更柔和 */
|
--brand-primary: #4b9cff; /* 暗色下蓝色更柔和 */
|
||||||
|
--brand-primary-light: #69b1ff;
|
||||||
|
--brand-primary-dark: #1668dc;
|
||||||
--color-success: #73d13d;
|
--color-success: #73d13d;
|
||||||
--color-warning: #ffc53d;
|
--color-warning: #ffc53d;
|
||||||
--color-error: #ff7875;
|
--color-error: #ff7875;
|
||||||
--color-info: #40a9ff;
|
--color-info: #4b9cff;
|
||||||
|
|
||||||
--text-primary: #ffffff;
|
--text-primary: #ffffff;
|
||||||
--text-secondary: #b0b8c1;
|
--text-secondary: #b0b8c1;
|
||||||
@@ -150,6 +154,14 @@ page {
|
|||||||
.bg-muted { background-color: var(--bg-muted); }
|
.bg-muted { background-color: var(--bg-muted); }
|
||||||
.bg-brand { background-color: var(--brand-primary); }
|
.bg-brand { background-color: var(--brand-primary); }
|
||||||
.bg-brand-light { background-color: var(--brand-primary-light); }
|
.bg-brand-light { background-color: var(--brand-primary-light); }
|
||||||
|
.bg-brand-dark { background-color: var(--brand-primary-dark); }
|
||||||
|
|
||||||
|
.bg-gradient-brand { background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-light)); }
|
||||||
|
.shadow-brand-colored { box-shadow: 0 4px 16px rgba(22, 119, 255, 0.3); }
|
||||||
|
.shadow-brand-sm { box-shadow: 0 2px 12px rgba(22, 119, 255, 0.15); }
|
||||||
|
.shadow-brand-xs { box-shadow: 0 2px 8px rgba(22, 119, 255, 0.2); }
|
||||||
|
|
||||||
|
.border-brand { border-color: var(--brand-primary); }
|
||||||
|
|
||||||
.border-primary { border-color: var(--border-primary); }
|
.border-primary { border-color: var(--border-primary); }
|
||||||
.border-secondary { border-color: var(--border-secondary); }
|
.border-secondary { border-color: var(--border-secondary); }
|
||||||
@@ -229,7 +241,7 @@ page {
|
|||||||
.input:focus {
|
.input:focus {
|
||||||
border-color: var(--brand-primary);
|
border-color: var(--brand-primary);
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px rgba(255, 103, 0, 0.1);
|
box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ export function post<T = any>(url: string, config?: HttpRequestConfig): Promise<
|
|||||||
return request({ ...config, url, method: 'POST' });
|
return request({ ...config, url, method: 'POST' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function put<T = any>(url: string, config?: HttpRequestConfig): Promise<T> {
|
||||||
|
return request({ ...config, url, method: 'PUT' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patch<T = any>(url: string, config?: HttpRequestConfig): Promise<T> {
|
||||||
|
return request({ ...config, url, method: 'PATCH' });
|
||||||
|
}
|
||||||
|
|
||||||
export function upload<T = any>(url: string, config?: HttpRequestConfig): Promise<T> {
|
export function upload<T = any>(url: string, config?: HttpRequestConfig): Promise<T> {
|
||||||
return request({ ...config, url, method: 'UPLOAD' });
|
return request({ ...config, url, method: 'UPLOAD' });
|
||||||
}
|
}
|
||||||
|
|||||||