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
|
||||
|
||||
### Brand Colors (Xiaomi-Inspired)
|
||||
### Brand Colors (Blue Theme)
|
||||
|
||||
```scss
|
||||
// Primary Brand Colors
|
||||
--brand-orange: #ff6700; // Primary CTA, hover states
|
||||
--brand-orange-light: #ff8533; // Lighter variant
|
||||
--brand-orange-dark: #e65a00; // Pressed states
|
||||
--brand-gray: #333333; // Headings, important text
|
||||
--brand-gray-light: #757575; // Secondary text
|
||||
--brand-primary: #1677ff; // Primary CTA, hover states
|
||||
--brand-primary-light: #4096ff; // Lighter variant
|
||||
--brand-primary-dark: #0958d9; // Pressed states
|
||||
--brand-secondary: #1e293b; // Dark Gray (headings)
|
||||
--brand-accent: #ffffff; // White (emphasis)
|
||||
```
|
||||
|
||||
### Semantic Colors (Light Mode)
|
||||
|
||||
```scss
|
||||
// Primary Theme Colors
|
||||
--theme-primary: #ff6700; // Orange (Trust + Energy)
|
||||
--theme-secondary: #ff8533; // Lighter Orange
|
||||
--theme-accent: #333333; // Dark Gray
|
||||
--theme-primary: #1677ff; // Blue (Trust + Energy)
|
||||
--theme-secondary: #1e293b; // Dark Gray
|
||||
--theme-accent: #ffffff; // White
|
||||
|
||||
// Status Colors
|
||||
--theme-success: #3ed268; // Green (Success states)
|
||||
--theme-warning: #e68815; // Amber (Warnings)
|
||||
--theme-error: #d31638; // Red (Errors)
|
||||
--theme-success: #52c41a; // Green (Success states)
|
||||
--theme-warning: #faad14; // Amber (Warnings)
|
||||
--theme-error: #ff4d4f; // Red (Errors)
|
||||
--theme-info: #1677ff; // Info (same as primary)
|
||||
|
||||
// Text Hierarchy
|
||||
--theme-main-color: #4c484f; // Primary text (slate-900 equivalent)
|
||||
--theme-content-color: #6b6569; // Body text (slate-700)
|
||||
--theme-tips-color: #8a8486; // Secondary text (slate-500)
|
||||
--theme-light-color: #bd9e84; // Muted text (beige)
|
||||
--theme-disabled-color: #dce0eb; // Disabled states
|
||||
--theme-main-color: #212121; // Primary text
|
||||
--theme-content-color: #666666; // Body text
|
||||
--theme-tips-color: #999999; // Secondary text
|
||||
--theme-light-color: #cccccc; // Muted text
|
||||
--theme-disabled-color: #f5f5f5; // Disabled states
|
||||
|
||||
// Backgrounds
|
||||
--theme-bg-color: #ffffff; // Main background
|
||||
--theme-bg-color-secondary: #faf8f7; // Card backgrounds
|
||||
--theme-bg-gradient-start: #c5dfdf;
|
||||
--theme-bg-gradient-end: #bd9e84;
|
||||
--theme-bg-color: #fafafa; // Main background
|
||||
--theme-bg-color-secondary: #ffffff; // Card backgrounds
|
||||
--theme-bg-gradient-start: #1677ff;
|
||||
--theme-bg-gradient-end: #4096ff;
|
||||
|
||||
// Borders
|
||||
--theme-border-color: #c5dfdf; // Borders, dividers
|
||||
--theme-border-color: #e8e8e8; // Borders, dividers
|
||||
```
|
||||
|
||||
### Dark Mode Colors
|
||||
|
||||
```scss
|
||||
.theme-dark {
|
||||
--theme-primary: #3c9cff; // Blue (better for dark mode)
|
||||
--theme-success: #5ac725;
|
||||
--theme-warning: #f9ae3d;
|
||||
--brand-primary: #4b9cff; // Blue (lighter for dark mode)
|
||||
--brand-primary-light: #69b1ff;
|
||||
--brand-primary-dark: #1668dc;
|
||||
--theme-success: #73d13d;
|
||||
--theme-warning: #ffc53d;
|
||||
--theme-error: #f56c6c;
|
||||
|
||||
// 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
|
||||
*/
|
||||
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 {
|
||||
id: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作参数 (新版,使用 identifier)
|
||||
*/
|
||||
export interface AssetOperationParams {
|
||||
identifier: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 资产解析 (智能识别)
|
||||
* 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) {
|
||||
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) {
|
||||
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) {
|
||||
return get<DeviceInfo>(`/api/admin/devices/${id}`)
|
||||
export function getDeviceDetail(virtualNo: string) {
|
||||
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) {
|
||||
return get<AssetRealtimeStatus>(`/api/admin/assets/${assetType}/${id}/realtime-status`)
|
||||
export function getAssetRealtimeStatus(identifier: string) {
|
||||
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<{
|
||||
items: AssetPackage[]
|
||||
page: number
|
||||
page_size: 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) {
|
||||
return get<AssetPackage>(`/api/admin/assets/${assetType}/${id}/current-package`)
|
||||
export function getCurrentPackage(identifier: string) {
|
||||
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<{
|
||||
list: HistoryRecord[]
|
||||
total: number
|
||||
page: 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) {
|
||||
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) {
|
||||
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) {
|
||||
return post<void>(`/api/admin/devices/${data.id}/stop`, {
|
||||
data: { reason: data.reason },
|
||||
export function stopDevice(virtualNo: string, reason?: string) {
|
||||
return post<void>(`/api/admin/devices/${virtualNo}/stop`, {
|
||||
data: { reason },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用设备
|
||||
* POST /api/admin/devices/:id/start
|
||||
* POST /api/admin/devices/:virtual_no/start
|
||||
* @param virtualNo 设备虚拟号
|
||||
*/
|
||||
export function startDevice(data: OperationParams) {
|
||||
return post<void>(`/api/admin/devices/${data.id}/start`, {
|
||||
data: { reason: data.reason },
|
||||
export function startDevice(virtualNo: string, reason?: string) {
|
||||
return post<void>(`/api/admin/devices/${virtualNo}/start`, {
|
||||
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) {
|
||||
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) {
|
||||
return post<void>(`/api/admin/assets/device/${deviceId}/start`)
|
||||
@@ -383,7 +400,7 @@ export function startDeviceAsset(deviceId: number) {
|
||||
|
||||
/**
|
||||
* 单卡停机 (通过ICCID)
|
||||
* POST /api/admin/assets/card/:iccid/stop
|
||||
* @deprecated 使用 stopAsset 代替,路由: POST /api/admin/assets/:identifier/stop
|
||||
*/
|
||||
export function stopCardByIccid(iccid: string) {
|
||||
return post<void>(`/api/admin/assets/card/${iccid}/stop`)
|
||||
@@ -391,8 +408,135 @@ export function stopCardByIccid(iccid: string) {
|
||||
|
||||
/**
|
||||
* 单卡复机 (通过ICCID)
|
||||
* POST /api/admin/assets/card/:iccid/start
|
||||
* @deprecated 使用 startAsset 代替,路由: POST /api/admin/assets/:identifier/start
|
||||
*/
|
||||
export function startCardByIccid(iccid: string) {
|
||||
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'
|
||||
|
||||
/**
|
||||
* 佣金概览
|
||||
* 资金概览 (代理端主页用)
|
||||
* 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 {
|
||||
shop_id: number
|
||||
@@ -67,24 +89,47 @@ export interface CommissionRecordsParams {
|
||||
}
|
||||
|
||||
/**
|
||||
* 佣金统计查询参数
|
||||
* 佣金统计查询参数 (新接口,shop_id 在路径中)
|
||||
*/
|
||||
export interface CommissionStatsParams {
|
||||
shop_id?: number
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 每日佣金统计查询参数
|
||||
* 每日佣金统计查询参数 (新接口,shop_id 在路径中)
|
||||
*/
|
||||
export interface DailyCommissionStatsParams {
|
||||
shop_id?: number
|
||||
start_date?: string // YYYY-MM-DD
|
||||
end_date?: string // YYYY-MM-DD
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资金概览 (代理端主页用)
|
||||
* 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() {
|
||||
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) {
|
||||
return get<ListResponse<CommissionRecord>>('/api/admin/my/commission-records', { params })
|
||||
export function getCommissionRecords(shopId: number, params?: CommissionRecordsParams) {
|
||||
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) {
|
||||
return get<CommissionStats>('/api/admin/my/commission-stats', { params })
|
||||
export function getCommissionStats(shopId: number, params?: CommissionStatsParams) {
|
||||
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) {
|
||||
return get<DailyCommissionStats[]>('/api/admin/my/commission-daily-stats', { params })
|
||||
export function getCommissionDailyStats(shopId: number, params?: DailyCommissionStatsParams) {
|
||||
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) {
|
||||
return post<WithdrawalCreateResponse>('/api/admin/my/withdrawal-requests', { data })
|
||||
export function createWithdrawal(shopId: number, data: WithdrawalCreateParams) {
|
||||
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) {
|
||||
return get<ListResponse<WithdrawalRecord>>('/api/admin/my/withdrawal-requests', { params })
|
||||
export function getWithdrawalRecords(shopId: number, params?: WithdrawalListParams) {
|
||||
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 {
|
||||
color: var(--brand-color, #ff6700);
|
||||
color: var(--brand-primary, #1677ff);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,27 +2,35 @@
|
||||
<view class="skeleton">
|
||||
<view v-if="type === 'home'" class="skeleton-home">
|
||||
<!-- 顶部用户信息骨架 -->
|
||||
<view class="safe-area-top bg-white px-4 pt-4 pb-4">
|
||||
<view class="flex items-center justify-between">
|
||||
<view>
|
||||
<view class="skeleton-block h-6 w-32 mb-2" />
|
||||
<view class="skeleton-block h-4 w-24" />
|
||||
<view class="px-4 pt-4">
|
||||
<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="skeleton-circle w-56px h-56px mr-4" />
|
||||
<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 class="skeleton-circle w-12 h-12" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据卡片骨架 -->
|
||||
<view class="px-4 pt-4">
|
||||
<view class="bg-white rounded-2xl p-5">
|
||||
<view class="grid grid-cols-2 gap-6">
|
||||
<view class="skeleton-block h-5 w-24 mb-3" />
|
||||
<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 class="skeleton-block h-3 w-16 mb-2" />
|
||||
<view class="skeleton-block h-8 w-24" />
|
||||
<view class="skeleton-block h-3 w-20 mb-1" />
|
||||
<view class="skeleton-block h-5 w-24" />
|
||||
</view>
|
||||
<view>
|
||||
<view class="skeleton-block h-3 w-16 mb-2" />
|
||||
<view class="skeleton-block h-8 w-24" />
|
||||
<view class="skeleton-block h-3 w-16 mb-1" />
|
||||
<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>
|
||||
@@ -30,8 +38,8 @@
|
||||
|
||||
<!-- 快捷功能骨架 -->
|
||||
<view class="px-4 pt-6 pb-4">
|
||||
<view class="skeleton-block h-4 w-16 mb-3" />
|
||||
<view class="bg-white rounded-2xl overflow-hidden">
|
||||
<view class="skeleton-block h-5 w-24 mb-3" />
|
||||
<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 class="skeleton-circle w-10 h-10 mr-3" />
|
||||
<view class="flex-1">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
|
||||
// 资产类型
|
||||
const assetType = ref<AssetType>('card')
|
||||
|
||||
// 资产ID
|
||||
const assetId = ref('')
|
||||
// 资产标识符 (ICCID 或 VirtualNo)
|
||||
const assetIdentifier = ref('')
|
||||
|
||||
// 资产详情数据
|
||||
const assetDetail = ref<CardInfo | DeviceInfo | null>(null)
|
||||
@@ -57,11 +57,11 @@ const statusColor = computed(() => {
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
// 从路由参数获取 assetType 和 assetId
|
||||
// 从路由参数获取 assetType 和 assetIdentifier
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1] as any
|
||||
assetType.value = currentPage.options.type as AssetType
|
||||
assetId.value = currentPage.options.id
|
||||
assetIdentifier.value = currentPage.options.id
|
||||
|
||||
loadAssetDetail()
|
||||
})
|
||||
@@ -120,7 +120,7 @@ async function loadPackages() {
|
||||
try {
|
||||
// 1. 调用当前生效套餐接口
|
||||
try {
|
||||
const current = await getCurrentPackage(assetType.value, Number(assetId.value))
|
||||
const current = await getCurrentPackage(assetIdentifier.value)
|
||||
currentPackage.value = current
|
||||
} catch (error) {
|
||||
console.warn('获取当前套餐失败:', error)
|
||||
@@ -128,7 +128,7 @@ async function loadPackages() {
|
||||
}
|
||||
|
||||
// 2. 调用套餐列表接口
|
||||
const packagesData = await getAssetPackages(assetType.value, Number(assetId.value), {
|
||||
const packagesData = await getAssetPackages(assetIdentifier.value, {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
})
|
||||
@@ -147,7 +147,7 @@ async function loadPackages() {
|
||||
// 加载历史记录
|
||||
async function loadHistory() {
|
||||
try {
|
||||
const historyData = await getAssetHistory(assetType.value, assetId.value, {
|
||||
const historyData = await getAssetHistory(assetIdentifier.value, {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
@@ -193,7 +193,7 @@ async function handleTabChange(tab: 'info' | 'package' | 'history') {
|
||||
}
|
||||
|
||||
// 停机
|
||||
async function stopAsset() {
|
||||
async function handleStopAsset() {
|
||||
uni.showModal({
|
||||
title: '确认停机',
|
||||
content: '停机后将无法使用网络服务,确定要停机吗?',
|
||||
@@ -201,13 +201,8 @@ async function stopAsset() {
|
||||
if (res.confirm) {
|
||||
operating.value = true
|
||||
try {
|
||||
// 调用停机接口
|
||||
if (assetType.value === 'card') {
|
||||
await stopCard({ id: assetId.value })
|
||||
}
|
||||
else {
|
||||
await stopDevice({ id: assetId.value })
|
||||
}
|
||||
// 调用统一停机接口
|
||||
await stopAsset(assetIdentifier.value)
|
||||
|
||||
uni.showToast({
|
||||
title: '停机成功',
|
||||
@@ -232,7 +227,7 @@ async function stopAsset() {
|
||||
}
|
||||
|
||||
// 复机
|
||||
async function startAsset() {
|
||||
async function handleStartAsset() {
|
||||
uni.showModal({
|
||||
title: '确认复机',
|
||||
content: '复机后将恢复网络服务,确定要复机吗?',
|
||||
@@ -240,13 +235,8 @@ async function startAsset() {
|
||||
if (res.confirm) {
|
||||
operating.value = true
|
||||
try {
|
||||
// 调用复机接口
|
||||
if (assetType.value === 'card') {
|
||||
await startCard({ id: assetId.value })
|
||||
}
|
||||
else {
|
||||
await startDevice({ id: assetId.value })
|
||||
}
|
||||
// 调用统一复机接口
|
||||
await startAsset(assetIdentifier.value)
|
||||
|
||||
uni.showToast({
|
||||
title: '复机成功',
|
||||
@@ -274,7 +264,7 @@ async function startAsset() {
|
||||
function viewWallet() {
|
||||
// 跳转到钱包页面
|
||||
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>
|
||||
@@ -459,7 +449,7 @@ function viewWallet() {
|
||||
<text class="text-16px font-700 text-[#212121] block mb-2">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</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
|
||||
</text>
|
||||
|
||||
@@ -546,7 +536,7 @@ function viewWallet() {
|
||||
transition-all duration-200
|
||||
active:opacity-80
|
||||
"
|
||||
@click="stopAsset"
|
||||
@click="handleStopAsset"
|
||||
>
|
||||
<i class="i-mdi-pause-circle text-18px text-[#c62828] mr-1" />
|
||||
<text class="text-15px font-600 text-[#c62828]">
|
||||
@@ -562,7 +552,7 @@ function viewWallet() {
|
||||
transition-all duration-200
|
||||
active:opacity-80
|
||||
"
|
||||
@click="startAsset"
|
||||
@click="handleStartAsset"
|
||||
>
|
||||
<i class="i-mdi-play-circle text-18px text-[#2e7d32] mr-1" />
|
||||
<text class="text-15px font-600 text-[#2e7d32]">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// 套餐列表
|
||||
const packageList = ref<AssetPackage[]>([])
|
||||
// 是否展开套餐列表
|
||||
const showPackageList = ref(false)
|
||||
const showPackageList = ref(true)
|
||||
|
||||
// 是否为卡片
|
||||
const isCard = computed(() => assetType.value === 'card')
|
||||
@@ -39,10 +39,11 @@
|
||||
assetType.value = result.asset_type
|
||||
assetInfo.value = result
|
||||
|
||||
// 加载实时状态和当前套餐
|
||||
if (result.asset_type && result.asset_id) {
|
||||
loadRealtimeStatus(result.asset_type, result.asset_id)
|
||||
loadCurrentPackage(result.asset_type, result.asset_id)
|
||||
// 加载实时状态和当前套餐 (使用 identifier)
|
||||
if (searchKeyword.value.trim()) {
|
||||
loadRealtimeStatus(searchKeyword.value.trim())
|
||||
loadCurrentPackage(searchKeyword.value.trim())
|
||||
loadPackageList(searchKeyword.value.trim())
|
||||
}
|
||||
} catch (error : any) {
|
||||
console.error('查询失败:', error)
|
||||
@@ -53,9 +54,9 @@
|
||||
}
|
||||
|
||||
// 加载实时状态
|
||||
async function loadRealtimeStatus(type : 'card' | 'device', id : number) {
|
||||
async function loadRealtimeStatus(identifier : string) {
|
||||
try {
|
||||
const status = await getAssetRealtimeStatus(type, id)
|
||||
const status = await getAssetRealtimeStatus(identifier)
|
||||
realtimeStatus.value = status
|
||||
} catch (error : any) {
|
||||
console.error('加载实时状态失败:', error)
|
||||
@@ -63,10 +64,10 @@
|
||||
}
|
||||
|
||||
// 加载当前套餐
|
||||
async function loadCurrentPackage(type : 'card' | 'device', id : number) {
|
||||
async function loadCurrentPackage(identifier : string) {
|
||||
try {
|
||||
console.log('开始加载当前套餐:', type, id)
|
||||
const pkg = await getCurrentPackage(type, id)
|
||||
console.log('开始加载当前套餐:', identifier)
|
||||
const pkg = await getCurrentPackage(identifier)
|
||||
console.log('当前套餐数据:', pkg)
|
||||
currentPackage.value = pkg
|
||||
} catch (error : any) {
|
||||
@@ -77,10 +78,10 @@
|
||||
}
|
||||
|
||||
// 加载套餐列表
|
||||
async function loadPackageList(type : 'card' | 'device', id : number) {
|
||||
async function loadPackageList(identifier : string) {
|
||||
try {
|
||||
console.log('开始加载套餐列表:', type, id)
|
||||
const response = await getAssetPackages(type, id, { page: 1, page_size: 50 })
|
||||
console.log('开始加载套餐列表:', identifier)
|
||||
const response = await getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||
console.log('套餐列表数据:', response)
|
||||
packageList.value = response?.items || []
|
||||
} catch (error : any) {
|
||||
@@ -93,8 +94,8 @@
|
||||
async function togglePackageList() {
|
||||
showPackageList.value = !showPackageList.value
|
||||
// 第一次展开时加载套餐列表
|
||||
if (showPackageList.value && packageList.value.length === 0 && assetType.value && assetInfo.value?.asset_id) {
|
||||
await loadPackageList(assetType.value, assetInfo.value.asset_id)
|
||||
if (showPackageList.value && packageList.value.length === 0 && searchKeyword.value.trim()) {
|
||||
await loadPackageList(searchKeyword.value.trim())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,9 +326,9 @@
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 电话号码 -->
|
||||
<!-- msisdn -->
|
||||
<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">
|
||||
{{ cardInfo.msisdn }}
|
||||
</text>
|
||||
@@ -376,7 +377,7 @@
|
||||
<!-- 累计充值 -->
|
||||
<view v-if="cardInfo.accumulated_recharge !== undefined" 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">
|
||||
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
|
||||
¥{{ cardInfo.accumulated_recharge }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -424,7 +425,7 @@
|
||||
<!-- 本月用量 -->
|
||||
<view class="flex items-center justify-between">
|
||||
<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
|
||||
? formatDataSize(realtimeStatus.current_month_usage_mb)
|
||||
@@ -495,7 +496,7 @@
|
||||
<view class="flex items-center gap-2">
|
||||
<i :class="[
|
||||
'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]">
|
||||
{{ currentPackage.package_name }}
|
||||
@@ -564,7 +565,7 @@
|
||||
<view class="flex items-center gap-1.5">
|
||||
<i :class="[
|
||||
'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 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">
|
||||
<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 }} 张
|
||||
</text>
|
||||
</view>
|
||||
@@ -753,7 +754,7 @@
|
||||
<!-- 累计充值 -->
|
||||
<view v-if="deviceInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
|
||||
<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 }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -876,7 +877,7 @@
|
||||
<!-- 今日用量 -->
|
||||
<view v-if="realtimeStatus.device_realtime.daily_usage" class="flex items-center justify-between text-11px">
|
||||
<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>
|
||||
|
||||
<!-- 下载流量 -->
|
||||
@@ -1017,7 +1018,7 @@
|
||||
<view class="flex items-center gap-2">
|
||||
<i :class="[
|
||||
'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]">
|
||||
{{ currentPackage.package_name }}
|
||||
@@ -1086,7 +1087,7 @@
|
||||
<view class="flex items-center gap-1.5">
|
||||
<i :class="[
|
||||
'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 v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getAssetWallet } from '@/api/wallet'
|
||||
import type { AssetWalletInfo, AssetType } from '@/api/wallet'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getAssetWallet } from '@/api/wallet'
|
||||
import type { AssetWalletInfo, AssetType } from '@/api/wallet'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
// 资产类型和ID (从URL参数获取)
|
||||
const assetType = ref<AssetType>('card')
|
||||
const assetId = ref<number>(0)
|
||||
// 资产类型和ID (从URL参数获取)
|
||||
const assetType = ref<AssetType>('card')
|
||||
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) {
|
||||
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
|
||||
// 格式化金额(分转元)
|
||||
function formatAmount(amount : number) {
|
||||
return `¥${(amount / 100).toFixed(2)}`
|
||||
}
|
||||
|
||||
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)
|
||||
walletInfo.value = wallet
|
||||
// 获取钱包状态颜色
|
||||
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' }
|
||||
}
|
||||
catch (error) {
|
||||
console.error('加载资产钱包信息失败:', error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
// 格式化时间
|
||||
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
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -106,7 +106,8 @@ function goBack() {
|
||||
<!-- 钱包信息 -->
|
||||
<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>
|
||||
<text class="text-13px text-white/70 block mb-1">
|
||||
@@ -116,13 +117,10 @@ function goBack() {
|
||||
{{ formatAmount(walletInfo.available_balance) }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
:style="{
|
||||
<view :style="{
|
||||
backgroundColor: getStatusColor(walletInfo.status).bg,
|
||||
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) }}
|
||||
</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="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>
|
||||
@@ -227,26 +225,27 @@ function goBack() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.space-y-2 > view:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -5,8 +5,7 @@ import type { UserInfo } from '@/api/auth'
|
||||
import { getEnterpriseCards, getEnterpriseDevices } from '@/api/enterprise'
|
||||
import { getCarriers } from '@/api/carrier'
|
||||
import type { CarrierInfo } from '@/api/carrier'
|
||||
import { stopDeviceAsset, startDeviceAsset, stopCardByIccid, startCardByIccid } from '@/api/assets'
|
||||
import type { DeviceStopResponse } from '@/api/assets'
|
||||
import { stopAsset, startAsset } from '@/api/assets'
|
||||
import Skeleton from '@/components/Skeleton.vue'
|
||||
import SimplePicker from '@/components/SimplePicker.vue'
|
||||
import { formatTime, formatDataSize } from '@/utils/format'
|
||||
@@ -404,10 +403,9 @@ async function handleStopDevice(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const device = asset.raw as DeviceInfo
|
||||
const deviceId = device.id || device.device_id
|
||||
|
||||
if (!deviceId) {
|
||||
uni.$u.toast('设备ID不存在')
|
||||
if (!device.virtual_no) {
|
||||
uni.$u.toast('设备虚拟号不存在')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -418,14 +416,13 @@ async function handleStopDevice(asset: AssetItem, event: Event) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '停机中...' })
|
||||
try {
|
||||
const result = await stopDeviceAsset(Number(deviceId))
|
||||
const result = await stopAsset(device.virtual_no)
|
||||
uni.hideLoading()
|
||||
|
||||
// 显示停机结果
|
||||
const msg = `停机完成\n成功: ${result.success_count}张\n失败: ${result.fail_count}张\n跳过: ${result.skip_count}张`
|
||||
// 显示停机结果 (统一接口不返回详情,仅提示成功)
|
||||
uni.showModal({
|
||||
title: '停机结果',
|
||||
content: msg,
|
||||
title: '停机成功',
|
||||
content: '设备已停机',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
// 刷新列表
|
||||
@@ -447,10 +444,9 @@ async function handleStartDevice(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const device = asset.raw as DeviceInfo
|
||||
const deviceId = device.id || device.device_id
|
||||
|
||||
if (!deviceId) {
|
||||
uni.$u.toast('设备ID不存在')
|
||||
if (!device.virtual_no) {
|
||||
uni.$u.toast('设备虚拟号不存在')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -461,7 +457,7 @@ async function handleStartDevice(asset: AssetItem, event: Event) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '复机中...' })
|
||||
try {
|
||||
await startDeviceAsset(Number(deviceId))
|
||||
await startAsset(device.virtual_no)
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('复机成功')
|
||||
// 刷新列表
|
||||
@@ -494,7 +490,7 @@ async function handleStopCard(asset: AssetItem, event: Event) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '停机中...' })
|
||||
try {
|
||||
await stopCardByIccid(card.iccid)
|
||||
await stopAsset(card.iccid)
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('停机成功')
|
||||
// 刷新列表
|
||||
@@ -527,7 +523,7 @@ async function handleStartCard(asset: AssetItem, event: Event) {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '复机中...' })
|
||||
try {
|
||||
await startCardByIccid(card.iccid)
|
||||
await startAsset(card.iccid)
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('复机成功')
|
||||
// 刷新列表
|
||||
@@ -682,135 +678,124 @@ onMounted(async () => {
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<view v-if="assetList.length > 0" class="px-4 pb-4">
|
||||
<view
|
||||
v-for="asset in assetList"
|
||||
:key="`${asset.type}-${asset.id}`"
|
||||
class="card shadow-sm mb-3 cursor-pointer transition-fast hover:shadow-md active:scale-98"
|
||||
@click="viewAssetDetail(asset)"
|
||||
>
|
||||
<!-- 头部区域 -->
|
||||
<view class="flex items-start justify-between mb-3">
|
||||
<!-- 左侧信息 -->
|
||||
<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"
|
||||
:style="{ backgroundColor: asset.statusColor }"
|
||||
/>
|
||||
|
||||
<!-- 资产名称 -->
|
||||
<view class="flex-1 min-w-0">
|
||||
<text class="text-base font-600 text-primary block truncate">
|
||||
<view
|
||||
v-for="asset in assetList"
|
||||
:key="`${asset.type}-${asset.id}`"
|
||||
class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3 cursor-pointer"
|
||||
@click="viewAssetDetail(asset)"
|
||||
>
|
||||
<!-- 顶部区域:左侧状态+名称,右侧类型标签 -->
|
||||
<view class="flex items-start justify-between mb-3">
|
||||
<view class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<view
|
||||
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">
|
||||
{{ asset.name }}
|
||||
</text>
|
||||
<text class="text-xs text-tertiary mt-1 block">
|
||||
{{ asset.detail }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
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
|
||||
class="px-2 py-1 radius-sm text-xs font-500 ml-2 flex-shrink-0"
|
||||
:style="{
|
||||
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 }}
|
||||
<!-- 详情信息 -->
|
||||
<view class="mb-3">
|
||||
<text class="text-12px text-[#999]">
|
||||
{{ asset.detail }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<view class="flex items-center gap-2">
|
||||
<!-- IoT卡操作按钮 -->
|
||||
<template v-if="asset.type === 'card'">
|
||||
<!-- 根据网络状态显示对应按钮 -->
|
||||
<template v-if="(asset.raw as CardInfo).network_status === 0">
|
||||
<!-- 已停机,只显示复机按钮 -->
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #52c41a; color: white;"
|
||||
@click="handleStartCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">复机</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else-if="(asset.raw as CardInfo).network_status === 1">
|
||||
<!-- 正常运行,只显示停机按钮 -->
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #ff4d4f; color: white;"
|
||||
@click="handleStopCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">停机</text>
|
||||
</view>
|
||||
<!-- 底部区域:状态标签 + 操作按钮 -->
|
||||
<view class="flex items-center justify-between pt-3 border-t border-[#f5f5f5]">
|
||||
<view class="flex items-center gap-1">
|
||||
<view
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: asset.statusColor }"
|
||||
/>
|
||||
<text class="text-13px font-500" :style="{ color: asset.statusColor }">
|
||||
{{ asset.status }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center gap-2">
|
||||
<!-- IoT卡操作按钮 -->
|
||||
<template v-if="asset.type === 'card'">
|
||||
<template v-if="(asset.raw as CardInfo).network_status === 0">
|
||||
<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 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 v-else>
|
||||
<!-- 其他状态,显示两个按钮 -->
|
||||
<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;"
|
||||
@click="handleStopCard(asset, $event)"
|
||||
@click="handleStopDevice(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">停机</text>
|
||||
<text class="text-12px font-500">停机</text>
|
||||
</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;"
|
||||
@click="handleStartCard(asset, $event)"
|
||||
@click="handleStartDevice(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">复机</text>
|
||||
<text class="text-12px font-500">复机</text>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 设备操作按钮 - 始终显示两个按钮 -->
|
||||
<template v-else>
|
||||
<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" />
|
||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loading" class="py-4 text-center">
|
||||
<i class="i-mdi-loading animate-spin text-xl text-brand mr-2" />
|
||||
<text class="text-sm text-[#64748b]">加载中...</text>
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loading" class="py-4 text-center">
|
||||
<i class="i-mdi-loading animate-spin text-xl text-brand mr-2" />
|
||||
<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 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">
|
||||
|
||||
@@ -53,7 +53,7 @@ const isFormValid = computed(() => {
|
||||
const { old_password, new_password, confirm_password } = formData.value
|
||||
return (
|
||||
old_password.length > 0
|
||||
&& new_password.length >= 6
|
||||
&& new_password.length >= 8
|
||||
&& new_password === confirm_password
|
||||
)
|
||||
})
|
||||
@@ -74,9 +74,9 @@ async function submitChange() {
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.value.new_password.length < 6) {
|
||||
if (formData.value.new_password.length < 8) {
|
||||
uni.showToast({
|
||||
title: '新密码至少6位',
|
||||
title: '新密码至少8位',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
@@ -149,256 +149,126 @@ function clearForm() {
|
||||
</script>
|
||||
|
||||
<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="bg-[#e3f2fd] rounded-12px p-4 mb-4 flex items-start gap-3">
|
||||
<i class="i-mdi-information text-20px text-[#1976d2] mt-0.5" />
|
||||
<view class="flex-1">
|
||||
<text class="text-13px text-[#1976d2] block mb-1 font-600">
|
||||
密码安全提示
|
||||
</text>
|
||||
<text class="text-12px text-[#1976d2] leading-relaxed">
|
||||
为了您的账号安全,建议密码长度至少8位,包含大小写字母、数字和特殊字符。
|
||||
</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 class="bg-white rounded-12px p-4 mb-3">
|
||||
<text class="text-12px text-[#999] block mb-2">当前密码</text>
|
||||
<view class="flex items-center">
|
||||
<input
|
||||
v-model="formData.old_password"
|
||||
:password="!passwordVisible.old"
|
||||
class="flex-1 text-16px text-[#212121]"
|
||||
placeholder="请输入当前密码"
|
||||
placeholder-class="text-[#ccc]"
|
||||
>
|
||||
<view
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
:class="[
|
||||
'flex-1 h-2px rounded-full',
|
||||
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"
|
||||
<image
|
||||
:src="passwordVisible.old ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
|
||||
class="w-20px h-20px"
|
||||
mode="aspectFit"
|
||||
@click="togglePasswordVisible('old')"
|
||||
/>
|
||||
<text class="text-15px font-600 text-white">
|
||||
{{ submitting ? '修改中...' : '确认修改' }}
|
||||
</view>
|
||||
</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>
|
||||
</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="
|
||||
py-3 rounded-12px center
|
||||
bg-white shadow-[0_2px_12px_rgba(0,0,0,0.06)]
|
||||
transition-all duration-200
|
||||
active:bg-[#f5f5f5]
|
||||
"
|
||||
@click="clearForm"
|
||||
>
|
||||
<i class="i-mdi-refresh text-18px text-[#666] mr-2" />
|
||||
<text class="text-15px font-600 text-[#666]">
|
||||
清空
|
||||
<view class="bg-white rounded-12px p-4 mb-3">
|
||||
<text class="text-12px text-[#999] block mb-2">确认密码</text>
|
||||
<view class="flex items-center">
|
||||
<input
|
||||
v-model="formData.confirm_password"
|
||||
:password="!passwordVisible.confirm"
|
||||
class="flex-1 text-16px text-[#212121]"
|
||||
placeholder="请再次输入新密码"
|
||||
placeholder-class="text-[#ccc]"
|
||||
>
|
||||
<image
|
||||
: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>
|
||||
</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>
|
||||
</view>
|
||||
@@ -423,6 +293,10 @@ function clearForm() {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.space-y-2 > view:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getCommissionSummary, getCommissionRecords, getCommissionStats } from '@/api/commission'
|
||||
import type { CommissionSummary, CommissionRecord, CommissionStats } from '@/api/commission'
|
||||
import { getFundSummary, getCommissionRecords, getCommissionStats } from '@/api/commission'
|
||||
import type { FundSummary, CommissionRecord, CommissionStats } from '@/api/commission'
|
||||
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[]>([])
|
||||
@@ -92,6 +101,12 @@ function formatTime(time: string) {
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
// 从登录信息获取 shop_id
|
||||
const userInfo = getUserInfo()
|
||||
if (userInfo?.shop_id) {
|
||||
shopId.value = userInfo.shop_id
|
||||
}
|
||||
|
||||
loadCommissionSummary()
|
||||
loadCommissionStats()
|
||||
loadCommissionRecords()
|
||||
@@ -100,7 +115,7 @@ onMounted(() => {
|
||||
// 加载佣金概览
|
||||
async function loadCommissionSummary() {
|
||||
try {
|
||||
const summary = await getCommissionSummary()
|
||||
const summary = await getFundSummary()
|
||||
commissionSummary.value = summary
|
||||
}
|
||||
catch (error) {
|
||||
@@ -110,8 +125,10 @@ async function loadCommissionSummary() {
|
||||
|
||||
// 加载佣金统计
|
||||
async function loadCommissionStats() {
|
||||
if (!shopId.value) return
|
||||
|
||||
try {
|
||||
const stats = await getCommissionStats()
|
||||
const stats = await getCommissionStats(shopId.value)
|
||||
commissionStats.value = stats
|
||||
}
|
||||
catch (error) {
|
||||
@@ -121,6 +138,8 @@ async function loadCommissionStats() {
|
||||
|
||||
// 加载佣金明细
|
||||
async function loadCommissionRecords() {
|
||||
if (!shopId.value) return
|
||||
|
||||
loadingRecords.value = true
|
||||
|
||||
try {
|
||||
@@ -152,7 +171,7 @@ async function loadCommissionRecords() {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await getCommissionRecords(params)
|
||||
const response = await getCommissionRecords(shopId.value, params)
|
||||
|
||||
if (page.value === 1) {
|
||||
commissionRecords.value = response.items || []
|
||||
@@ -214,217 +233,155 @@ function refreshList() {
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
||||
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else class="px-4 pt-4">
|
||||
<!-- 佣金概览卡片 -->
|
||||
<view v-if="commissionSummary" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||
<view class="flex items-center justify-between mb-3">
|
||||
<view class="flex items-center gap-2">
|
||||
<i class="i-mdi-currency-cny text-24px text-[#ff6700]" />
|
||||
<text class="text-15px font-600 text-[#212121]">
|
||||
佣金概览
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||
<text class="text-11px text-white/60">可提现佣金</text>
|
||||
<text class="text-24px font-700 text-white block mt-1">
|
||||
{{ commissionSummary ? formatAmount(commissionSummary.available_commission) : '--' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-2 gap-3">
|
||||
<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-[#ff6700]">
|
||||
{{ formatAmount(commissionSummary.available_commission) }}
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mx-4 mt-3">
|
||||
<view class="grid grid-cols-3 gap-4">
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">累计佣金</text>
|
||||
<text class="text-16px font-700 text-[var(--text-primary)] mt-1">
|
||||
{{ commissionSummary ? formatAmount(commissionSummary.total_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-[#212121]">
|
||||
{{ formatAmount(commissionSummary.total_commission) }}
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">冻结</text>
|
||||
<text class="text-16px font-700 text-[var(--text-tertiary)] mt-1">
|
||||
{{ commissionSummary ? formatAmount(commissionSummary.frozen_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-[#999]">
|
||||
{{ formatAmount(commissionSummary.frozen_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) }}
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">已提现</text>
|
||||
<text class="text-16px font-700 text-[#52c41a] mt-1">
|
||||
{{ commissionSummary ? formatAmount(commissionSummary.withdrawn_commission) : '--' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 佣金统计卡片 -->
|
||||
<view v-if="commissionStats" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||
<view class="px-4 mt-4">
|
||||
<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">
|
||||
<i class="i-mdi-chart-pie text-20px text-[#ff6700]" />
|
||||
<text class="text-15px font-600 text-[#212121]">
|
||||
佣金统计
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="space-y-2">
|
||||
<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.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>
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="flex-1 h-40px bg-[var(--bg-container)] rounded-8px px-3 text-14px text-[var(--text-primary)]"
|
||||
placeholder="搜索 ICCID / 虚拟号 / 订单号"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
@confirm="handleSearch"
|
||||
>
|
||||
<view
|
||||
class="px-4 h-40px bg-brand text-white text-14px rounded-8px flex items-center justify-center"
|
||||
@click="handleSearch"
|
||||
>
|
||||
搜索
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="mb-3">
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||
<view class="flex items-center px-4 py-3 gap-2">
|
||||
<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 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="showSourcePicker = true">
|
||||
<text class="flex-1 text-14px text-[var(--text-primary)]">{{ selectedSourceName }}</text>
|
||||
<text class="text-12px text-[var(--text-tertiary)]">▼</text>
|
||||
</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="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 class="flex items-center justify-between mb-3">
|
||||
<text class="text-12px text-[var(--text-secondary)]">共 {{ total }} 条记录</text>
|
||||
</view>
|
||||
|
||||
<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 v-else-if="commissionRecords.length > 0" class="space-y-2">
|
||||
<view
|
||||
v-for="record in commissionRecords"
|
||||
: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">
|
||||
<i class="i-mdi-cash-plus text-20px text-[#ff6700]" />
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
<text class="text-14px font-600 text-[var(--text-primary)]">
|
||||
{{ getSourceText(record.commission_source) }}
|
||||
</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
|
||||
: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]">
|
||||
<text class="text-18px font-700 text-brand">
|
||||
+{{ formatAmount(record.amount) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-if="record.order_id" class="mt-2 pt-2 border-t-1px border-[#f5f5f5]">
|
||||
<text class="text-11px text-[#999]">
|
||||
订单ID: {{ record.order_id }}
|
||||
</text>
|
||||
<view class="flex items-center justify-between mt-2">
|
||||
<text class="text-12px text-[var(--text-tertiary)]">{{ formatTime(record.created_at) }}</text>
|
||||
<text v-if="record.order_id" class="text-11px text-[var(--text-tertiary)]">订单ID: {{ record.order_id }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="commissionRecords.length < total" class="py-3 center">
|
||||
<text v-if="loadingRecords" class="text-12px text-[#999]">
|
||||
加载中...
|
||||
</text>
|
||||
<text v-else class="text-12px text-[#ff6700]" @click="loadMore">
|
||||
加载更多
|
||||
</text>
|
||||
<text v-if="loadingRecords" class="text-12px text-[var(--text-secondary)]">加载中...</text>
|
||||
<text v-else class="text-12px text-brand" @click="loadMore">加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="pt-10 center flex-col">
|
||||
<i class="i-mdi-cash-remove text-60px text-[#ddd] mb-3" />
|
||||
<text class="text-14px text-[#999]">
|
||||
暂无佣金记录
|
||||
</text>
|
||||
<view v-else class="pt-10 center">
|
||||
<text class="text-14px text-[var(--text-secondary)]">暂无佣金记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="h-8" />
|
||||
<view class="h-16" />
|
||||
</view>
|
||||
|
||||
<!-- 佣金来源选择器 -->
|
||||
<SimplePicker
|
||||
v-model:show="showSourcePicker"
|
||||
:options="sourceOptions"
|
||||
|
||||
@@ -202,16 +202,6 @@ function getCardStatusText(status: number, statusName?: string) {
|
||||
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() {
|
||||
searchKeyword.value = ''
|
||||
@@ -257,204 +247,120 @@ function onCarrierConfirm(option: { label: string, value: any }) {
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
||||
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else class="px-4 pt-4">
|
||||
<!-- 统计卡片 -->
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||
<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 v-else>
|
||||
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||
<text class="text-11px text-white/60">企业授权IoT卡</text>
|
||||
<text class="text-24px font-700 text-white block mt-1">{{ stats.total }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="mb-3">
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||
<view class="flex items-center px-4 py-3 gap-2">
|
||||
<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 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)"
|
||||
<view class="px-4 mt-4">
|
||||
<view class="flex items-center gap-2 mb-3">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="flex-1 h-40px bg-[var(--bg-container)] rounded-full px-4 text-14px text-[var(--text-primary)]"
|
||||
placeholder="搜索 ICCID / 虚拟号 / 手机号"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
@confirm="handleSearch"
|
||||
>
|
||||
{{ 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 v-if="filteredCards.length > 0" class="space-y-3 pb-4">
|
||||
<view
|
||||
v-for="card in filteredCards"
|
||||
:key="card.id"
|
||||
class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
||||
@click="viewCardDetail(card)"
|
||||
>
|
||||
<!-- 顶部状态栏 -->
|
||||
<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="[
|
||||
getCarrierIcon(card.carrier_name),
|
||||
'text-24px text-[#212121]',
|
||||
]"
|
||||
/>
|
||||
<view>
|
||||
<text class="text-16px font-700 text-[#212121] block">
|
||||
{{ card.carrier_name || '-' }}
|
||||
</text>
|
||||
<text class="text-11px text-[#999]">
|
||||
{{ getCardStatusText(card.status, card.status_name) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="px-4">
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mb-3">
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">正常</text>
|
||||
<text class="text-18px font-700 text-[#52c41a] mt-1">{{ stats.active }}</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">停机</text>
|
||||
<text class="text-18px font-700 text-[#ff4d4f] mt-1">{{ stats.inactive }}</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-[var(--text-tertiary)] block">已分销</text>
|
||||
<text class="text-18px font-700 text-brand mt-1">{{ stats.distributed }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center gap-2 mt-4">
|
||||
<view
|
||||
v-for="option in statusOptions"
|
||||
:key="option.value"
|
||||
:class="[
|
||||
'flex-1 text-center py-2 rounded-full text-13px transition-all',
|
||||
statusFilter === option.value
|
||||
? 'bg-brand text-white font-600'
|
||||
: '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
|
||||
:style="{
|
||||
backgroundColor: getNetworkStatusColor(card.network_status).bg,
|
||||
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) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="text-12px text-[#999]">
|
||||
<view class="mt-3 text-13px text-[var(--text-secondary)]">
|
||||
{{ 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 class="px-4 pb-4">
|
||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
||||
<view class="mt-3 flex items-center justify-between text-12px text-[var(--text-tertiary)]">
|
||||
<text>{{ card.iccid || '-' }}</text>
|
||||
<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">
|
||||
<text class="text-12px text-[#999]">
|
||||
流量使用
|
||||
</text>
|
||||
<text class="text-12px font-600 text-[#212121]">
|
||||
<text class="text-12px text-[var(--text-secondary)]">流量</text>
|
||||
<text class="text-12px font-600 text-[var(--text-primary)]">
|
||||
{{ card.data_usage || '0' }} / {{ card.data_total || '0' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<view class="h-6px bg-[#e0e0e0] rounded-full overflow-hidden">
|
||||
<view class="h-6px bg-[var(--border-primary)] rounded-full overflow-hidden">
|
||||
<view
|
||||
:style="{
|
||||
width: `${(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) * 100}%`,
|
||||
@@ -462,37 +368,28 @@ function onCarrierConfirm(option: { label: string, value: any }) {
|
||||
:class="[
|
||||
'h-full rounded-full transition-all duration-300',
|
||||
(parseFloat(card.data_usage || '0') / parseFloat(card.data_total || '1')) > 0.9
|
||||
? 'bg-[#ff6b6b]'
|
||||
: 'bg-[#3ed268]',
|
||||
? 'bg-[#ff4d4f]'
|
||||
: 'bg-[#52c41a]',
|
||||
]"
|
||||
/>
|
||||
</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.expire_date || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="pt-16 center">
|
||||
<text class="text-14px text-[var(--text-secondary)]">{{ searchKeyword ? '未找到匹配的卡' : '暂无卡数据' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<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 class="h-16" />
|
||||
</view>
|
||||
|
||||
<!-- 运营商选择器 -->
|
||||
<SimplePicker
|
||||
v-model:show="showCarrierPicker"
|
||||
:options="carrierOptions"
|
||||
|
||||
@@ -4,48 +4,33 @@ import { getUserInfo } from '@/api/auth'
|
||||
import { getEnterpriseDevices } from '@/api/enterprise'
|
||||
import type { DeviceInfo } from '@/api/enterprise'
|
||||
|
||||
// 企业ID (用于调用企业接口)
|
||||
const enterpriseId = ref<number>(0)
|
||||
|
||||
// 设备列表
|
||||
const deviceList = ref<DeviceInfo[]>([])
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 不再需要状态筛选
|
||||
|
||||
// 直接显示设备列表 (搜索由后端处理)
|
||||
const filteredDevices = computed(() => {
|
||||
return deviceList.value
|
||||
})
|
||||
|
||||
// 统计数据
|
||||
const stats = computed(() => {
|
||||
const total = deviceList.value.length
|
||||
return { total }
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
loadEnterpriseDevices()
|
||||
})
|
||||
|
||||
// 加载企业设备列表
|
||||
async function loadEnterpriseDevices() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 1. 获取企业ID (优先从本地存储获取)
|
||||
if (!enterpriseId.value) {
|
||||
const userInfo = uni.getStorageSync('user_info')
|
||||
if (userInfo?.enterprise_id) {
|
||||
enterpriseId.value = userInfo.enterprise_id
|
||||
} else {
|
||||
// 如果本地存储没有,则调用接口获取
|
||||
const apiUserInfo = await getUserInfo()
|
||||
if (!apiUserInfo.enterprise_id) {
|
||||
throw new Error('未找到企业ID')
|
||||
@@ -54,12 +39,10 @@ async function loadEnterpriseDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取企业设备列表 (支持分页和虚拟号搜索)
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 100, // 暂时获取更多数据,因为没有分页加载
|
||||
page_size: 100,
|
||||
}
|
||||
// 添加虚拟号搜索支持
|
||||
if (searchKeyword.value) {
|
||||
params.virtual_no = searchKeyword.value
|
||||
}
|
||||
@@ -69,35 +52,21 @@ async function loadEnterpriseDevices() {
|
||||
}
|
||||
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 viewDeviceDetail(device: any) {
|
||||
// 跳转到资产详情页
|
||||
uni.navigateTo({
|
||||
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) {
|
||||
if (!time) return '-'
|
||||
try {
|
||||
@@ -111,178 +80,85 @@ function formatAuthorizedTime(time: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
function clearSearch() {
|
||||
searchKeyword.value = ''
|
||||
deviceList.value = []
|
||||
loadEnterpriseDevices()
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
function handleSearch() {
|
||||
deviceList.value = []
|
||||
loadEnterpriseDevices()
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refreshList() {
|
||||
deviceList.value = []
|
||||
loadEnterpriseDevices()
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
||||
<text class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else class="px-4 pt-4">
|
||||
<!-- 搜索框 -->
|
||||
<view class="mb-3">
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||
<view class="flex items-center px-4 py-3 gap-2">
|
||||
<i class="i-mdi-magnify text-20px text-[#999]" />
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="flex-1 text-14px text-[#212121] placeholder:text-[#999]"
|
||||
placeholder="搜索设备名称 / 设备型号 / 虚拟号"
|
||||
@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 v-else>
|
||||
<view class="bg-brand px-4 py-3 mx-4 mt-4 rounded-12px">
|
||||
<text class="text-11px text-white/60">企业授权设备</text>
|
||||
<text class="text-24px font-700 text-white block mt-1">{{ stats.total }}</text>
|
||||
</view>
|
||||
|
||||
<view class="px-4 mt-4">
|
||||
<view class="flex items-center gap-2 mb-3">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="flex-1 h-40px bg-[var(--bg-container)] rounded-full px-4 text-14px text-[var(--text-primary)]"
|
||||
placeholder="搜索设备名称 / 设备型号 / 虚拟号"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
@confirm="handleSearch"
|
||||
>
|
||||
<view
|
||||
class="px-4 h-40px bg-[var(--brand-primary)] text-white text-14px rounded-full flex items-center justify-center"
|
||||
@click="handleSearch"
|
||||
>
|
||||
搜索
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex items-center gap-2">
|
||||
<i class="i-mdi-devices text-24px text-[#ff6700]" />
|
||||
<text class="text-15px font-600 text-[#212121]">
|
||||
企业授权设备
|
||||
</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 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 class="px-4">
|
||||
<view v-if="filteredDevices.length > 0" class="space-y-3 pb-4">
|
||||
<view
|
||||
v-for="device in filteredDevices"
|
||||
:key="device.virtual_no"
|
||||
class="bg-[var(--bg-container)] rounded-12px p-4"
|
||||
@click="viewDeviceDetail(device)"
|
||||
>
|
||||
<view class="flex items-center justify-between">
|
||||
<view>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<text class="text-12px text-[#999]">
|
||||
<view class="mt-3 text-12px text-[var(--text-tertiary)]">
|
||||
绑定卡数: {{ 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 class="px-4 pb-4">
|
||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
||||
<view class="mt-3 text-13px text-[var(--text-secondary)]">
|
||||
<text class="mr-4">虚拟号: {{ device.virtual_no || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="mt-3 bg-[var(--bg-muted)] rounded-8px p-3">
|
||||
<view class="flex items-center justify-between">
|
||||
<text class="text-11px text-[#999]">
|
||||
授权时间
|
||||
</text>
|
||||
<text class="text-12px font-600 text-[#212121]">
|
||||
<text class="text-12px text-[var(--text-secondary)]">授权时间</text>
|
||||
<text class="text-12px font-600 text-[var(--text-primary)]">
|
||||
{{ formatAuthorizedTime(device.authorized_at) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="pt-16 center">
|
||||
<text class="text-14px text-[var(--text-secondary)]">{{ searchKeyword ? '未找到匹配的设备' : '暂无设备数据' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<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 class="h-16" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -297,21 +173,4 @@ function refreshList() {
|
||||
.space-y-3 > view:not(:last-child) {
|
||||
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>
|
||||
|
||||
@@ -1,301 +1,283 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getUserInfo, logout as logoutApi } from '@/api/auth'
|
||||
import { clearToken } from '@/utils/auth'
|
||||
import type { UserInfo } from '@/api/auth'
|
||||
import { getCommissionSummary, getCommissionDailyStats } from '@/api/commission'
|
||||
import type { CommissionSummary, DailyCommissionStats } from '@/api/commission'
|
||||
import Skeleton from '@/components/Skeleton.vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getUserInfo, logout as logoutApi } from '@/api/auth'
|
||||
import { clearToken } from '@/utils/auth'
|
||||
import type { UserInfo } from '@/api/auth'
|
||||
import { getFundSummary, getCommissionDailyStats } from '@/api/commission'
|
||||
import type { FundSummary, DailyCommissionStats } from '@/api/commission'
|
||||
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
// 佣金概览
|
||||
const commissionSummary = ref<CommissionSummary | null>(null)
|
||||
// 每日佣金统计 (最近7天)
|
||||
const dailyStats = ref<DailyCommissionStats[]>([])
|
||||
// 加载状态
|
||||
const loading = ref(true)
|
||||
const loadingCommission = ref(false)
|
||||
// 用户信息
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
// 佣金概览
|
||||
const commissionSummary = ref<FundSummary | null>(null)
|
||||
// 每日佣金统计 (最近7天)
|
||||
const dailyStats = ref<DailyCommissionStats[]>([])
|
||||
// 加载状态
|
||||
const loading = ref(true)
|
||||
const loadingCommission = ref(false)
|
||||
|
||||
// 是否为代理账号
|
||||
const isAgent = computed(() => userInfo.value?.user_type === 3)
|
||||
// 店铺ID
|
||||
const shopId = ref<number>(0)
|
||||
|
||||
// 格式化金额(分转元,带千分号)
|
||||
function formatAmount(amount: number) {
|
||||
const yuan = (amount / 100).toFixed(2)
|
||||
const parts = yuan.split('.')
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
return `¥${parts.join('.')}`
|
||||
}
|
||||
// 是否为代理账号
|
||||
const isAgent = computed(() => userInfo.value?.user_type === 3)
|
||||
|
||||
// 格式化日期 (MM-DD)
|
||||
function formatDate(dateStr: string) {
|
||||
const date = new Date(dateStr)
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${month}-${day}`
|
||||
}
|
||||
// 格式化金额(分转元,带千分号)
|
||||
function formatAmount(amount : number) {
|
||||
const yuan = (amount / 100).toFixed(2)
|
||||
const parts = yuan.split('.')
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
return `¥${parts.join('.')}`
|
||||
}
|
||||
|
||||
// 根据用户类型动态生成菜单
|
||||
const menuItems = computed(() => {
|
||||
const menus = [
|
||||
{
|
||||
title: '资产列表',
|
||||
desc: userInfo.value?.user_type === 3 ? '代理资产列表' : '企业资产列表',
|
||||
icon: 'i-mdi-view-list',
|
||||
path: '/pages/agent-system/assets/index',
|
||||
},
|
||||
]
|
||||
// 格式化日期 (MM-DD)
|
||||
function formatDate(dateStr : string) {
|
||||
const date = new Date(dateStr)
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${month}-${day}`
|
||||
}
|
||||
|
||||
// 企业端显示授权卡和授权设备 (仅 user_type === 4)
|
||||
if (userInfo.value?.user_type === 4) {
|
||||
menus.push(
|
||||
// 根据用户类型动态生成菜单
|
||||
const menuItems = computed(() => {
|
||||
const menus = [
|
||||
{
|
||||
title: '授权卡列表',
|
||||
desc: '企业授权IoT卡',
|
||||
icon: 'i-mdi-sim',
|
||||
path: '/pages/agent-system/enterprise-cards/index',
|
||||
title: '资产列表',
|
||||
desc: userInfo.value?.user_type === 3 ? '代理资产列表' : '企业资产列表',
|
||||
icon: '/static/icons/资产列表.png',
|
||||
path: '/pages/agent-system/assets/index',
|
||||
},
|
||||
{
|
||||
title: '授权设备列表',
|
||||
desc: '企业授权设备',
|
||||
icon: 'i-mdi-devices',
|
||||
path: '/pages/agent-system/enterprise-devices/index',
|
||||
}
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
// 仅代理端显示佣金和提现
|
||||
if (userInfo.value?.user_type === 3) {
|
||||
menus.push(
|
||||
{
|
||||
title: '佣金中心',
|
||||
desc: '收益统计',
|
||||
icon: 'i-mdi-wallet',
|
||||
path: '/pages/agent-system/commission-center/index',
|
||||
},
|
||||
{
|
||||
title: '提现管理',
|
||||
desc: '申请提现',
|
||||
icon: 'i-mdi-cash',
|
||||
path: '/pages/agent-system/withdraw/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()
|
||||
// 企业端显示授权卡和授权设备 (仅 user_type === 4)
|
||||
if (userInfo.value?.user_type === 4) {
|
||||
menus.push(
|
||||
{
|
||||
title: '授权卡列表',
|
||||
desc: '企业授权IoT卡',
|
||||
icon: '/static/icons/授权卡列表.png',
|
||||
path: '/pages/agent-system/enterprise-cards/index',
|
||||
},
|
||||
{
|
||||
title: '授权设备列表',
|
||||
desc: '企业授权设备',
|
||||
icon: '/static/icons/授权设备列表.png',
|
||||
path: '/pages/agent-system/enterprise-devices/index',
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载首页数据失败:', error)
|
||||
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
||||
if (error instanceof Error && error.message && !error.statusCode) {
|
||||
uni.$u.toast(error.message)
|
||||
|
||||
// 仅代理端显示佣金和提现
|
||||
if (userInfo.value?.user_type === 3) {
|
||||
menus.push(
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转页面
|
||||
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',
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
return menus
|
||||
})
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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 class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] px-6 pt-6 pb-5">
|
||||
<view class="flex items-center justify-between">
|
||||
<!-- 左侧用户信息 -->
|
||||
<view class="flex-1">
|
||||
<view class="flex items-center gap-2 mb-1">
|
||||
<text class="text-22px font-700 text-white">
|
||||
<!-- 顶部用户信息卡片 -->
|
||||
<view class="px-4 pt-4">
|
||||
<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="w-56px h-56px rounded-full bg-[#f5f5f5] center mr-4"
|
||||
@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 || '欢迎回来' }}
|
||||
</text>
|
||||
<view
|
||||
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">
|
||||
<text class="text-13px text-[#999]">
|
||||
{{ userInfo?.phone || '-' }}
|
||||
</text>
|
||||
</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 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">
|
||||
<view class="flex items-center justify-between mb-3">
|
||||
<view>
|
||||
<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">
|
||||
<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)] p-4 mb-3">
|
||||
<view class="grid grid-cols-4 gap-3">
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-white/70 block mb-1">累计</text>
|
||||
<text class="text-14px font-600 text-white">
|
||||
<text class="text-11px text-[#999] block mb-1">累计</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ formatAmount(commissionSummary.total_commission) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-white/70 block mb-1">冻结</text>
|
||||
<text class="text-14px font-600 text-white">
|
||||
<text class="text-11px text-[#999] block mb-1">冻结</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ formatAmount(commissionSummary.frozen_commission) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-11px text-white/70 block mb-1">已提现</text>
|
||||
<text class="text-14px font-600 text-white">
|
||||
<text class="text-11px text-[#999] block mb-1">已提现</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ formatAmount(commissionSummary.withdrawn_commission) }}
|
||||
</text>
|
||||
</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 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 gap-2">
|
||||
<i class="i-mdi-chart-line text-20px text-[#ff6700]" />
|
||||
<text class="text-15px font-600 text-[#212121]">
|
||||
近7日佣金
|
||||
</text>
|
||||
</view>
|
||||
<text class="text-16px font-700 text-[#212121]">今日佣金</text>
|
||||
<text class="text-13px text-[#999]" @click="navigateTo('/pages/agent-system/commission-center/index')">更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 简单的趋势条形图 -->
|
||||
<view class="space-y-2">
|
||||
<view
|
||||
v-for="stat in dailyStats"
|
||||
:key="stat.date"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<text class="text-11px text-[#999] w-40px">
|
||||
{{ formatDate(stat.date) }}
|
||||
</text>
|
||||
<view class="flex-1 h-20px bg-[#f5f5f5] rounded-full overflow-hidden relative">
|
||||
<view
|
||||
:style="{
|
||||
width: `${Math.min(100, (stat.total_amount / Math.max(...dailyStats.map(s => s.total_amount))) * 100)}%`,
|
||||
}"
|
||||
class="h-full bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-full"
|
||||
/>
|
||||
<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="text-center">
|
||||
<text class="text-12px text-[#999] block mb-1">日期</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ formatDate(dailyStats[0].date) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-12px text-[#999] block mb-1">佣金金额</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ formatAmount(dailyStats[0].total_amount) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="text-12px text-[#999] block mb-1">订单笔数</text>
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ dailyStats[0].total_count }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="text-11px font-600 text-[#212121] w-60px text-right">
|
||||
{{ formatAmount(stat.total_amount) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -303,23 +285,55 @@ onMounted(() => {
|
||||
|
||||
<!-- 快捷功能区 -->
|
||||
<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
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="item.path"
|
||||
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 items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/assets/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">{{ item.title }}</text>
|
||||
<text class="text-12px text-[#999] block mt-0.5">{{ item.desc }}</text>
|
||||
<text class="text-15px text-[#212121] font-600 block">资产列表</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>
|
||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||
</view>
|
||||
@@ -328,35 +342,19 @@ onMounted(() => {
|
||||
|
||||
<!-- 其他操作区 -->
|
||||
<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="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
|
||||
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/change-password/index')"
|
||||
>
|
||||
<i class="i-mdi-lock-reset text-20px text-[#ff6700] mr-3" />
|
||||
<view class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/change-password/index')">
|
||||
<image src="@/static/icons/修改密码.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||
<text class="flex-1 text-15px text-[#212121]">修改密码</text>
|
||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view
|
||||
class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200"
|
||||
@click="logout"
|
||||
>
|
||||
<i class="i-mdi-logout text-20px text-[#999] mr-3" />
|
||||
<view class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="logout">
|
||||
<image src="@/static/icons/退出登录.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
|
||||
<text class="flex-1 text-15px text-[#212121]">退出登录</text>
|
||||
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
|
||||
</view>
|
||||
@@ -367,26 +365,27 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.space-y-2 > view:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -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="flex items-center justify-between mb-3">
|
||||
<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 class="space-y-3">
|
||||
@@ -660,9 +660,9 @@ const filteredPackages = computed(() => {
|
||||
<view v-if="currentTab === 'my-packages'">
|
||||
<!-- 总计卡片 (橙色渐变) -->
|
||||
<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
|
||||
shadow-[0_8px_24px_rgba(255,103,0,0.2)]
|
||||
shadow-brand-colored
|
||||
">
|
||||
<text class="text-13px text-white/70 block mb-2">套餐总计</text>
|
||||
|
||||
@@ -699,7 +699,7 @@ const filteredPackages = computed(() => {
|
||||
<view class="
|
||||
bg-white rounded-12px p-4
|
||||
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">
|
||||
<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" />
|
||||
<text class="text-13px text-[#9e9e9e] block mb-3">暂无加油包</text>
|
||||
<button class="
|
||||
bg-[#ff6700] text-white
|
||||
bg-[var(--brand-primary)] text-white
|
||||
px-5 py-2 rounded-8px
|
||||
text-13px font-500
|
||||
">
|
||||
@@ -872,7 +872,7 @@ const filteredPackages = computed(() => {
|
||||
<view class="flex gap-2 mb-3 overflow-x-auto pb-2">
|
||||
<button
|
||||
:class="{
|
||||
'bg-[#fff7f0] text-[#ff6700]': filterStatus === null,
|
||||
'bg-[var(--brand-primary-light)] text-[var(--brand-primary)]': filterStatus === null,
|
||||
'text-[#9e9e9e]': filterStatus !== null
|
||||
}"
|
||||
class="px-4 py-1.5 rounded-full text-13px font-500 whitespace-nowrap"
|
||||
@@ -1076,7 +1076,7 @@ const filteredPackages = computed(() => {
|
||||
<span
|
||||
v-for="tag in asset.tags"
|
||||
: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 }}
|
||||
</span>
|
||||
@@ -1131,7 +1131,7 @@ const filteredPackages = computed(() => {
|
||||
<text class="text-11px text-[#9e9e9e]">标签总数</text>
|
||||
</view>
|
||||
<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) }}
|
||||
</text>
|
||||
<text class="text-11px text-[#9e9e9e]">已分配资产</text>
|
||||
@@ -1179,7 +1179,7 @@ const filteredPackages = computed(() => {
|
||||
已分配 {{ tag.asset_count }} 个资产
|
||||
</text>
|
||||
<button
|
||||
class="text-12px text-[#ff6700] font-500"
|
||||
class="text-12px text-[var(--brand-primary)] font-500"
|
||||
@click="enterAssignMode(tag)"
|
||||
>
|
||||
分配 →
|
||||
@@ -1192,11 +1192,11 @@ const filteredPackages = computed(() => {
|
||||
<button class="
|
||||
w-full
|
||||
mt-4
|
||||
bg-[#ff6700] text-white
|
||||
bg-[var(--brand-primary)] text-white
|
||||
py-3 rounded-12px
|
||||
text-14px font-500
|
||||
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" />
|
||||
创建新标签
|
||||
@@ -1255,7 +1255,7 @@ const filteredPackages = computed(() => {
|
||||
<!-- 勾选框 -->
|
||||
<view
|
||||
: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)
|
||||
}"
|
||||
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]">
|
||||
<button class="
|
||||
w-full
|
||||
bg-[#ff6700] text-white
|
||||
bg-[var(--brand-primary)] text-white
|
||||
py-3 rounded-8px
|
||||
text-15px font-600
|
||||
shadow-[0_2px_8px_rgba(255,103,0,0.2)]
|
||||
shadow-brand-xs
|
||||
">
|
||||
保存分配
|
||||
</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 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>
|
||||
</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) }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -1488,10 +1488,10 @@ const filteredPackages = computed(() => {
|
||||
续费套餐
|
||||
</button>
|
||||
<button class="
|
||||
bg-[#ff6700] text-white
|
||||
bg-[var(--brand-primary)] text-white
|
||||
py-3 rounded-8px
|
||||
text-14px font-500
|
||||
shadow-[0_2px_8px_rgba(255,103,0,0.2)]
|
||||
shadow-brand-xs
|
||||
">
|
||||
购买加油包
|
||||
</button>
|
||||
|
||||
@@ -125,7 +125,7 @@ onMounted(() => {
|
||||
<text class="text-11px text-[#999] mt-1">总IoT卡</text>
|
||||
</view>
|
||||
<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>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
@@ -153,7 +153,7 @@ onMounted(() => {
|
||||
<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 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>
|
||||
</view>
|
||||
<text class="text-12px text-[#999]">{{ group.count }} 张</text>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getCommissionSummary } from '@/api/commission'
|
||||
import type { CommissionSummary } from '@/api/commission'
|
||||
import { getFundSummary } from '@/api/commission'
|
||||
import type { FundSummary } from '@/api/commission'
|
||||
import { createWithdrawal, getWithdrawalRecords } from '@/api/withdrawal'
|
||||
import type { WithdrawalRecord } from '@/api/withdrawal'
|
||||
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('')
|
||||
@@ -47,9 +56,9 @@ const statusOptions = [
|
||||
|
||||
// 获取当前选中的状态名称
|
||||
const selectedStatusName = computed(() => {
|
||||
if (statusFilter.value === undefined) return '提现状态'
|
||||
if (statusFilter.value === undefined) return '全部状态'
|
||||
const option = statusOptions.find(o => o.value === statusFilter.value)
|
||||
return option?.label || '提现状态'
|
||||
return option?.label || '全部状态'
|
||||
})
|
||||
|
||||
// Tab选项
|
||||
@@ -136,6 +145,12 @@ function formatTime(time: string) {
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
// 从登录信息获取 shop_id
|
||||
const userInfo = getUserInfo()
|
||||
if (userInfo?.shop_id) {
|
||||
shopId.value = userInfo.shop_id
|
||||
}
|
||||
|
||||
loadCommissionSummary()
|
||||
loadWithdrawRecords()
|
||||
})
|
||||
@@ -144,8 +159,9 @@ onMounted(() => {
|
||||
async function loadCommissionSummary() {
|
||||
loading.value = true
|
||||
try {
|
||||
const summary = await getCommissionSummary()
|
||||
const summary = await getFundSummary()
|
||||
commissionSummary.value = summary
|
||||
withdrawAmount.value = availableAmount.value.toFixed(2)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('加载佣金概览失败:', error)
|
||||
@@ -157,6 +173,8 @@ async function loadCommissionSummary() {
|
||||
|
||||
// 加载提现记录
|
||||
async function loadWithdrawRecords() {
|
||||
if (!shopId.value) return
|
||||
|
||||
loadingRecords.value = true
|
||||
|
||||
try {
|
||||
@@ -170,7 +188,7 @@ async function loadWithdrawRecords() {
|
||||
params.status = statusFilter.value
|
||||
}
|
||||
|
||||
const response = await getWithdrawalRecords(params)
|
||||
const response = await getWithdrawalRecords(shopId.value, params)
|
||||
|
||||
if (page.value === 1) {
|
||||
withdrawRecords.value = response.items || []
|
||||
@@ -213,39 +231,48 @@ async function handleSubmit() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!accountName.value.trim()) {
|
||||
const name = accountName.value.trim()
|
||||
if (!name) {
|
||||
uni.$u.toast('请输入收款人姓名')
|
||||
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('请输入支付宝账号')
|
||||
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
|
||||
|
||||
try {
|
||||
const result = await createWithdrawal({
|
||||
account_name: accountName.value.trim(),
|
||||
account_number: accountNumber.value.trim(),
|
||||
amount: Math.round(amount * 100), // 元转分
|
||||
const result = await createWithdrawal(shopId.value, {
|
||||
account_name: name,
|
||||
account_number: account,
|
||||
amount: Math.round(amount * 100),
|
||||
withdrawal_method: 'alipay',
|
||||
})
|
||||
|
||||
uni.$u.toast('提现申请已提交')
|
||||
|
||||
// 清空表单
|
||||
withdrawAmount.value = ''
|
||||
accountName.value = ''
|
||||
accountNumber.value = ''
|
||||
|
||||
// 刷新数据
|
||||
await loadCommissionSummary()
|
||||
page.value = 1
|
||||
await loadWithdrawRecords()
|
||||
|
||||
// 切换到记录Tab
|
||||
activeTab.value = 'records'
|
||||
}
|
||||
catch (error) {
|
||||
@@ -284,35 +311,22 @@ function loadMore() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
||||
<!-- 可提现金额卡片 -->
|
||||
<view class="px-4 pt-4 pb-3">
|
||||
<view class="bg-gradient-to-r from-[#ff6700] to-[#ff8534] rounded-16px shadow-[0_4px_16px_rgba(255,103,0,0.3)] p-4">
|
||||
<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 class="bg-[var(--bg-page)] min-h-screen pb-safe">
|
||||
<view class="bg-brand rounded-12px px-4 py-3 mx-4">
|
||||
<text class="text-11px text-white/60">可提现金额</text>
|
||||
<text class="text-24px font-700 text-white block mt-1">¥{{ formattedAvailableAmount }}</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="px-4 mb-3">
|
||||
<view class="bg-white rounded-12px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-1 flex">
|
||||
<view class="px-4 mt-4">
|
||||
<view class="flex rounded-full bg-[var(--bg-container)] p-1">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
: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
|
||||
? 'bg-[#ff6700] text-white font-600'
|
||||
: 'text-[#666]',
|
||||
? 'bg-brand text-white font-600'
|
||||
: 'text-[var(--text-secondary)]',
|
||||
]"
|
||||
@click="handleTabChange(tab.key)"
|
||||
>
|
||||
@@ -321,237 +335,136 @@ function loadMore() {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 申请提现 -->
|
||||
<view v-if="activeTab === 'apply'" class="px-4">
|
||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 mb-3">
|
||||
<!-- 提现金额 -->
|
||||
<view class="mb-4">
|
||||
<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>
|
||||
<view v-if="activeTab === 'apply'" class="px-4 mt-4">
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4">
|
||||
<text class="text-12px text-[var(--text-tertiary)]">提现金额</text>
|
||||
<view class="flex items-center mt-1">
|
||||
<text class="text-24px text-[var(--text-secondary)] mr-1">¥</text>
|
||||
<input
|
||||
v-model="accountName"
|
||||
class="w-full text-16px text-[#212121] font-500"
|
||||
placeholder="请输入支付宝实名认证的真实姓名"
|
||||
placeholder-style="color: #ccc"
|
||||
v-model="withdrawAmount"
|
||||
type="digit"
|
||||
class="flex-1 text-24px text-[var(--text-primary)]"
|
||||
placeholder="0.00"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
>
|
||||
</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
|
||||
v-model="accountNumber"
|
||||
class="w-full text-16px text-[#212121] font-500"
|
||||
placeholder="请输入支付宝账号/手机号/邮箱"
|
||||
placeholder-style="color: #ccc"
|
||||
<view class="flex items-center gap-2 mt-3">
|
||||
<view
|
||||
v-for="amount in [100, 500, 1000]"
|
||||
:key="amount"
|
||||
:class="[
|
||||
'px-4 py-1.5 text-13px rounded-full transition-all',
|
||||
withdrawAmount == amount.toString()
|
||||
? 'bg-brand text-white font-600'
|
||||
: 'bg-[var(--bg-muted)] text-[var(--text-secondary)]',
|
||||
]"
|
||||
@click="setQuickAmount(amount)"
|
||||
>
|
||||
</view>
|
||||
|
||||
<!-- 费用说明 -->
|
||||
<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>
|
||||
{{ amount }}
|
||||
</view>
|
||||
<view class="pt-3 border-t-2px border-[#ff6700]">
|
||||
<view class="flex items-center justify-between">
|
||||
<view>
|
||||
<text class="text-14px font-600 text-[#212121] block">实际到账</text>
|
||||
<text class="text-11px text-[#52c41a] block mt-0.5">
|
||||
✓ 无手续费,全额到账
|
||||
</text>
|
||||
</view>
|
||||
<text class="text-24px font-700 text-[#52c41a]">¥{{ actualAmount }}</text>
|
||||
</view>
|
||||
<view
|
||||
:class="[
|
||||
'px-4 py-1.5 text-13px rounded-full transition-all font-500',
|
||||
withdrawAmount == availableAmount.toFixed(2)
|
||||
? 'bg-brand text-white'
|
||||
: 'bg-[var(--bg-muted)] text-[var(--text-secondary)]',
|
||||
]"
|
||||
@click="setAllAmount"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view
|
||||
:class="[
|
||||
'w-full py-3 rounded-8px text-center text-15px font-600 transition-all duration-200',
|
||||
submitting ? 'bg-[#ccc] text-white' : 'bg-[#ff6700] text-white',
|
||||
]"
|
||||
@click="handleSubmit"
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||
<text class="text-12px text-[var(--text-tertiary)]">收款人姓名</text>
|
||||
<input
|
||||
v-model="accountName"
|
||||
class="w-full text-16px text-[var(--text-primary)] mt-1"
|
||||
placeholder="请输入姓名"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
>
|
||||
{{ submitting ? '提交中...' : '提交申请' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="mt-3 p-4 bg-[#fff3e0] rounded-12px">
|
||||
<view class="flex items-start gap-2">
|
||||
<i class="i-mdi-information text-16px text-[#e65100] mt-0.5" />
|
||||
<view class="flex-1">
|
||||
<text class="text-12px font-600 text-[#e65100] block mb-2">温馨提示</text>
|
||||
<text class="text-12px text-[#666] block leading-relaxed">
|
||||
1. 目前仅支持支付宝提现<br>
|
||||
2. 无手续费,全额到账<br>
|
||||
3. 收款人姓名需与支付宝实名一致<br>
|
||||
4. 提现申请提交后将在1-3个工作日内处理
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||
<text class="text-12px text-[var(--text-tertiary)]">支付宝账号</text>
|
||||
<input
|
||||
v-model="accountNumber"
|
||||
class="w-full text-16px text-[var(--text-primary)] mt-1"
|
||||
placeholder="请输入账号"
|
||||
placeholder-class="text-[var(--text-quaternary)]"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="bg-[var(--bg-container)] rounded-12px p-4 mt-3">
|
||||
<view class="flex items-center justify-between">
|
||||
<text class="text-14px text-[var(--text-secondary)]">提现金额</text>
|
||||
<text class="text-20px font-600 text-brand">¥{{ formattedWithdrawAmount }}</text>
|
||||
</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 v-else class="px-4">
|
||||
<!-- 筛选条件 -->
|
||||
<view v-else class="px-4 mt-4">
|
||||
<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="showStatusPicker = true">
|
||||
<text class="flex-1 text-14px text-[#212121]">{{ selectedStatusName }}</text>
|
||||
<i class="i-mdi-chevron-down text-18px text-[#999]" />
|
||||
<view class="flex-1 bg-[var(--bg-container)] rounded-12px px-4 py-3" @click="showStatusPicker = true">
|
||||
<text class="text-14px text-[var(--text-primary)]">{{ selectedStatusName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 记录列表 -->
|
||||
<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 v-else-if="withdrawRecords.length > 0" class="space-y-2 mb-3">
|
||||
<view v-else-if="withdrawRecords.length > 0" class="space-y-3">
|
||||
<view
|
||||
v-for="record in withdrawRecords"
|
||||
: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 gap-2">
|
||||
<i class="i-mdi-bank-transfer text-20px text-[#ff6700]" />
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
{{ record.withdrawal_no }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<text class="text-13px text-[var(--text-tertiary)]">{{ formatTime(record.created_at) }}</text>
|
||||
<view
|
||||
:style="{
|
||||
backgroundColor: getStatusColor(record.status).bg,
|
||||
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) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="space-y-2">
|
||||
<view class="flex items-center justify-between mb-2">
|
||||
<text class="text-13px text-[#666]">提现金额</text>
|
||||
<text class="text-18px font-700 text-[#212121]">
|
||||
{{ formatAmount(record.amount) }}
|
||||
</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 class="flex items-center justify-between mt-3">
|
||||
<text class="text-14px text-[var(--text-secondary)]">{{ record.account_name }}</text>
|
||||
<text class="text-20px font-700 text-brand">-{{ formatAmount(record.amount) }}</text>
|
||||
</view>
|
||||
<view v-if="record.status === 3 && record.reject_reason" class="mt-2 pt-2 border-t border-[var(--border-secondary)]">
|
||||
<text class="text-12px text-[var(--color-error)]">{{ record.reject_reason }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="withdrawRecords.length < total" class="py-3 center">
|
||||
<text v-if="loadingRecords" class="text-12px text-[#999]">
|
||||
加载中...
|
||||
</text>
|
||||
<text v-else class="text-12px text-[#ff6700]" @click="loadMore">
|
||||
加载更多
|
||||
</text>
|
||||
<view v-if="withdrawRecords.length < total" class="py-4 center">
|
||||
<text v-if="loadingRecords" class="text-14px text-[var(--text-secondary)]">加载中...</text>
|
||||
<text v-else class="text-14px text-[var(--brand-primary)]" @click="loadMore">加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="pt-10 center flex-col">
|
||||
<i class="i-mdi-file-document-outline text-60px text-[#ddd] mb-3" />
|
||||
<text class="text-14px text-[#999]">
|
||||
暂无提现记录
|
||||
</text>
|
||||
<view v-else class="pt-16 center">
|
||||
<text class="text-14px text-[var(--text-secondary)]">暂无记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="h-8" />
|
||||
<view class="h-16" />
|
||||
|
||||
<!-- 状态选择器 -->
|
||||
<SimplePicker
|
||||
v-model:show="showStatusPicker"
|
||||
:options="statusOptions"
|
||||
|
||||
@@ -202,7 +202,7 @@ onLoad((options: any) => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ onLoad((options: any) => {
|
||||
height: 800rpx;
|
||||
top: -300rpx;
|
||||
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%;
|
||||
animation: pulse1 10s infinite ease-in-out;
|
||||
}
|
||||
@@ -225,7 +225,7 @@ onLoad((options: any) => {
|
||||
height: 600rpx;
|
||||
bottom: -200rpx;
|
||||
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%;
|
||||
animation: pulse2 12s infinite ease-in-out;
|
||||
}
|
||||
@@ -235,7 +235,7 @@ onLoad((options: any) => {
|
||||
height: 400rpx;
|
||||
top: 50%;
|
||||
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%;
|
||||
animation: pulse3 14s infinite ease-in-out;
|
||||
}
|
||||
@@ -384,8 +384,8 @@ onLoad((options: any) => {
|
||||
|
||||
&.focused {
|
||||
background: #ffffff;
|
||||
border-color: #d4af37;
|
||||
box-shadow: 0 0 0 4rpx rgba(212, 175, 55, 0.08);
|
||||
border-color: var(--brand-primary);
|
||||
box-shadow: 0 0 0 4rpx rgba(22, 119, 255, 0.08);
|
||||
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,
|
||||
page {
|
||||
/* === 品牌色 Brand Colors - 只保留3个核心颜色 === */
|
||||
--brand-primary: #d4af37; /* 主色 - 金色 */
|
||||
/* === 品牌色 Brand Colors === */
|
||||
--brand-primary: #1677ff; /* 主色 - 蓝色 */
|
||||
--brand-primary-light: #4096ff; /* 主色浅 */
|
||||
--brand-primary-dark: #0958d9; /* 主色深 */
|
||||
--brand-secondary: #1e293b; /* 辅色 - 深灰/黑色 */
|
||||
--brand-accent: #ffffff; /* 强调 - 白色 */
|
||||
|
||||
@@ -17,7 +19,7 @@ page {
|
||||
--color-warning-bg: #fffbe6; /* 警告背景 */
|
||||
--color-error: #ff4d4f; /* 错误 - Red */
|
||||
--color-error-bg: #fff2f0; /* 错误背景 */
|
||||
--color-info: #1890ff; /* 信息 - Blue */
|
||||
--color-info: #1677ff; /* 信息 - Blue(同主色) */
|
||||
--color-info-bg: #e6f7ff; /* 信息背景 */
|
||||
|
||||
/* === 文字色阶 Text Colors === */
|
||||
@@ -44,7 +46,7 @@ page {
|
||||
--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-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-xs: 4px;
|
||||
@@ -98,11 +100,13 @@ page {
|
||||
|
||||
/* === 暗色主题 Dark Theme === */
|
||||
.theme-dark {
|
||||
--brand-primary: #ff8533; /* 暗色下橙色更柔和 */
|
||||
--brand-primary: #4b9cff; /* 暗色下蓝色更柔和 */
|
||||
--brand-primary-light: #69b1ff;
|
||||
--brand-primary-dark: #1668dc;
|
||||
--color-success: #73d13d;
|
||||
--color-warning: #ffc53d;
|
||||
--color-error: #ff7875;
|
||||
--color-info: #40a9ff;
|
||||
--color-info: #4b9cff;
|
||||
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #b0b8c1;
|
||||
@@ -150,6 +154,14 @@ page {
|
||||
.bg-muted { background-color: var(--bg-muted); }
|
||||
.bg-brand { background-color: var(--brand-primary); }
|
||||
.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-secondary { border-color: var(--border-secondary); }
|
||||
@@ -229,7 +241,7 @@ page {
|
||||
.input:focus {
|
||||
border-color: var(--brand-primary);
|
||||
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 {
|
||||
|
||||
@@ -47,6 +47,14 @@ export function post<T = any>(url: string, config?: HttpRequestConfig): Promise<
|
||||
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> {
|
||||
return request({ ...config, url, method: 'UPLOAD' });
|
||||
}
|
||||
|
||||