diff --git a/PROJECT_TASKS.md b/PROJECT_TASKS.md index b924259..e08cbf5 100644 --- a/PROJECT_TASKS.md +++ b/PROJECT_TASKS.md @@ -153,7 +153,6 @@ - [ ] 删除/禁用平台账号 - [ ] 重置密码 - [ ] 页面开发 - - [ ] `src/views/account-management/platform-account/index.vue` - [ ] 账号列表(支持搜索、筛选) - [ ] 新增/编辑账号对话框 - [ ] 角色分配 @@ -173,7 +172,6 @@ - [ ] 删除客户角色 - [ ] 设置能力边界 - [ ] 页面开发 - - [ ] `src/views/account-management/customer-role/index.vue` - [ ] 角色列表 - [ ] 能力边界配置(功能权限、资源限制) - [ ] 组件开发 @@ -197,7 +195,6 @@ - [ ] 查看代理商详情 - [ ] 获取代理商下级列表 - [ ] 页面开发 - - [ ] `src/views/account-management/agent-management/index.vue` - [ ] 代理商树形列表 - [ ] 新增/编辑代理商对话框 - [ ] 代理商详情页面 @@ -238,7 +235,6 @@ - [ ] 重置密码 - [ ] 禁用/启用账号 - [ ] 页面开发 - - [ ] `src/views/account-management/customer-account/index.vue` - [ ] 账号列表(支持筛选) - [ ] 账号操作(解绑、重置密码等) - [ ] Mock 数据 @@ -259,7 +255,6 @@ - [ ] 查看佣金详情 - [ ] 查看提现记录 - [ ] 页面开发 - - [ ] `src/views/finance-management/customer-account/index.vue` - [ ] 客户账户列表 - [ ] 佣金详情展示 - [ ] 提现记录列表 @@ -315,7 +310,6 @@ - [ ] 获取佣金明细 - [ ] 申请提现 - [ ] 页面开发 - - [ ] `src/views/finance-management/my-account/index.vue` - [ ] 佣金概览(卡片展示,非列表) - [ ] 佣金明细列表 - [ ] 提现申请对话框 @@ -453,7 +447,6 @@ - [ ] 编辑套餐 - [ ] 删除套餐 - [ ] 页面开发 - - [ ] 重构 `src/views/package-management/package-create/index.vue` - [ ] 套餐列表(支持角色过滤) - [ ] 新增/编辑套餐对话框 - [ ] Mock 数据 @@ -502,7 +495,6 @@ - [ ] 续充 - [ ] 设备操作 - [ ] 页面开发 - - [ ] 重构 `src/views/card-management/single-card/index.vue` - [ ] 单卡信息展示(描述列表) - [ ] 操作按钮组 - [ ] 各类操作对话框 @@ -521,7 +513,6 @@ - [ ] 获取网卡列表 - [ ] 批量操作入口 - [ ] 页面开发 - - [ ] 重构 `src/views/card-management/card-list/index.vue` - [ ] 网卡列表 - [ ] 批量操作按钮 - [ ] Mock 数据 @@ -581,7 +572,6 @@ - [ ] 处理换卡申请 - [ ] 填充新 ICCID - [ ] 页面开发 - - [ ] 重构 `src/views/card-management/card-replacement/index.vue` - [ ] 申请列表 - [ ] 处理对话框 - [ ] Mock 数据 @@ -637,7 +627,6 @@ - [ ] 查看批量充值记录 - [ ] Excel 批量充值导入 - [ ] 页面开发 - - [ ] 重构 `src/views/card-management/offline-batch-recharge/index.vue` - [ ] 充值记录列表 - [ ] 批量充值 Excel 导入 - [ ] Mock 数据 diff --git a/openspec/changes/add-button-permissions/tasks.md b/openspec/changes/add-button-permissions/tasks.md index cb9a7c7..e961f6e 100644 --- a/openspec/changes/add-button-permissions/tasks.md +++ b/openspec/changes/add-button-permissions/tasks.md @@ -61,22 +61,6 @@ - 编辑: `account:edit` - 删除: `account:delete` -- [x] **平台账号页面** (`/account-management/platform-account`) - - - ✅ 引入 `useAuth` composable - - ✅ 为"新增平台账号"按钮添加 `v-permission="'platform_account:add'"` - - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: - - 分配角色: `platform_account:patch_role` - - 修改密码: `platform_account:update_psd` - - 编辑: `platform_account:edit` - - 删除: `platform_account:delete` - -- [x] **代理账号页面** (`/account-management/shop-account`) - - ✅ 引入 `useAuth` composable - - ✅ 为"新增代理账号"按钮添加 `v-permission="'shop_account:add'"` - - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: - - 修改密码: `shop_account:update_psd` - - 编辑: `shop_account:edit` ### 4. 资产管理模块 (Asset Management) ✅ diff --git a/openspec/changes/add-commission-management/tasks.md b/openspec/changes/add-commission-management/tasks.md index e5733d7..7b0d701 100644 --- a/openspec/changes/add-commission-management/tasks.md +++ b/openspec/changes/add-commission-management/tasks.md @@ -18,10 +18,9 @@ - [ ] 3.1 Update customer account types in `src/types/api/account.ts` - [ ] 3.2 Update AccountService in `src/api/modules/account.ts` with new endpoints -- [ ] 3.3 Update customer account list page `/account-management/customer-account` -- [ ] 3.4 Add create/edit customer account forms with user type selection -- [ ] 3.5 Add password reset functionality -- [ ] 3.6 Add status toggle functionality +- [ ] 3.3 Add create/edit customer account forms with user type selection +- [ ] 3.4 Add password reset functionality +- [ ] 3.5 Add status toggle functionality ## 4. Enterprise Customer Enhancement diff --git a/openspec/changes/add-customer-account-management/proposal.md b/openspec/changes/add-customer-account-management/proposal.md deleted file mode 100644 index e47c6ae..0000000 --- a/openspec/changes/add-customer-account-management/proposal.md +++ /dev/null @@ -1,37 +0,0 @@ -# Change: 客户账户管理功能 - -## Why - -运营平台需要统一查看和管理所有客户(包括代理商和企业客户)的账户佣金情况,包括佣金总额、可提现金额、待入账金额、已提现金额等财务数据。目前系统缺少集中的客户账户财务视图,运营人员无法高效地了解客户的佣金和提现状况。 - -## What Changes - -- 新增客户账户管理页面(`src/views/finance/customer-account/index.vue`) -- 提供客户账户列表查询功能,支持按客户账号、客户名称、客户类型筛选 -- 展示客户的佣金相关财务数据: - - 佣金总额 - - 可提现金额 - - 待入账金额 - - 已提现金额 - - 提现次数 - - 最后提现时间 -- 提供查看客户账户详情功能 -- 提供查看客户流水记录功能(入口) -- 添加国际化支持(中英文) -- 添加路由配置(财务模块下) - -## Impact - -- 新增规范:`specs/customer-account-management/spec.md` -- 新增文件: - - `src/views/finance/customer-account/index.vue` -- 修改文件: - - `src/router/routes/asyncRoutes.ts`(添加路由) - - `src/router/routesAlias.ts`(添加路由别名) - - `src/locales/langs/zh.json`(添加中文翻译) - - `src/locales/langs/en.json`(添加英文翻译) - - `docs/功能.md`(文档更新) -- 依赖模块: - - Element Plus 组件(ElTable, ElForm, ElDialog 等) - - 现有的 ArtTable 组件 - - 后端 API(待对接) diff --git a/openspec/changes/add-customer-account-management/specs/customer-account-management/spec.md b/openspec/changes/add-customer-account-management/specs/customer-account-management/spec.md deleted file mode 100644 index b0ebec8..0000000 --- a/openspec/changes/add-customer-account-management/specs/customer-account-management/spec.md +++ /dev/null @@ -1,176 +0,0 @@ -# 客户账户管理规范 - -## ADDED Requirements - -### Requirement: 客户账户列表查询 - -系统 SHALL 提供客户账户列表查询功能,运营人员可以查看所有客户的账户财务信息。 - -#### Scenario: 查询所有客户账户 - -- **WHEN** 运营人员访问客户账户管理页面 -- **THEN** 系统显示所有客户账户列表,包含以下字段: - - 客户账号 - - 客户名称 - - 客户类型(代理商/企业客户) - - 佣金总额 - - 可提现金额 - - 待入账金额 - - 已提现金额 - - 提现次数 - - 最后提现时间 - -#### Scenario: 按客户账号搜索 - -- **WHEN** 运营人员在搜索框输入客户账号并点击查询 -- **THEN** 系统返回匹配该账号的客户记录 - -#### Scenario: 按客户名称搜索 - -- **WHEN** 运营人员在搜索框输入客户名称并点击查询 -- **THEN** 系统返回名称包含该关键词的客户记录 - -#### Scenario: 按客户类型筛选 - -- **WHEN** 运营人员选择客户类型(代理商或企业客户)并点击查询 -- **THEN** 系统返回该类型的所有客户记录 - -#### Scenario: 组合条件搜索 - -- **WHEN** 运营人员同时使用多个搜索条件(如账号 + 类型)并点击查询 -- **THEN** 系统返回同时满足所有条件的客户记录 - -#### Scenario: 重置搜索条件 - -- **WHEN** 运营人员点击重置按钮 -- **THEN** 系统清空所有搜索条件并显示完整列表 - -### Requirement: 分页功能 - -系统 SHALL 支持客户账户列表的分页展示,以提高大数据量下的性能和用户体验。 - -#### Scenario: 默认分页显示 - -- **WHEN** 运营人员首次访问页面 -- **THEN** 系统默认显示第 1 页,每页 20 条记录 - -#### Scenario: 切换页码 - -- **WHEN** 运营人员点击分页器的页码 -- **THEN** 系统跳转到对应页面并加载数据 - -#### Scenario: 调整每页显示数量 - -- **WHEN** 运营人员选择不同的每页显示数量(10/20/50/100) -- **THEN** 系统重新加载数据并按新的数量显示 - -#### Scenario: 显示总记录数 - -- **WHEN** 数据加载完成后 -- **THEN** 分页器显示总记录数 - -### Requirement: 客户账户详情查看 - -系统 SHALL 提供客户账户详情查看功能,展示客户的完整财务信息。 - -#### Scenario: 打开详情对话框 - -- **WHEN** 运营人员点击某个客户的"查看详情"按钮 -- **THEN** 系统弹出详情对话框,展示以下信息: - - 客户账号 - - 客户名称 - - 客户类型 - - 联系电话 - - 佣金总额 - - 可提现金额 - - 待入账金额 - - 已提现金额 - - 提现次数 - - 最后提现时间 - - 注册时间 - - 备注 - -#### Scenario: 关闭详情对话框 - -- **WHEN** 运营人员点击对话框关闭按钮或遮罩层 -- **THEN** 系统关闭详情对话框 - -### Requirement: 客户流水记录入口 - -系统 SHALL 提供客户流水记录查看入口,方便运营人员查看客户的佣金流水明细。 - -#### Scenario: 触发流水记录查看 - -- **WHEN** 运营人员点击某个客户的"流水记录"按钮 -- **THEN** 系统导航到该客户的流水记录页面或打开流水记录对话框 - -### Requirement: 数据展示样式 - -系统 SHALL 使用不同的视觉样式区分不同类型的数据,提升可读性。 - -#### Scenario: 客户类型标签样式 - -- **WHEN** 列表或详情中显示客户类型 -- **THEN** 代理商显示为绿色标签,企业客户显示为蓝色标签 - -#### Scenario: 金额颜色区分 - -- **WHEN** 列表或详情中显示金额数据 -- **THEN** 可提现金额使用绿色高亮,待入账金额使用橙色高亮 - -#### Scenario: 金额格式化 - -- **WHEN** 系统显示金额数据 -- **THEN** 金额显示为货币格式,保留两位小数,前缀人民币符号"¥" - -### Requirement: 国际化支持 - -系统 SHALL 支持中英文双语界面,所有文案通过国际化文件管理。 - -#### Scenario: 中文界面显示 - -- **WHEN** 系统语言设置为中文 -- **THEN** 所有界面文案显示为中文 - -#### Scenario: 英文界面显示 - -- **WHEN** 系统语言设置为英文 -- **THEN** 所有界面文案显示为英文 - -### Requirement: 数据权限控制 - -系统 SHALL 根据运营人员的权限角色,控制其可见的客户账户范围。 - -#### Scenario: 管理员权限 - -- **WHEN** 管理员访问客户账户页面 -- **THEN** 系统显示所有客户账户 - -#### Scenario: 普通运营人员权限 - -- **WHEN** 普通运营人员访问客户账户页面 -- **THEN** 系统仅显示其权限范围内的客户账户 - -### Requirement: 异常处理与用户反馈 - -系统 SHALL 在操作过程中提供清晰的用户反馈和错误处理。 - -#### Scenario: 查询成功反馈 - -- **WHEN** 用户执行搜索操作且成功返回结果 -- **THEN** 系统显示成功消息提示 - -#### Scenario: 查询失败处理 - -- **WHEN** API 请求失败或超时 -- **THEN** 系统显示错误消息,并提示用户重试 - -#### Scenario: 数据加载状态 - -- **WHEN** 系统正在加载数据 -- **THEN** 显示加载动画或骨架屏,防止用户误操作 - -#### Scenario: 空数据提示 - -- **WHEN** 查询结果为空 -- **THEN** 系统显示"暂无数据"提示 diff --git a/openspec/changes/add-customer-account-management/tasks.md b/openspec/changes/add-customer-account-management/tasks.md deleted file mode 100644 index e89df2e..0000000 --- a/openspec/changes/add-customer-account-management/tasks.md +++ /dev/null @@ -1,58 +0,0 @@ -# 实现任务清单 - -## 1. 前端页面实现 - -- [x] 1.1 创建客户账户管理页面组件 `src/views/finance/customer-account/index.vue` - - [x] 1.1.1 实现搜索表单(客户账号、客户名称、客户类型) - - [x] 1.1.2 实现数据表格展示(使用 ArtTable) - - [x] 1.1.3 实现分页功能 - - [x] 1.1.4 实现详情对话框 - - [x] 1.1.5 添加操作按钮(查看详情、流水记录) -- [x] 1.2 添加路由配置 - - [x] 1.2.1 在 `src/router/routes/asyncRoutes.ts` 中添加路由 - - [x] 1.2.2 在 `src/router/routesAlias.ts` 中添加路由别名 -- [x] 1.3 添加国际化支持 - - [x] 1.3.1 在 `src/locales/langs/zh.json` 中添加中文文案 - - [x] 1.3.2 在 `src/locales/langs/en.json` 中添加英文文案 - -## 2. API 集成(待实现) - -- [ ] 2.1 创建 API 模块 `src/api/modules/customerAccountApi.ts` - - [ ] 2.1.1 实现客户账户列表查询接口 - - [ ] 2.1.2 实现客户账户详情查询接口 - - [ ] 2.1.3 实现客户流水记录查询接口 -- [ ] 2.2 定义 TypeScript 类型 - - [ ] 2.2.1 定义客户账户数据类型 `CustomerAccount` - - [ ] 2.2.2 定义搜索参数类型 `CustomerAccountSearchParams` - - [ ] 2.2.3 定义流水记录类型 `CustomerAccountFlow` -- [ ] 2.3 替换页面中的模拟数据为真实 API 调用 - -## 3. 流水记录功能(待实现) - -- [ ] 3.1 创建流水记录子页面或对话框 -- [ ] 3.2 实现流水记录列表展示 -- [ ] 3.3 实现流水记录筛选和分页 - -## 4. 权限控制(待实现) - -- [ ] 4.1 配置页面访问权限(后台权限系统) -- [ ] 4.2 添加按钮级权限控制(如需要) - -## 5. 测试与优化 - -- [ ] 5.1 单元测试(工具函数、业务逻辑) -- [ ] 5.2 集成测试(API 调用) -- [ ] 5.3 性能优化(大数据量处理) -- [ ] 5.4 用户体验优化(加载状态、错误处理) - -## 6. 文档更新 - -- [x] 6.1 更新 `docs/功能.md` 功能列表 - -## 当前状态 - -- ✅ 第 1 阶段(前端页面框架)已完成,使用模拟数据 -- ⏳ 第 2 阶段(API 集成)待实现 -- ⏳ 第 3 阶段(流水记录)待实现 -- ⏳ 第 4 阶段(权限控制)待实现 -- ⏳ 第 5 阶段(测试优化)待实现 diff --git a/openspec/changes/add-device-management/design.md b/openspec/changes/add-device-management/design.md deleted file mode 100644 index bd15a29..0000000 --- a/openspec/changes/add-device-management/design.md +++ /dev/null @@ -1,309 +0,0 @@ -# Device Management Design - -## Context - -The system currently has a basic device-import page but lacks comprehensive device management capabilities. Devices are critical assets that need to be tracked throughout their lifecycle - from import, allocation to shops, binding with SIM cards, to eventual recall. This change adds full device management to complement the existing card management features. - -Key integration points: - -- **Existing Card System**: Devices must bind with cards from the existing card-list module -- **Shop System**: Devices are allocated to shops using the existing ShopService -- **Object Storage**: Imports use the existing StorageService for large file uploads -- **Task System**: Import tasks must integrate with the existing task-management infrastructure - -## Goals - -1. **Complete Lifecycle Management**: Support all device operations from import to deletion -2. **Seamless Integration**: Reuse existing UI patterns, services, and components -3. **Scalable Import**: Handle large CSV imports efficiently using object storage -4. **Operation Traceability**: Track all device operations through task management -5. **Consistent UX**: Match the look and feel of existing card-list and enterprise-customer pages - -## Key Decisions - -### Decision 1: Three-Step Object Storage Import - -**Choice**: Use StorageService.getUploadUrl → uploadFile → DeviceService.importDevices - -**Rationale**: - -- Handles large files (thousands of devices) without timeout issues -- Decouples upload from processing (backend can process asynchronously) -- Consistent with modern cloud architecture patterns -- Allows progress tracking through task management - -**Alternatives Considered**: - -- Direct multipart upload to backend (rejected: not scalable for large files) -- Two-step process without pre-signed URL (rejected: less secure, more backend load) - -**Implementation Notes**: - -- Frontend only handles upload to object storage, not file parsing -- Backend processes the file asynchronously and creates task records -- Task management provides visibility into import progress - -### Decision 2: Device-Card Binding Model - -**Choice**: Store binding with explicit slot_position (1-4) in device_cards table - -**Rationale**: - -- IoT devices have physical SIM slots that need explicit identification -- Each device can have 1-4 slots (max_sim_slots) -- One card can only bind to one device slot (enforced by backend) -- Slot position is critical for physical device configuration - -**Alternatives Considered**: - -- Auto-assign slot positions (rejected: operators need to know physical slot numbers) -- Allow one card to bind to multiple devices (rejected: not realistic for physical SIMs) - -**Implementation Notes**: - -- Device detail page shows a 4-slot grid (empty slots show "Bind Card" button) -- Binding dialog requires explicit slot selection -- Unbinding updates bound_card_count and frees the slot - -### Decision 3: Unified Task Management - -**Choice**: Extend existing task-management to support device import tasks - -**Rationale**: - -- Reuses existing task infrastructure -- Provides consistent UX for all import operations -- Avoids duplicate task tracking logic -- Allows unified search/filter across task types - -**Alternatives Considered**: - -- Separate device task management page (rejected: creates UX fragmentation) -- Embed task tracking only in device-import page (rejected: limited visibility) - -**Implementation Notes**: - -- Add task_type field to distinguish ICCID vs Device imports -- Task detail page renders different content based on task_type -- Device tasks show device_no and bound ICCIDs instead of just ICCID - -### Decision 4: Batch Operation Result Display - -**Choice**: Show detailed results in dialog after batch allocation/recall - -**Rationale**: - -- Operations may partially succeed (some devices succeed, others fail) -- Operators need to know exactly which devices failed and why -- Allows retry of failed operations without re-selecting all devices - -**Alternatives Considered**: - -- Just show toast notification (rejected: insufficient detail for partial failures) -- Navigate to separate results page (rejected: disrupts workflow) - -**Implementation Notes**: - -- Dialog shows summary: total, success count, failure count -- Expandable table shows failed devices with error messages -- Success closes dialog, partial failure keeps it open for review - -### Decision 5: Component Reuse Strategy - -**Choice**: Use existing Art\* components (ArtTableFullScreen, ArtSearchBar, ArtTable, ArtButtonTable) - -**Rationale**: - -- Maintains UI consistency across the application -- Reduces development time -- Leverages tested, stable components -- Easier for users familiar with other pages - -**Reference Implementation**: - -- Device-list follows role/index.vue pattern -- Device-detail follows card-list detail modal pattern -- Search form follows enterprise-customer search pattern - -**Implementation Notes**: - -- Use CommonStatus for status values (ENABLED/DISABLED) -- Use ElSwitch for status toggles -- Use ArtButtonTable for action buttons -- Follow ElDescriptions layout for detail pages - -## Architecture Diagrams - -### Device Import Flow - -``` -┌─────────┐ 1. Select CSV ┌──────────────┐ -│ Admin │ ──────────────────> │ device-import│ -│ │ │ (Vue) │ -└─────────┘ └──────┬───────┘ - │ - │ 2. getUploadUrl() - v - ┌──────────────┐ - │ Storage │ - │ Service │ - └──────┬───────┘ - │ - │ 3. Upload to OSS - v - ┌──────────────┐ - │ Object │ - │ Storage │ - └──────────────┘ - │ - │ 4. importDevices(file_key) - v - ┌──────────────┐ - │ Device │ - │ Service │ - └──────┬───────┘ - │ - │ 5. Create Task - v - ┌──────────────┐ - │ Task │ - │ Management │ - └──────────────┘ -``` - -### Device-Card Binding - -``` -┌─────────┐ ┌──────────────┐ -│ Device │ ───── bound to ────> │ Card │ -│ │ (1 to N) │ │ -└─────────┘ └──────────────┘ - │ │ - │ has_many bindings │ has_one binding - v v -┌─────────────────────────────────────────────┐ -│ DeviceCardBinding │ -│ - device_id │ -│ - card_id │ -│ - slot_position (1-4) │ -│ - bound_at │ -└─────────────────────────────────────────────┘ -``` - -## Data Flow - -### Device List Page Load - -1. User navigates to /asset-management/device-list -2. Vue component mounts, calls DeviceService.getDevices(params) -3. Backend returns paginated device list with bound_card_count -4. Table renders with status switches and action buttons - -### Batch Allocation Flow - -1. User selects devices, clicks "Batch Allocate" -2. Dialog opens with shop selector -3. User selects shop, adds remarks, confirms -4. Call DeviceService.allocateDevices({ device_ids, shop_id, remarks }) -5. Backend processes each device, returns results array -6. Dialog shows summary and failed device details - -### Card Binding Flow - -1. User opens device detail page -2. Clicks "Bind Card" for an empty slot -3. Dialog shows card search and slot selection -4. User selects card and slot (e.g., slot 3) -5. Call DeviceService.bindCard(device_id, { card_id, slot_position: 3 }) -6. Backend validates slot is empty, creates binding -7. Refresh device detail to show new binding - -## Migration Plan - -### Updating Existing Device Import Page - -**Current State** (`src/views/batch/device-import/index.vue`): - -- Uses ElUpload with drag-and-drop -- Mock data for import records -- No real API integration - -**Migration Steps**: - -1. Replace ElUpload with three-step upload logic - - Add getUploadUrl call - - Add uploadFile to object storage - - Add importDevices call with file_key -2. Remove mock import records -3. Link to task-management for tracking -4. Update CSV format instructions - -**Backward Compatibility**: - -- This is a new feature area with no existing production data -- No API breaking changes -- UI changes are additive (improve existing page) - -### Extending Task Management - -**Current State**: - -- Only handles ICCID import tasks -- Single task type rendering - -**Migration Steps**: - -1. Add task_type filter dropdown (default: show all) -2. Add task_type badge in task list -3. Task detail page: switch rendering based on task_type -4. Add device-specific fields to task detail view - -**Backward Compatibility**: - -- Existing ICCID tasks continue to work unchanged -- Filter defaults to showing all types -- No database schema changes required (task_type already exists) - -## Testing Strategy - -### Unit Testing - -- DeviceService API methods with mock responses -- Form validation logic -- Utility functions (formatters, validators) - -### Integration Testing - -- Device list search and pagination -- Batch allocation with partial failures -- Card binding/unbinding workflow -- Import task creation and status tracking - -### E2E Testing Scenarios - -1. Import devices via CSV → verify task created → check task detail -2. Search devices → select multiple → allocate to shop → verify shop assignment -3. View device detail → bind card to slot 2 → unbind → verify empty slot -4. Batch recall devices → verify shop cleared → check operation history - -### Performance Considerations - -- Device list pagination (default 20 per page) -- Debounced search input (300ms delay) -- Batch operation result pagination (if >100 results) -- Large CSV imports handled asynchronously (no frontend timeout) - -## Security Considerations - -1. **Authorization**: All device operations require admin role -2. **Input Validation**: - - Device number format validation - - Slot position range (1 to max_sim_slots) - - CSV file size limits -3. **Object Storage Security**: Pre-signed URLs expire after 15 minutes -4. **Batch Operation Limits**: Backend enforces max 1000 devices per batch -5. **XSS Prevention**: All user inputs sanitized before rendering - -## Open Questions - -None at this time. All major design decisions have been made based on existing system patterns and API specifications. diff --git a/openspec/changes/add-device-management/proposal.md b/openspec/changes/add-device-management/proposal.md deleted file mode 100644 index 48f1a19..0000000 --- a/openspec/changes/add-device-management/proposal.md +++ /dev/null @@ -1,67 +0,0 @@ -# Change: 添加设备管理功能 - -## Why - -当前系统只有设备导入页面,缺少完整的设备管理能力。需要提供设备的全生命周期管理,包括: - -- 查看和搜索设备列表 -- 查看设备详情和绑定的卡信息 -- 管理设备与卡的绑定关系 -- 批量分配和回收设备 -- 完善设备导入流程和任务追踪 - -这是物联网卡管理系统的核心资产管理功能之一。 - -## What Changes - -### 新增功能 - -- **设备列表页面**: 支持多条件搜索、分页、列筛选、批量操作(分配、回收、删除) -- **设备详情页面**: 展示设备基本信息、绑定的卡列表、操作历史 -- **卡绑定管理**: 在设备详情页绑定/解绑卡 -- **批量分配对话框**: 选择设备批量分配给店铺 -- **批量回收对话框**: 从店铺批量回收设备 -- **设备导入改进**: 基于对象存储的三步导入流程 -- **导入任务管理**: 任务列表和详情查看 - -### API 集成 - -- DeviceService: 11个API接口 -- 类型定义: Device, DeviceBinding, ImportTask 等 - -### UI 组件 - -- 遵循现有组件模式 (ArtTableFullScreen, ArtSearchBar等) -- 复用 CommonStatus 统一状态变量 -- 使用 ElDescriptions、ElTag、ElSwitch 等组件 - -## Impact - -### 影响的功能模块 - -- **新增**: 资产管理 / 设备列表(主列表页) -- **新增**: 资产管理 / 设备详情 -- **改进**: 批量操作 / 设备导入(改为对象存储模式) -- **新增**: 批量操作 / 导入任务列表(独立页面) - -### 影响的代码 - -- `src/api/modules/device.ts` (新增) -- `src/types/api/device.ts` (新增) -- `src/views/asset-management/device-list/index.vue` (新增) -- `src/views/asset-management/device-detail/index.vue` (新增) -- `src/views/asset-management/task-management/index.vue` (修改:支持设备导入任务) -- `src/views/asset-management/task-detail/index.vue` (修改:支持设备导入任务详情) -- `src/views/batch/device-import/index.vue` (修改:使用对象存储) -- `src/router/routes/asyncRoutes.ts` (新增路由) -- `src/router/routesAlias.ts` (新增路由别名) -- `src/locales/langs/zh.json` (新增翻译) -- `src/api/modules/index.ts` (导出 DeviceService) -- `src/types/api/index.ts` (导出 Device 类型) - -### 依赖关系 - -- 依赖现有的 StorageService (对象存储) -- 依赖现有的 ShopService (店铺选择) -- 设备与卡的关联管理 -- 任务管理页面需要支持多种任务类型(ICCID导入 + 设备导入) diff --git a/openspec/changes/add-device-management/specs/device-management/spec.md b/openspec/changes/add-device-management/specs/device-management/spec.md deleted file mode 100644 index 8323675..0000000 --- a/openspec/changes/add-device-management/specs/device-management/spec.md +++ /dev/null @@ -1,159 +0,0 @@ -# Device Management Specification - -## ADDED Requirements - -### Requirement: Device List Management - -The system SHALL provide a searchable device list with multi-condition filtering, pagination, and batch operations. - -#### Scenario: Search devices by multiple criteria - -**WHEN** an administrator enters search criteria (device number, device name, status, shop, batch number, device type, manufacturer, creation time range) **THEN** the system shall display only devices matching all specified criteria with pagination support - -#### Scenario: View device list with bound card count - -**WHEN** the device list is displayed **THEN** each device row shall show: ID, device number, device name, device model, device type, manufacturer, max slots, bound card count, status, shop, batch number, activation time, creation time - -#### Scenario: Toggle device status - -**WHEN** an administrator clicks the status switch for a device **THEN** the system shall update the device status between ENABLED and DISABLED and refresh the display - -#### Scenario: Delete a device - -**WHEN** an administrator clicks the delete button and confirms the deletion **THEN** the system shall delete the device and refresh the list - -#### Scenario: Select multiple devices for batch operations - -**WHEN** an administrator checks multiple device rows **THEN** the system shall enable batch operation buttons (Allocate, Recall) and track selected device IDs - -### Requirement: Device Detail Viewing - -The system SHALL display comprehensive device information including basic properties and bound SIM cards. - -#### Scenario: View device basic information - -**WHEN** an administrator navigates to a device detail page **THEN** the system shall display all device properties in a descriptive layout: device number, name, model, type, manufacturer, max SIM slots, status, shop, batch number, activation time, creation time - -#### Scenario: View bound SIM cards with slot positions - -**WHEN** viewing device details **THEN** the system shall display a table of bound cards showing: slot position (1-4), ICCID, phone number, carrier, card status, and binding time - -#### Scenario: Empty slots display - -**WHEN** a device has fewer than max_sim_slots cards bound **THEN** empty slot positions shall be clearly indicated with "Bind Card" action buttons - -### Requirement: Device-Card Binding Management - -The system SHALL allow administrators to bind and unbind SIM cards to specific device slots. - -#### Scenario: Bind a card to a device slot - -**WHEN** an administrator selects a card and slot position (1-4) in the binding dialog **THEN** the system shall create the binding, update the bound card count, and refresh the card list - -#### Scenario: Prevent duplicate slot binding - -**WHEN** an administrator attempts to bind a card to an already occupied slot **THEN** the system shall show an error message and prevent the binding - -#### Scenario: Unbind a card from device - -**WHEN** an administrator clicks unbind for a bound card and confirms **THEN** the system shall remove the binding, decrement the bound card count, and refresh the card list - -#### Scenario: Validate slot range - -**WHEN** an administrator selects a slot position **THEN** the system shall only allow values from 1 to the device's max_sim_slots value - -### Requirement: Batch Device Allocation - -The system SHALL support batch allocation of devices to shops with result tracking. - -#### Scenario: Allocate multiple devices to a shop - -**WHEN** an administrator selects multiple devices, chooses a target shop, adds optional remarks, and confirms allocation **THEN** the system shall allocate all selected devices to the shop and display success/failure results for each device - -#### Scenario: Show allocation results - -**WHEN** the batch allocation completes **THEN** the system shall display: total count, success count, failure count, and detailed failure reasons for each failed device - -#### Scenario: Prevent allocation of already allocated devices - -**WHEN** the batch allocation includes devices already allocated to a shop **THEN** the system shall show warnings for those devices but proceed with allocating unallocated devices - -### Requirement: Batch Device Recall - -The system SHALL support batch recall of devices from shops with result tracking. - -#### Scenario: Recall multiple devices - -**WHEN** an administrator selects multiple devices, adds optional remarks, and confirms recall **THEN** the system shall recall all selected devices from their shops and display success/failure results - -#### Scenario: Show recall results - -**WHEN** the batch recall completes **THEN** the system shall display: total count, success count, failure count, and detailed failure reasons for each failed device - -#### Scenario: Prevent recall of unallocated devices - -**WHEN** the batch recall includes devices not allocated to any shop **THEN** the system shall show warnings for those devices but proceed with recalling allocated devices - -### Requirement: Device Import via Object Storage - -The system SHALL support CSV-based device import using a three-step object storage upload process. - -#### Scenario: Import devices with three-step process - -**WHEN** an administrator uploads a CSV file **THEN** the system shall: - -1. Get upload URL from StorageService.getUploadUrl -2. Upload file to object storage using StorageService.uploadFile -3. Call DeviceService.importDevices with the file_key **AND** display the task number for tracking - -#### Scenario: Validate CSV format - -**WHEN** the CSV file is uploaded **THEN** the system shall validate the presence of required columns: device_no, device_name, device_model, device_type, max_sim_slots, manufacturer - -#### Scenario: Import devices with card bindings - -**WHEN** the CSV includes iccid_1, iccid_2, iccid_3, iccid_4 columns **THEN** the system shall attempt to bind the specified cards to slots 1-4 respectively during import - -#### Scenario: Handle import errors - -**WHEN** the import process encounters errors (duplicate device_no, invalid ICCID, etc.) **THEN** the system shall record failed rows in the import task with detailed error messages - -### Requirement: Import Task Management - -The system SHALL track device import tasks with detailed status and record information. - -#### Scenario: List import tasks with type filter - -**WHEN** an administrator views the task management page **THEN** the system shall display both ICCID import tasks and Device import tasks with a type filter dropdown - -#### Scenario: View device import task details - -**WHEN** an administrator clicks on a device import task **THEN** the system shall display: task ID, status, total count, success count, failed count, skipped count, created time, completed time - -#### Scenario: View successful import records - -**WHEN** viewing a device import task detail **THEN** the system shall show a table of successfully imported devices with: device_no, device_name, bound ICCIDs - -#### Scenario: View failed import records - -**WHEN** viewing a device import task detail with failures **THEN** the system shall show a table of failed records with: row number, device_no, failure reason - -#### Scenario: Navigate from import page to task detail - -**WHEN** a device import completes successfully **THEN** the system shall display the task number with a link to view task details - -## MODIFIED Requirements - -### Requirement: Task Type Support - -Task management SHALL support multiple task types including ICCID import and Device import with type-specific detail views. - -**Previous behavior**: Task management only supported ICCID import tasks. - -#### Scenario: Filter tasks by type - -**WHEN** an administrator selects a task type filter (ICCID Import / Device Import) **THEN** the system shall display only tasks of the selected type - -#### Scenario: Display task type badges - -**WHEN** viewing the task list **THEN** each task shall display a type badge indicating whether it's an ICCID import or Device import task diff --git a/openspec/changes/add-device-management/tasks.md b/openspec/changes/add-device-management/tasks.md deleted file mode 100644 index ebad95b..0000000 --- a/openspec/changes/add-device-management/tasks.md +++ /dev/null @@ -1,126 +0,0 @@ -# Implementation Tasks - -## 1. 类型定义和API服务 (Foundation) - -- [ ] 1.1 创建 `src/types/api/device.ts` 定义所有设备相关类型 - - DeviceItem, DeviceQueryParams, DevicePageResult - - DeviceCardBinding, DeviceCardBindingList - - AllocateDevicesRequest/Response, RecallDevicesRequest/Response - - BindCardRequest/Response, UnbindCardResponse - - ImportDeviceRequest/Response, DeviceImportTask相关类型 -- [ ] 1.2 创建 `src/api/modules/device.ts` 实现 DeviceService - - getDevices (列表) - - getDeviceById (详情) - - deleteDevice (删除) - - getDeviceCards (绑定的卡列表) - - bindCard (绑定卡) - - unbindCard (解绑卡) - - allocateDevices (批量分配) - - recallDevices (批量回收) - - importDevices (批量导入) - - getImportTasks (导入任务列表) - - getImportTaskDetail (导入任务详情) -- [ ] 1.3 在 `src/api/modules/index.ts` 导出 DeviceService -- [ ] 1.4 在 `src/types/api/index.ts` 导出 Device 相关类型 - -## 2. 设备列表页面 (Core UI) - -- [ ] 2.1 创建 `src/views/asset-management/device-list/index.vue` - - 搜索栏:设备号、设备名称、状态、店铺、批次号、设备类型、制造商、创建时间范围 - - 表格:展示设备信息,支持勾选、列筛选 - - 表格列:ID、设备号、设备名称、设备型号、设备类型、制造商、最大插槽数、已绑定卡数、状态、店铺、批次号、激活时间、创建时间 - - 状态使用 CommonStatus + ElSwitch 显示 - - 操作列:查看详情、删除按钮(使用 ArtButtonTable) -- [ ] 2.2 实现批量操作按钮 - - 批量分配:弹出对话框选择目标店铺 - - 批量回收:弹出对话框确认回收 - - 导入设备:跳转到设备导入页面 -- [ ] 2.3 实现批量分配对话框 - - 选择目标店铺(下拉搜索) - - 显示已选设备数量 - - 备注输入 - - 展示分配结果(成功/失败) -- [ ] 2.4 实现批量回收对话框 - - 确认回收设备列表 - - 备注输入 - - 展示回收结果(成功/失败) - -## 3. 设备详情页面 (Detail View) - -- [ ] 3.1 创建 `src/views/asset-management/device-detail/index.vue` - - 设备基本信息卡片(ElDescriptions) - - 绑定的卡列表表格(展示4个插槽位置) - - 返回按钮 -- [ ] 3.2 实现卡绑定管理 - - 绑定卡按钮:弹出对话框选择卡和插槽位置 - - 解绑按钮:每个绑定记录旁边 - - 显示插槽位置(1-4) - - 展示卡的基本信息(ICCID、手机号、运营商、状态) - -## 4. 任务管理改进 (Task Management) - -- [ ] 4.1 修改 `src/views/asset-management/task-management/index.vue` - - 添加任务类型筛选:ICCID导入 / 设备导入 - - 支持展示设备导入任务 - - 任务列表显示任务类型标签 -- [ ] 4.2 修改 `src/views/asset-management/task-detail/index.vue` - - 根据任务类型展示不同的详情内容 - - 设备导入任务:显示设备号、绑定的ICCID、失败原因 - - 失败和跳过记录展示 - -## 5. 设备导入改进 (Import Enhancement) - -- [ ] 5.1 修改 `src/views/batch/device-import/index.vue` - - 改用对象存储三步上传流程 - 1. 获取上传URL (StorageService.getUploadUrl) - 2. 上传文件到对象存储 (StorageService.uploadFile) - 3. 调用导入接口 (DeviceService.importDevices) - - CSV格式说明:device_no, device_name, device_model, device_type, max_sim_slots, manufacturer, iccid_1~iccid_4, batch_no - - 导入成功后显示任务编号,引导去任务管理查看 -- [ ] 5.2 优化导入记录展示 - - 移除mock数据,改为真实API调用 - - 点击"查看详情"跳转到任务详情页 - -## 6. 路由和导航 (Routing) - -- [ ] 6.1 在 `src/router/routes/asyncRoutes.ts` 添加路由 - - /asset-management/device-list (设备列表) - - /asset-management/device-detail (设备详情,带query参数id) -- [ ] 6.2 在 `src/router/routesAlias.ts` 添加路由别名 - - DeviceList = '/asset-management/device-list' - - DeviceDetail = '/asset-management/device-detail' -- [ ] 6.3 在 `src/locales/langs/zh.json` 添加翻译 - - assetManagement.deviceList: "设备列表" - - assetManagement.deviceDetail: "设备详情" - -## 7. 测试和优化 (Testing & Polish) - -- [ ] 7.1 功能测试 - - 设备列表的搜索、分页、排序 - - 批量分配和回收流程 - - 设备与卡的绑定/解绑 - - 设备导入完整流程 - - 任务状态查看 -- [ ] 7.2 边界情况处理 - - 空列表状态 - - 加载失败提示 - - 删除确认提示 - - 表单验证 -- [ ] 7.3 性能优化 - - 列表数据懒加载 - - 防抖搜索 - - 批量操作结果分页展示 -- [ ] 7.4 代码审查 - - TypeScript类型完整性 - - 错误处理 - - 代码复用性 - - 注释完整性 - -## Dependencies - -- 1.x 必须先完成才能开始 2.x-6.x -- 2.1-2.2 可以并行开发 -- 3.x 依赖 1.x,可与 2.x 并行 -- 4.x 和 5.x 依赖 1.x,可与其他任务并行 -- 6.x 可在对应页面开发完成后立即进行 -- 7.x 在所有功能开发完成后进行 diff --git a/openspec/changes/add-iot-device-operations/proposal.md b/openspec/changes/add-iot-device-operations/proposal.md deleted file mode 100644 index 31a6edf..0000000 --- a/openspec/changes/add-iot-device-operations/proposal.md +++ /dev/null @@ -1,62 +0,0 @@ -# Change: Add IoT Card and Device Operations - -## Why - -运营人员和代理商需要更丰富的物联网卡和设备管理功能,包括查询流量使用情况、实名认证状态、卡片状态、启停操作,以及设备的重启、重置、限速、换卡、WiFi设置等操作。这些功能是物联网卡全生命周期管理的核心能力。 - -## What Changes - -### IoT Card Operations (6 new APIs) - -- 查询流量使用情况 (GET /api/admin/iot-cards/{iccid}/gateway-flow) -- 查询实名认证状态 (GET /api/admin/iot-cards/{iccid}/gateway-realname) -- 查询卡片状态 (GET /api/admin/iot-cards/{iccid}/gateway-status) -- 获取实名认证链接 (GET /api/admin/iot-cards/{iccid}/realname-link) -- 启用物联网卡 (POST /api/admin/iot-cards/{iccid}/start) -- 停用物联网卡 (POST /api/admin/iot-cards/{iccid}/stop) - -### Device Operations (6 new APIs) - -- 重启设备 (POST /api/admin/devices/by-imei/{imei}/reboot) -- 重置设备 (POST /api/admin/devices/by-imei/{imei}/reset) -- 设置限速 (PUT /api/admin/devices/by-imei/{imei}/speed-limit) -- 切换SIM卡 (POST /api/admin/devices/by-imei/{imei}/switch-card) -- 设置WiFi (PUT /api/admin/devices/by-imei/{imei}/wifi) - -### UI Changes - -- 在 `/asset-management/iot-card-management` 页面添加操作按钮: - - "查询流量"按钮(主要操作) - - "更多操作"下拉菜单(包含其他5个操作) -- 在 `/asset-management/devices` 页面添加操作按钮: - - "重启设备"按钮(主要操作) - - "更多操作"下拉菜单(包含其他5个操作) - -## Impact - -### Affected Specs - -- **NEW**: `specs/iot-card-operations/spec.md` - IoT卡操作相关的所有需求 -- **NEW**: `specs/device-operations/spec.md` - 设备操作相关的所有需求 - -### Affected Code - -- **API层**: - - 新增 `src/api/modules/iotCard.ts` - IoT卡操作API方法 - - 新增 `src/api/modules/device.ts` - 设备操作API方法 -- **类型定义**: - - 新增 `src/types/api/iotCard.ts` - IoT卡相关类型 - - 新增 `src/types/api/device.ts` - 设备相关类型 -- **页面组件**: - - 修改 `src/views/asset-management/iot-card-management/index.vue` - 添加操作按钮和对话框 - - 修改 `src/views/asset-management/devices/index.vue` - 添加操作按钮和对话框 - -### Breaking Changes - -无破坏性变更。所有变更都是增量式的新功能添加。 - -## Dependencies - -- 后端API已实现(见 `docs/2-3新增接口.md`) -- Element Plus UI组件库(已在项目中) -- Axios HTTP客户端(已在项目中) diff --git a/openspec/changes/add-iot-device-operations/specs/device-operations/spec.md b/openspec/changes/add-iot-device-operations/specs/device-operations/spec.md deleted file mode 100644 index a06ce7b..0000000 --- a/openspec/changes/add-iot-device-operations/specs/device-operations/spec.md +++ /dev/null @@ -1,194 +0,0 @@ -# Device Operations Specification - -## ADDED Requirements - -### Requirement: Reboot Device - -The system SHALL provide the ability to reboot an IoT device via its IMEI. - -#### Scenario: Successfully reboot device - -- **WHEN** an authenticated user initiates a reboot operation on a device with valid IMEI -- **THEN** the system sends the reboot command to the device -- **AND** returns a success response with operation confirmation - -#### Scenario: Reboot operation with confirmation - -- **WHEN** a user clicks the reboot device button -- **THEN** the system prompts for confirmation with a warning message -- **AND** only proceeds if the user confirms the action - -#### Scenario: Reboot with invalid IMEI - -- **WHEN** a user attempts to reboot a device with invalid or non-existent IMEI -- **THEN** the system returns a 400 error with appropriate error message - -### Requirement: Reset Device to Factory Settings - -The system SHALL provide the ability to reset an IoT device to factory settings via its IMEI. - -#### Scenario: Successfully reset device - -- **WHEN** an authenticated user initiates a factory reset operation on a device -- **THEN** the system sends the reset command to the device -- **AND** returns a success response - -#### Scenario: Reset operation with strong confirmation - -- **WHEN** a user clicks the factory reset button -- **THEN** the system displays a warning dialog explaining data loss -- **AND** requires user confirmation before proceeding -- **AND** only executes if the user explicitly confirms - -#### Scenario: Insufficient permissions for reset - -- **WHEN** a user without proper permissions attempts to reset a device -- **THEN** the system returns a 403 forbidden error - -### Requirement: Set Device Speed Limit - -The system SHALL provide the ability to configure upload and download speed limits for an IoT device. - -#### Scenario: Successfully set speed limit - -- **WHEN** an authenticated user submits valid speed limit parameters -- **THEN** the system applies the upload and download speed limits to the device -- **AND** returns a success response - -#### Scenario: Configure speed limits via dialog - -- **WHEN** a user selects the speed limit option -- **THEN** the system displays a dialog with input fields for upload_speed and download_speed (KB/s) -- **AND** validates that both values are integers >= 1 - -#### Scenario: Invalid speed limit parameters - -- **WHEN** a user submits speed limits less than 1 KB/s -- **THEN** the system returns a 400 parameter error -- **AND** displays validation error messages - -### Requirement: Switch Device SIM Card - -The system SHALL provide the ability to switch the active SIM card on a multi-SIM device. - -#### Scenario: Successfully switch to target card - -- **WHEN** an authenticated user initiates a card switch with valid target ICCID -- **THEN** the system switches the device to use the specified card -- **AND** returns a success response - -#### Scenario: Switch card via dialog - -- **WHEN** a user selects the switch card option -- **THEN** the system displays a dialog prompting for target_iccid -- **AND** validates the ICCID format - -#### Scenario: Switch to non-existent card - -- **WHEN** a user attempts to switch to an ICCID that doesn't exist or isn't available -- **THEN** the system returns a 400 error with descriptive message - -### Requirement: Configure Device WiFi Settings - -The system SHALL provide the ability to configure WiFi settings including SSID, password, and enabled status. - -#### Scenario: Successfully configure WiFi - -- **WHEN** an authenticated user submits valid WiFi configuration -- **THEN** the system applies the WiFi settings to the device -- **AND** returns a success response - -#### Scenario: Configure WiFi via dialog - -- **WHEN** a user selects the WiFi configuration option -- **THEN** the system displays a dialog with fields for: - - enabled (启用状态: 0=禁用, 1=启用) - - ssid (WiFi名称, 1-32 characters) - - password (WiFi密码, 8-63 characters) -- **AND** validates all field constraints - -#### Scenario: Invalid WiFi parameters - -- **WHEN** a user submits WiFi configuration with invalid parameters -- **THEN** the system returns a 400 error -- **AND** displays specific validation errors (e.g., "SSID too long", "Password too short") - -### Requirement: Device Management UI Integration - -The system SHALL integrate device operations into the device management page at `/asset-management/devices`. - -#### Scenario: Display primary operation button - -- **WHEN** a user views the device management page -- **THEN** the system displays a "重启设备" (Reboot Device) button as the primary operation - -#### Scenario: Display dropdown menu for additional operations - -- **WHEN** a user views the device management page -- **THEN** the system displays a "更多操作" (More Operations) dropdown menu -- **AND** the dropdown contains: 恢复出厂, 设置限速, 切换SIM卡, 设置WiFi - -#### Scenario: Show loading indicator during operations - -- **WHEN** a device operation is in progress -- **THEN** the system displays a loading indicator -- **AND** disables the operation buttons to prevent duplicate requests - -### Requirement: Authentication and Authorization - -The system SHALL enforce JWT-based authentication for all device operations. - -#### Scenario: Access with valid JWT token - -- **WHEN** a user makes a request with a valid Bearer token -- **THEN** the system processes the request normally - -#### Scenario: Access with expired token - -- **WHEN** a user makes a request with an expired JWT token -- **THEN** the system returns a 401 unauthorized error -- **AND** redirects to the login page - -### Requirement: Error Handling and User Feedback - -The system SHALL provide clear error messages and success notifications for all operations. - -#### Scenario: Display success message - -- **WHEN** a device operation completes successfully -- **THEN** the system displays a success message notification -- **AND** automatically closes the operation dialog - -#### Scenario: Handle network errors - -- **WHEN** a network error occurs during a device operation -- **THEN** the system displays a user-friendly error message -- **AND** allows the user to retry the operation - -#### Scenario: Handle server errors - -- **WHEN** a 500 server error occurs -- **THEN** the system displays an error message with timestamp -- **AND** logs the error for debugging - -### Requirement: Operation Confirmation Dialogs - -The system SHALL require user confirmation for destructive operations. - -#### Scenario: Confirm reboot operation - -- **WHEN** a user initiates a reboot -- **THEN** the system shows a confirmation dialog stating "确定要重启该设备吗?" -- **AND** requires explicit confirmation - -#### Scenario: Confirm factory reset operation - -- **WHEN** a user initiates a factory reset -- **THEN** the system shows a strong warning dialog stating "确定要恢复出厂设置吗?此操作将清除所有数据!" -- **AND** requires explicit confirmation - -#### Scenario: No confirmation for query operations - -- **WHEN** a user initiates speed limit, card switch, or WiFi configuration -- **THEN** the system displays input dialogs without confirmation prompts -- **AND** only executes after user submits valid parameters diff --git a/openspec/changes/add-iot-device-operations/specs/iot-card-operations/spec.md b/openspec/changes/add-iot-device-operations/specs/iot-card-operations/spec.md deleted file mode 100644 index fbba87d..0000000 --- a/openspec/changes/add-iot-device-operations/specs/iot-card-operations/spec.md +++ /dev/null @@ -1,151 +0,0 @@ -# IoT Card Operations Specification - -## ADDED Requirements - -### Requirement: Query IoT Card Flow Usage - -The system SHALL provide the ability to query real-time flow usage for an IoT card via its ICCID. - -#### Scenario: Successfully query flow usage - -- **WHEN** an authenticated user requests flow usage for a valid ICCID -- **THEN** the system returns flow usage data including used flow amount and unit - -#### Scenario: Query with invalid ICCID - -- **WHEN** a user requests flow usage for an invalid or non-existent ICCID -- **THEN** the system returns a 400 error with appropriate error message - -### Requirement: Query IoT Card Realname Status - -The system SHALL provide the ability to query the realname authentication status for an IoT card via its ICCID. - -#### Scenario: Successfully query realname status - -- **WHEN** an authenticated user requests realname status for a valid ICCID -- **THEN** the system returns the current realname authentication status - -#### Scenario: Query status for unauthenticated user - -- **WHEN** an unauthenticated user attempts to query realname status -- **THEN** the system returns a 401 unauthorized error - -### Requirement: Query IoT Card Real-time Status - -The system SHALL provide the ability to query the real-time operational status of an IoT card via its ICCID. - -#### Scenario: Successfully query card status - -- **WHEN** an authenticated user requests card status for a valid ICCID -- **THEN** the system returns card status information including ICCID and current status (准备/正常/停机) - -#### Scenario: Query with unauthorized access - -- **WHEN** a user without proper permissions attempts to query card status -- **THEN** the system returns a 403 forbidden error - -### Requirement: Get IoT Card Realname Link - -The system SHALL provide the ability to generate and retrieve a realname authentication link for an IoT card. - -#### Scenario: Successfully retrieve realname link - -- **WHEN** an authenticated user requests the realname link for a valid ICCID -- **THEN** the system returns an HTTPS URL that can be used for realname authentication - -#### Scenario: Display realname link as QR code - -- **WHEN** the system returns a realname authentication link -- **THEN** the UI displays the link as a QR code for easy mobile scanning - -### Requirement: Start IoT Card (复机) - -The system SHALL provide the ability to start (restore service) an IoT card via its ICCID. - -#### Scenario: Successfully start a stopped card - -- **WHEN** an authenticated user initiates a start operation on a stopped card -- **THEN** the system processes the request and restores card service -- **AND** displays a success message to the user - -#### Scenario: Start operation with confirmation - -- **WHEN** a user clicks the start card button -- **THEN** the system prompts for confirmation before executing the operation -- **AND** only proceeds if the user confirms the action - -#### Scenario: Insufficient permissions for start operation - -- **WHEN** a user without proper permissions attempts to start a card -- **THEN** the system returns a 403 forbidden error - -### Requirement: Stop IoT Card (停机) - -The system SHALL provide the ability to stop (suspend service) an IoT card via its ICCID. - -#### Scenario: Successfully stop an active card - -- **WHEN** an authenticated user initiates a stop operation on an active card -- **THEN** the system processes the request and suspends card service -- **AND** displays a success message to the user - -#### Scenario: Stop operation with confirmation - -- **WHEN** a user clicks the stop card button -- **THEN** the system prompts for confirmation before executing the operation -- **AND** only proceeds if the user confirms the action - -#### Scenario: Server error during stop operation - -- **WHEN** a server error occurs during the stop operation -- **THEN** the system returns a 500 error with error details -- **AND** displays an appropriate error message to the user - -### Requirement: IoT Card Management UI Integration - -The system SHALL integrate IoT card operations into the card management page at `/asset-management/iot-card-management`. - -#### Scenario: Display primary operation button - -- **WHEN** a user views the IoT card management page -- **THEN** the system displays a "查询流量使用" (Query Flow Usage) button as the primary operation - -#### Scenario: Display dropdown menu for additional operations - -- **WHEN** a user views the IoT card management page -- **THEN** the system displays a "更多操作" (More Operations) dropdown menu -- **AND** the dropdown contains: 查询实名状态, 查询卡状态, 获取实名链接, 启用卡片, 停用卡片 - -#### Scenario: Show operation results in dialog - -- **WHEN** a user executes a query operation (flow, realname status, or card status) -- **THEN** the system displays results in a modal dialog with properly formatted data - -### Requirement: Authentication and Authorization - -The system SHALL enforce JWT-based authentication for all IoT card operations. - -#### Scenario: Access with valid JWT token - -- **WHEN** a user makes a request with a valid Bearer token -- **THEN** the system processes the request normally - -#### Scenario: Access with expired token - -- **WHEN** a user makes a request with an expired JWT token -- **THEN** the system returns a 401 unauthorized error - -### Requirement: Error Handling - -The system SHALL provide clear error messages for all failure scenarios. - -#### Scenario: Handle 400 parameter errors - -- **WHEN** a request contains invalid parameters -- **THEN** the system returns a 400 error with specific validation failure details - -#### Scenario: Handle 500 server errors - -- **WHEN** an internal server error occurs -- **THEN** the system returns a 500 error with error timestamp -- **AND** logs the error for debugging purposes diff --git a/openspec/changes/add-iot-device-operations/tasks.md b/openspec/changes/add-iot-device-operations/tasks.md deleted file mode 100644 index 2894221..0000000 --- a/openspec/changes/add-iot-device-operations/tasks.md +++ /dev/null @@ -1,86 +0,0 @@ -# Implementation Tasks - -## 1. API Layer - IoT Card Operations - -- [ ] 1.1 创建 `src/api/modules/iotCard.ts` -- [ ] 1.2 实现 `getGatewayFlow(iccid)` - 查询流量使用 -- [ ] 1.3 实现 `getGatewayRealname(iccid)` - 查询实名状态 -- [ ] 1.4 实现 `getGatewayStatus(iccid)` - 查询卡片状态 -- [ ] 1.5 实现 `getRealnameLink(iccid)` - 获取实名链接 -- [ ] 1.6 实现 `startCard(iccid)` - 启用卡片 -- [ ] 1.7 实现 `stopCard(iccid)` - 停用卡片 - -## 2. API Layer - Device Operations - -- [ ] 2.1 创建 `src/api/modules/device.ts` -- [ ] 2.2 实现 `rebootDevice(imei)` - 重启设备 -- [ ] 2.3 实现 `resetDevice(imei)` - 重置设备 -- [ ] 2.4 实现 `setSpeedLimit(imei, params)` - 设置限速 -- [ ] 2.5 实现 `switchCard(imei, params)` - 切换SIM卡 -- [ ] 2.6 实现 `setWifi(imei, params)` - 设置WiFi - -## 3. Type Definitions - IoT Card - -- [ ] 3.1 创建 `src/types/api/iotCard.ts` -- [ ] 3.2 定义流量使用响应类型 `GatewayFlowResponse` -- [ ] 3.3 定义实名状态响应类型 `GatewayRealnameResponse` -- [ ] 3.4 定义卡片状态响应类型 `GatewayStatusResponse` -- [ ] 3.5 定义实名链接响应类型 `RealnameUrlResponse` -- [ ] 3.6 定义启停操作请求类型 `StartStopCardRequest` - -## 4. Type Definitions - Device - -- [ ] 4.1 创建 `src/types/api/device.ts` -- [ ] 4.2 定义限速参数类型 `SpeedLimitParams` -- [ ] 4.3 定义换卡参数类型 `SwitchCardParams` -- [ ] 4.4 定义WiFi参数类型 `WifiParams` -- [ ] 4.5 定义操作响应类型 `DeviceOperationResponse` - -## 5. UI - IoT Card Management Page - -- [ ] 5.1 在表格操作列添加"查询流量"按钮 -- [ ] 5.2 在表格操作列添加"更多操作"下拉菜单 -- [ ] 5.3 创建"流量使用查询"对话框组件 -- [ ] 5.4 创建"实名状态查询"对话框组件 -- [ ] 5.5 创建"卡片状态查询"对话框组件 -- [ ] 5.6 创建"获取实名链接"对话框组件(显示二维码) -- [ ] 5.7 实现"启用卡片"操作(带确认提示) -- [ ] 5.8 实现"停用卡片"操作(带确认提示) - -## 6. UI - Device Management Page - -- [ ] 6.1 在表格操作列添加"重启设备"按钮 -- [ ] 6.2 在表格操作列添加"更多操作"下拉菜单 -- [ ] 6.3 实现"重启设备"操作(带确认提示) -- [ ] 6.4 实现"重置设备"操作(带确认提示) -- [ ] 6.5 创建"设置限速"对话框组件(包含上下行速率输入) -- [ ] 6.6 创建"切换SIM卡"对话框组件(选择卡槽) -- [ ] 6.7 创建"设置WiFi"对话框组件(SSID、密码、频段等) - -## 7. Error Handling & User Feedback - -- [ ] 7.1 为所有API调用添加错误处理 -- [ ] 7.2 添加操作成功提示消息 -- [ ] 7.3 添加操作失败错误提示 -- [ ] 7.4 添加加载状态指示器 - -## 8. Permission Control - -- [ ] 8.1 检查IoT卡操作权限配置 -- [ ] 8.2 检查设备操作权限配置 -- [ ] 8.3 根据权限显示/隐藏操作按钮 - -## 9. Testing & Validation - -- [ ] 9.1 测试所有IoT卡操作API调用 -- [ ] 9.2 测试所有设备操作API调用 -- [ ] 9.3 测试UI交互和对话框显示 -- [ ] 9.4 测试错误处理场景 -- [ ] 9.5 测试权限控制 -- [ ] 9.6 验证响应数据格式和显示 - -## 10. Documentation - -- [ ] 10.1 更新API文档(如有需要) -- [ ] 10.2 添加操作说明注释 -- [ ] 10.3 更新用户手册(如有需要) diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md b/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md deleted file mode 100644 index a4777bf..0000000 --- a/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md +++ /dev/null @@ -1,33 +0,0 @@ -# Change: 重命名 API 字段 series_allocation_id 为 series_id - -## Why - -后端 API 字段命名不一致。前端表单使用 `series_id`(套餐系列ID),但 API 参数仍使用 `series_allocation_id`(套餐系列分配ID)。这导致命名混淆且不符合实际业务含义(现在直接指向套餐系列 ID,而非分配记录 ID)。 - -## What Changes - -- **BREAKING**: 重命名 4 个 API 端点的请求/响应字段 - - - `PATCH /api/admin/iot-cards/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/devices` - 查询参数和响应字段 `series_allocation_id` → `series_id` - -- 前端同步修改: - - TypeScript 类型定义 - - API 方法参数 - - 页面组件调用代码 - - 移除临时注释 - -## Impact - -- **Affected specs**: iot-card-api, device-api -- **Affected code**: - - `src/types/api/device.ts` (BatchSetDeviceSeriesBindingRequest) - - `src/types/api/card.ts` (BatchSetCardSeriesBindingRequest) - - `src/api/modules/device.ts` (batchSetDeviceSeriesBinding) - - `src/api/modules/card.ts` (batchSetCardSeriesBinding) - - `src/views/asset-management/device-list/index.vue` - - `src/views/asset-management/iot-card-management/index.vue` -- **Breaking change**: 需要后端先部署更新,前端再部署 -- **Migration**: 更新所有使用这些字段的 API 调用 diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md b/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md deleted file mode 100644 index e1506be..0000000 --- a/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md +++ /dev/null @@ -1,104 +0,0 @@ -# Device API Specification Delta - -## ADDED Requirements - -### Requirement: Device Series ID Query Parameter - -The system SHALL support filtering devices by package series ID in query parameters. - -**Query Parameter**: `series_id` (number, optional) - -#### Scenario: Filter devices by series - -- **WHEN** admin queries devices with series_id parameter -- **THEN** system returns only devices associated with that package series -- **AND** returns empty list if no matches found - -#### Scenario: Query without series filter - -- **WHEN** admin queries devices without series_id parameter -- **THEN** system returns all devices matching other filter criteria -- **AND** series association is not considered - -### Requirement: Device Series ID Response Field - -The system SHALL include package series ID in device response data. - -**Response Field**: `series_id` (number | null) - -- Value is package series ID if device is bound to a series -- Value is null if device has no series binding - -#### Scenario: Display series association - -- **WHEN** system returns device in list response -- **THEN** each device includes series_id field -- **AND** field shows current series binding or null - -#### Scenario: Null series binding - -- **WHEN** device has no series binding -- **THEN** series_id field is null -- **AND** device can still be displayed in results - -## RENAMED Requirements - -### API Field Renaming - -- FROM: `series_allocation_id` (in batch series binding endpoints) -- TO: `series_id` - -**Reason**: Field name `series_allocation_id` implied a reference to an allocation record ID, but it actually stores the package series ID directly. Renaming to `series_id` clarifies this relationship and aligns with frontend naming conventions. - -## MODIFIED Requirements - -### Requirement: Batch Set Device Series Binding - -The system SHALL provide an endpoint to batch set package series binding for devices. - -**Endpoint**: `PATCH /api/admin/devices/series-binding` - -**Request Body**: - -```json -{ - "device_ids": [1, 2, 3], - "series_id": 123 // Changed from series_allocation_id -} -``` - -**Field Definitions**: - -- `device_ids`: Array of device IDs (max 500 items) -- `series_id`: Package series ID (tb_package_series.id), 0 means clear association - -**Response**: - -```json -{ - "code": 0, - "data": { - "success_count": 3, - "fail_count": 0, - "failed_items": null - } -} -``` - -#### Scenario: Successful batch series binding - -- **WHEN** admin calls batch series binding API with valid device IDs and series_id -- **THEN** system updates series association for all valid devices -- **AND** returns success count and failure details - -#### Scenario: Clear series association - -- **WHEN** admin calls batch series binding API with series_id = 0 -- **THEN** system clears series association for specified devices -- **AND** returns success confirmation - -#### Scenario: Partial failure handling - -- **WHEN** some devices in the batch cannot be updated -- **THEN** system processes valid devices successfully -- **AND** returns failed_items list with reasons for failures diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md b/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md deleted file mode 100644 index 6376d3b..0000000 --- a/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md +++ /dev/null @@ -1,104 +0,0 @@ -# IoT Card API Specification Delta - -## ADDED Requirements - -### Requirement: IoT Card Series ID Query Parameter - -The system SHALL support filtering standalone IoT cards by package series ID in query parameters. - -**Query Parameter**: `series_id` (number, optional) - -#### Scenario: Filter cards by series - -- **WHEN** admin queries standalone cards with series_id parameter -- **THEN** system returns only cards associated with that package series -- **AND** returns empty list if no matches found - -#### Scenario: Query without series filter - -- **WHEN** admin queries cards without series_id parameter -- **THEN** system returns all cards matching other filter criteria -- **AND** series association is not considered - -### Requirement: IoT Card Series ID Response Field - -The system SHALL include package series ID in standalone IoT card response data. - -**Response Field**: `series_id` (number | null) - -- Value is package series ID if card is bound to a series -- Value is null if card has no series binding - -#### Scenario: Display series association - -- **WHEN** system returns IoT card in list response -- **THEN** each card includes series_id field -- **AND** field shows current series binding or null - -#### Scenario: Null series binding - -- **WHEN** card has no series binding -- **THEN** series_id field is null -- **AND** card can still be displayed in results - -## RENAMED Requirements - -### API Field Renaming - -- FROM: `series_allocation_id` (in batch series binding endpoints) -- TO: `series_id` - -**Reason**: Field name `series_allocation_id` implied a reference to an allocation record ID, but it actually stores the package series ID directly. Renaming to `series_id` clarifies this relationship and aligns with frontend naming conventions. - -## MODIFIED Requirements - -### Requirement: Batch Set Card Series Binding - -The system SHALL provide an endpoint to batch set package series binding for IoT cards. - -**Endpoint**: `PATCH /api/admin/iot-cards/series-binding` - -**Request Body**: - -```json -{ - "iccids": ["898600..."], - "series_id": 123 // Changed from series_allocation_id -} -``` - -**Field Definitions**: - -- `iccids`: Array of ICCIDs (max 500 items) -- `series_id`: Package series ID (tb_package_series.id), 0 means clear association - -**Response**: - -```json -{ - "code": 0, - "data": { - "success_count": 100, - "fail_count": 0, - "failed_items": null - } -} -``` - -#### Scenario: Successful batch series binding - -- **WHEN** admin calls batch series binding API with valid ICCIDs and series_id -- **THEN** system updates series association for all valid cards -- **AND** returns success count and failure details - -#### Scenario: Clear series association - -- **WHEN** admin calls batch series binding API with series_id = 0 -- **THEN** system clears series association for specified cards -- **AND** returns success confirmation - -#### Scenario: Partial failure handling - -- **WHEN** some cards in the batch cannot be updated -- **THEN** system processes valid cards successfully -- **AND** returns failed_items list with reasons for failures diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md b/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md deleted file mode 100644 index 027a914..0000000 --- a/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md +++ /dev/null @@ -1,37 +0,0 @@ -# Implementation Tasks - -## 1. 类型定义更新 - -### 1.1 批量设置接口参数重命名 - -- [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` - -### 1.2 查询参数新增字段 - -- [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` 查询参数 - -### 1.3 响应类型新增字段 - -- [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 后面) - -## 2. API 方法更新 - -- [x] 2.1 更新 `src/api/modules/device.ts` 中 batchSetDeviceSeriesBinding 方法参数,将 `series_allocation_id` 改为 `series_id` -- [x] 2.2 更新 `src/api/modules/card.ts` 中 batchSetCardSeriesBinding 方法参数,将 `series_allocation_id` 改为 `series_id` - -## 3. 页面组件更新 - -- [x] 3.1 更新 `src/views/asset-management/device-list/index.vue` 中调用 batchSetDeviceSeriesBinding 的代码,将参数名从 `series_allocation_id` 改为 `series_id` -- [x] 3.2 移除 device-list/index.vue 中第 1191 行的临时注释 -- [x] 3.3 更新 `src/views/asset-management/iot-card-management/index.vue` 中调用 batchSetCardSeriesBinding 的代码,将参数名从 `series_allocation_id` 改为 `series_id` -- [x] 3.4 移除 iot-card-management/index.vue 中第 1326 行的临时注释 - -## 4. 验证 - -- [x] 4.1 运行 TypeScript 类型检查确认无类型错误 -- [ ] 4.2 本地测试批量设置 IoT 卡系列绑定功能 -- [ ] 4.3 本地测试批量设置设备系列绑定功能 -- [ ] 4.4 确认所有相关功能正常工作 diff --git a/src/assets/img/safeguard/server.png b/src/assets/img/safeguard/server.png deleted file mode 100644 index d7eee9d..0000000 Binary files a/src/assets/img/safeguard/server.png and /dev/null differ diff --git a/src/components/core/layouts/art-fast-enter/index.vue b/src/components/core/layouts/art-fast-enter/index.vue index d890b0e..9008a73 100644 --- a/src/components/core/layouts/art-fast-enter/index.vue +++ b/src/components/core/layouts/art-fast-enter/index.vue @@ -88,13 +88,6 @@ iconColor: '#ff3b30', path: RoutesAlias.Analysis }, - { - name: '礼花效果', - description: '动画特效展示', - icon: '', - iconColor: '#7A7FFF', - path: RoutesAlias.Fireworks - }, { name: '聊天', description: '即时通讯功能', @@ -116,13 +109,6 @@ iconColor: '#ff6b6b', path: WEB_LINKS.COMMUNITY }, - { - name: '更新日志', - description: '版本更新与变更记录', - icon: '', - iconColor: '#38C0FC', - path: RoutesAlias.ChangeLog - }, { name: '哔哩哔哩', description: '技术分享与交流', @@ -134,11 +120,7 @@ const quickLinks: QuickLink[] = [ { name: '登录', path: RoutesAlias.Login }, - { name: '注册', path: RoutesAlias.Register }, - { name: '忘记密码', path: RoutesAlias.ForgetPassword }, - { name: '定价', path: RoutesAlias.Pricing }, - { name: '个人中心', path: RoutesAlias.UserCenter }, - { name: '留言管理', path: RoutesAlias.Comment } + { name: '个人中心', path: RoutesAlias.UserCenter } ] const handleAppClick = (path: string) => { diff --git a/src/components/core/tables/ArtTable.vue b/src/components/core/tables/ArtTable.vue index 5165fb9..d43fb40 100644 --- a/src/components/core/tables/ArtTable.vue +++ b/src/components/core/tables/ArtTable.vue @@ -362,6 +362,15 @@ const actionList = props.actions(row) + // 如果没有操作按钮,显示提示 + if (actionList.length === 0) { + return h( + 'span', + { style: 'color: var(--el-text-color-placeholder); font-size: 12px;' }, + '暂无可用操作' + ) + } + // 如果操作数量 <= 2,直接显示所有按钮 if (actionList.length <= 2) { return h( diff --git a/src/locales/langs/zh.json b/src/locales/langs/zh.json index 2b81893..ebad2f7 100644 --- a/src/locales/langs/zh.json +++ b/src/locales/langs/zh.json @@ -312,38 +312,17 @@ "ecommerce": "电子商务" }, "widgets": { - "title": "组件中心", - "iconList": "Icon 图标", - "iconSelector": "图标选择器", - "imageCrop": "图像裁剪", - "excel": "Excel 导入导出", - "video": "视频播放器", - "countTo": "数字滚动", "wangEditor": "富文本编辑器", "watermark": "水印", "contextMenu": "右键菜单", "qrcode": "二维码", - "drag": "拖拽", - "textScroll": "文字滚动", - "fireworks": "礼花", - "elementUI": "组件总览" + "drag": "拖拽" }, "template": { - "title": "模板中心", "chat": "聊天", - "cards": "卡片", - "banners": "横幅", "charts": "图表", "map": "地图", - "calendar": "日历", - "pricing": "定价" - }, - "article": { - "title": "文章管理", - "articleList": "文章列表", - "articleDetail": "文章详情", - "comment": "留言管理", - "articlePublish": "文章发布" + "calendar": "日历" }, "result": { "title": "结果页面", @@ -361,55 +340,16 @@ "role": "角色管理", "permission": "权限管理", "carrierManagement": "运营商管理", - "userCenter": "个人中心", - "menu": "菜单管理", - "nested": "嵌套菜单", - "menu1": "菜单1", - "menu2": "菜单2", - "menu21": "菜单2-1", - "menu3": "菜单3", - "menu31": "菜单3-1", - "menu32": "菜单3-2", - "menu321": "菜单3-2-1" - }, - "safeguard": { - "title": "运维管理", - "server": "服务器管理" - }, - "plan": { - "title": "版本计划", - "log": "更新日志" - }, - "help": { - "title": "帮助中心", - "document": "官方文档" - }, - "cardManagement": { - "title": "我的网卡", - "singleCard": "资产信息", - "cardList": "网卡管理", - "cardDetail": "网卡明细", - "cardAssign": "网卡分配", - "cardShutdown": "停机管理", - "myCards": "我的网卡", - "offlineBatchRecharge": "线下批量充值", - "cardTransfer": "网卡转接", - "cardReplacement": "换卡管理", - "packageGift": "套餐赠送", - "cardChange": "换卡网卡" + "userCenter": "个人中心" }, "packageManagement": { "title": "套餐管理", - "packageCreate": "新建套餐", - "packageBatch": "批量管理", "packageList": "套餐列表", "packageDetail": "套餐详情", - "packageChange": "套餐变更", "seriesGrants": "代理系列授权", "seriesGrantsDetail": "代理系列授权详情", "packageSeries": "套餐系列", - "packageSeriesDetail": "套餐系列详情", - "packageCommission": "套餐佣金网卡" + "packageSeriesDetail": "套餐系列详情" }, "accountManagement": { "title": "账号管理", @@ -425,15 +365,6 @@ "enterpriseCards": "企业卡管理", "customerCommission": "客户账号佣金" }, - "product": { - "title": "商品管理", - "simCard": "号卡管理", - "simCardAssign": "号卡分配", - "packageSeries": "套餐系列管理", - "packageList": "套餐列表", - "packageAssign": "套餐分配", - "shopAccounts": "店铺账号列表" - }, "shopManagement": { "title": "店铺管理", "shopList": "店铺列表" @@ -469,20 +400,24 @@ "orderList": "订单列表", "orderDetail": "订单详情" }, + "financialManagement": { + "title": "财务管理", + "agentRecharge": "代理充值", + "agentRechargeDetail": "代理充值详情", + "refundManagement": "退款管理", + "refundDetail": "退款详情" + }, "commission": { - "menu": { - "management": "佣金管理", - "withdrawal": "提现审批", - "withdrawalSettings": "提现配置", - "myCommission": "我的佣金", - "agentCommission": "代理商佣金" - } + "title": "佣金管理", + "withdrawal": "提现审批", + "withdrawalSettings": "提现配置", + "myCommission": "我的佣金", + "agentCommission": "代理商佣金" }, "settings": { "title": "设置管理", - "paymentMerchant": "支付商户", - "developerApi": "开发者API", - "commissionTemplate": "分佣模板" + "wechatPayConfiguration": "微信支付配置", + "detailsOfPaymentConfiguration": "支付配置详情" } }, "table": { diff --git a/src/router/guards/permission.ts b/src/router/guards/permission.ts index 46c4353..caba73e 100644 --- a/src/router/guards/permission.ts +++ b/src/router/guards/permission.ts @@ -11,8 +11,6 @@ import { useUserStore } from '@/store/modules/user' */ export const LOGIN_WHITE_LIST = [ '/auth/login', - '/auth/register', - '/auth/forget-password', '/exception/403', '/exception/404', '/exception/500' diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 0725450..ec37c3e 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -12,6 +12,7 @@ import { AppRouteRecord } from '@/types/router' * 可以是 i18n 的 key,也可以是字符串,比如:'用户列表' */ export const asyncRoutes: AppRouteRecord[] = [ + // 仪表台 { name: 'Dashboard', path: '/dashboard', @@ -52,222 +53,7 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, - // { - // path: '/widgets', - // name: 'Widgets', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.widgets.title', - // icon: '' - // }, - // children: [ - // { - // path: 'icon-list', - // name: 'IconList', - // component: RoutesAlias.IconList, - // meta: { - // title: 'menus.widgets.iconList', - // keepAlive: true - // } - // }, - // { - // path: 'icon-selector', - // name: 'IconSelector', - // component: RoutesAlias.IconSelector, - // meta: { - // title: 'menus.widgets.iconSelector', - // keepAlive: true - // } - // }, - // { - // path: 'image-crop', - // name: 'ImageCrop', - // component: RoutesAlias.ImageCrop, - // meta: { - // title: 'menus.widgets.imageCrop', - // keepAlive: true - // } - // }, - // { - // path: 'excel', - // name: 'Excel', - // component: RoutesAlias.Excel, - // meta: { - // title: 'menus.widgets.excel', - // keepAlive: true - // } - // }, - // { - // path: 'video', - // name: 'Video', - // component: RoutesAlias.Video, - // meta: { - // title: 'menus.widgets.video', - // keepAlive: true - // } - // }, - // { - // path: 'count-to', - // name: 'CountTo', - // component: RoutesAlias.CountTo, - // meta: { - // title: 'menus.widgets.countTo', - // keepAlive: false - // } - // }, - // { - // path: 'wang-editor', - // name: 'WangEditor', - // component: RoutesAlias.WangEditor, - // meta: { - // title: 'menus.widgets.wangEditor', - // keepAlive: true - // } - // }, - // { - // path: 'watermark', - // name: 'Watermark', - // component: RoutesAlias.Watermark, - // meta: { - // title: 'menus.widgets.watermark', - // keepAlive: true - // } - // }, - // { - // path: 'context-menu', - // name: 'ContextMenu', - // component: RoutesAlias.ContextMenu, - // meta: { - // title: 'menus.widgets.contextMenu', - // keepAlive: true - // } - // }, - // { - // path: 'qrcode', - // name: 'Qrcode', - // component: RoutesAlias.Qrcode, - // meta: { - // title: 'menus.widgets.qrcode', - // keepAlive: true - // } - // }, - // { - // path: 'drag', - // name: 'Drag', - // component: RoutesAlias.Drag, - // meta: { - // title: 'menus.widgets.drag', - // keepAlive: true - // } - // }, - // { - // path: 'text-scroll', - // name: 'TextScroll', - // component: RoutesAlias.TextScroll, - // meta: { - // title: 'menus.widgets.textScroll', - // keepAlive: true - // } - // }, - // { - // path: 'fireworks', - // name: 'Fireworks', - // component: RoutesAlias.Fireworks, - // meta: { - // title: 'menus.widgets.fireworks', - // keepAlive: true, - // showTextBadge: 'Hot' - // } - // }, - // { - // path: '/outside/iframe/elementui', - // name: 'ElementUI', - // component: '', - // meta: { - // title: 'menus.widgets.elementUI', - // keepAlive: false, - // link: 'https://element-plus.org/zh-CN/component/overview.html', - // isIframe: true, - // showBadge: true - // } - // } - // ] - // }, - // { - // path: '/template', - // name: 'Template', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.template.title', - // icon: '' - // }, - // children: [ - // { - // path: 'cards', - // name: 'Cards', - // component: RoutesAlias.Cards, - // meta: { - // title: 'menus.template.cards', - // keepAlive: false - // } - // }, - // { - // path: 'banners', - // name: 'Banners', - // component: RoutesAlias.Banners, - // meta: { - // title: 'menus.template.banners', - // keepAlive: false - // } - // }, - // { - // path: 'charts', - // name: 'Charts', - // component: RoutesAlias.Charts, - // meta: { - // title: 'menus.template.charts', - // keepAlive: false - // } - // }, - // { - // path: 'map', - // name: 'Map', - // component: RoutesAlias.Map, - // meta: { - // title: 'menus.template.map', - // keepAlive: true - // } - // }, - // { - // path: 'chat', - // name: 'Chat', - // component: RoutesAlias.Chat, - // meta: { - // title: 'menus.template.chat', - // keepAlive: true - // } - // }, - // { - // path: 'calendar', - // name: 'Calendar', - // component: RoutesAlias.Calendar, - // meta: { - // title: 'menus.template.calendar', - // keepAlive: true - // } - // }, - // { - // path: 'pricing', - // name: 'Pricing', - // component: RoutesAlias.Pricing, - // meta: { - // title: 'menus.template.pricing', - // keepAlive: true - // } - // } - // ] - // }, - + // 系统管理 { path: '/system', name: 'System', @@ -318,404 +104,10 @@ export const asyncRoutes: AppRouteRecord[] = [ isHideTab: true } } - // { - // path: 'menu', - // name: 'Menus', - // component: RoutesAlias.Menu, - // meta: { - // title: 'menus.system.menu', - // keepAlive: true, - // roles: ['R_SUPER'], - // authList: [ - // { - // title: '新增', - // auth_mark: 'add' - // }, - // { - // title: '编辑', - // auth_mark: 'edit' - // }, - // { - // title: '删除', - // auth_mark: 'delete' - // } - // ] - // } - // } - // { - // path: 'nested', - // name: 'Nested', - // component: '', - // meta: { - // title: 'menus.system.nested', - // keepAlive: true - // }, - // children: [ - // { - // path: 'menu1', - // name: 'NestedMenu1', - // component: RoutesAlias.NestedMenu1, - // meta: { - // title: 'menus.system.menu1', - // icon: '', - // keepAlive: true - // } - // }, - // { - // path: 'menu2', - // name: 'NestedMenu2', - // component: '', - // meta: { - // title: 'menus.system.menu2', - // icon: '', - // keepAlive: true - // }, - // children: [ - // { - // path: 'menu2-1', - // name: 'NestedMenu2-1', - // component: RoutesAlias.NestedMenu21, - // meta: { - // title: 'menus.system.menu21', - // icon: '', - // keepAlive: true - // } - // } - // ] - // }, - // { - // path: 'menu3', - // name: 'NestedMenu3', - // component: '', - // meta: { - // title: 'menus.system.menu3', - // icon: '', - // keepAlive: true - // }, - // children: [ - // { - // path: 'menu3-1', - // name: 'NestedMenu3-1', - // component: RoutesAlias.NestedMenu31, - // meta: { - // title: 'menus.system.menu31', - // icon: '', - // keepAlive: true - // } - // }, - // { - // path: 'menu3-2', - // name: 'NestedMenu3-2', - // component: '', - // meta: { - // title: 'menus.system.menu32', - // icon: '', - // keepAlive: true - // }, - // children: [ - // { - // path: 'menu3-2-1', - // name: 'NestedMenu3-2-1', - // component: RoutesAlias.NestedMenu321, - // meta: { - // title: 'menus.system.menu321', - // icon: '', - // keepAlive: true - // } - // } - // ] - // } - // ] - // } - // ] - // } ] }, - // { - // path: '/article', - // name: 'Article', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.article.title', - // icon: '', - // roles: ['R_SUPER', 'R_ADMIN'] - // }, - // children: [ - // { - // path: 'article-list', - // name: 'ArticleList', - // component: RoutesAlias.ArticleList, - // meta: { - // title: 'menus.article.articleList', - // keepAlive: true, - // authList: [ - // { - // title: '新增', - // auth_mark: 'add' - // }, - // { - // title: '编辑', - // auth_mark: 'edit' - // } - // ] - // } - // }, - - // { - // path: 'detail', - // name: 'ArticleDetail', - // component: RoutesAlias.ArticleDetail, - // meta: { - // title: 'menus.article.articleDetail', - // isHide: true, - // keepAlive: true - // } - // }, - // { - // path: 'comment', - // name: 'ArticleComment', - // component: RoutesAlias.Comment, - // meta: { - // title: 'menus.article.comment', - // keepAlive: true - // } - // }, - // { - // path: 'publish', - // name: 'ArticlePublish', - // component: RoutesAlias.ArticlePublish, - // meta: { - // title: 'menus.article.articlePublish', - // keepAlive: true, - // authList: [ - // { - // title: '发布', - // auth_mark: 'article/article-publish/add' - // } - // ] - // } - // } - // ] - // }, - // { - // path: '/result', - // name: 'Result', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.result.title', - // icon: '' - // }, - // children: [ - // { - // path: 'success', - // name: 'ResultSuccess', - // component: RoutesAlias.Success, - // meta: { - // title: 'menus.result.success', - // keepAlive: true - // } - // }, - // { - // path: 'fail', - // name: 'ResultFail', - // component: RoutesAlias.Fail, - // meta: { - // title: 'menus.result.fail', - // keepAlive: true - // } - // } - // ] - // }, - // { - // path: '/exception', - // name: 'Exception', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.exception.title', - // icon: '' - // }, - // children: [ - // { - // path: '403', - // name: '403', - // component: RoutesAlias.Exception403, - // meta: { - // title: 'menus.exception.forbidden', - // keepAlive: true - // } - // }, - // { - // path: '404', - // name: '404', - // component: RoutesAlias.Exception404, - // meta: { - // title: 'menus.exception.notFound', - // keepAlive: true - // } - // }, - // { - // path: '500', - // name: '500', - // component: RoutesAlias.Exception500, - // meta: { - // title: 'menus.exception.serverError', - // keepAlive: true - // } - // } - // ] - // }, - - // { - // path: '/safeguard', - // name: 'Safeguard', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.safeguard.title', - // icon: '', - // keepAlive: false - // }, - // children: [ - // { - // path: 'server', - // name: 'SafeguardServer', - // component: RoutesAlias.Server, - // meta: { - // title: 'menus.safeguard.server', - // keepAlive: true - // } - // } - // ] - // }, - // { - // name: 'Help', - // path: '/help', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.help.title', - // icon: '', - // keepAlive: false, - // roles: ['R_SUPER', 'R_ADMIN'] - // }, - // children: [ - // { - // path: '', - // name: 'Document', - // meta: { - // title: 'menus.help.document', - // link: WEB_LINKS.DOCS, - // isIframe: false, - // keepAlive: false - // } - // } - // ] - // }, - // 一级菜单 - // { - // name: 'ChangeLog', - // path: '/change/log', - // component: RoutesAlias.ChangeLog, - // meta: { - // title: 'menus.plan.log', - // showTextBadge: `v${__APP_VERSION__}`, - // icon: '', - // keepAlive: false - // } - // }, - // 物联网卡管理系统模块 - // { - // path: '/card-management', - // name: 'CardManagement', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.cardManagement.title', - // icon: '' - // }, - // children: [ - // { - // path: 'card-detail', - // name: 'CardDetail', - // component: RoutesAlias.CardDetail, - // meta: { - // title: 'menus.cardManagement.cardDetail', - // keepAlive: true - // } - // }, - // { - // path: 'single-card', - // name: 'SingleCard', - // component: RoutesAlias.SingleCard, - // meta: { - // title: 'menus.cardManagement.singleCard', - // keepAlive: true - // } - // }, - // { - // path: 'card-assign', - // name: 'CardAssign', - // component: RoutesAlias.CardAssign, - // meta: { - // title: 'menus.cardManagement.cardAssign', - // keepAlive: true - // } - // }, - // { - // path: 'card-shutdown', - // name: 'CardShutdown', - // component: RoutesAlias.CardShutdown, - // meta: { - // title: 'menus.cardManagement.cardShutdown', - // keepAlive: true - // } - // }, - // { - // path: 'my-cards', - // name: 'MyCards', - // component: RoutesAlias.MyCards, - // meta: { - // title: 'menus.cardManagement.myCards', - // keepAlive: true - // } - // }, - // { - // path: 'card-transfer', - // name: 'CardTransfer', - // component: RoutesAlias.CardTransfer, - // meta: { - // title: 'menus.cardManagement.cardTransfer', - // keepAlive: true - // } - // }, - // { - // path: 'card-replacement', - // name: 'CardReplacement', - // component: RoutesAlias.CardReplacement, - // meta: { - // title: 'menus.cardManagement.cardReplacement', - // keepAlive: true - // } - // }, - // { - // path: 'package-gift', - // name: 'PackageGift', - // component: RoutesAlias.PackageGift, - // meta: { - // title: 'menus.cardManagement.packageGift', - // keepAlive: true - // } - // }, - // { - // path: 'card-change-card', - // name: 'CardChangeCard', - // component: RoutesAlias.CardChangeCard, - // meta: { - // title: 'menus.cardManagement.cardChange', - // keepAlive: true - // } - // } - // ] - // }, - + // 套餐管理 { path: '/package-management', name: 'PackageManagement', @@ -725,6 +117,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 套餐系列 { path: 'package-series', name: 'PackageSeries', @@ -734,6 +127,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 套餐系列详情 { path: 'package-series/detail/:id', name: 'PackageSeriesDetail', @@ -744,6 +138,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: false } }, + // 套餐列表 { path: 'package-list', name: 'PackageList', @@ -753,6 +148,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 套餐列表详情 { path: 'package-list/detail/:id', name: 'PackageDetail', @@ -763,6 +159,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: false } }, + // 代理系列授权 { path: 'series-grants', name: 'SeriesGrants', @@ -772,6 +169,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 系列授权详情 { path: 'series-grants/detail/:id', name: 'SeriesGrantsDetail', @@ -785,6 +183,7 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 店铺管理 { path: '/shop-management', name: 'ShopManagement', @@ -794,6 +193,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 店铺列表 { path: 'list', name: 'Shop', @@ -806,6 +206,7 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 账号管理 { path: '/account-management', name: 'AccountManagement', @@ -815,6 +216,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 账号列表 { path: 'account', name: 'Account', @@ -824,42 +226,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, - // { - // path: 'platform-account', - // name: 'PlatformAccount', - // component: RoutesAlias.PlatformAccount, - // meta: { - // title: 'menus.accountManagement.platformAccount', - // keepAlive: true - // } - // }, - // { - // path: 'shop-account', - // name: 'ShopAccount', - // component: RoutesAlias.ShopAccount, - // meta: { - // title: 'menus.accountManagement.shopAccount', - // keepAlive: true - // } - // }, - // { - // path: 'customer-role', - // name: 'CustomerRole', - // component: RoutesAlias.CustomerRole, - // meta: { - // title: 'menus.accountManagement.customerRole', - // keepAlive: true - // } - // }, - // { - // path: 'agent', - // name: 'AgentManagement', - // component: RoutesAlias.AgentManagement, - // meta: { - // title: 'menus.accountManagement.agent', - // keepAlive: true - // } - // }, + // 企业客户 { path: 'enterprise-customer', name: 'EnterpriseCustomer', @@ -869,6 +236,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + //企业客户账号列表和店铺账号列表共用 { path: 'enterprise-customer/customer-accounts/:id', name: 'EnterpriseCustomerAccounts', @@ -878,86 +246,11 @@ export const asyncRoutes: AppRouteRecord[] = [ isHide: true, keepAlive: false } - }, - { - path: 'enterprise-cards', - name: 'EnterpriseCards', - component: RoutesAlias.EnterpriseCards, - meta: { - title: 'menus.accountManagement.enterpriseCards', - isHide: true, - keepAlive: false - } } ] }, - // { - // path: '/product', - // name: 'Product', - // component: RoutesAlias.Home, - // meta: { - // title: 'menus.product.title', - // icon: '' - // }, - // children: [ - // // { - // // path: 'sim-card', - // // name: 'SimCardManagement', - // // component: RoutesAlias.SimCardManagement, - // // meta: { - // // title: 'menus.product.simCard', - // // keepAlive: true - // // } - // // }, - // // { - // // path: 'sim-card-assign', - // // name: 'SimCardAssign', - // // component: RoutesAlias.SimCardAssign, - // // meta: { - // // title: 'menus.product.simCardAssign', - // // keepAlive: true - // // } - // // }, - // // { - // // path: 'package-series', - // // name: 'PackageSeries', - // // component: RoutesAlias.PackageSeries, - // // meta: { - // // title: 'menus.product.packageSeries', - // // keepAlive: true - // // } - // // }, - // // { - // // path: 'package-list', - // // name: 'PackageList', - // // component: RoutesAlias.PackageList, - // // meta: { - // // title: 'menus.product.packageList', - // // keepAlive: true - // // } - // // }, - // // { - // // path: 'package-assign', - // // name: 'PackageAssign', - // // component: RoutesAlias.PackageAssign, - // // meta: { - // // title: 'menus.product.packageAssign', - // // keepAlive: true - // // } - // // }, - // { - // path: 'shop', - // name: 'Shop', - // component: RoutesAlias.Shop, - // meta: { - // title: 'menus.product.shop', - // keepAlive: true - // } - // } - // ] - // }, - + // 资产管理 { path: '/asset-management', name: 'AssetManagement', @@ -967,6 +260,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 资产信息 { path: 'asset-information', name: 'AssetInformation', @@ -976,6 +270,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // IoT卡管理 { path: 'iot-card-management', name: 'StandaloneCardList', @@ -985,6 +280,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 设备管理 { path: 'devices', name: 'DeviceList', @@ -994,6 +290,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 企业设备管理(设备授权) { path: 'enterprise-devices', name: 'EnterpriseDevices', @@ -1004,6 +301,17 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: false } }, + // 企业卡授权 + { + path: 'enterprise-cards', + name: 'EnterpriseCards', + component: RoutesAlias.EnterpriseCards, + meta: { + title: 'menus.accountManagement.enterpriseCards', + isHide: true, + keepAlive: false + } + }, // 换货管理 { path: 'exchange-management', @@ -1014,6 +322,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 换货详情 { path: 'exchange-management/detail/:id', name: 'ExchangeDetail', @@ -1026,7 +335,7 @@ export const asyncRoutes: AppRouteRecord[] = [ }, // 记录管理 { - path: 'records', + path: 'record-management', name: 'RecordsManagement', component: '', meta: { @@ -1034,6 +343,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true }, children: [ + // 资产分配 { path: 'asset-assign', name: 'AssetAssign', @@ -1043,6 +353,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 授权记录 { path: 'authorization-records', name: 'AuthorizationRecords', @@ -1056,7 +367,7 @@ export const asyncRoutes: AppRouteRecord[] = [ }, // 分配记录详情(放在 asset-management 层级) { - path: 'asset-assign/detail/:id', + path: 'record-management/asset-assign/detail/:id', name: 'AssetAssignDetail', component: RoutesAlias.AssetAssignDetail, meta: { @@ -1067,7 +378,7 @@ export const asyncRoutes: AppRouteRecord[] = [ }, // 授权记录详情(放在 asset-management 层级) { - path: 'authorization-records/detail/:id', + path: 'record-management/authorization-records/detail/:id', name: 'AuthorizationRecordDetail', component: RoutesAlias.AuthorizationRecordDetail, meta: { @@ -1078,7 +389,7 @@ export const asyncRoutes: AppRouteRecord[] = [ }, // 任务管理 { - path: 'tasks', + path: 'task-management', name: 'TaskManagement', component: '', meta: { @@ -1086,6 +397,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true }, children: [ + // IoT卡任务 { path: 'iot-card-task', name: 'IotCardTask', @@ -1095,6 +407,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 设备任务 { path: 'device-task', name: 'DeviceTask', @@ -1108,7 +421,7 @@ export const asyncRoutes: AppRouteRecord[] = [ }, // 任务详情(放在 asset-management 层级) { - path: 'task-detail', + path: 'task-management/task-detail', name: 'TaskDetail', component: RoutesAlias.TaskDetail, meta: { @@ -1120,6 +433,7 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 订单管理 { path: '/order-management', name: 'OrderManagement', @@ -1129,6 +443,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 订单列表 { path: 'order-list', name: 'OrderList', @@ -1138,6 +453,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 订单详情 { path: 'order-list/detail/:id', name: 'OrderDetail', @@ -1151,49 +467,54 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 财务管理 { path: '/finance', name: 'FinanceManagement', component: RoutesAlias.Home, meta: { - title: '财务管理', + title: 'menus.financialManagement.title', icon: '' }, children: [ + // 代理充值 { path: 'agent-recharge', name: 'AgentRecharge', component: RoutesAlias.AgentRecharge, meta: { - title: '代理充值', + title: 'menus.financialManagement.agentRecharge', keepAlive: true } }, + // 充值详情 { path: 'agent-recharge/detail/:id', name: 'AgentRechargeDetailRoute', component: RoutesAlias.AgentRechargeDetail, meta: { - title: '充值详情', + title: 'menus.financialManagement.agentRechargeDetail', isHide: true, keepAlive: false } }, + // 退款管理 { path: 'refund', name: 'RefundManagement', component: RoutesAlias.RefundManagement, meta: { - title: '退款管理', + title: 'menus.financialManagement.refundManagement', keepAlive: true } }, + // 退款详情 { path: 'refund/detail/:id', name: 'RefundDetailRoute', component: RoutesAlias.RefundDetail, meta: { - title: '退款详情', + title: 'menus.financialManagement.refundDetail', isHide: true, keepAlive: false } @@ -1201,51 +522,56 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 佣金管理 { path: '/commission', name: 'CommissionManagement', component: RoutesAlias.Home, meta: { - title: 'menus.commission.menu.management', + title: 'menus.commission.title', icon: '' }, children: [ + // 提现审批 { path: 'withdrawal-approval', name: 'WithdrawalApproval', component: RoutesAlias.WithdrawalApproval, meta: { - title: 'menus.commission.menu.withdrawal', + title: 'menus.commission.withdrawal', keepAlive: true, roles: ['R_SUPER', 'R_ADMIN'] } }, + // 提现配置 { path: 'withdrawal-settings', name: 'CommissionWithdrawalSettings', component: RoutesAlias.CommissionWithdrawalSettings, meta: { - title: 'menus.commission.menu.withdrawalSettings', + title: 'menus.commission.withdrawalSettings', keepAlive: true, roles: ['R_SUPER', 'R_ADMIN'] } }, + // 我的佣金 { path: 'my-commission', name: 'MyCommission', component: RoutesAlias.MyCommission, meta: { - title: 'menus.commission.menu.myCommission', + title: 'menus.commission.myCommission', keepAlive: true, roles: ['R_AGENT'] } }, + // 代理商佣金 { path: 'agent-commission', name: 'AgentCommission', component: RoutesAlias.AgentCommission, meta: { - title: 'menus.commission.menu.agentCommission', + title: 'menus.commission.agentCommission', keepAlive: true, roles: ['R_SUPER', 'R_ADMIN'] } @@ -1253,61 +579,37 @@ export const asyncRoutes: AppRouteRecord[] = [ ] }, + // 设置管理 { path: '/settings', name: 'Settings', component: RoutesAlias.Home, meta: { - title: '设置管理', + title: 'menus.settings.title', icon: '' }, children: [ + // 微信支付配置 { path: 'wechat-config', name: 'WechatConfig', component: RoutesAlias.WechatConfig, meta: { - title: '微信支付配置', + title: 'menus.settings.wechatPayConfiguration', keepAlive: true } }, + // 微信支付配置详情 { path: 'wechat-config/detail/:id', name: 'WechatConfigDetailRoute', component: RoutesAlias.WechatConfigDetail, meta: { - title: '支付配置详情', + title: 'menus.settings.detailsOfPaymentConfiguration', isHide: true, keepAlive: false } } - // { - // path: 'payment-merchant', - // name: 'PaymentMerchant', - // component: RoutesAlias.PaymentMerchant, - // meta: { - // title: 'menus.settings.paymentMerchant', - // keepAlive: true - // } - // }, - // { - // path: 'developer-api', - // name: 'DeveloperApi', - // component: RoutesAlias.DeveloperApi, - // meta: { - // title: 'menus.settings.developerApi', - // keepAlive: true - // } - // }, - // { - // path: 'commission-template', - // name: 'CommissionTemplate', - // component: RoutesAlias.CommissionTemplate, - // meta: { - // title: 'menus.settings.commissionTemplate', - // keepAlive: true - // } - // } ] }, @@ -1321,6 +623,7 @@ export const asyncRoutes: AppRouteRecord[] = [ icon: '' }, children: [ + // 数据清理 { path: 'data-cleanup', name: 'DataCleanup', @@ -1330,6 +633,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 告警规则 { path: 'alert-rules', name: 'AlertRules', @@ -1339,6 +643,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 告警历史 { path: 'alert-history', name: 'AlertHistory', @@ -1348,6 +653,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 并发控制 { path: 'concurrency', name: 'PollingConcurrency', @@ -1357,6 +663,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 轮询配置 { path: 'config', name: 'PollingConfig', @@ -1366,6 +673,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 手动触发 { path: 'manual-trigger', name: 'ManualTrigger', @@ -1375,6 +683,7 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + // 轮询监控 { path: 'monitor', name: 'PollingMonitor', @@ -1386,4 +695,111 @@ export const asyncRoutes: AppRouteRecord[] = [ } ] } + + // 组件中心 + // { + // path: '/widgets', + // name: 'Widgets', + // component: RoutesAlias.Home, + // meta: { + // title: 'menus.widgets.title', + // icon: '' + // }, + // children: [ + // { + // path: 'wang-editor', + // name: 'WangEditor', + // component: RoutesAlias.WangEditor, + // meta: { + // title: 'menus.widgets.wangEditor', + // keepAlive: true + // } + // }, + // { + // path: 'watermark', + // name: 'Watermark', + // component: RoutesAlias.Watermark, + // meta: { + // title: 'menus.widgets.watermark', + // keepAlive: true + // } + // }, + // { + // path: 'context-menu', + // name: 'ContextMenu', + // component: RoutesAlias.ContextMenu, + // meta: { + // title: 'menus.widgets.contextMenu', + // keepAlive: true + // } + // }, + // { + // path: 'qrcode', + // name: 'Qrcode', + // component: RoutesAlias.Qrcode, + // meta: { + // title: 'menus.widgets.qrcode', + // keepAlive: true + // } + // }, + // { + // path: 'drag', + // name: 'Drag', + // component: RoutesAlias.Drag, + // meta: { + // title: 'menus.widgets.drag', + // keepAlive: true + // } + // } + // ] + // }, + + // 模板中心 + // { + // path: '/template', + // name: 'Template', + // component: RoutesAlias.Home, + // meta: { + // title: 'menus.template.title', + // icon: '' + // }, + // children: [ + // { + // path: 'charts', + // name: 'Charts', + // component: RoutesAlias.Charts, + // meta: { + // title: 'menus.template.charts', + // keepAlive: false + // } + // }, + // { + // path: 'map', + // name: 'Map', + // component: RoutesAlias.Map, + // meta: { + // title: 'menus.template.map', + // keepAlive: true + // } + // }, + // { + // path: 'chat', + // name: 'Chat', + // component: RoutesAlias.Chat, + // meta: { + // title: 'menus.template.chat', + // keepAlive: true + // } + // }, + // { + // path: 'calendar', + // name: 'Calendar', + // component: RoutesAlias.Calendar, + // meta: { + // title: 'menus.template.calendar', + // keepAlive: true + // } + // } + // ] + // }, ] diff --git a/src/router/routes/staticRoutes.ts b/src/router/routes/staticRoutes.ts index d3308c3..1789fe7 100644 --- a/src/router/routes/staticRoutes.ts +++ b/src/router/routes/staticRoutes.ts @@ -17,18 +17,6 @@ export const staticRoutes: AppRouteRecordRaw[] = [ component: () => import('@views/auth/login/index.vue'), meta: { title: 'menus.login.title', isHideTab: true, setTheme: true } }, - { - path: RoutesAlias.Register, - name: 'Register', - component: () => import('@views/auth/register/index.vue'), - meta: { title: 'menus.register.title', isHideTab: true, noLogin: true, setTheme: true } - }, - { - path: RoutesAlias.ForgetPassword, - name: 'ForgetPassword', - component: () => import('@views/auth/forget-password/index.vue'), - meta: { title: 'menus.forgetPassword.title', isHideTab: true, noLogin: true, setTheme: true } - }, { path: '/exception', component: Home, @@ -55,6 +43,35 @@ export const staticRoutes: AppRouteRecordRaw[] = [ } ] }, + { + path: '/result', + name: 'Result', + component: Home, + meta: { + title: 'menus.result.title', + icon: '' + }, + children: [ + { + path: 'success', + name: 'ResultSuccess', + component: () => import('@views/result/success/index.vue'), + meta: { + title: 'menus.result.success', + keepAlive: true + } + }, + { + path: 'fail', + name: 'ResultFail', + component: () => import('@views/result/fail/index.vue'), + meta: { + title: 'menus.result.fail', + keepAlive: true + } + } + ] + }, { path: '/outside', component: Home, diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index d428f9f..9d6dcd3 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -34,12 +34,12 @@ export enum RoutesAlias { Shop = '/shop-management/list', // 店铺列表 // 通用页面(店铺账号-企业账号) - EnterpriseCustomerAccounts = '/common/account-list', // 企业客户账号列表(通用) + EnterpriseCustomerAccounts = '/common/account-list', // 企业客户账号列表和店铺账号列表共用 // 账号管理 Account = '/account-management/account', // 账号列表 EnterpriseCustomer = '/account-management/enterprise-customer', // 企业客户管理 - EnterpriseCards = '/account-management/enterprise-cards', // 企业卡授权 + EnterpriseCards = '/asset-management/enterprise-cards', // 企业卡授权 EnterpriseDevices = '/asset-management/enterprise-devices', // 企业设备授权 // 资产管理 @@ -48,13 +48,17 @@ export enum RoutesAlias { DeviceList = '/asset-management/device-list', // 设备列表 ExchangeManagement = '/asset-management/exchange-management', // 换货管理 ExchangeDetail = '/asset-management/exchange-management/detail', // 换货单详情 - AuthorizationRecords = '/asset-management/authorization-records', // 授权记录 - AuthorizationRecordDetail = '/asset-management/authorization-records/detail', // 授权记录详情 - AssetAssign = '/asset-management/asset-assign', // 分配记录 - AssetAssignDetail = '/asset-management/asset-assign/detail', // 分配记录详情 - IotCardTask = '/asset-management/iot-card-task', // IoT卡任务 - DeviceTask = '/asset-management/device-task', // 设备任务 - TaskDetail = '/asset-management/task-detail', // 任务详情(IoT卡/设备任务详情) + + // 记录管理 + AuthorizationRecords = '/asset-management/record-management/authorization-records', // 授权记录 + AuthorizationRecordDetail = '/asset-management/record-management/authorization-records/detail', // 授权记录详情 + AssetAssign = '/asset-management/record-management/asset-assign', // 分配记录 + AssetAssignDetail = '/asset-management/record-management/asset-assign/detail', // 分配记录详情 + + // 任务管理 + IotCardTask = '/asset-management/task-management/iot-card-task', // IoT卡任务 + DeviceTask = '/asset-management/task-management/device-task', // 设备任务 + TaskDetail = '/asset-management/task-management/task-detail', // 任务详情(IoT卡/设备任务详情) // 订单管理 OrderList = '/order-management/order-list', // 订单列表 @@ -67,10 +71,10 @@ export enum RoutesAlias { RefundDetail = '/finance/refund/detail', // 退款详情 // 佣金管理 - WithdrawalApproval = '/finance/commission/withdrawal-approval', // 提现审批 - CommissionWithdrawalSettings = '/finance/commission/withdrawal-settings', // 提现配置 - MyCommission = '/finance/commission/my-commission', // 我的佣金 - AgentCommission = '/finance/commission/agent-commission', // 代理商佣金管理 + WithdrawalApproval = '/commission-management/withdrawal-approval', // 提现审批 + CommissionWithdrawalSettings = '/commission-management/withdrawal-settings', // 提现配置 + MyCommission = '/commission-management/my-commission', // 我的佣金 + AgentCommission = '/commission-management/agent-commission', // 代理商佣金管理 // 设置管理 WechatConfig = '/settings/wechat-config', // 微信支付配置 @@ -85,83 +89,18 @@ export enum RoutesAlias { ManualTrigger = '/polling-management/manual-trigger', // 手动触发 PollingMonitor = '/polling-management/monitor', // 轮询监控 - // 其他页面------------------------------------------------暂时不知道有没有用到不要动 - Register = '/auth/register', // 注册 - ForgetPassword = '/auth/forget-password', // 忘记密码 + // 公共组件 Success = '/result/success', // 成功 Fail = '/result/fail', // 失败 - IconList = '/widgets/icon-list', // 图标列表 - IconSelector = '/widgets/icon-selector', // 图标选择器 - ImageCrop = '/widgets/image-crop', // 图片裁剪 - Excel = '/widgets/excel', // Excel - Video = '/widgets/video', // 视频 - CountTo = '/widgets/count-to', // 计数 WangEditor = '/widgets/wang-editor', // 富文本编辑器 Watermark = '/widgets/watermark', // 水印 ContextMenu = '/widgets/context-menu', // 上下文菜单 Qrcode = '/widgets/qrcode', // 二维码 Drag = '/widgets/drag', // 拖拽 - TextScroll = '/widgets/text-scroll', // 文字滚动 - Fireworks = '/widgets/fireworks', // 礼花效果 Chat = '/template/chat', // 聊天 - Cards = '/template/cards', // 卡片 - Banners = '/template/banners', // 横幅 Charts = '/template/charts', // 图表 Map = '/template/map', // 地图 - Calendar = '/template/calendar', // 日历 - Pricing = '/template/pricing', // 定价 - ArticleList = '/article/list', // 文章列表 - ArticleDetail = '/article/detail', // 文章详情 - Comment = '/article/comment', // 评论 - ArticlePublish = '/article/publish', // 文章发布 - Server = '/safeguard/server', // 服务器 - ChangeLog = '/change/log', // 更新日志 - - // 物联网卡管理系统模块 - // 我的网卡 - CardList = '/card-management/card-list', // 网卡管理 - CardDetail = '/card-management/card-detail', // 网卡明细 - - CardAssign = '/card-management/card-assign', // 网卡分配 - CardShutdown = '/card-management/card-shutdown', // 停机管理 - MyCards = '/card-management/my-cards', // 我的网卡 - OfflineBatchRecharge = '/card-management/offline-batch-recharge', // 线下批量充值 - CardTransfer = '/card-management/card-transfer', // 网卡转接 - CardReplacement = '/card-management/card-replacement', // 换卡管理 - PackageGift = '/card-management/package-gift', // 套餐赠送 - CardChangeCard = '/card-management/card-change-card', // 换卡网卡 - - // other - PackageCreate = '/package-management/package-create', // 新建套餐 - PackageBatch = '/package-management/package-batch', // 批量管理 - PackageChange = '/package-management/package-change', // 套餐变更 - PackageCommission = '/package-management/package-commission', // 套餐佣金网卡 - - // PlatformAccount = '/account-management/platform-account', // 平台账号管理 - CustomerManagement = '/account-management/customer', // 客户管理 - CustomerRole = '/account-management/customer-role', // 客户角色 - AgentManagement = '/account-management/agent', // 代理商管理 - ShopAccount = '/account-management/shop-account', // 代理账号管理 - - CustomerCommission = '/account-management/customer-commission', // 客户账号佣金 - - // 产品管理 - SimCardManagement = '/product/sim-card', // 网卡产品管理 - SimCardAssign = '/product/sim-card-assign', // 号卡分配 - ShopAccounts = '/common/account-list', // 店铺账号列表(通用) - - // 资产管理 - DeviceDetail = '/asset-management/device-detail', // 设备详情 - CardReplacementRequest = '/asset-management/card-replacement-request', // 换卡申请 - - // 账户管理 - CustomerAccountList = '/finance/customer-account', // 客户账号 - MyAccount = '/finance/my-account', // 我的账户 - - // 设置管理 - PaymentMerchant = '/settings/payment-merchant', // 支付商户 - DeveloperApi = '/settings/developer-api', // 开发者API - CommissionTemplate = '/settings/commission-template' // 分佣模板 + Calendar = '/template/calendar' // 日历 } // 主页路由 diff --git a/src/types/api/agentRecharge.ts b/src/types/api/agentRecharge.ts index 36f8468..988257a 100644 --- a/src/types/api/agentRecharge.ts +++ b/src/types/api/agentRecharge.ts @@ -42,6 +42,7 @@ export interface AgentRechargeQueryParams { status?: AgentRechargeStatus start_date?: string end_date?: string + dateRange?: string[] | any // For date range picker in UI } // 代理充值订单列表响应 @@ -49,7 +50,8 @@ export interface AgentRechargeListResponse { page: number page_size: number total: number - list: AgentRecharge[] + items: AgentRecharge[] + list: AgentRecharge[] // 兼容旧字段 } // 创建代理充值订单请求 diff --git a/src/types/api/order.ts b/src/types/api/order.ts index 9700e84..58e4833 100644 --- a/src/types/api/order.ts +++ b/src/types/api/order.ts @@ -47,6 +47,7 @@ export interface Order { payment_status_text: string payment_method: OrderPaymentMethod total_amount: number // 订单总金额(分) + actual_paid_amount?: number // 实付金额(分) paid_at: string | null commission_status: OrderCommissionStatus commission_config_version: number @@ -54,6 +55,9 @@ export interface Order { iot_card_id: number | null asset_identifier?: string // 资产标识符快照(ICCID 或 VirtualNo) asset_type?: string // 资产类型:single_card 或 device + purchase_role?: string // 订单渠道 + purchase_remark?: string // 购买备注 + operator_name?: string // 操作者 items: OrderItem[] | null created_at: string updated_at: string @@ -67,6 +71,7 @@ export interface OrderQueryParams { order_type?: OrderType order_no?: string identifier?: string // 按资产标识符过滤 + purchase_role?: string // 订单渠道 start_time?: string end_time?: string dateRange?: string[] | any // For date range picker in UI @@ -83,7 +88,10 @@ export interface OrderListResponse { // 创建订单请求 export interface CreateOrderRequest { - identifier: string // 资产标识符(ICCID 或 VirtualNo) + order_type?: OrderType // 订单类型 + iot_card_id?: number | null // IoT卡ID + device_id?: number | null // 设备ID + identifier?: string // 资产标识符(ICCID 或 VirtualNo) package_ids: number[] // 套餐ID列表 payment_method: OrderPaymentMethod // 支付方式 } diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 8525fc0..3f1a047 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -85,8 +85,10 @@ declare module 'vue' { ElAlert: typeof import('element-plus/es')['ElAlert'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElButton: typeof import('element-plus/es')['ElButton'] + ElCalendar: typeof import('element-plus/es')['ElCalendar'] ElCard: typeof import('element-plus/es')['ElCard'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] @@ -109,6 +111,7 @@ declare module 'vue' { ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] + ElProgress: typeof import('element-plus/es')['ElProgress'] ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] @@ -120,6 +123,7 @@ declare module 'vue' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/account-management/agent/index.vue b/src/views/account-management/agent/index.vue deleted file mode 100644 index ac8af5f..0000000 --- a/src/views/account-management/agent/index.vue +++ /dev/null @@ -1,476 +0,0 @@ - - - - - diff --git a/src/views/account-management/customer-commission/index.vue b/src/views/account-management/customer-commission/index.vue deleted file mode 100644 index 0b706f3..0000000 --- a/src/views/account-management/customer-commission/index.vue +++ /dev/null @@ -1,457 +0,0 @@ - - - - - diff --git a/src/views/account-management/customer-role/index.vue b/src/views/account-management/customer-role/index.vue deleted file mode 100644 index 1ca9c74..0000000 --- a/src/views/account-management/customer-role/index.vue +++ /dev/null @@ -1,253 +0,0 @@ - - - - - diff --git a/src/views/account-management/customer/index.vue b/src/views/account-management/customer/index.vue deleted file mode 100644 index ea4bf38..0000000 --- a/src/views/account-management/customer/index.vue +++ /dev/null @@ -1,359 +0,0 @@ - - - - - diff --git a/src/views/account-management/enterprise-customer/index.vue b/src/views/account-management/enterprise-customer/index.vue index 3a1369a..322e724 100644 --- a/src/views/account-management/enterprise-customer/index.vue +++ b/src/views/account-management/enterprise-customer/index.vue @@ -207,6 +207,7 @@ - - diff --git a/src/views/account-management/shop-account/index.vue b/src/views/account-management/shop-account/index.vue deleted file mode 100644 index 90b6259..0000000 --- a/src/views/account-management/shop-account/index.vue +++ /dev/null @@ -1,599 +0,0 @@ - - - - - diff --git a/src/views/article/comment/index.vue b/src/views/article/comment/index.vue deleted file mode 100644 index ee98422..0000000 --- a/src/views/article/comment/index.vue +++ /dev/null @@ -1,269 +0,0 @@ - - - - - diff --git a/src/views/article/detail/index.vue b/src/views/article/detail/index.vue deleted file mode 100644 index bff8217..0000000 --- a/src/views/article/detail/index.vue +++ /dev/null @@ -1,122 +0,0 @@ - - - - - diff --git a/src/views/article/list/index.vue b/src/views/article/list/index.vue deleted file mode 100644 index ef6ebdb..0000000 --- a/src/views/article/list/index.vue +++ /dev/null @@ -1,380 +0,0 @@ - - - - - diff --git a/src/views/article/publish/index.vue b/src/views/article/publish/index.vue deleted file mode 100644 index 7186155..0000000 --- a/src/views/article/publish/index.vue +++ /dev/null @@ -1,411 +0,0 @@ - - - - - diff --git a/src/views/asset-management/asset-information/index.vue b/src/views/asset-management/asset-information/index.vue index 58207d2..c05e76a 100644 --- a/src/views/asset-management/asset-information/index.vue +++ b/src/views/asset-management/asset-information/index.vue @@ -3811,27 +3811,27 @@ .wallet-balance-cards { display: flex; flex-direction: column; - gap: 14px; + gap: 8px; .balance-card { display: flex; - gap: 18px; + gap: 10px; align-items: center; - padding: 18px 20px; + padding: 10px 14px; background: var(--el-fill-color-light); border: 1px solid var(--el-border-color-lighter); - border-radius: 8px; + border-radius: 4px; transition: all 0.3s ease; &:hover { border-color: var(--el-color-primary); - box-shadow: 0 2px 12px rgb(0 0 0 / 10%); - transform: translateX(4px); + box-shadow: 0 2px 6px rgb(0 0 0 / 6%); + transform: translateX(2px); } .balance-icon { flex-shrink: 0; - font-size: 32px; + font-size: 20px; line-height: 1; } @@ -3839,17 +3839,17 @@ display: flex; flex: 1; flex-direction: column; - gap: 6px; + gap: 2px; .balance-label { - font-size: 13px; + font-size: 11px; font-weight: 500; color: var(--el-text-color-secondary); } .balance-value { - font-size: 22px; - font-weight: 700; + font-size: 16px; + font-weight: 600; line-height: 1.2; color: var(--el-text-color-primary); } diff --git a/src/views/asset-management/card-replacement-request/index.vue b/src/views/asset-management/card-replacement-request/index.vue deleted file mode 100644 index 16bcf42..0000000 --- a/src/views/asset-management/card-replacement-request/index.vue +++ /dev/null @@ -1,542 +0,0 @@ - - - - - diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index f98bd37..f2b535f 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -439,46 +439,6 @@ - - - - - {{ currentOperatingImei }} - - - -
单位: KB/s
-
- - -
单位: KB/s
-
-
- -
-
@@ -604,6 +564,7 @@ - - diff --git a/src/views/account-management/enterprise-cards/index.vue b/src/views/asset-management/enterprise-cards/index.vue similarity index 100% rename from src/views/account-management/enterprise-cards/index.vue rename to src/views/asset-management/enterprise-cards/index.vue diff --git a/src/views/asset-management/exchange-management/index.vue b/src/views/asset-management/exchange-management/index.vue index 5ca3c3a..f44e62c 100644 --- a/src/views/asset-management/exchange-management/index.vue +++ b/src/views/asset-management/exchange-management/index.vue @@ -31,28 +31,16 @@ :pageSize="pagination.page_size" :total="pagination.total" :marginTop="10" + :actions="getActions" + :actionsWidth="120" @size-change="handleSizeChange" @current-change="handleCurrentChange" - @row-contextmenu="handleRowContextMenu" - @cell-mouse-enter="handleCellMouseEnter" - @cell-mouse-leave="handleCellMouseLeave" > - - - - - - >() const currentExchangeRow = ref(null) - const loading = ref(false) const shipDialogVisible = ref(false) const shipLoading = ref(false) @@ -621,17 +599,18 @@ router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`) } - // 右键菜单项 - const contextMenuItems = computed(() => { - const items: MenuItemType[] = [] - const status = currentExchangeRow.value?.status + // 获取操作按钮 + const getActions = (row: ExchangeResponse) => { + const actions: any[] = [] + const status = row.status // 状态1(待填写信息):取消换货 if (status === 1) { if (hasAuth('exchange:cancel')) { - items.push({ - key: 'cancel', - label: '取消换货' + actions.push({ + label: '取消换货', + handler: () => handleCancelExchange(row), + type: 'danger' }) } } @@ -639,15 +618,17 @@ // 状态2(待发货):发货、取消换货 if (status === 2) { if (hasAuth('exchange:ship')) { - items.push({ - key: 'ship', - label: '发货' + actions.push({ + label: '发货', + handler: () => handleShipExchange(row), + type: 'primary' }) } if (hasAuth('exchange:cancel')) { - items.push({ - key: 'cancel', - label: '取消换货' + actions.push({ + label: '取消换货', + handler: () => handleCancelExchange(row), + type: 'danger' }) } } @@ -655,9 +636,10 @@ // 状态3(已发货待确认):确认完成 if (status === 3) { if (hasAuth('exchange:complete')) { - items.push({ - key: 'complete', - label: '确认完成' + actions.push({ + label: '确认完成', + handler: () => handleCompleteExchange(row), + type: 'success' }) } } @@ -665,42 +647,15 @@ // 状态4(已完成):旧资产转新 if (status === 4) { if (hasAuth('exchange:renew')) { - items.push({ - key: 'renew', - label: '旧资产转新' + actions.push({ + label: '旧资产转新', + handler: () => handleRenewExchange(row), + type: 'warning' }) } } - return items - }) - - // 处理右键菜单 - const handleRowContextMenu = (row: ExchangeResponse, _column: any, event: MouseEvent) => { - event.preventDefault() - event.stopPropagation() - currentExchangeRow.value = row - contextMenuRef.value?.show(event) - } - - // 处理右键菜单选择 - const handleContextMenuSelect = (item: MenuItemType) => { - if (!currentExchangeRow.value) return - - switch (item.key) { - case 'cancel': - handleCancelExchange(currentExchangeRow.value) - break - case 'ship': - handleShipExchange(currentExchangeRow.value) - break - case 'complete': - handleCompleteExchange(currentExchangeRow.value) - break - case 'renew': - handleRenewExchange(currentExchangeRow.value) - break - } + return actions } // 取消换货 diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index 5e82f19..390f5ff 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -572,6 +572,7 @@ - - diff --git a/src/views/asset-management/asset-assign/detail.vue b/src/views/asset-management/record-management/asset-assign/detail.vue similarity index 100% rename from src/views/asset-management/asset-assign/detail.vue rename to src/views/asset-management/record-management/asset-assign/detail.vue diff --git a/src/views/asset-management/asset-assign/index.vue b/src/views/asset-management/record-management/asset-assign/index.vue similarity index 100% rename from src/views/asset-management/asset-assign/index.vue rename to src/views/asset-management/record-management/asset-assign/index.vue diff --git a/src/views/asset-management/authorization-records/detail.vue b/src/views/asset-management/record-management/authorization-records/detail.vue similarity index 100% rename from src/views/asset-management/authorization-records/detail.vue rename to src/views/asset-management/record-management/authorization-records/detail.vue diff --git a/src/views/asset-management/authorization-records/index.vue b/src/views/asset-management/record-management/authorization-records/index.vue similarity index 100% rename from src/views/asset-management/authorization-records/index.vue rename to src/views/asset-management/record-management/authorization-records/index.vue diff --git a/src/views/asset-management/device-task/index.vue b/src/views/asset-management/task-management/device-task/index.vue similarity index 88% rename from src/views/asset-management/device-task/index.vue rename to src/views/asset-management/task-management/device-task/index.vue index 3cc2ccb..00704fb 100644 --- a/src/views/asset-management/device-task/index.vue +++ b/src/views/asset-management/task-management/device-task/index.vue @@ -39,28 +39,15 @@ :pageSize="pagination.pageSize" :total="pagination.total" :marginTop="10" - :row-class-name="getRowClassName" + :actions="getActions" + :actionsWidth="120" @size-change="handleSizeChange" @current-change="handleCurrentChange" - @row-contextmenu="handleRowContextMenu" - @cell-mouse-enter="handleCellMouseEnter" - @cell-mouse-leave="handleCellMouseLeave" > - - - - - -
@@ -124,6 +111,7 @@ @@ -697,8 +656,4 @@ } } } - - :deep(.el-table__row.table-row-with-context-menu) { - cursor: pointer; - } diff --git a/src/views/asset-management/iot-card-task/index.vue b/src/views/asset-management/task-management/iot-card-task/index.vue similarity index 90% rename from src/views/asset-management/iot-card-task/index.vue rename to src/views/asset-management/task-management/iot-card-task/index.vue index 88ab665..ae6410a 100644 --- a/src/views/asset-management/iot-card-task/index.vue +++ b/src/views/asset-management/task-management/iot-card-task/index.vue @@ -39,28 +39,15 @@ :pageSize="pagination.pageSize" :total="pagination.total" :marginTop="10" - :row-class-name="getRowClassName" + :actions="getActions" + :actionsWidth="120" @size-change="handleSizeChange" @current-change="handleCurrentChange" - @row-contextmenu="handleRowContextMenu" - @cell-mouse-enter="handleCellMouseEnter" - @cell-mouse-leave="handleCellMouseLeave" > - - - - - - @@ -185,6 +172,7 @@ @@ -829,8 +788,4 @@ } } } - - :deep(.el-table__row.table-row-with-context-menu) { - cursor: pointer; - } diff --git a/src/views/asset-management/task-detail/index.vue b/src/views/asset-management/task-management/task-detail/index.vue similarity index 93% rename from src/views/asset-management/task-detail/index.vue rename to src/views/asset-management/task-management/task-detail/index.vue index df4f896..c131e48 100644 --- a/src/views/asset-management/task-detail/index.vue +++ b/src/views/asset-management/task-management/task-detail/index.vue @@ -47,16 +47,17 @@ - - diff --git a/src/views/auth/register/index.scss b/src/views/auth/register/index.scss deleted file mode 100644 index ad828e1..0000000 --- a/src/views/auth/register/index.scss +++ /dev/null @@ -1,29 +0,0 @@ -.register { - .right-wrap { - .login-wrap { - .form { - .el-form { - margin-top: 20px; - } - - .privacy-policy { - margin-top: 15px; - - :deep(.el-checkbox__label) { - color: #333 !important; - } - - a { - color: var(--main-color); - text-decoration: none; - } - } - - .register-btn { - width: 100%; - height: 40px !important; - } - } - } - } -} diff --git a/src/views/auth/register/index.vue b/src/views/auth/register/index.vue deleted file mode 100644 index b54a6cb..0000000 --- a/src/views/auth/register/index.vue +++ /dev/null @@ -1,173 +0,0 @@ - - - - - diff --git a/src/views/batch/card-change-notice/index.vue b/src/views/batch/card-change-notice/index.vue deleted file mode 100644 index a30c911..0000000 --- a/src/views/batch/card-change-notice/index.vue +++ /dev/null @@ -1,570 +0,0 @@ - - - - - diff --git a/src/views/batch/device-import/index.vue b/src/views/batch/device-import/index.vue deleted file mode 100644 index 92414f1..0000000 --- a/src/views/batch/device-import/index.vue +++ /dev/null @@ -1,694 +0,0 @@ - - - - - diff --git a/src/views/batch/sim-import/index.vue b/src/views/batch/sim-import/index.vue deleted file mode 100644 index a7ef8fb..0000000 --- a/src/views/batch/sim-import/index.vue +++ /dev/null @@ -1,386 +0,0 @@ - - - - - diff --git a/src/views/card-management/_template.vue b/src/views/card-management/_template.vue deleted file mode 100644 index 9054dbd..0000000 --- a/src/views/card-management/_template.vue +++ /dev/null @@ -1,383 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-assign/index.vue b/src/views/card-management/card-assign/index.vue deleted file mode 100644 index c993db8..0000000 --- a/src/views/card-management/card-assign/index.vue +++ /dev/null @@ -1,436 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-change-card/index.vue b/src/views/card-management/card-change-card/index.vue deleted file mode 100644 index 40cf264..0000000 --- a/src/views/card-management/card-change-card/index.vue +++ /dev/null @@ -1,549 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-detail/index.vue b/src/views/card-management/card-detail/index.vue deleted file mode 100644 index 11ac2c2..0000000 --- a/src/views/card-management/card-detail/index.vue +++ /dev/null @@ -1,1050 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-list/index.vue b/src/views/card-management/card-list/index.vue deleted file mode 100644 index 80eced4..0000000 --- a/src/views/card-management/card-list/index.vue +++ /dev/null @@ -1,607 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-replacement/index.vue b/src/views/card-management/card-replacement/index.vue deleted file mode 100644 index 4c9bc5f..0000000 --- a/src/views/card-management/card-replacement/index.vue +++ /dev/null @@ -1,695 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-shutdown/index.vue b/src/views/card-management/card-shutdown/index.vue deleted file mode 100644 index f8f5adc..0000000 --- a/src/views/card-management/card-shutdown/index.vue +++ /dev/null @@ -1,460 +0,0 @@ - - - - - diff --git a/src/views/card-management/card-transfer/index.vue b/src/views/card-management/card-transfer/index.vue deleted file mode 100644 index 2a8a5bd..0000000 --- a/src/views/card-management/card-transfer/index.vue +++ /dev/null @@ -1,739 +0,0 @@ - - - - - diff --git a/src/views/card-management/my-cards/index.vue b/src/views/card-management/my-cards/index.vue deleted file mode 100644 index 2874b34..0000000 --- a/src/views/card-management/my-cards/index.vue +++ /dev/null @@ -1,699 +0,0 @@ - - - - - diff --git a/src/views/card-management/offline-batch-recharge/index.vue b/src/views/card-management/offline-batch-recharge/index.vue deleted file mode 100644 index 0b4eb00..0000000 --- a/src/views/card-management/offline-batch-recharge/index.vue +++ /dev/null @@ -1,705 +0,0 @@ - - - - - diff --git a/src/views/card-management/package-gift/index.vue b/src/views/card-management/package-gift/index.vue deleted file mode 100644 index 0c47de7..0000000 --- a/src/views/card-management/package-gift/index.vue +++ /dev/null @@ -1,689 +0,0 @@ - - - - - diff --git a/src/views/change/log/index.vue b/src/views/change/log/index.vue deleted file mode 100644 index b264e19..0000000 --- a/src/views/change/log/index.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/src/views/finance/commission/agent-commission/index.vue b/src/views/commission-management/agent-commission/index.vue similarity index 92% rename from src/views/finance/commission/agent-commission/index.vue rename to src/views/commission-management/agent-commission/index.vue index d07d2bf..c8f005e 100644 --- a/src/views/finance/commission/agent-commission/index.vue +++ b/src/views/commission-management/agent-commission/index.vue @@ -46,28 +46,15 @@ :pageSize="pagination.pageSize" :total="pagination.total" :marginTop="10" - :row-class-name="getRowClassName" + :actions="getActions" + :actionsWidth="120" @size-change="handleSizeChange" @current-change="handleCurrentChange" - @row-contextmenu="handleRowContextMenu" - @cell-mouse-enter="handleCellMouseEnter" - @cell-mouse-leave="handleCellMouseLeave" > - - - - - - @@ -323,11 +310,7 @@ } from '@/types/api/commission' import { useCheckedColumns } from '@/composables/useCheckedColumns' import { useAuth } from '@/composables/useAuth' - import { useTableContextMenu } from '@/composables/useTableContextMenu' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' - import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue' - import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue' - import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' import { formatDateTime, formatMoney } from '@/utils/business/format' import { CommissionStatusMap, @@ -342,21 +325,10 @@ const route = useRoute() - // 使用表格右键菜单功能 - const { - showContextMenuHint, - hintPosition, - getRowClassName, - handleCellMouseEnter, - handleCellMouseLeave - } = useTableContextMenu() - // 主表格状态 const loading = ref(false) const tableRef = ref() const summaryList = ref([]) - const contextMenuRef = ref>() - const currentRow = ref(null) // 搜索表单 const searchForm = reactive({ @@ -604,6 +576,21 @@ loadCommissionRecords() } + // 获取操作按钮 + const getActions = (row: ShopCommissionSummaryItem) => { + const actions: any[] = [] + + if (hasAuth('agent_commission:detail')) { + actions.push({ + label: '查看详情', + handler: () => showDetail(row), + type: 'primary' + }) + } + + return actions + } + // 监听tab切换 watch(activeTab, (newTab) => { if (newTab === 'commission') { @@ -704,30 +691,6 @@ loadWithdrawalRecords() } - // 右键菜单项配置 - const contextMenuItems = computed((): MenuItemType[] => { - const items: MenuItemType[] = [] - - // 移除详情选项,通过点击店铺名称查看详情 - - return items - }) - - // 处理表格行右键菜单 - const handleRowContextMenu = (row: ShopCommissionSummaryItem, column: any, event: MouseEvent) => { - event.preventDefault() - event.stopPropagation() - currentRow.value = row - contextMenuRef.value?.show(event) - } - - // 处理右键菜单选择 - const handleContextMenuSelect = (item: MenuItemType) => { - if (!currentRow.value) return - - // 暂无其他菜单项 - } - // 处理佣金修正 const handleResolveCommission = ( row: ShopCommissionRecordItem, diff --git a/src/views/finance/commission/my-commission/index.vue b/src/views/commission-management/my-commission/index.vue similarity index 100% rename from src/views/finance/commission/my-commission/index.vue rename to src/views/commission-management/my-commission/index.vue diff --git a/src/views/finance/commission/withdrawal-approval/index.vue b/src/views/commission-management/withdrawal-approval/index.vue similarity index 100% rename from src/views/finance/commission/withdrawal-approval/index.vue rename to src/views/commission-management/withdrawal-approval/index.vue diff --git a/src/views/finance/commission/withdrawal-settings/index.vue b/src/views/commission-management/withdrawal-settings/index.vue similarity index 100% rename from src/views/finance/commission/withdrawal-settings/index.vue rename to src/views/commission-management/withdrawal-settings/index.vue diff --git a/src/views/dashboard/analysis/index.vue b/src/views/dashboard/analysis/index.vue index aa7634c..a7efdc1 100644 --- a/src/views/dashboard/analysis/index.vue +++ b/src/views/dashboard/analysis/index.vue @@ -1,5 +1,9 @@