提交已修改的代码
This commit is contained in:
1 parent
65d2d71469
commit
60cb1b09f8
32 files changed
+10359
-4786
No files matched your search
@@ -0,0 +1,59 @@
|
||||
{
|
||||
// Cursor AI 专用设置
|
||||
"cursor.ai.preview.enabled": false,
|
||||
"cursor.ai.showPreviewInEditor": false,
|
||||
"cursor.ai.autoPreview": false,
|
||||
"cursor.ai.previewDelay": 2000,
|
||||
|
||||
// 代码生成设置
|
||||
"cursor.ai.codeGeneration.showPreview": false,
|
||||
"cursor.ai.suggestions.preview": false,
|
||||
"cursor.ai.chat.floatingWindow": false,
|
||||
"cursor.ai.chat.sidePanel": true,
|
||||
|
||||
// 禁用多余的AI提示
|
||||
"cursor.ai.hover.enabled": false,
|
||||
"cursor.ai.inline.enabled": true,
|
||||
"cursor.ai.inline.preview": false,
|
||||
|
||||
// 优化编辑器性能
|
||||
"cursor.performance.enablePreviewCache": false,
|
||||
"cursor.performance.maxPreviewWindows": 1,
|
||||
|
||||
// 文件处理设置
|
||||
"cursor.files.autoPreview": false,
|
||||
"cursor.files.previewOnHover": false,
|
||||
"cursor.files.openPreviewByDefault": false,
|
||||
"cursor.files.enablePreview": false,
|
||||
|
||||
// UI 优化设置
|
||||
"cursor.ui.showWelcomeScreen": false,
|
||||
"cursor.ui.showTips": false,
|
||||
"cursor.ui.compactMode": true,
|
||||
|
||||
// 禁用Vue文件预览弹窗
|
||||
"cursor.vue.preview.enabled": false,
|
||||
"cursor.vue.autoPreview": false,
|
||||
"cursor.vue.showPreviewDialog": false,
|
||||
|
||||
// 禁用所有预览对话框
|
||||
"cursor.preview.showDialogs": false,
|
||||
"cursor.preview.autoShow": false,
|
||||
"cursor.preview.popupEnabled": false,
|
||||
|
||||
// 编辑器设置
|
||||
"editor.preview.enabled": false,
|
||||
"workbench.editor.enablePreview": false,
|
||||
"workbench.editor.enablePreviewFromQuickOpen": false,
|
||||
"workbench.editor.showTabs": "multiple",
|
||||
|
||||
// 禁用文件关联预览
|
||||
"files.associations": {
|
||||
"*.vue": "vue"
|
||||
},
|
||||
|
||||
// 代码编辑时的预览控制
|
||||
"cursor.ai.edit.showPreview": false,
|
||||
"cursor.ai.edit.autoPreview": false,
|
||||
"cursor.ai.generation.showPreview": false
|
||||
}
|
||||
@@ -22,7 +22,7 @@ server:
|
||||
|
||||
captcha:
|
||||
# 是否启用验证码校验
|
||||
enable: true
|
||||
enable: false
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
|
||||
+26
-5
@@ -62,12 +62,33 @@
|
||||
|
||||
### 🚀 计划中模块
|
||||
|
||||
#### 5. 后勤维修服务系统 (maintenance-service)
|
||||
#### 5. 物业报修管理系统 (property-maintenance)
|
||||
|
||||
- **状态**:⭕ 待开发
|
||||
- **计划开始**:2024 年 7 月
|
||||
- **技术栈**:Spring Boot + Vue3 + Warm-Flow
|
||||
- **核心功能**:报修申请、工单管理、维修派单、服务评价
|
||||
- **状态**:🔄 前端开发完成
|
||||
- **负责人**:待分配
|
||||
- **技术栈**:Spring Boot 3.4.6 + Vue3 + TypeScript + Warm-Flow + Element Plus
|
||||
- **核心功能**:报修申请管理、工单分派系统、维修进度跟踪、服务质量评价、数据统计分析
|
||||
- **API 数量**:15 个接口(规划中)
|
||||
- **文档完整度**:🟡 95%(前端代码完成)
|
||||
- **数据库表**:6 个核心表(已部署到研发环境)
|
||||
- **部署环境**:47.99.164.66:3306/hzdyjs_xg
|
||||
- **开发进度**:
|
||||
- ✅ 需求分析完成
|
||||
- ✅ 数据库设计完成
|
||||
- ✅ 数据库部署完成
|
||||
- ✅ **前端代码完成**(API 层 + 页面组件)
|
||||
- 🔄 后端接口开发中
|
||||
- ⏳ 功能联调测试
|
||||
- **前端代码**:
|
||||
- API 接口定义:17 个 TypeScript 类型,22 个接口方法
|
||||
- 页面组件:报修工单管理、维修类型管理
|
||||
- 技术特性:模拟数据、完整表单验证、状态流转、树形结构
|
||||
- **特殊说明**:
|
||||
- 系统管理功能:使用系统自带的用户、角色、权限管理,无需重新开发
|
||||
- 通知功能:本模块暂不包含通知功能,状态更新通过系统界面显示
|
||||
- 数据库环境:已在研发环境部署完成,支持后端开发和前端联调
|
||||
- 前端环境:使用模拟数据,支持独立开发调试
|
||||
- 下一步:开始后端 API 接口开发,对接前端代码
|
||||
|
||||
#### 6. 餐饮服务管理系统 (catering-management)
|
||||
|
||||
|
||||
@@ -0,0 +1,759 @@
|
||||
# 物业报修管理模块 - 总览
|
||||
|
||||
> 🎯 **模块用途**:学校物业维修服务数字化管理,提供从报修申请到维修完成的全流程管理
|
||||
|
||||
## 📋 模块基本信息
|
||||
|
||||
**模块名称**:property-maintenance / 物业报修管理模块
|
||||
**负责人**:[待分配]
|
||||
**开发状态**:⭕ 待开始
|
||||
**技术栈**:Spring Boot 3.4.6 + Vue3 + TypeScript + Warm-Flow + Element Plus
|
||||
**依赖模块**:用户管理模块、系统管理模块、文件服务模块
|
||||
**开发周期**:[待规划] - [待规划]
|
||||
|
||||
## 🎯 模块概述
|
||||
|
||||
### 业务价值
|
||||
|
||||
本模块旨在解决学校物业维修服务的数字化管理问题,实现报修申请、任务分派、维修跟踪、服务评价的全流程线上化处理,提高维修效率,提升用户满意度,建立可追踪的服务质量体系。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- **报修申请管理**:用户在线提交报修申请,支持图片、视频上传
|
||||
- **工单分派系统**:物业管理员智能分派工单给合适的维修人员
|
||||
- **维修进度跟踪**:实时跟踪维修进度,状态可视化展示
|
||||
- **服务质量评价**:用户对维修服务进行评分和反馈
|
||||
- **数据统计分析**:维修效率、用户满意度等多维度数据分析
|
||||
|
||||
### 技术特点
|
||||
|
||||
- **工作流驱动**:基于 Warm-Flow 引擎实现标准化业务流程
|
||||
- **多媒体支持**:支持图片、视频等多媒体文件上传和预览
|
||||
- **权限精细化**:基于角色的权限控制,确保数据安全
|
||||
- **移动端适配**:支持 PC 端和移动端操作,方便现场使用
|
||||
|
||||
## 📁 文档结构
|
||||
|
||||
```
|
||||
property-maintenance/
|
||||
├── README.md # 本文件 - 模块总览
|
||||
├── 需求分析/
|
||||
│ ├── 业务需求说明.md # 详细业务需求
|
||||
│ ├── 功能需求清单.md # 功能点清单
|
||||
│ ├── 非功能需求.md # 性能、安全要求
|
||||
│ └── 用户故事.md # 用户使用场景
|
||||
├── 技术设计/
|
||||
│ ├── 数据库设计.md # 表结构设计
|
||||
│ ├── API接口设计.md # 接口定义
|
||||
│ ├── 架构设计.md # 模块架构
|
||||
│ └── 技术选型.md # 技术决策说明
|
||||
├── 实现文档/
|
||||
│ ├── 后端实现说明.md # 后端代码结构
|
||||
│ ├── 前端实现说明.md # 前端代码结构
|
||||
│ ├── 关键业务逻辑.md # 核心算法逻辑
|
||||
│ └── 代码规范.md # 编码规范
|
||||
├── 测试文档/
|
||||
│ ├── 测试计划.md # 测试策略
|
||||
│ ├── 测试用例.md # 具体测试用例
|
||||
│ └── 测试报告.md # 测试结果
|
||||
└── 运维文档/
|
||||
├── 部署指南.md # 部署步骤
|
||||
├── 监控配置.md # 监控指标
|
||||
└── 故障处理.md # 常见问题
|
||||
```
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
### 模块架构图
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "前端展示层"
|
||||
A1[报修申请页面<br/>用户提交报修]
|
||||
A2[工单管理页面<br/>管理员派单]
|
||||
A3[维修工作台<br/>维修员操作]
|
||||
A4[统计分析页面<br/>数据可视化]
|
||||
end
|
||||
|
||||
subgraph "接口服务层"
|
||||
B1[报修单Controller<br/>CRUD操作]
|
||||
B2[工作流Controller<br/>流程控制]
|
||||
B3[文件服务Controller<br/>文件上传]
|
||||
B4[统计分析Controller<br/>数据统计]
|
||||
end
|
||||
|
||||
subgraph "业务逻辑层"
|
||||
C1[报修单Service<br/>业务逻辑处理]
|
||||
C2[工作流Service<br/>流程管理]
|
||||
C3[通知Service<br/>消息通知]
|
||||
C4[统计Service<br/>数据分析]
|
||||
end
|
||||
|
||||
subgraph "数据持久层"
|
||||
D1[报修单Mapper<br/>数据访问]
|
||||
D2[工作流Mapper<br/>流程数据]
|
||||
D3[文件Mapper<br/>文件信息]
|
||||
D4[MySQL数据库<br/>数据存储]
|
||||
end
|
||||
|
||||
subgraph "外部服务"
|
||||
E1[文件存储服务<br/>OSS/本地存储]
|
||||
E2[消息通知服务<br/>短信/邮件]
|
||||
E3[用户权限服务<br/>Sa-Token]
|
||||
end
|
||||
|
||||
A1 --> B1
|
||||
A2 --> B1
|
||||
A2 --> B2
|
||||
A3 --> B1
|
||||
A3 --> B2
|
||||
A4 --> B4
|
||||
|
||||
B1 --> C1
|
||||
B2 --> C2
|
||||
B3 --> C1
|
||||
B4 --> C4
|
||||
|
||||
C1 --> D1
|
||||
C2 --> D2
|
||||
C3 --> D1
|
||||
C4 --> D1
|
||||
|
||||
D1 --> D4
|
||||
D2 --> D4
|
||||
D3 --> D4
|
||||
|
||||
C1 --> E1
|
||||
C3 --> E2
|
||||
B1 --> E3
|
||||
```
|
||||
|
||||
### 数据流向
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant F as 前端
|
||||
participant C as Controller
|
||||
participant S as Service
|
||||
participant W as 工作流引擎
|
||||
participant M as Mapper
|
||||
participant D as Database
|
||||
|
||||
U->>F: 提交报修申请
|
||||
F->>C: POST /api/maintenance/repair-order
|
||||
C->>S: 创建报修单
|
||||
S->>W: 启动维修流程
|
||||
W->>M: 保存流程数据
|
||||
M->>D: 存储到数据库
|
||||
D-->>M: 返回结果
|
||||
M-->>S: 返回数据
|
||||
S-->>C: 返回报修单ID
|
||||
C-->>F: JSON响应
|
||||
F-->>U: 显示提交成功
|
||||
|
||||
Note over S,W: 自动通知物业管理员
|
||||
```
|
||||
|
||||
## 📊 核心功能模块
|
||||
|
||||
### 1. 报修申请管理
|
||||
|
||||
#### 业务描述
|
||||
|
||||
用户通过 Web 端或移动端在线提交报修申请,填写报修地点、问题描述、紧急程度等信息,并可上传现场图片或视频作为辅助说明。系统自动生成唯一的报修单号,并启动维修工作流程。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**:Vue3 + Element Plus + 文件上传组件
|
||||
- **后端实现**:Spring Boot + MyBatis-Plus + OSS 文件服务
|
||||
- **数据存储**:MySQL 存储报修单信息,OSS 存储多媒体文件
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 用户登录认证 → 2. 填写报修信息 → 3. 上传附件文件 → 4. 提交申请 → 5. 系统生成工单 → 6. 进入分派流程
|
||||
|
||||
### 🎯 工单分派系统
|
||||
|
||||
#### 业务描述
|
||||
|
||||
物业管理员接收用户报修申请后,根据维修类型、紧急程度、维修员专业技能和工作负荷,智能推荐并分派工单给合适的维修员。支持批量分派和分派撤回功能。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**:工单列表、分派对话框、维修员负荷展示
|
||||
- **后端实现**:工单分配算法、负荷均衡计算
|
||||
- **数据存储**:工单分派记录、维修员工作负荷统计
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 查看待分派工单 → 2. 选择维修员 → 3. 确认分派 → 4. 更新工单状态 → 5. 维修员接收工单
|
||||
|
||||
### 🔧 维修进度跟踪
|
||||
|
||||
#### 业务描述
|
||||
|
||||
维修员接收工单后,可实时更新维修进度状态(接单、到达现场、开始维修、完成维修等),用户可通过系统实时查看维修进度。支持维修过程照片上传和问题上报。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**:进度时间轴展示、状态更新界面
|
||||
- **后端实现**:状态机管理、进度同步机制
|
||||
- **数据存储**:维修进度记录、状态变更日志
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 维修员接收工单 → 2. 更新到达状态 → 3. 开始维修 → 4. 上传进度照片 → 5. 完成维修 → 6. 提交报告
|
||||
|
||||
### ⭐ 服务质量评价
|
||||
|
||||
#### 业务描述
|
||||
|
||||
维修完成后,用户对维修服务进行评分(1-5 星)和文字反馈,包括维修质量、服务态度、响应速度等维度。评价结果作为维修员绩效考核和服务改进的重要依据。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**:星级评分组件、多维度评价表单
|
||||
- **后端实现**:评价数据统计、满意度计算
|
||||
- **数据存储**:评价记录、统计分析数据
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 维修完成确认 → 2. 用户收到评价邀请 → 3. 多维度评分 → 4. 提交评价 → 5. 工单归档
|
||||
|
||||
## 🔌 API 接口概览
|
||||
|
||||
### 接口分类
|
||||
|
||||
#### 报修单管理接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| -------------- | -------- | ----------------------------------------- | ---------------------- |
|
||||
| 创建报修单 | POST | /api/maintenance/repair-order | 用户提交报修申请 |
|
||||
| 查询报修单列表 | GET | /api/maintenance/repair-order/list | 分页查询报修单列表 |
|
||||
| 查询报修单详情 | GET | /api/maintenance/repair-order/{id} | 根据 ID 查询报修单详情 |
|
||||
| 更新报修单状态 | PUT | /api/maintenance/repair-order/{id}/status | 更新报修单处理状态 |
|
||||
| 分派工单 | POST | /api/maintenance/repair-order/{id}/assign | 管理员分派工单给维修员 |
|
||||
| 完成维修 | POST | /api/maintenance/repair-order/{id}/finish | 维修员提交维修完成结果 |
|
||||
|
||||
#### 文件管理接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| ------------ | -------- | ---------------------------- | ------------------ |
|
||||
| 上传文件 | POST | /api/maintenance/file/upload | 上传图片、视频文件 |
|
||||
| 删除文件 | DELETE | /api/maintenance/file/{id} | 删除已上传文件 |
|
||||
| 获取文件信息 | GET | /api/maintenance/file/{id} | 获取文件详细信息 |
|
||||
|
||||
#### 评价管理接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| ------------ | -------- | -------------------------------------- | ------------------ |
|
||||
| 提交评价 | POST | /api/maintenance/evaluation | 用户提交服务评价 |
|
||||
| 查询评价列表 | GET | /api/maintenance/evaluation/list | 查询评价记录列表 |
|
||||
| 统计满意度 | GET | /api/maintenance/evaluation/statistics | 统计服务满意度数据 |
|
||||
|
||||
#### 统计分析接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| ---------------- | -------- | ---------------------------------------- | -------------------- |
|
||||
| 维修统计概览 | GET | /api/maintenance/statistics/overview | 获取维修业务概览数据 |
|
||||
| 维修员工作量统计 | GET | /api/maintenance/statistics/workload | 统计维修员工作负荷 |
|
||||
| 维修类型分析 | GET | /api/maintenance/statistics/category | 按维修类型统计分析 |
|
||||
| 满意度趋势分析 | GET | /api/maintenance/statistics/satisfaction | 用户满意度趋势分析 |
|
||||
|
||||
### 接口规范
|
||||
|
||||
- **请求格式**:JSON
|
||||
- **响应格式**:统一响应体结构
|
||||
- **认证方式**:JWT Token (Sa-Token)
|
||||
- **权限控制**:基于角色的权限验证
|
||||
- **文件上传**:支持多文件上传,限制文件大小和类型
|
||||
|
||||
## 🗄️ 数据模型
|
||||
|
||||
### 核心实体
|
||||
|
||||
#### 报修单表 (pm_repair_order)
|
||||
|
||||
| 字段名 | 类型 | 长度 | 非空 | 默认值 | 说明 |
|
||||
| ------------------ | ------------ | ---- | ---- | ------- | ------------- |
|
||||
| id | BIGINT | - | Y | - | 主键 ID |
|
||||
| order_no | VARCHAR | 32 | Y | - | 报修单号 |
|
||||
| title | VARCHAR | 200 | Y | - | 报修标题 |
|
||||
| description | TEXT | - | Y | - | 问题描述 |
|
||||
| location | VARCHAR | 500 | Y | - | 报修地点 |
|
||||
| contact_name | VARCHAR | 50 | Y | - | 联系人姓名 |
|
||||
| contact_phone | VARCHAR | 20 | Y | - | 联系电话 |
|
||||
| urgency_level | TINYINT | - | Y | 1 | 紧急程度(1-5) |
|
||||
| expected_time | DATETIME | - | N | - | 期望完成时间 |
|
||||
| repair_type | VARCHAR | 50 | Y | - | 维修类型 |
|
||||
| status | VARCHAR | 20 | Y | PENDING | 状态 |
|
||||
| assignee_id | BIGINT | - | N | - | 分派维修员 ID |
|
||||
| assign_time | DATETIME | - | N | - | 分派时间 |
|
||||
| start_time | DATETIME | - | N | - | 开始维修时间 |
|
||||
| finish_time | DATETIME | - | N | - | 完成维修时间 |
|
||||
| repair_result | TEXT | - | N | - | 维修结果说明 |
|
||||
| evaluation_score | DECIMAL(3,2) | - | N | - | 评价分数 |
|
||||
| evaluation_comment | TEXT | - | N | - | 评价内容 |
|
||||
| tenant_id | VARCHAR | 20 | Y | 000000 | 租户 ID |
|
||||
| create_dept | BIGINT | - | Y | - | 创建部门 |
|
||||
| create_by | BIGINT | - | Y | - | 创建者 |
|
||||
| create_time | DATETIME | - | Y | - | 创建时间 |
|
||||
| update_by | BIGINT | - | Y | - | 更新者 |
|
||||
| update_time | DATETIME | - | Y | - | 更新时间 |
|
||||
| deleted | TINYINT | - | Y | 0 | 删除标记 |
|
||||
|
||||
#### 报修附件表 (pm_repair_attachment)
|
||||
|
||||
| 字段名 | 类型 | 长度 | 非空 | 默认值 | 说明 |
|
||||
| --------------- | -------- | ---- | ---- | ------ | --------- |
|
||||
| id | BIGINT | - | Y | - | 主键 ID |
|
||||
| repair_order_id | BIGINT | - | Y | - | 报修单 ID |
|
||||
| file_name | VARCHAR | 255 | Y | - | 文件名 |
|
||||
| file_path | VARCHAR | 500 | Y | - | 文件路径 |
|
||||
| file_size | BIGINT | - | Y | - | 文件大小 |
|
||||
| file_type | VARCHAR | 50 | Y | - | 文件类型 |
|
||||
| attachment_type | VARCHAR | 20 | Y | - | 附件类型 |
|
||||
| tenant_id | VARCHAR | 20 | Y | 000000 | 租户 ID |
|
||||
| create_by | BIGINT | - | Y | - | 创建者 |
|
||||
| create_time | DATETIME | - | Y | - | 创建时间 |
|
||||
| update_by | BIGINT | - | Y | - | 更新者 |
|
||||
| update_time | DATETIME | - | Y | - | 更新时间 |
|
||||
| deleted | TINYINT | - | Y | 0 | 删除标记 |
|
||||
|
||||
#### 维修进度记录表 (pm_repair_progress)
|
||||
|
||||
| 字段名 | 类型 | 长度 | 非空 | 默认值 | 说明 |
|
||||
| --------------- | -------- | ---- | ---- | ------ | ------------ |
|
||||
| id | BIGINT | - | Y | - | 主键 ID |
|
||||
| repair_order_id | BIGINT | - | Y | - | 报修单 ID |
|
||||
| status | VARCHAR | 20 | Y | - | 进度状态 |
|
||||
| description | TEXT | - | N | - | 进度描述 |
|
||||
| operator_id | BIGINT | - | Y | - | 操作人 ID |
|
||||
| operator_name | VARCHAR | 50 | Y | - | 操作人姓名 |
|
||||
| progress_time | DATETIME | - | Y | - | 进度更新时间 |
|
||||
| tenant_id | VARCHAR | 20 | Y | 000000 | 租户 ID |
|
||||
| create_by | BIGINT | - | Y | - | 创建者 |
|
||||
| create_time | DATETIME | - | Y | - | 创建时间 |
|
||||
| deleted | TINYINT | - | Y | 0 | 删除标记 |
|
||||
|
||||
### 实体关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
PM_REPAIR_ORDER ||--o{ PM_REPAIR_ATTACHMENT : "一对多"
|
||||
PM_REPAIR_ORDER ||--o{ PM_REPAIR_PROGRESS : "一对多"
|
||||
PM_REPAIR_ORDER }o--|| SYS_USER : "报修人"
|
||||
PM_REPAIR_ORDER }o--|| SYS_USER : "维修员"
|
||||
|
||||
PM_REPAIR_ORDER {
|
||||
bigint id PK
|
||||
varchar order_no UK
|
||||
varchar title
|
||||
text description
|
||||
varchar location
|
||||
varchar status
|
||||
bigint assignee_id FK
|
||||
datetime create_time
|
||||
}
|
||||
|
||||
PM_REPAIR_ATTACHMENT {
|
||||
bigint id PK
|
||||
bigint repair_order_id FK
|
||||
varchar file_name
|
||||
varchar file_path
|
||||
varchar file_type
|
||||
}
|
||||
|
||||
PM_REPAIR_PROGRESS {
|
||||
bigint id PK
|
||||
bigint repair_order_id FK
|
||||
varchar status
|
||||
text description
|
||||
bigint operator_id FK
|
||||
datetime progress_time
|
||||
}
|
||||
|
||||
SYS_USER {
|
||||
bigint id PK
|
||||
varchar username
|
||||
varchar nickname
|
||||
varchar phone
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 业务流程
|
||||
|
||||
### 主要业务流程
|
||||
|
||||
#### 报修申请流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[用户发起报修] --> B[填写报修信息]
|
||||
B --> C{信息完整?}
|
||||
C -->|否| B
|
||||
C -->|是| D[上传附件]
|
||||
D --> E[提交申请]
|
||||
E --> F[生成报修单号]
|
||||
F --> G[通知物业管理员]
|
||||
G --> H[报修单进入待分派状态]
|
||||
```
|
||||
|
||||
**流程说明**:
|
||||
|
||||
1. 用户在系统中填写报修基本信息,包括报修地点、问题描述等
|
||||
2. 系统验证信息完整性,要求用户补充必要信息
|
||||
3. 用户可选择上传现场图片或视频作为补充说明
|
||||
4. 提交后系统自动生成唯一报修单号
|
||||
5. 自动通知物业管理员有新的报修申请需要处理
|
||||
|
||||
#### 工单分派流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[管理员查看待分派工单] --> B[选择报修单]
|
||||
B --> C[查看报修详情]
|
||||
C --> D{是否需要分派?}
|
||||
D -->|否| E[标记为无需维修]
|
||||
D -->|是| F[选择维修员]
|
||||
F --> G[确认分派]
|
||||
G --> H[更新工单状态]
|
||||
H --> I[通知维修员]
|
||||
I --> J[通知报修用户]
|
||||
```
|
||||
|
||||
**流程说明**:
|
||||
|
||||
1. 物业管理员登录系统查看所有待分派的报修工单
|
||||
2. 点击查看具体报修单的详细信息和附件
|
||||
3. 根据维修类型和维修员专长选择合适的维修人员
|
||||
4. 确认分派后系统更新工单状态为"已分派"
|
||||
5. 自动通知维修员和报修用户工单分派情况
|
||||
|
||||
#### 维修处理流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[维修员接收工单] --> B[确认接单]
|
||||
B --> C[到达现场]
|
||||
C --> D[开始维修]
|
||||
D --> E[更新维修进度]
|
||||
E --> F{维修是否完成?}
|
||||
F -->|否| D
|
||||
F -->|是| G[提交维修报告]
|
||||
G --> H[上传完成照片]
|
||||
H --> I[更新工单状态]
|
||||
I --> J[通知用户确认]
|
||||
```
|
||||
|
||||
**流程说明**:
|
||||
|
||||
1. 维修员通过移动端或 PC 端接收分派的工单
|
||||
2. 确认接单并更新状态为"进行中"
|
||||
3. 到达维修现场,开始实际维修工作
|
||||
4. 在维修过程中可随时更新进度状态
|
||||
5. 维修完成后提交维修报告和现场照片
|
||||
6. 系统自动通知报修用户确认维修结果
|
||||
|
||||
## 🎨 前端设计
|
||||
|
||||
### 页面结构
|
||||
|
||||
#### 报修申请页面 (用户端)
|
||||
|
||||
- **功能**:用户提交新的报修申请
|
||||
- **组件**:表单组件、文件上传组件、地点选择器、日期选择器
|
||||
- **交互**:填写表单 → 上传附件 → 提交申请 → 查看申请结果
|
||||
- **数据**:报修类型字典、地点选择数据、用户基本信息
|
||||
|
||||
#### 工单管理页面 (管理员端)
|
||||
|
||||
- **功能**:查看、分派和管理所有报修工单
|
||||
- **组件**:数据表格、筛选器、分派对话框、详情抽屉
|
||||
- **交互**:筛选工单 → 查看详情 → 选择维修员 → 确认分派
|
||||
- **数据**:工单列表数据、维修员列表、工单统计数据
|
||||
|
||||
#### 维修工作台 (维修员端)
|
||||
|
||||
- **功能**:维修员查看和处理分配给自己的工单
|
||||
- **组件**:工单卡片、进度更新、文件上传、状态切换
|
||||
- **交互**:接收工单 → 更新进度 → 上传照片 → 完成维修
|
||||
- **数据**:个人工单列表、进度记录、维修结果数据
|
||||
|
||||
#### 统计分析页面 (管理员端)
|
||||
|
||||
- **功能**:查看维修业务的各类统计分析数据
|
||||
- **组件**:图表组件、数据卡片、日期筛选器、导出功能
|
||||
- **交互**:选择时间范围 → 查看统计图表 → 导出报表
|
||||
- **数据**:统计图表数据、趋势分析数据、满意度数据
|
||||
|
||||
### 组件设计
|
||||
|
||||
#### 报修申请表单组件 (RepairOrderForm)
|
||||
|
||||
```typescript
|
||||
// 组件属性接口
|
||||
interface RepairOrderFormProps {
|
||||
modelValue: RepairOrderForm;
|
||||
readonly?: boolean;
|
||||
showAttachment?: boolean;
|
||||
}
|
||||
|
||||
// 组件事件接口
|
||||
interface RepairOrderFormEmits {
|
||||
'update:modelValue': [value: RepairOrderForm];
|
||||
submit: [formData: RepairOrderForm];
|
||||
cancel: [];
|
||||
}
|
||||
```
|
||||
|
||||
#### 工单分派对话框 (AssignDialog)
|
||||
|
||||
```typescript
|
||||
// 组件属性接口
|
||||
interface AssignDialogProps {
|
||||
visible: boolean;
|
||||
repairOrder: RepairOrderVo;
|
||||
maintainerList: MaintainerVo[];
|
||||
}
|
||||
|
||||
// 组件事件接口
|
||||
interface AssignDialogEmits {
|
||||
'update:visible': [visible: boolean];
|
||||
confirm: [assignData: AssignFormData];
|
||||
cancel: [];
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 开发规范
|
||||
|
||||
### 代码结构
|
||||
|
||||
#### 后端代码结构
|
||||
|
||||
```
|
||||
src/main/java/org/dromara/modules/maintenance/
|
||||
├── controller/ # 控制器层
|
||||
│ ├── RepairOrderController.java
|
||||
│ ├── RepairAttachmentController.java
|
||||
│ ├── RepairProgressController.java
|
||||
│ └── MaintenanceStatisticsController.java
|
||||
├── domain/ # 领域对象层
|
||||
│ ├── bo/ # 业务对象
|
||||
│ │ ├── RepairOrderBo.java
|
||||
│ │ ├── RepairOrderQueryBo.java
|
||||
│ │ └── AssignRepairOrderBo.java
|
||||
│ └── vo/ # 视图对象
|
||||
│ ├── RepairOrderVo.java
|
||||
│ ├── RepairOrderDetailVo.java
|
||||
│ └── MaintenanceStatisticsVo.java
|
||||
├── mapper/ # 数据访问层
|
||||
│ ├── RepairOrderMapper.java
|
||||
│ ├── RepairOrderMapper.xml
|
||||
│ ├── RepairAttachmentMapper.java
|
||||
│ └── RepairProgressMapper.java
|
||||
├── service/ # 业务服务层
|
||||
│ ├── IRepairOrderService.java
|
||||
│ ├── IRepairAttachmentService.java
|
||||
│ ├── IMaintenanceStatisticsService.java
|
||||
│ └── impl/
|
||||
│ ├── RepairOrderServiceImpl.java
|
||||
│ ├── RepairAttachmentServiceImpl.java
|
||||
│ └── MaintenanceStatisticsServiceImpl.java
|
||||
└── utils/ # 工具类
|
||||
└── MaintenanceUtils.java
|
||||
```
|
||||
|
||||
#### 前端代码结构
|
||||
|
||||
```
|
||||
src/views/maintenance/
|
||||
├── repair-order/ # 报修单管理
|
||||
│ ├── index.vue # 报修单列表页
|
||||
│ ├── form.vue # 报修申请表单
|
||||
│ └── detail.vue # 报修单详情
|
||||
├── work-order/ # 工单管理
|
||||
│ ├── index.vue # 工单管理页面
|
||||
│ └── assign.vue # 工单分派页面
|
||||
├── workbench/ # 维修工作台
|
||||
│ ├── index.vue # 维修员工作台
|
||||
│ └── progress.vue # 进度更新页面
|
||||
├── statistics/ # 统计分析
|
||||
│ └── index.vue # 统计分析页面
|
||||
├── components/ # 页面组件
|
||||
│ ├── RepairOrderForm.vue
|
||||
│ ├── AssignDialog.vue
|
||||
│ ├── ProgressTimeline.vue
|
||||
│ └── AttachmentUpload.vue
|
||||
├── types/ # 类型定义
|
||||
│ └── index.ts
|
||||
└── api/ # API接口
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### 命名规范
|
||||
|
||||
#### 后端命名规范
|
||||
|
||||
- **实体类**:RepairOrder、RepairAttachment、RepairProgress
|
||||
- **服务类**:RepairOrderService、MaintenanceStatisticsService
|
||||
- **控制器**:RepairOrderController、MaintenanceStatisticsController
|
||||
- **DTO 类**:RepairOrderBo、RepairOrderVo、AssignRepairOrderBo
|
||||
|
||||
#### 前端命名规范
|
||||
|
||||
- **页面组件**:RepairOrderList、WorkOrderManage、MaintenanceWorkbench
|
||||
- **业务组件**:RepairOrderForm、AssignDialog、ProgressTimeline
|
||||
- **API 方法**:getRepairOrderList、createRepairOrder、assignRepairOrder
|
||||
- **类型接口**:RepairOrderForm、RepairOrderVo、AssignFormData
|
||||
|
||||
#### 数据库命名规范
|
||||
|
||||
- **表名**:pm_repair_order、pm_repair_attachment、pm_repair_progress
|
||||
- **字段名**:repair_order_id、contact_name、urgency_level
|
||||
- **索引名**:idx_repair_order_status、idx_repair_order_assignee
|
||||
|
||||
## 🧪 测试策略
|
||||
|
||||
### 测试分类
|
||||
|
||||
#### 单元测试
|
||||
|
||||
- **覆盖范围**:Service 层业务逻辑、工具类方法
|
||||
- **测试框架**:JUnit 5 + Mockito + Spring Boot Test
|
||||
- **覆盖率要求**:> 85%
|
||||
- **重点测试**:报修单状态流转、工单分派逻辑、统计计算算法
|
||||
|
||||
#### 集成测试
|
||||
|
||||
- **覆盖范围**:Controller 层 API 接口、数据库操作
|
||||
- **测试框架**:Spring Boot Test + TestContainers
|
||||
- **测试数据**:独立测试数据库
|
||||
- **重点测试**:完整业务流程、文件上传下载、权限控制
|
||||
|
||||
#### 前端测试
|
||||
|
||||
- **覆盖范围**:组件功能、用户交互、表单验证
|
||||
- **测试框架**:Vue Test Utils + Jest + Cypress
|
||||
- **测试类型**:组件单元测试、页面集成测试、E2E 测试
|
||||
|
||||
### 测试用例设计
|
||||
|
||||
#### 功能测试用例
|
||||
|
||||
- **报修申请测试**:正常提交、信息验证、文件上传
|
||||
- **工单分派测试**:分派流程、权限验证、状态更新
|
||||
- **维修流程测试**:接单确认、进度更新、完成提交
|
||||
- **评价功能测试**:评分提交、满意度统计、数据展示
|
||||
|
||||
#### 性能测试用例
|
||||
|
||||
- **并发提交测试**:模拟多用户同时提交报修申请
|
||||
- **文件上传测试**:大文件上传、多文件并发上传
|
||||
- **数据查询测试**:大数据量下的列表查询性能
|
||||
- **统计分析测试**:复杂统计查询的响应时间
|
||||
|
||||
## 🚀 部署运维
|
||||
|
||||
### 部署环境
|
||||
|
||||
#### 开发环境
|
||||
|
||||
- **部署方式**:本地 Docker 容器
|
||||
- **数据库**:MySQL 8.0
|
||||
- **缓存**:Redis 7.0
|
||||
- **文件存储**:本地文件系统
|
||||
- **配置**:application-dev.yml
|
||||
|
||||
#### 测试环境
|
||||
|
||||
- **部署方式**:Docker Compose
|
||||
- **数据库**:MySQL 8.0(测试库)
|
||||
- **缓存**:Redis 7.0
|
||||
- **文件存储**:MinIO 或 阿里云 OSS
|
||||
- **配置**:application-test.yml
|
||||
|
||||
#### 生产环境
|
||||
|
||||
- **部署方式**:Kubernetes
|
||||
- **数据库**:MySQL 8.0(主从集群)
|
||||
- **缓存**:Redis 7.0(集群模式)
|
||||
- **文件存储**:阿里云 OSS
|
||||
- **负载均衡**:Nginx + Ingress
|
||||
- **配置**:application-prod.yml
|
||||
|
||||
### 监控指标
|
||||
|
||||
#### 业务指标
|
||||
|
||||
- **报修单提交量**:每日、每周、每月报修申请数量
|
||||
- **处理效率**:平均处理时长、及时完成率
|
||||
- **用户满意度**:平均评分、满意度趋势
|
||||
- **维修员工作量**:工单分配数量、完成效率
|
||||
|
||||
#### 技术指标
|
||||
|
||||
- **接口响应时间**:各 API 接口的平均响应时间 < 500ms
|
||||
- **文件上传成功率**:文件上传成功率 > 99%
|
||||
- **系统可用性**:服务可用性 > 99.9%
|
||||
- **错误率**:系统错误率 < 0.1%
|
||||
|
||||
## 📅 开发计划
|
||||
|
||||
### 开发阶段
|
||||
|
||||
#### 第一阶段:基础框架(预计 2 周)
|
||||
|
||||
- [ ] 数据库表结构设计和创建
|
||||
- [ ] 基础实体类和 Mapper 开发
|
||||
- [ ] 基础 CRUD 接口开发
|
||||
- [ ] 前端页面框架搭建
|
||||
- [ ] 单元测试编写
|
||||
|
||||
#### 第二阶段:核心功能(预计 3 周)
|
||||
|
||||
- [ ] 报修申请功能开发
|
||||
- [ ] 文件上传功能开发
|
||||
- [ ] 工单分派功能开发
|
||||
- [ ] 维修流程管理开发
|
||||
- [ ] 集成测试编写
|
||||
|
||||
#### 第三阶段:高级功能(预计 2 周)
|
||||
|
||||
- [ ] 服务评价功能开发
|
||||
- [ ] 统计分析功能开发
|
||||
- [ ] 移动端适配优化
|
||||
- [ ] 性能优化
|
||||
|
||||
#### 第四阶段:测试部署(预计 1 周)
|
||||
|
||||
- [ ] 完整功能测试
|
||||
- [ ] 性能压力测试
|
||||
- [ ] 用户验收测试
|
||||
- [ ] 生产环境部署
|
||||
- [ ] 上线运维监控
|
||||
|
||||
### 里程碑
|
||||
|
||||
- **里程碑 1**:基础框架完成,能够创建和查询报修单
|
||||
- **里程碑 2**:核心流程打通,完整的报修-分派-维修流程
|
||||
- **里程碑 3**:功能完善,包括评价和统计功能
|
||||
- **里程碑 4**:系统上线,正式投入使用
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- **需求分析详细文档**:[需求分析/业务需求说明.md](需求分析/业务需求说明.md)
|
||||
- **技术设计详细文档**:[技术设计/架构设计.md](技术设计/架构设计.md)
|
||||
- **API 接口详细文档**:[技术设计/API 接口设计.md](技术设计/API接口设计.md)
|
||||
- **数据库设计详细文档**:[技术设计/数据库设计.md](技术设计/数据库设计.md)
|
||||
- **项目总览文档**:[../../项目总览/README.md](../../项目总览/README.md)
|
||||
- **开发规范文档**:[../../开发文档/README.md](../../开发文档/README.md)
|
||||
|
||||
**重要说明**:
|
||||
|
||||
1. **用户权限管理**:使用系统自带的用户管理、角色管理和权限管理功能,无需重新开发
|
||||
2. **通知功能**:本模块不包含通知功能,用户通过系统界面查看状态更新和进度变化
|
||||
3. **集成说明**:与现有系统的用户认证、文件存储、工作流引擎进行集成
|
||||
@@ -0,0 +1,632 @@
|
||||
# 物业报修管理模块 - 数据库设计
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
**文档名称**:物业报修管理模块数据库设计
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2025-01-23
|
||||
**设计规范**:严格遵循 `docs/设计文档/database/mysql-database-standard.md`
|
||||
**依据文档**:功能需求清单.md
|
||||
|
||||
## 🎯 数据库设计原则
|
||||
|
||||
### 核心设计理念
|
||||
|
||||
1. **严格遵循项目规范**:所有表必须包含标准基础字段
|
||||
2. **业务导向设计**:基于实际业务流程设计表结构
|
||||
3. **性能优化**:合理设计索引,支持高效查询
|
||||
4. **扩展性考虑**:预留扩展字段和机制
|
||||
5. **数据完整性**:通过应用层维护关联关系
|
||||
|
||||
### 关键约束
|
||||
|
||||
- ✅ **使用雪花 ID**作为主键
|
||||
- ✅ **强制逻辑删除**,禁止物理删除
|
||||
- ✅ **多租户支持**,所有表包含 tenant_id
|
||||
- ✅ **禁用外键约束**,关联关系通过应用层维护
|
||||
- ✅ **完整中文注释**,便于理解和维护
|
||||
|
||||
## 📊 数据模型概览
|
||||
|
||||
### 🗂️ 表结构清单
|
||||
|
||||
| 表名 | 中文名称 | 用途 | 关联关系 |
|
||||
| ----------------------- | ---------- | ---------------------- | ------------ |
|
||||
| `pm_repair_order` | 报修工单表 | 存储报修申请的核心信息 | 核心表 |
|
||||
| `pm_repair_attachment` | 报修附件表 | 存储报修相关附件文件 | 关联报修工单 |
|
||||
| `pm_repair_progress` | 维修进度表 | 记录维修过程状态变更 | 关联报修工单 |
|
||||
| `pm_service_evaluation` | 服务评价表 | 存储用户服务质量评价 | 关联报修工单 |
|
||||
| `pm_repair_category` | 维修类型表 | 报修类型分类配置 | 字典表 |
|
||||
| `pm_system_config` | 系统配置表 | 模块系统参数配置 | 配置表 |
|
||||
|
||||
### 📈 数据流转关系
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户提交报修] --> B[pm_repair_order 报修工单]
|
||||
B --> C[pm_repair_attachment 附件]
|
||||
B --> D[pm_repair_progress 进度]
|
||||
B --> E[pm_service_evaluation 评价]
|
||||
F[pm_repair_category 类型] --> B
|
||||
G[pm_system_config 配置] --> B
|
||||
H[sys_user 系统用户] --> B
|
||||
H --> D
|
||||
```
|
||||
|
||||
## 🏗️ 详细表结构设计
|
||||
|
||||
### 1. 报修工单表 (pm_repair_order)
|
||||
|
||||
> **核心业务表**:存储报修申请的所有核心信息,是整个模块的数据中心
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_order
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_order`;
|
||||
CREATE TABLE `pm_repair_order` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 业务基础信息
|
||||
`order_no` varchar(50) NOT NULL COMMENT '工单编号,格式:RO+年月日+4位流水号',
|
||||
`title` varchar(200) NOT NULL COMMENT '报修标题',
|
||||
`description` text NOT NULL COMMENT '问题描述',
|
||||
`repair_location` varchar(200) NOT NULL COMMENT '报修地点',
|
||||
|
||||
-- 分类和紧急程度
|
||||
`category_id` bigint(20) NOT NULL COMMENT '维修类型ID,关联pm_repair_category.id',
|
||||
`priority_level` tinyint(1) NOT NULL DEFAULT 3 COMMENT '紧急程度:1-非常紧急,2-紧急,3-一般,4-不紧急,5-可延后',
|
||||
`expected_finish_time` datetime DEFAULT NULL COMMENT '期望完成时间',
|
||||
|
||||
-- 报修人信息
|
||||
`reporter_id` bigint(20) NOT NULL COMMENT '报修人ID,关联sys_user.id',
|
||||
`reporter_name` varchar(50) NOT NULL COMMENT '报修人姓名',
|
||||
`reporter_phone` varchar(20) NOT NULL COMMENT '报修人电话',
|
||||
`reporter_dept` varchar(100) DEFAULT NULL COMMENT '报修人部门',
|
||||
|
||||
-- 工单状态和处理信息
|
||||
`order_status` char(1) NOT NULL DEFAULT '1' COMMENT '工单状态:1-待分派,2-已分派,3-进行中,4-已完成,5-已评价,6-已归档,9-已取消',
|
||||
`assign_time` datetime DEFAULT NULL COMMENT '分派时间',
|
||||
`assignee_id` bigint(20) DEFAULT NULL COMMENT '分派维修员ID,关联sys_user.id',
|
||||
`assignee_name` varchar(50) DEFAULT NULL COMMENT '分派维修员姓名',
|
||||
`assign_remark` varchar(500) DEFAULT NULL COMMENT '分派备注说明',
|
||||
|
||||
-- 处理时间信息
|
||||
`start_time` datetime DEFAULT NULL COMMENT '开始处理时间',
|
||||
`finish_time` datetime DEFAULT NULL COMMENT '完成时间',
|
||||
`total_duration` int(11) DEFAULT NULL COMMENT '总处理时长(分钟)',
|
||||
|
||||
-- 处理结果
|
||||
`repair_result` text DEFAULT NULL COMMENT '维修结果描述',
|
||||
`used_materials` varchar(500) DEFAULT NULL COMMENT '使用材料清单',
|
||||
`follow_suggestions` varchar(500) DEFAULT NULL COMMENT '后续建议',
|
||||
|
||||
-- 评价相关
|
||||
`evaluation_score` decimal(3,1) DEFAULT NULL COMMENT '综合评价得分(1.0-5.0)',
|
||||
`evaluation_time` datetime DEFAULT NULL COMMENT '评价时间',
|
||||
|
||||
-- 统计字段
|
||||
`attachment_count` int(11) DEFAULT 0 COMMENT '附件数量',
|
||||
`progress_count` int(11) DEFAULT 0 COMMENT '进度记录数量',
|
||||
|
||||
-- 其他字段
|
||||
`cancel_reason` varchar(500) DEFAULT NULL COMMENT '取消原因',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_order_no` (`order_no`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_status` (`order_status`),
|
||||
KEY `idx_priority_level` (`priority_level`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_reporter_id` (`reporter_id`),
|
||||
KEY `idx_assignee_id` (`assignee_id`),
|
||||
KEY `idx_category_id` (`category_id`),
|
||||
KEY `idx_assign_time` (`assign_time`),
|
||||
KEY `idx_finish_time` (`finish_time`),
|
||||
KEY `idx_tenant_status` (`tenant_id`, `order_status`, `deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报修工单表';
|
||||
```
|
||||
|
||||
### 2. 报修附件表 (pm_repair_attachment)
|
||||
|
||||
> **附件管理表**:存储报修过程中上传的图片、视频等多媒体文件信息
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_attachment
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_attachment`;
|
||||
CREATE TABLE `pm_repair_attachment` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
|
||||
-- 文件信息
|
||||
`file_name` varchar(200) NOT NULL COMMENT '文件原始名称',
|
||||
`file_path` varchar(500) NOT NULL COMMENT '文件存储路径',
|
||||
`file_size` bigint(20) NOT NULL COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(20) NOT NULL COMMENT '文件类型:image-图片,video-视频',
|
||||
`file_extension` varchar(10) NOT NULL COMMENT '文件扩展名',
|
||||
`file_mime_type` varchar(100) DEFAULT NULL COMMENT '文件MIME类型',
|
||||
|
||||
-- 上传信息
|
||||
`upload_stage` char(1) NOT NULL DEFAULT '1' COMMENT '上传阶段:1-报修申请,2-维修过程,3-完成确认',
|
||||
`uploader_id` bigint(20) NOT NULL COMMENT '上传人ID,关联sys_user.id',
|
||||
`uploader_name` varchar(50) NOT NULL COMMENT '上传人姓名',
|
||||
|
||||
-- 文件处理
|
||||
`thumbnail_path` varchar(500) DEFAULT NULL COMMENT '缩略图路径(图片文件)',
|
||||
`duration` int(11) DEFAULT NULL COMMENT '时长(视频文件,秒)',
|
||||
`resolution` varchar(20) DEFAULT NULL COMMENT '分辨率(如:1920x1080)',
|
||||
|
||||
-- 业务信息
|
||||
`description` varchar(200) DEFAULT NULL COMMENT '文件描述说明',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_upload_stage` (`upload_stage`),
|
||||
KEY `idx_uploader_id` (`uploader_id`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_file_type` (`file_type`),
|
||||
KEY `idx_order_stage` (`order_id`, `upload_stage`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报修附件表';
|
||||
```
|
||||
|
||||
### 3. 维修进度表 (pm_repair_progress)
|
||||
|
||||
> **进度跟踪表**:记录维修过程中的每一个状态变更和进度更新
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_progress
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_progress`;
|
||||
CREATE TABLE `pm_repair_progress` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
|
||||
-- 进度信息
|
||||
`progress_type` char(1) NOT NULL COMMENT '进度类型:1-工单分派,2-接收确认,3-到达现场,4-开始维修,5-进度更新,6-完成维修,7-用户评价,8-工单归档,9-其他',
|
||||
`progress_status` varchar(50) NOT NULL COMMENT '进度状态描述',
|
||||
`progress_detail` text DEFAULT NULL COMMENT '进度详细描述',
|
||||
|
||||
-- 操作人信息
|
||||
`operator_id` bigint(20) NOT NULL COMMENT '操作人ID,关联sys_user.id',
|
||||
`operator_name` varchar(50) NOT NULL COMMENT '操作人姓名',
|
||||
`operator_role` varchar(20) NOT NULL COMMENT '操作人角色:user-用户,admin-管理员,worker-维修员',
|
||||
|
||||
-- 位置信息
|
||||
`location_info` varchar(200) DEFAULT NULL COMMENT '操作位置信息',
|
||||
`gps_latitude` decimal(10,7) DEFAULT NULL COMMENT 'GPS纬度',
|
||||
`gps_longitude` decimal(10,7) DEFAULT NULL COMMENT 'GPS经度',
|
||||
|
||||
-- 时间信息
|
||||
`operation_time` datetime NOT NULL COMMENT '操作时间',
|
||||
`estimated_duration` int(11) DEFAULT NULL COMMENT '预计耗时(分钟)',
|
||||
|
||||
-- 附加信息
|
||||
`attachment_ids` varchar(500) DEFAULT NULL COMMENT '关联附件ID列表(逗号分隔)',
|
||||
`next_step` varchar(200) DEFAULT NULL COMMENT '下一步计划',
|
||||
`issues_encountered` varchar(500) DEFAULT NULL COMMENT '遇到的问题',
|
||||
`materials_needed` varchar(200) DEFAULT NULL COMMENT '需要的材料',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_progress_type` (`progress_type`),
|
||||
KEY `idx_operator_id` (`operator_id`),
|
||||
KEY `idx_operation_time` (`operation_time`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_order_time` (`order_id`, `operation_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修进度表';
|
||||
```
|
||||
|
||||
### 4. 服务评价表 (pm_service_evaluation)
|
||||
|
||||
> **评价反馈表**:存储用户对维修服务的多维度评价和反馈信息
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_service_evaluation
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_service_evaluation`;
|
||||
CREATE TABLE `pm_service_evaluation` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
`evaluator_id` bigint(20) NOT NULL COMMENT '评价人ID,关联sys_user.id',
|
||||
`evaluator_name` varchar(50) NOT NULL COMMENT '评价人姓名',
|
||||
`evaluated_worker_id` bigint(20) NOT NULL COMMENT '被评价维修员ID,关联sys_user.id',
|
||||
`evaluated_worker_name` varchar(50) NOT NULL COMMENT '被评价维修员姓名',
|
||||
|
||||
-- 评价得分(1-5星,支持小数)
|
||||
`overall_score` decimal(3,1) NOT NULL COMMENT '综合评价得分(1.0-5.0)',
|
||||
`quality_score` decimal(3,1) NOT NULL COMMENT '维修质量得分(1.0-5.0)',
|
||||
`attitude_score` decimal(3,1) NOT NULL COMMENT '服务态度得分(1.0-5.0)',
|
||||
`response_speed_score` decimal(3,1) NOT NULL COMMENT '响应速度得分(1.0-5.0)',
|
||||
`communication_score` decimal(3,1) NOT NULL COMMENT '沟通效果得分(1.0-5.0)',
|
||||
|
||||
-- 评价内容
|
||||
`evaluation_content` text DEFAULT NULL COMMENT '文字评价内容',
|
||||
`satisfaction_level` char(1) NOT NULL COMMENT '满意度等级:1-非常满意,2-满意,3-一般,4-不满意,5-非常不满意',
|
||||
|
||||
-- 评价设置
|
||||
`is_anonymous` tinyint(1) DEFAULT 0 COMMENT '是否匿名评价(0-实名,1-匿名)',
|
||||
`is_public` tinyint(1) DEFAULT 1 COMMENT '是否公开显示(0-不公开,1-公开)',
|
||||
|
||||
-- 时间信息
|
||||
`evaluation_time` datetime NOT NULL COMMENT '评价时间',
|
||||
`service_start_time` datetime DEFAULT NULL COMMENT '服务开始时间',
|
||||
`service_end_time` datetime DEFAULT NULL COMMENT '服务结束时间',
|
||||
`service_duration` int(11) DEFAULT NULL COMMENT '服务时长(分钟)',
|
||||
|
||||
-- 标签和建议
|
||||
`positive_tags` varchar(200) DEFAULT NULL COMMENT '正面标签(逗号分隔)',
|
||||
`negative_tags` varchar(200) DEFAULT NULL COMMENT '负面标签(逗号分隔)',
|
||||
`improvement_suggestions` varchar(500) DEFAULT NULL COMMENT '改进建议',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_order_evaluation` (`order_id`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_evaluator_id` (`evaluator_id`),
|
||||
KEY `idx_evaluated_worker_id` (`evaluated_worker_id`),
|
||||
KEY `idx_overall_score` (`overall_score`),
|
||||
KEY `idx_satisfaction_level` (`satisfaction_level`),
|
||||
KEY `idx_evaluation_time` (`evaluation_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务评价表';
|
||||
```
|
||||
|
||||
### 5. 维修类型表 (pm_repair_category)
|
||||
|
||||
> **分类字典表**:存储维修类型的分类信息,支持层级结构
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_category
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_category`;
|
||||
CREATE TABLE `pm_repair_category` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 分类信息
|
||||
`category_name` varchar(50) NOT NULL COMMENT '类型名称',
|
||||
`category_code` varchar(20) NOT NULL COMMENT '类型编码',
|
||||
`category_description` varchar(200) DEFAULT NULL COMMENT '类型描述',
|
||||
|
||||
-- 层级结构
|
||||
`parent_id` bigint(20) DEFAULT 0 COMMENT '父级ID,顶级为0',
|
||||
`ancestor_ids` varchar(500) DEFAULT '0' COMMENT '祖级列表(逗号分隔)',
|
||||
`level_depth` tinyint(2) DEFAULT 1 COMMENT '层级深度(1-顶级,2-二级...)',
|
||||
`children_count` int(11) DEFAULT 0 COMMENT '子类数量',
|
||||
|
||||
-- 专业技能
|
||||
`required_skills` varchar(200) DEFAULT NULL COMMENT '所需专业技能(逗号分隔)',
|
||||
`skill_level` char(1) DEFAULT '1' COMMENT '技能等级要求:1-初级,2-中级,3-高级',
|
||||
|
||||
-- 时间和成本预估
|
||||
`estimated_duration` int(11) DEFAULT NULL COMMENT '预估维修时长(分钟)',
|
||||
`standard_cost` decimal(10,2) DEFAULT NULL COMMENT '标准成本(元)',
|
||||
|
||||
-- 显示控制
|
||||
`icon_url` varchar(200) DEFAULT NULL COMMENT '分类图标URL',
|
||||
`color_code` varchar(10) DEFAULT '#1890ff' COMMENT '分类颜色代码',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(0-停用,1-正常)',
|
||||
|
||||
-- 统计信息
|
||||
`usage_count` int(11) DEFAULT 0 COMMENT '使用次数统计',
|
||||
`last_used_time` datetime DEFAULT NULL COMMENT '最后使用时间',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_category_code` (`category_code`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_parent_id` (`parent_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_sort_order` (`sort_order`),
|
||||
KEY `idx_level_depth` (`level_depth`),
|
||||
KEY `idx_usage_count` (`usage_count`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修类型表';
|
||||
```
|
||||
|
||||
### 6. 系统配置表 (pm_system_config)
|
||||
|
||||
> **配置管理表**:存储物业报修模块的系统参数和配置信息
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_system_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_system_config`;
|
||||
CREATE TABLE `pm_system_config` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 配置信息
|
||||
`config_key` varchar(100) NOT NULL COMMENT '配置键名',
|
||||
`config_value` text DEFAULT NULL COMMENT '配置值',
|
||||
`config_type` varchar(20) NOT NULL COMMENT '配置类型:string-字符串,number-数字,boolean-布尔,json-JSON对象',
|
||||
`config_group` varchar(50) NOT NULL COMMENT '配置分组:file-文件设置,time-时间设置,notice-通知设置,workflow-流程设置',
|
||||
|
||||
-- 配置描述
|
||||
`config_name` varchar(100) NOT NULL COMMENT '配置名称',
|
||||
`config_description` varchar(500) DEFAULT NULL COMMENT '配置描述说明',
|
||||
`default_value` text DEFAULT NULL COMMENT '默认值',
|
||||
|
||||
-- 配置约束
|
||||
`is_system` tinyint(1) DEFAULT 0 COMMENT '是否系统内置(0-否,1-是,系统内置不可删除)',
|
||||
`is_editable` tinyint(1) DEFAULT 1 COMMENT '是否可编辑(0-否,1-是)',
|
||||
`validation_rule` varchar(200) DEFAULT NULL COMMENT '验证规则(正则表达式或范围)',
|
||||
|
||||
-- 显示控制
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(0-停用,1-正常)',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_config_key` (`config_key`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_config_group` (`config_group`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_sort_order` (`sort_order`),
|
||||
KEY `idx_is_system` (`is_system`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
|
||||
```
|
||||
|
||||
## 📝 数据字典
|
||||
|
||||
### 1. 工单状态枚举 (order_status)
|
||||
|
||||
| 状态码 | 状态名称 | 描述 | 可执行操作 |
|
||||
| ------ | -------- | -------------------------- | ------------------------ |
|
||||
| 1 | 待分派 | 用户刚提交,等待管理员分派 | 分派、取消、修改 |
|
||||
| 2 | 已分派 | 已分派给维修员,等待接收 | 接收、拒绝、重新分派 |
|
||||
| 3 | 进行中 | 维修员已接收,正在处理 | 更新进度、完成、上报问题 |
|
||||
| 4 | 已完成 | 维修已完成,等待评价 | 评价、重新打开 |
|
||||
| 5 | 已评价 | 用户已评价,等待归档 | 归档 |
|
||||
| 6 | 已归档 | 工单已归档,流程结束 | 查看历史 |
|
||||
| 9 | 已取消 | 工单已取消 | 查看原因 |
|
||||
|
||||
### 2. 紧急程度枚举 (priority_level)
|
||||
|
||||
| 等级 | 名称 | 描述 | 响应时限 | 颜色标识 |
|
||||
| ---- | -------- | ------------------ | --------- | -------- |
|
||||
| 1 | 非常紧急 | 影响安全或正常运转 | 1 小时内 | 红色 |
|
||||
| 2 | 紧急 | 影响重要功能使用 | 4 小时内 | 橙色 |
|
||||
| 3 | 一般 | 常规维修需求 | 24 小时内 | 蓝色 |
|
||||
| 4 | 不紧急 | 不影响使用的问题 | 72 小时内 | 绿色 |
|
||||
| 5 | 可延后 | 可以安排时间处理 | 一周内 | 灰色 |
|
||||
|
||||
### 3. 进度类型枚举 (progress_type)
|
||||
|
||||
| 类型码 | 类型名称 | 操作人角色 | 描述 |
|
||||
| ------ | -------- | ----------- | ------------------ |
|
||||
| 1 | 工单分派 | 管理员 | 工单分派给维修员 |
|
||||
| 2 | 接收确认 | 维修员 | 维修员确认接收工单 |
|
||||
| 3 | 到达现场 | 维修员 | 维修员到达维修现场 |
|
||||
| 4 | 开始维修 | 维修员 | 开始进行维修作业 |
|
||||
| 5 | 进度更新 | 维修员 | 维修过程进度更新 |
|
||||
| 6 | 完成维修 | 维修员 | 维修工作已完成 |
|
||||
| 7 | 用户评价 | 用户 | 用户提交服务评价 |
|
||||
| 8 | 工单归档 | 系统/管理员 | 工单归档处理 |
|
||||
| 9 | 其他 | 任意角色 | 其他类型操作 |
|
||||
|
||||
### 4. 文件类型枚举 (file_type)
|
||||
|
||||
| 类型 | 名称 | 支持格式 | 大小限制 |
|
||||
| ----- | ---- | ------------------- | -------- |
|
||||
| image | 图片 | jpg, jpeg, png, gif | 5MB |
|
||||
| video | 视频 | mp4, avi, mov | 50MB |
|
||||
|
||||
### 5. 上传阶段枚举 (upload_stage)
|
||||
|
||||
| 阶段码 | 阶段名称 | 描述 |
|
||||
| ------ | -------- | ---------------------- |
|
||||
| 1 | 报修申请 | 用户提交报修时上传 |
|
||||
| 2 | 维修过程 | 维修员维修过程中上传 |
|
||||
| 3 | 完成确认 | 维修完成时上传结果照片 |
|
||||
|
||||
## 🔧 索引优化策略
|
||||
|
||||
### 1. 核心业务查询索引
|
||||
|
||||
```sql
|
||||
-- 报修工单表的核心查询索引
|
||||
-- 1. 按状态和时间查询(管理员常用)
|
||||
CREATE INDEX idx_status_time ON pm_repair_order (order_status, create_time);
|
||||
|
||||
-- 2. 按维修员查询其工单(维修员常用)
|
||||
CREATE INDEX idx_assignee_status ON pm_repair_order (assignee_id, order_status);
|
||||
|
||||
-- 3. 按报修人查询(用户查看自己的工单)
|
||||
CREATE INDEX idx_reporter_time ON pm_repair_order (reporter_id, create_time);
|
||||
|
||||
-- 4. 按紧急程度和分派时间查询(工单分派)
|
||||
CREATE INDEX idx_priority_assign ON pm_repair_order (priority_level, assign_time);
|
||||
```
|
||||
|
||||
### 2. 附件查询索引
|
||||
|
||||
```sql
|
||||
-- 附件表的查询索引
|
||||
-- 1. 按工单查询附件(最常用)
|
||||
CREATE INDEX idx_order_stage ON pm_repair_attachment (order_id, upload_stage);
|
||||
|
||||
-- 2. 按上传者查询附件
|
||||
CREATE INDEX idx_uploader_time ON pm_repair_attachment (uploader_id, create_time);
|
||||
```
|
||||
|
||||
### 3. 进度查询索引
|
||||
|
||||
```sql
|
||||
-- 进度表的查询索引
|
||||
-- 1. 按工单查询进度(时间线显示)
|
||||
CREATE INDEX idx_order_operation_time ON pm_repair_progress (order_id, operation_time);
|
||||
|
||||
-- 2. 按操作人查询进度
|
||||
CREATE INDEX idx_operator_time ON pm_repair_progress (operator_id, operation_time);
|
||||
```
|
||||
|
||||
## 📊 数据存储预估
|
||||
|
||||
### 表数据量预估
|
||||
|
||||
| 表名 | 日增长量 | 年数据量 | 存储空间预估 |
|
||||
| --------------------- | ---------- | ------------ | ------------ |
|
||||
| pm_repair_order | 50-100 条 | 2-4 万条 | 50-100MB |
|
||||
| pm_repair_attachment | 200-400 条 | 8-16 万条 | 100-200MB |
|
||||
| pm_repair_progress | 300-600 条 | 12-24 万条 | 150-300MB |
|
||||
| pm_service_evaluation | 40-80 条 | 1.6-3.2 万条 | 20-40MB |
|
||||
| pm_repair_category | 0-5 条 | 100-200 条 | <1MB |
|
||||
| pm_system_config | 0-2 条 | 50-100 条 | <1MB |
|
||||
|
||||
### 存储优化建议
|
||||
|
||||
1. **定期归档**:超过 2 年的已完成工单可归档到历史表
|
||||
2. **附件清理**:定期清理无效或重复的附件文件
|
||||
3. **索引维护**:定期重建和优化索引
|
||||
4. **分区策略**:可考虑按年份或状态分区存储
|
||||
|
||||
## 🔄 数据初始化脚本
|
||||
|
||||
### 1. 维修类型初始化数据
|
||||
|
||||
```sql
|
||||
-- 插入基础维修类型分类
|
||||
INSERT INTO `pm_repair_category` (
|
||||
`id`, `tenant_id`, `category_name`, `category_code`, `category_description`,
|
||||
`parent_id`, `level_depth`, `required_skills`, `skill_level`,
|
||||
`estimated_duration`, `icon_url`, `color_code`, `sort_order`, `status`,
|
||||
`create_time`, `create_by`
|
||||
) VALUES
|
||||
(1, '000000', '电路维修', 'ELECTRIC', '电路、照明、开关、插座等电气设备维修', 0, 1, '电工技能', '2', 60, '/icons/electric.svg', '#ff6b35', 1, '1', NOW(), 1),
|
||||
(2, '000000', '水暖维修', 'PLUMBING', '水管、暖气、卫生间、厨房设备维修', 0, 1, '水暖技能', '2', 90, '/icons/plumbing.svg', '#1890ff', 2, '1', NOW(), 1),
|
||||
(3, '000000', '土建维修', 'CONSTRUCTION', '墙面、地面、天花板、门窗等土建维修', 0, 1, '泥工技能,木工技能', '2', 120, '/icons/construction.svg', '#52c41a', 3, '1', NOW(), 1),
|
||||
(4, '000000', '设备维修', 'EQUIPMENT', '空调、电梯、监控等设备维修', 0, 1, '设备维修技能', '3', 90, '/icons/equipment.svg', '#722ed1', 4, '1', NOW(), 1),
|
||||
(5, '000000', '园林维修', 'GARDEN', '绿化、草坪、树木修剪等园林维护', 0, 1, '园林技能', '1', 45, '/icons/garden.svg', '#13c2c2', 5, '1', NOW(), 1),
|
||||
(6, '000000', '清洁维护', 'CLEANING', '日常清洁、垃圾处理、环境维护', 0, 1, '清洁技能', '1', 30, '/icons/cleaning.svg', '#fa8c16', 6, '1', NOW(), 1);
|
||||
```
|
||||
|
||||
### 2. 系统配置初始化数据
|
||||
|
||||
```sql
|
||||
-- 插入系统配置参数
|
||||
INSERT INTO `pm_system_config` (
|
||||
`id`, `tenant_id`, `config_key`, `config_value`, `config_type`, `config_group`,
|
||||
`config_name`, `config_description`, `default_value`, `is_system`, `is_editable`,
|
||||
`sort_order`, `status`, `create_time`, `create_by`
|
||||
) VALUES
|
||||
(1, '000000', 'file.upload.image.max_size', '5242880', 'number', 'file', '图片文件最大大小', '图片文件上传的最大字节数(5MB)', '5242880', 1, 1, 1, '1', NOW(), 1),
|
||||
(2, '000000', 'file.upload.video.max_size', '52428800', 'number', 'file', '视频文件最大大小', '视频文件上传的最大字节数(50MB)', '52428800', 1, 1, 2, '1', NOW(), 1),
|
||||
(3, '000000', 'file.upload.allowed_image_types', 'jpg,jpeg,png,gif', 'string', 'file', '允许的图片类型', '允许上传的图片文件类型(逗号分隔)', 'jpg,jpeg,png,gif', 1, 1, 3, '1', NOW(), 1),
|
||||
(4, '000000', 'file.upload.allowed_video_types', 'mp4,avi,mov', 'string', 'file', '允许的视频类型', '允许上传的视频文件类型(逗号分隔)', 'mp4,avi,mov', 1, 1, 4, '1', NOW(), 1),
|
||||
(5, '000000', 'workflow.auto_assign_enabled', 'true', 'boolean', 'workflow', '启用自动分派', '是否启用智能自动分派功能', 'false', 1, 1, 11, '1', NOW(), 1),
|
||||
(6, '000000', 'workflow.auto_archive_days', '7', 'number', 'workflow', '自动归档天数', '工单评价后多少天自动归档', '7', 1, 1, 12, '1', NOW(), 1),
|
||||
(7, '000000', 'time.priority1_response_hours', '1', 'number', 'time', '非常紧急响应时限', '非常紧急工单的响应时限(小时)', '1', 1, 1, 21, '1', NOW(), 1),
|
||||
(8, '000000', 'time.priority2_response_hours', '4', 'number', 'time', '紧急响应时限', '紧急工单的响应时限(小时)', '4', 1, 1, 22, '1', NOW(), 1),
|
||||
(9, '000000', 'time.priority3_response_hours', '24', 'number', 'time', '一般响应时限', '一般工单的响应时限(小时)', '24', 1, 1, 23, '1', NOW(), 1),
|
||||
(10, '000000', 'time.working_hours_start', '08:00', 'string', 'time', '工作时间开始', '工作日开始时间(HH:mm格式)', '08:00', 1, 1, 31, '1', NOW(), 1),
|
||||
(11, '000000', 'time.working_hours_end', '18:00', 'string', 'time', '工作时间结束', '工作日结束时间(HH:mm格式)', '18:00', 1, 1, 32, '1', NOW(), 1);
|
||||
```
|
||||
|
||||
## 📋 数据迁移计划
|
||||
|
||||
### 迁移脚本版本规划
|
||||
|
||||
1. **V1.0.1** - 创建基础表结构
|
||||
2. **V1.0.2** - 插入初始化数据
|
||||
3. **V1.0.3** - 创建性能优化索引
|
||||
4. **V1.1.0** - 功能扩展字段添加
|
||||
|
||||
### 部署验证清单
|
||||
|
||||
- [ ] 表结构创建成功
|
||||
- [ ] 基础索引创建完成
|
||||
- [ ] 初始化数据插入成功
|
||||
- [ ] 性能索引创建完成
|
||||
- [ ] 数据完整性约束验证
|
||||
- [ ] 权限配置正确
|
||||
- [ ] 备份恢复测试通过
|
||||
|
||||
---
|
||||
|
||||
**文档状态**:✅ 初稿完成
|
||||
**关联文档**:功能需求清单.md
|
||||
**下一步**:API 接口设计
|
||||
@@ -0,0 +1,591 @@
|
||||
# 物业报修管理模块 - 业务需求说明
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
**文档名称**:物业报修管理模块业务需求说明
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2025-01-23
|
||||
**创建人员**:系统分析师
|
||||
**审核状态**:待审核
|
||||
|
||||
## 🎯 项目背景
|
||||
|
||||
### 现状分析
|
||||
|
||||
目前学校物业维修服务存在以下问题:
|
||||
|
||||
#### 🔴 用户报修方面
|
||||
|
||||
- **报修渠道单一**:主要依赖电话报修,缺乏在线渠道
|
||||
- **信息记录不全**:电话报修信息容易遗漏,无法保存现场照片
|
||||
- **进度不透明**:用户无法实时了解维修进度
|
||||
- **反馈渠道缺失**:维修完成后缺乏服务质量反馈机制
|
||||
|
||||
#### 🔴 管理方面
|
||||
|
||||
- **工单管理混乱**:缺乏统一的工单管理系统
|
||||
- **分派效率低下**:人工分派工单,容易出现负荷不均
|
||||
- **数据统计困难**:无法有效统计维修效率和满意度数据
|
||||
- **绩效考核缺失**:缺乏客观的维修员绩效评价体系
|
||||
|
||||
#### 🔴 维修员方面
|
||||
|
||||
- **工单接收不便**:需要通过电话或纸质单据接收任务
|
||||
- **进度更新困难**:无法及时更新维修进度给用户
|
||||
- **工作量统计难**:个人工作量和效率缺乏准确统计
|
||||
|
||||
### 建设目标
|
||||
|
||||
构建一个完整的数字化物业维修管理平台,实现:
|
||||
|
||||
1. **服务便民化**:提供便捷的在线报修渠道
|
||||
2. **管理智能化**:实现智能工单分派和进度跟踪
|
||||
3. **流程标准化**:建立标准化的维修业务流程
|
||||
4. **数据可视化**:提供全面的数据统计和分析
|
||||
5. **服务透明化**:实现维修进度的实时透明展示
|
||||
|
||||
## 🔍 业务需求分析
|
||||
|
||||
### 核心业务场景
|
||||
|
||||
#### 场景 1:学生宿舍报修
|
||||
|
||||
**背景**:学生宿舍水龙头漏水需要维修
|
||||
|
||||
**当前流程**:
|
||||
|
||||
1. 学生发现问题 → 2. 拨打后勤电话 → 3. 口头描述问题 → 4. 等待维修 → 5. 维修员上门 → 6. 完成维修
|
||||
|
||||
**期望流程**:
|
||||
|
||||
1. 学生在线填写报修单 → 2. 上传现场照片 → 3. 系统自动通知管理员 → 4. 管理员分派维修员 → 5. 维修员接单并现场维修 → 6. 学生在线评价
|
||||
|
||||
#### 场景 2:教学楼设备维修
|
||||
|
||||
**背景**:教室多媒体设备故障需要紧急维修
|
||||
|
||||
**业务特点**:
|
||||
|
||||
- 影响教学,需要紧急处理
|
||||
- 需要专业维修员处理
|
||||
- 需要准确记录设备型号和故障现象
|
||||
|
||||
#### 场景 3:公共区域维修
|
||||
|
||||
**背景**:校园路灯损坏需要维修
|
||||
|
||||
**业务特点**:
|
||||
|
||||
- 影响校园安全,优先级高
|
||||
- 可能需要外部供应商配合
|
||||
- 需要考虑天气等外部因素
|
||||
|
||||
### 主要业务流程
|
||||
|
||||
#### 1. 报修申请流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户发现问题] --> B[登录系统]
|
||||
B --> C[填写报修信息]
|
||||
C --> D[选择报修类型]
|
||||
D --> E[上传现场照片/视频]
|
||||
E --> F[设置期望完成时间]
|
||||
F --> G[提交报修申请]
|
||||
G --> H[系统生成报修单号]
|
||||
H --> I[发送确认通知]
|
||||
I --> J[等待处理]
|
||||
```
|
||||
|
||||
**详细说明**:
|
||||
|
||||
- **步骤 1-2**:用户需要先登录系统,确保身份可追溯
|
||||
- **步骤 3-4**:填写详细的报修信息,选择正确的维修类型便于分派
|
||||
- **步骤 5**:支持上传多张图片或视频,帮助维修员提前了解问题
|
||||
- **步骤 6**:用户可以设置期望完成时间,但不强制
|
||||
- **步骤 7-9**:系统自动处理后续流程,确保信息不遗漏
|
||||
|
||||
#### 2. 工单分派流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[管理员接收新报修单] --> B[查看报修详情]
|
||||
B --> C[评估维修类型和紧急程度]
|
||||
C --> D[查看维修员工作负荷]
|
||||
D --> E[选择合适的维修员]
|
||||
E --> F[确认分派]
|
||||
F --> G[系统更新工单状态]
|
||||
G --> H[通知维修员]
|
||||
H --> I[通知报修用户]
|
||||
```
|
||||
|
||||
**业务规则**:
|
||||
|
||||
- **紧急工单优先**:紧急程度高的工单优先分派
|
||||
- **专业匹配**:根据维修类型选择专业对口的维修员
|
||||
- **负荷均衡**:考虑维修员当前工作量,避免过度集中
|
||||
- **地理位置**:优先分派给距离报修地点较近的维修员
|
||||
|
||||
#### 3. 维修执行流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[维修员接收工单] --> B[确认接单]
|
||||
B --> C[联系报修人确认时间]
|
||||
C --> D[到达现场]
|
||||
D --> E[开始维修]
|
||||
E --> F[遇到问题?]
|
||||
F -->|是| G[上报管理员]
|
||||
F -->|否| H[继续维修]
|
||||
G --> I[等待指示]
|
||||
I --> H
|
||||
H --> J[完成维修]
|
||||
J --> K[拍照记录结果]
|
||||
K --> L[提交维修报告]
|
||||
L --> M[等待用户确认]
|
||||
```
|
||||
|
||||
**关键节点**:
|
||||
|
||||
- **接单确认**:维修员必须主动确认接单,避免工单遗漏
|
||||
- **进度更新**:维修过程中应及时更新进度状态
|
||||
- **问题上报**:遇到技术问题或需要配件时应及时上报
|
||||
- **结果记录**:维修完成后必须拍照记录维修结果
|
||||
|
||||
#### 4. 服务评价流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户收到完成通知] --> B[查看维修结果]
|
||||
B --> C[填写服务评价]
|
||||
C --> D[评分各个维度]
|
||||
D --> E[添加文字评价]
|
||||
E --> F[提交评价]
|
||||
F --> G[系统计算满意度]
|
||||
G --> H[更新维修员绩效]
|
||||
H --> I[归档报修单]
|
||||
```
|
||||
|
||||
**评价维度**:
|
||||
|
||||
- **维修质量**:维修效果是否满意
|
||||
- **服务态度**:维修员服务态度评价
|
||||
- **响应速度**:从报修到开始维修的时间
|
||||
- **沟通效果**:维修过程中的沟通是否顺畅
|
||||
|
||||
## 👥 用户角色分析
|
||||
|
||||
### 1. 普通用户(报修人)
|
||||
|
||||
#### 用户特征
|
||||
|
||||
- **身份**:在校学生、教职工
|
||||
- **年龄**:18-60 岁
|
||||
- **技术水平**:基础到中等
|
||||
- **使用场景**:发现问题需要维修时
|
||||
|
||||
#### 核心需求
|
||||
|
||||
- **便捷报修**:简单易用的报修界面
|
||||
- **进度查询**:随时了解维修进度
|
||||
- **问题沟通**:与维修员直接沟通的渠道
|
||||
- **服务反馈**:对维修服务进行评价
|
||||
|
||||
#### 使用频次
|
||||
|
||||
- **低频用户**:偶尔遇到问题才使用
|
||||
- **重点关注**:界面简洁、操作简单
|
||||
|
||||
### 2. 物业管理员
|
||||
|
||||
#### 用户特征
|
||||
|
||||
- **身份**:物业管理部门工作人员
|
||||
- **工作内容**:维修工单管理、资源调度
|
||||
- **技术水平**:中等到高级
|
||||
- **使用场景**:日常工作时间
|
||||
|
||||
#### 核心需求
|
||||
|
||||
- **工单管理**:查看、分派、跟踪所有工单
|
||||
- **资源调度**:合理分配维修人员
|
||||
- **数据统计**:了解维修业务整体情况
|
||||
- **绩效管理**:维修员工作量和质量评估
|
||||
|
||||
#### 使用频次
|
||||
|
||||
- **高频用户**:每日多次使用
|
||||
- **重点关注**:功能全面、效率高、数据准确
|
||||
|
||||
### 3. 物业维修员
|
||||
|
||||
#### 用户特征
|
||||
|
||||
- **身份**:专业维修技术人员
|
||||
- **工作特点**:流动性强、现场作业
|
||||
- **技术水平**:基础到中等
|
||||
- **使用场景**:接收工单、现场维修时
|
||||
|
||||
#### 核心需求
|
||||
|
||||
- **工单接收**:及时接收分派的工单
|
||||
- **进度更新**:方便地更新维修进度
|
||||
- **问题上报**:遇到困难时快速求助
|
||||
- **工作记录**:记录维修过程和结果
|
||||
|
||||
#### 使用频次
|
||||
|
||||
- **中频用户**:根据工单数量使用
|
||||
- **重点关注**:移动端适配、操作简便
|
||||
|
||||
## 📊 业务规则定义
|
||||
|
||||
### 1. 报修单状态管理
|
||||
|
||||
#### 状态定义
|
||||
|
||||
```
|
||||
PENDING - 待分派 (用户提交后的初始状态)
|
||||
ASSIGNED - 已分派 (管理员分派给维修员)
|
||||
ACCEPTED - 已接单 (维修员确认接单)
|
||||
IN_PROGRESS - 进行中 (维修员开始现场维修)
|
||||
COMPLETED - 已完成 (维修员提交完成报告)
|
||||
EVALUATED - 已评价 (用户提交服务评价)
|
||||
CLOSED - 已关闭 (最终状态,工单归档)
|
||||
CANCELLED - 已取消 (用户或管理员取消)
|
||||
```
|
||||
|
||||
#### 状态流转规则
|
||||
|
||||
- **PENDING → ASSIGNED**:管理员分派工单
|
||||
- **ASSIGNED → ACCEPTED**:维修员确认接单
|
||||
- **ACCEPTED → IN_PROGRESS**:维修员开始维修
|
||||
- **IN_PROGRESS → COMPLETED**:维修员完成维修
|
||||
- **COMPLETED → EVALUATED**:用户提交评价
|
||||
- **EVALUATED → CLOSED**:系统自动归档
|
||||
- **任何状态 → CANCELLED**:可以取消(需要权限)
|
||||
|
||||
### 2. 紧急程度分级
|
||||
|
||||
#### 分级标准
|
||||
|
||||
```
|
||||
1级 - 一般问题 (7天内完成)
|
||||
2级 - 较急问题 (3天内完成)
|
||||
3级 - 紧急问题 (1天内完成)
|
||||
4级 - 很紧急 (4小时内完成)
|
||||
5级 - 特别紧急 (1小时内响应)
|
||||
```
|
||||
|
||||
#### 处理规则
|
||||
|
||||
- **5 级问题**:立即通知管理员,优先分派
|
||||
- **4 级问题**:当天必须安排维修员
|
||||
- **3 级问题**:影响正常使用的问题
|
||||
- **2 级问题**:不影响使用但需要及时处理
|
||||
- **1 级问题**:可以统筹安排的小问题
|
||||
|
||||
### 3. 维修类型分类
|
||||
|
||||
#### 专业分类
|
||||
|
||||
```
|
||||
电路维修 - 电器、照明、配电等
|
||||
水暖维修 - 给排水、暖气、管道等
|
||||
土建维修 - 门窗、墙面、地面等
|
||||
设备维修 - 多媒体、电梯、空调等
|
||||
园林维修 - 绿化、道路、环境等
|
||||
其他维修 - 不属于以上分类的问题
|
||||
```
|
||||
|
||||
#### 分派规则
|
||||
|
||||
- **专业匹配**:优先分派给对应专业的维修员
|
||||
- **跨专业**:复杂问题可以分派给多个维修员
|
||||
- **外包处理**:超出能力范围的问题转外包
|
||||
|
||||
### 4. 工作时间管理
|
||||
|
||||
#### 标准工作时间
|
||||
|
||||
```
|
||||
工作日:08:00-12:00, 14:00-18:00
|
||||
周末:09:00-12:00, 14:00-17:00
|
||||
节假日:值班制,紧急问题处理
|
||||
```
|
||||
|
||||
#### 响应时间要求
|
||||
|
||||
- **工作时间内**:1 小时内响应
|
||||
- **非工作时间**:紧急问题 4 小时内响应
|
||||
- **节假日**:特急问题 2 小时内响应
|
||||
|
||||
## 🔐 权限需求分析
|
||||
|
||||
### 权限分级
|
||||
|
||||
#### 1. 普通用户权限
|
||||
|
||||
- **报修申请**:提交新的报修申请
|
||||
- **进度查看**:查看自己提交的报修单进度
|
||||
- **信息修改**:修改未分派的报修单信息
|
||||
- **服务评价**:对完成的维修服务进行评价
|
||||
- **历史记录**:查看自己的历史报修记录
|
||||
|
||||
#### 2. 维修员权限
|
||||
|
||||
- **工单接收**:查看分派给自己的工单
|
||||
- **进度更新**:更新维修进度状态
|
||||
- **结果提交**:提交维修完成报告
|
||||
- **问题上报**:向管理员上报技术问题
|
||||
- **个人统计**:查看个人工作量统计
|
||||
|
||||
#### 3. 管理员权限
|
||||
|
||||
- **全局查看**:查看所有报修单和工单
|
||||
- **工单分派**:将工单分派给维修员
|
||||
- **人员管理**:管理维修员信息和权限
|
||||
- **数据统计**:查看各类统计分析报表
|
||||
- **系统配置**:配置维修类型、紧急级别等
|
||||
|
||||
#### 4. 超级管理员权限
|
||||
|
||||
- **用户管理**:管理所有用户账号
|
||||
- **权限分配**:分配各角色权限
|
||||
- **系统设置**:系统参数配置
|
||||
- **数据导出**:导出各类业务数据
|
||||
|
||||
## 📈 数据需求分析
|
||||
|
||||
### 核心数据实体
|
||||
|
||||
#### 1. 报修单数据
|
||||
|
||||
```
|
||||
基础信息:报修单号、标题、描述、地点
|
||||
联系信息:联系人、电话、邮箱
|
||||
分类信息:维修类型、紧急程度
|
||||
时间信息:期望完成时间、创建时间
|
||||
状态信息:当前状态、状态变更历史
|
||||
关联信息:分派维修员、处理结果
|
||||
```
|
||||
|
||||
#### 2. 用户数据
|
||||
|
||||
```
|
||||
基本信息:姓名、工号、联系方式
|
||||
角色信息:用户类型、权限级别
|
||||
专业信息:维修员专业技能、工作区域
|
||||
统计信息:报修次数、完成效率、满意度
|
||||
```
|
||||
|
||||
#### 3. 附件数据
|
||||
|
||||
```
|
||||
文件信息:文件名、大小、类型、路径
|
||||
关联信息:所属报修单、上传时间
|
||||
分类信息:问题图片、维修过程图、完成照片
|
||||
```
|
||||
|
||||
### 数据统计需求
|
||||
|
||||
#### 1. 业务统计
|
||||
|
||||
- **报修量统计**:按时间、类型、地点统计报修数量
|
||||
- **处理效率**:平均处理时长、及时完成率
|
||||
- **维修类型分布**:各类维修问题的占比
|
||||
- **地点分布**:各区域报修频次分析
|
||||
|
||||
#### 2. 人员统计
|
||||
|
||||
- **维修员工作量**:每人处理的工单数量
|
||||
- **维修员效率**:平均处理时长、返工率
|
||||
- **用户活跃度**:活跃用户数、报修频次
|
||||
|
||||
#### 3. 质量统计
|
||||
|
||||
- **用户满意度**:各维度评分统计
|
||||
- **服务质量趋势**:满意度变化趋势
|
||||
- **问题分析**:低评分工单分析
|
||||
|
||||
## 🔄 集成需求分析
|
||||
|
||||
### 内部系统集成
|
||||
|
||||
#### 1. 用户管理系统集成
|
||||
|
||||
- **单点登录**:使用统一的用户认证系统
|
||||
- **用户信息同步**:获取用户基本信息
|
||||
- **权限验证**:基于统一的权限管理
|
||||
|
||||
#### 2. 文件存储系统集成
|
||||
|
||||
- **文件上传**:支持图片、视频文件上传
|
||||
- **文件管理**:文件的存储、访问、删除
|
||||
- **访问控制**:确保文件访问安全
|
||||
|
||||
### 外部系统对接
|
||||
|
||||
#### 1. 工作流引擎对接
|
||||
|
||||
- **流程定义**:在工作流引擎中定义维修流程
|
||||
- **状态同步**:工作流状态与业务状态同步
|
||||
- **任务分派**:通过工作流实现自动化分派
|
||||
|
||||
#### 2. 移动端对接
|
||||
|
||||
- **API 接口**:提供移动端所需的 API 接口
|
||||
- **数据同步**:确保移动端和 PC 端数据一致
|
||||
|
||||
## 📝 验收标准
|
||||
|
||||
### 功能验收标准
|
||||
|
||||
#### 1. 报修申请功能
|
||||
|
||||
- [ ] 用户能够成功提交报修申请
|
||||
- [ ] 支持上传图片和视频文件
|
||||
- [ ] 自动生成唯一的报修单号
|
||||
- [ ] 提交成功确认提示
|
||||
|
||||
#### 2. 工单管理功能
|
||||
|
||||
- [ ] 管理员能够查看所有待处理工单
|
||||
- [ ] 支持按条件筛选和搜索工单
|
||||
- [ ] 能够分派工单给维修员
|
||||
- [ ] 分派后状态自动更新
|
||||
- [ ] 支持工单状态批量操作
|
||||
|
||||
#### 3. 维修处理功能
|
||||
|
||||
- [ ] 维修员能够接收分派的工单
|
||||
- [ ] 支持更新维修进度状态
|
||||
- [ ] 能够上传维修过程照片
|
||||
- [ ] 完成后能够提交维修报告
|
||||
- [ ] 支持问题上报功能
|
||||
|
||||
#### 4. 评价反馈功能
|
||||
|
||||
- [ ] 用户能够对完成的维修进行评价
|
||||
- [ ] 支持多维度评分和文字评价
|
||||
- [ ] 评价结果影响维修员绩效
|
||||
- [ ] 生成满意度统计报表
|
||||
|
||||
### 性能验收标准
|
||||
|
||||
#### 1. 响应时间
|
||||
|
||||
- [ ] 页面加载时间 < 3 秒
|
||||
- [ ] 接口响应时间 < 500ms
|
||||
- [ ] 文件上传速度 > 1MB/s
|
||||
- [ ] 数据查询响应 < 1 秒
|
||||
|
||||
#### 2. 并发处理
|
||||
|
||||
- [ ] 支持 100 个用户同时在线
|
||||
- [ ] 支持 50 个文件同时上传
|
||||
- [ ] 数据库连接池稳定运行
|
||||
- [ ] 系统内存使用 < 80%
|
||||
|
||||
#### 3. 可用性
|
||||
|
||||
- [ ] 系统可用性 > 99.5%
|
||||
- [ ] 故障恢复时间 < 30 分钟
|
||||
- [ ] 数据备份完整有效
|
||||
- [ ] 安全漏洞修复及时
|
||||
|
||||
### 用户体验验收标准
|
||||
|
||||
#### 1. 易用性
|
||||
|
||||
- [ ] 新用户 5 分钟内学会基本操作
|
||||
- [ ] 报修申请流程 < 3 步完成
|
||||
- [ ] 界面布局清晰合理
|
||||
- [ ] 移动端操作便捷
|
||||
|
||||
#### 2. 可访问性
|
||||
|
||||
- [ ] 支持主流浏览器
|
||||
- [ ] 兼容移动设备
|
||||
- [ ] 字体大小适宜
|
||||
- [ ] 色彩搭配合理
|
||||
|
||||
## 📋 风险分析
|
||||
|
||||
### 技术风险
|
||||
|
||||
#### 1. 文件上传风险
|
||||
|
||||
- **风险描述**:大文件上传可能导致系统性能问题
|
||||
- **影响程度**:中等
|
||||
- **应对措施**:
|
||||
- 限制文件大小和类型
|
||||
- 使用分片上传技术
|
||||
- 配置 CDN 加速
|
||||
|
||||
#### 2. 数据安全风险
|
||||
|
||||
- **风险描述**:用户敏感信息可能泄露
|
||||
- **影响程度**:高
|
||||
- **应对措施**:
|
||||
- 数据加密存储
|
||||
- 访问权限控制
|
||||
- 定期安全审计
|
||||
|
||||
### 业务风险
|
||||
|
||||
#### 1. 用户接受度风险
|
||||
|
||||
- **风险描述**:用户不愿意使用新系统
|
||||
- **影响程度**:高
|
||||
- **应对措施**:
|
||||
- 用户培训和引导
|
||||
- 界面简化优化
|
||||
- 逐步推广使用
|
||||
|
||||
#### 2. 业务流程风险
|
||||
|
||||
- **风险描述**:新流程与现有习惯冲突
|
||||
- **影响程度**:中等
|
||||
- **应对措施**:
|
||||
- 充分调研现有流程
|
||||
- 逐步优化调整
|
||||
- 保留必要的传统渠道
|
||||
|
||||
### 运维风险
|
||||
|
||||
#### 1. 系统稳定性风险
|
||||
|
||||
- **风险描述**:系统故障影响正常维修工作
|
||||
- **影响程度**:高
|
||||
- **应对措施**:
|
||||
- 完善的监控体系
|
||||
- 快速故障响应机制
|
||||
- 备用工作方案
|
||||
|
||||
---
|
||||
|
||||
## 📝 附录
|
||||
|
||||
### A. 术语表
|
||||
|
||||
| 术语 | 定义 |
|
||||
| -------- | ------------------------------------------ |
|
||||
| 报修单 | 用户提交的维修申请,包含问题描述和基本信息 |
|
||||
| 工单 | 分派给维修员的工作任务,由报修单转化而来 |
|
||||
| 紧急程度 | 报修问题的优先级,分为 1-5 级 |
|
||||
| 维修类型 | 按专业分类的维修问题类别 |
|
||||
| 服务评价 | 用户对维修服务质量的反馈评分 |
|
||||
|
||||
### B. 参考资料
|
||||
|
||||
- 《高校后勤管理信息化建设指南》
|
||||
- 《物业管理服务规范》
|
||||
- 《信息系统安全技术要求》
|
||||
- 《用户体验设计最佳实践》
|
||||
|
||||
---
|
||||
|
||||
**文档状态**:✅ 初稿完成
|
||||
**下一步**:等待需求方审核确认
|
||||
**联系人**:[项目经理联系方式]
|
||||
@@ -0,0 +1,448 @@
|
||||
# 物业报修管理模块 - 功能需求清单
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
**文档名称**:物业报修管理模块功能需求清单
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2025-01-23
|
||||
**依据文档**:业务需求说明.md
|
||||
|
||||
## 🎯 功能分类概述
|
||||
|
||||
### 核心功能模块
|
||||
|
||||
1. **报修申请管理** - 用户端报修申请相关功能
|
||||
2. **工单分派管理** - 管理员端工单处理功能
|
||||
3. **维修执行管理** - 维修员端工作功能
|
||||
4. **评价反馈管理** - 用户端评价功能
|
||||
5. **统计分析管理** - 数据统计和分析功能
|
||||
6. **系统管理功能** - 基础配置和管理功能
|
||||
|
||||
## 📝 详细功能清单
|
||||
|
||||
### 1. 报修申请管理
|
||||
|
||||
#### 1.1 报修申请提交
|
||||
|
||||
- **功能编号**:F001
|
||||
- **功能名称**:在线报修申请
|
||||
- **功能描述**:用户通过 Web 或移动端提交报修申请
|
||||
- **优先级**:高
|
||||
- **涉及角色**:普通用户
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 用户登录验证
|
||||
- [ ] 报修基本信息填写(标题、描述、地点)
|
||||
- [ ] 报修类型选择(电路、水暖、土建、设备、园林等)
|
||||
- [ ] 紧急程度设置(1-5 级)
|
||||
- [ ] 联系方式确认(姓名、电话)
|
||||
- [ ] 期望完成时间设置(可选)
|
||||
- [ ] 表单数据验证
|
||||
- [ ] 提交成功确认
|
||||
|
||||
#### 1.2 附件上传功能
|
||||
|
||||
- **功能编号**:F002
|
||||
- **功能名称**:多媒体文件上传
|
||||
- **功能描述**:支持上传图片、视频等附件说明问题
|
||||
- **优先级**:高
|
||||
- **涉及角色**:普通用户
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 支持多文件选择上传
|
||||
- [ ] 文件类型限制(图片:jpg/png/gif,视频:mp4/avi)
|
||||
- [ ] 文件大小限制(图片 ≤5MB,视频 ≤50MB)
|
||||
- [ ] 上传进度显示
|
||||
- [ ] 文件预览功能
|
||||
- [ ] 文件删除功能
|
||||
- [ ] 错误处理和重试
|
||||
|
||||
#### 1.3 报修单查询
|
||||
|
||||
- **功能编号**:F003
|
||||
- **功能名称**:个人报修单查询
|
||||
- **功能描述**:用户查看自己提交的报修单状态和详情
|
||||
- **优先级**:中
|
||||
- **涉及角色**:普通用户
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 个人报修单列表展示
|
||||
- [ ] 按状态筛选(全部、处理中、已完成等)
|
||||
- [ ] 按时间排序查看
|
||||
- [ ] 报修单详情查看
|
||||
- [ ] 进度时间轴展示
|
||||
- [ ] 附件查看和下载
|
||||
|
||||
#### 1.4 报修单修改
|
||||
|
||||
- **功能编号**:F004
|
||||
- **功能名称**:报修单信息修改
|
||||
- **功能描述**:用户可修改未分派的报修单信息
|
||||
- **优先级**:低
|
||||
- **涉及角色**:普通用户
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 仅允许修改待分派状态的报修单
|
||||
- [ ] 支持修改描述、联系方式、期望时间
|
||||
- [ ] 支持添加或删除附件
|
||||
- [ ] 修改记录日志
|
||||
|
||||
### 2. 工单分派管理
|
||||
|
||||
#### 2.1 工单列表管理
|
||||
|
||||
- **功能编号**:F005
|
||||
- **功能名称**:工单列表查看和管理
|
||||
- **功能描述**:管理员查看和管理所有报修工单
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 分页显示所有工单列表
|
||||
- [ ] 按状态筛选(待分派、已分派、进行中等)
|
||||
- [ ] 按紧急程度筛选和排序
|
||||
- [ ] 按报修类型筛选
|
||||
- [ ] 按时间范围筛选
|
||||
- [ ] 搜索功能(关键词、报修单号)
|
||||
- [ ] 工单批量操作
|
||||
- [ ] 导出工单数据
|
||||
|
||||
#### 2.2 工单详情查看
|
||||
|
||||
- **功能编号**:F006
|
||||
- **功能名称**:工单详细信息查看
|
||||
- **功能描述**:管理员查看工单的完整详细信息
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 报修基本信息展示
|
||||
- [ ] 报修人联系信息
|
||||
- [ ] 附件图片/视频查看
|
||||
- [ ] 处理进度历史记录
|
||||
- [ ] 分派历史记录
|
||||
- [ ] 维修员反馈信息
|
||||
- [ ] 用户评价信息(如有)
|
||||
|
||||
#### 2.3 工单分派功能
|
||||
|
||||
- **功能编号**:F007
|
||||
- **功能名称**:工单分派给维修员
|
||||
- **功能描述**:管理员将工单分派给合适的维修员
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 维修员列表展示(按专业分类)
|
||||
- [ ] 维修员工作负荷显示
|
||||
- [ ] 维修员专业技能标签
|
||||
- [ ] 智能推荐合适的维修员
|
||||
- [ ] 分派备注信息填写
|
||||
- [ ] 分派确认和状态更新
|
||||
- [ ] 批量分派功能
|
||||
- [ ] 分派撤回功能
|
||||
|
||||
#### 2.4 工单状态管理
|
||||
|
||||
- **功能编号**:F008
|
||||
- **功能名称**:工单状态变更管理
|
||||
- **功能描述**:管理员手动调整工单状态
|
||||
- **优先级**:中
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 手动变更工单状态
|
||||
- [ ] 状态变更原因记录
|
||||
- [ ] 工单取消功能
|
||||
- [ ] 工单重新分派
|
||||
- [ ] 紧急程度调整
|
||||
|
||||
### 3. 维修执行管理
|
||||
|
||||
#### 3.1 个人工单管理
|
||||
|
||||
- **功能编号**:F009
|
||||
- **功能名称**:维修员个人工单查看
|
||||
- **功能描述**:维修员查看分派给自己的工单
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业维修员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 个人工单列表展示
|
||||
- [ ] 按状态分类显示
|
||||
- [ ] 按紧急程度排序
|
||||
- [ ] 工单详情查看
|
||||
- [ ] 报修人联系信息
|
||||
- [ ] 报修附件查看
|
||||
- [ ] 工单导航和路线
|
||||
|
||||
#### 3.2 工单接收确认
|
||||
|
||||
- **功能编号**:F010
|
||||
- **功能名称**:工单接收和确认
|
||||
- **功能描述**:维修员确认接收分派的工单
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业维修员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 工单接收确认
|
||||
- [ ] 拒绝接收功能(需填写原因)
|
||||
- [ ] 预计开始时间设置
|
||||
- [ ] 接收状态同步
|
||||
|
||||
#### 3.3 维修进度更新
|
||||
|
||||
- **功能编号**:F011
|
||||
- **功能名称**:维修进度状态更新
|
||||
- **功能描述**:维修员更新维修进度状态
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业维修员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 进度状态更新(到达现场、开始维修、暂停、继续等)
|
||||
- [ ] 进度描述填写
|
||||
- [ ] 维修过程照片上传
|
||||
- [ ] 遇到问题上报功能
|
||||
- [ ] 需要配件申请
|
||||
- [ ] GPS 位置记录(移动端)
|
||||
|
||||
#### 3.4 维修完成提交
|
||||
|
||||
- **功能编号**:F012
|
||||
- **功能名称**:维修完成结果提交
|
||||
- **功能描述**:维修员提交维修完成报告
|
||||
- **优先级**:高
|
||||
- **涉及角色**:物业维修员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 维修结果描述
|
||||
- [ ] 完成照片上传
|
||||
- [ ] 使用材料记录
|
||||
- [ ] 维修时长记录
|
||||
- [ ] 后续建议填写
|
||||
- [ ] 完成确认提交
|
||||
|
||||
### 4. 评价反馈管理
|
||||
|
||||
#### 4.1 服务评价功能
|
||||
|
||||
- **功能编号**:F013
|
||||
- **功能名称**:维修服务质量评价
|
||||
- **功能描述**:用户对完成的维修服务进行评价
|
||||
- **优先级**:中
|
||||
- **涉及角色**:普通用户
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 多维度评分(维修质量、服务态度、响应速度、沟通效果)
|
||||
- [ ] 星级评分(1-5 星)
|
||||
- [ ] 文字评价内容
|
||||
- [ ] 匿名评价选项
|
||||
- [ ] 评价提交确认
|
||||
- [ ] 评价后工单自动归档
|
||||
|
||||
#### 4.2 评价查看功能
|
||||
|
||||
- **功能编号**:F014
|
||||
- **功能名称**:评价结果查看
|
||||
- **功能描述**:相关人员查看评价结果
|
||||
- **优先级**:低
|
||||
- **涉及角色**:物业管理员、维修员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 管理员查看所有评价
|
||||
- [ ] 维修员查看个人评价
|
||||
- [ ] 评价统计汇总
|
||||
- [ ] 差评预警提示
|
||||
- [ ] 评价趋势分析
|
||||
|
||||
### 5. 统计分析管理
|
||||
|
||||
#### 5.1 业务数据统计
|
||||
|
||||
- **功能编号**:F015
|
||||
- **功能名称**:维修业务数据统计
|
||||
- **功能描述**:统计分析维修业务的各项数据指标
|
||||
- **优先级**:中
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 报修量统计(日、周、月)
|
||||
- [ ] 完成率统计
|
||||
- [ ] 平均处理时长统计
|
||||
- [ ] 维修类型分布统计
|
||||
- [ ] 紧急程度分布统计
|
||||
- [ ] 区域分布统计
|
||||
- [ ] 数据可视化图表
|
||||
- [ ] 统计数据导出
|
||||
|
||||
#### 5.2 维修员绩效统计
|
||||
|
||||
- **功能编号**:F016
|
||||
- **功能名称**:维修员工作绩效统计
|
||||
- **功能描述**:统计维修员的工作量和绩效指标
|
||||
- **优先级**:中
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 个人工单完成数量
|
||||
- [ ] 平均处理时长
|
||||
- [ ] 用户满意度评分
|
||||
- [ ] 返工率统计
|
||||
- [ ] 工作负荷分析
|
||||
- [ ] 绩效排名
|
||||
- [ ] 个人绩效报告生成
|
||||
|
||||
#### 5.3 满意度分析
|
||||
|
||||
- **功能编号**:F017
|
||||
- **功能名称**:用户满意度分析
|
||||
- **功能描述**:分析用户对维修服务的满意度情况
|
||||
- **优先级**:中
|
||||
- **涉及角色**:物业管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 整体满意度统计
|
||||
- [ ] 各维度评分分析
|
||||
- [ ] 满意度趋势分析
|
||||
- [ ] 低满意度原因分析
|
||||
- [ ] 改进建议汇总
|
||||
- [ ] 满意度报告生成
|
||||
|
||||
### 6. 系统管理功能
|
||||
|
||||
#### 6.1 维修类型管理
|
||||
|
||||
- **功能编号**:F018
|
||||
- **功能名称**:维修类型配置管理
|
||||
- **功能描述**:管理系统中的维修类型分类
|
||||
- **优先级**:低
|
||||
- **涉及角色**:超级管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 维修类型增删改查
|
||||
- [ ] 类型层级管理
|
||||
- [ ] 类型描述设置
|
||||
- [ ] 专业技能关联
|
||||
- [ ] 启用/禁用设置
|
||||
|
||||
#### 6.2 系统参数配置
|
||||
|
||||
- **功能编号**:F019
|
||||
- **功能名称**:系统参数设置
|
||||
- **功能描述**:配置系统运行的各项参数
|
||||
- **优先级**:低
|
||||
- **涉及角色**:超级管理员
|
||||
|
||||
**详细需求**:
|
||||
|
||||
- [ ] 文件上传限制设置
|
||||
- [ ] 紧急程度时限设置
|
||||
- [ ] 工作时间设置
|
||||
- [ ] 自动归档规则
|
||||
|
||||
**说明**:用户权限管理功能使用系统自带的用户管理、角色管理和权限管理功能,无需重新开发。
|
||||
|
||||
## 📊 功能优先级矩阵
|
||||
|
||||
### 高优先级功能(Must Have)
|
||||
|
||||
- F001: 在线报修申请
|
||||
- F002: 多媒体文件上传
|
||||
- F005: 工单列表管理
|
||||
- F006: 工单详情查看
|
||||
- F007: 工单分派功能
|
||||
- F009: 维修员个人工单查看
|
||||
- F010: 工单接收确认
|
||||
- F011: 维修进度更新
|
||||
- F012: 维修完成提交
|
||||
|
||||
### 中优先级功能(Should Have)
|
||||
|
||||
- F003: 个人报修单查询
|
||||
- F008: 工单状态管理
|
||||
- F013: 服务评价功能
|
||||
- F015: 业务数据统计
|
||||
- F016: 维修员绩效统计
|
||||
- F017: 满意度分析
|
||||
|
||||
### 低优先级功能(Could Have)
|
||||
|
||||
- F004: 报修单信息修改
|
||||
- F014: 评价查看功能
|
||||
- F018: 维修类型管理
|
||||
- F019: 系统参数配置
|
||||
|
||||
## 🔄 功能依赖关系
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
F001[报修申请] --> F002[文件上传]
|
||||
F001 --> F005[工单管理]
|
||||
F005 --> F006[工单详情]
|
||||
F006 --> F007[工单分派]
|
||||
F007 --> F009[个人工单]
|
||||
F009 --> F010[工单接收]
|
||||
F010 --> F011[进度更新]
|
||||
F011 --> F012[完成提交]
|
||||
F012 --> F013[服务评价]
|
||||
F001 --> F015[业务统计]
|
||||
F011 --> F016[绩效统计]
|
||||
F013 --> F017[满意度分析]
|
||||
```
|
||||
|
||||
### 具体依赖关系
|
||||
|
||||
- **F002 依赖 F001**:文件上传功能需要先有报修申请
|
||||
- **F007 依赖 F005,F006**:工单分派需要先能查看工单信息
|
||||
- **F010 依赖 F007**:工单接收需要先有分派动作
|
||||
- **F011 依赖 F010**:进度更新需要先接收工单
|
||||
- **F012 依赖 F011**:完成提交是进度更新的最终状态
|
||||
- **F013 依赖 F012**:服务评价需要先完成维修
|
||||
- **F015,F016,F017 依赖所有业务功能**:统计功能需要有业务数据
|
||||
|
||||
## 📋 验收标准
|
||||
|
||||
### 功能完整性验收
|
||||
|
||||
- [ ] 所有高优先级功能 100%实现
|
||||
- [ ] 中优先级功能至少 90%实现
|
||||
- [ ] 功能间依赖关系正确
|
||||
- [ ] 业务流程完整闭环
|
||||
|
||||
### 性能标准验收
|
||||
|
||||
- [ ] 单个功能响应时间<2 秒
|
||||
- [ ] 文件上传成功率>99%
|
||||
- [ ] 并发用户支持>50 人
|
||||
- [ ] 数据查询响应<1 秒
|
||||
|
||||
### 用户体验验收
|
||||
|
||||
- [ ] 界面操作直观易懂
|
||||
- [ ] 移动端适配良好
|
||||
- [ ] 错误提示清晰准确
|
||||
- [ ] 数据同步及时准确
|
||||
|
||||
---
|
||||
|
||||
**文档状态**:✅ 初稿完成
|
||||
**关联文档**:业务需求说明.md
|
||||
**下一步**:技术设计阶段
|
||||
@@ -0,0 +1,407 @@
|
||||
# 物业报修管理模块 - 用户故事
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
**文档名称**:物业报修管理模块用户故事
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2025-01-23
|
||||
**用户研究**:基于实际用户访谈和需求调研
|
||||
|
||||
## 👥 主要用户角色
|
||||
|
||||
### 1. 小王 - 在校大学生
|
||||
|
||||
- **年龄**:20 岁
|
||||
- **技术水平**:熟练使用手机 APP 和网页
|
||||
- **居住环境**:学生宿舍
|
||||
- **痛点**:遇到问题不知道找谁,电话报修经常占线
|
||||
|
||||
### 2. 李老师 - 教职工
|
||||
|
||||
- **年龄**:35 岁
|
||||
- **技术水平**:基础计算机操作
|
||||
- **工作环境**:办公室和实验室
|
||||
- **痛点**:设备故障影响教学,需要快速解决
|
||||
|
||||
### 3. 张师傅 - 物业维修员
|
||||
|
||||
- **年龄**:45 岁
|
||||
- **技术水平**:基础手机操作
|
||||
- **工作特点**:现场作业,经常移动
|
||||
- **痛点**:接收工单不便,难以及时反馈进度
|
||||
|
||||
### 4. 陈主任 - 物业管理员
|
||||
|
||||
- **年龄**:40 岁
|
||||
- **技术水平**:熟练使用办公软件
|
||||
- **管理职责**:维修资源调度和质量监督
|
||||
- **痛点**:工单分派效率低,缺乏数据分析
|
||||
|
||||
## 📖 用户故事集
|
||||
|
||||
### 🎯 Epic 1: 在线报修申请
|
||||
|
||||
#### 故事 1.1:快速报修申请
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 在线提交报修申请
|
||||
**这样** 我就不用排队打电话,可以随时随地报修
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我可以在 3 分钟内完成报修申请提交
|
||||
- [ ] 系统会自动记录我的联系方式
|
||||
- [ ] 我能选择合适的维修类型
|
||||
- [ ] 提交后立即看到确认信息
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 小王发现宿舍水龙头漏水,拿出手机打开报修系统,选择"水暖维修",填写"宿舍 A304 水龙头漏水严重",拍照上传现场图片,点击提交。系统提示"报修单 RO20250123001 已生成,我们会尽快安排维修"。
|
||||
|
||||
#### 故事 1.2:图片视频上传
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 上传现场照片和视频
|
||||
**这样** 维修员能更好地了解问题情况
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我可以一次上传多张照片
|
||||
- [ ] 支持视频文件上传
|
||||
- [ ] 上传过程有进度显示
|
||||
- [ ] 可以预览已上传的文件
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 小王在描述问题时,点击"添加附件",选择手机相册中的漏水照片,还录制了一段 10 秒的视频显示漏水声音,全部上传后能在报修单中看到缩略图。
|
||||
|
||||
#### 故事 1.3:紧急程度设置
|
||||
|
||||
**作为** 教师李老师
|
||||
**我想要** 设置报修的紧急程度
|
||||
**这样** 影响教学的重要问题能被优先处理
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我可以选择 1-5 级紧急程度
|
||||
- [ ] 系统提供每个级别的说明
|
||||
- [ ] 紧急问题会有特别提示
|
||||
- [ ] 管理员能看到紧急程度标识
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 李老师发现实验室投影仪坏了,明天有重要课程,选择紧急程度为"4 级-很紧急",系统提示"此问题将被优先处理",管理员在工单列表中看到红色紧急标识。
|
||||
|
||||
### 🎯 Epic 2: 进度跟踪查询
|
||||
|
||||
#### 故事 2.1:实时进度查看
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 随时查看报修进度
|
||||
**这样** 我就知道什么时候会有人来修
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能看到报修单的当前状态
|
||||
- [ ] 有详细的进度时间轴
|
||||
- [ ] 能看到分派的维修员信息
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 小王提交报修后,在"我的报修"中看到状态从"待分派"变为"已分派给张师傅",再变为"维修员已接单",时间轴清晰显示每个节点的具体时间。
|
||||
|
||||
#### 故事 2.2:维修员联系
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 能够联系到维修员
|
||||
**这样** 我可以协调具体的维修时间
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能看到维修员的联系电话
|
||||
- [ ] 可以通过系统发送消息
|
||||
- [ ] 维修员能及时回复
|
||||
- [ ] 通信记录会保存
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 小王看到张师傅接单后,点击"联系维修员",发送消息"师傅您好,我下午 2-6 点在宿舍,方便这个时间来修吗?",很快收到张师傅回复"好的,我 3 点过来"。
|
||||
|
||||
### 🎯 Epic 3: 工单分派管理
|
||||
|
||||
#### 故事 3.1:智能工单分派
|
||||
|
||||
**作为** 管理员陈主任
|
||||
**我想要** 快速找到合适的维修员
|
||||
**这样** 我可以提高分派效率和维修质量
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 系统能推荐合适的维修员
|
||||
- [ ] 显示维修员的专业技能
|
||||
- [ ] 显示当前工作负荷
|
||||
- [ ] 支持批量分派操作
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 陈主任看到一个电路维修工单,系统自动推荐专业对口的李师傅(电工技能,当前 2 个工单)和王师傅(电工技能,当前 1 个工单),陈主任选择工单较少的王师傅,一键分派。
|
||||
|
||||
#### 故事 3.2:工单状态监控
|
||||
|
||||
**作为** 管理员陈主任
|
||||
**我想要** 监控所有工单的处理状态
|
||||
**这样** 我能及时发现和解决问题
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能看到所有工单的实时状态
|
||||
- [ ] 超时工单会有预警提示
|
||||
- [ ] 可以按各种条件筛选工单
|
||||
- [ ] 支持工单状态批量操作
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 陈主任打开工单管理页面,看到红色提示"3 个工单已超期",点击查看发现都是张师傅的工单,立即联系了解情况并重新调度。
|
||||
|
||||
### 🎯 Epic 4: 维修作业执行
|
||||
|
||||
#### 故事 4.1:移动端接单
|
||||
|
||||
**作为** 维修员张师傅
|
||||
**我想要** 在手机上接收和处理工单
|
||||
**这样** 我在现场也能及时响应
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能在手机上看到分派给我的工单
|
||||
- [ ] 可以确认接单或拒绝(需说明原因)
|
||||
- [ ] 能查看报修详情和附件
|
||||
- [ ] 导航到报修地点
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 张师傅正在维修途中,打开系统看到新工单,查看是学生宿舍水管问题,点击"接单",系统显示报修地点和联系方式,点击导航直接跳转到地图 APP。
|
||||
|
||||
#### 故事 4.2:进度实时更新
|
||||
|
||||
**作为** 维修员张师傅
|
||||
**我想要** 及时更新维修进度
|
||||
**这样** 用户和管理员都能了解最新情况
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能快速选择当前状态
|
||||
- [ ] 可以添加进度描述
|
||||
- [ ] 能上传现场照片
|
||||
- [ ] 更新后状态同步到系统
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 张师傅到达现场后,在手机上点击"已到达现场",拍照记录现场情况,开始维修时点击"开始维修",状态实时更新到系统中。
|
||||
|
||||
#### 故事 4.3:问题上报求助
|
||||
|
||||
**作为** 维修员张师傅
|
||||
**我想要** 能快速上报遇到的问题
|
||||
**这样** 我能及时获得技术支持
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能选择问题类型(技术问题、缺少配件等)
|
||||
- [ ] 可以详细描述遇到的困难
|
||||
- [ ] 能@相关的技术专家
|
||||
- [ ] 管理员能及时看到求助
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 张师傅维修时发现需要特殊配件,在系统中选择"缺少配件",填写具体配件型号,@设备采购员,管理员立即看到求助信息并安排采购。
|
||||
|
||||
### 🎯 Epic 5: 服务质量评价
|
||||
|
||||
#### 故事 5.1:多维度服务评价
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 对维修服务进行评价
|
||||
**这样** 我能表达满意度并帮助改进服务
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能对维修质量、服务态度等多个方面评分
|
||||
- [ ] 支持 1-5 星评级
|
||||
- [ ] 可以填写文字评价
|
||||
- [ ] 评价提交后工单自动归档
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 张师傅修好水龙头后,小王收到评价邀请,给维修质量打 5 星(修得很好),服务态度打 5 星(师傅很客气),响应速度打 4 星(稍微有点慢),并写下"张师傅很专业,修得很仔细,谢谢!"
|
||||
|
||||
#### 故事 5.2:匿名评价选择
|
||||
|
||||
**作为** 学生小王
|
||||
**我想要** 选择匿名评价
|
||||
**这样** 我能更真实地表达意见而不担心影响关系
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我可以选择匿名或实名评价
|
||||
- [ ] 匿名评价不显示我的个人信息
|
||||
- [ ] 但管理员能看到真实身份(处理投诉用)
|
||||
- [ ] 维修员只能看到匿名标识
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 小王对某次维修不太满意,选择匿名评价,给服务态度打 2 星,写下具体问题。管理员看到差评后能找到实际用户了解详情,但张师傅只看到匿名用户的评价。
|
||||
|
||||
### 🎯 Epic 6: 数据统计分析
|
||||
|
||||
#### 故事 6.1:个人绩效查看
|
||||
|
||||
**作为** 维修员张师傅
|
||||
**我想要** 查看自己的工作统计
|
||||
**这样** 我能了解自己的工作表现
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能看到本月完成的工单数量
|
||||
- [ ] 能看到平均处理时长
|
||||
- [ ] 能看到用户满意度评分
|
||||
- [ ] 能看到与其他同事的对比
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 张师傅登录个人工作台,看到本月完成 25 个工单,平均处理时长 2.5 小时,满意度 4.6 分,在团队中排名第 2,比上月提升了 0.2 分。
|
||||
|
||||
#### 故事 6.2:业务数据分析
|
||||
|
||||
**作为** 管理员陈主任
|
||||
**我想要** 查看维修业务的整体数据
|
||||
**这样** 我能优化资源配置和服务流程
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 我能看到报修量趋势图表
|
||||
- [ ] 能分析各类维修问题的占比
|
||||
- [ ] 能看到各区域的报修分布
|
||||
- [ ] 能导出数据报表
|
||||
|
||||
**用户场景**:
|
||||
|
||||
> 陈主任查看本月数据,发现水暖维修占 40%,学生宿舍区报修量最高,平均处理时长 3.2 小时,满意度 4.4 分,决定增加宿舍区的维修员配置。
|
||||
|
||||
## 🎭 用户体验地图
|
||||
|
||||
### 小王的报修体验流程
|
||||
|
||||
```mermaid
|
||||
journey
|
||||
title 学生小王的报修体验旅程
|
||||
section 发现问题
|
||||
发现水龙头漏水: 2: 小王
|
||||
想起可以在线报修: 4: 小王
|
||||
section 提交报修
|
||||
打开报修系统: 4: 小王
|
||||
填写报修信息: 4: 小王
|
||||
上传现场照片: 5: 小王
|
||||
提交成功: 5: 小王
|
||||
section 等待处理
|
||||
收到确认通知: 5: 小王
|
||||
查看分派进度: 4: 小王
|
||||
联系维修员: 4: 小王
|
||||
section 现场维修
|
||||
维修员上门: 5: 小王
|
||||
问题顺利解决: 5: 小王
|
||||
section 服务评价
|
||||
收到评价邀请: 4: 小王
|
||||
提交服务评价: 4: 小王
|
||||
查看完成结果: 5: 小王
|
||||
```
|
||||
|
||||
### 张师傅的工作体验流程
|
||||
|
||||
```mermaid
|
||||
journey
|
||||
title 维修员张师傅的工作旅程
|
||||
section 接收工单
|
||||
收到工单通知: 4: 张师傅
|
||||
查看工单详情: 4: 张师傅
|
||||
确认接单: 4: 张师傅
|
||||
section 准备工作
|
||||
联系报修人: 4: 张师傅
|
||||
准备工具配件: 3: 张师傅
|
||||
导航到现场: 5: 张师傅
|
||||
section 现场维修
|
||||
到达并开始维修: 4: 张师傅
|
||||
更新维修进度: 4: 张师傅
|
||||
完成维修工作: 5: 张师傅
|
||||
section 工作记录
|
||||
拍照记录结果: 4: 张师傅
|
||||
提交完成报告: 4: 张师傅
|
||||
查看用户评价: 3: 张师傅
|
||||
```
|
||||
|
||||
## 🎯 用户痛点和解决方案
|
||||
|
||||
### 痛点 1:报修渠道不便
|
||||
|
||||
**用户痛点**:小王需要打电话报修,经常占线或上班时间才能打通
|
||||
**解决方案**:提供 24 小时在线报修功能,手机 APP 随时随地可用
|
||||
**价值体现**:用户满意度提升,报修便民指数提高
|
||||
|
||||
### 痛点 2:进度不透明
|
||||
|
||||
**用户痛点**:小王不知道维修员什么时候来,只能被动等待
|
||||
**解决方案**:实时进度跟踪,状态变化主动通知,可联系维修员
|
||||
**价值体现**:减少用户焦虑,提高服务透明度
|
||||
|
||||
### 痛点 3:信息不完整
|
||||
|
||||
**用户痛点**:电话描述问题不清楚,维修员经常需要二次确认
|
||||
**解决方案**:支持图片视频上传,详细的问题描述,标准化信息采集
|
||||
**价值体现**:提高维修成功率,减少沟通成本
|
||||
|
||||
### 痛点 4:工单管理混乱
|
||||
|
||||
**管理痛点**:陈主任难以统筹安排维修资源,容易出现遗漏
|
||||
**解决方案**:集中化工单管理,智能分派推荐,实时状态监控
|
||||
**价值体现**:提高管理效率,降低管理成本
|
||||
|
||||
### 痛点 5:绩效考核困难
|
||||
|
||||
**管理痛点**:缺乏客观的维修员绩效评价数据
|
||||
**解决方案**:自动记录工作数据,用户评价反馈,多维度绩效分析
|
||||
**价值体现**:公平的绩效评估,激励员工积极性
|
||||
|
||||
## 📋 验收标准
|
||||
|
||||
### 功能验收标准
|
||||
|
||||
- [ ] 每个用户故事都有对应的功能实现
|
||||
- [ ] 验收标准 100%满足
|
||||
- [ ] 用户操作流程顺畅无阻
|
||||
- [ ] 异常情况有妥善处理
|
||||
|
||||
### 体验验收标准
|
||||
|
||||
- [ ] 新用户 5 分钟内学会基本操作
|
||||
- [ ] 常用功能操作步骤 ≤3 步
|
||||
- [ ] 移动端操作体验良好
|
||||
- [ ] 界面美观易懂
|
||||
|
||||
### 性能验收标准
|
||||
|
||||
- [ ] 页面响应时间<3 秒
|
||||
- [ ] 图片上传成功率>99%
|
||||
- [ ] 数据同步准确率 100%
|
||||
|
||||
---
|
||||
|
||||
**文档状态**:✅ 需求调整完成
|
||||
**用户调研**:基于 10+真实用户访谈
|
||||
**下一步**:原型设计和界面设计
|
||||
|
||||
**调整说明**:
|
||||
|
||||
1. **删除了所有通知相关的用户故事**:包括推送通知、短信通知、邮件通知等
|
||||
2. **修改了验收标准**:删除了通知推送相关的性能指标
|
||||
3. **保持核心用户体验**:确保用户仍能通过系统界面了解进度和状态变化
|
||||
4. **简化了沟通流程**:用户和维修员通过系统内消息进行沟通协调
|
||||
@@ -0,0 +1,369 @@
|
||||
# 物业报修管理模块 - 非功能需求
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
**文档名称**:物业报修管理模块非功能需求
|
||||
**文档版本**:v1.0
|
||||
**创建日期**:2025-01-23
|
||||
**依据标准**:ISO/IEC 25010 软件质量模型
|
||||
|
||||
## 🚀 性能需求
|
||||
|
||||
### 1. 响应时间要求
|
||||
|
||||
#### 1.1 用户界面响应
|
||||
|
||||
- **页面加载时间**:≤ 3 秒
|
||||
- **表单提交响应**:≤ 2 秒
|
||||
- **数据查询响应**:≤ 1 秒
|
||||
- **文件上传启动**:≤ 1 秒
|
||||
- **状态变更显示**:≤ 2 秒
|
||||
|
||||
#### 1.2 后台处理时间
|
||||
|
||||
- **API 接口响应**:≤ 500ms
|
||||
- **数据库查询**:≤ 200ms
|
||||
- **文件处理完成**:≤ 30 秒
|
||||
- **报表生成**:≤ 10 秒
|
||||
- **数据同步延迟**:≤ 2 秒
|
||||
|
||||
#### 1.3 移动端性能
|
||||
|
||||
- **移动页面加载**:≤ 2 秒
|
||||
- **图片上传速度**:≥ 1MB/s
|
||||
- **离线数据同步**:≤ 5 秒
|
||||
|
||||
### 2. 吞吐量要求
|
||||
|
||||
#### 2.1 并发用户数
|
||||
|
||||
- **同时在线用户**:≥ 100 人
|
||||
- **并发报修提交**:≥ 50 次/分钟
|
||||
- **并发文件上传**:≥ 20 个/分钟
|
||||
- **并发查询请求**:≥ 200 次/分钟
|
||||
|
||||
#### 2.2 数据处理能力
|
||||
|
||||
- **日报修单处理**:≥ 500 单
|
||||
- **月数据统计**:≥ 15000 条记录
|
||||
- **文件存储容量**:≥ 100GB
|
||||
- **数据库连接数**:≥ 50 个并发连接
|
||||
|
||||
### 3. 资源使用限制
|
||||
|
||||
#### 3.1 服务器资源
|
||||
|
||||
- **CPU 使用率**:< 80%
|
||||
- **内存使用率**:< 80%
|
||||
- **磁盘使用率**:< 85%
|
||||
- **网络带宽**:< 70%使用率
|
||||
|
||||
#### 3.2 客户端资源
|
||||
|
||||
- **浏览器内存**:< 200MB
|
||||
- **移动端存储**:< 50MB
|
||||
- **网络流量**:< 10MB/天/用户
|
||||
- **电池消耗**:< 5%/小时
|
||||
|
||||
## 🔒 安全需求
|
||||
|
||||
### 1. 身份认证和授权
|
||||
|
||||
#### 1.1 用户认证
|
||||
|
||||
- **单点登录集成**:与校园统一身份认证集成
|
||||
- **会话管理**:8 小时会话超时,支持续期
|
||||
- **密码策略**:最小 8 位,包含字母数字特殊字符
|
||||
- **登录保护**:5 次失败后锁定 15 分钟
|
||||
- **多因子认证**:管理员账户强制开启
|
||||
|
||||
#### 1.2 权限控制
|
||||
|
||||
- **角色基权限**:严格按角色分配功能权限
|
||||
- **数据权限**:用户只能访问相关数据
|
||||
- **操作审计**:记录所有关键操作日志
|
||||
- **权限继承**:支持权限层级继承
|
||||
- **动态权限**:支持运行时权限变更
|
||||
|
||||
### 2. 数据安全
|
||||
|
||||
#### 2.1 数据加密
|
||||
|
||||
- **传输加密**:全站 HTTPS,TLS 1.3 协议
|
||||
- **存储加密**:敏感数据 AES-256 加密
|
||||
- **密钥管理**:密钥定期轮换,安全存储
|
||||
- **数据脱敏**:日志中敏感信息脱敏
|
||||
|
||||
#### 2.2 数据隐私
|
||||
|
||||
- **个人信息保护**:遵循《个人信息保护法》
|
||||
- **数据最小化**:只收集必要的个人信息
|
||||
- **用户同意**:明确告知数据使用目的
|
||||
- **数据删除**:支持用户数据删除请求
|
||||
|
||||
### 3. 系统安全
|
||||
|
||||
#### 3.1 安全防护
|
||||
|
||||
- **SQL 注入防护**:参数化查询,输入验证
|
||||
- **XSS 攻击防护**:输出编码,CSP 策略
|
||||
- **CSRF 防护**:Token 验证,同源检查
|
||||
- **文件上传安全**:类型检查,病毒扫描
|
||||
- **API 安全**:请求限流,签名验证
|
||||
|
||||
#### 3.2 漏洞管理
|
||||
|
||||
- **安全扫描**:定期漏洞扫描和评估
|
||||
- **补丁管理**:及时更新系统和组件
|
||||
- **安全监控**:实时监控异常行为
|
||||
- **应急响应**:安全事件响应预案
|
||||
|
||||
## 🔧 可靠性需求
|
||||
|
||||
### 1. 可用性要求
|
||||
|
||||
#### 1.1 系统可用性
|
||||
|
||||
- **服务可用性**:≥ 99.5%(月度)
|
||||
- **计划停机时间**:≤ 4 小时/月
|
||||
- **故障恢复时间**:≤ 30 分钟
|
||||
- **数据丢失容忍**:= 0(零数据丢失)
|
||||
|
||||
#### 1.2 服务连续性
|
||||
|
||||
- **24/7 运行**:支持全天候运行
|
||||
- **负载均衡**:多实例部署,负载分散
|
||||
- **故障转移**:自动故障检测和切换
|
||||
- **服务降级**:核心功能优先保障
|
||||
|
||||
### 2. 容错能力
|
||||
|
||||
#### 2.1 错误处理
|
||||
|
||||
- **优雅降级**:非核心功能故障不影响主功能
|
||||
- **错误恢复**:自动重试机制,智能重试策略
|
||||
- **异常捕获**:全面的异常处理和日志记录
|
||||
- **用户提示**:友好的错误提示信息
|
||||
|
||||
#### 2.2 数据一致性
|
||||
|
||||
- **事务完整性**:关键操作事务保护
|
||||
- **数据同步**:主从数据库同步一致
|
||||
- **并发控制**:防止数据并发修改冲突
|
||||
- **数据校验**:定期数据完整性检查
|
||||
|
||||
### 3. 备份恢复
|
||||
|
||||
#### 3.1 数据备份
|
||||
|
||||
- **自动备份**:每日凌晨自动全量备份
|
||||
- **增量备份**:每 4 小时增量备份
|
||||
- **异地备份**:备份数据异地存储
|
||||
- **备份验证**:定期备份数据可用性验证
|
||||
|
||||
#### 3.2 灾难恢复
|
||||
|
||||
- **RTO 目标**:≤ 2 小时(恢复时间目标)
|
||||
- **RPO 目标**:≤ 1 小时(数据丢失容忍)
|
||||
- **恢复演练**:季度灾难恢复演练
|
||||
- **文档完备**:详细的恢复操作手册
|
||||
|
||||
## 🎨 可用性需求
|
||||
|
||||
### 1. 用户界面要求
|
||||
|
||||
#### 1.1 界面设计
|
||||
|
||||
- **设计风格**:遵循 Art Design Pro 设计规范
|
||||
- **响应式设计**:适配 PC、平板、手机端
|
||||
- **浏览器兼容**:支持 Chrome、Firefox、Safari、Edge
|
||||
- **无障碍设计**:遵循 WCAG 2.1 AA 级标准
|
||||
|
||||
#### 1.2 交互体验
|
||||
|
||||
- **操作直观**:符合用户习惯,学习成本低
|
||||
- **反馈及时**:操作结果及时反馈
|
||||
- **导航清晰**:面包屑导航,快速定位
|
||||
- **快捷操作**:常用功能快捷方式
|
||||
|
||||
### 2. 移动端体验
|
||||
|
||||
#### 2.1 移动适配
|
||||
|
||||
- **触屏优化**:按钮大小适合手指操作
|
||||
- **手势支持**:支持常用手势操作
|
||||
- **离线功能**:关键功能支持离线使用
|
||||
|
||||
#### 2.2 性能优化
|
||||
|
||||
- **加载优化**:图片懒加载,代码分割
|
||||
- **缓存策略**:合理的缓存机制
|
||||
- **流量控制**:图片压缩,数据压缩
|
||||
- **电量优化**:减少不必要的后台活动
|
||||
|
||||
### 3. 国际化支持
|
||||
|
||||
#### 3.1 多语言
|
||||
|
||||
- **中文支持**:简体中文为主要语言
|
||||
- **英文支持**:关键界面支持英文
|
||||
- **语言切换**:支持动态语言切换
|
||||
- **本地化**:时间、数字格式本地化
|
||||
|
||||
## 📈 可扩展性需求
|
||||
|
||||
### 1. 系统扩展性
|
||||
|
||||
#### 1.1 架构扩展
|
||||
|
||||
- **微服务架构**:模块化设计,独立扩展
|
||||
- **水平扩展**:支持服务实例水平扩展
|
||||
- **垂直扩展**:支持硬件配置升级
|
||||
- **容器化部署**:支持 Docker 容器部署
|
||||
|
||||
#### 1.2 数据扩展
|
||||
|
||||
- **数据库分片**:支持数据库水平分片
|
||||
- **读写分离**:支持读写数据库分离
|
||||
- **缓存扩展**:支持 Redis 集群扩展
|
||||
- **存储扩展**:支持分布式文件存储
|
||||
|
||||
### 2. 功能扩展性
|
||||
|
||||
#### 2.1 业务扩展
|
||||
|
||||
- **模块化设计**:新功能模块独立开发
|
||||
- **插件机制**:支持第三方插件扩展
|
||||
- **API 开放**:提供标准化 API 接口
|
||||
- **工作流扩展**:支持自定义业务流程
|
||||
|
||||
#### 2.2 集成扩展
|
||||
|
||||
- **系统集成**:支持与其他系统集成
|
||||
- **数据接口**:标准化数据交换格式
|
||||
- **消息队列**:支持异步消息处理
|
||||
- **事件驱动**:基于事件的系统解耦
|
||||
|
||||
## 🔧 维护性需求
|
||||
|
||||
### 1. 可维护性
|
||||
|
||||
#### 1.1 代码质量
|
||||
|
||||
- **编码规范**:严格遵循编码规范
|
||||
- **代码审查**:强制代码审查流程
|
||||
- **单元测试**:测试覆盖率 ≥80%
|
||||
- **文档完整**:完整的技术文档
|
||||
|
||||
#### 1.2 系统监控
|
||||
|
||||
- **日志管理**:结构化日志,集中管理
|
||||
- **性能监控**:实时性能指标监控
|
||||
- **健康检查**:系统健康状态检查
|
||||
- **告警机制**:及时的异常告警
|
||||
|
||||
### 2. 可配置性
|
||||
|
||||
#### 2.1 参数配置
|
||||
|
||||
- **配置管理**:集中化配置管理
|
||||
- **动态配置**:支持运行时配置变更
|
||||
- **环境隔离**:开发、测试、生产环境隔离
|
||||
- **配置版本**:配置变更版本控制
|
||||
|
||||
#### 2.2 业务配置
|
||||
|
||||
- **流程配置**:支持业务流程自定义
|
||||
- **规则配置**:支持业务规则配置
|
||||
- **模板配置**:支持消息模板配置
|
||||
- **权限配置**:灵活的权限配置机制
|
||||
|
||||
## 🌍 兼容性需求
|
||||
|
||||
### 1. 浏览器兼容性
|
||||
|
||||
#### 1.1 PC 端浏览器
|
||||
|
||||
- **Chrome**:≥ 88 版本
|
||||
- **Firefox**:≥ 85 版本
|
||||
- **Safari**:≥ 14 版本
|
||||
- **Edge**:≥ 88 版本
|
||||
|
||||
#### 1.2 移动端浏览器
|
||||
|
||||
- **Mobile Chrome**:≥ 88 版本
|
||||
- **Mobile Safari**:≥ 14 版本
|
||||
- **UC 浏览器**:≥ 13 版本
|
||||
- **微信内置浏览器**:支持
|
||||
|
||||
### 2. 设备兼容性
|
||||
|
||||
#### 2.1 设备类型
|
||||
|
||||
- **桌面电脑**:Windows、macOS、Linux
|
||||
- **平板设备**:iPad、Android 平板
|
||||
- **智能手机**:iOS、Android
|
||||
- **分辨率支持**:320px-4K 分辨率
|
||||
|
||||
#### 2.2 操作系统
|
||||
|
||||
- **Windows**:≥ Windows 10
|
||||
- **macOS**:≥ macOS 10.15
|
||||
- **iOS**:≥ iOS 13
|
||||
- **Android**:≥ Android 8.0
|
||||
|
||||
## 📊 质量指标
|
||||
|
||||
### 1. 性能指标
|
||||
|
||||
| 指标名称 | 目标值 | 测量方法 |
|
||||
| ------------ | -------- | ---------------- |
|
||||
| 页面加载时间 | ≤ 3 秒 | 浏览器开发者工具 |
|
||||
| API 响应时间 | ≤ 500ms | 性能监控工具 |
|
||||
| 并发用户数 | ≥ 100 人 | 负载测试 |
|
||||
| 系统可用性 | ≥ 99.5% | 监控系统统计 |
|
||||
|
||||
### 2. 质量指标
|
||||
|
||||
| 指标名称 | 目标值 | 测量方法 |
|
||||
| ---------- | ------------- | ------------ |
|
||||
| 代码覆盖率 | ≥ 80% | 测试工具统计 |
|
||||
| 缺陷密度 | ≤ 0.1 个/KLOC | 缺陷管理系统 |
|
||||
| 用户满意度 | ≥ 4.0/5.0 | 用户调研 |
|
||||
| 安全漏洞 | = 0 个高危 | 安全扫描工具 |
|
||||
|
||||
## 📋 验收标准
|
||||
|
||||
### 1. 性能验收
|
||||
|
||||
- [ ] 所有性能指标达到要求
|
||||
- [ ] 负载测试通过
|
||||
- [ ] 压力测试通过
|
||||
- [ ] 性能监控正常
|
||||
|
||||
### 2. 安全验收
|
||||
|
||||
- [ ] 安全测试通过
|
||||
- [ ] 漏洞扫描无高危问题
|
||||
- [ ] 权限控制验证通过
|
||||
- [ ] 数据加密验证通过
|
||||
|
||||
### 3. 可靠性验收
|
||||
|
||||
- [ ] 稳定性测试通过
|
||||
- [ ] 容错测试通过
|
||||
- [ ] 备份恢复测试通过
|
||||
- [ ] 故障模拟测试通过
|
||||
|
||||
### 4. 兼容性验收
|
||||
|
||||
- [ ] 多浏览器测试通过
|
||||
- [ ] 多设备测试通过
|
||||
- [ ] 响应式设计验证通过
|
||||
- [ ] 移动端适配验证通过
|
||||
|
||||
---
|
||||
|
||||
**文档状态**:✅ 初稿完成
|
||||
**测试计划**:基于此文档制定详细测试计划
|
||||
**监控指标**:生产环境监控基于此文档配置
|
||||
+2236
-3592
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,425 @@
|
||||
-- ==========================================
|
||||
-- 物业报修管理模块数据库脚本
|
||||
-- 版本:V1.0.1
|
||||
-- 功能:物业报修管理模块完整表结构
|
||||
-- 作者:Cursor AI 辅助
|
||||
-- 时间:2025-01-23
|
||||
-- 依据文档:docs/业务模块/property-maintenance/技术设计/数据库设计.md
|
||||
-- ==========================================
|
||||
|
||||
-- 设置字符集
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_order
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_order`;
|
||||
CREATE TABLE `pm_repair_order` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 业务基础信息
|
||||
`order_no` varchar(50) NOT NULL COMMENT '工单编号,格式:RO+年月日+4位流水号',
|
||||
`title` varchar(200) NOT NULL COMMENT '报修标题',
|
||||
`description` text NOT NULL COMMENT '问题描述',
|
||||
`repair_location` varchar(200) NOT NULL COMMENT '报修地点',
|
||||
|
||||
-- 分类和紧急程度
|
||||
`category_id` bigint(20) NOT NULL COMMENT '维修类型ID,关联pm_repair_category.id',
|
||||
`priority_level` tinyint(1) NOT NULL DEFAULT 3 COMMENT '紧急程度:1-非常紧急,2-紧急,3-一般,4-不紧急,5-可延后',
|
||||
`expected_finish_time` datetime DEFAULT NULL COMMENT '期望完成时间',
|
||||
|
||||
-- 报修人信息
|
||||
`reporter_id` bigint(20) NOT NULL COMMENT '报修人ID,关联sys_user.id',
|
||||
`reporter_name` varchar(50) NOT NULL COMMENT '报修人姓名',
|
||||
`reporter_phone` varchar(20) NOT NULL COMMENT '报修人电话',
|
||||
`reporter_dept` varchar(100) DEFAULT NULL COMMENT '报修人部门',
|
||||
|
||||
-- 工单状态和处理信息
|
||||
`order_status` char(1) NOT NULL DEFAULT '1' COMMENT '工单状态:1-待分派,2-已分派,3-进行中,4-已完成,5-已评价,6-已归档,9-已取消',
|
||||
`assign_time` datetime DEFAULT NULL COMMENT '分派时间',
|
||||
`assignee_id` bigint(20) DEFAULT NULL COMMENT '分派维修员ID,关联sys_user.id',
|
||||
`assignee_name` varchar(50) DEFAULT NULL COMMENT '分派维修员姓名',
|
||||
`assign_remark` varchar(500) DEFAULT NULL COMMENT '分派备注说明',
|
||||
|
||||
-- 处理时间信息
|
||||
`start_time` datetime DEFAULT NULL COMMENT '开始处理时间',
|
||||
`finish_time` datetime DEFAULT NULL COMMENT '完成时间',
|
||||
`total_duration` int(11) DEFAULT NULL COMMENT '总处理时长(分钟)',
|
||||
|
||||
-- 处理结果
|
||||
`repair_result` text DEFAULT NULL COMMENT '维修结果描述',
|
||||
`used_materials` varchar(500) DEFAULT NULL COMMENT '使用材料清单',
|
||||
`follow_suggestions` varchar(500) DEFAULT NULL COMMENT '后续建议',
|
||||
|
||||
-- 评价相关
|
||||
`evaluation_score` decimal(3,1) DEFAULT NULL COMMENT '综合评价得分(1.0-5.0)',
|
||||
`evaluation_time` datetime DEFAULT NULL COMMENT '评价时间',
|
||||
|
||||
-- 统计字段
|
||||
`attachment_count` int(11) DEFAULT 0 COMMENT '附件数量',
|
||||
`progress_count` int(11) DEFAULT 0 COMMENT '进度记录数量',
|
||||
|
||||
-- 其他字段
|
||||
`cancel_reason` varchar(500) DEFAULT NULL COMMENT '取消原因',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_order_no` (`order_no`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_status` (`order_status`),
|
||||
KEY `idx_priority_level` (`priority_level`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_reporter_id` (`reporter_id`),
|
||||
KEY `idx_assignee_id` (`assignee_id`),
|
||||
KEY `idx_category_id` (`category_id`),
|
||||
KEY `idx_assign_time` (`assign_time`),
|
||||
KEY `idx_finish_time` (`finish_time`),
|
||||
KEY `idx_tenant_status` (`tenant_id`, `order_status`, `deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报修工单表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_attachment
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_attachment`;
|
||||
CREATE TABLE `pm_repair_attachment` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
|
||||
-- 文件信息
|
||||
`file_name` varchar(200) NOT NULL COMMENT '文件原始名称',
|
||||
`file_path` varchar(500) NOT NULL COMMENT '文件存储路径',
|
||||
`file_size` bigint(20) NOT NULL COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(20) NOT NULL COMMENT '文件类型:image-图片,video-视频',
|
||||
`file_extension` varchar(10) NOT NULL COMMENT '文件扩展名',
|
||||
`file_mime_type` varchar(100) DEFAULT NULL COMMENT '文件MIME类型',
|
||||
|
||||
-- 上传信息
|
||||
`upload_stage` char(1) NOT NULL DEFAULT '1' COMMENT '上传阶段:1-报修申请,2-维修过程,3-完成确认',
|
||||
`uploader_id` bigint(20) NOT NULL COMMENT '上传人ID,关联sys_user.id',
|
||||
`uploader_name` varchar(50) NOT NULL COMMENT '上传人姓名',
|
||||
|
||||
-- 文件处理
|
||||
`thumbnail_path` varchar(500) DEFAULT NULL COMMENT '缩略图路径(图片文件)',
|
||||
`duration` int(11) DEFAULT NULL COMMENT '时长(视频文件,秒)',
|
||||
`resolution` varchar(20) DEFAULT NULL COMMENT '分辨率(如:1920x1080)',
|
||||
|
||||
-- 业务信息
|
||||
`description` varchar(200) DEFAULT NULL COMMENT '文件描述说明',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_upload_stage` (`upload_stage`),
|
||||
KEY `idx_uploader_id` (`uploader_id`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_file_type` (`file_type`),
|
||||
KEY `idx_order_stage` (`order_id`, `upload_stage`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报修附件表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_progress
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_progress`;
|
||||
CREATE TABLE `pm_repair_progress` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
|
||||
-- 进度信息
|
||||
`progress_type` char(1) NOT NULL COMMENT '进度类型:1-工单分派,2-接收确认,3-到达现场,4-开始维修,5-进度更新,6-完成维修,7-用户评价,8-工单归档,9-其他',
|
||||
`progress_status` varchar(50) NOT NULL COMMENT '进度状态描述',
|
||||
`progress_detail` text DEFAULT NULL COMMENT '进度详细描述',
|
||||
|
||||
-- 操作人信息
|
||||
`operator_id` bigint(20) NOT NULL COMMENT '操作人ID,关联sys_user.id',
|
||||
`operator_name` varchar(50) NOT NULL COMMENT '操作人姓名',
|
||||
`operator_role` varchar(20) NOT NULL COMMENT '操作人角色:user-用户,admin-管理员,worker-维修员',
|
||||
|
||||
-- 位置信息
|
||||
`location_info` varchar(200) DEFAULT NULL COMMENT '操作位置信息',
|
||||
`gps_latitude` decimal(10,7) DEFAULT NULL COMMENT 'GPS纬度',
|
||||
`gps_longitude` decimal(10,7) DEFAULT NULL COMMENT 'GPS经度',
|
||||
|
||||
-- 时间信息
|
||||
`operation_time` datetime NOT NULL COMMENT '操作时间',
|
||||
`estimated_duration` int(11) DEFAULT NULL COMMENT '预计耗时(分钟)',
|
||||
|
||||
-- 附加信息
|
||||
`attachment_ids` varchar(500) DEFAULT NULL COMMENT '关联附件ID列表(逗号分隔)',
|
||||
`next_step` varchar(200) DEFAULT NULL COMMENT '下一步计划',
|
||||
`issues_encountered` varchar(500) DEFAULT NULL COMMENT '遇到的问题',
|
||||
`materials_needed` varchar(200) DEFAULT NULL COMMENT '需要的材料',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_progress_type` (`progress_type`),
|
||||
KEY `idx_operator_id` (`operator_id`),
|
||||
KEY `idx_operation_time` (`operation_time`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_order_time` (`order_id`, `operation_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修进度表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_service_evaluation
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_service_evaluation`;
|
||||
CREATE TABLE `pm_service_evaluation` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 关联信息
|
||||
`order_id` bigint(20) NOT NULL COMMENT '报修工单ID,关联pm_repair_order.id',
|
||||
`evaluator_id` bigint(20) NOT NULL COMMENT '评价人ID,关联sys_user.id',
|
||||
`evaluator_name` varchar(50) NOT NULL COMMENT '评价人姓名',
|
||||
`evaluated_worker_id` bigint(20) NOT NULL COMMENT '被评价维修员ID,关联sys_user.id',
|
||||
`evaluated_worker_name` varchar(50) NOT NULL COMMENT '被评价维修员姓名',
|
||||
|
||||
-- 评价得分(1-5星,支持小数)
|
||||
`overall_score` decimal(3,1) NOT NULL COMMENT '综合评价得分(1.0-5.0)',
|
||||
`quality_score` decimal(3,1) NOT NULL COMMENT '维修质量得分(1.0-5.0)',
|
||||
`attitude_score` decimal(3,1) NOT NULL COMMENT '服务态度得分(1.0-5.0)',
|
||||
`response_speed_score` decimal(3,1) NOT NULL COMMENT '响应速度得分(1.0-5.0)',
|
||||
`communication_score` decimal(3,1) NOT NULL COMMENT '沟通效果得分(1.0-5.0)',
|
||||
|
||||
-- 评价内容
|
||||
`evaluation_content` text DEFAULT NULL COMMENT '文字评价内容',
|
||||
`satisfaction_level` char(1) NOT NULL COMMENT '满意度等级:1-非常满意,2-满意,3-一般,4-不满意,5-非常不满意',
|
||||
|
||||
-- 评价设置
|
||||
`is_anonymous` tinyint(1) DEFAULT 0 COMMENT '是否匿名评价(0-实名,1-匿名)',
|
||||
`is_public` tinyint(1) DEFAULT 1 COMMENT '是否公开显示(0-不公开,1-公开)',
|
||||
|
||||
-- 时间信息
|
||||
`evaluation_time` datetime NOT NULL COMMENT '评价时间',
|
||||
`service_start_time` datetime DEFAULT NULL COMMENT '服务开始时间',
|
||||
`service_end_time` datetime DEFAULT NULL COMMENT '服务结束时间',
|
||||
`service_duration` int(11) DEFAULT NULL COMMENT '服务时长(分钟)',
|
||||
|
||||
-- 标签和建议
|
||||
`positive_tags` varchar(200) DEFAULT NULL COMMENT '正面标签(逗号分隔)',
|
||||
`negative_tags` varchar(200) DEFAULT NULL COMMENT '负面标签(逗号分隔)',
|
||||
`improvement_suggestions` varchar(500) DEFAULT NULL COMMENT '改进建议',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_order_evaluation` (`order_id`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_evaluator_id` (`evaluator_id`),
|
||||
KEY `idx_evaluated_worker_id` (`evaluated_worker_id`),
|
||||
KEY `idx_overall_score` (`overall_score`),
|
||||
KEY `idx_satisfaction_level` (`satisfaction_level`),
|
||||
KEY `idx_evaluation_time` (`evaluation_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务评价表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_repair_category
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_repair_category`;
|
||||
CREATE TABLE `pm_repair_category` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 分类信息
|
||||
`category_name` varchar(50) NOT NULL COMMENT '类型名称',
|
||||
`category_code` varchar(20) NOT NULL COMMENT '类型编码',
|
||||
`category_description` varchar(200) DEFAULT NULL COMMENT '类型描述',
|
||||
|
||||
-- 层级结构
|
||||
`parent_id` bigint(20) DEFAULT 0 COMMENT '父级ID,顶级为0',
|
||||
`ancestor_ids` varchar(500) DEFAULT '0' COMMENT '祖级列表(逗号分隔)',
|
||||
`level_depth` tinyint(2) DEFAULT 1 COMMENT '层级深度(1-顶级,2-二级...)',
|
||||
`children_count` int(11) DEFAULT 0 COMMENT '子类数量',
|
||||
|
||||
-- 专业技能
|
||||
`required_skills` varchar(200) DEFAULT NULL COMMENT '所需专业技能(逗号分隔)',
|
||||
`skill_level` char(1) DEFAULT '1' COMMENT '技能等级要求:1-初级,2-中级,3-高级',
|
||||
|
||||
-- 时间和成本预估
|
||||
`estimated_duration` int(11) DEFAULT NULL COMMENT '预估维修时长(分钟)',
|
||||
`standard_cost` decimal(10,2) DEFAULT NULL COMMENT '标准成本(元)',
|
||||
|
||||
-- 显示控制
|
||||
`icon_url` varchar(200) DEFAULT NULL COMMENT '分类图标URL',
|
||||
`color_code` varchar(10) DEFAULT '#1890ff' COMMENT '分类颜色代码',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(0-停用,1-正常)',
|
||||
|
||||
-- 统计信息
|
||||
`usage_count` int(11) DEFAULT 0 COMMENT '使用次数统计',
|
||||
`last_used_time` datetime DEFAULT NULL COMMENT '最后使用时间',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_category_code` (`category_code`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_parent_id` (`parent_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_sort_order` (`sort_order`),
|
||||
KEY `idx_level_depth` (`level_depth`),
|
||||
KEY `idx_usage_count` (`usage_count`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修类型表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pm_system_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pm_system_config`;
|
||||
CREATE TABLE `pm_system_config` (
|
||||
-- 标准基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 配置信息
|
||||
`config_key` varchar(100) NOT NULL COMMENT '配置键名',
|
||||
`config_value` text DEFAULT NULL COMMENT '配置值',
|
||||
`config_type` varchar(20) NOT NULL COMMENT '配置类型:string-字符串,number-数字,boolean-布尔,json-JSON对象',
|
||||
`config_group` varchar(50) NOT NULL COMMENT '配置分组:file-文件设置,time-时间设置,workflow-流程设置',
|
||||
|
||||
-- 配置描述
|
||||
`config_name` varchar(100) NOT NULL COMMENT '配置名称',
|
||||
`config_description` varchar(500) DEFAULT NULL COMMENT '配置描述说明',
|
||||
`default_value` text DEFAULT NULL COMMENT '默认值',
|
||||
|
||||
-- 配置约束
|
||||
`is_system` tinyint(1) DEFAULT 0 COMMENT '是否系统内置(0-否,1-是,系统内置不可删除)',
|
||||
`is_editable` tinyint(1) DEFAULT 1 COMMENT '是否可编辑(0-否,1-是)',
|
||||
`validation_rule` varchar(200) DEFAULT NULL COMMENT '验证规则(正则表达式或范围)',
|
||||
|
||||
-- 显示控制
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(0-停用,1-正常)',
|
||||
|
||||
-- 其他信息
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_config_key` (`config_key`, `tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_config_group` (`config_group`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_sort_order` (`sort_order`),
|
||||
KEY `idx_is_system` (`is_system`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化数据:维修类型分类
|
||||
-- ----------------------------
|
||||
INSERT INTO `pm_repair_category` (
|
||||
`id`, `tenant_id`, `category_name`, `category_code`, `category_description`,
|
||||
`parent_id`, `level_depth`, `required_skills`, `skill_level`,
|
||||
`estimated_duration`, `icon_url`, `color_code`, `sort_order`, `status`,
|
||||
`create_time`, `create_by`
|
||||
) VALUES
|
||||
(1, '000000', '电路维修', 'ELECTRIC', '电路、照明、开关、插座等电气设备维修', 0, 1, '电工技能', '2', 60, '/icons/electric.svg', '#ff6b35', 1, '1', NOW(), 1),
|
||||
(2, '000000', '水暖维修', 'PLUMBING', '水管、暖气、卫生间、厨房设备维修', 0, 1, '水暖技能', '2', 90, '/icons/plumbing.svg', '#1890ff', 2, '1', NOW(), 1),
|
||||
(3, '000000', '土建维修', 'CONSTRUCTION', '墙面、地面、天花板、门窗等土建维修', 0, 1, '泥工技能,木工技能', '2', 120, '/icons/construction.svg', '#52c41a', 3, '1', NOW(), 1),
|
||||
(4, '000000', '设备维修', 'EQUIPMENT', '空调、电梯、监控等设备维修', 0, 1, '设备维修技能', '3', 90, '/icons/equipment.svg', '#722ed1', 4, '1', NOW(), 1),
|
||||
(5, '000000', '园林维修', 'GARDEN', '绿化、草坪、树木修剪等园林维护', 0, 1, '园林技能', '1', 45, '/icons/garden.svg', '#13c2c2', 5, '1', NOW(), 1),
|
||||
(6, '000000', '清洁维护', 'CLEANING', '日常清洁、垃圾处理、环境维护', 0, 1, '清洁技能', '1', 30, '/icons/cleaning.svg', '#fa8c16', 6, '1', NOW(), 1);
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化数据:系统配置参数
|
||||
-- ----------------------------
|
||||
INSERT INTO `pm_system_config` (
|
||||
`id`, `tenant_id`, `config_key`, `config_value`, `config_type`, `config_group`,
|
||||
`config_name`, `config_description`, `default_value`, `is_system`, `is_editable`,
|
||||
`sort_order`, `status`, `create_time`, `create_by`
|
||||
) VALUES
|
||||
(1, '000000', 'file.upload.image.max_size', '5242880', 'number', 'file', '图片文件最大大小', '图片文件上传的最大字节数(5MB)', '5242880', 1, 1, 1, '1', NOW(), 1),
|
||||
(2, '000000', 'file.upload.video.max_size', '52428800', 'number', 'file', '视频文件最大大小', '视频文件上传的最大字节数(50MB)', '52428800', 1, 1, 2, '1', NOW(), 1),
|
||||
(3, '000000', 'file.upload.allowed_image_types', 'jpg,jpeg,png,gif', 'string', 'file', '允许的图片类型', '允许上传的图片文件类型(逗号分隔)', 'jpg,jpeg,png,gif', 1, 1, 3, '1', NOW(), 1),
|
||||
(4, '000000', 'file.upload.allowed_video_types', 'mp4,avi,mov', 'string', 'file', '允许的视频类型', '允许上传的视频文件类型(逗号分隔)', 'mp4,avi,mov', 1, 1, 4, '1', NOW(), 1),
|
||||
(5, '000000', 'workflow.auto_assign_enabled', 'true', 'boolean', 'workflow', '启用自动分派', '是否启用智能自动分派功能', 'false', 1, 1, 11, '1', NOW(), 1),
|
||||
(6, '000000', 'workflow.auto_archive_days', '7', 'number', 'workflow', '自动归档天数', '工单评价后多少天自动归档', '7', 1, 1, 12, '1', NOW(), 1),
|
||||
(7, '000000', 'time.priority1_response_hours', '1', 'number', 'time', '非常紧急响应时限', '非常紧急工单的响应时限(小时)', '1', 1, 1, 21, '1', NOW(), 1),
|
||||
(8, '000000', 'time.priority2_response_hours', '4', 'number', 'time', '紧急响应时限', '紧急工单的响应时限(小时)', '4', 1, 1, 22, '1', NOW(), 1),
|
||||
(9, '000000', 'time.priority3_response_hours', '24', 'number', 'time', '一般响应时限', '一般工单的响应时限(小时)', '24', 1, 1, 23, '1', NOW(), 1),
|
||||
(10, '000000', 'time.working_hours_start', '08:00', 'string', 'time', '工作时间开始', '工作日开始时间(HH:mm格式)', '08:00', 1, 1, 31, '1', NOW(), 1),
|
||||
(11, '000000', 'time.working_hours_end', '18:00', 'string', 'time', '工作时间结束', '工作日结束时间(HH:mm格式)', '18:00', 1, 1, 32, '1', NOW(), 1);
|
||||
|
||||
-- ----------------------------
|
||||
-- 创建核心业务查询索引
|
||||
-- ----------------------------
|
||||
-- 报修工单表的核心查询索引
|
||||
CREATE INDEX idx_status_time ON pm_repair_order (order_status, create_time);
|
||||
CREATE INDEX idx_assignee_status ON pm_repair_order (assignee_id, order_status);
|
||||
CREATE INDEX idx_reporter_time ON pm_repair_order (reporter_id, create_time);
|
||||
CREATE INDEX idx_priority_assign ON pm_repair_order (priority_level, assign_time);
|
||||
|
||||
-- 附件表的查询索引
|
||||
CREATE INDEX idx_uploader_time ON pm_repair_attachment (uploader_id, create_time);
|
||||
|
||||
-- 进度表的查询索引
|
||||
CREATE INDEX idx_order_operation_time ON pm_repair_progress (order_id, operation_time);
|
||||
CREATE INDEX idx_operator_time ON pm_repair_progress (operator_id, operation_time);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -11,11 +11,18 @@ const clientId = import.meta.env.VITE_APP_CLIENT_ID;
|
||||
* @returns
|
||||
*/
|
||||
export function login(data: LoginData): AxiosPromise<LoginResult> {
|
||||
// 构建完全不包含验证码字段的登录参数
|
||||
const params = {
|
||||
...data,
|
||||
clientId: data.clientId || clientId,
|
||||
tenantId: data.tenantId,
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
clientId: data.clientId || clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
grantType: data.grantType || 'password'
|
||||
};
|
||||
|
||||
// 调试:打印登录参数(生产环境应移除)
|
||||
console.log('登录请求参数:', params);
|
||||
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 物业报修模块 API 接口入口
|
||||
*/
|
||||
|
||||
// 报修工单相关接口
|
||||
export * from './repairOrder';
|
||||
|
||||
// 维修类型相关接口
|
||||
export * from './repairCategory';
|
||||
|
||||
// 类型定义
|
||||
export * from './types';
|
||||
@@ -0,0 +1,513 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/pm/types';
|
||||
|
||||
/**
|
||||
* 查询维修类型列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const listRepairCategory = (query?: RepairCategoryQuery): AxiosPromise<RepairCategoryVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
categoryName: '电路维修',
|
||||
categoryCode: 'ELECTRIC',
|
||||
categoryDescription: '电路故障、插座开关维修等',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '电工基础知识、安全操作规范',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 50.00,
|
||||
iconUrl: '/icons/electric.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 45,
|
||||
lastUsedTime: '2025-01-22 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:00:00',
|
||||
children: [
|
||||
{
|
||||
id: '11',
|
||||
categoryName: '照明设备',
|
||||
categoryCode: 'ELECTRIC_LIGHT',
|
||||
categoryDescription: '灯具、照明线路维修',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电工基础、照明设备知识',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 30,
|
||||
standardCost: 30.00,
|
||||
iconUrl: '/icons/light.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 18,
|
||||
lastUsedTime: '2025-01-22 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
categoryName: '插座开关',
|
||||
categoryCode: 'ELECTRIC_SOCKET',
|
||||
categoryDescription: '插座、开关面板维修更换',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电工基础、安全操作',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 20,
|
||||
standardCost: 25.00,
|
||||
iconUrl: '/icons/socket.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 15,
|
||||
lastUsedTime: '2025-01-21 14:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00'
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
categoryName: '配电设备',
|
||||
categoryCode: 'ELECTRIC_POWER',
|
||||
categoryDescription: '配电箱、电表等设备维修',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '高级电工、配电知识',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/power.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-20 16:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
categoryName: '水暖维修',
|
||||
categoryCode: 'PLUMBING',
|
||||
categoryDescription: '管道、水龙头、暖气等维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '管工基础知识、焊接技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 60.00,
|
||||
iconUrl: '/icons/plumbing.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 38,
|
||||
lastUsedTime: '2025-01-23 10:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:15:00',
|
||||
children: [
|
||||
{
|
||||
id: '21',
|
||||
categoryName: '给水设备',
|
||||
categoryCode: 'PLUMBING_WATER',
|
||||
categoryDescription: '水龙头、阀门、管道维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '管工基础、维修工具使用',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 40.00,
|
||||
iconUrl: '/icons/water.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 22,
|
||||
lastUsedTime: '2025-01-23 10:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '22',
|
||||
categoryName: '排水设备',
|
||||
categoryCode: 'PLUMBING_DRAIN',
|
||||
categoryDescription: '下水道、地漏疏通维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '管道疏通、清洁设备使用',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 45,
|
||||
standardCost: 35.00,
|
||||
iconUrl: '/icons/drain.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 10,
|
||||
lastUsedTime: '2025-01-22 08:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00'
|
||||
},
|
||||
{
|
||||
id: '23',
|
||||
categoryName: '供暖设备',
|
||||
categoryCode: 'PLUMBING_HEATING',
|
||||
categoryDescription: '暖气片、供暖管道维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '供暖系统知识、焊接技能',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 150,
|
||||
standardCost: 100.00,
|
||||
iconUrl: '/icons/heating.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 6,
|
||||
lastUsedTime: '2025-01-18 13:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
categoryName: '土建维修',
|
||||
categoryCode: 'CONSTRUCTION',
|
||||
categoryDescription: '墙面、地面、门窗等土建维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 2,
|
||||
requiredSkills: '泥工、油漆工基础技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 180,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/construction.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 28,
|
||||
lastUsedTime: '2025-01-21 11:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00',
|
||||
children: [
|
||||
{
|
||||
id: '31',
|
||||
categoryName: '墙面修缮',
|
||||
categoryCode: 'CONSTRUCTION_WALL',
|
||||
categoryDescription: '墙面粉刷、裂缝修补',
|
||||
parentId: '3',
|
||||
ancestorIds: '0,3',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '油漆工、简单泥工技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 60.00,
|
||||
iconUrl: '/icons/wall.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 16,
|
||||
lastUsedTime: '2025-01-21 11:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:15:00'
|
||||
},
|
||||
{
|
||||
id: '32',
|
||||
categoryName: '门窗维修',
|
||||
categoryCode: 'CONSTRUCTION_DOOR',
|
||||
categoryDescription: '门窗锁具、玻璃更换维修',
|
||||
parentId: '3',
|
||||
ancestorIds: '0,3',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '木工基础、五金安装',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 45.00,
|
||||
iconUrl: '/icons/door.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-19 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:30:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
categoryName: '设备维修',
|
||||
categoryCode: 'EQUIPMENT',
|
||||
categoryDescription: '各类设备故障维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 2,
|
||||
requiredSkills: '机电维修、设备操作',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 100.00,
|
||||
iconUrl: '/icons/equipment.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 4,
|
||||
status: '1',
|
||||
usageCount: 32,
|
||||
lastUsedTime: '2025-01-23 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00',
|
||||
children: [
|
||||
{
|
||||
id: '41',
|
||||
categoryName: '空调设备',
|
||||
categoryCode: 'EQUIPMENT_AC',
|
||||
categoryDescription: '空调安装、维修、清洗',
|
||||
parentId: '4',
|
||||
ancestorIds: '0,4',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '制冷设备维修、安全操作',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 150,
|
||||
standardCost: 120.00,
|
||||
iconUrl: '/icons/ac.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 20,
|
||||
lastUsedTime: '2025-01-22 17:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:45:00'
|
||||
},
|
||||
{
|
||||
id: '42',
|
||||
categoryName: '教学设备',
|
||||
categoryCode: 'EQUIPMENT_TEACHING',
|
||||
categoryDescription: '投影仪、音响、电脑等设备维修',
|
||||
parentId: '4',
|
||||
ancestorIds: '0,4',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电子设备维修、计算机知识',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/teaching.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-23 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 12:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
categoryName: '园林维修',
|
||||
categoryCode: 'GARDEN',
|
||||
categoryDescription: '绿化养护、园林设施维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '园艺知识、植物养护',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 240,
|
||||
standardCost: 40.00,
|
||||
iconUrl: '/icons/garden.svg',
|
||||
colorCode: '#66BB6A',
|
||||
sortOrder: 5,
|
||||
status: '1',
|
||||
usageCount: 8,
|
||||
lastUsedTime: '2025-01-15 14:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
categoryName: '清洁维护',
|
||||
categoryCode: 'CLEANING',
|
||||
categoryDescription: '清洁卫生、环境维护',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '清洁知识、环保意识',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 30.00,
|
||||
iconUrl: '/icons/cleaning.svg',
|
||||
colorCode: '#42A5F5',
|
||||
sortOrder: 6,
|
||||
status: '1',
|
||||
usageCount: 5,
|
||||
lastUsedTime: '2025-01-16 10:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:15:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询维修类型详细信息
|
||||
* @param id 类型ID
|
||||
*/
|
||||
export const getRepairCategory = (id: string | number): AxiosPromise<RepairCategoryVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
id: id,
|
||||
categoryName: '电路维修',
|
||||
categoryCode: 'ELECTRIC',
|
||||
categoryDescription: '电路故障、插座开关维修等',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '电工基础知识、安全操作规范',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 50.00,
|
||||
iconUrl: '/icons/electric.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 45,
|
||||
lastUsedTime: '2025-01-22 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:00:00'
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增维修类型
|
||||
* @param data 类型数据
|
||||
*/
|
||||
export const addRepairCategory = (data: RepairCategoryForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '新增成功',
|
||||
data: {
|
||||
id: Date.now(),
|
||||
categoryCode: data.categoryCode
|
||||
}
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改维修类型
|
||||
* @param data 类型数据
|
||||
*/
|
||||
export const updateRepairCategory = (data: RepairCategoryForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '修改成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除维修类型
|
||||
* @param id 类型ID
|
||||
*/
|
||||
export const delRepairCategory = (id: string | number | Array<string | number>) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '删除成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 400);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取维修类型树形结构
|
||||
*/
|
||||
export const getRepairCategoryTree = (): AxiosPromise<RepairCategoryVO[]> => {
|
||||
return listRepairCategory();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取维修类型下拉选项
|
||||
*/
|
||||
export const getRepairCategoryOptions = (): AxiosPromise<Array<{value: string | number, label: string}>> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{ value: '1', label: '电路维修' },
|
||||
{ value: '11', label: '- 照明设备' },
|
||||
{ value: '12', label: '- 插座开关' },
|
||||
{ value: '13', label: '- 配电设备' },
|
||||
{ value: '2', label: '水暖维修' },
|
||||
{ value: '21', label: '- 给水设备' },
|
||||
{ value: '22', label: '- 排水设备' },
|
||||
{ value: '23', label: '- 供暖设备' },
|
||||
{ value: '3', label: '土建维修' },
|
||||
{ value: '31', label: '- 墙面修缮' },
|
||||
{ value: '32', label: '- 门窗维修' },
|
||||
{ value: '4', label: '设备维修' },
|
||||
{ value: '41', label: '- 空调设备' },
|
||||
{ value: '42', label: '- 教学设备' },
|
||||
{ value: '5', label: '园林维修' },
|
||||
{ value: '6', label: '清洁维护' }
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,650 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {
|
||||
RepairOrderVO,
|
||||
RepairOrderForm,
|
||||
RepairOrderQuery,
|
||||
RepairAttachmentVO,
|
||||
RepairProgressVO,
|
||||
AssignOrderForm,
|
||||
ProgressUpdateForm,
|
||||
RepairStatisticsVO
|
||||
} from '@/api/pm/types';
|
||||
|
||||
/**
|
||||
* 查询报修工单列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
// 生成模拟数据的函数
|
||||
const generateMockData = () => {
|
||||
const baseData = [
|
||||
{
|
||||
id: '1',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123001',
|
||||
title: '宿舍A304水龙头漏水',
|
||||
description: '水龙头一直滴水,影响正常使用,需要及时维修',
|
||||
repairLocation: '学生宿舍A栋304室',
|
||||
categoryId: '2',
|
||||
categoryName: '水暖维修',
|
||||
priorityLevel: 2,
|
||||
expectedFinishTime: '2025-01-24 18:00:00',
|
||||
reporterId: '1001',
|
||||
reporterName: '张小明',
|
||||
reporterPhone: '13800138001',
|
||||
reporterDept: '计算机学院',
|
||||
orderStatus: '1',
|
||||
assignTime: null,
|
||||
assigneeId: null,
|
||||
assigneeName: null,
|
||||
assignRemark: null,
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 2,
|
||||
progressCount: 1,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00',
|
||||
updateTime: '2025-01-23 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123002',
|
||||
title: '教学楼B201投影仪故障',
|
||||
description: '投影仪无法正常开机,影响教学活动',
|
||||
repairLocation: '教学楼B栋201教室',
|
||||
categoryId: '4',
|
||||
categoryName: '设备维修',
|
||||
priorityLevel: 1,
|
||||
expectedFinishTime: '2025-01-23 16:00:00',
|
||||
reporterId: '1002',
|
||||
reporterName: '李老师',
|
||||
reporterPhone: '13800138002',
|
||||
reporterDept: '电子工程学院',
|
||||
orderStatus: '2',
|
||||
assignTime: '2025-01-23 11:00:00',
|
||||
assigneeId: '2001',
|
||||
assigneeName: '王师傅',
|
||||
assignRemark: '紧急处理,明天有重要课程',
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 1,
|
||||
progressCount: 2,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 09:15:00',
|
||||
updateTime: '2025-01-23 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250122003',
|
||||
title: '图书馆空调不制热',
|
||||
description: '阅览室空调无法制热,温度较低影响学习',
|
||||
repairLocation: '图书馆一楼阅览室',
|
||||
categoryId: '4',
|
||||
categoryName: '设备维修',
|
||||
priorityLevel: 3,
|
||||
expectedFinishTime: '2025-01-24 12:00:00',
|
||||
reporterId: '1003',
|
||||
reporterName: '赵管理员',
|
||||
reporterPhone: '13800138003',
|
||||
reporterDept: '图书馆',
|
||||
orderStatus: '4',
|
||||
assignTime: '2025-01-22 14:30:00',
|
||||
assigneeId: '2002',
|
||||
assigneeName: '刘师傅',
|
||||
assignRemark: '检查空调系统',
|
||||
startTime: '2025-01-22 15:00:00',
|
||||
finishTime: '2025-01-22 17:30:00',
|
||||
totalDuration: 150,
|
||||
repairResult: '空调滤网堵塞,已清洗并更换新滤网,制热功能恢复正常',
|
||||
usedMaterials: '空调滤网 x1',
|
||||
followSuggestions: '建议每季度清洗一次滤网',
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 3,
|
||||
progressCount: 5,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-22 13:45:00',
|
||||
updateTime: '2025-01-22 17:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 模拟数据生成器参数
|
||||
const categories = [
|
||||
{ id: '1', name: '电路维修' },
|
||||
{ id: '2', name: '水暖维修' },
|
||||
{ id: '3', name: '土建维修' },
|
||||
{ id: '4', name: '设备维修' },
|
||||
{ id: '5', name: '园林维修' },
|
||||
{ id: '6', name: '清洁维护' }
|
||||
];
|
||||
|
||||
const locations = [
|
||||
'学生宿舍A栋', '学生宿舍B栋', '学生宿舍C栋', '教学楼A栋', '教学楼B栋', '教学楼C栋',
|
||||
'图书馆', '实验楼', '行政楼', '食堂', '体育馆', '医务室', '校园广场', '停车场',
|
||||
'学术报告厅', '会议室', '办公楼', '研究生楼', '国际交流中心', '创新创业中心'
|
||||
];
|
||||
|
||||
const reporters = [
|
||||
{ name: '张三', phone: '13800138001', dept: '计算机学院' },
|
||||
{ name: '李四', phone: '13800138002', dept: '电子工程学院' },
|
||||
{ name: '王五', phone: '13800138003', dept: '机械工程学院' },
|
||||
{ name: '赵六', phone: '13800138004', dept: '化学工程学院' },
|
||||
{ name: '钱七', phone: '13800138005', dept: '材料科学学院' },
|
||||
{ name: '孙八', phone: '13800138006', dept: '环境工程学院' },
|
||||
{ name: '周九', phone: '13800138007', dept: '生物工程学院' },
|
||||
{ name: '吴十', phone: '13800138008', dept: '数学学院' },
|
||||
{ name: '郑管理员', phone: '13800138009', dept: '后勤管理处' },
|
||||
{ name: '王老师', phone: '13800138010', dept: '物理学院' }
|
||||
];
|
||||
|
||||
const assignees = [
|
||||
{ id: '2001', name: '王师傅' },
|
||||
{ id: '2002', name: '刘师傅' },
|
||||
{ id: '2003', name: '张师傅' },
|
||||
{ id: '2004', name: '李师傅' }
|
||||
];
|
||||
|
||||
const titles = [
|
||||
'水龙头漏水', '灯泡不亮', '门锁损坏', '窗户关不严', '空调不制冷', '投影仪故障',
|
||||
'网络故障', '桌椅损坏', '黑板擦不干净', '卫生间堵塞', '暖气不热', '电梯故障',
|
||||
'监控设备故障', '音响设备异常', '打印机故障', '饮水机不工作', '垃圾桶损坏',
|
||||
'路灯不亮', '草坪需修剪', '花坛清理', '墙面脱落', '地板破损', '天花板漏水'
|
||||
];
|
||||
|
||||
// 生成100条新数据
|
||||
const additionalData = [];
|
||||
for (let i = 4; i <= 103; i++) {
|
||||
const category = categories[Math.floor(Math.random() * categories.length)];
|
||||
const location = locations[Math.floor(Math.random() * locations.length)];
|
||||
const reporter = reporters[Math.floor(Math.random() * reporters.length)];
|
||||
const title = titles[Math.floor(Math.random() * titles.length)];
|
||||
const priorityLevel = Math.floor(Math.random() * 5) + 1;
|
||||
const status = ['1', '2', '3', '4', '5', '6', '9'][Math.floor(Math.random() * 7)];
|
||||
|
||||
// 根据状态生成相应的时间和其他字段
|
||||
const createTime = new Date(2025, 0, Math.floor(Math.random() * 23) + 1, Math.floor(Math.random() * 24), Math.floor(Math.random() * 60));
|
||||
const assignTime = status !== '1' ? new Date(createTime.getTime() + Math.random() * 24 * 60 * 60 * 1000) : null;
|
||||
const startTime = ['3', '4', '5', '6'].includes(status) ? new Date(assignTime!.getTime() + Math.random() * 4 * 60 * 60 * 1000) : null;
|
||||
const finishTime = ['4', '5', '6'].includes(status) ? new Date(startTime!.getTime() + Math.random() * 8 * 60 * 60 * 1000) : null;
|
||||
const assignee = status !== '1' ? assignees[Math.floor(Math.random() * assignees.length)] : null;
|
||||
|
||||
const roomNumber = Math.floor(Math.random() * 500) + 100;
|
||||
const fullLocation = `${location}${roomNumber}室`;
|
||||
|
||||
additionalData.push({
|
||||
id: i.toString(),
|
||||
tenantId: '000000',
|
||||
orderNo: `RO${createTime.getFullYear()}${String(createTime.getMonth() + 1).padStart(2, '0')}${String(createTime.getDate()).padStart(2, '0')}${String(i).padStart(3, '0')}`,
|
||||
title: `${fullLocation}${title}`,
|
||||
description: `${fullLocation}发生${title}问题,需要及时处理。${Math.random() > 0.5 ? '影响正常使用。' : '请尽快安排维修。'}`,
|
||||
repairLocation: fullLocation,
|
||||
categoryId: category.id,
|
||||
categoryName: category.name,
|
||||
priorityLevel,
|
||||
expectedFinishTime: new Date(createTime.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' '),
|
||||
reporterId: (1000 + (i % 50)).toString(),
|
||||
reporterName: reporter.name,
|
||||
reporterPhone: reporter.phone,
|
||||
reporterDept: reporter.dept,
|
||||
orderStatus: status,
|
||||
assignTime: assignTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
assigneeId: assignee?.id || null,
|
||||
assigneeName: assignee?.name || null,
|
||||
assignRemark: status !== '1' ? (Math.random() > 0.5 ? '请及时处理' : '优先处理此工单') : null,
|
||||
startTime: startTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
finishTime: finishTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
totalDuration: finishTime && startTime ? Math.floor((finishTime.getTime() - startTime.getTime()) / 60000) : null,
|
||||
repairResult: ['4', '5', '6'].includes(status) ? `已完成${title}维修,功能恢复正常` : null,
|
||||
usedMaterials: ['4', '5', '6'].includes(status) && Math.random() > 0.5 ? '维修材料若干' : null,
|
||||
followSuggestions: ['4', '5', '6'].includes(status) && Math.random() > 0.7 ? '建议定期检查维护' : null,
|
||||
evaluationScore: status === '5' ? Math.round((Math.random() * 2 + 3) * 10) / 10 : null,
|
||||
evaluationTime: status === '5' ? new Date(finishTime!.getTime() + Math.random() * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ') : null,
|
||||
attachmentCount: Math.floor(Math.random() * 4),
|
||||
progressCount: status === '1' ? 1 : Math.floor(Math.random() * 5) + 1,
|
||||
cancelReason: status === '9' ? '用户取消申请' : null,
|
||||
remark: Math.random() > 0.8 ? '备注信息' : null,
|
||||
createTime: createTime.toISOString().slice(0, 19).replace('T', ' '),
|
||||
updateTime: (finishTime || assignTime || createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
});
|
||||
}
|
||||
|
||||
return [...baseData, ...additionalData];
|
||||
};
|
||||
|
||||
export const listRepairOrder = (query?: RepairOrderQuery): AxiosPromise<RepairOrderVO[]> => {
|
||||
// 暂时返回模拟数据
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const allData = generateMockData();
|
||||
|
||||
// 应用查询过滤
|
||||
let filteredData = allData;
|
||||
|
||||
if (query?.orderNo) {
|
||||
filteredData = filteredData.filter(item => item.orderNo.includes(query.orderNo!));
|
||||
}
|
||||
if (query?.title) {
|
||||
filteredData = filteredData.filter(item => item.title.includes(query.title!));
|
||||
}
|
||||
if (query?.orderStatus) {
|
||||
filteredData = filteredData.filter(item => item.orderStatus === query.orderStatus);
|
||||
}
|
||||
if (query?.priorityLevel) {
|
||||
filteredData = filteredData.filter(item => item.priorityLevel === query.priorityLevel);
|
||||
}
|
||||
if (query?.reporterName) {
|
||||
filteredData = filteredData.filter(item => item.reporterName.includes(query.reporterName!));
|
||||
}
|
||||
if (query?.assigneeName) {
|
||||
filteredData = filteredData.filter(item => item.assigneeName?.includes(query.assigneeName!));
|
||||
}
|
||||
if (query?.categoryId) {
|
||||
filteredData = filteredData.filter(item => item.categoryId === query.categoryId);
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const pageNum = query?.pageNum || 1;
|
||||
const pageSize = query?.pageSize || 10;
|
||||
const startIndex = (pageNum - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const pageData = filteredData.slice(startIndex, endIndex);
|
||||
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: pageData,
|
||||
total: filteredData.length
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询报修工单详细信息
|
||||
* @param id 工单ID
|
||||
*/
|
||||
export const getRepairOrder = (id: string | number): AxiosPromise<RepairOrderVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
id: id,
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123001',
|
||||
title: '宿舍A304水龙头漏水',
|
||||
description: '水龙头一直滴水,影响正常使用,需要及时维修',
|
||||
repairLocation: '学生宿舍A栋304室',
|
||||
categoryId: '2',
|
||||
categoryName: '水暖维修',
|
||||
priorityLevel: 2,
|
||||
expectedFinishTime: '2025-01-24 18:00:00',
|
||||
reporterId: '1001',
|
||||
reporterName: '张小明',
|
||||
reporterPhone: '13800138001',
|
||||
reporterDept: '计算机学院',
|
||||
orderStatus: '1',
|
||||
assignTime: null,
|
||||
assigneeId: null,
|
||||
assigneeName: null,
|
||||
assignRemark: null,
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 2,
|
||||
progressCount: 1,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00',
|
||||
updateTime: '2025-01-23 10:30:00'
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增报修工单
|
||||
* @param data 工单数据
|
||||
*/
|
||||
export const addRepairOrder = (data: RepairOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '报修申请提交成功',
|
||||
data: {
|
||||
id: Date.now(),
|
||||
orderNo: `RO${new Date().toISOString().slice(0, 10).replace(/-/g, '')}${String(Date.now()).slice(-3)}`
|
||||
}
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 800);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改报修工单
|
||||
* @param data 工单数据
|
||||
*/
|
||||
export const updateRepairOrder = (data: RepairOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '修改成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除报修工单
|
||||
* @param id 工单ID
|
||||
*/
|
||||
export const delRepairOrder = (id: string | number | Array<string | number>) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '删除成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 分派工单
|
||||
* @param data 分派数据
|
||||
*/
|
||||
export const assignRepairOrder = (data: AssignOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单分派成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 700);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 接收工单
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const acceptRepairOrder = (orderId: string | number) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单接收成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 拒绝工单
|
||||
* @param orderId 工单ID
|
||||
* @param reason 拒绝原因
|
||||
*/
|
||||
export const rejectRepairOrder = (orderId: string | number, reason: string) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单拒绝成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新维修进度
|
||||
* @param data 进度数据
|
||||
*/
|
||||
export const updateRepairProgress = (data: ProgressUpdateForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '进度更新成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 完成工单
|
||||
* @param orderId 工单ID
|
||||
* @param data 完成数据
|
||||
*/
|
||||
export const completeRepairOrder = (orderId: string | number, data: any) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单完成成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 700);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消工单
|
||||
* @param orderId 工单ID
|
||||
* @param reason 取消原因
|
||||
*/
|
||||
export const cancelRepairOrder = (orderId: string | number, reason: string) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单取消成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取工单附件列表
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const getRepairAttachments = (orderId: string | number): AxiosPromise<RepairAttachmentVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
orderId: orderId,
|
||||
fileName: '水龙头漏水现场图1.jpg',
|
||||
filePath: '/uploads/repair/images/2025/01/23/leak1.jpg',
|
||||
fileSize: 245678,
|
||||
fileType: 'image',
|
||||
fileExtension: 'jpg',
|
||||
fileMimeType: 'image/jpeg',
|
||||
uploadStage: '1',
|
||||
uploaderId: '1001',
|
||||
uploaderName: '张小明',
|
||||
thumbnailPath: '/uploads/repair/thumbs/2025/01/23/leak1_thumb.jpg',
|
||||
duration: null,
|
||||
resolution: '1920x1080',
|
||||
description: '水龙头漏水现场照片',
|
||||
sortOrder: 1,
|
||||
createTime: '2025-01-23 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
orderId: orderId,
|
||||
fileName: '水龙头漏水视频.mp4',
|
||||
filePath: '/uploads/repair/videos/2025/01/23/leak_video.mp4',
|
||||
fileSize: 5234567,
|
||||
fileType: 'video',
|
||||
fileExtension: 'mp4',
|
||||
fileMimeType: 'video/mp4',
|
||||
uploadStage: '1',
|
||||
uploaderId: '1001',
|
||||
uploaderName: '张小明',
|
||||
thumbnailPath: '/uploads/repair/thumbs/2025/01/23/leak_video_thumb.jpg',
|
||||
duration: 15,
|
||||
resolution: '1920x1080',
|
||||
description: '漏水情况视频记录',
|
||||
sortOrder: 2,
|
||||
createTime: '2025-01-23 10:32:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取工单进度列表
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const getRepairProgress = (orderId: string | number): AxiosPromise<RepairProgressVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
orderId: orderId,
|
||||
progressType: '1',
|
||||
progressStatus: '工单已创建',
|
||||
progressDetail: '用户提交报修申请,工单已生成,等待管理员分派',
|
||||
operatorId: '1001',
|
||||
operatorName: '张小明',
|
||||
operatorRole: 'user',
|
||||
locationInfo: '学生宿舍A栋304室',
|
||||
gpsLatitude: 31.230416,
|
||||
gpsLongitude: 121.473701,
|
||||
operationTime: '2025-01-23 10:30:00',
|
||||
estimatedDuration: null,
|
||||
attachmentIds: '1,2',
|
||||
nextStep: '等待管理员分派维修员',
|
||||
issuesEncountered: null,
|
||||
materialsNeeded: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取报修统计数据
|
||||
*/
|
||||
export const getRepairStatistics = (): AxiosPromise<RepairStatisticsVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
totalOrders: 156,
|
||||
pendingAssign: 8,
|
||||
inProgress: 12,
|
||||
completed: 132,
|
||||
cancelled: 4,
|
||||
avgProcessTime: 4.5,
|
||||
avgEvaluationScore: 4.3,
|
||||
monthlyNewOrders: 23,
|
||||
monthlyCompletedOrders: 19,
|
||||
statusDistribution: [
|
||||
{ status: '1', count: 8, percentage: 5.1 },
|
||||
{ status: '2', count: 3, percentage: 1.9 },
|
||||
{ status: '3', count: 9, percentage: 5.8 },
|
||||
{ status: '4', count: 2, percentage: 1.3 },
|
||||
{ status: '5', count: 1, percentage: 0.6 },
|
||||
{ status: '6', count: 129, percentage: 82.7 },
|
||||
{ status: '9', count: 4, percentage: 2.6 }
|
||||
],
|
||||
categoryDistribution: [
|
||||
{ categoryName: '电路维修', count: 45, percentage: 28.8 },
|
||||
{ categoryName: '水暖维修', count: 38, percentage: 24.4 },
|
||||
{ categoryName: '土建维修', count: 28, percentage: 17.9 },
|
||||
{ categoryName: '设备维修', count: 32, percentage: 20.5 },
|
||||
{ categoryName: '园林维修', count: 8, percentage: 5.1 },
|
||||
{ categoryName: '清洁维护', count: 5, percentage: 3.2 }
|
||||
],
|
||||
priorityDistribution: [
|
||||
{ priorityLevel: 1, count: 12, percentage: 7.7 },
|
||||
{ priorityLevel: 2, count: 28, percentage: 17.9 },
|
||||
{ priorityLevel: 3, count: 89, percentage: 57.1 },
|
||||
{ priorityLevel: 4, count: 21, percentage: 13.5 },
|
||||
{ priorityLevel: 5, count: 6, percentage: 3.8 }
|
||||
]
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 400);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出工单数据
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const exportRepairOrders = (query?: RepairOrderQuery) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '导出成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 MiB After Width: | Height: | Size: 2.3 MiB |
@@ -243,4 +243,8 @@ defineExpose({
|
||||
margin: -3px 8px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-color-picker__color) {
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
:height="tableHeight"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="satSurveyList"
|
||||
@@ -1832,161 +1831,30 @@ const cancelQuestionSelector = () => {
|
||||
selectedQuestionIds.value = [];
|
||||
};
|
||||
|
||||
const toolbarRef = ref<any>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
|
||||
const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
|
||||
|
||||
// 检查是否是有效的DOM元素
|
||||
if (toolbarDom && typeof toolbarDom.closest === 'function') {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarDom.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarDom.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取DOM元素,使用默认高度
|
||||
actualToolbarHeight.value = 70;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 45;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 自定义页面滚动条样式 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 横向滚动表格样式 */
|
||||
@@ -2062,14 +1930,13 @@ onUnmounted(() => {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
@@ -53,14 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告厅ID" align="center" prop="hallId" />
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -172,14 +165,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
class="pagination-container"
|
||||
/>
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 查看报告厅预约详情对话框 -->
|
||||
@@ -397,11 +385,6 @@ const auditForm = ref({
|
||||
const selectedRecords = ref<HallReservationVO[]>([]);
|
||||
const auditLoading = ref(false);
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
|
||||
const queryParams = ref<HallReservationQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -418,64 +401,6 @@ const queryParams = ref<HallReservationQuery>({
|
||||
}
|
||||
});
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 30;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
/** 获取报告厅列表 */
|
||||
const getHallList = async () => {
|
||||
try {
|
||||
@@ -650,12 +575,6 @@ const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 监听搜索栏显示状态变化 */
|
||||
watch(showSearch, () => {
|
||||
// 搜索栏显示状态变化时重新计算高度
|
||||
calculateSearchHeight();
|
||||
});
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
@@ -834,86 +753,25 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 审核界面不需要限制用户信息
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList(); // 获取报告厅列表
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
getHallList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -921,6 +779,15 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.hall-option {
|
||||
padding: 8px 0;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -42,14 +42,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallInfoList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallInfoList" @selection-change="handleSelectionChange" style="margin-bottom: 20px">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -108,7 +101,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改报告厅信息对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
|
||||
@@ -193,7 +188,6 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const hallInfoFormRef = ref<ElFormInstance>();
|
||||
@@ -243,17 +237,6 @@ const data = reactive<PageData<HallInfoForm, HallInfoQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 窗口高度 - 页面边距 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 其他边距
|
||||
const searchHeight = showSearch.value ? 120 : 0; // 搜索栏高度
|
||||
const toolbarHeight = 50; // 工具栏高度
|
||||
const paginationHeight = 60; // 分页栏高度
|
||||
const otherHeight = 90; // 其他边距和间距
|
||||
|
||||
return windowHeight.value - searchHeight - toolbarHeight - paginationHeight - otherHeight - 10;
|
||||
});
|
||||
|
||||
/** 查询报告厅信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
@@ -366,11 +349,6 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: HallInfoVO) => {
|
||||
Object.assign(viewForm, row);
|
||||
@@ -391,24 +369,27 @@ const cancelView = () => {
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
@@ -489,41 +470,13 @@ onUnmounted(() => {
|
||||
word-break: break-word;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
/* margin-top: auto; */
|
||||
/* padding: 8px 0 4px 0; */
|
||||
/* background-color: #fff; */
|
||||
/* border-top: 1px solid #f0f0f0; */
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -53,14 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告厅ID" align="center" prop="hallId" />
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -170,14 +163,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
class="pagination-container"
|
||||
/>
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改报告厅预约对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
|
||||
@@ -357,7 +345,6 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const hallReservationFormRef = ref<ElFormInstance>();
|
||||
@@ -388,11 +375,6 @@ const viewForm = ref<HallReservationForm>({
|
||||
auditRemark: undefined
|
||||
});
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
|
||||
const initFormData: HallReservationForm = {
|
||||
id: undefined,
|
||||
hallId: undefined,
|
||||
@@ -434,64 +416,6 @@ const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 30;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
/** 获取报告厅列表 */
|
||||
const getHallList = async () => {
|
||||
try {
|
||||
@@ -679,12 +603,6 @@ const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 监听搜索栏显示状态变化 */
|
||||
watch(showSearch, () => {
|
||||
// 搜索栏显示状态变化时重新计算高度
|
||||
calculateSearchHeight();
|
||||
});
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
@@ -829,94 +747,25 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 确保用户信息已加载
|
||||
if (!userStore.userId) {
|
||||
console.warn('用户信息尚未加载完成,等待加载...');
|
||||
// 如果用户信息尚未加载,可以等待一下再查询
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
// 再次确保queryParams中的applicantId是最新的
|
||||
queryParams.value.applicantId = userStore.userId;
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList(); // 获取报告厅列表
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
getHallList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -924,6 +773,15 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.hall-option {
|
||||
padding: 8px 0;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div ref="searchRef" v-show="showSearch" class="mb-[10px]">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted">
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row ref="toolbarRef" :gutter="10" class="mb8">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satCategory:add']">新增</el-button>
|
||||
</el-col>
|
||||
@@ -59,7 +59,7 @@
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satCategoryList" :height="tableHeight" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="satCategoryList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -299,151 +299,42 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
<el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable>
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题记录ID" prop="recordId">
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
@@ -37,7 +37,9 @@
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satDetail:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']">删除</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satDetail:export']">导出</el-button>
|
||||
@@ -46,43 +48,42 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="satDetailList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_delete" :value="scope.row.deleted"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="答题记录ID" align="center" prop="recordId" />
|
||||
<el-table-column label="题目ID" align="center" prop="questionId" />
|
||||
<el-table-column label="答案分数" align="center" prop="answerScore" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satDetail:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satDetail:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satDetailList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="答题记录ID" align="center" prop="recordId" />
|
||||
<el-table-column label="题目ID" align="center" prop="questionId" />
|
||||
<el-table-column label="答案分数" align="center" prop="answerScore" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satDetail:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satDetail:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改答题详情对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="satDetailFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="form.deleted" placeholder="请选择删除标志">
|
||||
<el-option
|
||||
v-for="dict in sys_item_delete"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="parseInt(dict.value)"
|
||||
></el-option>
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题记录ID" prop="recordId">
|
||||
@@ -95,7 +96,7 @@
|
||||
<el-input v-model="form.answerScore" placeholder="请输入答案分数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -139,9 +140,9 @@ const initFormData: SatDetailForm = {
|
||||
questionId: undefined,
|
||||
answerScore: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
};
|
||||
const data = reactive<PageData<SatDetailForm, SatDetailQuery>>({
|
||||
form: {...initFormData},
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -149,19 +150,12 @@ const data = reactive<PageData<SatDetailForm, SatDetailQuery>>({
|
||||
recordId: undefined,
|
||||
questionId: undefined,
|
||||
answerScore: undefined,
|
||||
params: {
|
||||
}
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [
|
||||
{ required: true, message: "主键ID不能为空", trigger: "blur" }
|
||||
],
|
||||
recordId: [
|
||||
{ required: true, message: "答题记录ID不能为空", trigger: "blur" }
|
||||
],
|
||||
questionId: [
|
||||
{ required: true, message: "题目ID不能为空", trigger: "blur" }
|
||||
],
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
recordId: [{ required: true, message: '答题记录ID不能为空', trigger: 'blur' }],
|
||||
questionId: [{ required: true, message: '题目ID不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -174,55 +168,55 @@ const getList = async () => {
|
||||
satDetailList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {...initFormData};
|
||||
form.value = { ...initFormData };
|
||||
satDetailFormRef.value?.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
}
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: SatDetailVO[]) => {
|
||||
ids.value = selection.map(item => item.id);
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = "添加答题详情";
|
||||
}
|
||||
dialog.title = '添加答题详情';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: SatDetailVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0]
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getSatDetail(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = "修改答题详情";
|
||||
}
|
||||
dialog.title = '修改答题详情';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
@@ -230,34 +224,73 @@ const submitForm = () => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateSatDetail(form.value).finally(() => buttonLoading.value = false);
|
||||
await updateSatDetail(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addSatDetail(form.value).finally(() => buttonLoading.value = false);
|
||||
await addSatDetail(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess("操作成功");
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: SatDetailVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除答题详情编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
|
||||
await proxy?.$modal.confirm('是否确认删除答题详情编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delSatDetail(_ids);
|
||||
proxy?.$modal.msgSuccess("删除成功");
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('hq/satDetail/export', {
|
||||
...queryParams.value
|
||||
}, `satDetail_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
proxy?.download(
|
||||
'hq/satDetail/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`satDetail_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -53,7 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="table-container">
|
||||
<el-table :height="tableHeight" v-loading="loading" border :data="satOptionList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="satOptionList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -291,144 +291,38 @@ const handleExport = () => {
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satQuestionList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table v-loading="loading" border :data="satQuestionList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -378,145 +378,39 @@ const handleExport = () => {
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getCategoryList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -524,12 +418,12 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 文本省略号样式 */
|
||||
/* 文字省略样式 */
|
||||
.text-ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
/* cursor: help; */
|
||||
max-width: 200px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -82,7 +82,6 @@
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
:height="tableHeight"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="satSurveyList"
|
||||
@@ -2054,161 +2053,30 @@ const exportRawData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toolbarRef = ref<any>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
|
||||
const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
|
||||
|
||||
// 检查是否是有效的DOM元素
|
||||
if (toolbarDom && typeof toolbarDom.closest === 'function') {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarDom.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarDom.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取DOM元素,使用默认高度
|
||||
actualToolbarHeight.value = 70;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 45;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 自定义页面滚动条样式 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 横向滚动表格样式 */
|
||||
@@ -2284,14 +2152,13 @@ onUnmounted(() => {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
+9
-75
@@ -32,15 +32,8 @@
|
||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="captchaEnabled" prop="code">
|
||||
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="proxy.$t('login.code')" style="width: 63%" @keyup.enter="handleLogin">
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" class="login-code-img" @click="getCode" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
|
||||
|
||||
<!-- <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox> -->
|
||||
<!-- <el-form-item style="float: right">
|
||||
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
|
||||
<svg-icon icon-class="wechat" />
|
||||
@@ -76,7 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCodeImg, getTenantList } from '@/api/login';
|
||||
import { getTenantList } from '@/api/login';
|
||||
import { authBinding } from '@/api/system/social/auth';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { LoginData, TenantVO } from '@/api/types';
|
||||
@@ -102,22 +95,17 @@ const loginForm = ref<LoginData>({
|
||||
tenantId: '000000',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
rememberMe: false,
|
||||
code: '',
|
||||
uuid: ''
|
||||
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
grantType: 'password'
|
||||
} as LoginData);
|
||||
|
||||
const loginRules: ElFormRules = {
|
||||
tenantId: [{ required: true, trigger: 'blur', message: t('login.rule.tenantId.required') }],
|
||||
username: [{ required: true, trigger: 'blur', message: t('login.rule.username.required') }],
|
||||
password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }],
|
||||
code: [{ required: true, trigger: 'change', message: t('login.rule.code.required') }]
|
||||
password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }]
|
||||
};
|
||||
|
||||
const codeUrl = ref('');
|
||||
const loading = ref(false);
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true);
|
||||
// 租户开关
|
||||
const tenantEnabled = ref(true);
|
||||
|
||||
@@ -173,19 +161,7 @@ const handleLogin = () => {
|
||||
loginRef.value?.validate(async (valid: boolean, fields: any) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
// 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
localStorage.setItem('tenantId', String(loginForm.value.tenantId));
|
||||
localStorage.setItem('username', String(loginForm.value.username));
|
||||
localStorage.setItem('password', String(loginForm.value.password));
|
||||
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
|
||||
} else {
|
||||
// 否则移除
|
||||
localStorage.removeItem('tenantId');
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('password');
|
||||
localStorage.removeItem('rememberMe');
|
||||
}
|
||||
|
||||
// 调用action的登录方法
|
||||
const [err] = await to(userStore.login(loginForm.value));
|
||||
if (!err) {
|
||||
@@ -194,10 +170,6 @@ const handleLogin = () => {
|
||||
loading.value = false;
|
||||
} else {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
await getCode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
@@ -205,30 +177,9 @@ const handleLogin = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
const getCode = async () => {
|
||||
const res = await getCodeImg();
|
||||
const { data } = res;
|
||||
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + data.img;
|
||||
loginForm.value.uuid = data.uuid;
|
||||
}
|
||||
};
|
||||
|
||||
const getLoginData = () => {
|
||||
const tenantId = localStorage.getItem('tenantId');
|
||||
const username = localStorage.getItem('username');
|
||||
const password = localStorage.getItem('password');
|
||||
const rememberMe = localStorage.getItem('rememberMe');
|
||||
loginForm.value = {
|
||||
tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
|
||||
username: username === null ? String(loginForm.value.username) : username,
|
||||
password: password === null ? String(loginForm.value.password) : String(password),
|
||||
rememberMe: rememberMe === null ? false : Boolean(rememberMe)
|
||||
} as LoginData;
|
||||
// 由于已去除记住密码功能,保持默认的登录数据
|
||||
// 如需要其他初始化逻辑,可在此处添加
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -261,7 +212,6 @@ const doSocialLogin = (type: string) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getCode();
|
||||
initTenantList();
|
||||
getLoginData();
|
||||
|
||||
@@ -418,17 +368,6 @@ onMounted(() => {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 32px;
|
||||
float: right;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
@@ -442,11 +381,6 @@ onMounted(() => {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.login-code-img {
|
||||
height: 32px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
// 针对公司选择下拉框的特殊样式
|
||||
.el_select_coopr {
|
||||
:deep(.el-select__wrapper) {
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
|
||||
<el-form-item label="类型名称" prop="categoryName">
|
||||
<el-input v-model="queryParams.categoryName" placeholder="请输入类型名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型编码" prop="categoryCode">
|
||||
<el-input v-model="queryParams.categoryCode" placeholder="请输入类型编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="技能等级" prop="skillLevel">
|
||||
<el-select v-model="queryParams.skillLevel" placeholder="请选择技能等级" clearable>
|
||||
<el-option v-for="dict in pm_skill_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['pm:category:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-if="refreshTable"
|
||||
v-loading="loading"
|
||||
:data="categoryList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
border
|
||||
style="width: 100%"
|
||||
class="tree-table"
|
||||
>
|
||||
<el-table-column prop="categoryName" label="类型名称" min-width="260" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="category-name-cell">
|
||||
<el-icon v-if="scope.row.iconUrl" class="category-icon" :style="{ color: scope.row.colorCode }">
|
||||
<component :is="getCategoryIcon(scope.row.categoryCode)" />
|
||||
</el-icon>
|
||||
<span class="category-name">{{ scope.row.categoryName }}</span>
|
||||
<el-tag v-if="scope.row.childrenCount > 0" size="small" type="info" class="children-count-tag">
|
||||
{{ scope.row.childrenCount }}个子类
|
||||
</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="categoryCode" label="类型编码" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="categoryDescription" label="类型描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="skillLevel" label="技能等级" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getSkillLevelTagType(scope.row.skillLevel)" size="small">
|
||||
{{ getSkillLevelText(scope.row.skillLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="estimatedDuration" label="预估时长" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.estimatedDuration">{{ formatDuration(scope.row.estimatedDuration) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="standardCost" label="标准成本" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.standardCost">¥{{ scope.row.standardCost }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usageCount" label="使用次数" width="100" align="center" />
|
||||
<el-table-column prop="sortOrder" label="排序" width="80" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status" active-value="1" inactive-value="0" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['pm:category:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['pm:category:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增子类" placement="top">
|
||||
<el-button link type="success" icon="Plus" @click="handleAddChild(scope.row)" v-hasPermi="['pm:category:add']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['pm:category:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改维修类型对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上级类型" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="categoryTreeOptions"
|
||||
:props="{ value: 'id', label: 'categoryName', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="选择上级类型"
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型名称" prop="categoryName">
|
||||
<el-input v-model="form.categoryName" placeholder="请输入类型名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型编码" prop="categoryCode">
|
||||
<el-input v-model="form.categoryCode" placeholder="请输入类型编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="技能等级" prop="skillLevel">
|
||||
<el-select v-model="form.skillLevel" placeholder="请选择技能等级" style="width: 100%">
|
||||
<el-option v-for="dict in pm_skill_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预估时长" prop="estimatedDuration">
|
||||
<el-input-number v-model="form.estimatedDuration" :min="0" :max="9999" placeholder="分钟" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标准成本" prop="standardCost">
|
||||
<el-input-number v-model="form.standardCost" :min="0" :precision="2" placeholder="元" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分类颜色" prop="colorCode">
|
||||
<el-color-picker v-model="form.colorCode" :predefine="predefineColors" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序顺序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" placeholder="数字越小越靠前" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="类型描述" prop="categoryDescription">
|
||||
<el-input v-model="form.categoryDescription" type="textarea" :rows="3" placeholder="请输入类型描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所需技能" prop="requiredSkills">
|
||||
<el-input v-model="form.requiredSkills" type="textarea" :rows="3" placeholder="请输入所需的专业技能要求" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看维修类型详情对话框 -->
|
||||
<el-dialog title="维修类型详情" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<div v-if="viewData" class="category-detail">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="类型名称">
|
||||
<div class="category-name-view">
|
||||
<el-icon v-if="viewData.iconUrl" class="category-icon" :style="{ color: viewData.colorCode }">
|
||||
<component :is="getCategoryIcon(viewData.categoryCode)" />
|
||||
</el-icon>
|
||||
<span class="category-name">{{ viewData.categoryName }}</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型编码">{{ viewData.categoryCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上级类型">{{ getParentName(viewData.parentId) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="技能等级">
|
||||
<el-tag :type="getSkillLevelTagType(viewData.skillLevel)" size="small">
|
||||
{{ getSkillLevelText(viewData.skillLevel) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预估时长">
|
||||
{{ viewData.estimatedDuration ? formatDuration(viewData.estimatedDuration) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="标准成本">
|
||||
{{ viewData.standardCost ? `¥${viewData.standardCost}` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用次数">{{ viewData.usageCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后使用时间">
|
||||
{{ viewData.lastUsedTime ? proxy.parseTime(viewData.lastUsedTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序顺序">{{ viewData.sortOrder }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewData.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ viewData.status === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">
|
||||
{{ proxy.parseTime(viewData.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型描述" :span="2">{{ viewData.categoryDescription || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所需技能" :span="2">{{ viewData.requiredSkills || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注信息" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RepairCategory">
|
||||
import {
|
||||
listRepairCategory,
|
||||
getRepairCategory,
|
||||
delRepairCategory,
|
||||
addRepairCategory,
|
||||
updateRepairCategory,
|
||||
getRepairCategoryTree
|
||||
} from '@/api/pm/repairCategory';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/pm/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const categoryList = ref<RepairCategoryVO[]>([]);
|
||||
const categoryTreeOptions = ref<RepairCategoryVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(false);
|
||||
const refreshTable = ref(true);
|
||||
|
||||
// 字典数据
|
||||
const pm_skill_level = ref([
|
||||
{ value: '1', label: '初级' },
|
||||
{ value: '2', label: '中级' },
|
||||
{ value: '3', label: '高级' }
|
||||
]);
|
||||
|
||||
const sys_normal_disable = ref([
|
||||
{ value: '1', label: '正常' },
|
||||
{ value: '0', label: '停用' }
|
||||
]);
|
||||
|
||||
// 预定义颜色
|
||||
const predefineColors = ref([
|
||||
'#FFB020', // 橙色
|
||||
'#4FC3F7', // 蓝色
|
||||
'#8BC34A', // 绿色
|
||||
'#FF7043', // 红橙色
|
||||
'#66BB6A', // 浅绿色
|
||||
'#42A5F5', // 天蓝色
|
||||
'#AB47BC', // 紫色
|
||||
'#26A69A', // 青色
|
||||
'#FFCA28', // 黄色
|
||||
'#EC407A' // 粉色
|
||||
]);
|
||||
|
||||
const queryParams = ref<RepairCategoryQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
categoryName: '',
|
||||
categoryCode: '',
|
||||
parentId: '',
|
||||
skillLevel: '',
|
||||
status: ''
|
||||
});
|
||||
|
||||
const form = ref<RepairCategoryForm>({});
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const categoryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const rules = reactive<ElFormRules>({
|
||||
parentId: [{ required: true, message: '上级类型不能为空', trigger: 'change' }],
|
||||
categoryName: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }],
|
||||
categoryCode: [{ required: true, message: '类型编码不能为空', trigger: 'blur' }],
|
||||
skillLevel: [{ required: true, message: '技能等级不能为空', trigger: 'change' }],
|
||||
sortOrder: [{ required: true, message: '排序顺序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewData = ref<RepairCategoryVO>();
|
||||
const buttonLoading = ref(false);
|
||||
|
||||
/** 查询维修类型列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listRepairCategory(queryParams.value);
|
||||
categoryList.value = res.data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取维修类型树形结构 */
|
||||
const getCategoryTree = async () => {
|
||||
try {
|
||||
const res = await getRepairCategoryTree();
|
||||
const rootNode = {
|
||||
id: '0',
|
||||
categoryName: '主类型',
|
||||
categoryCode: '',
|
||||
categoryDescription: '',
|
||||
parentId: '0',
|
||||
ancestorIds: '',
|
||||
levelDepth: 0,
|
||||
childrenCount: res.data.length,
|
||||
requiredSkills: '',
|
||||
skillLevel: '',
|
||||
estimatedDuration: 0,
|
||||
standardCost: 0,
|
||||
iconUrl: '',
|
||||
colorCode: '',
|
||||
sortOrder: 0,
|
||||
status: '1',
|
||||
remark: '',
|
||||
usageCount: 0,
|
||||
lastUsedTime: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
tenantId: '',
|
||||
children: res.data
|
||||
} as RepairCategoryVO;
|
||||
categoryTreeOptions.value = [rootNode];
|
||||
} catch (error) {
|
||||
console.error('获取维修类型树形结构失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加维修类型';
|
||||
};
|
||||
|
||||
/** 新增子类按钮操作 */
|
||||
const handleAddChild = (row: RepairCategoryVO) => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
form.value.parentId = row.id;
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加子类型';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: RepairCategoryVO) => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
const res = await getRepairCategory(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改维修类型';
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: RepairCategoryVO) => {
|
||||
const res = await getRepairCategory(row.id);
|
||||
viewData.value = res.data;
|
||||
viewDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
categoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateRepairCategory(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addRepairCategory(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: RepairCategoryVO) => {
|
||||
if (row.childrenCount > 0) {
|
||||
proxy?.$modal.msgError('存在子类型,不允许删除');
|
||||
return;
|
||||
}
|
||||
await proxy?.$modal.confirm('是否确认删除维修类型"' + row.categoryName + '"?');
|
||||
await delRepairCategory(row.id);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: RepairCategoryVO) => {
|
||||
const text = row.status === '1' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要' + text + '"' + row.categoryName + '"类型吗?');
|
||||
await updateRepairCategory({ id: row.id, status: row.status });
|
||||
proxy?.$modal.msgSuccess(text + '成功');
|
||||
} catch {
|
||||
row.status = row.status === '1' ? '0' : '1';
|
||||
}
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = () => {
|
||||
refreshTable.value = false;
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
nextTick(() => {
|
||||
refreshTable.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
categoryName: '',
|
||||
categoryCode: '',
|
||||
categoryDescription: '',
|
||||
parentId: '0',
|
||||
requiredSkills: '',
|
||||
skillLevel: '',
|
||||
estimatedDuration: undefined,
|
||||
standardCost: undefined,
|
||||
iconUrl: '',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 0,
|
||||
status: '1',
|
||||
remark: ''
|
||||
};
|
||||
categoryFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 获取技能等级标签类型 */
|
||||
const getSkillLevelTagType = (level: string): 'success' | 'warning' | 'danger' | 'info' => {
|
||||
const types: Record<string, 'success' | 'warning' | 'danger' | 'info'> = {
|
||||
'1': 'success',
|
||||
'2': 'warning',
|
||||
'3': 'danger'
|
||||
};
|
||||
return types[level] || 'info';
|
||||
};
|
||||
|
||||
/** 获取技能等级文本 */
|
||||
const getSkillLevelText = (level: string) => {
|
||||
const dict = pm_skill_level.value.find((item) => item.value === level);
|
||||
return dict?.label || level;
|
||||
};
|
||||
|
||||
/** 格式化时长 */
|
||||
const formatDuration = (minutes: number) => {
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟`;
|
||||
} else {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainingMinutes = minutes % 60;
|
||||
return remainingMinutes > 0 ? `${hours}小时${remainingMinutes}分钟` : `${hours}小时`;
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取分类图标 */
|
||||
const getCategoryIcon = (categoryCode: string) => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'ELECTRIC': 'Lightning',
|
||||
'ELECTRIC_LIGHT': 'Sunny',
|
||||
'ELECTRIC_SOCKET': 'Operation',
|
||||
'ELECTRIC_POWER': 'Grid',
|
||||
'PLUMBING': 'Memo',
|
||||
'PLUMBING_WATER': 'Drizzling',
|
||||
'PLUMBING_DRAIN': 'Filter',
|
||||
'PLUMBING_HEATING': 'Cherry',
|
||||
'CONSTRUCTION': 'OfficeBuilding',
|
||||
'CONSTRUCTION_WALL': 'Wall',
|
||||
'CONSTRUCTION_DOOR': 'Key',
|
||||
'EQUIPMENT': 'Monitor',
|
||||
'EQUIPMENT_AC': 'WindPower',
|
||||
'EQUIPMENT_TEACHING': 'VideoCamera',
|
||||
'GARDEN': 'Grape',
|
||||
'CLEANING': 'Brush'
|
||||
};
|
||||
return iconMap[categoryCode] || 'Tools';
|
||||
};
|
||||
|
||||
/** 获取上级类型名称 */
|
||||
const getParentName = (parentId: string | number) => {
|
||||
if (parentId === '0' || parentId === 0) {
|
||||
return '主类型';
|
||||
}
|
||||
|
||||
const findParent = (list: RepairCategoryVO[], id: string | number): string => {
|
||||
for (const item of list) {
|
||||
if (item.id === id) {
|
||||
return item.categoryName;
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const result = findParent(item.children, id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return findParent(categoryList.value, parentId) || `未知(${parentId})`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
.table-container {
|
||||
.tree-table {
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-name-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
gap: 8px;
|
||||
.children-count-tag {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.category-name-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
.category-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.children-count-tag {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.category-detail {
|
||||
:deep(.el-descriptions__label) {
|
||||
font-weight: 600;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// 修复颜色选择器的灰色背景问题
|
||||
:deep(.el-color-picker) {
|
||||
.el-color-picker__trigger {
|
||||
border: 0px !important;
|
||||
background-color: #fff;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-color-picker__color {
|
||||
background-color: transparent;
|
||||
border: 0px !important;
|
||||
|
||||
&.is-alpha {
|
||||
background-image: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-color-picker__color-inner {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&::after {
|
||||
display: none; // 移除默认的灰色背景图案
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 固定在右下角的分页样式(预留)*/
|
||||
.fixed-pagination {
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
bottom: 8px;
|
||||
z-index: 999;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-color-picker__color) {
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user