This commit is contained in:
@@ -35,20 +35,24 @@
|
|||||||
根据提供的权限JSON,需要添加权限控制的模块包括:
|
根据提供的权限JSON,需要添加权限控制的模块包括:
|
||||||
|
|
||||||
1. **套餐管理** (`/package-management`)
|
1. **套餐管理** (`/package-management`)
|
||||||
|
|
||||||
- 套餐系列 (`package-series`)
|
- 套餐系列 (`package-series`)
|
||||||
- 套餐列表 (`package-list`)
|
- 套餐列表 (`package-list`)
|
||||||
- 单套餐分配 (`package-assign`)
|
- 单套餐分配 (`package-assign`)
|
||||||
- 套餐系列分配 (`series-assign`)
|
- 套餐系列分配 (`series-assign`)
|
||||||
|
|
||||||
2. **店铺管理** (`/shop-management`)
|
2. **店铺管理** (`/shop-management`)
|
||||||
|
|
||||||
- 店铺列表 (`list`)
|
- 店铺列表 (`list`)
|
||||||
|
|
||||||
3. **账号管理** (`/account-management`)
|
3. **账号管理** (`/account-management`)
|
||||||
|
|
||||||
- 账号列表 (`account`)
|
- 账号列表 (`account`)
|
||||||
- 平台账号 (`platform-account`)
|
- 平台账号 (`platform-account`)
|
||||||
- 代理账号 (`shop-account`)
|
- 代理账号 (`shop-account`)
|
||||||
|
|
||||||
4. **资产管理** (`/asset-management`)
|
4. **资产管理** (`/asset-management`)
|
||||||
|
|
||||||
- IoT卡管理 (`iot-card-management`)
|
- IoT卡管理 (`iot-card-management`)
|
||||||
- IoT卡任务 (`iot-card-task`)
|
- IoT卡任务 (`iot-card-task`)
|
||||||
- 设备任务 (`device-task`)
|
- 设备任务 (`device-task`)
|
||||||
@@ -67,9 +71,9 @@
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -79,29 +83,28 @@ const { hasAuth } = useAuth()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 2. 表格列中的状态切换 -->
|
<!-- 2. 表格列中的状态切换 -->
|
||||||
<ElSwitch
|
<ElSwitch :disabled="!hasAuth('module:update_status')" v-model="row.status" />
|
||||||
:disabled="!hasAuth('module:update_status')"
|
|
||||||
v-model="row.status"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 3. 表格列中的操作按钮 -->
|
<!-- 3. 表格列中的操作按钮 -->
|
||||||
<script>
|
<script>
|
||||||
const columns = [{
|
const columns = [
|
||||||
prop: 'operation',
|
{
|
||||||
formatter: (row) => {
|
prop: 'operation',
|
||||||
const buttons = []
|
formatter: (row) => {
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
if (hasAuth('module:edit')) {
|
if (hasAuth('module:edit')) {
|
||||||
buttons.push(h(ArtButtonTable, { type: 'edit', onClick: () => edit(row) }))
|
buttons.push(h(ArtButtonTable, { type: 'edit', onClick: () => edit(row) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuth('module:delete')) {
|
||||||
|
buttons.push(h(ArtButtonTable, { type: 'delete', onClick: () => del(row) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
if (hasAuth('module:delete')) {
|
|
||||||
buttons.push(h(ArtButtonTable, { type: 'delete', onClick: () => del(row) }))
|
|
||||||
}
|
|
||||||
|
|
||||||
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
</script>
|
</script>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,12 +9,14 @@
|
|||||||
### 1. 套餐管理模块 (Package Management) ✅
|
### 1. 套餐管理模块 (Package Management) ✅
|
||||||
|
|
||||||
- [x] **套餐系列页面** (`/package-management/package-series`)
|
- [x] **套餐系列页面** (`/package-management/package-series`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增套餐系列"按钮添加 `v-permission="'package_series:add'"`
|
- ✅ 为"新增套餐系列"按钮添加 `v-permission="'package_series:add'"`
|
||||||
- ✅ 为状态切换添加 `disabled: !hasAuth('package_series:update_status')`
|
- ✅ 为状态切换添加 `disabled: !hasAuth('package_series:update_status')`
|
||||||
- ✅ 在操作列中使用 `hasAuth()` 判断编辑权限 (`package_series:edit`)和删除权限 (`package_series:delete`)
|
- ✅ 在操作列中使用 `hasAuth()` 判断编辑权限 (`package_series:edit`)和删除权限 (`package_series:delete`)
|
||||||
|
|
||||||
- [x] **套餐列表页面** (`/package-management/package-list`)
|
- [x] **套餐列表页面** (`/package-management/package-list`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增套餐"按钮添加 `v-permission="'package:add'"`
|
- ✅ 为"新增套餐"按钮添加 `v-permission="'package:add'"`
|
||||||
- ✅ 为上/下架切换添加 `disabled: !hasAuth('package:update_away')`
|
- ✅ 为上/下架切换添加 `disabled: !hasAuth('package:update_away')`
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
- ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`package:edit`) 和删除 (`package:delete`) 权限
|
- ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`package:edit`) 和删除 (`package:delete`) 权限
|
||||||
|
|
||||||
- [x] **单套餐分配页面** (`/package-management/package-assign`)
|
- [x] **单套餐分配页面** (`/package-management/package-assign`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增分配"按钮添加 `v-permission="'package_assign:add'"`
|
- ✅ 为"新增分配"按钮添加 `v-permission="'package_assign:add'"`
|
||||||
- ✅ 为状态切换添加 `disabled: !hasAuth('package_assign:update_status')`
|
- ✅ 为状态切换添加 `disabled: !hasAuth('package_assign:update_status')`
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
### 3. 账号管理模块 (Account Management) ✅
|
### 3. 账号管理模块 (Account Management) ✅
|
||||||
|
|
||||||
- [x] **账号列表页面** (`/account-management/account`)
|
- [x] **账号列表页面** (`/account-management/account`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增账号"按钮添加 `v-permission="'account:add'"`
|
- ✅ 为"新增账号"按钮添加 `v-permission="'account:add'"`
|
||||||
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
||||||
@@ -58,6 +62,7 @@
|
|||||||
- 删除: `account:delete`
|
- 删除: `account:delete`
|
||||||
|
|
||||||
- [x] **平台账号页面** (`/account-management/platform-account`)
|
- [x] **平台账号页面** (`/account-management/platform-account`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增平台账号"按钮添加 `v-permission="'platform_account:add'"`
|
- ✅ 为"新增平台账号"按钮添加 `v-permission="'platform_account:add'"`
|
||||||
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
||||||
@@ -76,6 +81,7 @@
|
|||||||
### 4. 资产管理模块 (Asset Management) ✅
|
### 4. 资产管理模块 (Asset Management) ✅
|
||||||
|
|
||||||
- [x] **IoT卡管理页面** (`/asset-management/iot-card-management`)
|
- [x] **IoT卡管理页面** (`/asset-management/iot-card-management`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为表格头部操作按钮添加权限控制:
|
- ✅ 为表格头部操作按钮添加权限控制:
|
||||||
- 批量分配: `v-permission="'iot_card:batch_allocation'"`
|
- 批量分配: `v-permission="'iot_card:batch_allocation'"`
|
||||||
@@ -89,16 +95,19 @@
|
|||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **IoT卡任务页面** (`/asset-management/iot-card-task`)
|
- [x] **IoT卡任务页面** (`/asset-management/iot-card-task`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"批量导入IoT卡"按钮添加 `v-permission="''"`
|
- ✅ 为"批量导入IoT卡"按钮添加 `v-permission="''"`
|
||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **设备任务页面** (`/asset-management/device-task`)
|
- [x] **设备任务页面** (`/asset-management/device-task`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"批量导入设备"按钮添加 `v-permission="'device_task:bulk_import'"`
|
- ✅ 为"批量导入设备"按钮添加 `v-permission="'device_task:bulk_import'"`
|
||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **设备管理页面** (`/asset-management/device-list`)
|
- [x] **设备管理页面** (`/asset-management/device-list`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为表格头部操作按钮添加权限控制:
|
- ✅ 为表格头部操作按钮添加权限控制:
|
||||||
- 批量分配: `v-permission="'devices:batch_allocation'"`
|
- 批量分配: `v-permission="'devices:batch_allocation'"`
|
||||||
@@ -117,6 +126,7 @@
|
|||||||
### 5. 账户管理模块 (Account Module) ✅
|
### 5. 账户管理模块 (Account Module) ✅
|
||||||
|
|
||||||
- [x] **企业客户页面** (`/account/enterprise-customer`)
|
- [x] **企业客户页面** (`/account/enterprise-customer`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增企业客户"按钮添加 `v-permission="'enterprise_customer:add'"`
|
- ✅ 为"新增企业客户"按钮添加 `v-permission="'enterprise_customer:add'"`
|
||||||
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
|
||||||
@@ -127,6 +137,7 @@
|
|||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **运营商管理页面** (`/account/carrier-management`)
|
- [x] **运营商管理页面** (`/account/carrier-management`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增运营商"按钮添加 `v-permission="'carrier:add'"`
|
- ✅ 为"新增运营商"按钮添加 `v-permission="'carrier:add'"`
|
||||||
- ✅ 为状态切换添加 `disabled: !hasAuth('carrier:update_status')`
|
- ✅ 为状态切换添加 `disabled: !hasAuth('carrier:update_status')`
|
||||||
@@ -134,16 +145,19 @@
|
|||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **订单管理页面** (`/account/orders`)
|
- [x] **订单管理页面** (`/account/orders`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"创建订单"按钮添加 `v-permission="'orders:add'"`
|
- ✅ 为"创建订单"按钮添加 `v-permission="'orders:add'"`
|
||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **提现配置页面** (`/account/commission/withdrawal-settings`)
|
- [x] **提现配置页面** (`/account/commission/withdrawal-settings`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"新增配置"按钮添加 `v-permission="'withdrawal_settings:add'"`
|
- ✅ 为"新增配置"按钮添加 `v-permission="'withdrawal_settings:add'"`
|
||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
|
|
||||||
- [x] **我的佣金页面** (`/account/commission/my-commission`)
|
- [x] **我的佣金页面** (`/account/commission/my-commission`)
|
||||||
|
|
||||||
- ✅ 引入 `useAuth` composable
|
- ✅ 引入 `useAuth` composable
|
||||||
- ✅ 为"发起提现"按钮添加 `v-permission="'my_commission:add'"`
|
- ✅ 为"发起提现"按钮添加 `v-permission="'my_commission:add'"`
|
||||||
- ✅ 验证权限控制正确工作
|
- ✅ 验证权限控制正确工作
|
||||||
@@ -164,6 +178,7 @@
|
|||||||
### 7. 测试与文档
|
### 7. 测试与文档
|
||||||
|
|
||||||
- [ ] **权限控制测试**
|
- [ ] **权限控制测试**
|
||||||
|
|
||||||
- 创建不同权限组合的测试用户
|
- 创建不同权限组合的测试用户
|
||||||
- 验证每个页面的按钮显示/隐藏符合预期
|
- 验证每个页面的按钮显示/隐藏符合预期
|
||||||
- 验证状态切换的启用/禁用符合预期
|
- 验证状态切换的启用/禁用符合预期
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
## What Changes
|
## What Changes
|
||||||
|
|
||||||
### IoT Card Operations (6 new APIs)
|
### IoT Card Operations (6 new APIs)
|
||||||
|
|
||||||
- 查询流量使用情况 (GET /api/admin/iot-cards/{iccid}/gateway-flow)
|
- 查询流量使用情况 (GET /api/admin/iot-cards/{iccid}/gateway-flow)
|
||||||
- 查询实名认证状态 (GET /api/admin/iot-cards/{iccid}/gateway-realname)
|
- 查询实名认证状态 (GET /api/admin/iot-cards/{iccid}/gateway-realname)
|
||||||
- 查询卡片状态 (GET /api/admin/iot-cards/{iccid}/gateway-status)
|
- 查询卡片状态 (GET /api/admin/iot-cards/{iccid}/gateway-status)
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
- 停用物联网卡 (POST /api/admin/iot-cards/{iccid}/stop)
|
- 停用物联网卡 (POST /api/admin/iot-cards/{iccid}/stop)
|
||||||
|
|
||||||
### Device Operations (6 new APIs)
|
### Device Operations (6 new APIs)
|
||||||
|
|
||||||
- 重启设备 (POST /api/admin/devices/by-imei/{imei}/reboot)
|
- 重启设备 (POST /api/admin/devices/by-imei/{imei}/reboot)
|
||||||
- 重置设备 (POST /api/admin/devices/by-imei/{imei}/reset)
|
- 重置设备 (POST /api/admin/devices/by-imei/{imei}/reset)
|
||||||
- 设置限速 (PUT /api/admin/devices/by-imei/{imei}/speed-limit)
|
- 设置限速 (PUT /api/admin/devices/by-imei/{imei}/speed-limit)
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
- 设置WiFi (PUT /api/admin/devices/by-imei/{imei}/wifi)
|
- 设置WiFi (PUT /api/admin/devices/by-imei/{imei}/wifi)
|
||||||
|
|
||||||
### UI Changes
|
### UI Changes
|
||||||
|
|
||||||
- 在 `/asset-management/iot-card-management` 页面添加操作按钮:
|
- 在 `/asset-management/iot-card-management` 页面添加操作按钮:
|
||||||
- "查询流量"按钮(主要操作)
|
- "查询流量"按钮(主要操作)
|
||||||
- "更多操作"下拉菜单(包含其他5个操作)
|
- "更多操作"下拉菜单(包含其他5个操作)
|
||||||
@@ -32,10 +35,12 @@
|
|||||||
## Impact
|
## Impact
|
||||||
|
|
||||||
### Affected Specs
|
### Affected Specs
|
||||||
|
|
||||||
- **NEW**: `specs/iot-card-operations/spec.md` - IoT卡操作相关的所有需求
|
- **NEW**: `specs/iot-card-operations/spec.md` - IoT卡操作相关的所有需求
|
||||||
- **NEW**: `specs/device-operations/spec.md` - 设备操作相关的所有需求
|
- **NEW**: `specs/device-operations/spec.md` - 设备操作相关的所有需求
|
||||||
|
|
||||||
### Affected Code
|
### Affected Code
|
||||||
|
|
||||||
- **API层**:
|
- **API层**:
|
||||||
- 新增 `src/api/modules/iotCard.ts` - IoT卡操作API方法
|
- 新增 `src/api/modules/iotCard.ts` - IoT卡操作API方法
|
||||||
- 新增 `src/api/modules/device.ts` - 设备操作API方法
|
- 新增 `src/api/modules/device.ts` - 设备操作API方法
|
||||||
@@ -47,6 +52,7 @@
|
|||||||
- 修改 `src/views/asset-management/devices/index.vue` - 添加操作按钮和对话框
|
- 修改 `src/views/asset-management/devices/index.vue` - 添加操作按钮和对话框
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
无破坏性变更。所有变更都是增量式的新功能添加。
|
无破坏性变更。所有变更都是增量式的新功能添加。
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
**API端点**: `GET /api/admin/shops/{shop_id}/roles`
|
**API端点**: `GET /api/admin/shops/{shop_id}/roles`
|
||||||
|
|
||||||
**路径参数**:
|
**路径参数**:
|
||||||
|
|
||||||
- `shop_id` (integer, required): 店铺ID
|
- `shop_id` (integer, required): 店铺ID
|
||||||
|
|
||||||
**响应数据**:
|
**响应数据**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
shop_id: number // 店铺ID
|
shop_id: number // 店铺ID
|
||||||
@@ -54,9 +56,11 @@ interface ShopRole {
|
|||||||
**API端点**: `POST /api/admin/shops/{shop_id}/roles`
|
**API端点**: `POST /api/admin/shops/{shop_id}/roles`
|
||||||
|
|
||||||
**路径参数**:
|
**路径参数**:
|
||||||
|
|
||||||
- `shop_id` (integer, required): 店铺ID
|
- `shop_id` (integer, required): 店铺ID
|
||||||
|
|
||||||
**请求体**:
|
**请求体**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
role_ids: number[] | null // 角色ID列表
|
role_ids: number[] | null // 角色ID列表
|
||||||
@@ -106,6 +110,7 @@ interface ShopRole {
|
|||||||
**API端点**: `DELETE /api/admin/shops/{shop_id}/roles/{role_id}`
|
**API端点**: `DELETE /api/admin/shops/{shop_id}/roles/{role_id}`
|
||||||
|
|
||||||
**路径参数**:
|
**路径参数**:
|
||||||
|
|
||||||
- `shop_id` (integer, required): 店铺ID
|
- `shop_id` (integer, required): 店铺ID
|
||||||
- `role_id` (integer, required): 角色ID
|
- `role_id` (integer, required): 角色ID
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,13 @@
|
|||||||
### 完成的功能
|
### 完成的功能
|
||||||
|
|
||||||
1. **类型定义** - 已在 `src/types/api/shop.ts` 添加了三个接口:
|
1. **类型定义** - 已在 `src/types/api/shop.ts` 添加了三个接口:
|
||||||
|
|
||||||
- `ShopRoleResponse`: 店铺角色响应实体
|
- `ShopRoleResponse`: 店铺角色响应实体
|
||||||
- `ShopRolesResponse`: 店铺角色列表响应
|
- `ShopRolesResponse`: 店铺角色列表响应
|
||||||
- `AssignShopRolesRequest`: 分配角色请求
|
- `AssignShopRolesRequest`: 分配角色请求
|
||||||
|
|
||||||
2. **API 服务层** - 已在 `src/api/modules/shop.ts` 的 `ShopService` 类中添加:
|
2. **API 服务层** - 已在 `src/api/modules/shop.ts` 的 `ShopService` 类中添加:
|
||||||
|
|
||||||
- `getShopRoles(shopId)`: 获取店铺默认角色列表
|
- `getShopRoles(shopId)`: 获取店铺默认角色列表
|
||||||
- `assignShopRoles(shopId, data)`: 分配店铺默认角色
|
- `assignShopRoles(shopId, data)`: 分配店铺默认角色
|
||||||
- `deleteShopRole(shopId, roleId)`: 删除店铺默认角色
|
- `deleteShopRole(shopId, roleId)`: 删除店铺默认角色
|
||||||
@@ -56,6 +58,7 @@
|
|||||||
### 待测试项
|
### 待测试项
|
||||||
|
|
||||||
所有开发任务已完成,现在需要进行功能测试以确保:
|
所有开发任务已完成,现在需要进行功能测试以确保:
|
||||||
|
|
||||||
- API 调用正常工作
|
- API 调用正常工作
|
||||||
- UI 交互符合预期
|
- UI 交互符合预期
|
||||||
- 边界情况处理正确
|
- 边界情况处理正确
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
## What Changes
|
## What Changes
|
||||||
|
|
||||||
- **BREAKING**: 重命名 4 个 API 端点的请求/响应字段
|
- **BREAKING**: 重命名 4 个 API 端点的请求/响应字段
|
||||||
|
|
||||||
- `PATCH /api/admin/iot-cards/series-binding` - 请求参数 `series_allocation_id` → `series_id`
|
- `PATCH /api/admin/iot-cards/series-binding` - 请求参数 `series_allocation_id` → `series_id`
|
||||||
- `PATCH /api/admin/devices/series-binding` - 请求参数 `series_allocation_id` → `series_id`
|
- `PATCH /api/admin/devices/series-binding` - 请求参数 `series_allocation_id` → `series_id`
|
||||||
- `GET /api/admin/iot-cards/standalone` - 查询参数和响应字段 `series_allocation_id` → `series_id`
|
- `GET /api/admin/iot-cards/standalone` - 查询参数和响应字段 `series_allocation_id` → `series_id`
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ The system SHALL support filtering devices by package series ID in query paramet
|
|||||||
The system SHALL include package series ID in device response data.
|
The system SHALL include package series ID in device response data.
|
||||||
|
|
||||||
**Response Field**: `series_id` (number | null)
|
**Response Field**: `series_id` (number | null)
|
||||||
|
|
||||||
- Value is package series ID if device is bound to a series
|
- Value is package series ID if device is bound to a series
|
||||||
- Value is null if device has no series binding
|
- Value is null if device has no series binding
|
||||||
|
|
||||||
@@ -58,18 +59,21 @@ The system SHALL provide an endpoint to batch set package series binding for dev
|
|||||||
**Endpoint**: `PATCH /api/admin/devices/series-binding`
|
**Endpoint**: `PATCH /api/admin/devices/series-binding`
|
||||||
|
|
||||||
**Request Body**:
|
**Request Body**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"device_ids": [1, 2, 3],
|
"device_ids": [1, 2, 3],
|
||||||
"series_id": 123 // Changed from series_allocation_id
|
"series_id": 123 // Changed from series_allocation_id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Field Definitions**:
|
**Field Definitions**:
|
||||||
|
|
||||||
- `device_ids`: Array of device IDs (max 500 items)
|
- `device_ids`: Array of device IDs (max 500 items)
|
||||||
- `series_id`: Package series ID (tb_package_series.id), 0 means clear association
|
- `series_id`: Package series ID (tb_package_series.id), 0 means clear association
|
||||||
|
|
||||||
**Response**:
|
**Response**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ The system SHALL support filtering standalone IoT cards by package series ID in
|
|||||||
The system SHALL include package series ID in standalone IoT card response data.
|
The system SHALL include package series ID in standalone IoT card response data.
|
||||||
|
|
||||||
**Response Field**: `series_id` (number | null)
|
**Response Field**: `series_id` (number | null)
|
||||||
|
|
||||||
- Value is package series ID if card is bound to a series
|
- Value is package series ID if card is bound to a series
|
||||||
- Value is null if card has no series binding
|
- Value is null if card has no series binding
|
||||||
|
|
||||||
@@ -58,18 +59,21 @@ The system SHALL provide an endpoint to batch set package series binding for IoT
|
|||||||
**Endpoint**: `PATCH /api/admin/iot-cards/series-binding`
|
**Endpoint**: `PATCH /api/admin/iot-cards/series-binding`
|
||||||
|
|
||||||
**Request Body**:
|
**Request Body**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"iccids": ["898600..."],
|
"iccids": ["898600..."],
|
||||||
"series_id": 123 // Changed from series_allocation_id
|
"series_id": 123 // Changed from series_allocation_id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Field Definitions**:
|
**Field Definitions**:
|
||||||
|
|
||||||
- `iccids`: Array of ICCIDs (max 500 items)
|
- `iccids`: Array of ICCIDs (max 500 items)
|
||||||
- `series_id`: Package series ID (tb_package_series.id), 0 means clear association
|
- `series_id`: Package series ID (tb_package_series.id), 0 means clear association
|
||||||
|
|
||||||
**Response**:
|
**Response**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
## 1. 类型定义更新
|
## 1. 类型定义更新
|
||||||
|
|
||||||
### 1.1 批量设置接口参数重命名
|
### 1.1 批量设置接口参数重命名
|
||||||
|
|
||||||
- [x] 1.1.1 更新 `src/types/api/device.ts` 中 BatchSetDeviceSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id`
|
- [x] 1.1.1 更新 `src/types/api/device.ts` 中 BatchSetDeviceSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id`
|
||||||
- [x] 1.1.2 更新 `src/types/api/card.ts` 中 BatchSetCardSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id`
|
- [x] 1.1.2 更新 `src/types/api/card.ts` 中 BatchSetCardSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id`
|
||||||
|
|
||||||
### 1.2 查询参数新增字段
|
### 1.2 查询参数新增字段
|
||||||
|
|
||||||
- [x] 1.2.1 在 `src/types/api/device.ts` 的 DeviceQueryParams 接口中添加 `series_id?: number` 查询参数
|
- [x] 1.2.1 在 `src/types/api/device.ts` 的 DeviceQueryParams 接口中添加 `series_id?: number` 查询参数
|
||||||
- [x] 1.2.2 在 `src/types/api/card.ts` 的 StandaloneCardQueryParams 接口中添加 `series_id?: number` 查询参数
|
- [x] 1.2.2 在 `src/types/api/card.ts` 的 StandaloneCardQueryParams 接口中添加 `series_id?: number` 查询参数
|
||||||
|
|
||||||
### 1.3 响应类型新增字段
|
### 1.3 响应类型新增字段
|
||||||
|
|
||||||
- [x] 1.3.1 在 `src/types/api/device.ts` 的 Device 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面)
|
- [x] 1.3.1 在 `src/types/api/device.ts` 的 Device 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面)
|
||||||
- [x] 1.3.2 在 `src/types/api/card.ts` 的 StandaloneIotCard 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面)
|
- [x] 1.3.2 在 `src/types/api/card.ts` 的 StandaloneIotCard 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
**修改**: `src/types/api/packageManagement.ts`
|
**修改**: `src/types/api/packageManagement.ts`
|
||||||
|
|
||||||
**套餐系列相关类型**:
|
**套餐系列相关类型**:
|
||||||
|
|
||||||
- `PackageSeriesResponse` - 新增 `enable_one_time_commission` 和 `one_time_commission_config`
|
- `PackageSeriesResponse` - 新增 `enable_one_time_commission` 和 `one_time_commission_config`
|
||||||
- `CreatePackageSeriesRequest` - 新增一次性佣金配置字段
|
- `CreatePackageSeriesRequest` - 新增一次性佣金配置字段
|
||||||
- `UpdatePackageSeriesRequest` - 新增一次性佣金配置字段
|
- `UpdatePackageSeriesRequest` - 新增一次性佣金配置字段
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
- 新增 `OneTimeCommissionTier` - 梯度佣金档位配置
|
- 新增 `OneTimeCommissionTier` - 梯度佣金档位配置
|
||||||
|
|
||||||
**系列分配相关类型**:
|
**系列分配相关类型**:
|
||||||
|
|
||||||
- **BREAKING**: `ShopSeriesAllocationResponse` - 完全重构字段结构
|
- **BREAKING**: `ShopSeriesAllocationResponse` - 完全重构字段结构
|
||||||
- 移除: `base_commission` (BaseCommissionConfig)
|
- 移除: `base_commission` (BaseCommissionConfig)
|
||||||
- 新增: `series_code`, `enable_force_recharge`, `enable_one_time_commission`, `force_recharge_amount`, `force_recharge_trigger_type`, `one_time_commission_amount`, `one_time_commission_threshold`, `one_time_commission_trigger`
|
- 新增: `series_code`, `enable_force_recharge`, `enable_one_time_commission`, `force_recharge_amount`, `force_recharge_trigger_type`, `one_time_commission_amount`, `one_time_commission_threshold`, `one_time_commission_trigger`
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
- 移除不再使用的类型: `BaseCommissionConfig`, `TierEntry`, `TierCommissionConfig`, `OneTimeCommissionTierEntry`, `OneTimeCommissionConfig`(旧版)
|
- 移除不再使用的类型: `BaseCommissionConfig`, `TierEntry`, `TierCommissionConfig`, `OneTimeCommissionTierEntry`, `OneTimeCommissionConfig`(旧版)
|
||||||
|
|
||||||
**单套餐分配相关类型**:
|
**单套餐分配相关类型**:
|
||||||
|
|
||||||
- `ShopPackageAllocationResponse` - 字段调整
|
- `ShopPackageAllocationResponse` - 字段调整
|
||||||
- 新增: `series_id`, `series_name`, `series_allocation_id`, `allocator_shop_id`, `allocator_shop_name`
|
- 新增: `series_id`, `series_name`, `series_allocation_id`, `allocator_shop_id`, `allocator_shop_name`
|
||||||
- 移除: `allocation_id` (重命名为 `series_allocation_id`), `calculated_cost_price`
|
- 移除: `allocation_id` (重命名为 `series_allocation_id`), `calculated_cost_price`
|
||||||
@@ -44,6 +47,7 @@
|
|||||||
### 3. 页面/组件适配(实施阶段处理)
|
### 3. 页面/组件适配(实施阶段处理)
|
||||||
|
|
||||||
以下文件需要适配新的数据结构(本提案阶段不编写代码):
|
以下文件需要适配新的数据结构(本提案阶段不编写代码):
|
||||||
|
|
||||||
- 套餐系列管理页面 - 需支持一次性佣金配置表单
|
- 套餐系列管理页面 - 需支持一次性佣金配置表单
|
||||||
- 系列分配页面 - 需重构佣金配置表单UI
|
- 系列分配页面 - 需重构佣金配置表单UI
|
||||||
- 单套餐分配页面 - 需展示新增的关联字段
|
- 单套餐分配页面 - 需展示新增的关联字段
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
套餐系列的数据模型SHALL支持一次性佣金配置,包括固定佣金、梯度佣金、强制充值、时效类型等高级功能。
|
套餐系列的数据模型SHALL支持一次性佣金配置,包括固定佣金、梯度佣金、强制充值、时效类型等高级功能。
|
||||||
|
|
||||||
**字段定义**:
|
**字段定义**:
|
||||||
|
|
||||||
- `id` (number) - 系列ID
|
- `id` (number) - 系列ID
|
||||||
- `series_code` (string) - 系列编码,唯一标识
|
- `series_code` (string) - 系列编码,唯一标识
|
||||||
- `series_name` (string) - 系列名称
|
- `series_name` (string) - 系列名称
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
- `updated_at` (string) - 更新时间
|
- `updated_at` (string) - 更新时间
|
||||||
|
|
||||||
**一次性佣金配置结构** (`SeriesOneTimeCommissionConfig`):
|
**一次性佣金配置结构** (`SeriesOneTimeCommissionConfig`):
|
||||||
|
|
||||||
- `enable` (boolean) - 是否启用一次性佣金
|
- `enable` (boolean) - 是否启用一次性佣金
|
||||||
- `commission_type` (string) - 佣金类型: "fixed"(固定) | "tiered"(梯度)
|
- `commission_type` (string) - 佣金类型: "fixed"(固定) | "tiered"(梯度)
|
||||||
- `commission_amount` (number, optional) - 固定佣金金额(分),commission_type=fixed时使用
|
- `commission_amount` (number, optional) - 固定佣金金额(分),commission_type=fixed时使用
|
||||||
@@ -31,6 +33,7 @@
|
|||||||
- `validity_value` (string, optional) - 时效值(日期字符串或月数)
|
- `validity_value` (string, optional) - 时效值(日期字符串或月数)
|
||||||
|
|
||||||
**梯度档位结构** (`OneTimeCommissionTier`):
|
**梯度档位结构** (`OneTimeCommissionTier`):
|
||||||
|
|
||||||
- `threshold` (number) - 达标阈值
|
- `threshold` (number) - 达标阈值
|
||||||
- `dimension` (string) - 统计维度: "sales_count"(销量) | "sales_amount"(销售额)
|
- `dimension` (string) - 统计维度: "sales_count"(销量) | "sales_amount"(销售额)
|
||||||
- `amount` (number) - 佣金金额(分)
|
- `amount` (number) - 佣金金额(分)
|
||||||
@@ -64,10 +67,7 @@
|
|||||||
- `commission_type`: "tiered"
|
- `commission_type`: "tiered"
|
||||||
- `threshold`: 5000
|
- `threshold`: 5000
|
||||||
- `trigger_type`: "accumulated_recharge"
|
- `trigger_type`: "accumulated_recharge"
|
||||||
- `tiers`: [
|
- `tiers`: [ { threshold: 10, dimension: "sales_count", amount: 3000 }, { threshold: 50, dimension: "sales_count", amount: 8000 } ]
|
||||||
{ threshold: 10, dimension: "sales_count", amount: 3000 },
|
|
||||||
{ threshold: 50, dimension: "sales_count", amount: 8000 }
|
|
||||||
]
|
|
||||||
- `validity_type`: "relative"
|
- `validity_type`: "relative"
|
||||||
- `validity_value`: "12" (12个月)
|
- `validity_value`: "12" (12个月)
|
||||||
- **THEN** 系统SHALL创建套餐系列,并保存梯度佣金配置
|
- **THEN** 系统SHALL创建套餐系列,并保存梯度佣金配置
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
单套餐分配的数据模型SHALL新增系列关联字段和分配者信息字段,以支持更完整的分配关系追溯。
|
单套餐分配的数据模型SHALL新增系列关联字段和分配者信息字段,以支持更完整的分配关系追溯。
|
||||||
|
|
||||||
**字段定义**:
|
**字段定义**:
|
||||||
|
|
||||||
- `id` (number) - 分配ID
|
- `id` (number) - 分配ID
|
||||||
- `package_id` (number) - 套餐ID
|
- `package_id` (number) - 套餐ID
|
||||||
- `package_code` (string) - 套餐编码
|
- `package_code` (string) - 套餐编码
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
- `updated_at` (string) - 更新时间
|
- `updated_at` (string) - 更新时间
|
||||||
|
|
||||||
**查询参数** (`ShopPackageAllocationQueryParams`):
|
**查询参数** (`ShopPackageAllocationQueryParams`):
|
||||||
|
|
||||||
- `page` (number, optional) - 页码
|
- `page` (number, optional) - 页码
|
||||||
- `page_size` (number, optional) - 每页数量
|
- `page_size` (number, optional) - 每页数量
|
||||||
- `shop_id` (number, optional) - 被分配的店铺ID
|
- `shop_id` (number, optional) - 被分配的店铺ID
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
套餐系列分配的数据模型SHALL重构为更细粒度的佣金配置结构,移除旧的 `base_commission` 模式,采用独立的一次性佣金和强制充值配置字段。
|
套餐系列分配的数据模型SHALL重构为更细粒度的佣金配置结构,移除旧的 `base_commission` 模式,采用独立的一次性佣金和强制充值配置字段。
|
||||||
|
|
||||||
**字段定义**:
|
**字段定义**:
|
||||||
|
|
||||||
- `id` (number) - 分配ID
|
- `id` (number) - 分配ID
|
||||||
- `series_id` (number) - 套餐系列ID
|
- `series_id` (number) - 套餐系列ID
|
||||||
- `series_name` (string) - 套餐系列名称
|
- `series_name` (string) - 套餐系列名称
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
- `updated_at` (string) - 更新时间
|
- `updated_at` (string) - 更新时间
|
||||||
|
|
||||||
**创建请求字段** (`CreateShopSeriesAllocationRequest`):
|
**创建请求字段** (`CreateShopSeriesAllocationRequest`):
|
||||||
|
|
||||||
- `series_id` (number, required) - 套餐系列ID
|
- `series_id` (number, required) - 套餐系列ID
|
||||||
- `shop_id` (number, required) - 被分配的店铺ID
|
- `shop_id` (number, required) - 被分配的店铺ID
|
||||||
- `one_time_commission_amount` (number, required) - 一次性佣金金额上限(分)
|
- `one_time_commission_amount` (number, required) - 一次性佣金金额上限(分)
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
- `force_recharge_trigger_type` (number, optional) - 强充触发类型
|
- `force_recharge_trigger_type` (number, optional) - 强充触发类型
|
||||||
|
|
||||||
**更新请求字段** (`UpdateShopSeriesAllocationRequest`):
|
**更新请求字段** (`UpdateShopSeriesAllocationRequest`):
|
||||||
|
|
||||||
- 所有字段均为可选,允许部分更新
|
- 所有字段均为可选,允许部分更新
|
||||||
- `enable_one_time_commission` (boolean, optional)
|
- `enable_one_time_commission` (boolean, optional)
|
||||||
- `one_time_commission_amount` (number, optional)
|
- `one_time_commission_amount` (number, optional)
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ export class AgentRechargeService extends BaseService {
|
|||||||
static getAgentRecharges(
|
static getAgentRecharges(
|
||||||
params?: AgentRechargeQueryParams
|
params?: AgentRechargeQueryParams
|
||||||
): Promise<BaseResponse<AgentRechargeListResponse>> {
|
): Promise<BaseResponse<AgentRechargeListResponse>> {
|
||||||
return this.get<BaseResponse<AgentRechargeListResponse>>(
|
return this.get<BaseResponse<AgentRechargeListResponse>>('/api/admin/agent-recharges', params)
|
||||||
'/api/admin/agent-recharges',
|
|
||||||
params
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -383,19 +383,19 @@ export class CardService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用 IoT 卡
|
* 启用 IoT 卡 单卡复机
|
||||||
* @param id IoT卡ID
|
* @param iccid
|
||||||
*/
|
*/
|
||||||
static enableIotCard(id: number): Promise<BaseResponse> {
|
static enableIotCard(iccid: string): Promise<BaseResponse> {
|
||||||
return this.post<BaseResponse>(`/api/admin/iot-cards/${id}/enable`, {})
|
return this.post<BaseResponse>(`/api/admin/assets/card/${iccid}/start`, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停用 IoT 卡
|
* 停用 IoT 卡 单卡停机
|
||||||
* @param id IoT卡ID
|
* @param iccid
|
||||||
*/
|
*/
|
||||||
static disableIotCard(id: number): Promise<BaseResponse> {
|
static disableIotCard(iccid: string): Promise<BaseResponse> {
|
||||||
return this.post<BaseResponse>(`/api/admin/iot-cards/${id}/disable`, {})
|
return this.post<BaseResponse>(`/api/admin/assets/card/${iccid}/stop`, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -410,17 +410,8 @@ export class CardService extends BaseService {
|
|||||||
* 获取套餐流量详单(每日流量记录)
|
* 获取套餐流量详单(每日流量记录)
|
||||||
* @param packageUsageId 套餐使用记录ID
|
* @param packageUsageId 套餐使用记录ID
|
||||||
*/
|
*/
|
||||||
static getPackageDailyRecords(packageUsageId: number): Promise<BaseResponse<{
|
static getPackageDailyRecords(packageUsageId: number): Promise<
|
||||||
package_name: string
|
BaseResponse<{
|
||||||
package_usage_id: number
|
|
||||||
total_usage_mb: number
|
|
||||||
records: Array<{
|
|
||||||
date: string
|
|
||||||
daily_usage_mb: number
|
|
||||||
cumulative_usage_mb: number
|
|
||||||
}>
|
|
||||||
}>> {
|
|
||||||
return this.get<BaseResponse<{
|
|
||||||
package_name: string
|
package_name: string
|
||||||
package_usage_id: number
|
package_usage_id: number
|
||||||
total_usage_mb: number
|
total_usage_mb: number
|
||||||
@@ -429,6 +420,19 @@ export class CardService extends BaseService {
|
|||||||
daily_usage_mb: number
|
daily_usage_mb: number
|
||||||
cumulative_usage_mb: number
|
cumulative_usage_mb: number
|
||||||
}>
|
}>
|
||||||
}>>(`/api/admin/package-usage/${packageUsageId}/daily-records`)
|
}>
|
||||||
|
> {
|
||||||
|
return this.get<
|
||||||
|
BaseResponse<{
|
||||||
|
package_name: string
|
||||||
|
package_usage_id: number
|
||||||
|
total_usage_mb: number
|
||||||
|
records: Array<{
|
||||||
|
date: string
|
||||||
|
daily_usage_mb: number
|
||||||
|
cumulative_usage_mb: number
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
>(`/api/admin/package-usage/${packageUsageId}/daily-records`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export class DeviceService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
static rebootDevice(imei: string): Promise<BaseResponse<DeviceOperationResponse>> {
|
static rebootDevice(imei: string): Promise<BaseResponse<DeviceOperationResponse>> {
|
||||||
return this.post<BaseResponse<DeviceOperationResponse>>(
|
return this.post<BaseResponse<DeviceOperationResponse>>(
|
||||||
`/api/admin/devices/by-imei/${imei}/reboot`,
|
`/api/admin/devices/by-identifier/${imei}/reboot`,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ export class DeviceService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
static resetDevice(imei: string): Promise<BaseResponse<DeviceOperationResponse>> {
|
static resetDevice(imei: string): Promise<BaseResponse<DeviceOperationResponse>> {
|
||||||
return this.post<BaseResponse<DeviceOperationResponse>>(
|
return this.post<BaseResponse<DeviceOperationResponse>>(
|
||||||
`/api/admin/devices/by-imei/${imei}/reset`,
|
`/api/admin/devices/by-identifier/${imei}/reset`,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ export class DeviceService extends BaseService {
|
|||||||
data: SetSpeedLimitRequest
|
data: SetSpeedLimitRequest
|
||||||
): Promise<BaseResponse<DeviceOperationResponse>> {
|
): Promise<BaseResponse<DeviceOperationResponse>> {
|
||||||
return this.put<BaseResponse<DeviceOperationResponse>>(
|
return this.put<BaseResponse<DeviceOperationResponse>>(
|
||||||
`/api/admin/devices/by-imei/${imei}/speed-limit`,
|
`/api/admin/devices/by-identifier/${imei}/speed-limit`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ export class DeviceService extends BaseService {
|
|||||||
data: SwitchCardRequest
|
data: SwitchCardRequest
|
||||||
): Promise<BaseResponse<DeviceOperationResponse>> {
|
): Promise<BaseResponse<DeviceOperationResponse>> {
|
||||||
return this.post<BaseResponse<DeviceOperationResponse>>(
|
return this.post<BaseResponse<DeviceOperationResponse>>(
|
||||||
`/api/admin/devices/by-imei/${imei}/switch-card`,
|
`/api/admin/devices/by-identifier/${imei}/switch-card`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ export class DeviceService extends BaseService {
|
|||||||
data: SetWiFiRequest
|
data: SetWiFiRequest
|
||||||
): Promise<BaseResponse<DeviceOperationResponse>> {
|
): Promise<BaseResponse<DeviceOperationResponse>> {
|
||||||
return this.put<BaseResponse<DeviceOperationResponse>>(
|
return this.put<BaseResponse<DeviceOperationResponse>>(
|
||||||
`/api/admin/devices/by-imei/${imei}/wifi`,
|
`/api/admin/devices/by-identifier/${imei}/wifi`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ export class ExchangeService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
static getExchanges(
|
static getExchanges(
|
||||||
params?: ExchangeQueryParams
|
params?: ExchangeQueryParams
|
||||||
): Promise<BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }>> {
|
): Promise<
|
||||||
|
BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }>
|
||||||
|
> {
|
||||||
return this.get<
|
return this.get<
|
||||||
BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }>
|
BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }>
|
||||||
>('/api/admin/exchanges', params)
|
>('/api/admin/exchanges', params)
|
||||||
@@ -123,7 +125,10 @@ export class ExchangeService extends BaseService {
|
|||||||
* @param id 换货单ID
|
* @param id 换货单ID
|
||||||
* @param data 发货参数
|
* @param data 发货参数
|
||||||
*/
|
*/
|
||||||
static shipExchange(id: number, data: ShipExchangeRequest): Promise<BaseResponse<ExchangeResponse>> {
|
static shipExchange(
|
||||||
|
id: number,
|
||||||
|
data: ShipExchangeRequest
|
||||||
|
): Promise<BaseResponse<ExchangeResponse>> {
|
||||||
return this.post<BaseResponse<ExchangeResponse>>(`/api/admin/exchanges/${id}/ship`, data)
|
return this.post<BaseResponse<ExchangeResponse>>(`/api/admin/exchanges/${id}/ship`, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ export class ManualTriggerService extends BaseService {
|
|||||||
* 批量手动触发
|
* 批量手动触发
|
||||||
* POST /api/admin/polling-manual-trigger/batch
|
* POST /api/admin/polling-manual-trigger/batch
|
||||||
*/
|
*/
|
||||||
static batchTrigger(
|
static batchTrigger(data: BatchManualTriggerRequest): Promise<BaseResponse<ManualTriggerLog>> {
|
||||||
data: BatchManualTriggerRequest
|
|
||||||
): Promise<BaseResponse<ManualTriggerLog>> {
|
|
||||||
return this.post<BaseResponse<ManualTriggerLog>>(
|
return this.post<BaseResponse<ManualTriggerLog>>(
|
||||||
'/api/admin/polling-manual-trigger/batch',
|
'/api/admin/polling-manual-trigger/batch',
|
||||||
data
|
data
|
||||||
@@ -46,9 +44,7 @@ export class ManualTriggerService extends BaseService {
|
|||||||
* 单卡手动触发
|
* 单卡手动触发
|
||||||
* POST /api/admin/polling-manual-trigger/single
|
* POST /api/admin/polling-manual-trigger/single
|
||||||
*/
|
*/
|
||||||
static singleTrigger(
|
static singleTrigger(data: SingleManualTriggerRequest): Promise<BaseResponse<ManualTriggerLog>> {
|
||||||
data: SingleManualTriggerRequest
|
|
||||||
): Promise<BaseResponse<ManualTriggerLog>> {
|
|
||||||
return this.post<BaseResponse<ManualTriggerLog>>(
|
return this.post<BaseResponse<ManualTriggerLog>>(
|
||||||
'/api/admin/polling-manual-trigger/single',
|
'/api/admin/polling-manual-trigger/single',
|
||||||
data
|
data
|
||||||
|
|||||||
@@ -51,12 +51,7 @@ export class OrderService extends BaseService {
|
|||||||
* 套餐购买预检
|
* 套餐购买预检
|
||||||
* @param data 预检请求参数
|
* @param data 预检请求参数
|
||||||
*/
|
*/
|
||||||
static purchaseCheck(
|
static purchaseCheck(data: PurchaseCheckRequest): Promise<BaseResponse<PurchaseCheckResponse>> {
|
||||||
data: PurchaseCheckRequest
|
return this.post<BaseResponse<PurchaseCheckResponse>>('/api/admin/orders/purchase-check', data)
|
||||||
): Promise<BaseResponse<PurchaseCheckResponse>> {
|
|
||||||
return this.post<BaseResponse<PurchaseCheckResponse>>(
|
|
||||||
'/api/admin/orders/purchase-check',
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ export class PollingConcurrencyService extends BaseService {
|
|||||||
* @param taskType 任务类型
|
* @param taskType 任务类型
|
||||||
* @param params 更新参数
|
* @param params 更新参数
|
||||||
*/
|
*/
|
||||||
static updateConcurrency(taskType: string, params: UpdateConcurrencyParams): Promise<BaseResponse<void>> {
|
static updateConcurrency(
|
||||||
|
taskType: string,
|
||||||
|
params: UpdateConcurrencyParams
|
||||||
|
): Promise<BaseResponse<void>> {
|
||||||
return this.put<BaseResponse<void>>(`/api/admin/polling-concurrency/${taskType}`, params)
|
return this.put<BaseResponse<void>>(`/api/admin/polling-concurrency/${taskType}`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export class ShopSeriesGrantService extends BaseService {
|
|||||||
* GET /api/admin/shop-series-grants/{id}
|
* GET /api/admin/shop-series-grants/{id}
|
||||||
* @param id 授权ID
|
* @param id 授权ID
|
||||||
*/
|
*/
|
||||||
static getShopSeriesGrantDetail(
|
static getShopSeriesGrantDetail(id: number): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
id: number
|
|
||||||
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
|
||||||
return this.getOne<ShopSeriesGrantResponse>(`/api/admin/shop-series-grants/${id}`)
|
return this.getOne<ShopSeriesGrantResponse>(`/api/admin/shop-series-grants/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +55,7 @@ export class ShopSeriesGrantService extends BaseService {
|
|||||||
id: number,
|
id: number,
|
||||||
data: UpdateShopSeriesGrantRequest
|
data: UpdateShopSeriesGrantRequest
|
||||||
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
): Promise<BaseResponse<ShopSeriesGrantResponse>> {
|
||||||
return this.update<ShopSeriesGrantResponse>(
|
return this.update<ShopSeriesGrantResponse>(`/api/admin/shop-series-grants/${id}`, data)
|
||||||
`/api/admin/shop-series-grants/${id}`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ export class WechatConfigService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* 创建支付配置
|
* 创建支付配置
|
||||||
*/
|
*/
|
||||||
static createWechatConfig(
|
static createWechatConfig(data: CreateWechatConfigRequest): Promise<BaseResponse<WechatConfig>> {
|
||||||
data: CreateWechatConfigRequest
|
|
||||||
): Promise<BaseResponse<WechatConfig>> {
|
|
||||||
return this.post<BaseResponse<WechatConfig>>('/api/admin/wechat-configs', data)
|
return this.post<BaseResponse<WechatConfig>>('/api/admin/wechat-configs', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
ElMessage.success('操作成功')
|
ElMessage.success('操作成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('批量操作失败:', error)
|
console.error('批量操作失败:', error)
|
||||||
ElMessage.error('操作失败')
|
console.log('操作失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,9 @@
|
|||||||
label: '用户类型',
|
label: '用户类型',
|
||||||
width: 110,
|
width: 110,
|
||||||
formatter: (row: PlatformAccount) => {
|
formatter: (row: PlatformAccount) => {
|
||||||
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () => getUserTypeName(row.user_type))
|
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () =>
|
||||||
|
getUserTypeName(row.user_type)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,130 +30,130 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElCard } from 'element-plus'
|
import { ElCard } from 'element-plus'
|
||||||
|
|
||||||
export interface DetailField {
|
export interface DetailField {
|
||||||
label: string // 字段标签
|
label: string // 字段标签
|
||||||
prop?: string // 数据属性路径,支持点号分隔的嵌套路径,如 'one_time_commission_config.enable'
|
prop?: string // 数据属性路径,支持点号分隔的嵌套路径,如 'one_time_commission_config.enable'
|
||||||
formatter?: (value: any, data: any) => string // 自定义格式化函数
|
formatter?: (value: any, data: any) => string // 自定义格式化函数
|
||||||
render?: (data: any) => any // 自定义渲染函数,返回 VNode
|
render?: (data: any) => any // 自定义渲染函数,返回 VNode
|
||||||
fullWidth?: boolean // 是否占据整行
|
fullWidth?: boolean // 是否占据整行
|
||||||
}
|
|
||||||
|
|
||||||
export interface DetailSection {
|
|
||||||
title: string // 分组标题
|
|
||||||
fields: DetailField[] // 字段列表
|
|
||||||
columns?: 1 | 2 // 列数,默认2列
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
sections: DetailSection[] // 详情页分组配置
|
|
||||||
data: any // 详情数据
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据点号分隔的路径获取嵌套对象的值
|
|
||||||
*/
|
|
||||||
const getNestedValue = (obj: any, path: string): any => {
|
|
||||||
if (!path) return obj
|
|
||||||
return path.split('.').reduce((acc, part) => acc?.[part], obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化字段值
|
|
||||||
*/
|
|
||||||
const formatFieldValue = (field: DetailField, data: any): string => {
|
|
||||||
const value = field.prop ? getNestedValue(data, field.prop) : data
|
|
||||||
|
|
||||||
// 如果有自定义格式化函数
|
|
||||||
if (field.formatter) {
|
|
||||||
return field.formatter(value, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认处理
|
export interface DetailSection {
|
||||||
if (value === null || value === undefined || value === '') {
|
title: string // 分组标题
|
||||||
return '-'
|
fields: DetailField[] // 字段列表
|
||||||
|
columns?: 1 | 2 // 列数,默认2列
|
||||||
}
|
}
|
||||||
|
|
||||||
return String(value)
|
interface Props {
|
||||||
}
|
sections: DetailSection[] // 详情页分组配置
|
||||||
|
data: any // 详情数据
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据点号分隔的路径获取嵌套对象的值
|
||||||
|
*/
|
||||||
|
const getNestedValue = (obj: any, path: string): any => {
|
||||||
|
if (!path) return obj
|
||||||
|
return path.split('.').reduce((acc, part) => acc?.[part], obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化字段值
|
||||||
|
*/
|
||||||
|
const formatFieldValue = (field: DetailField, data: any): string => {
|
||||||
|
const value = field.prop ? getNestedValue(data, field.prop) : data
|
||||||
|
|
||||||
|
// 如果有自定义格式化函数
|
||||||
|
if (field.formatter) {
|
||||||
|
return field.formatter(value, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认处理
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.detail-container {
|
.detail-container {
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
overflow-y: auto;
|
padding: 4px;
|
||||||
padding: 4px;
|
overflow-y: auto;
|
||||||
}
|
|
||||||
|
|
||||||
.detail-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.detail-section {
|
||||||
font-size: 16px;
|
margin-bottom: 20px;
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-fields {
|
&:last-child {
|
||||||
display: grid;
|
margin-bottom: 0;
|
||||||
gap: 16px 24px;
|
}
|
||||||
|
|
||||||
&.single-column {
|
.section-title {
|
||||||
grid-template-columns: 1fr;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.double-column {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
min-height: 32px;
|
|
||||||
|
|
||||||
&.full-width {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 140px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-value {
|
|
||||||
flex: 1;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
line-height: 32px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.section-fields {
|
.section-fields {
|
||||||
&.double-column {
|
display: grid;
|
||||||
|
gap: 16px 24px;
|
||||||
|
|
||||||
|
&.single-column {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.double-column {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-item {
|
.field-item {
|
||||||
flex-direction: column;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 32px;
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
.field-label {
|
.field-label {
|
||||||
width: 100%;
|
flex-shrink: 0;
|
||||||
margin-bottom: 4px;
|
width: 140px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 32px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-value {
|
||||||
|
flex: 1;
|
||||||
|
line-height: 32px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.section-fields {
|
||||||
|
&.double-column {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-item {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -255,7 +255,7 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
transition: background-color 0.15s ease;
|
transition: background-color 0.15s ease;
|
||||||
|
|
||||||
&:hover:not(.is-disabled):not(.no-permission) {
|
&:hover:not(.is-disabled, .no-permission) {
|
||||||
background-color: rgba(var(--art-gray-200-rgb), 0.7);
|
background-color: rgba(var(--art-gray-200-rgb), 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +267,8 @@
|
|||||||
.menu-label {
|
.menu-label {
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: normal;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,15 @@
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.table-context-menu-hint {
|
.table-context-menu-hint {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 4px 10px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
pointer-events: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgb(0 0 0 / 80%);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElDialog v-model="visible" title="设置WiFi" width="500px" @close="handleClose">
|
<ElDialog v-model="visible" title="设置WiFi" width="500px" @close="handleClose">
|
||||||
<ElForm
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="rules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="设备信息">
|
<ElFormItem label="设备信息">
|
||||||
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -96,23 +91,29 @@
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(
|
||||||
visible.value = val
|
() => props.modelValue,
|
||||||
if (val) {
|
(val) => {
|
||||||
// 重置表单
|
visible.value = val
|
||||||
formData.enabled = 1
|
if (val) {
|
||||||
formData.ssid = ''
|
// 重置表单
|
||||||
formData.password = ''
|
formData.enabled = 1
|
||||||
|
formData.ssid = ''
|
||||||
|
formData.password = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
watch(visible, (val) => {
|
watch(visible, (val) => {
|
||||||
emit('update:modelValue', val)
|
emit('update:modelValue', val)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.loading, (val) => {
|
watch(
|
||||||
confirmLoading.value = val
|
() => props.loading,
|
||||||
})
|
(val) => {
|
||||||
|
confirmLoading.value = val
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
if (!formRef.value) return
|
if (!formRef.value) return
|
||||||
|
|
||||||
await formRef.value.validate((valid) => {
|
await formRef.value.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
emit('confirm', {
|
emit('confirm', {
|
||||||
enabled: formData.enabled,
|
enabled: formData.enabled,
|
||||||
ssid: formData.ssid,
|
ssid: formData.ssid,
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElDialog v-model="visible" title="设置限速" width="500px" @close="handleClose">
|
<ElDialog v-model="visible" title="设置限速" width="500px" @close="handleClose">
|
||||||
<ElForm
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="rules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="设备信息">
|
<ElFormItem label="设备信息">
|
||||||
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -17,7 +12,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="上行速率" prop="upload_speed">
|
<ElFormItem label="上行速率" prop="upload_speed">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
@@ -27,7 +22,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -75,22 +70,28 @@
|
|||||||
upload_speed: [{ required: true, message: '请输入上行速率', trigger: 'blur' }]
|
upload_speed: [{ required: true, message: '请输入上行速率', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(
|
||||||
visible.value = val
|
() => props.modelValue,
|
||||||
if (val) {
|
(val) => {
|
||||||
// 重置表单
|
visible.value = val
|
||||||
formData.download_speed = 1024
|
if (val) {
|
||||||
formData.upload_speed = 512
|
// 重置表单
|
||||||
|
formData.download_speed = 1024
|
||||||
|
formData.upload_speed = 512
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
watch(visible, (val) => {
|
watch(visible, (val) => {
|
||||||
emit('update:modelValue', val)
|
emit('update:modelValue', val)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.loading, (val) => {
|
watch(
|
||||||
confirmLoading.value = val
|
() => props.loading,
|
||||||
})
|
(val) => {
|
||||||
|
confirmLoading.value = val
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElDialog v-model="visible" title="切换SIM卡" width="600px" @close="handleClose">
|
<ElDialog v-model="visible" title="切换SIM卡" width="600px" @close="handleClose">
|
||||||
<div v-if="loading" style="text-align: center; padding: 40px">
|
<div v-if="loading" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
||||||
:value="card.iccid"
|
:value="card.iccid"
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span>{{ card.iccid }}</span>
|
<span>{{ card.iccid }}</span>
|
||||||
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,13 +123,16 @@
|
|||||||
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(
|
||||||
visible.value = val
|
() => props.modelValue,
|
||||||
if (val) {
|
(val) => {
|
||||||
// 重置表单
|
visible.value = val
|
||||||
formData.target_iccid = ''
|
if (val) {
|
||||||
|
// 重置表单
|
||||||
|
formData.target_iccid = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
watch(visible, (val) => {
|
watch(visible, (val) => {
|
||||||
emit('update:modelValue', val)
|
emit('update:modelValue', val)
|
||||||
|
|||||||
@@ -28,9 +28,7 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
if (Array.isArray(permission)) {
|
if (Array.isArray(permission)) {
|
||||||
// 多个权限(满足任意一个即可)
|
// 多个权限(满足任意一个即可)
|
||||||
return permission.some(
|
return permission.some((perm) => userStore.hasPermission(perm) || userStore.hasButton(perm))
|
||||||
(perm) => userStore.hasPermission(perm) || userStore.hasButton(perm)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -45,9 +43,7 @@ export const useAuth = () => {
|
|||||||
if (!permissions || !Array.isArray(permissions)) return false
|
if (!permissions || !Array.isArray(permissions)) return false
|
||||||
|
|
||||||
// 需要全部满足
|
// 需要全部满足
|
||||||
return permissions.every(
|
return permissions.every((perm) => userStore.hasPermission(perm) || userStore.hasButton(perm))
|
||||||
(perm) => userStore.hasPermission(perm) || userStore.hasButton(perm)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -94,7 +94,10 @@ export function useCheckedColumns(columnsFactory: () => ColumnOption[]) {
|
|||||||
|
|
||||||
cols.forEach((column) => {
|
cols.forEach((column) => {
|
||||||
// 为普通列添加空值处理
|
// 为普通列添加空值处理
|
||||||
if (!column.type || (column.type !== 'selection' && column.type !== 'expand' && column.type !== 'index')) {
|
if (
|
||||||
|
!column.type ||
|
||||||
|
(column.type !== 'selection' && column.type !== 'expand' && column.type !== 'index')
|
||||||
|
) {
|
||||||
const originalFormatter = column.formatter
|
const originalFormatter = column.formatter
|
||||||
|
|
||||||
// 包装原有的 formatter,在其基础上添加空值处理
|
// 包装原有的 formatter,在其基础上添加空值处理
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ export function useTableContextMenu() {
|
|||||||
/**
|
/**
|
||||||
* 单元格鼠标进入事件处理
|
* 单元格鼠标进入事件处理
|
||||||
*/
|
*/
|
||||||
const handleCellMouseEnter = (
|
const handleCellMouseEnter = (row: any, column: any, cell: HTMLElement, event: MouseEvent) => {
|
||||||
row: any,
|
|
||||||
column: any,
|
|
||||||
cell: HTMLElement,
|
|
||||||
event: MouseEvent
|
|
||||||
) => {
|
|
||||||
// 清除之前的定时器
|
// 清除之前的定时器
|
||||||
if (hintTimer) {
|
if (hintTimer) {
|
||||||
clearTimeout(hintTimer)
|
clearTimeout(hintTimer)
|
||||||
|
|||||||
14
src/types/auto-imports.d.ts
vendored
14
src/types/auto-imports.d.ts
vendored
@@ -7,19 +7,19 @@
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
const ElButton: typeof import('element-plus/es')['ElButton']
|
const ElButton: (typeof import('element-plus/es'))['ElButton']
|
||||||
const ElButton2: typeof import('element-plus/es')['ElButton2']
|
const ElButton2: (typeof import('element-plus/es'))['ElButton2']
|
||||||
const ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
const ElDropdown: (typeof import('element-plus/es'))['ElDropdown']
|
||||||
const ElDropdown2: typeof import('element-plus/es')['ElDropdown2']
|
const ElDropdown2: (typeof import('element-plus/es'))['ElDropdown2']
|
||||||
const ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
const ElDropdownItem: (typeof import('element-plus/es'))['ElDropdownItem']
|
||||||
const ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
const ElDropdownMenu: (typeof import('element-plus/es'))['ElDropdownMenu']
|
||||||
const ElMessage: (typeof import('element-plus/es'))['ElMessage']
|
const ElMessage: (typeof import('element-plus/es'))['ElMessage']
|
||||||
const ElMessageBox: (typeof import('element-plus/es'))['ElMessageBox']
|
const ElMessageBox: (typeof import('element-plus/es'))['ElMessageBox']
|
||||||
const ElNotification: (typeof import('element-plus/es'))['ElNotification']
|
const ElNotification: (typeof import('element-plus/es'))['ElNotification']
|
||||||
const ElPopconfirm: (typeof import('element-plus/es'))['ElPopconfirm']
|
const ElPopconfirm: (typeof import('element-plus/es'))['ElPopconfirm']
|
||||||
const ElPopover: (typeof import('element-plus/es'))['ElPopover']
|
const ElPopover: (typeof import('element-plus/es'))['ElPopover']
|
||||||
const ElTableColumn: (typeof import('element-plus/es'))['ElTableColumn']
|
const ElTableColumn: (typeof import('element-plus/es'))['ElTableColumn']
|
||||||
const ElTag: typeof import('element-plus/es')['ElTag']
|
const ElTag: (typeof import('element-plus/es'))['ElTag']
|
||||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
|||||||
@@ -888,8 +888,8 @@
|
|||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
@@ -899,8 +899,8 @@
|
|||||||
|
|
||||||
.account-info {
|
.account-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.account-name {
|
.account-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -911,25 +911,25 @@
|
|||||||
|
|
||||||
.role-transfer-container {
|
.role-transfer-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 20px 0;
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.transfer-panel {
|
.transfer-panel {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 380px;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
|
||||||
max-width: 380px;
|
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
@@ -943,8 +943,8 @@
|
|||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.role-list {
|
.role-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -964,9 +964,9 @@
|
|||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-checkbox) {
|
:deep(.el-checkbox) {
|
||||||
@@ -980,9 +980,9 @@
|
|||||||
|
|
||||||
.assigned-role-item {
|
.assigned-role-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -999,9 +999,9 @@
|
|||||||
.transfer-buttons {
|
.transfer-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
|
|||||||
@@ -1065,8 +1065,8 @@
|
|||||||
.card-selection-info {
|
.card-selection-info {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: var(--el-color-info);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--el-color-info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-pagination {
|
.card-pagination {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton @click="showDialog('add')" v-permission="'platform_account:add'">新增平台账号</ElButton>
|
<ElButton @click="showDialog('add')" v-permission="'platform_account:add'"
|
||||||
|
>新增平台账号</ElButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -158,18 +160,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
||||||
<div
|
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
|
||||||
v-for="role in filteredAvailableRoles"
|
|
||||||
:key="role.ID"
|
|
||||||
class="role-item"
|
|
||||||
>
|
|
||||||
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
||||||
<span class="role-info">
|
<span class="role-info">
|
||||||
<span>{{ role.role_name }}</span>
|
<span>{{ role.role_name }}</span>
|
||||||
<ElTag
|
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
@@ -212,19 +207,11 @@
|
|||||||
>
|
>
|
||||||
<span class="role-info">
|
<span class="role-info">
|
||||||
<span>{{ role.role_name }}</span>
|
<span>{{ role.role_name }}</span>
|
||||||
<ElTag
|
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
<ElButton
|
<ElButton type="danger" size="small" link @click="removeSingleRole(role.ID)">
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
link
|
|
||||||
@click="removeSingleRole(role.ID)"
|
|
||||||
>
|
|
||||||
移除
|
移除
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -956,8 +943,8 @@
|
|||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
@@ -967,8 +954,8 @@
|
|||||||
|
|
||||||
.account-info {
|
.account-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.account-name {
|
.account-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -979,25 +966,25 @@
|
|||||||
|
|
||||||
.role-transfer-container {
|
.role-transfer-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 20px 0;
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.transfer-panel {
|
.transfer-panel {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 380px;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
|
||||||
max-width: 380px;
|
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
@@ -1011,8 +998,8 @@
|
|||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.role-list {
|
.role-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1032,9 +1019,9 @@
|
|||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-checkbox) {
|
:deep(.el-checkbox) {
|
||||||
@@ -1048,9 +1035,9 @@
|
|||||||
|
|
||||||
.assigned-role-item {
|
.assigned-role-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1067,9 +1054,9 @@
|
|||||||
.transfer-buttons {
|
.transfer-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton @click="showDialog('add')" v-permission="'shop_account:add'">新增代理账号</ElButton>
|
<ElButton @click="showDialog('add')" v-permission="'shop_account:add'"
|
||||||
|
>新增代理账号</ElButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取资产分配详情失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -107,8 +106,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -122,10 +121,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取分配记录列表失败')
|
console.log('获取分配记录列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -382,8 +382,10 @@
|
|||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
if (!currentRow.value) return
|
if (!currentRow.value) return
|
||||||
|
|
||||||
switch (item.key) {
|
switch (
|
||||||
|
item.key
|
||||||
// No cases available
|
// No cases available
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取授权记录详情失败')
|
console.log('获取授权记录详情失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -112,8 +112,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -127,10 +127,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -343,7 +343,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取授权记录列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -417,7 +416,6 @@
|
|||||||
getTableData()
|
getTableData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('备注修改失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
remarkLoading.value = false
|
remarkLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@
|
|||||||
|
|
||||||
<!-- 设备详情弹窗 -->
|
<!-- 设备详情弹窗 -->
|
||||||
<ElDialog v-model="deviceDetailDialogVisible" title="设备详情" width="1000px">
|
<ElDialog v-model="deviceDetailDialogVisible" title="设备详情" width="1000px">
|
||||||
<div v-if="deviceDetailLoading" style="text-align: center; padding: 40px 0">
|
<div v-if="deviceDetailLoading" style=" padding: 40px 0;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="currentDeviceDetail">
|
<div v-else-if="currentDeviceDetail">
|
||||||
@@ -398,14 +398,16 @@
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
<!-- 已绑定卡片列表 -->
|
<!-- 已绑定卡片列表 -->
|
||||||
<div v-if="deviceCardsLoading" style="text-align: center; padding: 40px 0">
|
<div v-if="deviceCardsLoading" style=" padding: 40px 0;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ElTable :data="deviceCards" border style="width: 100%">
|
<ElTable :data="deviceCards" border style="width: 100%">
|
||||||
<ElTableColumn prop="slot_position" label="插槽位置" width="120" align="center">
|
<ElTableColumn prop="slot_position" label="插槽位置" width="120" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div style="display: flex; align-items: center; justify-content: center; gap: 4px">
|
<div
|
||||||
|
style="display: flex; gap: 4px; align-items: center; justify-content: center"
|
||||||
|
>
|
||||||
<span>{{ row.slot_position }}</span>
|
<span>{{ row.slot_position }}</span>
|
||||||
<ElTag v-if="row.is_current" type="success" size="small">当前</ElTag>
|
<ElTag v-if="row.is_current" type="success" size="small">当前</ElTag>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,7 +437,7 @@
|
|||||||
</ElTable>
|
</ElTable>
|
||||||
<div
|
<div
|
||||||
v-if="deviceCards.length === 0"
|
v-if="deviceCards.length === 0"
|
||||||
style="text-align: center; padding: 20px; color: #909399"
|
style=" padding: 20px; color: #909399;text-align: center"
|
||||||
>
|
>
|
||||||
暂无设备绑定的卡
|
暂无设备绑定的卡
|
||||||
</div>
|
</div>
|
||||||
@@ -451,7 +453,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 设置限速对话框 -->
|
<!-- 设置限速对话框 -->
|
||||||
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="500px">
|
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="50%">
|
||||||
<ElForm
|
<ElForm
|
||||||
ref="speedLimitFormRef"
|
ref="speedLimitFormRef"
|
||||||
:model="speedLimitForm"
|
:model="speedLimitForm"
|
||||||
@@ -469,7 +471,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="上行速率" prop="upload_speed">
|
<ElFormItem label="上行速率" prop="upload_speed">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
@@ -479,7 +481,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -492,7 +494,7 @@
|
|||||||
|
|
||||||
<!-- 切换SIM卡对话框 -->
|
<!-- 切换SIM卡对话框 -->
|
||||||
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
||||||
<div v-if="loadingDeviceCards" style="text-align: center; padding: 40px">
|
<div v-if="loadingDeviceCards" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -534,9 +536,11 @@
|
|||||||
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}${card.is_current ? ' (当前)' : ''}`"
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}${card.is_current ? ' (当前)' : ''}`"
|
||||||
:value="card.iccid"
|
:value="card.iccid"
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span>{{ card.iccid }}</span>
|
<span>{{ card.iccid }}</span>
|
||||||
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
<ElTag size="small" style="margin-left: 10px"
|
||||||
|
>插槽{{ card.slot_position }}</ElTag
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</ElOption>
|
</ElOption>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
@@ -1042,7 +1046,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('解绑卡失败:', error)
|
console.error('解绑卡失败:', error)
|
||||||
ElMessage.error(error?.message || '解绑失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -1214,13 +1217,15 @@
|
|||||||
prop: 'last_online_time',
|
prop: 'last_online_time',
|
||||||
label: '最后在线时间',
|
label: '最后在线时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: Device) => row.last_online_time ? formatDateTime(row.last_online_time) : '-'
|
formatter: (row: Device) =>
|
||||||
|
row.last_online_time ? formatDateTime(row.last_online_time) : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'last_gateway_sync_at',
|
prop: 'last_gateway_sync_at',
|
||||||
label: '最后同步时间',
|
label: '最后同步时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: Device) => row.last_gateway_sync_at ? formatDateTime(row.last_gateway_sync_at) : '-'
|
formatter: (row: Device) =>
|
||||||
|
row.last_gateway_sync_at ? formatDateTime(row.last_gateway_sync_at) : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'batch_no',
|
prop: 'batch_no',
|
||||||
@@ -1310,7 +1315,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取设备列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -1368,7 +1372,6 @@
|
|||||||
await getTableData()
|
await getTableData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('删除失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -1416,7 +1419,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('分配失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
allocateLoading.value = false
|
allocateLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1467,7 +1469,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('回收失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
recallLoading.value = false
|
recallLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1510,7 +1511,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取套餐系列列表失败:', error)
|
console.error('获取套餐系列列表失败:', error)
|
||||||
ElMessage.error('获取套餐系列列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
seriesLoading.value = false
|
seriesLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1711,7 +1711,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('重启设备失败:', error)
|
console.error('重启设备失败:', error)
|
||||||
ElMessage.error(error?.message || '重启失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -1740,7 +1739,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('恢复出厂设置失败:', error)
|
console.error('恢复出厂设置失败:', error)
|
||||||
ElMessage.error(error?.message || '操作失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -1776,7 +1774,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('设置限速失败:', error)
|
console.error('设置限速失败:', error)
|
||||||
ElMessage.error(error?.message || '设置失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
speedLimitLoading.value = false
|
speedLimitLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1818,7 +1815,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载设备绑定卡列表失败:', error)
|
console.error('加载设备绑定卡列表失败:', error)
|
||||||
ElMessage.error('加载卡列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loadingDeviceCards.value = false
|
loadingDeviceCards.value = false
|
||||||
}
|
}
|
||||||
@@ -1843,7 +1839,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('切换SIM卡失败:', error)
|
console.error('切换SIM卡失败:', error)
|
||||||
ElMessage.error(error?.message || '切换失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
switchCardLoading.value = false
|
switchCardLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1881,7 +1876,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('设置WiFi失败:', error)
|
console.error('设置WiFi失败:', error)
|
||||||
ElMessage.error(error?.message || '设置失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
setWiFiLoading.value = false
|
setWiFiLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1915,12 +1909,12 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAuth('device:set_speed_limit')) {
|
// if (hasAuth('device:set_speed_limit')) {
|
||||||
items.push({
|
// items.push({
|
||||||
key: 'speed-limit',
|
// key: 'speed-limit',
|
||||||
label: '设置限速'
|
// label: '设置限速'
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (hasAuth('device:switch_sim')) {
|
if (hasAuth('device:switch_sim')) {
|
||||||
items.push({
|
items.push({
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询设备详情失败:', error)
|
console.error('查询设备详情失败:', error)
|
||||||
ElMessage.error(error?.message || '查询失败,请检查设备号是否正确')
|
console.log(error?.message || '查询失败,请检查设备号是否正确')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,7 +506,6 @@
|
|||||||
ElMessage.success('设备导入模板下载成功')
|
ElMessage.success('设备导入模板下载成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载模板失败:', error)
|
console.error('下载模板失败:', error)
|
||||||
ElMessage.error('下载模板失败')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +594,6 @@
|
|||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('设备导入失败:', error)
|
console.error('设备导入失败:', error)
|
||||||
ElMessage.error(error.message || '设备导入失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
}
|
}
|
||||||
@@ -645,7 +643,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载失败数据失败:', error)
|
console.error('下载失败数据失败:', error)
|
||||||
ElMessage.error('下载失败数据失败')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -539,7 +539,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error(t('enterpriseDevices.messages.loadFailed'))
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -607,7 +606,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取设备列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
availableDevicesLoading.value = false
|
availableDevicesLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -699,7 +697,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error(t('enterpriseDevices.messages.allocateFailed'))
|
|
||||||
} finally {
|
} finally {
|
||||||
allocateLoading.value = false
|
allocateLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -799,8 +796,8 @@
|
|||||||
.device-selection-info {
|
.device-selection-info {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: var(--el-color-info);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--el-color-info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
<span>加载中...</span>
|
<span>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -64,8 +63,7 @@
|
|||||||
{ label: '换货单号', prop: 'exchange_no' },
|
{ label: '换货单号', prop: 'exchange_no' },
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
render: (data) =>
|
render: (data) => h(ElTag, { type: getStatusType(data.status) }, () => data.status_text)
|
||||||
h(ElTag, { type: getStatusType(data.status) }, () => data.status_text)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '换货原因',
|
label: '换货原因',
|
||||||
@@ -80,11 +78,7 @@
|
|||||||
{
|
{
|
||||||
label: '新资产类型',
|
label: '新资产类型',
|
||||||
formatter: (_, data) =>
|
formatter: (_, data) =>
|
||||||
data.new_asset_type
|
data.new_asset_type ? (data.new_asset_type === 'iot_card' ? 'IoT卡' : '设备') : '--'
|
||||||
? data.new_asset_type === 'iot_card'
|
|
||||||
? 'IoT卡'
|
|
||||||
: '设备'
|
|
||||||
: '--'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '新资产标识符',
|
label: '新资产标识符',
|
||||||
|
|||||||
@@ -13,16 +13,9 @@
|
|||||||
|
|
||||||
<ElCard shadow="never" class="art-table-card">
|
<ElCard shadow="never" class="art-table-card">
|
||||||
<!-- 表格头部 -->
|
<!-- 表格头部 -->
|
||||||
<ArtTableHeader
|
<ArtTableHeader v-model:columns="columnChecks" @refresh="handleRefresh">
|
||||||
v-model:columns="columnChecks"
|
|
||||||
@refresh="handleRefresh"
|
|
||||||
>
|
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton
|
<ElButton type="primary" @click="showCreateDialog" v-permission="'exchange:create'">
|
||||||
type="primary"
|
|
||||||
@click="showCreateDialog"
|
|
||||||
v-permission="'exchange:create'"
|
|
||||||
>
|
|
||||||
创建换货单
|
创建换货单
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -68,12 +61,7 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleCloseCreateDialog"
|
@closed="handleCloseCreateDialog"
|
||||||
>
|
>
|
||||||
<ElForm
|
<ElForm ref="createFormRef" :model="createForm" :rules="createRules" label-width="120px">
|
||||||
ref="createFormRef"
|
|
||||||
:model="createForm"
|
|
||||||
:rules="createRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="换货原因" prop="exchange_reason">
|
<ElFormItem label="换货原因" prop="exchange_reason">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="createForm.exchange_reason"
|
v-model="createForm.exchange_reason"
|
||||||
@@ -83,7 +71,11 @@
|
|||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="旧资产类型" prop="old_asset_type">
|
<ElFormItem label="旧资产类型" prop="old_asset_type">
|
||||||
<ElSelect v-model="createForm.old_asset_type" placeholder="请选择旧资产类型" style="width: 100%">
|
<ElSelect
|
||||||
|
v-model="createForm.old_asset_type"
|
||||||
|
placeholder="请选择旧资产类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<ElOption label="IoT卡" value="iot_card" />
|
<ElOption label="IoT卡" value="iot_card" />
|
||||||
<ElOption label="设备" value="device" />
|
<ElOption label="设备" value="device" />
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
@@ -122,12 +114,7 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleCloseShipDialog"
|
@closed="handleCloseShipDialog"
|
||||||
>
|
>
|
||||||
<ElForm
|
<ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px">
|
||||||
ref="shipFormRef"
|
|
||||||
:model="shipForm"
|
|
||||||
:rules="shipRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="新资产标识符" prop="new_identifier">
|
<ElFormItem label="新资产标识符" prop="new_identifier">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="shipForm.new_identifier"
|
v-model="shipForm.new_identifier"
|
||||||
@@ -142,7 +129,7 @@
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="是否迁移数据">
|
<ElFormItem label="是否迁移数据">
|
||||||
<ElSwitch v-model="shipForm.migrate_data" />
|
<ElSwitch v-model="shipForm.migrate_data" />
|
||||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">
|
<div style=" margin-top: 4px;font-size: 12px; color: #909399">
|
||||||
开启后将迁移钱包余额、套餐记录到新资产
|
开启后将迁移钱包余额、套餐记录到新资产
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -185,12 +172,8 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// 右键菜单相关
|
// 右键菜单相关
|
||||||
const {
|
const { showContextMenuHint, hintPosition, handleCellMouseEnter, handleCellMouseLeave } =
|
||||||
showContextMenuHint,
|
useTableContextMenu()
|
||||||
hintPosition,
|
|
||||||
handleCellMouseEnter,
|
|
||||||
handleCellMouseLeave
|
|
||||||
} = useTableContextMenu()
|
|
||||||
|
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentExchangeRow = ref<ExchangeResponse | null>(null)
|
const currentExchangeRow = ref<ExchangeResponse | null>(null)
|
||||||
@@ -334,15 +317,14 @@
|
|||||||
prop: 'status',
|
prop: 'status',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
width: 130,
|
width: 130,
|
||||||
formatter: (row: any) =>
|
formatter: (row: any) => h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
|
||||||
h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'created_at',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: any) => formatDateTime(row.created_at)
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
},
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const getStatusType = (status: number) => {
|
const getStatusType = (status: number) => {
|
||||||
|
|||||||
@@ -385,7 +385,7 @@
|
|||||||
|
|
||||||
<!-- 卡详情对话框 -->
|
<!-- 卡详情对话框 -->
|
||||||
<ElDialog v-model="cardDetailDialogVisible" title="卡片详情" width="900px">
|
<ElDialog v-model="cardDetailDialogVisible" title="卡片详情" width="900px">
|
||||||
<div v-if="cardDetailLoading" style="text-align: center; padding: 40px">
|
<div v-if="cardDetailLoading" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">加载中...</div>
|
<div style="margin-top: 16px">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
|
|
||||||
<!-- 流量使用查询对话框 -->
|
<!-- 流量使用查询对话框 -->
|
||||||
<ElDialog v-model="flowUsageDialogVisible" title="流量使用查询" width="500px">
|
<ElDialog v-model="flowUsageDialogVisible" title="流量使用查询" width="500px">
|
||||||
<div v-if="flowUsageLoading" style="text-align: center; padding: 40px">
|
<div v-if="flowUsageLoading" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">查询中...</div>
|
<div style="margin-top: 16px">查询中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -494,7 +494,7 @@
|
|||||||
|
|
||||||
<!-- 实名状态查询对话框 -->
|
<!-- 实名状态查询对话框 -->
|
||||||
<ElDialog v-model="realnameStatusDialogVisible" title="实名认证状态" width="500px">
|
<ElDialog v-model="realnameStatusDialogVisible" title="实名认证状态" width="500px">
|
||||||
<div v-if="realnameStatusLoading" style="text-align: center; padding: 40px">
|
<div v-if="realnameStatusLoading" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">查询中...</div>
|
<div style="margin-top: 16px">查询中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -517,7 +517,7 @@
|
|||||||
|
|
||||||
<!-- 卡实时状态查询对话框 -->
|
<!-- 卡实时状态查询对话框 -->
|
||||||
<ElDialog v-model="cardStatusDialogVisible" title="卡实时状态" width="500px">
|
<ElDialog v-model="cardStatusDialogVisible" title="卡实时状态" width="500px">
|
||||||
<div v-if="cardStatusLoading" style="text-align: center; padding: 40px">
|
<div v-if="cardStatusLoading" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">查询中...</div>
|
<div style="margin-top: 16px">查询中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1108,7 +1108,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取单卡列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -1364,7 +1363,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('批量回收失败,请重试')
|
|
||||||
} finally {
|
} finally {
|
||||||
recallLoading.value = false
|
recallLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1407,7 +1405,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取套餐系列列表失败:', error)
|
console.error('获取套餐系列列表失败:', error)
|
||||||
ElMessage.error('获取套餐系列列表失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
seriesLoading.value = false
|
seriesLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1475,7 +1472,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('批量设置套餐系列失败,请重试')
|
|
||||||
} finally {
|
} finally {
|
||||||
seriesBindingLoading.value = false
|
seriesBindingLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -1487,27 +1483,6 @@
|
|||||||
const moreMenuItems = computed((): MenuItemType[] => {
|
const moreMenuItems = computed((): MenuItemType[] => {
|
||||||
const items: MenuItemType[] = []
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
if (hasAuth('iot_card:network_distribution')) {
|
|
||||||
items.push({
|
|
||||||
key: 'distribution',
|
|
||||||
label: '网卡分销'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('iot_card:batch_recharge')) {
|
|
||||||
items.push({
|
|
||||||
key: 'recharge',
|
|
||||||
label: '批量充值'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('iot_card:network_recycle')) {
|
|
||||||
items.push({
|
|
||||||
key: 'recycle',
|
|
||||||
label: '网卡回收'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('iot_card:batch_download')) {
|
if (hasAuth('iot_card:batch_download')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'download',
|
key: 'download',
|
||||||
@@ -1518,7 +1493,7 @@
|
|||||||
if (hasAuth('iot_card:change_package')) {
|
if (hasAuth('iot_card:change_package')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'changePackage',
|
key: 'changePackage',
|
||||||
label: '变更套餐'
|
label: '其他功能暂定'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1698,7 +1673,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询流量使用失败:', error)
|
console.error('查询流量使用失败:', error)
|
||||||
ElMessage.error(error?.message || '查询失败')
|
|
||||||
flowUsageDialogVisible.value = false
|
flowUsageDialogVisible.value = false
|
||||||
} finally {
|
} finally {
|
||||||
flowUsageLoading.value = false
|
flowUsageLoading.value = false
|
||||||
@@ -1735,7 +1709,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询实名状态失败:', error)
|
console.error('查询实名状态失败:', error)
|
||||||
ElMessage.error(error?.message || '查询失败')
|
|
||||||
realnameStatusDialogVisible.value = false
|
realnameStatusDialogVisible.value = false
|
||||||
} finally {
|
} finally {
|
||||||
realnameStatusLoading.value = false
|
realnameStatusLoading.value = false
|
||||||
@@ -1755,7 +1728,7 @@
|
|||||||
// 直接使用 resolveAsset 返回的网络状态
|
// 直接使用 resolveAsset 返回的网络状态
|
||||||
cardStatusData.value = {
|
cardStatusData.value = {
|
||||||
iccid: iccid,
|
iccid: iccid,
|
||||||
cardStatus: res.data. === 1 ? '正常' : '停机',
|
cardStatus: res.data.network_status === 1 ? '正常' : '停机',
|
||||||
extend: res.data.extend
|
extend: res.data.extend
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1764,7 +1737,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询卡状态失败:', error)
|
console.error('查询卡状态失败:', error)
|
||||||
ElMessage.error(error?.message || '查询失败')
|
|
||||||
cardStatusDialogVisible.value = false
|
cardStatusDialogVisible.value = false
|
||||||
} finally {
|
} finally {
|
||||||
cardStatusLoading.value = false
|
cardStatusLoading.value = false
|
||||||
@@ -1841,7 +1813,7 @@
|
|||||||
const res = await CardService.deactivateIotCard(card.id)
|
const res = await CardService.deactivateIotCard(card.id)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('手动停用成功')
|
ElMessage.success('手动停用成功')
|
||||||
getTableData()
|
await getTableData()
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('手动停用失败:', error)
|
console.error('手动停用失败:', error)
|
||||||
|
|||||||
@@ -131,7 +131,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询卡片详情失败:', error)
|
console.error('查询卡片详情失败:', error)
|
||||||
ElMessage.error(error?.message || '查询失败,请检查ICCID是否正确')
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
align-center
|
align-center
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<div v-if="failDataLoading" style="text-align: center; padding: 40px">
|
<div v-if="failDataLoading" style=" padding: 40px;text-align: center">
|
||||||
<ElSkeleton :rows="5" animated />
|
<ElSkeleton :rows="5" animated />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="failedItems.length > 0">
|
<div v-else-if="failedItems.length > 0">
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
<ElTableColumn prop="message" label="失败原因" min-width="200" show-overflow-tooltip />
|
<ElTableColumn prop="message" label="失败原因" min-width="200" show-overflow-tooltip />
|
||||||
</ElTable>
|
</ElTable>
|
||||||
</div>
|
</div>
|
||||||
<div v-else style="text-align: center; padding: 40px">
|
<div v-else style=" padding: 40px;text-align: center">
|
||||||
<ElEmpty description="暂无失败数据" />
|
<ElEmpty description="暂无失败数据" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -555,7 +555,6 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取失败数据失败:', error)
|
console.error('获取失败数据失败:', error)
|
||||||
ElMessage.error('获取失败数据失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
failDataLoading.value = false
|
failDataLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -594,7 +593,6 @@
|
|||||||
ElMessage.success('失败数据下载成功')
|
ElMessage.success('失败数据下载成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载失败数据失败:', error)
|
console.error('下载失败数据失败:', error)
|
||||||
ElMessage.error('下载失败数据失败')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,7 +651,6 @@
|
|||||||
ElMessage.success('IoT卡导入模板下载成功')
|
ElMessage.success('IoT卡导入模板下载成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载模板失败:', error)
|
console.error('下载模板失败:', error)
|
||||||
ElMessage.error('下载模板失败')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +771,6 @@
|
|||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('IoT卡导入失败:', error)
|
console.error('IoT卡导入失败:', error)
|
||||||
ElMessage.error(error.message || 'IoT卡导入失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
ElMessage.error('获取任务详情失败')
|
console.log('获取任务详情失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -252,8 +252,8 @@
|
|||||||
.task-detail-page {
|
.task-detail-page {
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
|
|||||||
@@ -115,15 +115,7 @@
|
|||||||
const { isDark, systemThemeType } = storeToRefs(settingStore)
|
const { isDark, systemThemeType } = storeToRefs(settingStore)
|
||||||
|
|
||||||
// 使用登录 Composable
|
// 使用登录 Composable
|
||||||
const {
|
const { formRef, formData, rules, loading, mockAccounts, setupAccount, handleLogin } = useLogin()
|
||||||
formRef,
|
|
||||||
formData,
|
|
||||||
rules,
|
|
||||||
loading,
|
|
||||||
mockAccounts,
|
|
||||||
setupAccount,
|
|
||||||
handleLogin
|
|
||||||
} = useLogin()
|
|
||||||
|
|
||||||
const systemName = AppConfig.systemInfo.name
|
const systemName = AppConfig.systemInfo.name
|
||||||
|
|
||||||
|
|||||||
@@ -644,7 +644,7 @@
|
|||||||
// 刷新列表
|
// 刷新列表
|
||||||
getMyCardsList()
|
getMyCardsList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('分销操作失败,请重试')
|
console.log('分销操作失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +660,7 @@
|
|||||||
// 刷新列表
|
// 刷新列表
|
||||||
getMyCardsList()
|
getMyCardsList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('充值操作失败,请重试')
|
console.log('充值操作失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,7 +677,7 @@
|
|||||||
tableData.value = tableData.value.filter((item) => !recycledIds.includes(item.id))
|
tableData.value = tableData.value.filter((item) => !recycledIds.includes(item.id))
|
||||||
selectedRows.value = []
|
selectedRows.value = []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('回收操作失败,请重试')
|
console.log('回收操作失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -679,8 +679,8 @@
|
|||||||
.account-list-page {
|
.account-list-page {
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -694,8 +694,8 @@
|
|||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
@@ -705,8 +705,8 @@
|
|||||||
|
|
||||||
.account-info {
|
.account-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.account-name {
|
.account-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -717,25 +717,25 @@
|
|||||||
|
|
||||||
.role-transfer-container {
|
.role-transfer-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 20px 0;
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.transfer-panel {
|
.transfer-panel {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 380px;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
|
||||||
max-width: 380px;
|
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
@@ -749,8 +749,8 @@
|
|||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.role-list {
|
.role-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -772,15 +772,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.el-checkbox__label {
|
.el-checkbox__label {
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
> span:first-child {
|
> span:first-child {
|
||||||
@@ -794,16 +794,16 @@
|
|||||||
|
|
||||||
.assigned-role-item {
|
.assigned-role-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
> span:first-child {
|
> span:first-child {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -825,9 +825,9 @@
|
|||||||
.transfer-buttons {
|
.transfer-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
|
|||||||
@@ -132,10 +132,10 @@
|
|||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (width <= 768px) {
|
||||||
.active-wechat-config-widget {
|
.active-wechat-config-widget {
|
||||||
.card-header {
|
.card-header {
|
||||||
.header-left,
|
.header-left,
|
||||||
|
|||||||
@@ -151,8 +151,8 @@
|
|||||||
|
|
||||||
.stats-container {
|
.stats-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card-wrapper {
|
.stat-card-wrapper {
|
||||||
@@ -171,6 +171,7 @@
|
|||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -178,7 +179,6 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-content {
|
.stat-content {
|
||||||
@@ -193,10 +193,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
|
overflow: hidden;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1400px) {
|
@media (width <= 1400px) {
|
||||||
.commission-summary-widget {
|
.commission-summary-widget {
|
||||||
.stats-container {
|
.stats-container {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (width <= 1200px) {
|
||||||
.commission-summary-widget {
|
.commission-summary-widget {
|
||||||
.stat-card-wrapper {
|
.stat-card-wrapper {
|
||||||
flex: 1 1 calc(33.333% - 12px);
|
flex: 1 1 calc(33.333% - 12px);
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (width <= 768px) {
|
||||||
.commission-summary-widget {
|
.commission-summary-widget {
|
||||||
.stats-container {
|
.stats-container {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (width <= 480px) {
|
||||||
.commission-summary-widget {
|
.commission-summary-widget {
|
||||||
.stat-card-wrapper {
|
.stat-card-wrapper {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|||||||
@@ -117,10 +117,10 @@
|
|||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (width <= 768px) {
|
||||||
.withdrawal-settings-widget {
|
.withdrawal-settings-widget {
|
||||||
.card-header {
|
.card-header {
|
||||||
.header-left,
|
.header-left,
|
||||||
|
|||||||
@@ -32,11 +32,7 @@
|
|||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { AgentRechargeService } from '@/api/modules'
|
import { AgentRechargeService } from '@/api/modules'
|
||||||
import type {
|
import type { AgentRecharge, AgentRechargeStatus, AgentRechargePaymentMethod } from '@/types/api'
|
||||||
AgentRecharge,
|
|
||||||
AgentRechargeStatus,
|
|
||||||
AgentRechargePaymentMethod
|
|
||||||
} from '@/types/api'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
|
|
||||||
@@ -100,7 +96,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
render: (data) => h(ElTag, { type: getStatusType(data.status) }, () => getStatusText(data.status))
|
render: (data) =>
|
||||||
|
h(ElTag, { type: getStatusType(data.status) }, () => getStatusText(data.status))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -194,8 +191,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -209,10 +206,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -259,21 +259,14 @@
|
|||||||
>
|
>
|
||||||
<ElAlert
|
<ElAlert
|
||||||
:title="
|
:title="
|
||||||
resolveAction === 'release'
|
resolveAction === 'release' ? '确认将该笔待审佣金记录入账' : '确认将该笔待审佣金记录作废'
|
||||||
? '确认将该笔待审佣金记录入账'
|
|
||||||
: '确认将该笔待审佣金记录作废'
|
|
||||||
"
|
"
|
||||||
:type="resolveAction === 'release' ? 'success' : 'warning'"
|
:type="resolveAction === 'release' ? 'success' : 'warning'"
|
||||||
style="margin-bottom: 16px"
|
style="margin-bottom: 16px"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ElForm
|
<ElForm ref="resolveFormRef" :model="resolveForm" :rules="resolveRules" label-width="100px">
|
||||||
ref="resolveFormRef"
|
|
||||||
:model="resolveForm"
|
|
||||||
:rules="resolveRules"
|
|
||||||
label-width="100px"
|
|
||||||
>
|
|
||||||
<ElFormItem v-if="resolveAction === 'release'" label="入账金额" prop="amount">
|
<ElFormItem v-if="resolveAction === 'release'" label="入账金额" prop="amount">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="resolveForm.amount"
|
v-model="resolveForm.amount"
|
||||||
@@ -284,7 +277,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入入账金额(分)"
|
placeholder="请输入入账金额(分)"
|
||||||
/>
|
/>
|
||||||
<div style="color: var(--el-text-color-secondary); font-size: 12px; margin-top: 4px">
|
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-secondary)">
|
||||||
金额单位为分,例如:100元 = 10000分
|
金额单位为分,例如:100元 = 10000分
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -736,7 +729,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理佣金修正
|
// 处理佣金修正
|
||||||
const handleResolveCommission = (row: ShopCommissionRecordItem, action: CommissionResolveAction) => {
|
const handleResolveCommission = (
|
||||||
|
row: ShopCommissionRecordItem,
|
||||||
|
action: CommissionResolveAction
|
||||||
|
) => {
|
||||||
currentCommissionId.value = row.id
|
currentCommissionId.value = row.id
|
||||||
resolveAction.value = action
|
resolveAction.value = action
|
||||||
resolveForm.amount = undefined
|
resolveForm.amount = undefined
|
||||||
|
|||||||
@@ -22,7 +22,12 @@
|
|||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton type="primary" @click="showWithdrawalDialog" v-permission="'my_commission:add'">发起提现</ElButton>
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="showWithdrawalDialog"
|
||||||
|
v-permission="'my_commission:add'"
|
||||||
|
>发起提现</ElButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton type="primary" @click="showDialog" v-permission="'withdrawal_settings:add'">新增配置</ElButton>
|
<ElButton type="primary" @click="showDialog" v-permission="'withdrawal_settings:add'"
|
||||||
|
>新增配置</ElButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,7 @@
|
|||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="实际退款金额">
|
<ElDescriptionsItem label="实际退款金额">
|
||||||
{{
|
{{ refund.approved_refund_amount ? formatCurrency(refund.approved_refund_amount) : '-' }}
|
||||||
refund.approved_refund_amount ? formatCurrency(refund.approved_refund_amount) : '-'
|
|
||||||
}}
|
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="实收金额">
|
<ElDescriptionsItem label="实收金额">
|
||||||
{{ formatCurrency(refund.actual_received_amount) }}
|
{{ formatCurrency(refund.actual_received_amount) }}
|
||||||
@@ -95,12 +93,7 @@
|
|||||||
width="500px"
|
width="500px"
|
||||||
@closed="handleApproveDialogClosed"
|
@closed="handleApproveDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm
|
<ElForm ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="120px">
|
||||||
ref="approveFormRef"
|
|
||||||
:model="approveForm"
|
|
||||||
:rules="approveRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="退款单号">
|
<ElFormItem label="退款单号">
|
||||||
<span>{{ refund?.refund_no }}</span>
|
<span>{{ refund?.refund_no }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -341,9 +334,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
const getStatusType = (
|
const getStatusType = (status: RefundStatus): 'warning' | 'success' | 'danger' | 'info' => {
|
||||||
status: RefundStatus
|
|
||||||
): 'warning' | 'success' | 'danger' | 'info' => {
|
|
||||||
const statusMap: Record<RefundStatus, 'warning' | 'success' | 'danger' | 'info'> = {
|
const statusMap: Record<RefundStatus, 'warning' | 'success' | 'danger' | 'info'> = {
|
||||||
1: 'warning', // 待审批
|
1: 'warning', // 待审批
|
||||||
2: 'success', // 已通过
|
2: 'success', // 已通过
|
||||||
@@ -551,15 +542,15 @@
|
|||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -183,12 +183,7 @@
|
|||||||
width="500px"
|
width="500px"
|
||||||
@closed="handleRejectDialogClosed"
|
@closed="handleRejectDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm
|
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="120px">
|
||||||
ref="rejectFormRef"
|
|
||||||
:model="rejectForm"
|
|
||||||
:rules="rejectRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="退款单号">
|
<ElFormItem label="退款单号">
|
||||||
<span>{{ currentRefund?.refund_no }}</span>
|
<span>{{ currentRefund?.refund_no }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -220,12 +215,7 @@
|
|||||||
width="500px"
|
width="500px"
|
||||||
@closed="handleReturnDialogClosed"
|
@closed="handleReturnDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm
|
<ElForm ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px">
|
||||||
ref="returnFormRef"
|
|
||||||
:model="returnForm"
|
|
||||||
:rules="returnRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="退款单号">
|
<ElFormItem label="退款单号">
|
||||||
<span>{{ currentRefund?.refund_no }}</span>
|
<span>{{ currentRefund?.refund_no }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -460,9 +450,7 @@
|
|||||||
|
|
||||||
const createRules = reactive<FormRules>({
|
const createRules = reactive<FormRules>({
|
||||||
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
||||||
requested_refund_amount: [
|
requested_refund_amount: [{ required: true, message: '请输入申请退款金额', trigger: 'blur' }],
|
||||||
{ required: true, message: '请输入申请退款金额', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
actual_received_amount: [{ required: true, message: '请输入实收金额', trigger: 'blur' }]
|
actual_received_amount: [{ required: true, message: '请输入实收金额', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -519,9 +507,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
const getStatusType = (
|
const getStatusType = (status: RefundStatus): 'warning' | 'success' | 'danger' | 'info' => {
|
||||||
status: RefundStatus
|
|
||||||
): 'warning' | 'success' | 'danger' | 'info' => {
|
|
||||||
const statusMap: Record<RefundStatus, 'warning' | 'success' | 'danger' | 'info'> = {
|
const statusMap: Record<RefundStatus, 'warning' | 'success' | 'danger' | 'info'> = {
|
||||||
1: 'warning', // 待审批
|
1: 'warning', // 待审批
|
||||||
2: 'success', // 已通过
|
2: 'success', // 已通过
|
||||||
@@ -998,10 +984,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 已拒绝或已退回状态可以重新提交
|
// 已拒绝或已退回状态可以重新提交
|
||||||
if (
|
if (currentOperatingRefund.value.status === 3 || currentOperatingRefund.value.status === 4) {
|
||||||
currentOperatingRefund.value.status === 3 ||
|
|
||||||
currentOperatingRefund.value.status === 4
|
|
||||||
) {
|
|
||||||
items.push({
|
items.push({
|
||||||
key: 'resubmit',
|
key: 'resubmit',
|
||||||
label: '重新提交'
|
label: '重新提交'
|
||||||
|
|||||||
@@ -32,13 +32,19 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
@click="handleSearchCard"
|
@click="handleSearchCard"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:icon="Search"
|
||||||
style="margin-left: 16px"
|
style="margin-left: 16px"
|
||||||
>查询</ElButton
|
>查询</ElButton
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 操作按钮组 -->
|
||||||
<div v-if="cardInfo" class="operation-button-group">
|
<div v-if="cardInfo" class="operation-button-group">
|
||||||
<ElButton @click="handleRefreshAsset" :loading="refreshLoading" type="primary">
|
<ElButton
|
||||||
|
@click="handleRefreshAsset"
|
||||||
|
:loading="refreshLoading"
|
||||||
|
type="primary"
|
||||||
|
:icon="Refresh"
|
||||||
|
>
|
||||||
刷新
|
刷新
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +66,7 @@
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ElDescriptions :column="3" border>
|
<ElDescriptions :column="4" border>
|
||||||
<!--<ElDescriptionsItem label="资产状态">-->
|
<!--<ElDescriptionsItem label="资产状态">-->
|
||||||
<!-- <ElTag :type="getAssetStatusType(cardInfo?.status)" size="small">-->
|
<!-- <ElTag :type="getAssetStatusType(cardInfo?.status)" size="small">-->
|
||||||
<!-- {{ getAssetStatusName(cardInfo?.status) }}-->
|
<!-- {{ getAssetStatusName(cardInfo?.status) }}-->
|
||||||
@@ -70,10 +76,9 @@
|
|||||||
<!-- 卡专属字段 -->
|
<!-- 卡专属字段 -->
|
||||||
<template v-if="cardInfo?.asset_type === 'card'">
|
<template v-if="cardInfo?.asset_type === 'card'">
|
||||||
<ElDescriptionsItem label="ICCID">{{ cardInfo?.iccid || '--' }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="ICCID">{{ cardInfo?.iccid || '--' }}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="运营商账户">暂未接入</ElDescriptionsItem>
|
<ElDescriptionsItem label="运营商账户">{{
|
||||||
<!--<ElDescriptionsItem label="运营商">{{-->
|
cardInfo?.carrier_name || '--'
|
||||||
<!-- cardInfo?.carrier_name || '--'-->
|
}}</ElDescriptionsItem>
|
||||||
<!-- }}</ElDescriptionsItem>-->
|
|
||||||
<ElDescriptionsItem label="实名状态">
|
<ElDescriptionsItem label="实名状态">
|
||||||
<ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small">
|
<ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small">
|
||||||
{{ getRealNameStatusName(cardInfo?.real_name_status) }}
|
{{ getRealNameStatusName(cardInfo?.real_name_status) }}
|
||||||
@@ -83,6 +88,9 @@
|
|||||||
<ElDescriptionsItem label="店铺">{{
|
<ElDescriptionsItem label="店铺">{{
|
||||||
cardInfo?.shop_name || '--'
|
cardInfo?.shop_name || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="套餐系列">{{
|
||||||
|
cardInfo?.series_name || '--'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="虚拟号">{{
|
<ElDescriptionsItem label="虚拟号">{{
|
||||||
cardInfo?.virtual_no || '--'
|
cardInfo?.virtual_no || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
@@ -155,7 +163,11 @@
|
|||||||
cardInfo?.activated_at || '--'
|
cardInfo?.activated_at || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="实名时间">暂未接入</ElDescriptionsItem>
|
<ElDescriptionsItem label="实名时间">暂未接入</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="信号强度">暂未接入</ElDescriptionsItem>
|
<ElDescriptionsItem label="信号强度">
|
||||||
|
<ElTag :type="getSignalStrengthType()" size="small">
|
||||||
|
{{ calculateSignalStrength() }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="在线状态">
|
<ElDescriptionsItem label="在线状态">
|
||||||
<ElTag :type="getOnlineStatusType(cardInfo?.online_status)" size="small">
|
<ElTag :type="getOnlineStatusType(cardInfo?.online_status)" size="small">
|
||||||
@@ -205,21 +217,22 @@
|
|||||||
<ElTable
|
<ElTable
|
||||||
v-if="cardInfo.cards && cardInfo.cards.length > 0"
|
v-if="cardInfo.cards && cardInfo.cards.length > 0"
|
||||||
:data="cardInfo.cards"
|
:data="cardInfo.cards"
|
||||||
|
border
|
||||||
style="margin-top: 16px"
|
style="margin-top: 16px"
|
||||||
>
|
>
|
||||||
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
<ElTableColumn prop="iccid" label="ICCID" />
|
||||||
<ElTableColumn prop="msisdn" label="MSISDN" min-width="120" />
|
<ElTableColumn prop="msisdn" label="MSISDN" />
|
||||||
<ElTableColumn prop="slot_position" label="卡槽位置" width="120" align="center">
|
<ElTableColumn prop="slot_position" label="卡槽位置" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div
|
<div
|
||||||
style="display: flex; align-items: center; justify-content: center; gap: 4px"
|
style="display: flex; gap: 4px; align-items: center; justify-content: center"
|
||||||
>
|
>
|
||||||
<span>{{ scope.row.slot_position }}</span>
|
<span>{{ scope.row.slot_position }}</span>
|
||||||
<ElTag v-if="scope.row.is_current" type="success" size="small">当前</ElTag>
|
<ElTag v-if="scope.row.is_current" type="success" size="small">当前</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="网络状态" width="100" align="center">
|
<ElTableColumn label="网络状态" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ElTag
|
<ElTag
|
||||||
:type="scope.row.network_status === 1 ? 'success' : 'danger'"
|
:type="scope.row.network_status === 1 ? 'success' : 'danger'"
|
||||||
@@ -229,7 +242,7 @@
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="实名状态" width="100" align="center">
|
<ElTableColumn label="实名状态" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ElTag :type="getRealNameStatusType(scope.row.real_name_status)" size="small">
|
<ElTag :type="getRealNameStatusType(scope.row.real_name_status)" size="small">
|
||||||
{{ getRealNameStatusName(scope.row.real_name_status) }}
|
{{ getRealNameStatusName(scope.row.real_name_status) }}
|
||||||
@@ -242,7 +255,7 @@
|
|||||||
<!-- 设备实时信息 -->
|
<!-- 设备实时信息 -->
|
||||||
<ElDivider content-position="left">设备实时信息</ElDivider>
|
<ElDivider content-position="left">设备实时信息</ElDivider>
|
||||||
<div v-if="deviceRealtime" style="margin-top: 16px">
|
<div v-if="deviceRealtime" style="margin-top: 16px">
|
||||||
<ElDescriptions :column="3" border size="small">
|
<ElDescriptions :column="4" border>
|
||||||
<!-- 基本状态 -->
|
<!-- 基本状态 -->
|
||||||
<ElDescriptionsItem label="在线状态">
|
<ElDescriptionsItem label="在线状态">
|
||||||
<ElTag
|
<ElTag
|
||||||
@@ -299,8 +312,8 @@
|
|||||||
v-if="deviceRealtime.current_iccid"
|
v-if="deviceRealtime.current_iccid"
|
||||||
style="
|
style="
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
"
|
"
|
||||||
@click="handleViewCardDetail(deviceRealtime.current_iccid)"
|
@click="handleViewCardDetail(deviceRealtime.current_iccid)"
|
||||||
>
|
>
|
||||||
@@ -426,13 +439,13 @@
|
|||||||
<ElButton type="warning" @click="handleResetDevice" :loading="resetDeviceLoading">
|
<ElButton type="warning" @click="handleResetDevice" :loading="resetDeviceLoading">
|
||||||
恢复出厂
|
恢复出厂
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<!--<ElButton-->
|
||||||
type="info"
|
<!-- type="info"-->
|
||||||
@click="handleShowSpeedLimitDialog"
|
<!-- @click="handleShowSpeedLimitDialog"-->
|
||||||
:loading="speedLimitLoading"
|
<!-- :loading="speedLimitLoading"-->
|
||||||
>
|
<!-->-->
|
||||||
设置限速
|
<!-- 设置限速-->
|
||||||
</ElButton>
|
<!--</ElButton>-->
|
||||||
<ElButton
|
<ElButton
|
||||||
type="success"
|
type="success"
|
||||||
@click="handleShowSwitchCardDialog"
|
@click="handleShowSwitchCardDialog"
|
||||||
@@ -470,7 +483,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="cardInfo.currentPackageDetail">
|
<div v-if="cardInfo.currentPackageDetail">
|
||||||
<ElDescriptions :column="3" border>
|
<ElDescriptions :column="4" border>
|
||||||
<!-- 套餐基本信息 -->
|
<!-- 套餐基本信息 -->
|
||||||
<ElDescriptionsItem label="套餐名称" :span="1">{{
|
<ElDescriptionsItem label="套餐名称" :span="1">{{
|
||||||
cardInfo.currentPackageDetail.package_name || '--'
|
cardInfo.currentPackageDetail.package_name || '--'
|
||||||
@@ -524,11 +537,6 @@
|
|||||||
<ElDescriptionsItem label="创建时间">{{
|
<ElDescriptionsItem label="创建时间">{{
|
||||||
formatDateTime(cardInfo.currentPackageDetail.created_at)
|
formatDateTime(cardInfo.currentPackageDetail.created_at)
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<!-- 其他信息 -->
|
|
||||||
<ElDescriptionsItem label="主套餐ID" :span="2">{{
|
|
||||||
cardInfo.currentPackageDetail.master_usage_id || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 操作按钮组 -->
|
||||||
@@ -573,40 +581,10 @@
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三行:归属信息和钱包概况 -->
|
<!-- 第三行:钱包概况和套餐列表 -->
|
||||||
<div class="row two-columns">
|
<div class="row two-columns wallet-package-row">
|
||||||
<!-- 左侧:归属信息 -->
|
<!-- 左侧:钱包概况 -->
|
||||||
<div class="col">
|
<div class="col wallet-col">
|
||||||
<ElCard shadow="never" class="info-card business-info">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>归属信息</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<ElDescriptions :column="2" border>
|
|
||||||
<!--<ElDescriptionsItem label="所属店铺">{{-->
|
|
||||||
<!-- cardInfo?.shop_name || '--'-->
|
|
||||||
<!--}}</ElDescriptionsItem>-->
|
|
||||||
<ElDescriptionsItem label="套餐系列">{{
|
|
||||||
cardInfo?.series_name || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="批次号">{{
|
|
||||||
cardInfo?.batch_no || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="创建时间">{{
|
|
||||||
formatDateTime(cardInfo?.created_at)
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<!--<template v-if="cardInfo?.asset_type === 'card'">-->
|
|
||||||
<!-- <ElDescriptionsItem label="供应商">{{-->
|
|
||||||
<!-- cardInfo?.supplier || '--'-->
|
|
||||||
<!-- }}</ElDescriptionsItem>-->
|
|
||||||
<!--</template>-->
|
|
||||||
</ElDescriptions>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧:钱包概况 -->
|
|
||||||
<div class="col">
|
|
||||||
<ElCard shadow="never" class="info-card wallet-overview-card">
|
<ElCard shadow="never" class="info-card wallet-overview-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -647,28 +625,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 钱包详细信息 -->
|
|
||||||
<ElDescriptions :column="2" border style="margin-top: 20px">
|
|
||||||
<ElDescriptionsItem label="钱包ID">{{
|
|
||||||
walletInfo.wallet_id || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="币种">{{
|
|
||||||
walletInfo.currency || 'CNY'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="资源类型">{{
|
|
||||||
walletInfo.resource_type || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="资源ID">{{
|
|
||||||
walletInfo.resource_id || '--'
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="创建时间">{{
|
|
||||||
formatDateTime(walletInfo.created_at)
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="更新时间">{{
|
|
||||||
formatDateTime(walletInfo.updated_at)
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
</div>
|
</div>
|
||||||
<ElEmpty
|
<ElEmpty
|
||||||
v-else-if="!walletLoading"
|
v-else-if="!walletLoading"
|
||||||
@@ -677,123 +633,129 @@
|
|||||||
/>
|
/>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 套餐列表 -->
|
<!-- 右侧:套餐列表 -->
|
||||||
<div class="row full-width">
|
<div class="col package-col">
|
||||||
<ElCard shadow="never" class="info-card package-info">
|
<ElCard shadow="never" class="info-card package-info">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>套餐列表</span>
|
<span>套餐列表</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="package-table-wrapper">
|
<div class="package-table-wrapper">
|
||||||
<ElTable
|
<ElTable
|
||||||
v-if="cardInfo.packageList && cardInfo.packageList.length > 0"
|
v-if="cardInfo.packageList && cardInfo.packageList.length > 0"
|
||||||
:data="cardInfo.packageList"
|
:data="cardInfo.packageList"
|
||||||
class="package-table"
|
class="package-table"
|
||||||
border
|
border
|
||||||
>
|
max-height="400"
|
||||||
<ElTableColumn label="套餐名称" min-width="150">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElButton
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
class="package-name-link"
|
|
||||||
@click="handleShowDailyRecords(scope.row)"
|
|
||||||
v-permission="'package-usage:daily-records:view'"
|
|
||||||
>
|
|
||||||
{{ scope.row.package_name }}
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="package_type" label="套餐类型" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElTag
|
|
||||||
:type="scope.row.package_type === 'formal' ? 'primary' : 'warning'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ scope.row.package_type === 'formal' ? '正式套餐' : '加油包' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="usage_type" label="使用类型" width="110">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElTag
|
|
||||||
:type="scope.row.usage_type === 'single_card' ? 'success' : 'info'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ scope.row.usage_type === 'single_card' ? '单卡' : '设备' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="status_name" label="状态" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElTag :type="getPackageStatusTagType(scope.row.status)" size="small">
|
|
||||||
{{ scope.row.status_name }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="data_limit_mb" label="真流量总量" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDataSize(scope.row.data_limit_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="data_usage_mb" label="真流量已用" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDataSize(scope.row.data_usage_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_limit_mb" label="虚流量总量" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDataSize(scope.row.virtual_limit_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_used_mb" label="虚流量已用" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDataSize(scope.row.virtual_used_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_remain_mb" label="虚流量剩余" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDataSize(scope.row.virtual_remain_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_ratio" label="虚流量比例" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.virtual_ratio?.toFixed(2) || '1.00' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="priority" label="优先级" width="80" />
|
|
||||||
<ElTableColumn prop="master_usage_id" label="主套餐ID" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.master_usage_id || '-' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn
|
|
||||||
prop="activated_at"
|
|
||||||
label="激活时间"
|
|
||||||
width="170"
|
|
||||||
show-overflow-tooltip
|
|
||||||
>
|
>
|
||||||
<template #default="scope">
|
<ElTableColumn label="套餐名称" min-width="150">
|
||||||
{{ formatDateTime(scope.row.activated_at) }}
|
<template #default="scope">
|
||||||
</template>
|
<ElButton
|
||||||
</ElTableColumn>
|
type="primary"
|
||||||
<ElTableColumn prop="expires_at" label="到期时间" width="170" show-overflow-tooltip>
|
link
|
||||||
<template #default="scope">
|
class="package-name-link"
|
||||||
{{ formatDateTime(scope.row.expires_at) }}
|
@click="handleShowDailyRecords(scope.row)"
|
||||||
</template>
|
v-permission="'package-usage:daily-records:view'"
|
||||||
</ElTableColumn>
|
>
|
||||||
<ElTableColumn prop="created_at" label="创建时间" width="170" show-overflow-tooltip>
|
{{ scope.row.package_name }}
|
||||||
<template #default="scope">
|
</ElButton>
|
||||||
{{ formatDateTime(scope.row.created_at) }}
|
</template>
|
||||||
</template>
|
</ElTableColumn>
|
||||||
</ElTableColumn>
|
<ElTableColumn prop="package_type" label="套餐类型" width="100">
|
||||||
</ElTable>
|
<template #default="scope">
|
||||||
<ElEmpty v-else description="暂无套餐数据" :image-size="100" />
|
<ElTag
|
||||||
</div>
|
:type="scope.row.package_type === 'formal' ? 'primary' : 'warning'"
|
||||||
</ElCard>
|
size="small"
|
||||||
|
>
|
||||||
|
{{ scope.row.package_type === 'formal' ? '正式套餐' : '加油包' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="usage_type" label="使用类型" width="110">
|
||||||
|
<template #default="scope">
|
||||||
|
<ElTag
|
||||||
|
:type="scope.row.usage_type === 'single_card' ? 'success' : 'info'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ scope.row.usage_type === 'single_card' ? '单卡' : '设备' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="status_name" label="状态" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<ElTag :type="getPackageStatusTagType(scope.row.status)" size="small">
|
||||||
|
{{ scope.row.status_name }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="data_limit_mb" label="真流量总量" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDataSize(scope.row.data_limit_mb) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="data_usage_mb" label="真流量已用" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDataSize(scope.row.data_usage_mb) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="virtual_limit_mb" label="虚流量总量" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDataSize(scope.row.virtual_limit_mb) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="virtual_used_mb" label="虚流量已用" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDataSize(scope.row.virtual_used_mb) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="virtual_remain_mb" label="虚流量剩余" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDataSize(scope.row.virtual_remain_mb) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="virtual_ratio" label="虚流量比例" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.virtual_ratio?.toFixed(2) || '1.00' }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="priority" label="优先级" width="80" />
|
||||||
|
<ElTableColumn
|
||||||
|
prop="activated_at"
|
||||||
|
label="激活时间"
|
||||||
|
width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDateTime(scope.row.activated_at) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn
|
||||||
|
prop="expires_at"
|
||||||
|
label="到期时间"
|
||||||
|
width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDateTime(scope.row.expires_at) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn
|
||||||
|
prop="created_at"
|
||||||
|
label="创建时间"
|
||||||
|
width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDateTime(scope.row.created_at) }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<ElEmpty v-else description="暂无套餐数据" :image-size="100" />
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第六行:钱包流水列表 -->
|
<!-- 第六行:钱包流水列表 -->
|
||||||
@@ -955,7 +917,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="上行速率" prop="upload_speed">
|
<ElFormItem label="上行速率" prop="upload_speed">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
@@ -965,7 +927,7 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -982,7 +944,7 @@
|
|||||||
|
|
||||||
<!-- 切换SIM卡对话框 -->
|
<!-- 切换SIM卡对话框 -->
|
||||||
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
||||||
<div v-if="loadingDeviceCards" style="text-align: center; padding: 40px">
|
<div v-if="loadingDeviceCards" style=" padding: 40px;text-align: center">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1026,7 +988,7 @@
|
|||||||
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
||||||
:value="card.iccid"
|
:value="card.iccid"
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span>{{ card.iccid }}</span>
|
<span>{{ card.iccid }}</span>
|
||||||
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
</div>
|
</div>
|
||||||
@@ -1117,7 +1079,7 @@
|
|||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
|
||||||
<!-- 筛选器 -->
|
<!-- 筛选器 -->
|
||||||
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 12px">
|
<div style=" display: flex; gap: 12px; align-items: center;margin-bottom: 16px">
|
||||||
<span style="font-weight: 500">筛选方式:</span>
|
<span style="font-weight: 500">筛选方式:</span>
|
||||||
<ElRadioGroup v-model="dateFilterType" @change="handleDateFilterChange">
|
<ElRadioGroup v-model="dateFilterType" @change="handleDateFilterChange">
|
||||||
<ElRadio value="all">全部</ElRadio>
|
<ElRadio value="all">全部</ElRadio>
|
||||||
@@ -1183,7 +1145,7 @@
|
|||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
</ElTable>
|
</ElTable>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!dailyRecordsLoading" style="text-align: center; padding: 40px 0">
|
<div v-else-if="!dailyRecordsLoading" style=" padding: 40px 0;text-align: center">
|
||||||
<ElEmpty description="暂无流量详单数据" />
|
<ElEmpty description="暂无流量详单数据" />
|
||||||
</div>
|
</div>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
@@ -1221,7 +1183,7 @@
|
|||||||
ElInput
|
ElInput
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
import { Loading, Search, Refresh } from '@element-plus/icons-vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { EnterpriseService } from '@/api/modules/enterprise'
|
import { EnterpriseService } from '@/api/modules/enterprise'
|
||||||
import { CardService, AssetService, DeviceService } from '@/api/modules'
|
import { CardService, AssetService, DeviceService } from '@/api/modules'
|
||||||
@@ -1773,6 +1735,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算信号强度等级(基于RSSI、RSRP、RSRQ)
|
||||||
|
const calculateSignalStrength = () => {
|
||||||
|
if (!deviceRealtime.value) return '--'
|
||||||
|
|
||||||
|
const rssi = deviceRealtime.value.rssi ? Number(deviceRealtime.value.rssi) : null
|
||||||
|
const rsrp = deviceRealtime.value.rsrp ? Number(deviceRealtime.value.rsrp) : null
|
||||||
|
const rsrq = deviceRealtime.value.rsrq ? Number(deviceRealtime.value.rsrq) : null
|
||||||
|
|
||||||
|
// 优先使用RSRP判断(更准确)
|
||||||
|
// RSRP范围通常在-140到-44 dBm之间,0或正值表示无效数据
|
||||||
|
if (rsrp !== null && !isNaN(rsrp) && rsrp < 0) {
|
||||||
|
if (rsrp >= -80) return '优秀'
|
||||||
|
if (rsrp >= -90) return '良好'
|
||||||
|
if (rsrp >= -100) return '一般'
|
||||||
|
if (rsrp >= -110) return '较差'
|
||||||
|
return '极差'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其次使用RSSI
|
||||||
|
// RSSI范围通常在-113到-51 dBm之间,0或正值表示无效数据
|
||||||
|
if (rssi !== null && !isNaN(rssi) && rssi < 0) {
|
||||||
|
if (rssi >= -70) return '优秀'
|
||||||
|
if (rssi >= -85) return '良好'
|
||||||
|
if (rssi >= -100) return '一般'
|
||||||
|
if (rssi >= -110) return '较差'
|
||||||
|
return '极差'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取信号强度标签类型
|
||||||
|
const getSignalStrengthType = () => {
|
||||||
|
const strength = calculateSignalStrength()
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
优秀: 'success',
|
||||||
|
良好: 'success',
|
||||||
|
一般: 'warning',
|
||||||
|
较差: 'danger',
|
||||||
|
极差: 'danger'
|
||||||
|
}
|
||||||
|
return map[strength] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
// 点击ICCID查看卡详情
|
// 点击ICCID查看卡详情
|
||||||
const handleViewCardDetail = (iccid: string) => {
|
const handleViewCardDetail = (iccid: string) => {
|
||||||
if (!iccid) return
|
if (!iccid) return
|
||||||
@@ -2295,7 +2301,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
enableCardLoading.value = true
|
enableCardLoading.value = true
|
||||||
const res = await CardService.enableIotCard(cardInfo.value.asset_id)
|
const res = await CardService.enableIotCard(cardInfo.value.iccid)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('启用成功')
|
ElMessage.success('启用成功')
|
||||||
await handleRefreshAsset()
|
await handleRefreshAsset()
|
||||||
@@ -2318,7 +2324,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
disableCardLoading.value = true
|
disableCardLoading.value = true
|
||||||
const res = await CardService.disableIotCard(cardInfo.value.asset_id)
|
const res = await CardService.disableIotCard(cardInfo.value.iccid)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('停用成功')
|
ElMessage.success('停用成功')
|
||||||
await handleRefreshAsset()
|
await handleRefreshAsset()
|
||||||
@@ -2380,7 +2386,6 @@
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
console.error('重启失败:', error)
|
console.error('重启失败:', error)
|
||||||
ElMessage.error(error?.message || '重启失败')
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
rebootDeviceLoading.value = false
|
rebootDeviceLoading.value = false
|
||||||
@@ -2405,7 +2410,6 @@
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
console.error('恢复出厂失败:', error)
|
console.error('恢复出厂失败:', error)
|
||||||
ElMessage.error(error?.message || '恢复出厂失败')
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
resetDeviceLoading.value = false
|
resetDeviceLoading.value = false
|
||||||
@@ -2560,7 +2564,6 @@
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
console.error('手动停用失败:', error)
|
console.error('手动停用失败:', error)
|
||||||
ElMessage.error(error?.message || '手动停用失败')
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
manualDeactivateDeviceLoading.value = false
|
manualDeactivateDeviceLoading.value = false
|
||||||
@@ -2672,9 +2675,9 @@
|
|||||||
|
|
||||||
.iccid-search {
|
.iccid-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
.iccid-input-wrapper {
|
.iccid-input-wrapper {
|
||||||
@@ -2686,8 +2689,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 12px);
|
top: calc(100% + 12px);
|
||||||
left: 0;
|
left: 0;
|
||||||
white-space: nowrap;
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
// 三角箭头 - 上边框样式
|
// 三角箭头 - 上边框样式
|
||||||
.magnifier-arrow {
|
.magnifier-arrow {
|
||||||
@@ -2706,6 +2709,7 @@
|
|||||||
.magnifier-content {
|
.magnifier-content {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -2716,8 +2720,7 @@
|
|||||||
background-color: var(--el-bg-color, #fff);
|
background-color: var(--el-bg-color, #fff);
|
||||||
border: 2px solid var(--el-color-primary);
|
border: 2px solid var(--el-color-primary);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
|
||||||
font-family: 'Courier New', Courier, monospace;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2738,7 +2741,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 移动端
|
// 移动端
|
||||||
@media (max-width: 768px) {
|
@media (width <= 768px) {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
.search-card {
|
.search-card {
|
||||||
@@ -2827,6 +2830,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 钱包和套餐两栏在小屏幕也改为垂直布局
|
||||||
|
.wallet-package-row {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.wallet-col,
|
||||||
|
.package-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 768px) {
|
@media (width <= 768px) {
|
||||||
@@ -2858,8 +2872,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.package-series-value {
|
.package-series-value {
|
||||||
color: var(--el-color-primary);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
@@ -2882,8 +2896,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.traffic-progress-wrapper {
|
.traffic-progress-wrapper {
|
||||||
margin-top: 24px;
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
margin-top: 24px;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
@@ -2908,19 +2922,19 @@
|
|||||||
|
|
||||||
.flow-stats {
|
.flow-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
justify-content: space-between;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
background: var(--el-bg-color, #fff);
|
background: var(--el-bg-color, #fff);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -2987,19 +3001,19 @@
|
|||||||
|
|
||||||
.progress-stats {
|
.progress-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
justify-content: space-between;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -3031,13 +3045,13 @@
|
|||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex: 1;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -3054,9 +3068,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 钱包概况和套餐列表两栏布局
|
||||||
|
.wallet-package-row {
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
.wallet-col {
|
||||||
|
flex: 0 0 380px;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 套餐信息卡片
|
// 套餐信息卡片
|
||||||
.package-info {
|
.package-info {
|
||||||
.package-table-wrapper {
|
.package-table-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
.package-table {
|
.package-table {
|
||||||
:deep(.el-table__header) {
|
:deep(.el-table__header) {
|
||||||
th {
|
th {
|
||||||
@@ -3073,66 +3119,66 @@
|
|||||||
.wallet-balance-cards {
|
.wallet-balance-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
.balance-card {
|
.balance-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 18px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
padding: 18px 20px;
|
||||||
padding: 16px;
|
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
border-color: var(--el-color-primary);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
|
||||||
|
transform: translateX(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-icon {
|
.balance-icon {
|
||||||
font-size: 28px;
|
flex-shrink: 0;
|
||||||
|
font-size: 32px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-info {
|
.balance-info {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
|
|
||||||
.balance-label {
|
.balance-label {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-value {
|
.balance-value {
|
||||||
font-size: 20px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.total {
|
&.total {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
|
|
||||||
.balance-label,
|
|
||||||
.balance-value {
|
.balance-value {
|
||||||
color: #fff;
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.available {
|
&.available {
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
|
|
||||||
.balance-label,
|
|
||||||
.balance-value {
|
.balance-value {
|
||||||
color: #fff;
|
color: var(--el-color-success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.frozen {
|
&.frozen {
|
||||||
background: var(--el-fill-color-light);
|
.balance-value {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3142,10 +3188,10 @@
|
|||||||
.wallet-info {
|
.wallet-info {
|
||||||
.wallet-header {
|
.wallet-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3155,9 +3201,9 @@
|
|||||||
|
|
||||||
.filter-section {
|
.filter-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3172,10 +3218,10 @@
|
|||||||
|
|
||||||
.reference-no-link {
|
.reference-no-link {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-style: dashed;
|
text-decoration-style: dashed;
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary-light-3);
|
color: var(--el-color-primary-light-3);
|
||||||
|
|||||||
@@ -139,7 +139,8 @@
|
|||||||
{
|
{
|
||||||
label: '实付金额',
|
label: '实付金额',
|
||||||
prop: 'actual_paid_amount',
|
prop: 'actual_paid_amount',
|
||||||
formatter: (value) => (value !== undefined && value !== null ? formatCurrency(value) : '-')
|
formatter: (value) =>
|
||||||
|
value !== undefined && value !== null ? formatCurrency(value) : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'IoT卡ID',
|
label: 'IoT卡ID',
|
||||||
@@ -157,7 +158,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '订单渠道',
|
label: '订单渠道',
|
||||||
formatter: (_, data) => (data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-')
|
formatter: (_, data) =>
|
||||||
|
data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '购买备注',
|
label: '购买备注',
|
||||||
@@ -256,8 +258,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -269,12 +271,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-items-section {
|
.order-items-section {
|
||||||
margin-top: 30px;
|
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
margin-top: 30px;
|
||||||
border-top: 1px solid var(--el-border-color-lighter);
|
border-top: 1px solid var(--el-border-color-lighter);
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
margin: 0 0 16px 0;
|
margin: 0 0 16px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
@@ -284,10 +286,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<span>{{ card.iccid }}</span>
|
<span>{{ card.iccid }}</span>
|
||||||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
<span style=" font-size: 12px;color: var(--el-text-color-secondary)">
|
||||||
{{ card.msisdn || '无接入号' }}
|
{{ card.msisdn || '无接入号' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<span>{{ device.virtual_no }}</span>
|
<span>{{ device.virtual_no }}</span>
|
||||||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
<span style=" font-size: 12px;color: var(--el-text-color-secondary)">
|
||||||
{{ device.device_name }}
|
{{ device.device_name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<span>{{ pkg.package_name }}</span>
|
<span>{{ pkg.package_name }}</span>
|
||||||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
<span style=" font-size: 12px;color: var(--el-text-color-secondary)">
|
||||||
¥{{ (pkg.cost_price / 100).toFixed(2) }}
|
¥{{ (pkg.cost_price / 100).toFixed(2) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -259,8 +259,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -274,10 +274,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -61,11 +61,11 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增套餐' : '编辑套餐'"
|
:title="dialogType === 'add' ? '新增套餐' : '编辑套餐'"
|
||||||
width="60%"
|
width="45%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="24">
|
<ElCol :span="24">
|
||||||
<ElFormItem label="套餐编码" prop="package_code">
|
<ElFormItem label="套餐编码" prop="package_code">
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
:value="series.id"
|
:value="series.id"
|
||||||
/>
|
/>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div style="padding: 10px; text-align: center; color: #909399">
|
<div style="padding: 10px; color: #909399; text-align: center">
|
||||||
暂无套餐系列,请先创建套餐系列
|
暂无套餐系列,请先创建套餐系列
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -182,17 +182,17 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
<!-- 流量重置天数(calendar_type为by_day时显示) -->
|
<!-- 套餐天数(流量重置周期为每日,或calendar_type为by_day时显示) -->
|
||||||
<ElRow :gutter="20" v-if="form.calendar_type === 'by_day'">
|
<ElRow :gutter="20" v-if="form.data_reset_cycle === 'daily' || form.calendar_type === 'by_day'">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="流量重置天数" prop="duration_days">
|
<ElFormItem label="套餐天数" prop="duration_days">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.duration_days"
|
v-model="form.duration_days"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="3650"
|
:max="3650"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入流量重置天数"
|
placeholder="请输入套餐天数"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
@@ -341,7 +341,11 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<ElButton @click="retailPriceDialogVisible = false">取消</ElButton>
|
<ElButton @click="retailPriceDialogVisible = false">取消</ElButton>
|
||||||
<ElButton type="primary" @click="handleConfirmUpdateRetailPrice" :loading="retailPriceLoading">
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirmUpdateRetailPrice"
|
||||||
|
:loading="retailPriceLoading"
|
||||||
|
>
|
||||||
确认修改
|
确认修改
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -547,13 +551,25 @@
|
|||||||
cost_price: [{ required: true, message: '请输入成本价', trigger: 'blur' }]
|
cost_price: [{ required: true, message: '请输入成本价', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果启用虚流量,则虚流量额度为必填
|
// 如果启用虚流量,则虚流量额度为必填,并且不能超过真流量额度
|
||||||
if (form.enable_virtual_data) {
|
if (form.enable_virtual_data) {
|
||||||
baseRules.virtual_data_mb = [{ required: true, message: '请输入虚流量额度', trigger: 'blur' }]
|
baseRules.virtual_data_mb = [
|
||||||
|
{ required: true, message: '请输入虚流量额度', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule: any, value: any, callback: any) => {
|
||||||
|
if (value && form.real_data_mb && value > form.real_data_mb) {
|
||||||
|
callback(new Error('虚流量额度不能超过真流量额度'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果套餐周期类型是按天,则套餐天数为必填
|
// 如果流量重置周期是每日,或套餐周期类型是按天,则套餐天数为必填
|
||||||
if (form.calendar_type === 'by_day') {
|
if (form.data_reset_cycle === 'daily' || form.calendar_type === 'by_day') {
|
||||||
baseRules.duration_days = [
|
baseRules.duration_days = [
|
||||||
{ required: true, message: '请输入套餐天数', trigger: 'blur' },
|
{ required: true, message: '请输入套餐天数', trigger: 'blur' },
|
||||||
{ type: 'number', min: 1, max: 3650, message: '套餐天数范围 1-3650 天', trigger: 'blur' }
|
{ type: 'number', min: 1, max: 3650, message: '套餐天数范围 1-3650 天', trigger: 'blur' }
|
||||||
@@ -755,22 +771,36 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听流量重置周期变化,不是每月时清空套餐周期类型
|
// 监听真流量额度变化,如果虚流量超过真流量则触发验证
|
||||||
|
watch(
|
||||||
|
() => form.real_data_mb,
|
||||||
|
() => {
|
||||||
|
if (form.enable_virtual_data && formRef.value) {
|
||||||
|
formRef.value.validateField('virtual_data_mb')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听流量重置周期变化
|
||||||
watch(
|
watch(
|
||||||
() => form.data_reset_cycle,
|
() => form.data_reset_cycle,
|
||||||
(cycle) => {
|
(cycle) => {
|
||||||
|
// 不是每月时清空套餐周期类型
|
||||||
if (cycle !== 'monthly') {
|
if (cycle !== 'monthly') {
|
||||||
form.calendar_type = undefined
|
form.calendar_type = undefined
|
||||||
|
}
|
||||||
|
// 不是每日且calendar_type不是按天时,清空套餐天数
|
||||||
|
if (cycle !== 'daily' && form.calendar_type !== 'by_day') {
|
||||||
form.duration_days = undefined
|
form.duration_days = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听套餐周期类型变化,不是按天时清空套餐天数
|
// 监听套餐周期类型变化,不是按天且流量重置周期不是每日时清空套餐天数
|
||||||
watch(
|
watch(
|
||||||
() => form.calendar_type,
|
() => form.calendar_type,
|
||||||
(type) => {
|
(type) => {
|
||||||
if (type !== 'by_day') {
|
if (type !== 'by_day' && form.data_reset_cycle !== 'daily') {
|
||||||
form.duration_days = undefined
|
form.duration_days = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1036,7 +1066,8 @@
|
|||||||
if (form.data_reset_cycle === 'monthly' && form.calendar_type) {
|
if (form.data_reset_cycle === 'monthly' && form.calendar_type) {
|
||||||
data.calendar_type = form.calendar_type
|
data.calendar_type = form.calendar_type
|
||||||
}
|
}
|
||||||
if (form.calendar_type === 'by_day' && form.duration_days) {
|
// 当流量重置周期为每日,或套餐周期类型为按天时,传递套餐天数
|
||||||
|
if ((form.data_reset_cycle === 'daily' || form.calendar_type === 'by_day') && form.duration_days) {
|
||||||
data.duration_days = form.duration_days
|
data.duration_days = form.duration_days
|
||||||
}
|
}
|
||||||
if (suggestedRetailPriceInCents !== undefined) {
|
if (suggestedRetailPriceInCents !== undefined) {
|
||||||
|
|||||||
@@ -244,8 +244,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -259,10 +259,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -597,8 +597,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
@@ -619,8 +619,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
@@ -642,10 +642,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -211,7 +211,7 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增代理系列授权' : '编辑代理系列授权'"
|
:title="dialogType === 'add' ? '新增代理系列授权' : '编辑代理系列授权'"
|
||||||
width="60%"
|
width="45%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
@@ -2184,8 +2184,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-section-title {
|
.form-section-title {
|
||||||
margin: 20px 0 16px;
|
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
margin: 20px 0 16px;
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
@@ -2197,8 +2197,8 @@
|
|||||||
|
|
||||||
.commission-type-display {
|
.commission-type-display {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.type-hint {
|
.type-hint {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -2252,8 +2252,8 @@
|
|||||||
|
|
||||||
.cost-price-input-wrapper {
|
.cost-price-input-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.min-cost-hint {
|
.min-cost-hint {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -2282,8 +2282,8 @@
|
|||||||
.info-label {
|
.info-label {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--el-text-color-regular);
|
color: var(--el-text-color-regular);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
@@ -2294,8 +2294,8 @@
|
|||||||
|
|
||||||
.readonly-value {
|
.readonly-value {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-list-dialog-content {
|
.package-list-dialog-content {
|
||||||
|
|||||||
@@ -285,8 +285,8 @@
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.alert-history-page {
|
.alert-history-page {
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -58,7 +58,11 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="告警级别" prop="alert_level">
|
<el-form-item label="告警级别" prop="alert_level">
|
||||||
<el-select v-model="form.alert_level" placeholder="请选择告警级别" style="width: 100%">
|
<el-select
|
||||||
|
v-model="form.alert_level"
|
||||||
|
placeholder="请选择告警级别"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option label="警告" value="warning" />
|
<el-option label="警告" value="warning" />
|
||||||
<el-option label="严重" value="critical" />
|
<el-option label="严重" value="critical" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -155,188 +159,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, nextTick, watch, h } from 'vue'
|
import { ref, reactive, computed, nextTick, watch, h } from 'vue'
|
||||||
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
||||||
import { PollingAlertService } from '@/api/modules'
|
import { PollingAlertService } from '@/api/modules'
|
||||||
import type { PollingAlertRule, CreatePollingAlertRuleRequest } from '@/types/api'
|
import type { PollingAlertRule, CreatePollingAlertRuleRequest } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<PollingAlertRule[]>([])
|
const tableData = ref<PollingAlertRule[]>([])
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogType = ref<'create' | 'edit'>('create')
|
const dialogType = ref<'create' | 'edit'>('create')
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const contextMenuRef = ref()
|
const contextMenuRef = ref()
|
||||||
const currentRow = ref<PollingAlertRule | null>(null)
|
const currentRow = ref<PollingAlertRule | null>(null)
|
||||||
|
|
||||||
// 使用表格右键菜单功能
|
// 使用表格右键菜单功能
|
||||||
const {
|
const {
|
||||||
showContextMenuHint,
|
showContextMenuHint,
|
||||||
hintPosition,
|
hintPosition,
|
||||||
getRowClassName,
|
getRowClassName,
|
||||||
handleCellMouseEnter,
|
handleCellMouseEnter,
|
||||||
handleCellMouseLeave
|
handleCellMouseLeave
|
||||||
} = useTableContextMenu()
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const form = reactive<CreatePollingAlertRuleRequest & { id?: number }>({
|
const form = reactive<CreatePollingAlertRuleRequest & { id?: number }>({
|
||||||
rule_name: '',
|
|
||||||
alert_level: '',
|
|
||||||
metric_type: '',
|
|
||||||
task_type: '',
|
|
||||||
threshold: 0,
|
|
||||||
operator: '>',
|
|
||||||
cooldown_minutes: 5,
|
|
||||||
notify_channels: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const rules: FormRules = {
|
|
||||||
rule_name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
|
||||||
alert_level: [{ required: true, message: '请选择告警级别', trigger: 'change' }],
|
|
||||||
metric_type: [{ required: true, message: '请选择指标类型', trigger: 'change' }],
|
|
||||||
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
|
||||||
threshold: [{ required: true, message: '请输入阈值', trigger: 'blur' }],
|
|
||||||
operator: [{ required: true, message: '请选择运算符', trigger: 'change' }],
|
|
||||||
cooldown_minutes: [{ required: true, message: '请输入冷却时间', trigger: 'blur' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAlertLevelLabel = (level: string) => {
|
|
||||||
return level === 'critical' ? '严重' : '警告'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMetricTypeLabel = (type: string) => {
|
|
||||||
const labels: Record<string, string> = {
|
|
||||||
queue_size: '队列大小',
|
|
||||||
success_rate: '成功率',
|
|
||||||
avg_duration: '平均执行时间',
|
|
||||||
concurrency: '并发数'
|
|
||||||
}
|
|
||||||
return labels[type] || type
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTaskTypeLabel = (type: string) => {
|
|
||||||
const labels: Record<string, string> = {
|
|
||||||
'polling:realname': '实名认证轮询',
|
|
||||||
'polling:carddata': '卡数据轮询',
|
|
||||||
'polling:package': '套餐轮询'
|
|
||||||
}
|
|
||||||
return labels[type] || type
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态列配置
|
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
|
||||||
{
|
|
||||||
prop: 'rule_name',
|
|
||||||
label: '规则名称',
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'alert_level',
|
|
||||||
label: '告警级别',
|
|
||||||
width: 100,
|
|
||||||
slots: {
|
|
||||||
default: ({ row }: any) =>
|
|
||||||
h(
|
|
||||||
ElTag,
|
|
||||||
{ type: row.alert_level === 'critical' ? 'danger' : 'warning' },
|
|
||||||
() => getAlertLevelLabel(row.alert_level)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'metric_type_name',
|
|
||||||
label: '指标类型',
|
|
||||||
width: 120,
|
|
||||||
formatter: (row: any) => getMetricTypeLabel(row.metric_type)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'task_type_name',
|
|
||||||
label: '任务类型',
|
|
||||||
width: 150,
|
|
||||||
formatter: (row: any) => getTaskTypeLabel(row.task_type)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'operator',
|
|
||||||
label: '运算符',
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'threshold',
|
|
||||||
label: '阈值',
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'cooldown_minutes',
|
|
||||||
label: '冷却时间(分钟)',
|
|
||||||
width: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'status',
|
|
||||||
label: '状态',
|
|
||||||
width: 80,
|
|
||||||
slots: {
|
|
||||||
default: ({ row }: any) =>
|
|
||||||
h(ElTag, { type: row.status === 1 ? 'success' : 'info' }, () =>
|
|
||||||
row.status === 1 ? '启用' : '禁用'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'created_at',
|
|
||||||
label: '创建时间',
|
|
||||||
width: 180,
|
|
||||||
formatter: (row: any) => formatDateTime(row.created_at)
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const columnOptions = computed(() =>
|
|
||||||
columns.value.map((col) => ({
|
|
||||||
label: col.label,
|
|
||||||
prop: col.prop || col.label,
|
|
||||||
visible: true
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
// 右键菜单项
|
|
||||||
const contextMenuItems = computed<MenuItemType[]>(() => {
|
|
||||||
return [
|
|
||||||
{ key: 'edit', label: '编辑' },
|
|
||||||
{ key: 'toggle', label: currentRow.value?.status === 1 ? '禁用' : '启用' },
|
|
||||||
{ key: 'delete', label: '删除' }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(dialogVisible, (val) => {
|
|
||||||
if (!val) {
|
|
||||||
nextTick(() => {
|
|
||||||
formRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await PollingAlertService.getPollingAlertRules()
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
tableData.value = res.data.items
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('加载数据失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
Object.assign(form, {
|
|
||||||
id: undefined,
|
|
||||||
rule_name: '',
|
rule_name: '',
|
||||||
alert_level: '',
|
alert_level: '',
|
||||||
metric_type: '',
|
metric_type: '',
|
||||||
@@ -346,134 +195,287 @@ const resetForm = () => {
|
|||||||
cooldown_minutes: 5,
|
cooldown_minutes: 5,
|
||||||
notify_channels: ''
|
notify_channels: ''
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const showCreateDialog = () => {
|
const rules: FormRules = {
|
||||||
resetForm()
|
rule_name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||||
dialogType.value = 'create'
|
alert_level: [{ required: true, message: '请选择告警级别', trigger: 'change' }],
|
||||||
dialogVisible.value = true
|
metric_type: [{ required: true, message: '请选择指标类型', trigger: 'change' }],
|
||||||
nextTick(() => {
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
||||||
formRef.value?.clearValidate()
|
threshold: [{ required: true, message: '请输入阈值', trigger: 'blur' }],
|
||||||
|
operator: [{ required: true, message: '请选择运算符', trigger: 'change' }],
|
||||||
|
cooldown_minutes: [{ required: true, message: '请输入冷却时间', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAlertLevelLabel = (level: string) => {
|
||||||
|
return level === 'critical' ? '严重' : '警告'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetricTypeLabel = (type: string) => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
queue_size: '队列大小',
|
||||||
|
success_rate: '成功率',
|
||||||
|
avg_duration: '平均执行时间',
|
||||||
|
concurrency: '并发数'
|
||||||
|
}
|
||||||
|
return labels[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTaskTypeLabel = (type: string) => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
'polling:realname': '实名认证轮询',
|
||||||
|
'polling:carddata': '卡数据轮询',
|
||||||
|
'polling:package': '套餐轮询'
|
||||||
|
}
|
||||||
|
return labels[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态列配置
|
||||||
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
|
{
|
||||||
|
prop: 'rule_name',
|
||||||
|
label: '规则名称',
|
||||||
|
minWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'alert_level',
|
||||||
|
label: '告警级别',
|
||||||
|
width: 100,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: any) =>
|
||||||
|
h(ElTag, { type: row.alert_level === 'critical' ? 'danger' : 'warning' }, () =>
|
||||||
|
getAlertLevelLabel(row.alert_level)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'metric_type_name',
|
||||||
|
label: '指标类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: any) => getMetricTypeLabel(row.metric_type)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'task_type_name',
|
||||||
|
label: '任务类型',
|
||||||
|
width: 150,
|
||||||
|
formatter: (row: any) => getTaskTypeLabel(row.task_type)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'operator',
|
||||||
|
label: '运算符',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'threshold',
|
||||||
|
label: '阈值',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'cooldown_minutes',
|
||||||
|
label: '冷却时间(分钟)',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
width: 80,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: any) =>
|
||||||
|
h(ElTag, { type: row.status === 1 ? 'success' : 'info' }, () =>
|
||||||
|
row.status === 1 ? '启用' : '禁用'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'created_at',
|
||||||
|
label: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const columnOptions = computed(() =>
|
||||||
|
columns.value.map((col) => ({
|
||||||
|
label: col.label,
|
||||||
|
prop: col.prop || col.label,
|
||||||
|
visible: true
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// 右键菜单项
|
||||||
|
const contextMenuItems = computed<MenuItemType[]>(() => {
|
||||||
|
return [
|
||||||
|
{ key: 'edit', label: '编辑' },
|
||||||
|
{ key: 'toggle', label: currentRow.value?.status === 1 ? '禁用' : '启用' },
|
||||||
|
{ key: 'delete', label: '删除' }
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const showEditDialog = async (row: PollingAlertRule) => {
|
watch(dialogVisible, (val) => {
|
||||||
try {
|
if (!val) {
|
||||||
const res = await PollingAlertService.getPollingAlertRuleById(row.id)
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
Object.assign(form, {
|
|
||||||
id: res.data.id,
|
|
||||||
rule_name: res.data.rule_name,
|
|
||||||
alert_level: res.data.alert_level,
|
|
||||||
metric_type: res.data.metric_type,
|
|
||||||
task_type: res.data.task_type,
|
|
||||||
threshold: res.data.threshold,
|
|
||||||
operator: res.data.operator,
|
|
||||||
cooldown_minutes: res.data.cooldown_minutes,
|
|
||||||
notify_channels: res.data.notify_channels
|
|
||||||
})
|
|
||||||
dialogType.value = 'edit'
|
|
||||||
dialogVisible.value = true
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('获取规则详情失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!formRef.value) return
|
|
||||||
await formRef.value.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
try {
|
|
||||||
if (dialogType.value === 'create') {
|
|
||||||
const res = await PollingAlertService.createPollingAlertRule(form)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('创建成功')
|
|
||||||
dialogVisible.value = false
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { id, metric_type, task_type, operator, ...updateData } = form
|
|
||||||
const res = await PollingAlertService.updatePollingAlertRule(id!, updateData)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('更新成功')
|
|
||||||
dialogVisible.value = false
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(dialogType.value === 'create' ? '创建失败' : '更新失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const toggleStatus = async (row: PollingAlertRule) => {
|
const loadData = async () => {
|
||||||
try {
|
loading.value = true
|
||||||
const newStatus = row.status === 1 ? 0 : 1
|
|
||||||
const res = await PollingAlertService.updatePollingAlertRule(row.id, { status: newStatus })
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success(newStatus === 1 ? '已启用' : '已禁用')
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('操作失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (row: PollingAlertRule) => {
|
|
||||||
ElMessageBox.confirm('确定要删除该告警规则吗?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(async () => {
|
|
||||||
try {
|
try {
|
||||||
const res = await PollingAlertService.deletePollingAlertRule(row.id)
|
const res = await PollingAlertService.getPollingAlertRules()
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
tableData.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('加载数据失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(form, {
|
||||||
|
id: undefined,
|
||||||
|
rule_name: '',
|
||||||
|
alert_level: '',
|
||||||
|
metric_type: '',
|
||||||
|
task_type: '',
|
||||||
|
threshold: 0,
|
||||||
|
operator: '>',
|
||||||
|
cooldown_minutes: 5,
|
||||||
|
notify_channels: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCreateDialog = () => {
|
||||||
|
resetForm()
|
||||||
|
dialogType.value = 'create'
|
||||||
|
dialogVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showEditDialog = async (row: PollingAlertRule) => {
|
||||||
|
try {
|
||||||
|
const res = await PollingAlertService.getPollingAlertRuleById(row.id)
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
Object.assign(form, {
|
||||||
|
id: res.data.id,
|
||||||
|
rule_name: res.data.rule_name,
|
||||||
|
alert_level: res.data.alert_level,
|
||||||
|
metric_type: res.data.metric_type,
|
||||||
|
task_type: res.data.task_type,
|
||||||
|
threshold: res.data.threshold,
|
||||||
|
operator: res.data.operator,
|
||||||
|
cooldown_minutes: res.data.cooldown_minutes,
|
||||||
|
notify_channels: res.data.notify_channels
|
||||||
|
})
|
||||||
|
dialogType.value = 'edit'
|
||||||
|
dialogVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('获取规则详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
if (dialogType.value === 'create') {
|
||||||
|
const res = await PollingAlertService.createPollingAlertRule(form)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { id, metric_type, task_type, operator, ...updateData } = form
|
||||||
|
const res = await PollingAlertService.updatePollingAlertRule(id!, updateData)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(dialogType.value === 'create' ? '创建失败' : '更新失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleStatus = async (row: PollingAlertRule) => {
|
||||||
|
try {
|
||||||
|
const newStatus = row.status === 1 ? 0 : 1
|
||||||
|
const res = await PollingAlertService.updatePollingAlertRule(row.id, { status: newStatus })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success(newStatus === 1 ? '已启用' : '已禁用')
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('删除失败')
|
ElMessage.error('操作失败')
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表格行右键菜单
|
|
||||||
const handleRowContextMenu = (row: PollingAlertRule, column: any, event: MouseEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentRow.value = row
|
|
||||||
contextMenuRef.value?.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理右键菜单选择
|
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
|
||||||
if (!currentRow.value) return
|
|
||||||
|
|
||||||
switch (item.key) {
|
|
||||||
case 'edit':
|
|
||||||
showEditDialog(currentRow.value)
|
|
||||||
break
|
|
||||||
case 'toggle':
|
|
||||||
toggleStatus(currentRow.value)
|
|
||||||
break
|
|
||||||
case 'delete':
|
|
||||||
handleDelete(currentRow.value)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
loadData()
|
const handleDelete = (row: PollingAlertRule) => {
|
||||||
|
ElMessageBox.confirm('确定要删除该告警规则吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await PollingAlertService.deletePollingAlertRule(row.id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表格行右键菜单
|
||||||
|
const handleRowContextMenu = (row: PollingAlertRule, column: any, event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
currentRow.value = row
|
||||||
|
contextMenuRef.value?.show(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理右键菜单选择
|
||||||
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
|
if (!currentRow.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'edit':
|
||||||
|
showEditDialog(currentRow.value)
|
||||||
|
break
|
||||||
|
case 'toggle':
|
||||||
|
toggleStatus(currentRow.value)
|
||||||
|
break
|
||||||
|
case 'delete':
|
||||||
|
handleDelete(currentRow.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.alert-rules-page {
|
.alert-rules-page {
|
||||||
height: 100%;
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -71,12 +71,7 @@
|
|||||||
width="500px"
|
width="500px"
|
||||||
@close="handleDialogClose"
|
@close="handleDialogClose"
|
||||||
>
|
>
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<el-form-item label="任务类型">
|
<el-form-item label="任务类型">
|
||||||
<el-input v-model="currentRow.task_type_name" disabled />
|
<el-input v-model="currentRow.task_type_name" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -99,194 +94,183 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { PollingConcurrencyService } from '@/api/modules'
|
import { PollingConcurrencyService } from '@/api/modules'
|
||||||
import type { PollingConcurrency, UpdateConcurrencyParams } from '@/types/api'
|
import type { PollingConcurrency, UpdateConcurrencyParams } from '@/types/api'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<PollingConcurrency[]>([])
|
const tableData = ref<PollingConcurrency[]>([])
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentClickRow = ref<PollingConcurrency | null>(null)
|
const currentClickRow = ref<PollingConcurrency | null>(null)
|
||||||
|
|
||||||
const currentRow = ref<PollingConcurrency>({
|
const currentRow = ref<PollingConcurrency>({
|
||||||
task_type: '',
|
task_type: '',
|
||||||
task_type_name: '',
|
task_type_name: '',
|
||||||
max_concurrency: 0,
|
max_concurrency: 0,
|
||||||
current: 0,
|
current: 0,
|
||||||
available: 0,
|
available: 0,
|
||||||
utilization: 0
|
utilization: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = reactive<UpdateConcurrencyParams>({
|
const formData = reactive<UpdateConcurrencyParams>({
|
||||||
max_concurrency: 100
|
max_concurrency: 100
|
||||||
})
|
})
|
||||||
|
|
||||||
const formRules: FormRules = {
|
const formRules: FormRules = {
|
||||||
max_concurrency: [
|
max_concurrency: [
|
||||||
{ required: true, message: '请输入最大并发数', trigger: 'blur' },
|
{ required: true, message: '请输入最大并发数', trigger: 'blur' },
|
||||||
{ type: 'number', min: 1, max: 1000, message: '并发数范围为 1-1000', trigger: 'blur' }
|
{ type: 'number', min: 1, max: 1000, message: '并发数范围为 1-1000', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
showContextMenuHint,
|
|
||||||
hintPosition,
|
|
||||||
getRowClassName,
|
|
||||||
handleCellMouseEnter,
|
|
||||||
handleCellMouseLeave
|
|
||||||
} = useTableContextMenu()
|
|
||||||
|
|
||||||
const contextMenuItems = computed<MenuItemType[]>(() => [
|
|
||||||
{
|
|
||||||
label: '修改配置',
|
|
||||||
key: 'edit',
|
|
||||||
permission: 'polling_concurrency_update'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '重置',
|
|
||||||
key: 'reset',
|
|
||||||
permission: 'polling_concurrency_reset'
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
|
||||||
const getUtilizationColor = (percentage: number) => {
|
const {
|
||||||
if (percentage >= 90) return '#f56c6c'
|
showContextMenuHint,
|
||||||
if (percentage >= 70) return '#e6a23c'
|
hintPosition,
|
||||||
if (percentage >= 50) return '#409eff'
|
getRowClassName,
|
||||||
return '#67c23a'
|
handleCellMouseEnter,
|
||||||
}
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loadData = async () => {
|
const contextMenuItems = computed<MenuItemType[]>(() => [
|
||||||
loading.value = true
|
{
|
||||||
try {
|
label: '修改配置',
|
||||||
const { data } = await PollingConcurrencyService.getConcurrencyList()
|
key: 'edit',
|
||||||
tableData.value = data.items
|
permission: 'polling_concurrency_update'
|
||||||
} catch (error) {
|
},
|
||||||
ElMessage.error('加载并发配置失败')
|
{
|
||||||
} finally {
|
label: '重置',
|
||||||
loading.value = false
|
key: 'reset',
|
||||||
}
|
permission: 'polling_concurrency_reset'
|
||||||
}
|
|
||||||
|
|
||||||
const handleEdit = async (row: PollingConcurrency) => {
|
|
||||||
try {
|
|
||||||
// 调用接口#2:获取指定任务类型的最新并发配置
|
|
||||||
const { data } = await PollingConcurrencyService.getConcurrencyByType(row.task_type)
|
|
||||||
currentRow.value = { ...data }
|
|
||||||
formData.max_concurrency = data.max_concurrency
|
|
||||||
dialogVisible.value = true
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('获取并发配置失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReset = async (row: PollingConcurrency) => {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
`确定要重置 ${row.task_type_name} 的并发计数吗?`,
|
|
||||||
'提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await PollingConcurrencyService.resetConcurrency({ task_type: row.task_type })
|
|
||||||
ElMessage.success('重置成功')
|
|
||||||
await loadData()
|
|
||||||
} catch (error) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
ElMessage.error('重置失败')
|
|
||||||
}
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const getUtilizationColor = (percentage: number) => {
|
||||||
|
if (percentage >= 90) return '#f56c6c'
|
||||||
|
if (percentage >= 70) return '#e6a23c'
|
||||||
|
if (percentage >= 50) return '#409eff'
|
||||||
|
return '#67c23a'
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleResetAll = async () => {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'确定要重置所有任务类型的并发计数吗?',
|
|
||||||
'提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await PollingConcurrencyService.resetConcurrency()
|
|
||||||
ElMessage.success('重置成功')
|
|
||||||
await loadData()
|
|
||||||
} catch (error) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
ElMessage.error('重置失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!formRef.value) return
|
|
||||||
|
|
||||||
await formRef.value.validate(async valid => {
|
|
||||||
if (!valid) return
|
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await PollingConcurrencyService.updateConcurrency(
|
const { data } = await PollingConcurrencyService.getConcurrencyList()
|
||||||
currentRow.value.task_type,
|
tableData.value = data.items
|
||||||
formData
|
} catch (error) {
|
||||||
)
|
ElMessage.error('加载并发配置失败')
|
||||||
ElMessage.success('修改成功')
|
} finally {
|
||||||
dialogVisible.value = false
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = async (row: PollingConcurrency) => {
|
||||||
|
try {
|
||||||
|
// 调用接口#2:获取指定任务类型的最新并发配置
|
||||||
|
const { data } = await PollingConcurrencyService.getConcurrencyByType(row.task_type)
|
||||||
|
currentRow.value = { ...data }
|
||||||
|
formData.max_concurrency = data.max_concurrency
|
||||||
|
dialogVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('获取并发配置失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = async (row: PollingConcurrency) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定要重置 ${row.task_type_name} 的并发计数吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await PollingConcurrencyService.resetConcurrency({ task_type: row.task_type })
|
||||||
|
ElMessage.success('重置成功')
|
||||||
await loadData()
|
await loadData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('修改失败')
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error('重置失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDialogClose = () => {
|
|
||||||
formRef.value?.resetFields()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表格行右键菜单
|
|
||||||
const handleRowContextMenu = (row: PollingConcurrency, column: any, event: MouseEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentClickRow.value = row
|
|
||||||
contextMenuRef.value?.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理右键菜单选择
|
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
|
||||||
if (!currentClickRow.value) return
|
|
||||||
|
|
||||||
switch (item.key) {
|
|
||||||
case 'edit':
|
|
||||||
handleEdit(currentClickRow.value)
|
|
||||||
break
|
|
||||||
case 'reset':
|
|
||||||
handleReset(currentClickRow.value)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
const handleResetAll = async () => {
|
||||||
loadData()
|
try {
|
||||||
})
|
await ElMessageBox.confirm('确定要重置所有任务类型的并发计数吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await PollingConcurrencyService.resetConcurrency()
|
||||||
|
ElMessage.success('重置成功')
|
||||||
|
await loadData()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error('重置失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await PollingConcurrencyService.updateConcurrency(currentRow.value.task_type, formData)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
await loadData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('修改失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表格行右键菜单
|
||||||
|
const handleRowContextMenu = (row: PollingConcurrency, column: any, event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
currentClickRow.value = row
|
||||||
|
contextMenuRef.value?.show(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理右键菜单选择
|
||||||
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
|
if (!currentClickRow.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'edit':
|
||||||
|
handleEdit(currentClickRow.value)
|
||||||
|
break
|
||||||
|
case 'reset':
|
||||||
|
handleReset(currentClickRow.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
align-items: center;
|
||||||
align-items: center;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -697,8 +697,8 @@
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.polling-config-page {
|
.polling-config-page {
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
:total="logPagination.total"
|
:total="logPagination.total"
|
||||||
:page-sizes="[10, 20, 50]"
|
:page-sizes="[10, 20, 50]"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
style="margin-top: 16px; justify-content: flex-end"
|
style=" justify-content: flex-end;margin-top: 16px"
|
||||||
@size-change="getCleanupLogs"
|
@size-change="getCleanupLogs"
|
||||||
@current-change="getCleanupLogs"
|
@current-change="getCleanupLogs"
|
||||||
/>
|
/>
|
||||||
@@ -619,7 +619,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-danger {
|
.text-danger {
|
||||||
color: var(--el-color-danger);
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--el-color-danger);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -86,7 +86,11 @@
|
|||||||
>
|
>
|
||||||
<el-form ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="120px">
|
<el-form ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="120px">
|
||||||
<el-form-item label="任务类型" prop="task_type">
|
<el-form-item label="任务类型" prop="task_type">
|
||||||
<el-select v-model="batchForm.task_type" placeholder="请选择任务类型" style="width: 100%">
|
<el-select
|
||||||
|
v-model="batchForm.task_type"
|
||||||
|
placeholder="请选择任务类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option label="实名认证轮询" value="polling:realname" />
|
<el-option label="实名认证轮询" value="polling:realname" />
|
||||||
<el-option label="卡数据轮询" value="polling:carddata" />
|
<el-option label="卡数据轮询" value="polling:carddata" />
|
||||||
<el-option label="套餐轮询" value="polling:package" />
|
<el-option label="套餐轮询" value="polling:package" />
|
||||||
@@ -261,7 +265,11 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="任务类型" prop="task_type">
|
<el-form-item label="任务类型" prop="task_type">
|
||||||
<el-select v-model="singleForm.task_type" placeholder="请选择任务类型" style="width: 100%">
|
<el-select
|
||||||
|
v-model="singleForm.task_type"
|
||||||
|
placeholder="请选择任务类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option label="实名认证轮询" value="polling:realname" />
|
<el-option label="实名认证轮询" value="polling:realname" />
|
||||||
<el-option label="卡数据轮询" value="polling:carddata" />
|
<el-option label="卡数据轮询" value="polling:carddata" />
|
||||||
<el-option label="套餐轮询" value="polling:package" />
|
<el-option label="套餐轮询" value="polling:package" />
|
||||||
@@ -278,277 +286,62 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, nextTick, h } from 'vue'
|
import { ref, reactive, computed, onMounted, nextTick, h } from 'vue'
|
||||||
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
||||||
import { ManualTriggerService, CarrierService, ShopService, CardService } from '@/api/modules'
|
import { ManualTriggerService, CarrierService, ShopService, CardService } from '@/api/modules'
|
||||||
import type {
|
import type {
|
||||||
ManualTriggerLog,
|
ManualTriggerLog,
|
||||||
ManualTriggerHistoryQueryParams,
|
ManualTriggerHistoryQueryParams,
|
||||||
BatchManualTriggerRequest,
|
BatchManualTriggerRequest,
|
||||||
ConditionManualTriggerRequest,
|
ConditionManualTriggerRequest,
|
||||||
SingleManualTriggerRequest,
|
SingleManualTriggerRequest,
|
||||||
TaskType
|
TaskType
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<ManualTriggerLog[]>([])
|
const tableData = ref<ManualTriggerLog[]>([])
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const contextMenuRef = ref()
|
const contextMenuRef = ref()
|
||||||
const currentClickRow = ref<ManualTriggerLog | null>(null)
|
const currentClickRow = ref<ManualTriggerLog | null>(null)
|
||||||
|
|
||||||
const batchDialogVisible = ref(false)
|
const batchDialogVisible = ref(false)
|
||||||
const conditionDialogVisible = ref(false)
|
const conditionDialogVisible = ref(false)
|
||||||
const singleDialogVisible = ref(false)
|
const singleDialogVisible = ref(false)
|
||||||
|
|
||||||
const batchFormRef = ref<FormInstance>()
|
const batchFormRef = ref<FormInstance>()
|
||||||
const conditionFormRef = ref<FormInstance>()
|
const conditionFormRef = ref<FormInstance>()
|
||||||
const singleFormRef = ref<FormInstance>()
|
const singleFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
const carrierList = ref<any[]>([])
|
const carrierList = ref<any[]>([])
|
||||||
const shopTreeData = ref<any[]>([])
|
const shopTreeData = ref<any[]>([])
|
||||||
const cardList = ref<any[]>([])
|
const cardList = ref<any[]>([])
|
||||||
|
|
||||||
// 分页数据
|
// 分页数据
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 10,
|
page_size: 10,
|
||||||
total: 0
|
total: 0
|
||||||
})
|
|
||||||
|
|
||||||
// 搜索表单
|
|
||||||
const searchForm = reactive({
|
|
||||||
task_type: undefined as TaskType | undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// 批量触发表单
|
|
||||||
const batchForm = reactive({
|
|
||||||
task_type: '' as TaskType | '',
|
|
||||||
card_ids_text: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 条件触发表单
|
|
||||||
const conditionForm = reactive<ConditionManualTriggerRequest>({
|
|
||||||
task_type: '' as TaskType | '',
|
|
||||||
limit: 100,
|
|
||||||
card_status: undefined,
|
|
||||||
card_type: undefined,
|
|
||||||
carrier_code: undefined,
|
|
||||||
enable_polling: undefined,
|
|
||||||
package_ids: undefined,
|
|
||||||
shop_id: undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// 单卡触发表单
|
|
||||||
const singleForm = reactive({
|
|
||||||
card_id: undefined as number | undefined,
|
|
||||||
task_type: '' as TaskType | ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const batchRules: FormRules = {
|
|
||||||
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const conditionRules: FormRules = {
|
|
||||||
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
|
||||||
limit: [{ required: true, message: '请输入限制数量', trigger: 'blur' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const singleRules: FormRules = {
|
|
||||||
card_id: [{ required: true, message: '请输入卡ID', trigger: 'blur' }],
|
|
||||||
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
showContextMenuHint,
|
|
||||||
hintPosition,
|
|
||||||
getRowClassName,
|
|
||||||
handleCellMouseEnter,
|
|
||||||
handleCellMouseLeave
|
|
||||||
} = useTableContextMenu()
|
|
||||||
|
|
||||||
// 搜索表单配置
|
|
||||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: '任务类型',
|
|
||||||
prop: 'task_type',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
placeholder: '请选择任务类型'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '实名认证轮询', value: 'polling:realname' },
|
|
||||||
{ label: '卡数据轮询', value: 'polling:carddata' },
|
|
||||||
{ label: '套餐轮询', value: 'polling:package' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const getStatusTag = (status: string) => {
|
|
||||||
const typeMap: Record<string, 'info' | 'warning' | 'success' | 'danger'> = {
|
|
||||||
pending: 'info',
|
|
||||||
processing: 'warning',
|
|
||||||
completed: 'success',
|
|
||||||
cancelled: 'danger'
|
|
||||||
}
|
|
||||||
return typeMap[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProgressPercentage = (row: ManualTriggerLog) => {
|
|
||||||
if (row.total_count === 0) return 0
|
|
||||||
return Math.round((row.processed_count / row.total_count) * 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态列配置
|
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
|
||||||
{
|
|
||||||
prop: 'task_type_name',
|
|
||||||
label: '任务类型',
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'trigger_type_name',
|
|
||||||
label: '触发类型',
|
|
||||||
minWidth: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'status',
|
|
||||||
label: '状态',
|
|
||||||
minWidth: 100,
|
|
||||||
slots: {
|
|
||||||
default: ({ row }: any) =>
|
|
||||||
h(ElTag, { type: getStatusTag(row.status) }, () => row.status_name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '进度',
|
|
||||||
minWidth: 200,
|
|
||||||
slots: {
|
|
||||||
default: ({ row }: any) =>
|
|
||||||
h('div', { style: 'display: flex; align-items: center; gap: 8px;' }, [
|
|
||||||
h('span', { style: 'font-size: 12px;' }, `${row.processed_count}/${row.total_count}`),
|
|
||||||
h('el-progress', {
|
|
||||||
percentage: getProgressPercentage(row),
|
|
||||||
'stroke-width': 6,
|
|
||||||
'show-text': false,
|
|
||||||
style: 'flex: 1;'
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'success_count',
|
|
||||||
label: '成功数',
|
|
||||||
minWidth: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'failed_count',
|
|
||||||
label: '失败数',
|
|
||||||
minWidth: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'triggered_at',
|
|
||||||
label: '触发时间',
|
|
||||||
minWidth: 180,
|
|
||||||
formatter: (row: any) => formatDateTime(row.triggered_at)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'completed_at',
|
|
||||||
label: '完成时间',
|
|
||||||
minWidth: 180,
|
|
||||||
formatter: (row: any) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const columnOptions = computed(() =>
|
|
||||||
columns.value.map((col) => ({
|
|
||||||
label: col.label,
|
|
||||||
prop: col.prop || col.label,
|
|
||||||
visible: true
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
const contextMenuItems = computed<MenuItemType[]>(() => {
|
|
||||||
const items: MenuItemType[] = [{ label: '刷新', key: 'refresh' }]
|
|
||||||
|
|
||||||
if (
|
|
||||||
currentClickRow.value &&
|
|
||||||
(currentClickRow.value.status === 'pending' || currentClickRow.value.status === 'processing')
|
|
||||||
) {
|
|
||||||
items.push({
|
|
||||||
label: '取消任务',
|
|
||||||
key: 'cancel',
|
|
||||||
permission: 'polling_manual_trigger_cancel'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const queryParams: ManualTriggerHistoryQueryParams = {
|
|
||||||
page: pagination.page,
|
|
||||||
page_size: pagination.page_size,
|
|
||||||
...searchForm
|
|
||||||
}
|
|
||||||
const { data } = await ManualTriggerService.getHistory(queryParams)
|
|
||||||
tableData.value = data.items
|
|
||||||
pagination.total = data.total
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('加载触发历史失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearch = () => {
|
|
||||||
pagination.page = 1
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
Object.assign(searchForm, {
|
|
||||||
task_type: undefined
|
|
||||||
})
|
})
|
||||||
pagination.page = 1
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
// 搜索表单
|
||||||
loadData()
|
const searchForm = reactive({
|
||||||
}
|
task_type: undefined as TaskType | undefined
|
||||||
|
})
|
||||||
|
|
||||||
const handleSizeChange = (size: number) => {
|
// 批量触发表单
|
||||||
pagination.page_size = size
|
const batchForm = reactive({
|
||||||
loadData()
|
task_type: '' as TaskType | '',
|
||||||
}
|
|
||||||
|
|
||||||
const handleCurrentChange = (page: number) => {
|
|
||||||
pagination.page = page
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const showBatchTriggerDialog = () => {
|
|
||||||
Object.assign(batchForm, {
|
|
||||||
task_type: '',
|
|
||||||
card_ids_text: ''
|
card_ids_text: ''
|
||||||
})
|
})
|
||||||
batchDialogVisible.value = true
|
|
||||||
nextTick(() => {
|
|
||||||
batchFormRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const showConditionTriggerDialog = () => {
|
// 条件触发表单
|
||||||
Object.assign(conditionForm, {
|
const conditionForm = reactive<ConditionManualTriggerRequest>({
|
||||||
task_type: '',
|
task_type: '' as TaskType | '',
|
||||||
limit: 100,
|
limit: 100,
|
||||||
card_status: undefined,
|
card_status: undefined,
|
||||||
card_type: undefined,
|
card_type: undefined,
|
||||||
@@ -557,207 +350,422 @@ const showConditionTriggerDialog = () => {
|
|||||||
package_ids: undefined,
|
package_ids: undefined,
|
||||||
shop_id: undefined
|
shop_id: undefined
|
||||||
})
|
})
|
||||||
conditionDialogVisible.value = true
|
|
||||||
nextTick(() => {
|
// 单卡触发表单
|
||||||
conditionFormRef.value?.clearValidate()
|
const singleForm = reactive({
|
||||||
|
card_id: undefined as number | undefined,
|
||||||
|
task_type: '' as TaskType | ''
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const showSingleTriggerDialog = () => {
|
const batchRules: FormRules = {
|
||||||
Object.assign(singleForm, {
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
||||||
card_id: undefined,
|
}
|
||||||
task_type: ''
|
|
||||||
})
|
|
||||||
singleDialogVisible.value = true
|
|
||||||
loadCards() // 加载初始卡列表
|
|
||||||
nextTick(() => {
|
|
||||||
singleFormRef.value?.clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBatchSubmit = async () => {
|
const conditionRules: FormRules = {
|
||||||
if (!batchFormRef.value) return
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
||||||
await batchFormRef.value.validate(async (valid) => {
|
limit: [{ required: true, message: '请输入限制数量', trigger: 'blur' }]
|
||||||
if (!valid) return
|
}
|
||||||
|
|
||||||
try {
|
const singleRules: FormRules = {
|
||||||
// 解析卡ID列表
|
card_id: [{ required: true, message: '请输入卡ID', trigger: 'blur' }],
|
||||||
const cardIds = batchForm.card_ids_text
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
||||||
.split(/[,\n]/)
|
}
|
||||||
.map((id) => id.trim())
|
|
||||||
.filter((id) => id)
|
|
||||||
.map((id) => parseInt(id))
|
|
||||||
.filter((id) => !isNaN(id))
|
|
||||||
|
|
||||||
if (cardIds.length > 1000) {
|
const {
|
||||||
ElMessage.warning('卡ID数量不能超过1000个')
|
showContextMenuHint,
|
||||||
return
|
hintPosition,
|
||||||
}
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const requestData: BatchManualTriggerRequest = {
|
// 搜索表单配置
|
||||||
task_type: batchForm.task_type as TaskType,
|
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||||
card_ids: cardIds.length > 0 ? cardIds : undefined
|
{
|
||||||
}
|
label: '任务类型',
|
||||||
|
prop: 'task_type',
|
||||||
await ManualTriggerService.batchTrigger(requestData)
|
type: 'select',
|
||||||
ElMessage.success('批量触发成功')
|
config: {
|
||||||
batchDialogVisible.value = false
|
clearable: true,
|
||||||
loadData()
|
placeholder: '请选择任务类型'
|
||||||
} catch (error) {
|
},
|
||||||
ElMessage.error('批量触发失败')
|
options: () => [
|
||||||
|
{ label: '实名认证轮询', value: 'polling:realname' },
|
||||||
|
{ label: '卡数据轮询', value: 'polling:carddata' },
|
||||||
|
{ label: '套餐轮询', value: 'polling:package' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
}
|
|
||||||
|
|
||||||
const handleConditionSubmit = async () => {
|
const getStatusTag = (status: string) => {
|
||||||
if (!conditionFormRef.value) return
|
const typeMap: Record<string, 'info' | 'warning' | 'success' | 'danger'> = {
|
||||||
await conditionFormRef.value.validate(async (valid) => {
|
pending: 'info',
|
||||||
if (!valid) return
|
processing: 'warning',
|
||||||
|
completed: 'success',
|
||||||
try {
|
cancelled: 'danger'
|
||||||
const requestData: ConditionManualTriggerRequest = {
|
|
||||||
task_type: conditionForm.task_type as TaskType,
|
|
||||||
limit: conditionForm.limit,
|
|
||||||
card_status: conditionForm.card_status,
|
|
||||||
card_type: conditionForm.card_type,
|
|
||||||
carrier_code: conditionForm.carrier_code,
|
|
||||||
enable_polling: conditionForm.enable_polling,
|
|
||||||
package_ids: conditionForm.package_ids,
|
|
||||||
shop_id: conditionForm.shop_id
|
|
||||||
}
|
|
||||||
|
|
||||||
await ManualTriggerService.conditionTrigger(requestData)
|
|
||||||
ElMessage.success('条件触发成功')
|
|
||||||
conditionDialogVisible.value = false
|
|
||||||
loadData()
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('条件触发失败')
|
|
||||||
}
|
}
|
||||||
})
|
return typeMap[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSingleSubmit = async () => {
|
const getProgressPercentage = (row: ManualTriggerLog) => {
|
||||||
if (!singleFormRef.value) return
|
if (row.total_count === 0) return 0
|
||||||
await singleFormRef.value.validate(async (valid) => {
|
return Math.round((row.processed_count / row.total_count) * 100)
|
||||||
if (!valid) return
|
}
|
||||||
|
|
||||||
try {
|
// 动态列配置
|
||||||
await ManualTriggerService.singleTrigger({
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
card_id: singleForm.card_id!,
|
{
|
||||||
task_type: singleForm.task_type as TaskType
|
prop: 'task_type_name',
|
||||||
|
label: '任务类型',
|
||||||
|
minWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'trigger_type_name',
|
||||||
|
label: '触发类型',
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: any) =>
|
||||||
|
h(ElTag, { type: getStatusTag(row.status) }, () => row.status_name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '进度',
|
||||||
|
minWidth: 200,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: any) =>
|
||||||
|
h('div', { style: 'display: flex; align-items: center; gap: 8px;' }, [
|
||||||
|
h('span', { style: 'font-size: 12px;' }, `${row.processed_count}/${row.total_count}`),
|
||||||
|
h('el-progress', {
|
||||||
|
percentage: getProgressPercentage(row),
|
||||||
|
'stroke-width': 6,
|
||||||
|
'show-text': false,
|
||||||
|
style: 'flex: 1;'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'success_count',
|
||||||
|
label: '成功数',
|
||||||
|
minWidth: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'failed_count',
|
||||||
|
label: '失败数',
|
||||||
|
minWidth: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'triggered_at',
|
||||||
|
label: '触发时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: (row: any) => formatDateTime(row.triggered_at)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'completed_at',
|
||||||
|
label: '完成时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: (row: any) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const columnOptions = computed(() =>
|
||||||
|
columns.value.map((col) => ({
|
||||||
|
label: col.label,
|
||||||
|
prop: col.prop || col.label,
|
||||||
|
visible: true
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const contextMenuItems = computed<MenuItemType[]>(() => {
|
||||||
|
const items: MenuItemType[] = [{ label: '刷新', key: 'refresh' }]
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentClickRow.value &&
|
||||||
|
(currentClickRow.value.status === 'pending' || currentClickRow.value.status === 'processing')
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
label: '取消任务',
|
||||||
|
key: 'cancel',
|
||||||
|
permission: 'polling_manual_trigger_cancel'
|
||||||
})
|
})
|
||||||
ElMessage.success('单卡触发成功')
|
}
|
||||||
singleDialogVisible.value = false
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const queryParams: ManualTriggerHistoryQueryParams = {
|
||||||
|
page: pagination.page,
|
||||||
|
page_size: pagination.page_size,
|
||||||
|
...searchForm
|
||||||
|
}
|
||||||
|
const { data } = await ManualTriggerService.getHistory(queryParams)
|
||||||
|
tableData.value = data.items
|
||||||
|
pagination.total = data.total
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('加载触发历史失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(searchForm, {
|
||||||
|
task_type: undefined
|
||||||
|
})
|
||||||
|
pagination.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size: number) => {
|
||||||
|
pagination.page_size = size
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (page: number) => {
|
||||||
|
pagination.page = page
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const showBatchTriggerDialog = () => {
|
||||||
|
Object.assign(batchForm, {
|
||||||
|
task_type: '',
|
||||||
|
card_ids_text: ''
|
||||||
|
})
|
||||||
|
batchDialogVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
batchFormRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showConditionTriggerDialog = () => {
|
||||||
|
Object.assign(conditionForm, {
|
||||||
|
task_type: '',
|
||||||
|
limit: 100,
|
||||||
|
card_status: undefined,
|
||||||
|
card_type: undefined,
|
||||||
|
carrier_code: undefined,
|
||||||
|
enable_polling: undefined,
|
||||||
|
package_ids: undefined,
|
||||||
|
shop_id: undefined
|
||||||
|
})
|
||||||
|
conditionDialogVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
conditionFormRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showSingleTriggerDialog = () => {
|
||||||
|
Object.assign(singleForm, {
|
||||||
|
card_id: undefined,
|
||||||
|
task_type: ''
|
||||||
|
})
|
||||||
|
singleDialogVisible.value = true
|
||||||
|
loadCards() // 加载初始卡列表
|
||||||
|
nextTick(() => {
|
||||||
|
singleFormRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBatchSubmit = async () => {
|
||||||
|
if (!batchFormRef.value) return
|
||||||
|
await batchFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析卡ID列表
|
||||||
|
const cardIds = batchForm.card_ids_text
|
||||||
|
.split(/[,\n]/)
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter((id) => id)
|
||||||
|
.map((id) => parseInt(id))
|
||||||
|
.filter((id) => !isNaN(id))
|
||||||
|
|
||||||
|
if (cardIds.length > 1000) {
|
||||||
|
ElMessage.warning('卡ID数量不能超过1000个')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestData: BatchManualTriggerRequest = {
|
||||||
|
task_type: batchForm.task_type as TaskType,
|
||||||
|
card_ids: cardIds.length > 0 ? cardIds : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
await ManualTriggerService.batchTrigger(requestData)
|
||||||
|
ElMessage.success('批量触发成功')
|
||||||
|
batchDialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('批量触发失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConditionSubmit = async () => {
|
||||||
|
if (!conditionFormRef.value) return
|
||||||
|
await conditionFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData: ConditionManualTriggerRequest = {
|
||||||
|
task_type: conditionForm.task_type as TaskType,
|
||||||
|
limit: conditionForm.limit,
|
||||||
|
card_status: conditionForm.card_status,
|
||||||
|
card_type: conditionForm.card_type,
|
||||||
|
carrier_code: conditionForm.carrier_code,
|
||||||
|
enable_polling: conditionForm.enable_polling,
|
||||||
|
package_ids: conditionForm.package_ids,
|
||||||
|
shop_id: conditionForm.shop_id
|
||||||
|
}
|
||||||
|
|
||||||
|
await ManualTriggerService.conditionTrigger(requestData)
|
||||||
|
ElMessage.success('条件触发成功')
|
||||||
|
conditionDialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('条件触发失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSingleSubmit = async () => {
|
||||||
|
if (!singleFormRef.value) return
|
||||||
|
await singleFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ManualTriggerService.singleTrigger({
|
||||||
|
card_id: singleForm.card_id!,
|
||||||
|
task_type: singleForm.task_type as TaskType
|
||||||
|
})
|
||||||
|
ElMessage.success('单卡触发成功')
|
||||||
|
singleDialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('单卡触发失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancelTask = async (row: ManualTriggerLog) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要取消该任务吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await ManualTriggerService.cancelTrigger({ trigger_id: row.id })
|
||||||
|
ElMessage.success('任务已取消')
|
||||||
loadData()
|
loadData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('单卡触发失败')
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error('取消任务失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancelTask = async (row: ManualTriggerLog) => {
|
const handleRowContextMenu = (row: ManualTriggerLog, column: any, event: MouseEvent) => {
|
||||||
try {
|
event.preventDefault()
|
||||||
await ElMessageBox.confirm('确定要取消该任务吗?', '提示', {
|
event.stopPropagation()
|
||||||
confirmButtonText: '确定',
|
currentClickRow.value = row
|
||||||
cancelButtonText: '取消',
|
contextMenuRef.value?.show(event)
|
||||||
type: 'warning'
|
}
|
||||||
})
|
|
||||||
|
|
||||||
await ManualTriggerService.cancelTrigger({ trigger_id: row.id })
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
ElMessage.success('任务已取消')
|
if (!currentClickRow.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'refresh':
|
||||||
|
loadData()
|
||||||
|
break
|
||||||
|
case 'cancel':
|
||||||
|
handleCancelTask(currentClickRow.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载运营商列表
|
||||||
|
const loadCarriers = async (carrierName?: string) => {
|
||||||
|
try {
|
||||||
|
const { data } = await CarrierService.getCarriers({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
carrier_name: carrierName
|
||||||
|
})
|
||||||
|
carrierList.value = data.items || data.list || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载运营商列表失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteSearchCarrier = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
loadCarriers(query)
|
||||||
|
} else {
|
||||||
|
loadCarriers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载店铺树形数据
|
||||||
|
const loadShops = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await ShopService.getShops()
|
||||||
|
shopTreeData.value = data.items || data.list || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载店铺列表失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载卡列表
|
||||||
|
const loadCards = async (iccid?: string) => {
|
||||||
|
try {
|
||||||
|
const { data } = await CardService.getStandaloneIotCards({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
iccid: iccid
|
||||||
|
})
|
||||||
|
cardList.value = data.items || data.list || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载卡列表失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteSearchCard = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
loadCards(query)
|
||||||
|
} else {
|
||||||
|
loadCards()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
loadData()
|
loadData()
|
||||||
} catch (error) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
ElMessage.error('取消任务失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRowContextMenu = (row: ManualTriggerLog, column: any, event: MouseEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentClickRow.value = row
|
|
||||||
contextMenuRef.value?.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
|
||||||
if (!currentClickRow.value) return
|
|
||||||
|
|
||||||
switch (item.key) {
|
|
||||||
case 'refresh':
|
|
||||||
loadData()
|
|
||||||
break
|
|
||||||
case 'cancel':
|
|
||||||
handleCancelTask(currentClickRow.value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载运营商列表
|
|
||||||
const loadCarriers = async (carrierName?: string) => {
|
|
||||||
try {
|
|
||||||
const { data } = await CarrierService.getCarriers({
|
|
||||||
page: 1,
|
|
||||||
page_size: 20,
|
|
||||||
carrier_name: carrierName
|
|
||||||
})
|
|
||||||
carrierList.value = data.items || data.list || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载运营商列表失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteSearchCarrier = (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
loadCarriers(query)
|
|
||||||
} else {
|
|
||||||
loadCarriers()
|
loadCarriers()
|
||||||
}
|
loadShops()
|
||||||
}
|
})
|
||||||
|
|
||||||
// 加载店铺树形数据
|
|
||||||
const loadShops = async () => {
|
|
||||||
try {
|
|
||||||
const { data } = await ShopService.getShops()
|
|
||||||
shopTreeData.value = data.items || data.list || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载店铺列表失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载卡列表
|
|
||||||
const loadCards = async (iccid?: string) => {
|
|
||||||
try {
|
|
||||||
const { data } = await CardService.getStandaloneIotCards({
|
|
||||||
page: 1,
|
|
||||||
page_size: 20,
|
|
||||||
iccid: iccid
|
|
||||||
})
|
|
||||||
cardList.value = data.items || data.list || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载卡列表失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteSearchCard = (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
loadCards(query)
|
|
||||||
} else {
|
|
||||||
loadCards()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadData()
|
|
||||||
loadCarriers()
|
|
||||||
loadShops()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.manual-trigger-page {
|
.manual-trigger-page {
|
||||||
height: 100%;
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -290,17 +290,17 @@
|
|||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #ffffff;
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
&.total {
|
&.total {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
@@ -327,9 +327,9 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
|
margin-bottom: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
@@ -345,8 +345,8 @@
|
|||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
@@ -361,14 +361,14 @@
|
|||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
.label {
|
.label {
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@
|
|||||||
:value="role.ID"
|
:value="role.ID"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="display: flex; justify-content: space-between; align-items: center"
|
style="display: flex; align-items: center; justify-content: space-between"
|
||||||
>
|
>
|
||||||
<span>{{ role.role_name }}</span>
|
<span>{{ role.role_name }}</span>
|
||||||
<ElTag type="success" size="small">客户角色</ElTag>
|
<ElTag type="success" size="small">客户角色</ElTag>
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ElOption>
|
</ElOption>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
<div style="margin-top: 8px; color: #909399; font-size: 12px">
|
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
||||||
只能选择一个客户角色,选择后立即生效
|
只能选择一个客户角色,选择后立即生效
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1076,10 +1076,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-role-display {
|
.current-role-display {
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
border: 2px solid var(--el-color-primary);
|
border: 2px solid var(--el-color-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
&.no-role {
|
&.no-role {
|
||||||
border: 2px dashed var(--el-border-color);
|
border: 2px dashed var(--el-border-color);
|
||||||
@@ -1090,30 +1090,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-role-label {
|
.current-role-label {
|
||||||
font-size: 14px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-role-value {
|
.current-role-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-roles-section {
|
.default-roles-section {
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -279,8 +279,8 @@
|
|||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@@ -294,10 +294,10 @@
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
|
|||||||
@@ -64,7 +64,13 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="140px" class="config-form">
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="140px"
|
||||||
|
class="config-form"
|
||||||
|
>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="配置名称" prop="name">
|
<ElFormItem label="配置名称" prop="name">
|
||||||
@@ -103,10 +109,7 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="小程序AppID" prop="miniapp_app_id">
|
<ElFormItem label="小程序AppID" prop="miniapp_app_id">
|
||||||
<ElInput
|
<ElInput v-model="form.miniapp_app_id" placeholder="请输入小程序AppID" />
|
||||||
v-model="form.miniapp_app_id"
|
|
||||||
placeholder="请输入小程序AppID"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -127,10 +130,7 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="公众号AppID" prop="oa_app_id">
|
<ElFormItem label="公众号AppID" prop="oa_app_id">
|
||||||
<ElInput
|
<ElInput v-model="form.oa_app_id" placeholder="请输入公众号AppID" />
|
||||||
v-model="form.oa_app_id"
|
|
||||||
placeholder="请输入公众号AppID"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -167,10 +167,7 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElFormItem label="OAuth回调地址" prop="oa_oauth_redirect_url">
|
<ElFormItem label="OAuth回调地址" prop="oa_oauth_redirect_url">
|
||||||
<ElInput
|
<ElInput v-model="form.oa_oauth_redirect_url" placeholder="请输入OAuth回调地址" />
|
||||||
v-model="form.oa_oauth_redirect_url"
|
|
||||||
placeholder="请输入OAuth回调地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<ElDivider content-position="left">
|
<ElDivider content-position="left">
|
||||||
@@ -179,18 +176,12 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="微信商户号" prop="wx_mch_id">
|
<ElFormItem label="微信商户号" prop="wx_mch_id">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_mch_id" placeholder="请输入微信商户号" />
|
||||||
v-model="form.wx_mch_id"
|
|
||||||
placeholder="请输入微信商户号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="证书序列号" prop="wx_serial_no">
|
<ElFormItem label="证书序列号" prop="wx_serial_no">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_serial_no" placeholder="请输入微信证书序列号" />
|
||||||
v-model="form.wx_serial_no"
|
|
||||||
placeholder="请输入微信证书序列号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -217,10 +208,7 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElFormItem label="支付回调地址" prop="wx_notify_url">
|
<ElFormItem label="支付回调地址" prop="wx_notify_url">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_notify_url" placeholder="请输入微信支付回调地址" />
|
||||||
v-model="form.wx_notify_url"
|
|
||||||
placeholder="请输入微信支付回调地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -252,10 +240,7 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="小程序AppID" prop="miniapp_app_id">
|
<ElFormItem label="小程序AppID" prop="miniapp_app_id">
|
||||||
<ElInput
|
<ElInput v-model="form.miniapp_app_id" placeholder="请输入小程序AppID" />
|
||||||
v-model="form.miniapp_app_id"
|
|
||||||
placeholder="请输入小程序AppID"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -276,10 +261,7 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="公众号AppID" prop="oa_app_id">
|
<ElFormItem label="公众号AppID" prop="oa_app_id">
|
||||||
<ElInput
|
<ElInput v-model="form.oa_app_id" placeholder="请输入公众号AppID" />
|
||||||
v-model="form.oa_app_id"
|
|
||||||
placeholder="请输入公众号AppID"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -316,10 +298,7 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElFormItem label="OAuth回调地址" prop="oa_oauth_redirect_url">
|
<ElFormItem label="OAuth回调地址" prop="oa_oauth_redirect_url">
|
||||||
<ElInput
|
<ElInput v-model="form.oa_oauth_redirect_url" placeholder="请输入OAuth回调地址" />
|
||||||
v-model="form.oa_oauth_redirect_url"
|
|
||||||
placeholder="请输入OAuth回调地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<ElDivider content-position="left">
|
<ElDivider content-position="left">
|
||||||
@@ -328,18 +307,12 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="微信商户号" prop="wx_mch_id">
|
<ElFormItem label="微信商户号" prop="wx_mch_id">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_mch_id" placeholder="请输入微信商户号" />
|
||||||
v-model="form.wx_mch_id"
|
|
||||||
placeholder="请输入微信商户号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="证书序列号" prop="wx_serial_no">
|
<ElFormItem label="证书序列号" prop="wx_serial_no">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_serial_no" placeholder="请输入微信证书序列号" />
|
||||||
v-model="form.wx_serial_no"
|
|
||||||
placeholder="请输入微信证书序列号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -366,10 +339,7 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElFormItem label="支付回调地址" prop="wx_notify_url">
|
<ElFormItem label="支付回调地址" prop="wx_notify_url">
|
||||||
<ElInput
|
<ElInput v-model="form.wx_notify_url" placeholder="请输入微信支付回调地址" />
|
||||||
v-model="form.wx_notify_url"
|
|
||||||
placeholder="请输入微信支付回调地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -400,44 +370,29 @@
|
|||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="富友API地址" prop="fy_api_url">
|
<ElFormItem label="富友API地址" prop="fy_api_url">
|
||||||
<ElInput
|
<ElInput v-model="form.fy_api_url" placeholder="请输入富友API地址" />
|
||||||
v-model="form.fy_api_url"
|
|
||||||
placeholder="请输入富友API地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="富友机构号" prop="fy_ins_cd">
|
<ElFormItem label="富友机构号" prop="fy_ins_cd">
|
||||||
<ElInput
|
<ElInput v-model="form.fy_ins_cd" placeholder="请输入富友机构号" />
|
||||||
v-model="form.fy_ins_cd"
|
|
||||||
placeholder="请输入富友机构号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="富友商户号" prop="fy_mchnt_cd">
|
<ElFormItem label="富友商户号" prop="fy_mchnt_cd">
|
||||||
<ElInput
|
<ElInput v-model="form.fy_mchnt_cd" placeholder="请输入富友商户号" />
|
||||||
v-model="form.fy_mchnt_cd"
|
|
||||||
placeholder="请输入富友商户号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="富友终端号" prop="fy_term_id">
|
<ElFormItem label="富友终端号" prop="fy_term_id">
|
||||||
<ElInput
|
<ElInput v-model="form.fy_term_id" placeholder="请输入富友终端号" />
|
||||||
v-model="form.fy_term_id"
|
|
||||||
placeholder="请输入富友终端号"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElFormItem label="支付回调地址" prop="fy_notify_url">
|
<ElFormItem label="支付回调地址" prop="fy_notify_url">
|
||||||
<ElInput
|
<ElInput v-model="form.fy_notify_url" placeholder="请输入富友支付回调地址" />
|
||||||
v-model="form.fy_notify_url"
|
|
||||||
placeholder="请输入富友支付回调地址"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -1117,8 +1072,8 @@
|
|||||||
|
|
||||||
.config-form {
|
.config-form {
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.divider-title {
|
.divider-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -1127,7 +1082,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-divider) {
|
:deep(.el-divider) {
|
||||||
margin: 24px 0 20px 0;
|
margin: 24px 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@
|
|||||||
class="permission-tree"
|
class="permission-tree"
|
||||||
>
|
>
|
||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<span style="display: flex; align-items: center; gap: 8px">
|
<span style="display: flex; gap: 8px; align-items: center">
|
||||||
<span>{{ node.label }}</span>
|
<span>{{ node.label }}</span>
|
||||||
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
||||||
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
||||||
@@ -519,7 +519,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查所有顶层节点
|
// 检查所有顶层节点
|
||||||
availablePermissions.value.forEach(node => checkNode(node))
|
availablePermissions.value.forEach((node) => checkNode(node))
|
||||||
|
|
||||||
return Array.from(parentIdsToHalfCheck)
|
return Array.from(parentIdsToHalfCheck)
|
||||||
}
|
}
|
||||||
@@ -568,7 +568,8 @@
|
|||||||
label: node.perm_name,
|
label: node.perm_name,
|
||||||
perm_type: node.perm_type,
|
perm_type: node.perm_type,
|
||||||
status: node.status ?? 1,
|
status: node.status ?? 1,
|
||||||
children: node.children && node.children.length > 0 ? buildTreeData(node.children) : undefined
|
children:
|
||||||
|
node.children && node.children.length > 0 ? buildTreeData(node.children) : undefined
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +693,11 @@
|
|||||||
const parentIds: number[] = []
|
const parentIds: number[] = []
|
||||||
|
|
||||||
// 在原始权限树中递归查找父节点
|
// 在原始权限树中递归查找父节点
|
||||||
const findInTree = (treeNodes: PermissionTreeNode[], targetId: number, parentId?: number): number | null => {
|
const findInTree = (
|
||||||
|
treeNodes: PermissionTreeNode[],
|
||||||
|
targetId: number,
|
||||||
|
parentId?: number
|
||||||
|
): number | null => {
|
||||||
for (const node of treeNodes) {
|
for (const node of treeNodes) {
|
||||||
if (node.id === targetId) {
|
if (node.id === targetId) {
|
||||||
return parentId || null
|
return parentId || null
|
||||||
@@ -727,7 +732,7 @@
|
|||||||
// 为每个勾选的节点找到所有父节点
|
// 为每个勾选的节点找到所有父节点
|
||||||
checkedKeys.forEach((key: number) => {
|
checkedKeys.forEach((key: number) => {
|
||||||
const parents = getParentNodeIds(key)
|
const parents = getParentNodeIds(key)
|
||||||
parents.forEach(parentId => allIds.add(parentId))
|
parents.forEach((parentId) => allIds.add(parentId))
|
||||||
})
|
})
|
||||||
|
|
||||||
return Array.from(allIds)
|
return Array.from(allIds)
|
||||||
@@ -1079,8 +1084,8 @@
|
|||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
@@ -1090,8 +1095,8 @@
|
|||||||
|
|
||||||
.role-info {
|
.role-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.role-name {
|
.role-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -1102,25 +1107,25 @@
|
|||||||
|
|
||||||
.permission-tree-transfer-container {
|
.permission-tree-transfer-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 20px 0;
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.transfer-panel {
|
.transfer-panel {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 340px;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
|
||||||
max-width: 340px;
|
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
@@ -1134,8 +1139,8 @@
|
|||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.permission-tree {
|
.permission-tree {
|
||||||
:deep(.el-tree-node) {
|
:deep(.el-tree-node) {
|
||||||
@@ -1144,8 +1149,8 @@
|
|||||||
|
|
||||||
:deep(.el-tree-node__content) {
|
:deep(.el-tree-node__content) {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
line-height: 36px;
|
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-tree-node__label) {
|
:deep(.el-tree-node__label) {
|
||||||
@@ -1162,16 +1167,16 @@
|
|||||||
|
|
||||||
.tree-node-content {
|
.tree-node-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.tree-node-label {
|
.tree-node-label {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex: 1;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
@@ -1186,9 +1191,9 @@
|
|||||||
.transfer-buttons {
|
.transfer-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
|
|||||||
Reference in New Issue
Block a user