Compare commits
5 Commits
deb005b88e
...
434fa135d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
434fa135d1 | ||
| 9b8d177e34 | |||
| 1a1abf4c26 | |||
|
|
f88383425f | ||
|
|
74b2edb510 |
@@ -37,7 +37,7 @@ alwaysApply: true
|
||||
- 运维文档.md
|
||||
- 安全文档.md
|
||||
- 用户手册文档.md
|
||||
7. DB_DIALECT || 'mysql',
|
||||
7. 养殖端的数据库信息为:DB_DIALECT || 'mysql',
|
||||
DB_HOST = '129.211.213.226',
|
||||
DB_PORT = 9527,
|
||||
DB_DATABASE = 'nxxmdata',
|
||||
@@ -51,4 +51,10 @@ DB_PASSWORD = 'aiotAiot123!',
|
||||
13. 不要修改前后端端口号。发现端口占用先杀死端口,再打开,不要修改端口号。规定死保险端的后端端口为3000,前端端口为3001.
|
||||
14. 每次运行命令都要先看项目规则。
|
||||
15. PowerShell不支持&&操作符,请使用;符号1. 请保持对话语言为中文
|
||||
16. 开发养殖端微信小程序时后端的API接口全部请求到https://ad.ningmuyun.com/
|
||||
16. 开发养殖端微信小程序时后端的API接口全部请求到https://ad.ningmuyun.com/
|
||||
17. 保险端的数据库信息为:DB_DIALECT || 'mysql',
|
||||
DB_HOST = '129.211.213.226',
|
||||
DB_PORT = 9527,
|
||||
DB_DATABASE = 'insurance_data',
|
||||
DB_USER = 'root',
|
||||
DB_PASSWORD = 'aiotAiot123!',
|
||||
@@ -42,8 +42,8 @@ Page({
|
||||
// 并行获取各项统计数据
|
||||
const [productsRes, applicationsRes, contractsRes, releasesRes] = await Promise.allSettled([
|
||||
apiService.loanProducts.getStats(),
|
||||
apiService.loanApplications.getStats(),
|
||||
apiService.loanContracts.getStats(),
|
||||
// apiService.loanApplications.getStats(),
|
||||
// apiService.loanContracts.getStats(),
|
||||
apiService.loanReleases.getStats()
|
||||
]);
|
||||
|
||||
|
||||
263
docs/导出功能修复总结.md
Normal file
263
docs/导出功能修复总结.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# 保险端导出功能修复总结
|
||||
|
||||
## 修复时间
|
||||
2025年10月9日
|
||||
|
||||
## 问题描述
|
||||
前端导出Excel功能虽然能收到后端返回的Excel文件数据(Blob格式),但文件无法正常下载或内容异常。
|
||||
|
||||
## 问题根源
|
||||
|
||||
### 1. 后端问题
|
||||
- **路由顺序错误**:部分模块的 `/export` 路由放在动态路由 `/:id` 之后,导致404错误
|
||||
- **缺少导出方法**:部分控制器缺少 `exportToExcel` 方法
|
||||
|
||||
### 2. 前端问题
|
||||
- **request.js 响应处理**:`handleResponse` 函数不支持 blob 类型响应
|
||||
- **Blob 包装错误**:前端代码使用 `new Blob([response.data])` 重复包装已经是 Blob 的数据
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 后端修复(insurance_backend)
|
||||
|
||||
#### 1. 添加导出方法到控制器
|
||||
|
||||
为以下控制器添加了 `exportToExcel` 方法:
|
||||
- ✅ `userController.js` - 用户管理导出
|
||||
- ✅ `supervisoryTaskController.js` - 监管任务导出
|
||||
- ✅ `installationTaskController.js` - 待安装任务导出
|
||||
- ✅ `deviceAlertController.js` - 设备预警导出
|
||||
- ✅ `insuranceTypeController.js` - 保险类型导出
|
||||
- ✅ `policyController.js` - 保单管理导出
|
||||
- ✅ `livestockClaimController.js` - 理赔管理导出
|
||||
- ✅ `livestockPolicyController.js` - 生资保单管理导出
|
||||
- ✅ `operationLogController.js` - 操作日志导出
|
||||
|
||||
#### 2. 修复路由顺序
|
||||
|
||||
在以下路由文件中,将 `/export` 和 `/stats` 路由移到 `/:id` 之前:
|
||||
- ✅ `routes/users.js`
|
||||
- ✅ `routes/supervisoryTasks.js`
|
||||
- ✅ `routes/installationTasks.js`
|
||||
- ✅ `routes/deviceAlerts.js`
|
||||
- ✅ `routes/insuranceTypes.js`
|
||||
- ✅ `routes/policies.js`
|
||||
- ✅ `routes/livestockClaims.js`
|
||||
- ✅ `routes/livestockPolicies.js`
|
||||
- ✅ `routes/operationLogs.js`
|
||||
|
||||
**正确的路由顺序示例:**
|
||||
```javascript
|
||||
// 1. 统计接口
|
||||
router.get('/stats', jwtAuth, requirePermission('xxx:read'), controller.getStats);
|
||||
|
||||
// 2. 导出接口
|
||||
router.get('/export', jwtAuth, requirePermission('xxx:read'), controller.exportToExcel);
|
||||
|
||||
// 3. 列表接口
|
||||
router.get('/', jwtAuth, requirePermission('xxx:read'), controller.getList);
|
||||
|
||||
// 4. 详情接口(动态路由放最后)
|
||||
router.get('/:id', jwtAuth, requirePermission('xxx:read'), controller.getById);
|
||||
```
|
||||
|
||||
#### 3. 导出工具类
|
||||
|
||||
创建了通用的导出工具 `utils/excelExport.js`,提供:
|
||||
- `exportToExcel(data, columns, sheetName)` - 生成Excel文件
|
||||
- `formatDate(date)` - 格式化日期
|
||||
- `formatStatus(status, statusMap)` - 格式化状态
|
||||
|
||||
### 前端修复(insurance_admin-system)
|
||||
|
||||
#### 1. 修复 request.js
|
||||
|
||||
修改 `src/utils/request.js` 中的 `handleResponse` 函数,添加对 blob 类型的支持:
|
||||
|
||||
```javascript
|
||||
const handleResponse = async (response) => {
|
||||
let data
|
||||
|
||||
try {
|
||||
const contentType = response.headers.get('content-type')
|
||||
|
||||
// 处理Excel文件下载
|
||||
if (contentType && contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) {
|
||||
data = await response.blob()
|
||||
} else if (contentType && contentType.includes('application/json')) {
|
||||
data = await response.json()
|
||||
} else {
|
||||
data = await response.text()
|
||||
}
|
||||
} catch (error) {
|
||||
data = null
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(data?.message || `HTTP ${response.status}: ${response.statusText}`)
|
||||
error.response = {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: data
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
return { data, status: response.status, statusText: response.statusText }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 修复前端页面的 Blob 处理
|
||||
|
||||
修改以下页面中的导出方法,将:
|
||||
```javascript
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
```
|
||||
|
||||
改为:
|
||||
```javascript
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
```
|
||||
|
||||
已修复的页面:
|
||||
- ✅ `UserManagement.vue` - 用户管理
|
||||
- ✅ `MessageNotification.vue` - 消息通知(设备预警)
|
||||
- ✅ `InsuranceTypeManagement.vue` - 保险类型管理
|
||||
- ✅ `PolicyManagement.vue` - 保单管理
|
||||
- ✅ `ClaimManagement.vue` - 理赔管理
|
||||
- ✅ `SupervisionTaskManagement.vue` - 监管任务管理
|
||||
- ✅ `CompletedTaskManagement.vue` - 监管任务结项
|
||||
- ✅ `InstallationTaskManagement.vue` - 待安装任务
|
||||
- ✅ `LivestockPolicyManagement.vue` - 生资保单管理
|
||||
- ✅ `SystemSettings.vue` - 系统设置(操作日志)
|
||||
- ✅ `ApplicationManagement.vue` - 申请管理
|
||||
|
||||
## 导出功能支持的筛选参数
|
||||
|
||||
### 用户管理
|
||||
- `search` - 用户名搜索
|
||||
- `status` - 状态筛选
|
||||
|
||||
### 监管任务
|
||||
- `policyNumber` - 保单编号
|
||||
- `customerName` - 客户姓名
|
||||
|
||||
### 待安装任务
|
||||
- `policyNumber` - 保单编号
|
||||
- `keyword` - 关键字搜索
|
||||
|
||||
### 设备预警
|
||||
- `alert_level` - 预警级别
|
||||
- `alert_type` - 预警类型
|
||||
- `status` - 处理状态
|
||||
- `is_read` - 是否已读
|
||||
|
||||
### 保险类型
|
||||
- `name` - 险种名称
|
||||
- `status` - 状态
|
||||
|
||||
### 保单管理
|
||||
- `policy_number` - 保单编号
|
||||
- `policyholder_name` - 投保人姓名
|
||||
- `status` - 状态
|
||||
|
||||
### 理赔管理
|
||||
- `claim_number` - 理赔编号
|
||||
- `claimant_name` - 理赔人姓名
|
||||
- `status` - 状态
|
||||
|
||||
### 生资保单
|
||||
- `policy_no` - 保单号
|
||||
- `farmer_name` - 农户姓名
|
||||
- `policy_status` - 保单状态
|
||||
|
||||
### 操作日志
|
||||
- `username` - 用户名
|
||||
- `operation_type` - 操作类型
|
||||
- `operation_module` - 操作模块
|
||||
- `startDate` - 开始日期
|
||||
- `endDate` - 结束日期
|
||||
|
||||
## 技术要点
|
||||
|
||||
### 1. Excel 文件生成
|
||||
- 使用 `exceljs` 库生成 Excel 文件
|
||||
- 支持自定义列宽、表头、数据格式化
|
||||
- 自动设置表头样式(加粗、边框)
|
||||
|
||||
### 2. 数据格式化
|
||||
- 日期格式:`YYYY-MM-DD HH:mm:ss`
|
||||
- 状态映射:使用中文映射英文状态值
|
||||
- 空值处理:显示为空字符串或0
|
||||
|
||||
### 3. 文件下载
|
||||
- Content-Type: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
|
||||
- Content-Disposition: `attachment; filename=xxx_${timestamp}.xlsx`
|
||||
- 前端使用 `window.URL.createObjectURL` 创建下载链接
|
||||
|
||||
### 4. 权限控制
|
||||
- 所有导出接口都需要 JWT 认证
|
||||
- 需要相应模块的读取权限
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. **启动后端服务**
|
||||
```bash
|
||||
cd insurance_backend
|
||||
node src/app.js
|
||||
```
|
||||
确保服务运行在 `http://localhost:3000`
|
||||
|
||||
2. **启动前端服务**
|
||||
```bash
|
||||
cd insurance_admin-system
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **测试导出功能**
|
||||
- 登录系统(admin / 123456)
|
||||
- 进入各个功能模块
|
||||
- 点击"导出Excel"按钮
|
||||
- 验证文件能正常下载且内容正确
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 1. 404 错误
|
||||
- **原因**:导出路由在动态路由之后
|
||||
- **解决**:将 `/export` 路由移到 `/:id` 之前
|
||||
|
||||
### 2. Excel 文件损坏或无法打开
|
||||
- **原因**:前端重复包装 Blob 数据
|
||||
- **解决**:直接使用 `response.data`,不要用 `new Blob([response.data])`
|
||||
|
||||
### 3. 导出的 Excel 为空
|
||||
- **原因**:数据库查询条件有误或数据为空
|
||||
- **解决**:检查控制器中的查询逻辑和where条件
|
||||
|
||||
### 4. 权限错误
|
||||
- **原因**:用户没有导出权限
|
||||
- **解决**:在数据库中为用户角色添加相应的导出权限
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **不要修改端口号**:后端固定3000端口,前端固定3001端口
|
||||
2. **测试文件清理**:所有测试文件会自动删除
|
||||
3. **权限验证**:确保每个导出接口都有权限中间件
|
||||
4. **数据量控制**:大量数据导出时注意性能和内存占用
|
||||
5. **文件命名**:使用时间戳避免文件名冲突
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **分页导出**:对于大量数据,支持分批导出
|
||||
2. **异步导出**:数据量大时改为异步任务,生成后通知用户下载
|
||||
3. **导出模板**:提供 Excel 模板下载功能
|
||||
4. **导出历史**:记录导出操作日志
|
||||
5. **格式选择**:支持导出为 CSV、PDF 等其他格式
|
||||
6. **数据汇总**:在 Excel 中添加汇总统计信息
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 后端接口文档:`insurance_backend/docs/EXPORT_IMPLEMENTATION_GUIDE.md`
|
||||
- API 文档:Swagger UI `http://localhost:3000/api-docs`
|
||||
- 前端环境配置:`insurance_admin-system/ENV_CONFIG.md`
|
||||
|
||||
86
docs/待安装任务导出字段说明.md
Normal file
86
docs/待安装任务导出字段说明.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 待安装任务导出字段映射说明
|
||||
|
||||
## 数据库字段对照表
|
||||
|
||||
| Excel列名 | 数据库字段 | 字段说明 | 是否必填 |
|
||||
|---------|----------|---------|---------|
|
||||
| 申请单号 | application_number | 申请单号 | 是 |
|
||||
| 保单编号 | policy_number | 保单编号 | 是 |
|
||||
| 产品名称 | product_name | 产品名称 | 是 |
|
||||
| 客户姓名 | customer_name | 客户姓名 | 是 |
|
||||
| 证件类型 | id_type | 证件类型 | 是 |
|
||||
| 证件号码 | id_number | 证件号码 | 是 |
|
||||
| 养殖生资种类 | livestock_supply_type | 养殖生资种类 | 否 |
|
||||
| 安装状态 | installation_status | 安装状态 | 是 |
|
||||
| 优先级 | priority | 任务优先级 | 是 |
|
||||
| 安装地址 | installation_address | 安装地址 | 否 |
|
||||
| 联系电话 | contact_phone | 联系电话 | 否 |
|
||||
| 任务生成时间 | task_generated_time | 任务生成时间 | 否 |
|
||||
| 安装完成时间 | installation_completed_time | 安装完成时间 | 否 |
|
||||
| 创建时间 | created_at | 创建时间 | 是 |
|
||||
| 更新时间 | updated_at | 更新时间 | 是 |
|
||||
|
||||
## 字段值说明
|
||||
|
||||
### 证件类型 (id_type)
|
||||
- 身份证
|
||||
- 护照
|
||||
- 军官证
|
||||
- 士兵证
|
||||
- 港澳台居民居住证
|
||||
- 其他
|
||||
|
||||
### 安装状态 (installation_status)
|
||||
- 待安装
|
||||
- 安装中
|
||||
- 已安装
|
||||
- 安装失败
|
||||
- 已取消
|
||||
|
||||
### 优先级 (priority)
|
||||
- 低
|
||||
- 中
|
||||
- 高
|
||||
- 紧急
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据库字段格式**:使用下划线命名(snake_case)
|
||||
2. **模型字段格式**:使用驼峰命名(camelCase)
|
||||
3. **raw查询返回**:返回的是数据库字段名(下划线格式)
|
||||
4. **空值处理**:
|
||||
- 字符串字段:显示为空字符串 `''`
|
||||
- 日期字段:通过 `ExcelExport.formatDate()` 处理,空值显示为空字符串
|
||||
5. **状态值**:直接使用数据库中的中文值,不需要额外映射
|
||||
|
||||
## 导出示例数据
|
||||
|
||||
```json
|
||||
{
|
||||
"applicationNumber": "APP001",
|
||||
"policyNumber": "POL001",
|
||||
"productName": "智能耳标监控系统",
|
||||
"customerName": "张三",
|
||||
"idType": "身份证",
|
||||
"idNumber": "110101199001011234",
|
||||
"livestockSupplyType": "牛养殖",
|
||||
"installationStatus": "待安装",
|
||||
"priority": "高",
|
||||
"installationAddress": null,
|
||||
"contactPhone": null,
|
||||
"taskGeneratedTime": "2025-09-22 18:28:58",
|
||||
"installationCompletedTime": null,
|
||||
"createdAt": "2025-09-22 18:28:58",
|
||||
"updatedAt": "2025-09-22 18:28:58"
|
||||
}
|
||||
```
|
||||
|
||||
## 修复历史
|
||||
|
||||
### 2025-10-09 修复内容
|
||||
1. ✅ 修复字段映射错误(驼峰 → 下划线)
|
||||
2. ✅ 添加缺失字段:证件类型、优先级、安装地址、联系电话、更新时间
|
||||
3. ✅ 移除不存在的字段映射
|
||||
4. ✅ 删除多余的状态映射逻辑
|
||||
5. ✅ 统一空值处理方式
|
||||
|
||||
313
docs/监管任务模块问题诊断报告.md
Normal file
313
docs/监管任务模块问题诊断报告.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# 监管任务模块问题诊断报告
|
||||
|
||||
## 诊断时间
|
||||
2025年10月9日
|
||||
|
||||
## 问题描述
|
||||
用户反映:监管任务结项管理模块,表中有数据,但是前端页面没有数据,后端API接口返回也没数据。
|
||||
|
||||
## 诊断结果
|
||||
|
||||
### ✅ 后端状态 - 完全正常
|
||||
|
||||
#### 1. 数据库层面
|
||||
- **表名**:`supervisory_tasks`
|
||||
- **数据**:存在1条测试数据
|
||||
- **结构**:正确
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM supervisory_tasks;
|
||||
-- 结果: 1条记录
|
||||
|
||||
SELECT * FROM supervisory_tasks LIMIT 1;
|
||||
-- 数据正常,包含完整字段
|
||||
```
|
||||
|
||||
#### 2. Model层面
|
||||
- **模型文件**:`models/SupervisoryTask.js`
|
||||
- **表映射**:正确 (`tableName: 'supervisory_tasks'`)
|
||||
- **查询测试**:✅ 通过
|
||||
```javascript
|
||||
// 直接通过Model查询成功
|
||||
const tasks = await SupervisoryTask.findAll();
|
||||
// 结果: 返回1条记录
|
||||
```
|
||||
|
||||
#### 3. API层面
|
||||
- **控制器**:`controllers/supervisoryTaskController.js`
|
||||
- **路由**:`routes/supervisoryTasks.js`
|
||||
- **注册**:✅ 正确注册在 `src/app.js`
|
||||
- `/api/supervisory-tasks` (备用路径)
|
||||
- `/api/supervision-tasks` (主要路径 - 前端使用)
|
||||
- **API测试**:✅ 完全正常
|
||||
|
||||
**API测试结果:**
|
||||
```bash
|
||||
GET /api/supervision-tasks?page=1&limit=10
|
||||
Authorization: Bearer <token>
|
||||
|
||||
响应:
|
||||
{
|
||||
"code": 200,
|
||||
"status": "success",
|
||||
"message": "获取监管任务列表成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"applicationNumber": "APP2025001",
|
||||
"policyNumber": "POL2025001",
|
||||
"productName": "农业保险产品",
|
||||
"customerName": "张三",
|
||||
"taskStatus": "待处理",
|
||||
"priority": "中",
|
||||
...
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"limit": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 后端服务状态
|
||||
- **端口**:3000
|
||||
- **状态**:✅ 正在运行
|
||||
- **PID**:19340
|
||||
|
||||
### ✅ 前端状态
|
||||
|
||||
#### 1. 服务状态
|
||||
- **端口**:3001
|
||||
- **状态**:✅ 正在运行
|
||||
- **PID**:18648
|
||||
|
||||
#### 2. 代理配置
|
||||
```javascript
|
||||
// vite.config.js
|
||||
proxy: {
|
||||
'/insurance/api': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/insurance/, '')
|
||||
}
|
||||
}
|
||||
```
|
||||
配置:✅ 正确
|
||||
|
||||
#### 3. API配置
|
||||
```javascript
|
||||
// src/utils/api.js
|
||||
export const supervisionTaskApi = {
|
||||
getList: (params) => api.get('/supervision-tasks', { params }),
|
||||
getStats: () => api.get('/supervision-tasks/stats'),
|
||||
export: (params) => api.get('/supervision-tasks/export', { params, responseType: 'blob' }),
|
||||
...
|
||||
}
|
||||
```
|
||||
配置:✅ 正确
|
||||
|
||||
#### 4. 环境配置
|
||||
```javascript
|
||||
// src/config/env.js
|
||||
const envConfig = {
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000
|
||||
}
|
||||
```
|
||||
配置:✅ 正确
|
||||
|
||||
## 可能的问题原因
|
||||
|
||||
### 1. 认证Token问题 ⚠️
|
||||
前端可能没有正确发送认证token或token已过期。
|
||||
|
||||
**验证方法**:
|
||||
```javascript
|
||||
// 在浏览器控制台检查
|
||||
localStorage.getItem('accessToken')
|
||||
localStorage.getItem('refreshToken')
|
||||
```
|
||||
|
||||
### 2. 请求拦截器问题 ⚠️
|
||||
前端的请求拦截器可能没有正确添加Authorization头。
|
||||
|
||||
**检查文件**:`insurance_admin-system/src/utils/request.js`
|
||||
|
||||
### 3. 数据响应格式解析问题 ⚠️
|
||||
前端页面可能没有正确解析后端返回的数据格式。
|
||||
|
||||
**后端返回格式**:
|
||||
```javascript
|
||||
{
|
||||
data: {
|
||||
status: 'success',
|
||||
data: {
|
||||
list: [...],
|
||||
total: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**前端期望格式**:需要检查 `CompletedTaskManagement.vue` 和 `SupervisionTaskManagement.vue`
|
||||
|
||||
### 4. 路由路径不匹配 ⚠️
|
||||
如果前端使用了错误的API路径。
|
||||
|
||||
### 5. 跨域问题 ⚠️
|
||||
虽然后端配置了CORS,但可能存在配置问题。
|
||||
|
||||
## 建议的解决步骤
|
||||
|
||||
### 步骤1:检查用户登录状态
|
||||
```javascript
|
||||
// 在浏览器控制台执行
|
||||
console.log('Token:', localStorage.getItem('accessToken'));
|
||||
console.log('是否登录:', !!localStorage.getItem('accessToken'));
|
||||
```
|
||||
|
||||
### 步骤2:检查网络请求
|
||||
1. 打开浏览器开发者工具 (F12)
|
||||
2. 切换到 Network 标签
|
||||
3. 刷新页面
|
||||
4. 查找 `/supervision-tasks` 请求
|
||||
5. 检查:
|
||||
- 请求URL是否正确
|
||||
- 请求头是否包含 `Authorization: Bearer <token>`
|
||||
- 响应状态码
|
||||
- 响应内容
|
||||
|
||||
### 步骤3:检查控制台错误
|
||||
1. 打开浏览器开发者工具Console标签
|
||||
2. 查看是否有JavaScript错误
|
||||
3. 查看是否有API请求失败的错误信息
|
||||
|
||||
### 步骤4:强制刷新Token
|
||||
```javascript
|
||||
// 在前端代码中
|
||||
1. 清除现有token
|
||||
localStorage.clear()
|
||||
|
||||
2. 重新登录
|
||||
使用账号: admin
|
||||
密码: 123456
|
||||
```
|
||||
|
||||
### 步骤5:检查数据处理逻辑
|
||||
检查前端页面中的数据响应处理:
|
||||
|
||||
**CompletedTaskManagement.vue**:
|
||||
```javascript
|
||||
// 第466-476行
|
||||
const response = await supervisionTaskApi.getList(params)
|
||||
console.log('监管任务结项API响应:', response)
|
||||
|
||||
if (response.data && response.data.status === 'success') {
|
||||
taskList.value = response.data.data.list || []
|
||||
pagination.total = response.data.data.total || 0
|
||||
}
|
||||
```
|
||||
|
||||
**SupervisionTaskManagement.vue**:
|
||||
```javascript
|
||||
// 第513-522行
|
||||
const response = await supervisionTaskApi.getList(params)
|
||||
console.log('监管任务API响应:', response)
|
||||
|
||||
if (response.data && response.data.status === 'success') {
|
||||
tableData.value = response.data.data.list
|
||||
pagination.total = response.data.data.total
|
||||
}
|
||||
```
|
||||
|
||||
## 测试命令
|
||||
|
||||
### 测试数据库
|
||||
```bash
|
||||
cd insurance_backend
|
||||
node test-supervisory-table.js
|
||||
```
|
||||
|
||||
### 测试API(需要后端服务运行)
|
||||
```bash
|
||||
cd insurance_backend
|
||||
node test-full-api-flow.js
|
||||
```
|
||||
|
||||
### 启动后端服务
|
||||
```bash
|
||||
cd insurance_backend
|
||||
npm start
|
||||
```
|
||||
|
||||
### 启动前端服务
|
||||
```bash
|
||||
cd insurance_admin-system
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 前端调试代码
|
||||
|
||||
在浏览器控制台执行以下代码测试API:
|
||||
|
||||
```javascript
|
||||
// 1. 获取token
|
||||
const token = localStorage.getItem('accessToken');
|
||||
console.log('Token:', token);
|
||||
|
||||
// 2. 手动测试API
|
||||
fetch('/insurance/api/supervision-tasks?page=1&limit=10', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log('API响应:', data);
|
||||
if (data.status === 'success') {
|
||||
console.log('✓ 数据获取成功');
|
||||
console.log('记录数:', data.data.total);
|
||||
console.log('数据列表:', data.data.list);
|
||||
} else {
|
||||
console.log('✗ 响应状态异常');
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('✗ 请求失败:', err));
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
**后端完全正常**,问题很可能出在前端的以下几个方面:
|
||||
|
||||
1. **认证token未正确发送**(最可能)
|
||||
2. **数据响应格式解析错误**
|
||||
3. **页面组件渲染逻辑问题**
|
||||
4. **浏览器缓存问题**
|
||||
|
||||
建议用户按照上述步骤逐一排查,特别是先检查浏览器Network标签中的实际请求情况。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 后端文件
|
||||
- ✅ `models/SupervisoryTask.js` - 模型定义正确
|
||||
- ✅ `controllers/supervisoryTaskController.js` - 控制器逻辑正确
|
||||
- ✅ `routes/supervisoryTasks.js` - 路由配置正确
|
||||
- ✅ `src/app.js` - 路由注册正确
|
||||
|
||||
### 前端文件
|
||||
- ✅ `src/config/env.js` - 环境配置(新增)
|
||||
- ✅ `src/utils/api.js` - API封装正确
|
||||
- ✅ `src/utils/request.js` - 请求工具配置正确
|
||||
- ✅ `src/views/CompletedTaskManagement.vue` - 已更新使用API
|
||||
- ✅ `src/views/SupervisionTaskManagement.vue` - 已使用API
|
||||
- ✅ `vite.config.js` - 代理配置正确
|
||||
|
||||
### 测试文件(可删除)
|
||||
- `insurance_backend/test-supervisory-table.js`
|
||||
- `insurance_backend/test-api-response.js`
|
||||
- `insurance_backend/test-api-direct.js`
|
||||
- `insurance_backend/test-full-api-flow.js`
|
||||
|
||||
267
docs/监管任务结项管理API封装修复说明.md
Normal file
267
docs/监管任务结项管理API封装修复说明.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# 监管任务结项管理API封装修复说明
|
||||
|
||||
## 修复日期
|
||||
2025年10月9日
|
||||
|
||||
## 问题描述
|
||||
监管任务结项管理模块(`CompletedTaskManagement.vue`)中使用了模拟数据,没有调用真实的后端API接口。需要将其改为使用封装的API工具类。
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 创建环境配置文件 `src/config/env.js`
|
||||
|
||||
创建了统一的环境配置文件,用于管理所有API URL和环境变量:
|
||||
|
||||
**主要功能:**
|
||||
- 支持多环境配置(development、production、test)
|
||||
- 统一管理API基础URL、超时时间等配置
|
||||
- 定义了所有API端点的路径常量
|
||||
|
||||
**配置项:**
|
||||
```javascript
|
||||
{
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000,
|
||||
wsURL: 'ws://localhost:3000'
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 更新 API 工具类 `src/utils/api.js`
|
||||
|
||||
在 `supervisionTaskApi` 中添加了两个新的API方法:
|
||||
|
||||
```javascript
|
||||
export const supervisionTaskApi = {
|
||||
// ... 现有方法
|
||||
archive: (id) => api.patch(`/supervision-tasks/${id}/archive`),
|
||||
downloadReport: (id) => api.get(`/supervision-tasks/${id}/report`, { responseType: 'blob' })
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 更新请求工具 `src/utils/request.js`
|
||||
|
||||
- 导入环境配置文件
|
||||
- 使用 `envConfig.baseURL` 和 `envConfig.timeout` 替代硬编码的配置
|
||||
|
||||
**修改前:**
|
||||
```javascript
|
||||
const API_CONFIG = {
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```javascript
|
||||
import envConfig from '@/config/env'
|
||||
|
||||
const API_CONFIG = {
|
||||
baseURL: envConfig.baseURL,
|
||||
timeout: envConfig.timeout
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 更新用户状态管理 `src/stores/user.js`
|
||||
|
||||
- 导入环境配置文件
|
||||
- 在刷新token的fetch请求中使用 `envConfig.baseURL`
|
||||
|
||||
**修改前:**
|
||||
```javascript
|
||||
const response = await fetch('/insurance/api/auth/refresh', {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```javascript
|
||||
import envConfig from '@/config/env'
|
||||
|
||||
const response = await fetch(`${envConfig.baseURL}/auth/refresh`, {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### 5. 完善监管任务结项管理 `src/views/CompletedTaskManagement.vue`
|
||||
|
||||
#### 5.1 获取任务列表 `fetchTaskList`
|
||||
|
||||
**修改前:** 使用模拟数据
|
||||
|
||||
**修改后:** 调用真实API
|
||||
```javascript
|
||||
const fetchTaskList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
taskStatus: 'completed' // 只获取已完成的任务
|
||||
}
|
||||
|
||||
// 添加搜索条件
|
||||
if (searchForm.taskName) {
|
||||
params.taskName = searchForm.taskName
|
||||
}
|
||||
if (searchForm.status) {
|
||||
params.status = searchForm.status
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
params.startDate = searchForm.dateRange[0].format('YYYY-MM-DD')
|
||||
params.endDate = searchForm.dateRange[1].format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
const response = await supervisionTaskApi.getList(params)
|
||||
// 处理响应...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 获取统计数据 `fetchStats`
|
||||
|
||||
新增函数,调用统计API获取实时数据:
|
||||
```javascript
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await supervisionTaskApi.getStats()
|
||||
if (response.data && response.data.status === 'success') {
|
||||
const statsData = response.data.data
|
||||
stats.total = statsData.total || 0
|
||||
stats.thisMonth = statsData.thisMonth || 0
|
||||
stats.archived = statsData.archived || 0
|
||||
stats.avgDuration = statsData.avgDuration || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.3 下载报告 `handleDownload`
|
||||
|
||||
**修改前:** 仅显示提示信息
|
||||
|
||||
**修改后:** 调用API下载报告文件
|
||||
```javascript
|
||||
const handleDownload = async (record) => {
|
||||
try {
|
||||
message.loading(`正在下载 ${record.taskName} 的报告...`, 0)
|
||||
|
||||
const response = await supervisionTaskApi.downloadReport(record.id)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `${record.taskName}_报告_${new Date().getTime()}.pdf`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.destroy()
|
||||
message.success('报告下载成功')
|
||||
} catch (error) {
|
||||
message.destroy()
|
||||
console.error('下载报告失败:', error)
|
||||
message.error('下载报告失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.4 归档任务 `handleArchive`
|
||||
|
||||
**修改前:** 仅显示成功提示
|
||||
|
||||
**修改后:** 调用API执行归档操作
|
||||
```javascript
|
||||
const handleArchive = async (record) => {
|
||||
try {
|
||||
const response = await supervisionTaskApi.archive(record.id)
|
||||
if (response.data && response.data.status === 'success') {
|
||||
message.success(`任务 ${record.taskName} 已归档`)
|
||||
fetchTaskList()
|
||||
} else {
|
||||
message.error(response.data?.message || '归档失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('归档任务失败:', error)
|
||||
message.error('归档任务失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 修复后的优势
|
||||
|
||||
1. **统一配置管理**:所有API URL通过env.js统一管理,便于维护和切换环境
|
||||
2. **代码规范**:所有API调用都使用封装的工具类,符合项目规范
|
||||
3. **功能完整**:实现了真实的数据获取、统计、导出、归档、下载报告等功能
|
||||
4. **错误处理**:完善的错误提示和异常处理
|
||||
5. **用户体验**:添加了加载提示和操作反馈
|
||||
|
||||
## 后端需要实现的接口
|
||||
|
||||
为了使前端功能完整可用,后端需要实现以下接口:
|
||||
|
||||
### 1. 获取监管任务列表(支持已完成任务筛选)
|
||||
- **路径**:`GET /supervision-tasks`
|
||||
- **参数**:
|
||||
- `page`: 页码
|
||||
- `limit`: 每页数量
|
||||
- `taskStatus`: 任务状态(completed)
|
||||
- `taskName`: 任务名称(可选)
|
||||
- `status`: 状态(completed/archived)(可选)
|
||||
- `startDate`: 开始日期(可选)
|
||||
- `endDate`: 结束日期(可选)
|
||||
|
||||
### 2. 获取统计数据
|
||||
- **路径**:`GET /supervision-tasks/stats`
|
||||
- **返回数据**:
|
||||
```javascript
|
||||
{
|
||||
total: 总结项任务数,
|
||||
thisMonth: 本月结项数,
|
||||
archived: 已归档任务数,
|
||||
avgDuration: 平均处理时长(天)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 归档任务
|
||||
- **路径**:`PATCH /supervision-tasks/:id/archive`
|
||||
- **说明**:将已完成的任务标记为已归档
|
||||
|
||||
### 4. 下载任务报告
|
||||
- **路径**:`GET /supervision-tasks/:id/report`
|
||||
- **返回**:PDF文件流
|
||||
- **说明**:生成并下载任务报告
|
||||
|
||||
### 5. 导出任务列表
|
||||
- **路径**:`GET /supervision-tasks/export`
|
||||
- **参数**:与获取列表接口相同的筛选参数
|
||||
- **返回**:Excel文件流
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. 测试获取已完成任务列表功能
|
||||
2. 测试搜索和筛选功能
|
||||
3. 测试统计数据显示
|
||||
4. 测试任务归档功能
|
||||
5. 测试报告下载功能
|
||||
6. 测试导出Excel功能
|
||||
7. 测试不同环境下的API调用(开发、测试、生产)
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `insurance_admin-system/src/config/env.js` - 环境配置文件(新增)
|
||||
- `insurance_admin-system/src/utils/api.js` - API工具类(修改)
|
||||
- `insurance_admin-system/src/utils/request.js` - 请求工具(修改)
|
||||
- `insurance_admin-system/src/stores/user.js` - 用户状态管理(修改)
|
||||
- `insurance_admin-system/src/views/CompletedTaskManagement.vue` - 结项管理页面(修改)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有fetch调用都已改为使用封装的API工具类
|
||||
2. 环境配置文件支持多环境切换,部署时需要根据实际情况配置
|
||||
3. 请确保后端API接口已实现并测试通过
|
||||
4. 导出和下载功能需要后端返回正确的文件流和Content-Disposition头
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</a-sub-menu>
|
||||
|
||||
<!-- 无纸化检疫 -->
|
||||
<a-sub-menu key="/paperless/quarantine">
|
||||
<!-- <a-sub-menu key="/paperless/quarantine">
|
||||
<template #icon><SafetyOutlined /></template>
|
||||
<template #title>
|
||||
<span>无纸化检疫</span>
|
||||
@@ -93,7 +93,7 @@
|
||||
<a-menu-item key="/paperless/quarantine/declaration"><span>检疫审批</span></a-menu-item>
|
||||
<a-menu-item key="/paperless/quarantine/record-query"><span>检疫证查询</span></a-menu-item>
|
||||
<a-menu-item key="/paperless/quarantine/config"><span>检疫站清单</span></a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-sub-menu> -->
|
||||
|
||||
<!-- 生资认证 -->
|
||||
<a-menu-item key="/examine/index">
|
||||
@@ -108,7 +108,7 @@
|
||||
</a-menu-item>
|
||||
|
||||
<!-- 设备预警 -->
|
||||
<a-menu-item key="/device-alert">
|
||||
<a-menu-item key="/device-warning">
|
||||
<template #icon><ExclamationCircleOutlined /></template>
|
||||
<span>设备预警</span>
|
||||
</a-menu-item>
|
||||
|
||||
@@ -338,7 +338,7 @@ const routes = [
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHistory('/government/'), // 设置基础路径
|
||||
routes
|
||||
})
|
||||
|
||||
|
||||
62
government-admin/src/utils/approvalProcessApi.js
Normal file
62
government-admin/src/utils/approvalProcessApi.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/approval-process'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
// 可以在这里添加token等认证信息
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 审批流程管理API
|
||||
export default {
|
||||
// 获取审批流程列表
|
||||
getApprovalProcesses: (params) => api.get('/', { params }),
|
||||
|
||||
// 获取审批流程详情
|
||||
getApprovalProcessById: (id) => api.get(`/${id}`),
|
||||
|
||||
// 创建审批流程
|
||||
createApprovalProcess: (data) => api.post('/', data),
|
||||
|
||||
// 更新审批流程
|
||||
updateApprovalProcess: (id, data) => api.put(`/${id}`, data),
|
||||
|
||||
// 删除审批流程
|
||||
deleteApprovalProcess: (id) => api.delete(`/${id}`),
|
||||
|
||||
// 审批操作(通过/拒绝)
|
||||
processApproval: (id, data) => api.post(`/${id}/process`, data),
|
||||
|
||||
// 更新审批状态
|
||||
updateApprovalStatus: (id, status) => api.patch(`/${id}/status`, { status })
|
||||
}
|
||||
62
government-admin/src/utils/cattleAcademyApi.js
Normal file
62
government-admin/src/utils/cattleAcademyApi.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/cattle-academy'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
// 可以在这里添加token等认证信息
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 养牛学院资讯管理API
|
||||
export default {
|
||||
// 获取养牛学院资讯列表
|
||||
getCattleAcademyList: (params) => api.get('/', { params }),
|
||||
|
||||
// 获取养牛学院资讯详情
|
||||
getCattleAcademyById: (id) => api.get(`/${id}`),
|
||||
|
||||
// 创建养牛学院资讯
|
||||
createCattleAcademy: (data) => api.post('/', data),
|
||||
|
||||
// 更新养牛学院资讯
|
||||
updateCattleAcademy: (id, data) => api.put(`/${id}`, data),
|
||||
|
||||
// 删除养牛学院资讯
|
||||
deleteCattleAcademy: (id) => api.delete(`/${id}`),
|
||||
|
||||
// 切换资讯状态
|
||||
toggleCattleAcademyStatus: (id, status) => api.patch(`/${id}/status`, { status }),
|
||||
|
||||
// 批量更新排序
|
||||
updateSort: (items) => api.patch('/sort', { items })
|
||||
}
|
||||
44
government-admin/src/utils/deviceWarningApi.js
Normal file
44
government-admin/src/utils/deviceWarningApi.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/device-warning';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
response => {
|
||||
return response.data;
|
||||
},
|
||||
error => {
|
||||
console.error('API Error:', error.response ? error.response.data : error.message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default {
|
||||
getDeviceWarnings: (params) => api.get('/', { params }),
|
||||
getDeviceWarningById: (id) => api.get(`/${id}`),
|
||||
createDeviceWarning: (data) => api.post('/', data),
|
||||
updateDeviceWarning: (id, data) => api.put(`/${id}`, data),
|
||||
deleteDeviceWarning: (id) => api.delete(`/${id}`),
|
||||
updateWarningStatus: (id, data) => api.patch(`/${id}/status`, data),
|
||||
getWarningStats: () => api.get('/stats'),
|
||||
};
|
||||
73
government-admin/src/utils/epidemicActivityApi.js
Normal file
73
government-admin/src/utils/epidemicActivityApi.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/epidemic-activity'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
// 可以在这里添加token等认证信息
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 防疫活动管理API
|
||||
export const epidemicActivityApi = {
|
||||
// 获取防疫活动列表
|
||||
getActivities(params = {}) {
|
||||
return api.get('/', { params })
|
||||
},
|
||||
|
||||
// 根据ID获取防疫活动详情
|
||||
getActivityById(id) {
|
||||
return api.get(`/${id}`)
|
||||
},
|
||||
|
||||
// 创建防疫活动
|
||||
createActivity(data) {
|
||||
return api.post('/', data)
|
||||
},
|
||||
|
||||
// 更新防疫活动
|
||||
updateActivity(id, data) {
|
||||
return api.put(`/${id}`, data)
|
||||
},
|
||||
|
||||
// 删除防疫活动
|
||||
deleteActivity(id) {
|
||||
return api.delete(`/${id}`)
|
||||
},
|
||||
|
||||
// 切换活动状态
|
||||
toggleActivityStatus(id) {
|
||||
return api.patch(`/${id}/status`)
|
||||
}
|
||||
}
|
||||
|
||||
export default epidemicActivityApi
|
||||
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/production-material-certification'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
// 可以在这里添加token等认证信息
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 生资认证管理API
|
||||
export default {
|
||||
// 获取生资认证列表
|
||||
getCertifications: (params) => api.get('/', { params }),
|
||||
|
||||
// 获取生资认证详情
|
||||
getCertificationById: (id) => api.get(`/${id}`),
|
||||
|
||||
// 创建生资认证
|
||||
createCertification: (data) => api.post('/', data),
|
||||
|
||||
// 更新生资认证
|
||||
updateCertification: (id, data) => api.put(`/${id}`, data),
|
||||
|
||||
// 删除生资认证
|
||||
deleteCertification: (id) => api.delete(`/${id}`),
|
||||
|
||||
// 切换认证状态
|
||||
toggleCertificationStatus: (id, status) => api.patch(`/${id}/status`, { status })
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 审批卡片列表 -->
|
||||
<div class="card-list">
|
||||
<div class="card-list" v-loading="loading">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :span="8" v-for="item in approvalList" :key="item.id">
|
||||
<a-card class="approval-card" @click="() => viewApprovalDetail(item.id)">
|
||||
@@ -32,15 +32,21 @@
|
||||
<a-tag :color="getStatusColor(item.status)">{{ getStatusText(item.status) }}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<p>认证申请人: {{ item.applicant }}</p>
|
||||
<p>认证类型: {{ item.type }}</p>
|
||||
<p>认证数量: {{ item.quantity }}</p>
|
||||
<p>申请时间: {{ item.create_time }}</p>
|
||||
<p>联系电话: {{ item.phone }}</p>
|
||||
<p>养殖场名称: {{ item.farmName }}</p>
|
||||
<p><strong>审批标题:</strong> {{ item.title }}</p>
|
||||
<p><strong>申请人:</strong> {{ item.applicant }}</p>
|
||||
<p><strong>审批类型:</strong> {{ getTypeText(item.type) }}</p>
|
||||
<p><strong>申请时间:</strong> {{ item.create_time }}</p>
|
||||
<p><strong>联系电话:</strong> {{ item.phone || '未填写' }}</p>
|
||||
<p><strong>养殖场名称:</strong> {{ item.farmName }}</p>
|
||||
<p v-if="item.quantity"><strong>认证数量:</strong> {{ item.quantity }}</p>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && approvalList.length === 0" class="empty-state">
|
||||
<a-empty description="暂无审批流程数据" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@@ -137,8 +143,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { message } from 'antd'
|
||||
import axios from 'axios'
|
||||
import { message } from 'ant-design-vue'
|
||||
import approvalProcessApi from '@/utils/approvalProcessApi'
|
||||
import { UploadOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const allData = ref([])
|
||||
@@ -148,6 +154,7 @@ const currentPage = ref(1)
|
||||
const pageSize = ref(9)
|
||||
const totalItems = ref(0)
|
||||
const searchInput = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const createModalVisible = ref(false)
|
||||
const processModalVisible = ref(false)
|
||||
@@ -206,38 +213,58 @@ const getTypeText = (type) => {
|
||||
// 获取审批流程列表
|
||||
const fetchApprovalList = async () => {
|
||||
try {
|
||||
// 这里应该从API获取数据
|
||||
// 由于没有实际API,使用模拟数据
|
||||
approvalList.value = [
|
||||
{ id: 1, title: '食品经营许可证申请', type: 'license', applicant: '张三', create_time: '2024-01-10 09:00:00', status: 'pending', description: '申请食品经营许可证' },
|
||||
{ id: 2, title: '企业资质年审', type: 'enterprise', applicant: '李四', create_time: '2024-01-09 14:30:00', status: 'approved', description: '企业资质年度审核' },
|
||||
{ id: 3, title: '新药品研发项目', type: 'project', applicant: '王五', create_time: '2024-01-08 11:20:00', status: 'processing', description: '新型药品研发项目审批' },
|
||||
{ id: 4, title: '环保设施改造', type: 'project', applicant: '赵六', create_time: '2024-01-07 16:40:00', status: 'rejected', description: '工厂环保设施改造审批' },
|
||||
{ id: 5, title: '特殊行业许可证', type: 'license', applicant: '钱七', create_time: '2024-01-06 10:15:00', status: 'pending', description: '申请特殊行业经营许可证' },
|
||||
{ id: 6, title: '企业扩大经营规模', type: 'enterprise', applicant: '孙八', create_time: '2024-01-05 13:30:00', status: 'approved', description: '企业扩大生产经营规模审批' },
|
||||
{ id: 7, title: '新产品上市审批', type: 'other', applicant: '周九', create_time: '2024-01-04 09:45:00', status: 'pending', description: '新产品上市销售审批' },
|
||||
{ id: 8, title: '消防设施验收', type: 'project', applicant: '吴十', create_time: '2024-01-03 15:20:00', status: 'processing', description: '新建建筑消防设施验收' },
|
||||
{ id: 9, title: '卫生许可证换证', type: 'license', applicant: '郑一', create_time: '2024-01-02 11:00:00', status: 'approved', description: '卫生许可证到期换证' },
|
||||
{ id: 10, title: '临时占道经营', type: 'other', applicant: '王二', create_time: '2024-01-01 09:30:00', status: 'rejected', description: '临时占道经营活动审批' }
|
||||
]
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
status: activeTab.value === 'pending' ? 'pending' : activeTab.value === 'approved' ? 'approved' : undefined,
|
||||
applicant: searchInput.value
|
||||
}
|
||||
|
||||
const response = await approvalProcessApi.getApprovalProcesses(params)
|
||||
if (response.code === 200) {
|
||||
approvalList.value = response.data.list.map(item => ({
|
||||
...item,
|
||||
create_time: item.createdAt,
|
||||
farmName: item.farmName || '未填写'
|
||||
}))
|
||||
totalItems.value = response.data.total
|
||||
} else {
|
||||
message.error(response.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审批流程列表失败:', error)
|
||||
message.error('获取审批流程列表失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索审批
|
||||
const searchApproval = (value) => {
|
||||
message.info(`搜索关键词: ${value}`)
|
||||
// 这里应该根据搜索关键词过滤数据
|
||||
// 目前使用模拟数据,实际项目中需要调用API
|
||||
const onSearch = (value) => {
|
||||
searchInput.value = value
|
||||
currentPage.value = 1
|
||||
fetchApprovalList()
|
||||
}
|
||||
|
||||
// 查看审批详情
|
||||
const viewApprovalDetail = (id) => {
|
||||
message.info(`查看审批ID: ${id} 的详情`)
|
||||
// 这里可以跳转到详情页面
|
||||
// router.push(`/approval/detail/${id}`)
|
||||
const viewApprovalDetail = async (id) => {
|
||||
try {
|
||||
const response = await approvalProcessApi.getApprovalProcessById(id)
|
||||
if (response.code === 200) {
|
||||
currentApproval.value = {
|
||||
...response.data,
|
||||
create_time: response.data.createdAt,
|
||||
farmName: response.data.farmName || '未填写'
|
||||
}
|
||||
processModalVisible.value = true
|
||||
} else {
|
||||
message.error(response.message || '获取详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审批详情失败:', error)
|
||||
message.error('获取详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑审批
|
||||
@@ -264,23 +291,31 @@ const processApproval = (id) => {
|
||||
}
|
||||
|
||||
// 通过审批
|
||||
const approveApproval = () => {
|
||||
const approveApproval = async () => {
|
||||
if (!currentApproval.value) return
|
||||
|
||||
message.success(`已通过审批: ${currentApproval.value.title}`)
|
||||
// 这里应该调用审批通过API
|
||||
|
||||
// 更新本地数据
|
||||
const index = approvalList.value.findIndex(item => item.id === currentApproval.value.id)
|
||||
if (index !== -1) {
|
||||
approvalList.value[index].status = 'approved'
|
||||
try {
|
||||
const response = await approvalProcessApi.processApproval(currentApproval.value.id, {
|
||||
action: 'approve',
|
||||
approvalComment: approvalComment.value,
|
||||
approver: '当前用户' // 这里应该从用户信息中获取
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
message.success(`已通过审批: ${currentApproval.value.title}`)
|
||||
closeProcessModal()
|
||||
fetchApprovalList() // 重新加载数据
|
||||
} else {
|
||||
message.error(response.message || '审批失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批操作失败:', error)
|
||||
message.error('审批失败')
|
||||
}
|
||||
|
||||
closeProcessModal()
|
||||
}
|
||||
|
||||
// 拒绝审批
|
||||
const rejectApproval = () => {
|
||||
const rejectApproval = async () => {
|
||||
if (!currentApproval.value) return
|
||||
|
||||
if (!approvalComment.value.trim()) {
|
||||
@@ -288,16 +323,24 @@ const rejectApproval = () => {
|
||||
return
|
||||
}
|
||||
|
||||
message.success(`已拒绝审批: ${currentApproval.value.title}`)
|
||||
// 这里应该调用审批拒绝API
|
||||
|
||||
// 更新本地数据
|
||||
const index = approvalList.value.findIndex(item => item.id === currentApproval.value.id)
|
||||
if (index !== -1) {
|
||||
approvalList.value[index].status = 'rejected'
|
||||
try {
|
||||
const response = await approvalProcessApi.processApproval(currentApproval.value.id, {
|
||||
action: 'reject',
|
||||
approvalComment: approvalComment.value,
|
||||
approver: '当前用户' // 这里应该从用户信息中获取
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
message.success(`已拒绝审批: ${currentApproval.value.title}`)
|
||||
closeProcessModal()
|
||||
fetchApprovalList() // 重新加载数据
|
||||
} else {
|
||||
message.error(response.message || '审批失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批操作失败:', error)
|
||||
message.error('审批失败')
|
||||
}
|
||||
|
||||
closeProcessModal()
|
||||
}
|
||||
|
||||
// 显示新建弹窗
|
||||
@@ -333,15 +376,25 @@ const submitCreateForm = async () => {
|
||||
|
||||
try {
|
||||
await createFormRef.value.validate()
|
||||
// 这里应该调用创建审批API
|
||||
|
||||
message.success('新建审批流程成功')
|
||||
closeCreateModal()
|
||||
const response = await approvalProcessApi.createApprovalProcess({
|
||||
title: createFormData.value.title,
|
||||
type: createFormData.value.type,
|
||||
applicant: createFormData.value.applicant,
|
||||
description: createFormData.value.description,
|
||||
files: createFormData.value.files
|
||||
})
|
||||
|
||||
// 重新加载数据
|
||||
fetchApprovalList()
|
||||
if (response.code === 201) {
|
||||
message.success('新建审批流程成功')
|
||||
closeCreateModal()
|
||||
fetchApprovalList() // 重新加载数据
|
||||
} else {
|
||||
message.error(response.message || '创建失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
message.error('创建失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +415,19 @@ const exportApprovalList = () => {
|
||||
// 这里应该调用导出API
|
||||
}
|
||||
|
||||
// 标签页切换
|
||||
const handleTabChange = (key) => {
|
||||
activeTab.value = key
|
||||
currentPage.value = 1
|
||||
fetchApprovalList()
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = page
|
||||
fetchApprovalList()
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
fetchApprovalList()
|
||||
@@ -491,4 +557,56 @@ onMounted(() => {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 卡片列表样式 */
|
||||
.card-list {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.approval-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.approval-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title span {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.approval-card p {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.approval-card strong {
|
||||
color: #262626;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,108 +2,444 @@
|
||||
<div class="cattle-academy-container">
|
||||
<div class="header">
|
||||
<a-button type="primary" @click="handleNewInfo">新增资讯</a-button>
|
||||
<a-input-search
|
||||
placeholder="搜索标题或作者"
|
||||
style="width: 300px; margin-left: 16px;"
|
||||
@search="handleSearch"
|
||||
v-model:value="searchKeyword"
|
||||
/>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'coverImage'">
|
||||
<img :src="record.coverImage" alt="封面图" class="cover-image" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch :checked="record.status" />
|
||||
<a-switch
|
||||
:checked="record.status"
|
||||
@change="(checked) => handleToggleStatus(record, checked)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'operations'">
|
||||
<a-space>
|
||||
<a @click="handleView(record)">查看</a>
|
||||
<a @click="handleEdit(record)">编辑</a>
|
||||
<a @click="handleDelete(record)">删除</a>
|
||||
<a @click="handleDelete(record)" style="color: #ff4d4f;">删除</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新增/编辑表单 -->
|
||||
<a-modal
|
||||
v-model:open="isModalOpen"
|
||||
:title="currentInfo.id ? '编辑资讯' : '新增资讯'"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:width="800"
|
||||
:confirm-loading="modalLoading"
|
||||
>
|
||||
<a-form
|
||||
:model="currentInfo"
|
||||
layout="vertical"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="标题" name="title">
|
||||
<a-input v-model:value="currentInfo.title" placeholder="请输入标题" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="分类" name="category">
|
||||
<a-select v-model:value="currentInfo.category" placeholder="请选择分类">
|
||||
<a-select-option value="技术资讯">技术资讯</a-select-option>
|
||||
<a-select-option value="养殖技术">养殖技术</a-select-option>
|
||||
<a-select-option value="健康管理">健康管理</a-select-option>
|
||||
<a-select-option value="营养管理">营养管理</a-select-option>
|
||||
<a-select-option value="智能设备">智能设备</a-select-option>
|
||||
<a-select-option value="环保管理">环保管理</a-select-option>
|
||||
<a-select-option value="繁殖技术">繁殖技术</a-select-option>
|
||||
<a-select-option value="经营管理">经营管理</a-select-option>
|
||||
<a-select-option value="市场分析">市场分析</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="作者" name="author">
|
||||
<a-input v-model:value="currentInfo.author" placeholder="请输入作者" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number v-model:value="currentInfo.sort" placeholder="请输入排序" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="封面图URL" name="coverImage">
|
||||
<a-input v-model:value="currentInfo.coverImage" placeholder="请输入封面图URL" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="摘要" name="summary">
|
||||
<a-textarea v-model:value="currentInfo.summary" placeholder="请输入摘要" :rows="3" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="内容" name="content">
|
||||
<a-textarea v-model:value="currentInfo.content" placeholder="请输入内容" :rows="6" />
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="是否置顶">
|
||||
<a-switch v-model:checked="currentInfo.isTop" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="是否推荐">
|
||||
<a-switch v-model:checked="currentInfo.isRecommend" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="状态">
|
||||
<a-switch v-model:checked="currentInfo.status" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-textarea v-model:value="currentInfo.remarks" placeholder="请输入备注" :rows="2" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情查看 -->
|
||||
<a-modal
|
||||
v-model:open="isDetailModalOpen"
|
||||
title="资讯详情"
|
||||
:footer="null"
|
||||
:width="800"
|
||||
>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="标题">{{ detailData.title }}</a-descriptions-item>
|
||||
<a-descriptions-item label="分类">{{ detailData.category }}</a-descriptions-item>
|
||||
<a-descriptions-item label="作者">{{ detailData.author }}</a-descriptions-item>
|
||||
<a-descriptions-item label="排序">{{ detailData.sort }}</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览次数">{{ detailData.viewCount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="发布时间">{{ detailData.publishTime }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否置顶">
|
||||
<a-tag :color="detailData.isTop ? 'red' : 'default'">
|
||||
{{ detailData.isTop ? '是' : '否' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="是否推荐">
|
||||
<a-tag :color="detailData.isRecommend ? 'green' : 'default'">
|
||||
{{ detailData.isRecommend ? '是' : '否' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="detailData.status ? 'green' : 'red'">
|
||||
{{ detailData.status ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detailData.createdAt }}</a-descriptions-item>
|
||||
<a-descriptions-item label="封面图" :span="2">
|
||||
<img v-if="detailData.coverImage" :src="detailData.coverImage" alt="封面图" class="cover-image" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="摘要" :span="2">{{ detailData.summary }}</a-descriptions-item>
|
||||
<a-descriptions-item label="内容" :span="2">{{ detailData.content }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailData.remarks }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import cattleAcademyApi from '@/utils/cattleAcademyApi';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '封面图',
|
||||
dataIndex: 'coverImage',
|
||||
key: 'coverImage',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
dataIndex: 'author',
|
||||
key: 'author',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '浏览次数',
|
||||
dataIndex: 'viewCount',
|
||||
key: 'viewCount',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'creationTime',
|
||||
key: 'creationTime',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operations',
|
||||
width: 150,
|
||||
fixed: 'right'
|
||||
},
|
||||
];
|
||||
|
||||
const dataSource = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '新疆阿克苏地区电信5G数字乡村项目',
|
||||
coverImage: 'https://via.placeholder.com/100x50',
|
||||
sort: 100,
|
||||
status: true,
|
||||
creationTime: '2022-09-28 11:47:27',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '共享养牛,开启畜牧业财富之路',
|
||||
coverImage: 'https://via.placeholder.com/100x50',
|
||||
sort: 98,
|
||||
status: true,
|
||||
creationTime: '2023-08-28 15:38:35',
|
||||
},
|
||||
// ... more data
|
||||
]);
|
||||
const dataSource = ref([]);
|
||||
const loading = ref(false);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const pagination = ref({
|
||||
total: dataSource.value.length,
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
});
|
||||
|
||||
// 模态框状态
|
||||
const isModalOpen = ref(false);
|
||||
const isDetailModalOpen = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref(null);
|
||||
|
||||
// 当前编辑的资讯
|
||||
const currentInfo = reactive({
|
||||
id: '',
|
||||
title: '',
|
||||
coverImage: '',
|
||||
content: '',
|
||||
summary: '',
|
||||
category: '',
|
||||
sort: 0,
|
||||
status: true,
|
||||
author: '',
|
||||
publishTime: null,
|
||||
isTop: false,
|
||||
isRecommend: false,
|
||||
remarks: ''
|
||||
});
|
||||
|
||||
// 详情数据
|
||||
const detailData = reactive({});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||
],
|
||||
category: [
|
||||
{ required: true, message: '请选择分类', trigger: 'change' }
|
||||
],
|
||||
author: [
|
||||
{ required: true, message: '请输入作者', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
// 获取资讯列表
|
||||
const fetchCattleAcademyList = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
title: searchKeyword.value,
|
||||
author: searchKeyword.value
|
||||
};
|
||||
|
||||
const response = await cattleAcademyApi.getCattleAcademyList(params);
|
||||
if (response.code === 200) {
|
||||
dataSource.value = response.data.list;
|
||||
pagination.total = response.data.total;
|
||||
} else {
|
||||
message.error(response.message || '获取数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取资讯列表失败:', error);
|
||||
message.error('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchCattleAcademyList();
|
||||
};
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchCattleAcademyList();
|
||||
};
|
||||
|
||||
// 新增资讯
|
||||
const handleNewInfo = () => {
|
||||
console.log('New Info');
|
||||
};
|
||||
|
||||
const handleView = (record) => {
|
||||
console.log('View', record);
|
||||
Object.assign(currentInfo, {
|
||||
id: '',
|
||||
title: '',
|
||||
coverImage: '',
|
||||
content: '',
|
||||
summary: '',
|
||||
category: '',
|
||||
sort: 0,
|
||||
status: true,
|
||||
author: '',
|
||||
publishTime: new Date(),
|
||||
isTop: false,
|
||||
isRecommend: false,
|
||||
remarks: ''
|
||||
});
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
|
||||
// 编辑资讯
|
||||
const handleEdit = (record) => {
|
||||
console.log('Edit', record);
|
||||
Object.assign(currentInfo, {
|
||||
...record,
|
||||
publishTime: record.publishTime ? new Date(record.publishTime) : new Date()
|
||||
});
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
|
||||
const handleDelete = (record) => {
|
||||
console.log('Delete', record);
|
||||
// 查看详情
|
||||
const handleView = async (record) => {
|
||||
try {
|
||||
const response = await cattleAcademyApi.getCattleAcademyById(record.id);
|
||||
if (response.code === 200) {
|
||||
Object.assign(detailData, response.data);
|
||||
isDetailModalOpen.value = true;
|
||||
} else {
|
||||
message.error(response.message || '获取详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error);
|
||||
message.error('获取详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除资讯
|
||||
const handleDelete = async (record) => {
|
||||
try {
|
||||
const response = await cattleAcademyApi.deleteCattleAcademy(record.id);
|
||||
if (response.code === 200) {
|
||||
message.success('删除成功');
|
||||
fetchCattleAcademyList();
|
||||
} else {
|
||||
message.error(response.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除资讯失败:', error);
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 切换状态
|
||||
const handleToggleStatus = async (record, checked) => {
|
||||
try {
|
||||
const response = await cattleAcademyApi.toggleCattleAcademyStatus(record.id, checked);
|
||||
if (response.code === 200) {
|
||||
message.success('状态更新成功');
|
||||
fetchCattleAcademyList();
|
||||
} else {
|
||||
message.error(response.message || '状态更新失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换状态失败:', error);
|
||||
message.error('状态更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 保存资讯
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
modalLoading.value = true;
|
||||
|
||||
const data = {
|
||||
...currentInfo,
|
||||
publishTime: currentInfo.publishTime ? currentInfo.publishTime.toISOString() : new Date().toISOString()
|
||||
};
|
||||
|
||||
let response;
|
||||
if (currentInfo.id) {
|
||||
response = await cattleAcademyApi.updateCattleAcademy(currentInfo.id, data);
|
||||
} else {
|
||||
response = await cattleAcademyApi.createCattleAcademy(data);
|
||||
}
|
||||
|
||||
if (response.code === 200 || response.code === 201) {
|
||||
message.success(currentInfo.id ? '更新成功' : '创建成功');
|
||||
isModalOpen.value = false;
|
||||
fetchCattleAcademyList();
|
||||
} else {
|
||||
message.error(response.message || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存资讯失败:', error);
|
||||
message.error('保存失败');
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
isModalOpen.value = false;
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCattleAcademyList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,75 +3,199 @@
|
||||
<div class="header">
|
||||
<div class="title">设备预警</div>
|
||||
</div>
|
||||
|
||||
<!-- 预警统计卡片 -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">8056</div>
|
||||
<div class="stat-value">{{ warningStats.earTag }}</div>
|
||||
<div class="stat-label">耳标预警</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">1</div>
|
||||
<div class="stat-value">{{ warningStats.neckband }}</div>
|
||||
<div class="stat-label">项圈预警</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">61</div>
|
||||
<div class="stat-value">{{ warningStats.host }}</div>
|
||||
<div class="stat-label">主机预警</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-card :bordered="false">
|
||||
<!-- 设备类型标签页和搜索 -->
|
||||
<a-card :bordered="false" class="main-card">
|
||||
<div class="table-header">
|
||||
<a-tabs v-model:activeKey="activeTab">
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<a-tab-pane key="earTag" tab="智能耳标"></a-tab-pane>
|
||||
<a-tab-pane key="neckband" tab="智能项圈"></a-tab-pane>
|
||||
<a-tab-pane key="host" tab="智能主机"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-input v-model:value="searchKeyword" placeholder="请输入养殖户" style="width: 200px;">
|
||||
<template #suffix>
|
||||
<span class="iconfont icon-sousuo"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="search-container">
|
||||
<a-input
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="请输入养殖户"
|
||||
style="width: 200px;"
|
||||
@pressEnter="handleSearch"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="iconfont icon-sousuo" @click="handleSearch"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="handleSearch" style="margin-left: 8px;">Q 搜索</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:data-source="filteredTableData"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'alertType'">
|
||||
<a-tag :color="getAlertTypeColor(record.alertType)">
|
||||
{{ record.alertType }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import deviceWarningApi from '@/utils/deviceWarningApi';
|
||||
|
||||
const activeTab = ref('earTag');
|
||||
const activeTab = ref('neckband');
|
||||
const searchKeyword = ref('');
|
||||
const loading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const warningStats = ref({
|
||||
earTag: 0,
|
||||
neckband: 0,
|
||||
host: 0
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ title: '养殖场', dataIndex: 'farm', key: 'farm' },
|
||||
{ title: '养殖户', dataIndex: 'farmer', key: 'farmer' },
|
||||
{ title: '联系电话', dataIndex: 'phone', key: 'phone' },
|
||||
{ title: '设备类型', dataIndex: 'deviceType', key: 'deviceType' },
|
||||
{ title: '设备编号', dataIndex: 'deviceNumber', key: 'deviceNumber' },
|
||||
{ title: '预警类型', dataIndex: 'alertType', key: 'alertType' },
|
||||
{ title: '养殖场', dataIndex: 'farmName', key: 'farmName', width: 150 },
|
||||
{ title: '养殖户', dataIndex: 'farmerName', key: 'farmerName', width: 150 },
|
||||
{ title: '联系电话', dataIndex: 'phone', key: 'phone', width: 120 },
|
||||
{ title: '设备类型', dataIndex: 'deviceType', key: 'deviceType', width: 100 },
|
||||
{ title: '设备编号', dataIndex: 'deviceNumber', key: 'deviceNumber', width: 120 },
|
||||
{ title: '预警类型', dataIndex: 'alertType', key: 'alertType', width: 100 },
|
||||
];
|
||||
|
||||
const tableData = ref([
|
||||
{ id: '1', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407100002', alertType: '设备离线' },
|
||||
{ id: '2', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407100001', alertType: '设备离线' },
|
||||
{ id: '3', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402678', alertType: '设备离线' },
|
||||
{ id: '4', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402675', alertType: '设备离线' },
|
||||
{ id: '5', farm: '扎旗大数据中心', farmer: '扎旗大数据中心', phone: '132****9345', deviceType: '智能耳标', deviceNumber: '2407402674', alertType: '设备离线' },
|
||||
{ id: '6', farm: '139****8321_养殖场', farmer: '杜云鹏', phone: '139****8321', deviceType: '智能耳标', deviceNumber: '2404412397', alertType: '设备离线' },
|
||||
{ id: '7', farm: '139****8321_养殖场', farmer: '杜云鹏', phone: '139****8321', deviceType: '智能耳标', deviceNumber: '2404412404', alertType: '设备离线' },
|
||||
]);
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: tableData.value.length,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `共${total}条`
|
||||
});
|
||||
|
||||
// 根据当前标签页过滤数据
|
||||
const filteredTableData = computed(() => {
|
||||
let filtered = tableData.value;
|
||||
|
||||
// 根据设备类型过滤
|
||||
if (activeTab.value === 'earTag') {
|
||||
filtered = filtered.filter(item => item.deviceType === '智能耳标');
|
||||
} else if (activeTab.value === 'neckband') {
|
||||
filtered = filtered.filter(item => item.deviceType === '智能项圈');
|
||||
} else if (activeTab.value === 'host') {
|
||||
filtered = filtered.filter(item => item.deviceType === '智能主机');
|
||||
}
|
||||
|
||||
// 根据搜索关键词过滤
|
||||
if (searchKeyword.value) {
|
||||
filtered = filtered.filter(item =>
|
||||
item.farmerName.includes(searchKeyword.value) ||
|
||||
item.farmName.includes(searchKeyword.value)
|
||||
);
|
||||
}
|
||||
|
||||
pagination.total = filtered.length;
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// 获取预警类型颜色
|
||||
const getAlertTypeColor = (alertType) => {
|
||||
const colorMap = {
|
||||
'设备离线': 'red',
|
||||
'电量不足': 'orange',
|
||||
'信号异常': 'yellow',
|
||||
'温度异常': 'purple',
|
||||
'其他': 'default'
|
||||
};
|
||||
return colorMap[alertType] || 'default';
|
||||
};
|
||||
|
||||
// 获取设备预警数据
|
||||
const fetchDeviceWarnings = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
deviceType: activeTab.value === 'earTag' ? '智能耳标' :
|
||||
activeTab.value === 'neckband' ? '智能项圈' :
|
||||
activeTab.value === 'host' ? '智能主机' : undefined,
|
||||
farmerName: searchKeyword.value || undefined
|
||||
};
|
||||
|
||||
const response = await deviceWarningApi.getDeviceWarnings(params);
|
||||
if (response.code === 200) {
|
||||
tableData.value = response.data.list;
|
||||
pagination.total = response.data.total;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备预警数据失败:', error);
|
||||
message.error('获取设备预警数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取预警统计
|
||||
const fetchWarningStats = async () => {
|
||||
try {
|
||||
const response = await deviceWarningApi.getWarningStats();
|
||||
if (response.code === 200) {
|
||||
warningStats.value = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取预警统计失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 标签页切换
|
||||
const handleTabChange = (key) => {
|
||||
activeTab.value = key;
|
||||
pagination.current = 1;
|
||||
fetchDeviceWarnings();
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchDeviceWarnings();
|
||||
};
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchDeviceWarnings();
|
||||
};
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
fetchDeviceWarnings();
|
||||
fetchWarningStats();
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -80,6 +204,7 @@ const pagination = reactive({
|
||||
.page-container {
|
||||
background-color: #f0f2f5;
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -89,6 +214,7 @@ const pagination = reactive({
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
@@ -99,22 +225,36 @@ const pagination = reactive({
|
||||
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
color: #595959;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
@@ -122,6 +262,84 @@ const pagination = reactive({
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab-active) {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-ink-bar) {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
:deep(.ant-pagination) {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.ant-pagination-total-text) {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper:focus) {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
:deep(.ant-btn-primary) {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.ant-tag) {
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.icon-sousuo {
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon-sousuo:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.important-messages {
|
||||
|
||||
@@ -123,10 +123,10 @@ const captchaUrl = ref('')
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
remember: true
|
||||
remember: false
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
@@ -212,20 +212,7 @@ onMounted(() => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 40%, rgba(120, 119, 198, 0.2) 0%, transparent 50%);
|
||||
}
|
||||
background: #f5f5f5;
|
||||
|
||||
.background-overlay {
|
||||
position: absolute;
|
||||
@@ -233,7 +220,7 @@ onMounted(() => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,626 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>生资认证管理</h1>
|
||||
|
||||
<!-- 搜索和操作栏 -->
|
||||
<a-card style="margin-bottom: 16px;">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
|
||||
<a-input v-model:value="searchKeyword" placeholder="输入生产资料名称或生产厂商" style="width: 250px;">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-sousuo"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<a-select v-model:value="materialTypeFilter" placeholder="生产资料类型" style="width: 150px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="疫苗">疫苗</a-select-option>
|
||||
<a-select-option value="饲料">饲料</a-select-option>
|
||||
<a-select-option value="兽药">兽药</a-select-option>
|
||||
<a-select-option value="器械">器械</a-select-option>
|
||||
<a-select-option value="其他">其他</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select v-model:value="certificationTypeFilter" placeholder="认证类型" style="width: 150px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="兽药GMP认证">兽药GMP认证</a-select-option>
|
||||
<a-select-option value="饲料生产许可证">饲料生产许可证</a-select-option>
|
||||
<a-select-option value="医疗器械注册证">医疗器械注册证</a-select-option>
|
||||
<a-select-option value="其他认证">其他认证</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select v-model:value="statusFilter" placeholder="认证状态" style="width: 120px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="valid">有效</a-select-option>
|
||||
<a-select-option value="expired">过期</a-select-option>
|
||||
<a-select-option value="suspended">暂停</a-select-option>
|
||||
<a-select-option value="revoked">撤销</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button type="primary" @click="handleSearch" style="margin-left: auto;">
|
||||
<span class="iconfont icon-sousuo"></span> 搜索
|
||||
</a-button>
|
||||
|
||||
<a-button type="default" @click="handleReset">重置</a-button>
|
||||
|
||||
<a-button type="primary" danger @click="handleAddCertification">
|
||||
<span class="iconfont icon-tianjia"></span> 新增认证
|
||||
</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="certificationsData"
|
||||
row-key="id"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'certificationStatus'">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<a-switch
|
||||
:checked="record.certificationStatus === 'valid'"
|
||||
size="small"
|
||||
@change="(checked) => handleToggleStatus(record, checked)"
|
||||
/>
|
||||
<span>{{ getStatusText(record.certificationStatus) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" danger @click="handleDelete(record.id)">删除</a-button>
|
||||
<a-button size="small" @click="handleDetail(record)">详情</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增/编辑表单 -->
|
||||
<a-modal
|
||||
v-model:open="isModalOpen"
|
||||
:title="currentCertification.id ? '编辑生资认证' : '新增生资认证'"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:width="600"
|
||||
:confirm-loading="modalLoading"
|
||||
>
|
||||
<a-form
|
||||
:model="currentCertification"
|
||||
layout="vertical"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="生产资料名称" name="materialName">
|
||||
<a-input v-model:value="currentCertification.materialName" placeholder="请输入生产资料名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="生产资料类型" name="materialType">
|
||||
<a-select v-model:value="currentCertification.materialType" placeholder="请选择生产资料类型">
|
||||
<a-select-option value="疫苗">疫苗</a-select-option>
|
||||
<a-select-option value="饲料">饲料</a-select-option>
|
||||
<a-select-option value="兽药">兽药</a-select-option>
|
||||
<a-select-option value="器械">器械</a-select-option>
|
||||
<a-select-option value="其他">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="生产厂商" name="manufacturer">
|
||||
<a-input v-model:value="currentCertification.manufacturer" placeholder="请输入生产厂商" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="认证编号" name="certificationNumber">
|
||||
<a-input v-model:value="currentCertification.certificationNumber" placeholder="请输入认证编号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="认证类型" name="certificationType">
|
||||
<a-select v-model:value="currentCertification.certificationType" placeholder="请选择认证类型">
|
||||
<a-select-option value="兽药GMP认证">兽药GMP认证</a-select-option>
|
||||
<a-select-option value="饲料生产许可证">饲料生产许可证</a-select-option>
|
||||
<a-select-option value="医疗器械注册证">医疗器械注册证</a-select-option>
|
||||
<a-select-option value="其他认证">其他认证</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="认证机构" name="certificationAuthority">
|
||||
<a-input v-model:value="currentCertification.certificationAuthority" placeholder="请输入认证机构" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="发证日期" name="issueDate">
|
||||
<a-date-picker v-model:value="currentCertification.issueDate" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="有效期至" name="expiryDate">
|
||||
<a-date-picker v-model:value="currentCertification.expiryDate" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="产品描述" name="description">
|
||||
<a-textarea v-model:value="currentCertification.description" placeholder="请输入产品描述" :rows="3" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="技术规格" name="specifications">
|
||||
<a-textarea v-model:value="currentCertification.specifications" placeholder="请输入技术规格" :rows="2" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="适用范围" name="applicableScope">
|
||||
<a-textarea v-model:value="currentCertification.applicableScope" placeholder="请输入适用范围" :rows="2" />
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系人" name="contactPerson">
|
||||
<a-input v-model:value="currentCertification.contactPerson" placeholder="请输入联系人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="contactPhone">
|
||||
<a-input v-model:value="currentCertification.contactPhone" placeholder="请输入联系电话" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-textarea v-model:value="currentCertification.remarks" placeholder="请输入备注" :rows="2" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 详情查看 -->
|
||||
<a-modal
|
||||
v-model:open="isDetailModalOpen"
|
||||
title="生资认证详情"
|
||||
:footer="null"
|
||||
:width="600"
|
||||
>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="生产资料名称">{{ detailData.materialName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="生产资料类型">{{ detailData.materialType }}</a-descriptions-item>
|
||||
<a-descriptions-item label="生产厂商">{{ detailData.manufacturer }}</a-descriptions-item>
|
||||
<a-descriptions-item label="认证编号">{{ detailData.certificationNumber }}</a-descriptions-item>
|
||||
<a-descriptions-item label="认证类型">{{ detailData.certificationType }}</a-descriptions-item>
|
||||
<a-descriptions-item label="认证机构">{{ detailData.certificationAuthority }}</a-descriptions-item>
|
||||
<a-descriptions-item label="发证日期">{{ detailData.issueDate }}</a-descriptions-item>
|
||||
<a-descriptions-item label="有效期至">{{ detailData.expiryDate }}</a-descriptions-item>
|
||||
<a-descriptions-item label="认证状态">
|
||||
<a-tag :color="getStatusColor(detailData.certificationStatus)">
|
||||
{{ getStatusText(detailData.certificationStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="联系人">{{ detailData.contactPerson }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">{{ detailData.contactPhone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品描述" :span="2">{{ detailData.description }}</a-descriptions-item>
|
||||
<a-descriptions-item label="技术规格" :span="2">{{ detailData.specifications }}</a-descriptions-item>
|
||||
<a-descriptions-item label="适用范围" :span="2">{{ detailData.applicableScope }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detailData.remarks }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import productionMaterialCertificationApi from '@/utils/productionMaterialCertificationApi'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 搜索条件
|
||||
const searchKeyword = ref('')
|
||||
const materialTypeFilter = ref('')
|
||||
const certificationTypeFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
})
|
||||
|
||||
// 模态框状态
|
||||
const isModalOpen = ref(false)
|
||||
const isDetailModalOpen = ref(false)
|
||||
const modalLoading = ref(false)
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref(null)
|
||||
|
||||
// 当前编辑的认证
|
||||
const currentCertification = reactive({
|
||||
id: '',
|
||||
materialName: '',
|
||||
materialType: '',
|
||||
manufacturer: '',
|
||||
certificationNumber: '',
|
||||
certificationType: '',
|
||||
certificationAuthority: '',
|
||||
issueDate: null,
|
||||
expiryDate: null,
|
||||
certificationStatus: 'valid',
|
||||
description: '',
|
||||
specifications: '',
|
||||
applicableScope: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 详情数据
|
||||
const detailData = reactive({})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
materialName: [
|
||||
{ required: true, message: '请输入生产资料名称', trigger: 'blur' }
|
||||
],
|
||||
materialType: [
|
||||
{ required: true, message: '请选择生产资料类型', trigger: 'change' }
|
||||
],
|
||||
manufacturer: [
|
||||
{ required: true, message: '请输入生产厂商', trigger: 'blur' }
|
||||
],
|
||||
certificationNumber: [
|
||||
{ required: true, message: '请输入认证编号', trigger: 'blur' }
|
||||
],
|
||||
certificationType: [
|
||||
{ required: true, message: '请选择认证类型', trigger: 'change' }
|
||||
],
|
||||
certificationAuthority: [
|
||||
{ required: true, message: '请输入认证机构', trigger: 'blur' }
|
||||
],
|
||||
issueDate: [
|
||||
{ required: true, message: '请选择发证日期', trigger: 'change' }
|
||||
],
|
||||
expiryDate: [
|
||||
{ required: true, message: '请选择有效期至', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 认证列表数据
|
||||
const certificationsData = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '生产资料名称',
|
||||
dataIndex: 'materialName',
|
||||
key: 'materialName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '生产资料类型',
|
||||
dataIndex: 'materialType',
|
||||
key: 'materialType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '生产厂商',
|
||||
dataIndex: 'manufacturer',
|
||||
key: 'manufacturer',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '认证编号',
|
||||
dataIndex: 'certificationNumber',
|
||||
key: 'certificationNumber',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '认证类型',
|
||||
dataIndex: 'certificationType',
|
||||
key: 'certificationType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '认证机构',
|
||||
dataIndex: 'certificationAuthority',
|
||||
key: 'certificationAuthority',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '发证日期',
|
||||
dataIndex: 'issueDate',
|
||||
key: 'issueDate',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '有效期至',
|
||||
dataIndex: 'expiryDate',
|
||||
key: 'expiryDate',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '认证状态',
|
||||
dataIndex: 'certificationStatus',
|
||||
key: 'certificationStatus',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updatedAt',
|
||||
key: 'updatedAt',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'valid': '有效',
|
||||
'expired': '过期',
|
||||
'suspended': '暂停',
|
||||
'revoked': '撤销'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status) => {
|
||||
const colorMap = {
|
||||
'valid': 'green',
|
||||
'expired': 'red',
|
||||
'suspended': 'orange',
|
||||
'revoked': 'red'
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 获取认证列表
|
||||
const fetchCertifications = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
materialName: searchKeyword.value,
|
||||
materialType: materialTypeFilter.value,
|
||||
certificationType: certificationTypeFilter.value,
|
||||
certificationStatus: statusFilter.value
|
||||
}
|
||||
|
||||
const response = await productionMaterialCertificationApi.getCertifications(params)
|
||||
if (response.code === 200) {
|
||||
certificationsData.value = response.data.list
|
||||
pagination.total = response.data.total
|
||||
} else {
|
||||
message.error(response.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取认证列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchCertifications()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchKeyword.value = ''
|
||||
materialTypeFilter.value = ''
|
||||
certificationTypeFilter.value = ''
|
||||
statusFilter.value = ''
|
||||
pagination.current = 1
|
||||
fetchCertifications()
|
||||
}
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
fetchCertifications()
|
||||
}
|
||||
|
||||
// 新增认证
|
||||
const handleAddCertification = () => {
|
||||
Object.assign(currentCertification, {
|
||||
id: '',
|
||||
materialName: '',
|
||||
materialType: '',
|
||||
manufacturer: '',
|
||||
certificationNumber: '',
|
||||
certificationType: '',
|
||||
certificationAuthority: '',
|
||||
issueDate: null,
|
||||
expiryDate: null,
|
||||
certificationStatus: 'valid',
|
||||
description: '',
|
||||
specifications: '',
|
||||
applicableScope: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
remarks: ''
|
||||
})
|
||||
isModalOpen.value = true
|
||||
}
|
||||
|
||||
// 编辑认证
|
||||
const handleEdit = (record) => {
|
||||
Object.assign(currentCertification, {
|
||||
...record,
|
||||
issueDate: record.issueDate ? dayjs(record.issueDate) : null,
|
||||
expiryDate: record.expiryDate ? dayjs(record.expiryDate) : null
|
||||
})
|
||||
isModalOpen.value = true
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleDetail = (record) => {
|
||||
Object.assign(detailData, record)
|
||||
isDetailModalOpen.value = true
|
||||
}
|
||||
|
||||
// 删除认证
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
const response = await productionMaterialCertificationApi.deleteCertification(id)
|
||||
if (response.code === 200) {
|
||||
message.success('删除成功')
|
||||
fetchCertifications()
|
||||
} else {
|
||||
message.error(response.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除认证失败:', error)
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
const handleToggleStatus = async (record, checked) => {
|
||||
try {
|
||||
const status = checked ? 'valid' : 'suspended'
|
||||
const response = await productionMaterialCertificationApi.toggleCertificationStatus(record.id, status)
|
||||
if (response.code === 200) {
|
||||
message.success('状态更新成功')
|
||||
fetchCertifications()
|
||||
} else {
|
||||
message.error(response.message || '状态更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换状态失败:', error)
|
||||
message.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 保存认证
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
modalLoading.value = true
|
||||
|
||||
const data = {
|
||||
...currentCertification,
|
||||
issueDate: currentCertification.issueDate ? currentCertification.issueDate.format('YYYY-MM-DD') : null,
|
||||
expiryDate: currentCertification.expiryDate ? currentCertification.expiryDate.format('YYYY-MM-DD') : null
|
||||
}
|
||||
|
||||
let response
|
||||
if (currentCertification.id) {
|
||||
response = await productionMaterialCertificationApi.updateCertification(currentCertification.id, data)
|
||||
} else {
|
||||
response = await productionMaterialCertificationApi.createCertification(data)
|
||||
}
|
||||
|
||||
if (response.code === 200 || response.code === 201) {
|
||||
message.success(currentCertification.id ? '更新成功' : '创建成功')
|
||||
isModalOpen.value = false
|
||||
fetchCertifications()
|
||||
} else {
|
||||
message.error(response.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存认证失败:', error)
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
isModalOpen.value = false
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCertifications()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-modal-content) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-modal-header) {
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-modal-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label > label) {
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-primary) {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-primary:hover) {
|
||||
background-color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-dangerous) {
|
||||
background-color: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-dangerous:hover) {
|
||||
background-color: #ff7875;
|
||||
border-color: #ff7875;
|
||||
}
|
||||
|
||||
:deep(.ant-btn) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-input) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-select-selector) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.ant-picker) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +0,0 @@
|
||||
// 测试行政人员管理API接口
|
||||
import axios from 'axios';
|
||||
|
||||
// 创建axios实例
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 登录获取token
|
||||
const login = async () => {
|
||||
try {
|
||||
const response = await instance.post('/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
console.log('登录成功,获取到token');
|
||||
return response.data.token;
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试获取行政人员列表
|
||||
const testGetAdminStaffList = async (token) => {
|
||||
try {
|
||||
instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||||
const response = await instance.get('/personnel');
|
||||
console.log('\n获取行政人员列表成功:');
|
||||
console.log('- 状态码:', response.status);
|
||||
console.log('- 返回数据结构:', Object.keys(response.data));
|
||||
console.log('- 行政人员总数:', response.data.total);
|
||||
console.log('- 返回的行政人员列表长度:', response.data.data.length);
|
||||
if (response.data.data.length > 0) {
|
||||
console.log('- 第一条行政人员数据:', response.data.data[0]);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取行政人员列表失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 主测试函数
|
||||
const runTests = async () => {
|
||||
console.log('开始测试行政人员管理API...');
|
||||
try {
|
||||
// 1. 登录获取token
|
||||
const token = await login();
|
||||
|
||||
// 2. 测试获取行政人员列表
|
||||
await testGetAdminStaffList(token);
|
||||
|
||||
console.log('\n所有测试完成!');
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 运行测试
|
||||
runTests();
|
||||
@@ -1,152 +0,0 @@
|
||||
// 前端组件API测试脚本 - ES模块格式
|
||||
import axios from 'axios';
|
||||
|
||||
// 创建axios实例
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 登录获取token
|
||||
async function login() {
|
||||
try {
|
||||
const response = await instance.post('/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
console.log('登录成功:', response.data);
|
||||
// 修正token获取路径 - 正确路径是response.data.data.token
|
||||
return response.data.data?.token || null;
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
console.error('错误详情:', error.response?.data || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取行政人员列表
|
||||
async function testGetAdminStaffList(token) {
|
||||
try {
|
||||
const response = await instance.get('/personnel', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
params: {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
console.log('行政人员列表获取成功:');
|
||||
console.log('- 数据结构:', Object.keys(response.data));
|
||||
console.log('- 行政人员总数:', response.data.total);
|
||||
console.log('- 行政人员列表数据长度:', response.data.data?.length || 0);
|
||||
if (response.data.data && response.data.data.length > 0) {
|
||||
console.log('- 第一条数据示例:', response.data.data[0]);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('行政人员列表获取失败:', error);
|
||||
console.error('错误详情:', error.response?.data || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取部门列表
|
||||
async function testGetDepartmentsList(token) {
|
||||
try {
|
||||
const response = await instance.get('/government/departments', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
console.log('部门列表获取成功:');
|
||||
console.log('- 数据结构:', Object.keys(response.data));
|
||||
console.log('- 部门列表数据长度:', response.data.data?.length || 0);
|
||||
if (response.data.data && response.data.data.length > 0) {
|
||||
console.log('- 第一条数据示例:', response.data.data[0]);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('部门列表获取失败:', error);
|
||||
console.error('错误详情:', error.response?.data || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取岗位列表
|
||||
async function testGetPositionsList(token) {
|
||||
try {
|
||||
const response = await instance.get('/government/positions', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
console.log('岗位列表获取成功:');
|
||||
console.log('- 数据结构:', Object.keys(response.data));
|
||||
console.log('- 岗位列表数据长度:', response.data.data?.length || 0);
|
||||
if (response.data.data && response.data.data.length > 0) {
|
||||
console.log('- 第一条数据示例:', response.data.data[0]);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('岗位列表获取失败:', error);
|
||||
console.error('错误详情:', error.response?.data || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数 - 模拟AdminStaff组件的初始化流程
|
||||
async function runComponentTest() {
|
||||
console.log('开始模拟AdminStaff组件初始化测试...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 登录认证');
|
||||
const token = await login();
|
||||
if (!token) {
|
||||
console.error('测试失败: 无法获取登录token');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 测试获取部门列表 - 对应组件中的fetchDepartments()
|
||||
console.log('\n2. 获取部门列表');
|
||||
const departmentsData = await testGetDepartmentsList(token);
|
||||
|
||||
// 3. 测试获取岗位列表 - 对应组件中的fetchPositions()
|
||||
console.log('\n3. 获取岗位列表');
|
||||
const positionsData = await testGetPositionsList(token);
|
||||
|
||||
// 4. 测试获取行政人员列表 - 对应组件中的fetchAdminStaffList()
|
||||
console.log('\n4. 获取行政人员列表');
|
||||
const staffListData = await testGetAdminStaffList(token);
|
||||
|
||||
console.log('\n测试完成!');
|
||||
console.log('----------------------------------------');
|
||||
console.log('测试结果总结:');
|
||||
console.log('- 登录:', token ? '成功' : '失败');
|
||||
console.log('- 部门列表:', departmentsData ? '成功' : '失败');
|
||||
console.log('- 岗位列表:', positionsData ? '成功' : '失败');
|
||||
console.log('- 行政人员列表:', staffListData ? '成功' : '失败');
|
||||
|
||||
// 分析可能的组件渲染问题
|
||||
if (staffListData && departmentsData && positionsData) {
|
||||
console.log('\n所有API调用成功,但页面仍显示空白可能的原因:');
|
||||
console.log('1. 数据格式不匹配 - 检查返回数据结构是否与组件期望的一致');
|
||||
console.log('2. 组件生命周期问题 - 检查onMounted中是否正确调用initData()');
|
||||
console.log('3. 数据处理逻辑错误 - 检查staffData.value的赋值和转换逻辑');
|
||||
console.log('4. 权限问题 - 检查用户角色和权限是否正确');
|
||||
console.log('5. 前端控制台错误 - 检查浏览器控制台是否有详细错误信息');
|
||||
} else {
|
||||
console.log('\nAPI调用失败是页面空白的可能原因,请检查:');
|
||||
console.log('1. 后端接口是否正确实现');
|
||||
console.log('2. 认证token是否有效');
|
||||
console.log('3. 网络连接是否正常');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runComponentTest().catch(err => {
|
||||
console.error('测试过程中发生错误:', err);
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
// 从前端目录测试API访问
|
||||
const axios = require('axios');
|
||||
|
||||
// 创建axios实例,使用与前端相同的配置
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
async function testApi() {
|
||||
try {
|
||||
console.log('开始从前端目录测试API...');
|
||||
|
||||
// 测试行政人员列表API
|
||||
console.log('\n测试行政人员列表API:');
|
||||
const adminStaffResponse = await api.get('/government/admin-staff');
|
||||
console.log(`✅ 行政人员列表API调用成功,返回${adminStaffResponse.data.length}条数据`);
|
||||
|
||||
// 测试部门列表API
|
||||
console.log('\n测试部门列表API:');
|
||||
const departmentResponse = await api.get('/government/departments');
|
||||
console.log(`✅ 部门列表API调用成功,返回${departmentResponse.data.length}条数据`);
|
||||
|
||||
// 测试岗位列表API
|
||||
console.log('\n测试岗位列表API:');
|
||||
const positionResponse = await api.get('/government/positions');
|
||||
console.log(`✅ 岗位列表API调用成功,返回${positionResponse.data.length}条数据`);
|
||||
|
||||
// 测试带有查询参数的API
|
||||
console.log('\n测试带查询参数的API:');
|
||||
const filteredResponse = await api.get('/government/admin-staff?page=1&pageSize=3');
|
||||
console.log(`✅ 带查询参数的API调用成功,返回${filteredResponse.data.length}条数据`);
|
||||
|
||||
console.log('\n✅ 所有API测试成功完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ API测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error('没有收到响应:', error.request);
|
||||
} else {
|
||||
console.error('请求配置错误:', error.message);
|
||||
}
|
||||
console.error('错误详情:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testApi();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TextArea 测试页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/test-main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,86 +0,0 @@
|
||||
// 测试前端认证修复效果的脚本
|
||||
import axios from 'axios';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// 配置
|
||||
const BASE_URL = 'http://localhost:5352/api';
|
||||
const USERNAME = 'admin';
|
||||
const PASSWORD = '123456';
|
||||
|
||||
// 测试函数
|
||||
async function runTest() {
|
||||
console.log('===== 开始测试智慧耳标页面认证问题 =====');
|
||||
|
||||
try {
|
||||
// 1. 尝试登录获取token
|
||||
console.log('1. 尝试登录...');
|
||||
const loginResponse = await axios.post(`${BASE_URL}/auth/login`, {
|
||||
username: USERNAME,
|
||||
password: PASSWORD
|
||||
});
|
||||
|
||||
if (loginResponse.data && loginResponse.data.token) {
|
||||
const token = loginResponse.data.token;
|
||||
console.log('登录成功,获取到token:', token);
|
||||
|
||||
// 2. 使用获取的token尝试访问智能耳标API
|
||||
console.log('2. 使用token访问智能耳标API...');
|
||||
const headers = { 'Authorization': `Bearer ${token}` };
|
||||
const smartEarmarkResponse = await axios.get(`${BASE_URL}/smart-earmark`, { headers });
|
||||
|
||||
console.log('智能耳标API访问成功,返回状态:', smartEarmarkResponse.status);
|
||||
console.log('返回数据示例:', JSON.stringify(smartEarmarkResponse.data.slice(0, 1), null, 2));
|
||||
|
||||
console.log('\n===== 测试成功!智慧耳标页面认证问题已修复 =====');
|
||||
} else {
|
||||
console.error('登录失败,未获取到token:', loginResponse.data);
|
||||
|
||||
// 如果后端使用模拟数据,我们也创建一个模拟token来测试
|
||||
console.log('\n3. 尝试使用模拟token访问API(适用于前端模拟登录场景)...');
|
||||
const mockToken = 'mock-jwt-token-' + Date.now();
|
||||
const headers = { 'Authorization': `Bearer ${mockToken}` };
|
||||
|
||||
try {
|
||||
const smartEarmarkResponse = await axios.get(`${BASE_URL}/smart-earmark`, {
|
||||
headers,
|
||||
timeout: 5000
|
||||
});
|
||||
console.log('使用模拟token访问成功,状态:', smartEarmarkResponse.status);
|
||||
} catch (error) {
|
||||
console.error('使用模拟token访问失败:', error.code || error.message);
|
||||
if (error.response) {
|
||||
console.error('错误详情:', error.response.status, error.response.data);
|
||||
}
|
||||
|
||||
// 记录问题以便后续分析
|
||||
const errorInfo = {
|
||||
timestamp: new Date().toISOString(),
|
||||
error: error.message,
|
||||
response: error.response ? {
|
||||
status: error.response.status,
|
||||
data: error.response.data
|
||||
} : null
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, 'auth_error.log'),
|
||||
JSON.stringify(errorInfo, null, 2) + '\n',
|
||||
{ flag: 'a' }
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误详情:', error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error('未收到响应:', error.request);
|
||||
console.error('请检查后端服务是否正常运行在端口5352');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTest();
|
||||
@@ -1,66 +0,0 @@
|
||||
// 简化的测试脚本:专门验证智慧耳标API访问
|
||||
import axios from 'axios';
|
||||
|
||||
// 配置
|
||||
const API_BASE_URL = 'http://localhost:5352/api';
|
||||
const TEST_USERNAME = 'admin';
|
||||
const TEST_PASSWORD = '123456';
|
||||
|
||||
// 创建axios实例
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 测试函数
|
||||
async function testEarmarkApi() {
|
||||
try {
|
||||
console.log('===== 开始测试智慧耳标API =====');
|
||||
|
||||
// 1. 尝试登录获取真实token
|
||||
console.log('1. 登录获取token...');
|
||||
const loginResponse = await apiClient.post('/auth/login', {
|
||||
username: TEST_USERNAME,
|
||||
password: TEST_PASSWORD
|
||||
});
|
||||
|
||||
if (loginResponse.status === 200 && loginResponse.data.code === 200) {
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('登录成功,获取到token');
|
||||
|
||||
// 2. 使用获取的token访问智慧耳标API
|
||||
console.log('2. 使用token访问智慧耳标API...');
|
||||
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const earmarkResponse = await apiClient.get('/smart-earmark');
|
||||
|
||||
if (earmarkResponse.status === 200) {
|
||||
console.log('智慧耳标API访问成功!');
|
||||
console.log('响应状态码:', earmarkResponse.status);
|
||||
console.log('数据总量:', earmarkResponse.data.data?.length || '无数据');
|
||||
console.log('\n===== 测试成功 =====');
|
||||
console.log('修复总结:');
|
||||
console.log('1. 前端:修复了api.js中缺失的message组件导入');
|
||||
console.log('2. 前端:修复了authStore中认证状态判断问题');
|
||||
console.log('3. 后端:修复了auth.js中JWT验证相关的导入问题');
|
||||
console.log('4. 后端:修复了重复声明的变量问题');
|
||||
console.log('\n结论:点击智慧耳标页面自动退出到登录页的问题已解决!');
|
||||
} else {
|
||||
console.error('智慧耳标API访问失败,状态码:', earmarkResponse.status);
|
||||
}
|
||||
} else {
|
||||
console.error('登录失败:', loginResponse.data?.message || '未知错误');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误详情:', error.response.status, error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testEarmarkApi();
|
||||
@@ -4,6 +4,7 @@ import vue from '@vitejs/plugin-vue'
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
base: '/government/', // 设置基础路径
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src',
|
||||
@@ -18,5 +19,18 @@ export default defineConfig({
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['vue', 'vue-router', 'ant-design-vue'],
|
||||
echarts: ['echarts', 'vue-echarts']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -55,6 +55,11 @@ app.use('/api/harmless', require('./routes/harmless'));
|
||||
app.use('/api/harmless-place', require('./routes/harmlessPlace'));
|
||||
app.use('/api/epidemic-record', require('./routes/epidemicRecord'));
|
||||
app.use('/api/vaccine', require('./routes/vaccine'));
|
||||
app.use('/api/epidemic-activity', require('./routes/epidemicActivity'));
|
||||
app.use('/api/production-material-certification', require('./routes/productionMaterialCertification'));
|
||||
app.use('/api/approval-process', require('./routes/approvalProcess'));
|
||||
app.use('/api/cattle-academy', require('./routes/cattleAcademy'));
|
||||
app.use('/api/device-warning', require('./routes/deviceWarning'));
|
||||
|
||||
// 健康检查
|
||||
app.get('/health', (req, res) => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// 直接检查slaughter路由模块的导出内容
|
||||
const slaughterRoutes = require('./routes/slaughter');
|
||||
|
||||
console.log('模块类型:', typeof slaughterRoutes);
|
||||
console.log('是否为Express Router:', slaughterRoutes && slaughterRoutes.constructor && slaughterRoutes.constructor.name);
|
||||
console.log('是否有stack属性:', 'stack' in slaughterRoutes);
|
||||
|
||||
if (slaughterRoutes && slaughterRoutes.stack) {
|
||||
console.log('stack长度:', slaughterRoutes.stack.length);
|
||||
slaughterRoutes.stack.forEach((layer, index) => {
|
||||
console.log(`Layer ${index}:`, layer);
|
||||
});
|
||||
}
|
||||
|
||||
// 也检查另一个已知正常的路由模块,比如auth.js
|
||||
const authRoutes = require('./routes/auth');
|
||||
console.log('\nauth路由模块类型:', typeof authRoutes);
|
||||
console.log('auth路由模块构造函数:', authRoutes && authRoutes.constructor && authRoutes.constructor.name);
|
||||
@@ -1,31 +0,0 @@
|
||||
// 测试HarmlessPlace模型的导出和方法
|
||||
const HarmlessPlace = require('./models/HarmlessPlace');
|
||||
const User = require('./models/User');
|
||||
|
||||
console.log('=== 检查HarmlessPlace模型 ===');
|
||||
console.log('HarmlessPlace的类型:', typeof HarmlessPlace);
|
||||
console.log('HarmlessPlace是否为对象:', typeof HarmlessPlace === 'object' && HarmlessPlace !== null);
|
||||
console.log('HarmlessPlace是否有findAndCountAll方法:', typeof HarmlessPlace.findAndCountAll !== 'undefined');
|
||||
if (HarmlessPlace.findAndCountAll) {
|
||||
console.log('findAndCountAll的类型:', typeof HarmlessPlace.findAndCountAll);
|
||||
}
|
||||
console.log('\nHarmlessPlace对象的所有属性和方法:');
|
||||
console.log(Object.keys(HarmlessPlace));
|
||||
|
||||
console.log('\n=== 检查User模型(作为对比)===');
|
||||
console.log('User的类型:', typeof User);
|
||||
console.log('User是否为对象:', typeof User === 'object' && User !== null);
|
||||
console.log('User是否有findAndCountAll方法:', typeof User.findAndCountAll !== 'undefined');
|
||||
if (User.findAndCountAll) {
|
||||
console.log('findAndCountAll的类型:', typeof User.findAndCountAll);
|
||||
}
|
||||
console.log('\nUser对象的所有属性和方法:');
|
||||
console.log(Object.keys(User));
|
||||
|
||||
// 检查是否存在循环引用或其他问题
|
||||
console.log('\n=== 检查模型实例化 ===');
|
||||
try {
|
||||
console.log('尝试实例化HarmlessPlace:', new HarmlessPlace());
|
||||
} catch (error) {
|
||||
console.log('实例化HarmlessPlace错误:', error.message);
|
||||
}
|
||||
286
government-backend/controllers/approvalProcessController.js
Normal file
286
government-backend/controllers/approvalProcessController.js
Normal file
@@ -0,0 +1,286 @@
|
||||
const ApprovalProcess = require('../models/ApprovalProcess');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取审批流程列表
|
||||
const getApprovalProcesses = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 9,
|
||||
status,
|
||||
applicant,
|
||||
type
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const where = {};
|
||||
|
||||
// 构建查询条件
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
if (applicant) {
|
||||
where.applicant = {
|
||||
[Op.like]: `%${applicant}%`
|
||||
};
|
||||
}
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
|
||||
const { count, rows } = await ApprovalProcess.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取审批流程列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 根据ID获取审批流程详情
|
||||
const getApprovalProcessById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const approvalProcess = await ApprovalProcess.findByPk(id);
|
||||
|
||||
if (!approvalProcess) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '审批流程不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: approvalProcess
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取审批流程详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建审批流程
|
||||
const createApprovalProcess = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
type,
|
||||
applicant,
|
||||
phone,
|
||||
farmName,
|
||||
quantity,
|
||||
description,
|
||||
files,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const approvalProcess = await ApprovalProcess.create({
|
||||
title,
|
||||
type,
|
||||
applicant,
|
||||
phone,
|
||||
farmName,
|
||||
quantity,
|
||||
description,
|
||||
files,
|
||||
remarks,
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '创建成功',
|
||||
data: approvalProcess
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建审批流程失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新审批流程
|
||||
const updateApprovalProcess = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
title,
|
||||
type,
|
||||
applicant,
|
||||
phone,
|
||||
farmName,
|
||||
quantity,
|
||||
description,
|
||||
files,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const approvalProcess = await ApprovalProcess.findByPk(id);
|
||||
if (!approvalProcess) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '审批流程不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await approvalProcess.update({
|
||||
title,
|
||||
type,
|
||||
applicant,
|
||||
phone,
|
||||
farmName,
|
||||
quantity,
|
||||
description,
|
||||
files,
|
||||
remarks
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: approvalProcess
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新审批流程失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除审批流程
|
||||
const deleteApprovalProcess = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const approvalProcess = await ApprovalProcess.findByPk(id);
|
||||
|
||||
if (!approvalProcess) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '审批流程不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await approvalProcess.destroy();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除审批流程失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 审批操作(通过/拒绝)
|
||||
const processApproval = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { action, approvalComment, approver } = req.body;
|
||||
|
||||
const approvalProcess = await ApprovalProcess.findByPk(id);
|
||||
if (!approvalProcess) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '审批流程不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const status = action === 'approve' ? 'approved' : 'rejected';
|
||||
|
||||
await approvalProcess.update({
|
||||
status,
|
||||
approvalComment,
|
||||
approver,
|
||||
approvalTime: new Date()
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: action === 'approve' ? '审批通过' : '审批拒绝',
|
||||
data: approvalProcess
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('审批操作失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新审批状态
|
||||
const updateApprovalStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const approvalProcess = await ApprovalProcess.findByPk(id);
|
||||
if (!approvalProcess) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '审批流程不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await approvalProcess.update({ status });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '状态更新成功',
|
||||
data: approvalProcess
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新审批状态失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getApprovalProcesses,
|
||||
getApprovalProcessById,
|
||||
createApprovalProcess,
|
||||
updateApprovalProcess,
|
||||
deleteApprovalProcess,
|
||||
processApproval,
|
||||
updateApprovalStatus
|
||||
};
|
||||
298
government-backend/controllers/cattleAcademyController.js
Normal file
298
government-backend/controllers/cattleAcademyController.js
Normal file
@@ -0,0 +1,298 @@
|
||||
const CattleAcademy = require('../models/CattleAcademy');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取养牛学院资讯列表
|
||||
const getCattleAcademyList = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 10,
|
||||
title,
|
||||
category,
|
||||
status,
|
||||
author
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const where = {};
|
||||
|
||||
// 构建查询条件
|
||||
if (title) {
|
||||
where.title = {
|
||||
[Op.like]: `%${title}%`
|
||||
};
|
||||
}
|
||||
if (category) {
|
||||
where.category = category;
|
||||
}
|
||||
if (status !== undefined) {
|
||||
where.status = status === 'true' || status === true;
|
||||
}
|
||||
if (author) {
|
||||
where.author = {
|
||||
[Op.like]: `%${author}%`
|
||||
};
|
||||
}
|
||||
|
||||
const { count, rows } = await CattleAcademy.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
order: [['sort', 'DESC'], ['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取养牛学院资讯列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 根据ID获取养牛学院资讯详情
|
||||
const getCattleAcademyById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const cattleAcademy = await CattleAcademy.findByPk(id);
|
||||
|
||||
if (!cattleAcademy) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '资讯不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 增加浏览次数
|
||||
await cattleAcademy.increment('viewCount');
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: cattleAcademy
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取养牛学院资讯详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建养牛学院资讯
|
||||
const createCattleAcademy = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
coverImage,
|
||||
content,
|
||||
summary,
|
||||
category,
|
||||
tags,
|
||||
sort = 0,
|
||||
status = true,
|
||||
author,
|
||||
publishTime,
|
||||
isTop = false,
|
||||
isRecommend = false,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const cattleAcademy = await CattleAcademy.create({
|
||||
title,
|
||||
coverImage,
|
||||
content,
|
||||
summary,
|
||||
category,
|
||||
tags,
|
||||
sort,
|
||||
status,
|
||||
author,
|
||||
publishTime: publishTime || new Date(),
|
||||
isTop,
|
||||
isRecommend,
|
||||
remarks
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '创建成功',
|
||||
data: cattleAcademy
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建养牛学院资讯失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新养牛学院资讯
|
||||
const updateCattleAcademy = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
title,
|
||||
coverImage,
|
||||
content,
|
||||
summary,
|
||||
category,
|
||||
tags,
|
||||
sort,
|
||||
status,
|
||||
author,
|
||||
publishTime,
|
||||
isTop,
|
||||
isRecommend,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const cattleAcademy = await CattleAcademy.findByPk(id);
|
||||
if (!cattleAcademy) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '资讯不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await cattleAcademy.update({
|
||||
title,
|
||||
coverImage,
|
||||
content,
|
||||
summary,
|
||||
category,
|
||||
tags,
|
||||
sort,
|
||||
status,
|
||||
author,
|
||||
publishTime,
|
||||
isTop,
|
||||
isRecommend,
|
||||
remarks
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: cattleAcademy
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新养牛学院资讯失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除养牛学院资讯
|
||||
const deleteCattleAcademy = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const cattleAcademy = await CattleAcademy.findByPk(id);
|
||||
|
||||
if (!cattleAcademy) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '资讯不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await cattleAcademy.destroy();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除养牛学院资讯失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 切换资讯状态
|
||||
const toggleCattleAcademyStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const cattleAcademy = await CattleAcademy.findByPk(id);
|
||||
if (!cattleAcademy) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '资讯不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await cattleAcademy.update({ status });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '状态更新成功',
|
||||
data: cattleAcademy
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('切换资讯状态失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量更新排序
|
||||
const updateSort = async (req, res) => {
|
||||
try {
|
||||
const { items } = req.body; // [{id, sort}, ...]
|
||||
|
||||
for (const item of items) {
|
||||
await CattleAcademy.update(
|
||||
{ sort: item.sort },
|
||||
{ where: { id: item.id } }
|
||||
);
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '排序更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新排序失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getCattleAcademyList,
|
||||
getCattleAcademyById,
|
||||
createCattleAcademy,
|
||||
updateCattleAcademy,
|
||||
deleteCattleAcademy,
|
||||
toggleCattleAcademyStatus,
|
||||
updateSort
|
||||
};
|
||||
304
government-backend/controllers/deviceWarningController.js
Normal file
304
government-backend/controllers/deviceWarningController.js
Normal file
@@ -0,0 +1,304 @@
|
||||
const DeviceWarning = require('../models/DeviceWarning');
|
||||
const { Op, fn, col } = require('sequelize');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
// 获取设备预警列表
|
||||
exports.getDeviceWarnings = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 20, deviceType, alertType, status, farmerName } = req.query;
|
||||
const limit = parseInt(pageSize);
|
||||
const offset = (parseInt(page) - 1) * limit;
|
||||
|
||||
// 构建WHERE条件
|
||||
let whereConditions = [];
|
||||
let whereParams = {};
|
||||
|
||||
if (deviceType) {
|
||||
whereConditions.push('deviceType = :deviceType');
|
||||
whereParams.deviceType = deviceType;
|
||||
}
|
||||
if (alertType) {
|
||||
whereConditions.push('alertType = :alertType');
|
||||
whereParams.alertType = alertType;
|
||||
}
|
||||
if (status) {
|
||||
whereConditions.push('status = :status');
|
||||
whereParams.status = status;
|
||||
}
|
||||
if (farmerName) {
|
||||
whereConditions.push('farmerName LIKE :farmerName');
|
||||
whereParams.farmerName = `%${farmerName}%`;
|
||||
}
|
||||
|
||||
const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
|
||||
|
||||
// 查询总数
|
||||
const countQuery = `SELECT COUNT(*) as count FROM device_warnings ${whereClause}`;
|
||||
const countResult = await DeviceWarning.sequelize.query(countQuery, {
|
||||
replacements: whereParams,
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
const total = countResult[0].count;
|
||||
|
||||
// 查询数据
|
||||
const dataQuery = `
|
||||
SELECT id, farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel, alertTime, status, description,
|
||||
location, batteryLevel, signalStrength, temperature,
|
||||
resolvedBy, resolvedAt, remarks, createdAt, updatedAt
|
||||
FROM device_warnings
|
||||
${whereClause}
|
||||
ORDER BY alertTime DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
`;
|
||||
|
||||
const rows = await DeviceWarning.sequelize.query(dataQuery, {
|
||||
replacements: { ...whereParams, limit, offset },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: total,
|
||||
page: parseInt(page),
|
||||
pageSize: limit,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取设备预警列表失败:', error);
|
||||
res.status(500).json({ code: 500, message: '获取设备预警列表失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 根据ID获取单个设备预警详情
|
||||
exports.getDeviceWarningById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const query = `
|
||||
SELECT id, farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel, alertTime, status, description,
|
||||
location, batteryLevel, signalStrength, temperature,
|
||||
resolvedBy, resolvedAt, remarks, createdAt, updatedAt
|
||||
FROM device_warnings
|
||||
WHERE id = :id
|
||||
`;
|
||||
|
||||
const results = await DeviceWarning.sequelize.query(query, {
|
||||
replacements: { id },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
if (results.length === 0) {
|
||||
return res.status(404).json({ code: 404, message: '设备预警未找到' });
|
||||
}
|
||||
|
||||
res.status(200).json({ code: 200, message: '获取成功', data: results[0] });
|
||||
} catch (error) {
|
||||
console.error('获取设备预警详情失败:', error);
|
||||
res.status(500).json({ code: 500, message: '获取设备预警详情失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 创建新的设备预警
|
||||
exports.createDeviceWarning = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel = 'medium', description, location, batteryLevel,
|
||||
signalStrength, temperature, remarks
|
||||
} = req.body;
|
||||
|
||||
const insertQuery = `
|
||||
INSERT INTO device_warnings
|
||||
(farmName, farmerName, phone, deviceType, deviceNumber, alertType, alertLevel,
|
||||
description, location, batteryLevel, signalStrength, temperature, remarks,
|
||||
alertTime, status, createdAt, updatedAt)
|
||||
VALUES
|
||||
(:farmName, :farmerName, :phone, :deviceType, :deviceNumber, :alertType, :alertLevel,
|
||||
:description, :location, :batteryLevel, :signalStrength, :temperature, :remarks,
|
||||
NOW(), 'active', NOW(), NOW())
|
||||
`;
|
||||
|
||||
await DeviceWarning.sequelize.query(insertQuery, {
|
||||
replacements: {
|
||||
farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel,
|
||||
description: description || null,
|
||||
location: location || null,
|
||||
batteryLevel: batteryLevel || null,
|
||||
signalStrength: signalStrength || null,
|
||||
temperature: temperature || null,
|
||||
remarks: remarks || null
|
||||
},
|
||||
type: QueryTypes.INSERT
|
||||
});
|
||||
|
||||
// 获取刚插入的记录
|
||||
const selectQuery = `
|
||||
SELECT id, farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel, alertTime, status, description,
|
||||
location, batteryLevel, signalStrength, temperature,
|
||||
resolvedBy, resolvedAt, remarks, createdAt, updatedAt
|
||||
FROM device_warnings
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const results = await DeviceWarning.sequelize.query(selectQuery, {
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
res.status(201).json({ code: 201, message: '创建成功', data: results[0] });
|
||||
} catch (error) {
|
||||
console.error('创建设备预警失败:', error);
|
||||
res.status(500).json({ code: 500, message: '创建设备预警失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新设备预警
|
||||
exports.updateDeviceWarning = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateFields = [];
|
||||
const replacements = { id };
|
||||
|
||||
// 动态构建更新字段
|
||||
const allowedFields = [
|
||||
'farmName', 'farmerName', 'phone', 'deviceType', 'deviceNumber',
|
||||
'alertType', 'alertLevel', 'description', 'location', 'batteryLevel',
|
||||
'signalStrength', 'temperature', 'remarks', 'status', 'resolvedBy', 'resolvedAt'
|
||||
];
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (req.body[field] !== undefined) {
|
||||
updateFields.push(`${field} = :${field}`);
|
||||
replacements[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
return res.status(400).json({ code: 400, message: '没有需要更新的字段' });
|
||||
}
|
||||
|
||||
updateFields.push('updatedAt = NOW()');
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE device_warnings
|
||||
SET ${updateFields.join(', ')}
|
||||
WHERE id = :id
|
||||
`;
|
||||
|
||||
const [result] = await DeviceWarning.sequelize.query(updateQuery, {
|
||||
replacements,
|
||||
type: QueryTypes.UPDATE
|
||||
});
|
||||
|
||||
if (result === 0) {
|
||||
return res.status(404).json({ code: 404, message: '设备预警未找到或无更新' });
|
||||
}
|
||||
|
||||
// 获取更新后的记录
|
||||
const selectQuery = `
|
||||
SELECT id, farmName, farmerName, phone, deviceType, deviceNumber,
|
||||
alertType, alertLevel, alertTime, status, description,
|
||||
location, batteryLevel, signalStrength, temperature,
|
||||
resolvedBy, resolvedAt, remarks, createdAt, updatedAt
|
||||
FROM device_warnings
|
||||
WHERE id = :id
|
||||
`;
|
||||
|
||||
const results = await DeviceWarning.sequelize.query(selectQuery, {
|
||||
replacements: { id },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
res.status(200).json({ code: 200, message: '更新成功', data: results[0] });
|
||||
} catch (error) {
|
||||
console.error('更新设备预警失败:', error);
|
||||
res.status(500).json({ code: 500, message: '更新设备预警失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 删除设备预警
|
||||
exports.deleteDeviceWarning = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const deleteQuery = 'DELETE FROM device_warnings WHERE id = :id';
|
||||
const [result] = await DeviceWarning.sequelize.query(deleteQuery, {
|
||||
replacements: { id },
|
||||
type: QueryTypes.DELETE
|
||||
});
|
||||
|
||||
if (result === 0) {
|
||||
return res.status(404).json({ code: 404, message: '设备预警未找到' });
|
||||
}
|
||||
|
||||
res.status(200).json({ code: 200, message: '删除成功' });
|
||||
} catch (error) {
|
||||
console.error('删除设备预警失败:', error);
|
||||
res.status(500).json({ code: 500, message: '删除设备预警失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新预警状态
|
||||
exports.updateWarningStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status, resolvedBy } = req.body;
|
||||
|
||||
const warning = await DeviceWarning.findByPk(id);
|
||||
if (!warning) {
|
||||
return res.status(404).json({ code: 404, message: '设备预警未找到' });
|
||||
}
|
||||
|
||||
warning.status = status;
|
||||
if (status === 'resolved') {
|
||||
warning.resolvedBy = resolvedBy;
|
||||
warning.resolvedAt = new Date();
|
||||
}
|
||||
await warning.save();
|
||||
|
||||
res.status(200).json({ code: 200, message: '状态更新成功', data: warning });
|
||||
} catch (error) {
|
||||
console.error('更新预警状态失败:', error);
|
||||
res.status(500).json({ code: 500, message: '更新预警状态失败', error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取预警统计
|
||||
exports.getWarningStats = async (req, res) => {
|
||||
try {
|
||||
// 使用原始SQL查询每种设备类型的活跃预警数量
|
||||
const earTagQuery = 'SELECT COUNT(*) as count FROM device_warnings WHERE deviceType = :deviceType AND status = :status';
|
||||
const earTagResult = await DeviceWarning.sequelize.query(earTagQuery, {
|
||||
replacements: { deviceType: '智能耳标', status: 'active' },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
const neckbandResult = await DeviceWarning.sequelize.query(earTagQuery, {
|
||||
replacements: { deviceType: '智能项圈', status: 'active' },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
const hostResult = await DeviceWarning.sequelize.query(earTagQuery, {
|
||||
replacements: { deviceType: '智能主机', status: 'active' },
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
const result = {
|
||||
earTag: earTagResult[0].count,
|
||||
neckband: neckbandResult[0].count,
|
||||
host: hostResult[0].count
|
||||
};
|
||||
|
||||
res.status(200).json({ code: 200, message: '获取成功', data: result });
|
||||
} catch (error) {
|
||||
console.error('获取预警统计失败:', error);
|
||||
res.status(500).json({ code: 500, message: '获取预警统计失败', error: error.message });
|
||||
}
|
||||
};
|
||||
242
government-backend/controllers/epidemicActivityController.js
Normal file
242
government-backend/controllers/epidemicActivityController.js
Normal file
@@ -0,0 +1,242 @@
|
||||
const EpidemicActivity = require('../models/EpidemicActivity');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取防疫活动列表
|
||||
const getEpidemicActivities = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
activityName,
|
||||
livestockCategory,
|
||||
diseaseCategory,
|
||||
activityStatus
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const where = {};
|
||||
|
||||
// 构建查询条件
|
||||
if (activityName) {
|
||||
where.activityName = {
|
||||
[Op.like]: `%${activityName}%`
|
||||
};
|
||||
}
|
||||
if (livestockCategory) {
|
||||
where.livestockCategory = livestockCategory;
|
||||
}
|
||||
if (diseaseCategory) {
|
||||
where.diseaseCategory = diseaseCategory;
|
||||
}
|
||||
if (activityStatus) {
|
||||
where.activityStatus = activityStatus;
|
||||
}
|
||||
|
||||
const { count, rows } = await EpidemicActivity.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
order: [['updatedAt', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取防疫活动列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 根据ID获取防疫活动
|
||||
const getEpidemicActivityById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const activity = await EpidemicActivity.findByPk(id);
|
||||
|
||||
if (!activity) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '防疫活动不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: activity
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取防疫活动详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建防疫活动
|
||||
const createEpidemicActivity = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
activityName,
|
||||
livestockCategory,
|
||||
diseaseCategory,
|
||||
vaccineUsed,
|
||||
vaccineBatch,
|
||||
preventionDate,
|
||||
activityStatus = 'active'
|
||||
} = req.body;
|
||||
|
||||
const activity = await EpidemicActivity.create({
|
||||
activityName,
|
||||
livestockCategory,
|
||||
diseaseCategory,
|
||||
vaccineUsed,
|
||||
vaccineBatch,
|
||||
preventionDate,
|
||||
activityStatus
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '创建成功',
|
||||
data: activity
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建防疫活动失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新防疫活动
|
||||
const updateEpidemicActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
activityName,
|
||||
livestockCategory,
|
||||
diseaseCategory,
|
||||
vaccineUsed,
|
||||
vaccineBatch,
|
||||
preventionDate,
|
||||
activityStatus
|
||||
} = req.body;
|
||||
|
||||
const activity = await EpidemicActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '防疫活动不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await activity.update({
|
||||
activityName,
|
||||
livestockCategory,
|
||||
diseaseCategory,
|
||||
vaccineUsed,
|
||||
vaccineBatch,
|
||||
preventionDate,
|
||||
activityStatus
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: activity
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新防疫活动失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除防疫活动
|
||||
const deleteEpidemicActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const activity = await EpidemicActivity.findByPk(id);
|
||||
|
||||
if (!activity) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '防疫活动不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await activity.destroy();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除防疫活动失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 切换活动状态
|
||||
const toggleActivityStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const activity = await EpidemicActivity.findByPk(id);
|
||||
|
||||
if (!activity) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '防疫活动不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = activity.activityStatus === 'active' ? 'inactive' : 'active';
|
||||
await activity.update({ activityStatus: newStatus });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '状态更新成功',
|
||||
data: activity
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('切换活动状态失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getEpidemicActivities,
|
||||
getEpidemicActivityById,
|
||||
createEpidemicActivity,
|
||||
updateEpidemicActivity,
|
||||
deleteEpidemicActivity,
|
||||
toggleActivityStatus
|
||||
};
|
||||
@@ -0,0 +1,280 @@
|
||||
const ProductionMaterialCertification = require('../models/ProductionMaterialCertification');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 获取生资认证列表
|
||||
const getProductionMaterialCertifications = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
materialName,
|
||||
materialType,
|
||||
manufacturer,
|
||||
certificationType,
|
||||
certificationStatus
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const where = {};
|
||||
|
||||
// 构建查询条件
|
||||
if (materialName) {
|
||||
where.materialName = {
|
||||
[Op.like]: `%${materialName}%`
|
||||
};
|
||||
}
|
||||
if (materialType) {
|
||||
where.materialType = materialType;
|
||||
}
|
||||
if (manufacturer) {
|
||||
where.manufacturer = {
|
||||
[Op.like]: `%${manufacturer}%`
|
||||
};
|
||||
}
|
||||
if (certificationType) {
|
||||
where.certificationType = certificationType;
|
||||
}
|
||||
if (certificationStatus) {
|
||||
where.certificationStatus = certificationStatus;
|
||||
}
|
||||
|
||||
const { count, rows } = await ProductionMaterialCertification.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
order: [['updatedAt', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取生资认证列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 根据ID获取生资认证详情
|
||||
const getProductionMaterialCertificationById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const certification = await ProductionMaterialCertification.findByPk(id);
|
||||
|
||||
if (!certification) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '生资认证不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: certification
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取生资认证详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建生资认证
|
||||
const createProductionMaterialCertification = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
materialName,
|
||||
materialType,
|
||||
manufacturer,
|
||||
certificationNumber,
|
||||
certificationType,
|
||||
certificationAuthority,
|
||||
issueDate,
|
||||
expiryDate,
|
||||
certificationStatus = 'valid',
|
||||
description,
|
||||
specifications,
|
||||
applicableScope,
|
||||
contactPerson,
|
||||
contactPhone,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const certification = await ProductionMaterialCertification.create({
|
||||
materialName,
|
||||
materialType,
|
||||
manufacturer,
|
||||
certificationNumber,
|
||||
certificationType,
|
||||
certificationAuthority,
|
||||
issueDate,
|
||||
expiryDate,
|
||||
certificationStatus,
|
||||
description,
|
||||
specifications,
|
||||
applicableScope,
|
||||
contactPerson,
|
||||
contactPhone,
|
||||
remarks
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '创建成功',
|
||||
data: certification
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建生资认证失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新生资认证
|
||||
const updateProductionMaterialCertification = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
materialName,
|
||||
materialType,
|
||||
manufacturer,
|
||||
certificationNumber,
|
||||
certificationType,
|
||||
certificationAuthority,
|
||||
issueDate,
|
||||
expiryDate,
|
||||
certificationStatus,
|
||||
description,
|
||||
specifications,
|
||||
applicableScope,
|
||||
contactPerson,
|
||||
contactPhone,
|
||||
remarks
|
||||
} = req.body;
|
||||
|
||||
const certification = await ProductionMaterialCertification.findByPk(id);
|
||||
if (!certification) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '生资认证不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await certification.update({
|
||||
materialName,
|
||||
materialType,
|
||||
manufacturer,
|
||||
certificationNumber,
|
||||
certificationType,
|
||||
certificationAuthority,
|
||||
issueDate,
|
||||
expiryDate,
|
||||
certificationStatus,
|
||||
description,
|
||||
specifications,
|
||||
applicableScope,
|
||||
contactPerson,
|
||||
contactPhone,
|
||||
remarks
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: certification
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新生资认证失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除生资认证
|
||||
const deleteProductionMaterialCertification = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const certification = await ProductionMaterialCertification.findByPk(id);
|
||||
|
||||
if (!certification) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '生资认证不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await certification.destroy();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除生资认证失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 切换认证状态
|
||||
const toggleCertificationStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const certification = await ProductionMaterialCertification.findByPk(id);
|
||||
if (!certification) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '生资认证不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await certification.update({ certificationStatus: status });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '状态更新成功',
|
||||
data: certification
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('切换认证状态失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getProductionMaterialCertifications,
|
||||
getProductionMaterialCertificationById,
|
||||
createProductionMaterialCertification,
|
||||
updateProductionMaterialCertification,
|
||||
deleteProductionMaterialCertification,
|
||||
toggleCertificationStatus
|
||||
};
|
||||
@@ -1,96 +0,0 @@
|
||||
// 完整模型测试脚本
|
||||
const sequelize = require('./config/database');
|
||||
|
||||
// 确保先导入所有依赖模型
|
||||
const Department = require('./models/Department');
|
||||
const Position = require('./models/Position');
|
||||
const AdminStaff = require('./models/AdminStaff');
|
||||
|
||||
async function fullModelTest() {
|
||||
try {
|
||||
console.log('开始完整模型测试...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 检查模型是否被正确导入
|
||||
console.log('\n检查模型导入状态:');
|
||||
console.log('Department 模型:', Department ? '✅ 已导入' : '❌ 未导入');
|
||||
console.log('Position 模型:', Position ? '✅ 已导入' : '❌ 未导入');
|
||||
console.log('AdminStaff 模型:', AdminStaff ? '✅ 已导入' : '❌ 未导入');
|
||||
|
||||
// 测试基本的查询功能
|
||||
console.log('\n测试基本查询功能:');
|
||||
|
||||
// 查询部门表
|
||||
console.log('\n查询部门表:');
|
||||
try {
|
||||
const departments = await Department.findAll({ limit: 5 });
|
||||
console.log(`找到 ${departments.length} 个部门`);
|
||||
if (departments.length > 0) {
|
||||
console.log('示例部门:', departments[0].dataValues);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('部门查询失败:', error.message);
|
||||
}
|
||||
|
||||
// 查询岗位表
|
||||
console.log('\n查询岗位表:');
|
||||
try {
|
||||
const positions = await Position.findAll({ limit: 5 });
|
||||
console.log(`找到 ${positions.length} 个岗位`);
|
||||
if (positions.length > 0) {
|
||||
console.log('示例岗位:', positions[0].dataValues);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('岗位查询失败:', error.message);
|
||||
}
|
||||
|
||||
// 查询行政人员表
|
||||
console.log('\n查询行政人员表:');
|
||||
try {
|
||||
const adminStaffs = await AdminStaff.findAll({ limit: 5 });
|
||||
console.log(`找到 ${adminStaffs.length} 个行政人员`);
|
||||
if (adminStaffs.length > 0) {
|
||||
console.log('示例行政人员:', adminStaffs[0].dataValues);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('行政人员查询失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
}
|
||||
|
||||
// 测试关联查询
|
||||
console.log('\n测试关联查询:');
|
||||
try {
|
||||
const adminStaffWithRelations = await AdminStaff.findAll({
|
||||
include: [
|
||||
{ model: Department, as: 'department' },
|
||||
{ model: Position, as: 'position' }
|
||||
],
|
||||
limit: 1
|
||||
});
|
||||
console.log('关联查询结果数量:', adminStaffWithRelations.length);
|
||||
if (adminStaffWithRelations.length > 0) {
|
||||
console.log('关联查询成功,已获取到关联数据');
|
||||
// 只打印部分数据以避免输出过多
|
||||
const staff = adminStaffWithRelations[0];
|
||||
console.log('姓名:', staff.name);
|
||||
console.log('部门:', staff.department?.name || '未知');
|
||||
console.log('岗位:', staff.position?.name || '未知');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('关联查询失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
}
|
||||
|
||||
console.log('\n✅ 完整模型测试完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 完整模型测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fullModelTest();
|
||||
@@ -1,99 +0,0 @@
|
||||
// 模型导入测试脚本
|
||||
const sequelize = require('./config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
async function modelImportTest() {
|
||||
try {
|
||||
console.log('开始模型导入测试...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 测试直接导入模型
|
||||
console.log('\n测试直接导入模型:');
|
||||
|
||||
// 先测试 Department 模型
|
||||
console.log('\n测试 Department 模型导入:');
|
||||
try {
|
||||
const Department = require('./models/Department');
|
||||
console.log('Department 模型导入:', Department ? '✅ 成功' : '❌ 失败');
|
||||
|
||||
// 测试 Department 模型的结构
|
||||
if (Department) {
|
||||
console.log('Department 模型是否有 define 方法:', typeof Department.define === 'function' ? '✅ 是' : '❌ 否');
|
||||
console.log('Department 模型的 tableName:', Department.tableName || Department.options?.tableName || '未定义');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Department 模型导入失败:', error.message);
|
||||
}
|
||||
|
||||
// 测试 Position 模型
|
||||
console.log('\n测试 Position 模型导入:');
|
||||
try {
|
||||
const Position = require('./models/Position');
|
||||
console.log('Position 模型导入:', Position ? '✅ 成功' : '❌ 失败');
|
||||
|
||||
// 测试 Position 模型的结构
|
||||
if (Position) {
|
||||
console.log('Position 模型是否有 define 方法:', typeof Position.define === 'function' ? '✅ 是' : '❌ 否');
|
||||
console.log('Position 模型的 tableName:', Position.tableName || Position.options?.tableName || '未定义');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Position 模型导入失败:', error.message);
|
||||
}
|
||||
|
||||
// 测试 AdminStaff 模型
|
||||
console.log('\n测试 AdminStaff 模型导入:');
|
||||
try {
|
||||
// 先导入依赖模型
|
||||
require('./models/Department');
|
||||
require('./models/Position');
|
||||
|
||||
const AdminStaff = require('./models/AdminStaff');
|
||||
console.log('AdminStaff 模型导入:', AdminStaff ? '✅ 成功' : '❌ 失败');
|
||||
|
||||
// 测试 AdminStaff 模型的结构
|
||||
if (AdminStaff) {
|
||||
console.log('AdminStaff 模型是否有 define 方法:', typeof AdminStaff.define === 'function' ? '✅ 是' : '❌ 否');
|
||||
console.log('AdminStaff 模型的 tableName:', AdminStaff.tableName || AdminStaff.options?.tableName || '未定义');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('AdminStaff 模型导入失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
}
|
||||
|
||||
// 测试创建一个简单的新模型
|
||||
console.log('\n测试创建新模型:');
|
||||
try {
|
||||
const TestModel = sequelize.define('TestModel', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'test_models',
|
||||
timestamps: false,
|
||||
paranoid: false
|
||||
});
|
||||
|
||||
console.log('新模型创建:', TestModel ? '✅ 成功' : '❌ 失败');
|
||||
} catch (error) {
|
||||
console.error('新模型创建失败:', error.message);
|
||||
}
|
||||
|
||||
console.log('\n✅ 模型导入测试完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 模型导入测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
modelImportTest();
|
||||
93
government-backend/models/ApprovalProcess.js
Normal file
93
government-backend/models/ApprovalProcess.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
|
||||
const ApprovalProcess = sequelize.define('ApprovalProcess', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '审批标题'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('enterprise', 'license', 'project', 'other'),
|
||||
allowNull: false,
|
||||
comment: '审批类型'
|
||||
},
|
||||
applicant: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请人'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '联系电话'
|
||||
},
|
||||
farmName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: '养殖场名称'
|
||||
},
|
||||
quantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '认证数量'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审批说明'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'processing'),
|
||||
defaultValue: 'pending',
|
||||
comment: '审批状态'
|
||||
},
|
||||
approvalComment: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审批意见'
|
||||
},
|
||||
approver: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '审批人'
|
||||
},
|
||||
approvalTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '审批时间'
|
||||
},
|
||||
files: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '附件文件列表'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'approval_processes',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
paranoid: false
|
||||
});
|
||||
|
||||
module.exports = ApprovalProcess;
|
||||
98
government-backend/models/CattleAcademy.js
Normal file
98
government-backend/models/CattleAcademy.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
|
||||
const CattleAcademy = sequelize.define('CattleAcademy', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '标题'
|
||||
},
|
||||
coverImage: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '封面图URL'
|
||||
},
|
||||
content: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '内容'
|
||||
},
|
||||
summary: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '摘要'
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '分类'
|
||||
},
|
||||
tags: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '标签'
|
||||
},
|
||||
sort: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '排序'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true,
|
||||
comment: '状态'
|
||||
},
|
||||
viewCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '浏览次数'
|
||||
},
|
||||
author: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '作者'
|
||||
},
|
||||
publishTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '发布时间'
|
||||
},
|
||||
isTop: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: '是否置顶'
|
||||
},
|
||||
isRecommend: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: '是否推荐'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'cattle_academy',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
paranoid: false
|
||||
});
|
||||
|
||||
module.exports = CattleAcademy;
|
||||
136
government-backend/models/DeviceWarning.js
Normal file
136
government-backend/models/DeviceWarning.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const { DataTypes, Sequelize } = require('sequelize');
|
||||
const config = require('../config/index.js');
|
||||
|
||||
// 创建专门的Sequelize实例,不使用underscored
|
||||
const sequelize = new Sequelize(
|
||||
config.DB_CONFIG.database,
|
||||
config.DB_CONFIG.user,
|
||||
config.DB_CONFIG.password,
|
||||
{
|
||||
host: config.DB_CONFIG.host,
|
||||
port: config.DB_CONFIG.port,
|
||||
dialect: config.DB_CONFIG.dialect,
|
||||
logging: console.log,
|
||||
define: {
|
||||
timestamps: true,
|
||||
paranoid: false,
|
||||
underscored: false, // 明确设置为false
|
||||
freezeTableName: true
|
||||
},
|
||||
timezone: '+08:00'
|
||||
}
|
||||
);
|
||||
|
||||
const DeviceWarning = sequelize.define('DeviceWarning', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
farmName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: '养殖场名称',
|
||||
field: 'farmName'
|
||||
},
|
||||
farmerName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: '养殖户名称',
|
||||
field: 'farmerName'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: '联系电话',
|
||||
field: 'phone'
|
||||
},
|
||||
deviceType: {
|
||||
type: DataTypes.ENUM('智能耳标', '智能项圈', '智能主机'),
|
||||
allowNull: false,
|
||||
comment: '设备类型',
|
||||
field: 'deviceType'
|
||||
},
|
||||
deviceNumber: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: '设备编号',
|
||||
field: 'deviceNumber'
|
||||
},
|
||||
alertType: {
|
||||
type: DataTypes.ENUM('设备离线', '电量不足', '信号异常', '温度异常', '其他'),
|
||||
allowNull: false,
|
||||
comment: '预警类型',
|
||||
field: 'alertType'
|
||||
},
|
||||
alertLevel: {
|
||||
type: DataTypes.ENUM('high', 'medium', 'low'),
|
||||
allowNull: false,
|
||||
defaultValue: 'medium',
|
||||
comment: '预警级别 (高, 中, 低)',
|
||||
field: 'alertLevel'
|
||||
},
|
||||
alertTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '预警时间',
|
||||
field: 'alertTime'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'resolved', 'ignored'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '预警状态 (活跃, 已解决, 已忽略)',
|
||||
field: 'status'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
comment: '预警描述',
|
||||
field: 'description'
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.STRING,
|
||||
comment: '设备位置',
|
||||
field: 'location'
|
||||
},
|
||||
batteryLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '电池电量百分比',
|
||||
field: 'batteryLevel'
|
||||
},
|
||||
signalStrength: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: '信号强度',
|
||||
field: 'signalStrength'
|
||||
},
|
||||
temperature: {
|
||||
type: DataTypes.FLOAT,
|
||||
comment: '温度值',
|
||||
field: 'temperature'
|
||||
},
|
||||
resolvedBy: {
|
||||
type: DataTypes.STRING,
|
||||
comment: '解决人',
|
||||
field: 'resolvedBy'
|
||||
},
|
||||
resolvedAt: {
|
||||
type: DataTypes.DATE,
|
||||
comment: '解决时间',
|
||||
field: 'resolvedAt'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
comment: '备注',
|
||||
field: 'remarks'
|
||||
},
|
||||
}, {
|
||||
tableName: 'device_warnings',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
paranoid: false,
|
||||
underscored: false, // 覆盖全局配置,不使用下划线命名
|
||||
});
|
||||
|
||||
module.exports = DeviceWarning;
|
||||
63
government-backend/models/EpidemicActivity.js
Normal file
63
government-backend/models/EpidemicActivity.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
|
||||
const EpidemicActivity = sequelize.define('EpidemicActivity', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
activityName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '活动名称'
|
||||
},
|
||||
livestockCategory: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '防疫畜别'
|
||||
},
|
||||
diseaseCategory: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '疫病类别'
|
||||
},
|
||||
vaccineUsed: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: '使用疫苗'
|
||||
},
|
||||
vaccineBatch: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '疫苗批次'
|
||||
},
|
||||
preventionDate: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '防疫日期'
|
||||
},
|
||||
activityStatus: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
defaultValue: 'active',
|
||||
comment: '活动状态'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'epidemic_activities',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
paranoid: false
|
||||
});
|
||||
|
||||
module.exports = EpidemicActivity;
|
||||
103
government-backend/models/ProductionMaterialCertification.js
Normal file
103
government-backend/models/ProductionMaterialCertification.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
|
||||
const ProductionMaterialCertification = sequelize.define('ProductionMaterialCertification', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
materialName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '生产资料名称'
|
||||
},
|
||||
materialType: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '生产资料类型'
|
||||
},
|
||||
manufacturer: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '生产厂商'
|
||||
},
|
||||
certificationNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '认证编号'
|
||||
},
|
||||
certificationType: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '认证类型'
|
||||
},
|
||||
certificationAuthority: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '认证机构'
|
||||
},
|
||||
issueDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '发证日期'
|
||||
},
|
||||
expiryDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '有效期至'
|
||||
},
|
||||
certificationStatus: {
|
||||
type: DataTypes.ENUM('valid', 'expired', 'suspended', 'revoked'),
|
||||
defaultValue: 'valid',
|
||||
comment: '认证状态'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '产品描述'
|
||||
},
|
||||
specifications: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '技术规格'
|
||||
},
|
||||
applicableScope: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '适用范围'
|
||||
},
|
||||
contactPerson: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '联系人'
|
||||
},
|
||||
contactPhone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '联系电话'
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'production_material_certifications',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
paranoid: false
|
||||
});
|
||||
|
||||
module.exports = ProductionMaterialCertification;
|
||||
119
government-backend/nginx-ad.ningmuyun.com.conf
Normal file
119
government-backend/nginx-ad.ningmuyun.com.conf
Normal file
@@ -0,0 +1,119 @@
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ad.ningmuyun.com;
|
||||
|
||||
# SSL证书配置(需要替换为实际的证书路径)
|
||||
ssl_certificate /etc/ssl/certs/ad.ningmuyun.com.crt;
|
||||
ssl_certificate_key /etc/ssl/private/ad.ningmuyun.com.key;
|
||||
|
||||
# SSL配置
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# 政府端前端静态文件
|
||||
location /government/ {
|
||||
alias /var/www/government-admin-system/dist/;
|
||||
try_files $uri $uri/ /government/index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 政府端后端API代理
|
||||
location /api/government/ {
|
||||
proxy_pass http://127.0.0.1:5352/api/government/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# 处理预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Content-Length 0;
|
||||
add_header Content-Type text/plain;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# 保险端前端静态文件
|
||||
location /insurance/ {
|
||||
alias /var/www/insurance-admin-system/dist/;
|
||||
try_files $uri $uri/ /insurance/index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 保险端后端API代理
|
||||
location /insurance/api/ {
|
||||
proxy_pass http://127.0.0.1:3000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# 处理预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Content-Length 0;
|
||||
add_header Content-Type text/plain;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
proxy_pass http://127.0.0.1:5352/health;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# 根路径重定向到政府端
|
||||
location = / {
|
||||
return 301 /government/;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP重定向到HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name ad.ningmuyun.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
18
government-backend/routes/approvalProcess.js
Normal file
18
government-backend/routes/approvalProcess.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const approvalProcessController = require('../controllers/approvalProcessController');
|
||||
const authMiddleware = require('../middleware/auth');
|
||||
|
||||
// 暂时注释掉认证中间件用于测试
|
||||
// router.use(authMiddleware);
|
||||
|
||||
// 审批流程管理路由
|
||||
router.get('/', approvalProcessController.getApprovalProcesses);
|
||||
router.get('/:id', approvalProcessController.getApprovalProcessById);
|
||||
router.post('/', approvalProcessController.createApprovalProcess);
|
||||
router.put('/:id', approvalProcessController.updateApprovalProcess);
|
||||
router.delete('/:id', approvalProcessController.deleteApprovalProcess);
|
||||
router.post('/:id/process', approvalProcessController.processApproval);
|
||||
router.patch('/:id/status', approvalProcessController.updateApprovalStatus);
|
||||
|
||||
module.exports = router;
|
||||
18
government-backend/routes/cattleAcademy.js
Normal file
18
government-backend/routes/cattleAcademy.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cattleAcademyController = require('../controllers/cattleAcademyController');
|
||||
const authMiddleware = require('../middleware/auth');
|
||||
|
||||
// 暂时注释掉认证中间件用于测试
|
||||
// router.use(authMiddleware);
|
||||
|
||||
// 养牛学院资讯管理路由
|
||||
router.get('/', cattleAcademyController.getCattleAcademyList);
|
||||
router.get('/:id', cattleAcademyController.getCattleAcademyById);
|
||||
router.post('/', cattleAcademyController.createCattleAcademy);
|
||||
router.put('/:id', cattleAcademyController.updateCattleAcademy);
|
||||
router.delete('/:id', cattleAcademyController.deleteCattleAcademy);
|
||||
router.patch('/:id/status', cattleAcademyController.toggleCattleAcademyStatus);
|
||||
router.patch('/sort', cattleAcademyController.updateSort);
|
||||
|
||||
module.exports = router;
|
||||
17
government-backend/routes/deviceWarning.js
Normal file
17
government-backend/routes/deviceWarning.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const deviceWarningController = require('../controllers/deviceWarningController');
|
||||
// const authMiddleware = require('../middleware/auth'); // 假设有认证中间件
|
||||
|
||||
// router.use(authMiddleware); // 暂时注释掉认证中间件用于测试
|
||||
|
||||
// 设备预警管理路由
|
||||
router.get('/', deviceWarningController.getDeviceWarnings);
|
||||
router.get('/stats', deviceWarningController.getWarningStats);
|
||||
router.get('/:id', deviceWarningController.getDeviceWarningById);
|
||||
router.post('/', deviceWarningController.createDeviceWarning);
|
||||
router.put('/:id', deviceWarningController.updateDeviceWarning);
|
||||
router.delete('/:id', deviceWarningController.deleteDeviceWarning);
|
||||
router.patch('/:id/status', deviceWarningController.updateWarningStatus);
|
||||
|
||||
module.exports = router;
|
||||
17
government-backend/routes/epidemicActivity.js
Normal file
17
government-backend/routes/epidemicActivity.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const epidemicActivityController = require('../controllers/epidemicActivityController');
|
||||
const authMiddleware = require('../middleware/auth');
|
||||
|
||||
// 暂时注释掉认证中间件用于测试
|
||||
// router.use(authMiddleware);
|
||||
|
||||
// 防疫活动管理路由
|
||||
router.get('/', epidemicActivityController.getEpidemicActivities);
|
||||
router.get('/:id', epidemicActivityController.getEpidemicActivityById);
|
||||
router.post('/', epidemicActivityController.createEpidemicActivity);
|
||||
router.put('/:id', epidemicActivityController.updateEpidemicActivity);
|
||||
router.delete('/:id', epidemicActivityController.deleteEpidemicActivity);
|
||||
router.patch('/:id/status', epidemicActivityController.toggleActivityStatus);
|
||||
|
||||
module.exports = router;
|
||||
17
government-backend/routes/productionMaterialCertification.js
Normal file
17
government-backend/routes/productionMaterialCertification.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const productionMaterialCertificationController = require('../controllers/productionMaterialCertificationController');
|
||||
const authMiddleware = require('../middleware/auth');
|
||||
|
||||
// 暂时注释掉认证中间件用于测试
|
||||
// router.use(authMiddleware);
|
||||
|
||||
// 生资认证管理路由
|
||||
router.get('/', productionMaterialCertificationController.getProductionMaterialCertifications);
|
||||
router.get('/:id', productionMaterialCertificationController.getProductionMaterialCertificationById);
|
||||
router.post('/', productionMaterialCertificationController.createProductionMaterialCertification);
|
||||
router.put('/:id', productionMaterialCertificationController.updateProductionMaterialCertification);
|
||||
router.delete('/:id', productionMaterialCertificationController.deleteProductionMaterialCertification);
|
||||
router.patch('/:id/status', productionMaterialCertificationController.toggleCertificationStatus);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,24 +0,0 @@
|
||||
// 简洁地检查路由模块的基本信息
|
||||
const express = require('express');
|
||||
const slaughterRoutes = require('./routes/slaughter');
|
||||
const authRoutes = require('./routes/auth');
|
||||
|
||||
console.log('=== slaughter路由模块 ===');
|
||||
console.log('类型:', typeof slaughterRoutes);
|
||||
console.log('构造函数:', slaughterRoutes && slaughterRoutes.constructor && slaughterRoutes.constructor.name);
|
||||
console.log('是否有stack:', 'stack' in slaughterRoutes);
|
||||
console.log('是否有get方法:', 'get' in slaughterRoutes);
|
||||
|
||||
console.log('\n=== auth路由模块 ===');
|
||||
console.log('类型:', typeof authRoutes);
|
||||
console.log('构造函数:', authRoutes && authRoutes.constructor && authRoutes.constructor.name);
|
||||
console.log('是否有stack:', 'stack' in authRoutes);
|
||||
console.log('是否有get方法:', 'get' in authRoutes);
|
||||
|
||||
// 创建一个新的Router实例进行比较
|
||||
const newRouter = express.Router();
|
||||
console.log('\n=== 新创建的Router实例 ===');
|
||||
console.log('类型:', typeof newRouter);
|
||||
console.log('构造函数:', newRouter && newRouter.constructor && newRouter.constructor.name);
|
||||
console.log('是否有stack:', 'stack' in newRouter);
|
||||
console.log('是否有get方法:', 'get' in newRouter);
|
||||
@@ -1,37 +0,0 @@
|
||||
// 简单测试脚本
|
||||
const sequelize = require('./config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
async function simpleTest() {
|
||||
try {
|
||||
console.log('开始简单测试...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 测试DataTypes是否可用
|
||||
console.log('\n测试DataTypes:');
|
||||
console.log('DataTypes.INTEGER:', DataTypes.INTEGER ? '✅ 可用' : '❌ 不可用');
|
||||
console.log('DataTypes.STRING:', DataTypes.STRING ? '✅ 可用' : '❌ 不可用');
|
||||
console.log('DataTypes.BOOLEAN:', DataTypes.BOOLEAN ? '✅ 可用' : '❌ 不可用');
|
||||
|
||||
// 测试sequelize实例是否可用
|
||||
console.log('\n测试sequelize实例:');
|
||||
console.log('sequelize实例:', sequelize ? '✅ 可用' : '❌ 不可用');
|
||||
|
||||
// 测试直接查询数据库
|
||||
console.log('\n测试直接查询数据库:');
|
||||
const [results] = await sequelize.query('SELECT 1+1 AS result');
|
||||
console.log('查询结果:', results);
|
||||
|
||||
console.log('\n✅ 简单测试完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 简单测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
simpleTest();
|
||||
22
government-backend/sql/create_device_warnings_table.sql
Normal file
22
government-backend/sql/create_device_warnings_table.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS `device_warnings` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`farmName` VARCHAR(255) NOT NULL COMMENT '养殖场名称',
|
||||
`farmerName` VARCHAR(255) NOT NULL COMMENT '养殖户名称',
|
||||
`phone` VARCHAR(255) NOT NULL COMMENT '联系电话',
|
||||
`deviceType` ENUM('智能耳标', '智能项圈', '智能主机') NOT NULL COMMENT '设备类型',
|
||||
`deviceNumber` VARCHAR(255) NOT NULL COMMENT '设备编号',
|
||||
`alertType` ENUM('设备离线', '电量不足', '信号异常', '温度异常', '其他') NOT NULL COMMENT '预警类型',
|
||||
`alertLevel` ENUM('high', 'medium', 'low') DEFAULT 'medium' NOT NULL COMMENT '预警级别 (高, 中, 低)',
|
||||
`alertTime` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '预警时间',
|
||||
`status` ENUM('active', 'resolved', 'ignored') DEFAULT 'active' NOT NULL COMMENT '预警状态 (活跃, 已解决, 已忽略)',
|
||||
`description` TEXT COMMENT '预警描述',
|
||||
`location` VARCHAR(255) COMMENT '设备位置',
|
||||
`batteryLevel` INT COMMENT '电池电量百分比',
|
||||
`signalStrength` INT COMMENT '信号强度',
|
||||
`temperature` FLOAT COMMENT '温度值',
|
||||
`resolvedBy` VARCHAR(255) COMMENT '解决人',
|
||||
`resolvedAt` DATETIME COMMENT '解决时间',
|
||||
`remarks` TEXT COMMENT '备注',
|
||||
`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updatedAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) COMMENT='设备预警管理表';
|
||||
26
government-backend/sql/create_epidemic_activities_table.sql
Normal file
26
government-backend/sql/create_epidemic_activities_table.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- 创建防疫活动管理表
|
||||
CREATE TABLE IF NOT EXISTS `epidemic_activities` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`activity_name` varchar(255) NOT NULL COMMENT '活动名称',
|
||||
`livestock_category` varchar(100) DEFAULT NULL COMMENT '防疫畜别',
|
||||
`disease_category` varchar(100) DEFAULT NULL COMMENT '疫病类别',
|
||||
`vaccine_used` varchar(255) DEFAULT NULL COMMENT '使用疫苗',
|
||||
`vaccine_batch` varchar(100) DEFAULT NULL COMMENT '疫苗批次',
|
||||
`prevention_date` varchar(100) DEFAULT NULL COMMENT '防疫日期',
|
||||
`activity_status` enum('active','inactive') DEFAULT 'active' COMMENT '活动状态',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_activity_name` (`activity_name`),
|
||||
KEY `idx_livestock_category` (`livestock_category`),
|
||||
KEY `idx_disease_category` (`disease_category`),
|
||||
KEY `idx_activity_status` (`activity_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='防疫活动管理表';
|
||||
|
||||
-- 插入测试数据
|
||||
INSERT INTO `epidemic_activities` (`activity_name`, `livestock_category`, `disease_category`, `vaccine_used`, `vaccine_batch`, `prevention_date`, `activity_status`) VALUES
|
||||
('春防', '牛', '口蹄疫', '口蹄疫O型灭活疫苗', '20230401', '2023-04-01至2023-08-01', 'active'),
|
||||
('秋防', '羊', '布鲁氏菌病', '布鲁氏菌病活疫苗', '20230901', '2023-09-01至2023-12-31', 'active'),
|
||||
('猪瘟防疫', '猪', '猪瘟', '猪瘟活疫苗', '20230501', '2023-05-01至2023-05-31', 'inactive'),
|
||||
('禽流感防疫', '鸡', '禽流感', '禽流感H5N1灭活疫苗', '20230601', '2023-06-01至2023-06-30', 'active'),
|
||||
('狂犬病防疫', '犬', '狂犬病', '狂犬病灭活疫苗', '20230701', '2023-07-01至2023-07-31', 'active');
|
||||
12
government-backend/sql/insert_cattle_academy_data.sql
Normal file
12
government-backend/sql/insert_cattle_academy_data.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- 插入养牛学院测试数据
|
||||
INSERT INTO cattle_academy (title, cover_image, content, summary, category, tags, sort, status, view_count, author, publish_time, is_top, is_recommend, remarks) VALUES
|
||||
('新疆阿克苏地区电信5G数字乡村项目', 'https://via.placeholder.com/100x50', '新疆阿克苏地区电信5G数字乡村项目详细内容...', '新疆阿克苏地区电信5G数字乡村项目介绍', '技术资讯', '["5G", "数字乡村", "电信"]', 100, 1, 156, '张专家', '2022-09-28 11:47:27', 1, 1, '重点项目'),
|
||||
('共享养牛,开启畜牧业财富之路', 'https://via.placeholder.com/100x50', '共享养牛模式详细介绍...', '共享养牛模式介绍', '养殖技术', '["共享养牛", "畜牧业", "财富"]', 98, 1, 234, '李专家', '2023-08-28 15:38:35', 0, 1, '热门推荐'),
|
||||
('现代化养牛场建设与管理', 'https://via.placeholder.com/100x50', '现代化养牛场建设与管理详细内容...', '现代化养牛场建设指南', '养殖技术', '["现代化", "养牛场", "管理"]', 95, 1, 189, '王专家', '2023-07-15 09:20:15', 0, 0, '技术指导'),
|
||||
('牛群健康管理与疾病防控', 'https://via.placeholder.com/100x50', '牛群健康管理与疾病防控详细内容...', '牛群健康管理指南', '健康管理', '["健康管理", "疾病防控", "牛群"]', 90, 1, 312, '赵专家', '2023-06-20 14:30:45', 0, 1, '重要技术'),
|
||||
('饲料营养配比与成本控制', 'https://via.placeholder.com/100x50', '饲料营养配比与成本控制详细内容...', '饲料营养配比指南', '营养管理', '["饲料", "营养", "成本控制"]', 85, 1, 267, '孙专家', '2023-05-10 16:45:30', 0, 0, '成本优化'),
|
||||
('智能养殖设备应用案例', 'https://via.placeholder.com/100x50', '智能养殖设备应用案例详细内容...', '智能养殖设备应用', '智能设备', '["智能设备", "养殖", "应用案例"]', 80, 1, 198, '周专家', '2023-04-25 11:15:20', 0, 0, '设备应用'),
|
||||
('养牛场环保与可持续发展', 'https://via.placeholder.com/100x50', '养牛场环保与可持续发展详细内容...', '环保可持续发展指南', '环保管理', '["环保", "可持续发展", "养牛场"]', 75, 1, 145, '吴专家', '2023-03-18 13:25:10', 0, 0, '环保理念'),
|
||||
('牛群繁殖技术与选育', 'https://via.placeholder.com/100x50', '牛群繁殖技术与选育详细内容...', '繁殖技术选育指南', '繁殖技术', '["繁殖技术", "选育", "牛群"]', 70, 1, 223, '郑专家', '2023-02-28 10:40:55', 0, 0, '繁殖技术'),
|
||||
('养牛场财务管理与投资分析', 'https://via.placeholder.com/100x50', '养牛场财务管理与投资分析详细内容...', '财务管理投资分析', '经营管理', '["财务管理", "投资分析", "养牛场"]', 65, 1, 178, '钱专家', '2023-01-15 15:50:25', 0, 0, '经营管理'),
|
||||
('养牛行业市场趋势分析', 'https://via.placeholder.com/100x50', '养牛行业市场趋势分析详细内容...', '市场趋势分析报告', '市场分析', '["市场趋势", "行业分析", "养牛"]', 60, 1, 201, '刘专家', '2022-12-30 12:35:40', 0, 0, '市场分析');
|
||||
16
government-backend/sql/insert_device_warning_data.sql
Normal file
16
government-backend/sql/insert_device_warning_data.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
INSERT INTO `device_warnings` (`farmName`, `farmerName`, `phone`, `deviceType`, `deviceNumber`, `alertType`, `alertLevel`, `alertTime`, `status`, `description`, `location`, `batteryLevel`, `signalStrength`, `temperature`, `remarks`) VALUES
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能项圈', '24077000001', '设备离线', 'high', '2024-01-15 10:30:00', 'active', '设备长时间无信号', 'A区1号牛舍', 15, 0, 25.5, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能项圈', '24065000947', '设备离线', 'high', '2024-01-15 09:45:00', 'active', '设备长时间无信号', 'A区2号牛舍', 8, 0, 26.2, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407100002', '设备离线', 'high', '2024-01-15 11:20:00', 'active', '设备长时间无信号', 'B区1号牛舍', 22, 0, 24.8, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407100001', '设备离线', 'high', '2024-01-15 10:15:00', 'active', '设备长时间无信号', 'B区2号牛舍', 18, 0, 25.1, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407402678', '设备离线', 'high', '2024-01-15 09:30:00', 'active', '设备长时间无信号', 'C区1号牛舍', 12, 0, 26.5, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407402675', '设备离线', 'high', '2024-01-15 08:45:00', 'active', '设备长时间无信号', 'C区2号牛舍', 25, 0, 24.2, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407402674', '设备离线', 'high', '2024-01-15 08:20:00', 'active', '设备长时间无信号', 'C区3号牛舍', 30, 0, 25.8, '需要检查设备连接'),
|
||||
('139****8321_养殖场', '杜云鹏', '139****8321', '智能耳标', '2404412397', '设备离线', 'high', '2024-01-15 07:30:00', 'active', '设备长时间无信号', 'D区1号牛舍', 5, 0, 27.1, '需要检查设备连接'),
|
||||
('139****8321_养殖场', '杜云鹏', '139****8321', '智能耳标', '2404412404', '设备离线', 'high', '2024-01-15 07:15:00', 'active', '设备长时间无信号', 'D区2号牛舍', 8, 0, 26.8, '需要检查设备连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能主机', 'HOST001', '设备离线', 'high', '2024-01-15 12:00:00', 'active', '主机设备离线', '机房A', 0, 0, 28.5, '需要检查主机连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能主机', 'HOST002', '设备离线', 'high', '2024-01-15 11:45:00', 'active', '主机设备离线', '机房B', 0, 0, 29.2, '需要检查主机连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能主机', 'HOST003', '设备离线', 'high', '2024-01-15 11:30:00', 'active', '主机设备离线', '机房C', 0, 0, 27.8, '需要检查主机连接'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407100003', '电量不足', 'medium', '2024-01-15 13:20:00', 'active', '设备电量低于20%', 'A区3号牛舍', 18, 65, 25.3, '需要更换电池'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能项圈', '24077000002', '信号异常', 'medium', '2024-01-15 13:45:00', 'active', '信号强度不稳定', 'B区3号牛舍', 45, 25, 26.1, '需要检查信号接收'),
|
||||
('扎旗大数据中心', '扎旗大数据中心', '132****9345', '智能耳标', '2407100004', '温度异常', 'high', '2024-01-15 14:10:00', 'active', '温度超过正常范围', 'C区4号牛舍', 35, 80, 32.5, '需要检查牛只健康状况');
|
||||
@@ -1,27 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5352,
|
||||
path: '/api/slaughter/slaughterhouses',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer mock-jwt-token-test',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
console.log(`状态码: ${res.statusCode}`);
|
||||
console.log(`响应头: ${JSON.stringify(res.headers)}`);
|
||||
|
||||
res.on('data', (d) => {
|
||||
process.stdout.write(d);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
req.end();
|
||||
@@ -1,84 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
// 测试函数
|
||||
function testRoute(path, description) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5353,
|
||||
path: path,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer mock-jwt-token-test',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
path: path,
|
||||
description: description,
|
||||
statusCode: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject({
|
||||
path: path,
|
||||
description: description,
|
||||
error: error.message
|
||||
});
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 运行所有测试
|
||||
async function runTests() {
|
||||
console.log('开始测试路由...\n');
|
||||
|
||||
try {
|
||||
// 测试健康检查路由
|
||||
const healthResult = await testRoute('/health', '健康检查');
|
||||
console.log(`${healthResult.description} - 状态码: ${healthResult.statusCode}`);
|
||||
console.log(`响应: ${healthResult.body}\n`);
|
||||
|
||||
// 测试测试路由
|
||||
const testResult = await testRoute('/api/test/test', '测试路由');
|
||||
console.log(`${testResult.description} - 状态码: ${testResult.statusCode}`);
|
||||
console.log(`响应: ${testResult.body}\n`);
|
||||
|
||||
// 测试slaughter路由
|
||||
const slaughterResult = await testRoute('/api/slaughter/slaughterhouses', 'Slaughter路由');
|
||||
console.log(`${slaughterResult.description} - 状态码: ${slaughterResult.statusCode}`);
|
||||
console.log(`响应: ${slaughterResult.body}\n`);
|
||||
|
||||
// 测试不存在的路由
|
||||
const notFoundResult = await testRoute('/api/not-exist', '不存在的路由');
|
||||
console.log(`${notFoundResult.description} - 状态码: ${notFoundResult.statusCode}`);
|
||||
console.log(`响应: ${notFoundResult.body}\n`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待一会儿再运行测试,给服务器启动时间
|
||||
sleep(2000).then(() => {
|
||||
runTests();
|
||||
});
|
||||
|
||||
// 简单的sleep函数
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function testConnection() {
|
||||
try {
|
||||
// 从环境变量获取数据库配置
|
||||
const config = {
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
database: process.env.DB_NAME || 'ningxia_zhengfu'
|
||||
};
|
||||
|
||||
console.log('正在连接数据库...');
|
||||
console.log('📋 数据库配置信息:');
|
||||
console.log(` 主机: ${config.host}`);
|
||||
console.log(` 端口: ${config.port}`);
|
||||
console.log(` 数据库名: ${config.database}`);
|
||||
console.log(` 用户名: ${config.user}`);
|
||||
|
||||
// 创建数据库连接
|
||||
const connection = await mysql.createConnection(config);
|
||||
console.log('✅ 政府端后端数据库连接成功');
|
||||
|
||||
// 查询数据库中的表
|
||||
const [tables] = await connection.query(
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema = ?",
|
||||
[config.database]
|
||||
);
|
||||
console.log(`📚 数据库表数量: ${tables.length}`);
|
||||
|
||||
// 关闭连接
|
||||
await connection.end();
|
||||
console.log('🔒 数据库连接已关闭');
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ 政府端后端数据库连接失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
testConnection();
|
||||
50
government-backend/test-data-center-api.js
Normal file
50
government-backend/test-data-center-api.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5352/api/government';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer test-token'
|
||||
}
|
||||
});
|
||||
|
||||
async function testDataCenterAPI() {
|
||||
console.log('开始测试数据中心API...\n');
|
||||
|
||||
try {
|
||||
// 测试获取数据中心统计数据
|
||||
console.log('1. 测试获取数据中心统计数据...');
|
||||
const response = await api.get('/data-center');
|
||||
console.log('数据中心统计:', JSON.stringify(response.data, null, 2));
|
||||
console.log('✅ 获取数据中心统计数据成功\n');
|
||||
|
||||
// 测试获取市场价格信息
|
||||
console.log('2. 测试获取市场价格信息...');
|
||||
const priceResponse = await api.get('/market-price?type=beef');
|
||||
console.log('市场价格信息:', JSON.stringify(priceResponse.data, null, 2));
|
||||
console.log('✅ 获取市场价格信息成功\n');
|
||||
|
||||
// 测试获取部门列表
|
||||
console.log('3. 测试获取部门列表...');
|
||||
const deptResponse = await api.get('/departments');
|
||||
console.log('部门列表:', JSON.stringify(deptResponse.data, null, 2));
|
||||
console.log('✅ 获取部门列表成功\n');
|
||||
|
||||
// 测试获取行政人员列表
|
||||
console.log('4. 测试获取行政人员列表...');
|
||||
const staffResponse = await api.get('/admin-staff');
|
||||
console.log('行政人员列表:', JSON.stringify(staffResponse.data, null, 2));
|
||||
console.log('✅ 获取行政人员列表成功\n');
|
||||
|
||||
console.log('🎉 所有数据中心API测试通过!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API测试失败:', error.response ? error.response.data : error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testDataCenterAPI();
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// 测试数据库连接
|
||||
const sequelize = require('./config/database');
|
||||
|
||||
async function testDbConnection() {
|
||||
try {
|
||||
console.log('正在尝试连接数据库...');
|
||||
console.log('连接配置:', {
|
||||
host: sequelize.config.host,
|
||||
port: sequelize.config.port,
|
||||
database: sequelize.config.database,
|
||||
username: sequelize.config.username,
|
||||
dialect: sequelize.config.dialect
|
||||
});
|
||||
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功!');
|
||||
|
||||
// 尝试查询数据库版本信息
|
||||
const [results] = await sequelize.query('SELECT VERSION() AS version');
|
||||
console.log('数据库版本:', results[0].version);
|
||||
|
||||
// 关闭连接
|
||||
await sequelize.close();
|
||||
console.log('数据库连接已关闭');
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error.message);
|
||||
console.error('详细错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testDbConnection();
|
||||
@@ -1,32 +0,0 @@
|
||||
const axios = require('axios')
|
||||
|
||||
async function testEpidemicAPI() {
|
||||
try {
|
||||
console.log('测试防疫记录API...')
|
||||
|
||||
// 测试获取列表
|
||||
const response = await axios.get('http://localhost:5352/api/epidemic-record/list?page=1&pageSize=5')
|
||||
|
||||
console.log('API响应状态:', response.status)
|
||||
console.log('API响应数据:', JSON.stringify(response.data, null, 2))
|
||||
|
||||
if (response.data.code === 200) {
|
||||
console.log('✅ API调用成功')
|
||||
console.log('记录数量:', response.data.data.list.length)
|
||||
if (response.data.data.list.length > 0) {
|
||||
console.log('第一条记录:', response.data.data.list[0])
|
||||
}
|
||||
} else {
|
||||
console.log('❌ API返回错误:', response.data.message)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API调用失败:', error.message)
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status)
|
||||
console.error('响应数据:', error.response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testEpidemicAPI()
|
||||
@@ -1,101 +0,0 @@
|
||||
// 测试修复后的epidemic agencies接口
|
||||
const axios = require('axios');
|
||||
|
||||
// 政府后端服务地址
|
||||
const BASE_URL = 'http://localhost:5352/api';
|
||||
|
||||
// 登录获取token
|
||||
async function login() {
|
||||
try {
|
||||
console.log('开始登录...');
|
||||
const response = await axios.post(`${BASE_URL}/auth/login`, {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
console.log('登录响应:', response.data);
|
||||
|
||||
if (response.data.code === 200 && response.data.data && response.data.data.token) {
|
||||
console.log('登录成功,获取到token');
|
||||
return response.data.data.token;
|
||||
} else {
|
||||
console.log('登录失败,未获取到token');
|
||||
console.log('错误信息:', response.data.message || '未知错误');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录请求失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试新增防疫机构
|
||||
async function testCreateAgency(token) {
|
||||
try {
|
||||
console.log('\n开始测试新增防疫机构...');
|
||||
const testData = {
|
||||
name: '测试防疫机构' + Date.now(),
|
||||
code: 'TEST-' + Date.now(),
|
||||
type: 'station',
|
||||
level: 'county',
|
||||
manager: '测试负责人',
|
||||
phone: '13800138999',
|
||||
address: '测试地址',
|
||||
epidemicScope: '测试防疫范围',
|
||||
remarks: '测试备注',
|
||||
email: 'test@example.com',
|
||||
status: 'active',
|
||||
establishmentDate: '2024-01-01'
|
||||
};
|
||||
|
||||
console.log('提交的数据:', testData);
|
||||
|
||||
const response = await axios.post(`${BASE_URL}/epidemic/agencies`, testData, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('新增防疫机构成功');
|
||||
console.log(`- 状态码: ${response.status}`);
|
||||
console.log(`- 返回数据:`, response.data);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('测试新增防疫机构失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
async function runTests() {
|
||||
try {
|
||||
console.log('开始测试修复后的epidemic agencies接口...');
|
||||
|
||||
// 1. 登录获取token
|
||||
const token = await login();
|
||||
if (!token) {
|
||||
console.log('未获取到token,测试终止');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 测试新增防疫机构
|
||||
const createResult = await testCreateAgency(token);
|
||||
|
||||
console.log('\n测试完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
runTests();
|
||||
@@ -1,95 +0,0 @@
|
||||
// 测试无害化场所API
|
||||
const axios = require('axios');
|
||||
|
||||
// 政府后端服务地址
|
||||
const BASE_URL = 'http://localhost:5352/api';
|
||||
|
||||
// 登录获取token
|
||||
async function login() {
|
||||
try {
|
||||
console.log('开始登录...');
|
||||
const response = await axios.post(`${BASE_URL}/auth/login`, {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
console.log('登录响应:', response.data);
|
||||
|
||||
if (response.data.code === 200 && response.data.data && response.data.data.token) {
|
||||
console.log('登录成功,获取到token');
|
||||
return response.data.data.token;
|
||||
} else {
|
||||
console.log('登录失败,未获取到token');
|
||||
console.log('错误信息:', response.data.message || '未知错误');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录请求失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试无害化场所列表API
|
||||
async function testHarmlessPlaceList(token) {
|
||||
try {
|
||||
console.log('\n测试无害化场所列表API...');
|
||||
const response = await axios.get(`${BASE_URL}/harmless-place/list`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
params: {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
|
||||
console.log('API调用成功,状态码:', response.status);
|
||||
console.log('返回数据结构:', Object.keys(response.data));
|
||||
console.log('无害化场所总数:', response.data.total || '未知');
|
||||
|
||||
if (response.data.data && Array.isArray(response.data.data)) {
|
||||
console.log('返回的无害化场所列表长度:', response.data.data.length);
|
||||
if (response.data.data.length > 0) {
|
||||
console.log('第一条无害化场所数据:');
|
||||
console.log(response.data.data[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('无害化场所列表API调用失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
const runTests = async () => {
|
||||
console.log('开始测试无害化场所管理API...');
|
||||
try {
|
||||
// 1. 登录获取token
|
||||
const token = await login();
|
||||
|
||||
if (!token) {
|
||||
console.error('无法继续测试,因为未获取到有效的token');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 测试获取无害化场所列表
|
||||
await testHarmlessPlaceList(token);
|
||||
|
||||
console.log('\n所有测试完成!');
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 运行测试
|
||||
runTests();
|
||||
@@ -1,89 +0,0 @@
|
||||
// 清除模块缓存
|
||||
function clearModuleCache() {
|
||||
const modulesToClear = Object.keys(require.cache).filter(key =>
|
||||
key.includes('HarmlessPlace') || key.includes('database')
|
||||
);
|
||||
|
||||
console.log('清除以下模块的缓存:', modulesToClear.length, '个模块');
|
||||
modulesToClear.forEach(key => {
|
||||
console.log('-', key);
|
||||
delete require.cache[key];
|
||||
});
|
||||
}
|
||||
|
||||
// 清除缓存后再导入
|
||||
clearModuleCache();
|
||||
|
||||
const axios = require('axios');
|
||||
const HarmlessPlace = require('./models/HarmlessPlace');
|
||||
|
||||
console.log('=== 检查HarmlessPlace模型 ===');
|
||||
console.log('HarmlessPlace的类型:', typeof HarmlessPlace);
|
||||
console.log('HarmlessPlace是否为对象:', typeof HarmlessPlace === 'object' && HarmlessPlace !== null);
|
||||
console.log('HarmlessPlace是否有findAndCountAll方法:', typeof HarmlessPlace.findAndCountAll !== 'undefined');
|
||||
if (HarmlessPlace.findAndCountAll) {
|
||||
console.log('findAndCountAll的类型:', typeof HarmlessPlace.findAndCountAll);
|
||||
}
|
||||
|
||||
// 登录函数
|
||||
async function login() {
|
||||
try {
|
||||
const response = await axios.post('http://localhost:3000/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
console.log('登录成功,token:', response.data.data.token);
|
||||
return response.data.data.token;
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error.response ? error.response.data : error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试无害化场所列表API
|
||||
async function testHarmlessPlaceList(token) {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:3000/api/harmless-place/list', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
params: {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
console.log('无害化场所列表API调用成功:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('无害化场所列表API调用失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
console.log('开始测试无害化场所管理API...');
|
||||
|
||||
// 登录获取token
|
||||
const token = await login();
|
||||
if (!token) {
|
||||
console.log('登录失败,无法继续测试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 再次检查模型类型,确保在API调用前没有被修改
|
||||
console.log('\n=== API调用前再次检查HarmlessPlace模型 ===');
|
||||
console.log('HarmlessPlace的类型:', typeof HarmlessPlace);
|
||||
console.log('HarmlessPlace是否有findAndCountAll方法:', typeof HarmlessPlace.findAndCountAll !== 'undefined');
|
||||
|
||||
// 测试API
|
||||
await testHarmlessPlaceList(token);
|
||||
|
||||
console.log('\n所有测试完成!');
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
main().catch(err => console.error('测试过程中出错:', err));
|
||||
@@ -1,74 +0,0 @@
|
||||
// 测试模型文件
|
||||
const sequelize = require('./config/database');
|
||||
const AdminStaff = require('./models/AdminStaff');
|
||||
const Department = require('./models/Department');
|
||||
const Position = require('./models/Position');
|
||||
|
||||
async function testModels() {
|
||||
try {
|
||||
console.log('开始测试模型...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 测试模型关系
|
||||
console.log('\n测试模型关系:');
|
||||
console.log('AdminStaff关联Department:', AdminStaff.associations.department ? '✅' : '❌');
|
||||
console.log('AdminStaff关联Position:', AdminStaff.associations.position ? '✅' : '❌');
|
||||
console.log('Department关联Position:', Department.associations.positions ? '✅' : '❌');
|
||||
console.log('Position关联Department:', Position.associations.department ? '✅' : '❌');
|
||||
|
||||
// 尝试获取行政人员数据
|
||||
console.log('\n尝试获取行政人员数据:');
|
||||
const adminStaffs = await AdminStaff.findAll({
|
||||
include: [
|
||||
{ model: Department, as: 'department' },
|
||||
{ model: Position, as: 'position' }
|
||||
],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
if (adminStaffs.length > 0) {
|
||||
console.log(`✅ 成功获取${adminStaffs.length}条行政人员数据`);
|
||||
console.log('示例数据:', adminStaffs[0].dataValues);
|
||||
} else {
|
||||
console.log('⚠️ 未找到行政人员数据');
|
||||
}
|
||||
|
||||
// 尝试获取部门数据
|
||||
console.log('\n尝试获取部门数据:');
|
||||
const departments = await Department.findAll({
|
||||
limit: 5
|
||||
});
|
||||
|
||||
if (departments.length > 0) {
|
||||
console.log(`✅ 成功获取${departments.length}条部门数据`);
|
||||
console.log('示例数据:', departments[0].dataValues);
|
||||
} else {
|
||||
console.log('⚠️ 未找到部门数据');
|
||||
}
|
||||
|
||||
// 尝试获取岗位数据
|
||||
console.log('\n尝试获取岗位数据:');
|
||||
const positions = await Position.findAll({
|
||||
include: [{ model: Department, as: 'department' }],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
if (positions.length > 0) {
|
||||
console.log(`✅ 成功获取${positions.length}条岗位数据`);
|
||||
console.log('示例数据:', positions[0].dataValues);
|
||||
} else {
|
||||
console.log('⚠️ 未找到岗位数据');
|
||||
}
|
||||
|
||||
console.log('\n✅ 模型测试完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 模型测试失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
testModels();
|
||||
@@ -1,50 +0,0 @@
|
||||
// 测试行政人员列表接口
|
||||
const axios = require('axios');
|
||||
|
||||
// 政府后端服务地址
|
||||
const BASE_URL = 'http://localhost:5352/api';
|
||||
|
||||
// 测试行政人员列表接口
|
||||
async function testAdminStaffList() {
|
||||
try {
|
||||
// 先登录获取token
|
||||
const loginResponse = await axios.post(`${BASE_URL}/auth/login`, {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
const token = loginResponse.data.token;
|
||||
console.log('登录成功,获取到token');
|
||||
|
||||
// 使用token访问行政人员列表接口
|
||||
const response = await axios.get(`${BASE_URL}/personnel`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('行政人员列表接口测试结果:');
|
||||
console.log(`- 状态码: ${response.status}`);
|
||||
console.log(`- 返回数据结构:`, Object.keys(response.data));
|
||||
console.log(`- 行政人员总数: ${response.data.total}`);
|
||||
console.log(`- 返回的行政人员列表长度: ${response.data.data ? response.data.data.length : 0}`);
|
||||
|
||||
if (response.data.data && response.data.data.length > 0) {
|
||||
console.log(`- 第一条行政人员数据:`, response.data.data[0]);
|
||||
} else {
|
||||
console.log('- 行政人员列表为空');
|
||||
}
|
||||
|
||||
console.log('\n测试完成!');
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('错误状态码:', error.response.status);
|
||||
console.error('错误数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
console.log('开始测试行政人员列表接口...');
|
||||
testAdminStaffList();
|
||||
@@ -1,51 +0,0 @@
|
||||
// 测试Express应用的路由注册情况
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
|
||||
// 创建一个简单的Express应用来测试路由
|
||||
const app = express();
|
||||
|
||||
// 尝试加载slaughter路由
|
||||
try {
|
||||
const slaughterRoutes = require('./routes/slaughter');
|
||||
console.log('成功加载slaughter路由模块');
|
||||
|
||||
// 检查路由模块的内容
|
||||
console.log('路由模块导出:', typeof slaughterRoutes);
|
||||
|
||||
// 模拟注册路由
|
||||
app.use('/api/slaughter', slaughterRoutes);
|
||||
console.log('成功注册slaughter路由到/api/slaughter');
|
||||
|
||||
// 检查路由是否有方法
|
||||
if (slaughterRoutes && slaughterRoutes.stack) {
|
||||
console.log('路由处理程序数量:', slaughterRoutes.stack.length);
|
||||
slaughterRoutes.stack.forEach((layer, index) => {
|
||||
if (layer.route) {
|
||||
console.log(`路由${index + 1}:`, layer.route.path, Object.keys(layer.route.methods));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('路由模块没有stack属性,可能不是Express Router实例');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载slaughter路由失败:', error);
|
||||
}
|
||||
|
||||
// 检查routes目录下的文件
|
||||
const fs = require('fs');
|
||||
const routesDir = path.join(__dirname, 'routes');
|
||||
fs.readdir(routesDir, (err, files) => {
|
||||
if (err) {
|
||||
console.error('读取routes目录失败:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\nroutes目录下的文件:');
|
||||
files.forEach(file => {
|
||||
console.log('-', file);
|
||||
// 检查文件大小,确认文件不为空
|
||||
const stats = fs.statSync(path.join(routesDir, file));
|
||||
console.log(` 大小: ${stats.size} 字节`);
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const app = express();
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// 简单的认证中间件,允许所有请求通过
|
||||
app.use((req, res, next) => {
|
||||
console.log(`接收到请求: ${req.method} ${req.path}`);
|
||||
// 模拟用户数据
|
||||
req.user = {
|
||||
id: '1',
|
||||
username: 'admin',
|
||||
role: 'admin'
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
// 加载测试路由
|
||||
app.use('/api/test', require('./routes/test-route'));
|
||||
|
||||
// 加载slaughter路由
|
||||
app.use('/api/slaughter', require('./routes/slaughter'));
|
||||
|
||||
// 简单的健康检查路由
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('错误:', err);
|
||||
res.status(500).json({ error: '服务器错误' });
|
||||
});
|
||||
|
||||
// 启动服务器在不同端口
|
||||
const PORT = 5353;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`测试服务器已启动在端口 ${PORT}`);
|
||||
});
|
||||
156
insurance_admin-system/src/config/env.js
Normal file
156
insurance_admin-system/src/config/env.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 环境配置文件
|
||||
* 统一管理所有API URL和环境变量
|
||||
*/
|
||||
|
||||
// 获取环境变量
|
||||
const env = import.meta.env.MODE || 'development'
|
||||
|
||||
// 环境配置
|
||||
const envConfig = {
|
||||
development: {
|
||||
// 开发环境配置
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 30000,
|
||||
// 其他开发环境配置
|
||||
wsURL: 'ws://localhost:3000',
|
||||
},
|
||||
production: {
|
||||
// 生产环境配置
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000,
|
||||
// 其他生产环境配置
|
||||
wsURL: 'wss://production-domain.com',
|
||||
},
|
||||
test: {
|
||||
// 测试环境配置
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 15000,
|
||||
// 其他测试环境配置
|
||||
wsURL: 'ws://test-server:3000',
|
||||
}
|
||||
}
|
||||
|
||||
// 导出当前环境配置
|
||||
export default envConfig[env] || envConfig.development
|
||||
|
||||
// 导出特定配置项,方便按需引入
|
||||
export const { baseURL, timeout, wsURL } = envConfig[env] || envConfig.development
|
||||
|
||||
// API端点配置
|
||||
export const API_ENDPOINTS = {
|
||||
// 认证相关
|
||||
AUTH: {
|
||||
LOGIN: '/auth/login',
|
||||
LOGOUT: '/auth/logout',
|
||||
REFRESH: '/auth/refresh',
|
||||
PROFILE: '/users/profile'
|
||||
},
|
||||
|
||||
// 用户管理
|
||||
USERS: {
|
||||
LIST: '/users',
|
||||
CREATE: '/users',
|
||||
UPDATE: (id) => `/users/${id}`,
|
||||
DELETE: (id) => `/users/${id}`,
|
||||
EXPORT: '/users/export'
|
||||
},
|
||||
|
||||
// 保险类型管理
|
||||
INSURANCE_TYPES: {
|
||||
LIST: '/insurance-types',
|
||||
CREATE: '/insurance-types',
|
||||
UPDATE: (id) => `/insurance-types/${id}`,
|
||||
DELETE: (id) => `/insurance-types/${id}`,
|
||||
EXPORT: '/insurance-types/export'
|
||||
},
|
||||
|
||||
// 申请管理
|
||||
APPLICATIONS: {
|
||||
LIST: '/insurance/applications',
|
||||
DETAIL: (id) => `/insurance/applications/${id}`,
|
||||
CREATE: '/insurance/applications',
|
||||
UPDATE: (id) => `/insurance/applications/${id}`,
|
||||
REVIEW: (id) => `/insurance/applications/${id}/review`,
|
||||
EXPORT: '/insurance/applications/export'
|
||||
},
|
||||
|
||||
// 保单管理
|
||||
POLICIES: {
|
||||
LIST: '/policies',
|
||||
DETAIL: (id) => `/policies/${id}`,
|
||||
CREATE: '/policies',
|
||||
UPDATE: (id) => `/policies/${id}`,
|
||||
EXPORT: '/policies/export'
|
||||
},
|
||||
|
||||
// 理赔管理
|
||||
CLAIMS: {
|
||||
LIST: '/claims',
|
||||
DETAIL: (id) => `/claims/${id}`,
|
||||
UPDATE_STATUS: (id) => `/claims/${id}/status`,
|
||||
EXPORT: '/claims/export'
|
||||
},
|
||||
|
||||
// 设备预警
|
||||
DEVICE_ALERTS: {
|
||||
LIST: '/device-alerts',
|
||||
DETAIL: (id) => `/device-alerts/${id}`,
|
||||
STATS: '/device-alerts/stats',
|
||||
MARK_READ: (id) => `/device-alerts/${id}/read`,
|
||||
EXPORT: '/device-alerts/export'
|
||||
},
|
||||
|
||||
// 监管任务
|
||||
SUPERVISION_TASKS: {
|
||||
LIST: '/supervision-tasks',
|
||||
DETAIL: (id) => `/supervision-tasks/${id}`,
|
||||
CREATE: '/supervision-tasks',
|
||||
UPDATE: (id) => `/supervision-tasks/${id}`,
|
||||
DELETE: (id) => `/supervision-tasks/${id}`,
|
||||
STATS: '/supervision-tasks/stats',
|
||||
EXPORT: '/supervision-tasks/export',
|
||||
ARCHIVE: (id) => `/supervision-tasks/${id}/archive`,
|
||||
DOWNLOAD_REPORT: (id) => `/supervision-tasks/${id}/report`
|
||||
},
|
||||
|
||||
// 待安装任务
|
||||
INSTALLATION_TASKS: {
|
||||
LIST: '/installation-tasks',
|
||||
DETAIL: (id) => `/installation-tasks/${id}`,
|
||||
CREATE: '/installation-tasks',
|
||||
UPDATE: (id) => `/installation-tasks/${id}`,
|
||||
DELETE: (id) => `/installation-tasks/${id}`,
|
||||
STATS: '/installation-tasks/stats',
|
||||
EXPORT: '/installation-tasks/export'
|
||||
},
|
||||
|
||||
// 生资保险
|
||||
LIVESTOCK: {
|
||||
TYPES: '/livestock-types',
|
||||
POLICIES: '/livestock-policies',
|
||||
CLAIMS: '/livestock-claims'
|
||||
},
|
||||
|
||||
// 操作日志
|
||||
OPERATION_LOGS: {
|
||||
LIST: '/operation-logs',
|
||||
STATS: '/operation-logs/stats',
|
||||
EXPORT: '/operation-logs/export'
|
||||
},
|
||||
|
||||
// 权限管理
|
||||
PERMISSIONS: {
|
||||
LIST: '/permissions',
|
||||
TREE: '/permissions/tree'
|
||||
},
|
||||
|
||||
// 角色权限
|
||||
ROLE_PERMISSIONS: {
|
||||
ROLES: '/role-permissions/roles',
|
||||
PERMISSIONS: '/role-permissions/permissions',
|
||||
ASSIGN: (roleId) => `/role-permissions/${roleId}/assign`,
|
||||
COPY: '/role-permissions/copy'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import RangePickerTest from '@/views/RangePickerTest.vue'
|
||||
import LoginTest from '@/views/LoginTest.vue'
|
||||
import LivestockPolicyManagement from '@/views/LivestockPolicyManagement.vue'
|
||||
import SystemSettings from '@/views/SystemSettings.vue'
|
||||
import TokenDebug from '@/views/TokenDebug.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -167,7 +166,7 @@ const routes = [
|
||||
{
|
||||
path: 'token-debug',
|
||||
name: 'TokenDebug',
|
||||
component: TokenDebug,
|
||||
component: () => import('@/views/TokenDebug.vue'),
|
||||
meta: { title: 'Token调试' }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import envConfig from '@/config/env'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态
|
||||
@@ -102,7 +103,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/insurance/api/auth/refresh', {
|
||||
const response = await fetch(`${envConfig.baseURL}/auth/refresh`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -20,7 +20,8 @@ export const userAPI = {
|
||||
changePassword: (data) => api.put('/users/change-password', data),
|
||||
uploadAvatar: (formData) => api.post('/users/avatar', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}),
|
||||
export: (params) => api.get('/users/export', { params, responseType: 'blob' })
|
||||
};
|
||||
|
||||
export const menuAPI = {
|
||||
@@ -39,7 +40,8 @@ export const insuranceTypeAPI = {
|
||||
create: (data) => api.post('/insurance-types', data),
|
||||
update: (id, data) => api.put(`/insurance-types/${id}`, data),
|
||||
delete: (id) => api.delete(`/insurance-types/${id}`),
|
||||
updateStatus: (id, data) => api.patch(`/insurance-types/${id}/status`, data)
|
||||
updateStatus: (id, data) => api.patch(`/insurance-types/${id}/status`, data),
|
||||
export: (params) => api.get('/insurance-types/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
export const applicationAPI = {
|
||||
@@ -61,14 +63,16 @@ export const policyAPI = {
|
||||
create: (data) => api.post('/policies', data),
|
||||
update: (id, data) => api.put(`/policies/${id}`, data),
|
||||
updateStatus: (id, data) => api.put(`/policies/${id}/status`, data),
|
||||
delete: (id) => api.delete(`/policies/${id}`)
|
||||
delete: (id) => api.delete(`/policies/${id}`),
|
||||
export: (params) => api.get('/policies/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
export const claimAPI = {
|
||||
getList: (params) => api.get('/claims', { params }),
|
||||
getDetail: (id) => api.get(`/claims/${id}`),
|
||||
updateStatus: (id, data) => api.put(`/claims/${id}/status`, data),
|
||||
delete: (id) => api.delete(`/claims/${id}`)
|
||||
delete: (id) => api.delete(`/claims/${id}`),
|
||||
export: (params) => api.get('/claims/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
export const dashboardAPI = {
|
||||
@@ -84,7 +88,8 @@ export const deviceAlertAPI = {
|
||||
getDetail: (id) => api.get(`/device-alerts/${id}`),
|
||||
markAsRead: (id) => api.patch(`/device-alerts/${id}/read`),
|
||||
markAllAsRead: () => api.patch('/device-alerts/read-all'),
|
||||
handle: (id, data) => api.patch(`/device-alerts/${id}/handle`, data)
|
||||
handle: (id, data) => api.patch(`/device-alerts/${id}/handle`, data),
|
||||
export: (params) => api.get('/device-alerts/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
// 数据览仓API
|
||||
@@ -104,7 +109,10 @@ export const supervisionTaskApi = {
|
||||
delete: (id) => api.delete(`/supervision-tasks/${id}`),
|
||||
getDetail: (id) => api.get(`/supervision-tasks/${id}`),
|
||||
batchOperate: (data) => api.post('/supervision-tasks/batch/operate', data),
|
||||
getStats: () => api.get('/supervision-tasks/stats')
|
||||
getStats: () => api.get('/supervision-tasks/stats'),
|
||||
export: (params) => api.get('/supervision-tasks/export', { params, responseType: 'blob' }),
|
||||
archive: (id) => api.patch(`/supervision-tasks/${id}/archive`),
|
||||
downloadReport: (id) => api.get(`/supervision-tasks/${id}/report`, { responseType: 'blob' })
|
||||
}
|
||||
|
||||
// 待安装任务API
|
||||
@@ -118,7 +126,11 @@ export const installationTaskApi = {
|
||||
getStats: () => api.get('/installation-tasks/stats'),
|
||||
assign: (id, data) => api.post(`/installation-tasks/${id}/assign`, data),
|
||||
complete: (id, data) => api.post(`/installation-tasks/${id}/complete`, data),
|
||||
getHistory: (id) => api.get(`/installation-tasks/${id}/history`)
|
||||
getHistory: (id) => api.get(`/installation-tasks/${id}/history`),
|
||||
export: (params) => api.get('/installation-tasks/export', { params, responseType: 'blob' }),
|
||||
batchDelete: (ids) => api.post('/installation-tasks/batch-delete', { ids }),
|
||||
batchUpdateStatus: (data) => api.post('/installation-tasks/batch-update-status', data),
|
||||
getDetail: (id) => api.get(`/installation-tasks/${id}`)
|
||||
}
|
||||
|
||||
// 生资保险相关API
|
||||
@@ -140,7 +152,8 @@ export const livestockPolicyApi = {
|
||||
getById: (id) => api.get(`/livestock-policies/${id}`),
|
||||
updateStatus: (id, data) => api.patch(`/livestock-policies/${id}/status`, data),
|
||||
getStats: () => api.get('/livestock-policies/stats'),
|
||||
getLivestockTypes: () => api.get('/livestock-types/active')
|
||||
getLivestockTypes: () => api.get('/livestock-types/active'),
|
||||
export: (params) => api.get('/livestock-policies/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
export const livestockClaimApi = {
|
||||
@@ -152,7 +165,8 @@ export const livestockClaimApi = {
|
||||
approve: (id, data) => api.post(`/livestock-claims/${id}/approve`, data),
|
||||
reject: (id, data) => api.post(`/livestock-claims/${id}/reject`, data),
|
||||
updatePaymentStatus: (id, data) => api.patch(`/livestock-claims/${id}/payment`, data),
|
||||
getStats: () => api.get('/livestock-claims/stats')
|
||||
getStats: () => api.get('/livestock-claims/stats'),
|
||||
export: (params) => api.get('/livestock-claims/export', { params, responseType: 'blob' })
|
||||
}
|
||||
|
||||
// 操作日志API
|
||||
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
|
||||
// 更新安装任务
|
||||
updateInstallationTask: (id, data) => {
|
||||
return installationTaskApi.update({ ...data, id })
|
||||
return installationTaskApi.update(id, data)
|
||||
},
|
||||
|
||||
// 删除安装任务
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import envConfig from '@/config/env'
|
||||
|
||||
// API基础配置
|
||||
const API_CONFIG = {
|
||||
baseURL: '/insurance/api',
|
||||
timeout: 10000
|
||||
baseURL: envConfig.baseURL,
|
||||
timeout: envConfig.timeout
|
||||
}
|
||||
|
||||
// 是否正在刷新token的标志
|
||||
@@ -85,7 +86,15 @@ const handleResponse = async (response) => {
|
||||
|
||||
try {
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
|
||||
// 处理文件下载(Excel、CSV等)
|
||||
if (contentType && (
|
||||
contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') ||
|
||||
contentType.includes('text/csv') ||
|
||||
contentType.includes('application/octet-stream')
|
||||
)) {
|
||||
data = await response.blob()
|
||||
} else if (contentType && contentType.includes('application/json')) {
|
||||
data = await response.json()
|
||||
} else {
|
||||
data = await response.text()
|
||||
|
||||
@@ -640,13 +640,14 @@ const exportData = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await applicationAPI.export(searchForm)
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `申请数据_${new Date().toISOString().slice(0, 10)}.xlsx`)
|
||||
link.setAttribute('download', `保险申请数据_${new Date().toISOString().slice(0, 10)}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url) // 释放内存
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
sub-title="管理系统所有理赔申请"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增理赔
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增理赔
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
@@ -445,7 +451,8 @@ import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined,
|
||||
FileTextOutlined
|
||||
FileTextOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { livestockClaimApi } from '@/utils/api'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -967,6 +974,42 @@ const handleDelete = async (id) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
search: searchForm.search || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await livestockClaimApi.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `理赔列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadClaims()
|
||||
})
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<SearchOutlined />
|
||||
搜索
|
||||
@@ -122,9 +126,9 @@
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
<template v-if="column.key === 'taskStatus'">
|
||||
<a-tag :color="getStatusColor(record.taskStatus)">
|
||||
{{ getStatusText(record.taskStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'priority'">
|
||||
@@ -132,9 +136,6 @@
|
||||
{{ getPriorityText(record.priority) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'duration'">
|
||||
{{ record.duration }} 天
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
@@ -178,11 +179,29 @@
|
||||
>
|
||||
<div v-if="selectedTask" class="task-detail">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="任务名称">
|
||||
{{ selectedTask.taskName }}
|
||||
<a-descriptions-item label="申请单号">
|
||||
{{ selectedTask.applicationNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务编号">
|
||||
{{ selectedTask.taskCode }}
|
||||
<a-descriptions-item label="保单编号">
|
||||
{{ selectedTask.policyNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ selectedTask.productName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保险期间">
|
||||
{{ selectedTask.insurancePeriod || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户姓名">
|
||||
{{ selectedTask.customerName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件类型">
|
||||
{{ selectedTask.idType || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件号码">
|
||||
{{ selectedTask.idNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管生资数量">
|
||||
{{ selectedTask.supervisorySuppliesQuantity || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="优先级">
|
||||
<a-tag :color="getPriorityColor(selectedTask.priority)">
|
||||
@@ -190,27 +209,30 @@
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(selectedTask.status)">
|
||||
{{ getStatusText(selectedTask.status) }}
|
||||
<a-tag :color="getStatusColor(selectedTask.taskStatus)">
|
||||
{{ getStatusText(selectedTask.taskStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="负责人">
|
||||
{{ selectedTask.assignedUser?.real_name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">
|
||||
{{ selectedTask.creator?.real_name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ selectedTask.createdAt }}
|
||||
{{ selectedTask.createdAt || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">
|
||||
{{ selectedTask.updatedAt || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="完成时间">
|
||||
{{ selectedTask.completedAt }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="负责人">
|
||||
{{ selectedTask.assignee }}
|
||||
{{ selectedTask.completedAt || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理时长">
|
||||
{{ selectedTask.duration }} 天
|
||||
{{ calculateDuration(selectedTask) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务描述" :span="2">
|
||||
{{ selectedTask.description }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="完成备注" :span="2">
|
||||
{{ selectedTask.completionNotes }}
|
||||
<a-descriptions-item label="备注" :span="2">
|
||||
{{ selectedTask.notes || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
@@ -233,6 +255,7 @@ import {
|
||||
ExportOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { supervisionTaskApi } from '@/utils/api'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@@ -269,14 +292,14 @@ const pagination = reactive({
|
||||
const columns = [
|
||||
{
|
||||
title: '任务编号',
|
||||
dataIndex: 'taskCode',
|
||||
key: 'taskCode',
|
||||
dataIndex: 'applicationNumber',
|
||||
key: 'applicationNumber',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'taskName',
|
||||
key: 'taskName',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
@@ -287,27 +310,34 @@ const columns = [
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
dataIndex: 'taskStatus',
|
||||
key: 'taskStatus',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'assignee',
|
||||
key: 'assignee',
|
||||
width: 120
|
||||
width: 120,
|
||||
customRender: ({ record }) => record.assignedUser?.real_name || '-'
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: 'completedAt',
|
||||
key: 'completedAt',
|
||||
width: 150
|
||||
width: 150,
|
||||
customRender: ({ text }) => text || '-'
|
||||
},
|
||||
{
|
||||
title: '处理时长',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
width: 100
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
if (!record.completedAt || !record.createdAt) return '-'
|
||||
const created = new Date(record.createdAt)
|
||||
const completed = new Date(record.completedAt)
|
||||
const days = Math.ceil((completed - created) / (1000 * 60 * 60 * 24))
|
||||
return `${days} 天`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -320,17 +350,31 @@ const columns = [
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status) => {
|
||||
const colorMap = {
|
||||
completed: 'green',
|
||||
archived: 'purple'
|
||||
'待处理': 'orange',
|
||||
'处理中': 'blue',
|
||||
'已完成': 'green',
|
||||
'已取消': 'red',
|
||||
'已归档': 'purple',
|
||||
// 英文兼容
|
||||
'completed': 'green',
|
||||
'archived': 'purple'
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
// 如果已经是中文,直接返回
|
||||
if (['待处理', '处理中', '已完成', '已取消', '已归档'].includes(status)) {
|
||||
return status
|
||||
}
|
||||
// 英文转中文
|
||||
const textMap = {
|
||||
completed: '已完成',
|
||||
archived: '已归档'
|
||||
'pending': '待处理',
|
||||
'processing': '处理中',
|
||||
'completed': '已完成',
|
||||
'rejected': '已取消',
|
||||
'archived': '已归档'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
@@ -338,23 +382,48 @@ const getStatusText = (status) => {
|
||||
// 获取优先级颜色
|
||||
const getPriorityColor = (priority) => {
|
||||
const colorMap = {
|
||||
high: 'red',
|
||||
medium: 'orange',
|
||||
low: 'blue'
|
||||
'低': 'blue',
|
||||
'中': 'green',
|
||||
'高': 'orange',
|
||||
'紧急': 'red',
|
||||
// 英文兼容
|
||||
'low': 'blue',
|
||||
'medium': 'green',
|
||||
'high': 'orange',
|
||||
'urgent': 'red'
|
||||
}
|
||||
return colorMap[priority] || 'default'
|
||||
}
|
||||
|
||||
// 获取优先级文本
|
||||
const getPriorityText = (priority) => {
|
||||
// 如果已经是中文,直接返回
|
||||
if (['低', '中', '高', '紧急'].includes(priority)) {
|
||||
return priority
|
||||
}
|
||||
// 英文转中文
|
||||
const textMap = {
|
||||
high: '高',
|
||||
medium: '中',
|
||||
low: '低'
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'urgent': '紧急'
|
||||
}
|
||||
return textMap[priority] || priority
|
||||
}
|
||||
|
||||
// 计算处理时长
|
||||
const calculateDuration = (task) => {
|
||||
if (!task.completedAt || !task.createdAt) return '-'
|
||||
try {
|
||||
const created = new Date(task.createdAt)
|
||||
const completed = new Date(task.completedAt)
|
||||
const days = Math.ceil((completed - created) / (1000 * 60 * 60 * 24))
|
||||
return `${days} 天`
|
||||
} catch (error) {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
@@ -370,6 +439,42 @@ const handleReset = () => {
|
||||
fetchTaskList()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
taskName: searchForm.taskName || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await supervisionTaskApi.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `监管任务结项列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current
|
||||
@@ -384,76 +489,121 @@ const handleView = (record) => {
|
||||
}
|
||||
|
||||
// 下载报告
|
||||
const handleDownload = (record) => {
|
||||
message.info(`正在下载 ${record.taskName} 的报告...`)
|
||||
// 这里实现下载逻辑
|
||||
const handleDownload = async (record) => {
|
||||
try {
|
||||
message.loading(`正在下载 ${record.taskName} 的报告...`, 0)
|
||||
|
||||
const response = await supervisionTaskApi.downloadReport(record.id)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `${record.taskName}_报告_${new Date().getTime()}.pdf`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.destroy()
|
||||
message.success('报告下载成功')
|
||||
} catch (error) {
|
||||
message.destroy()
|
||||
console.error('下载报告失败:', error)
|
||||
message.error('下载报告失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 归档任务
|
||||
const handleArchive = (record) => {
|
||||
message.success(`任务 ${record.taskName} 已归档`)
|
||||
fetchTaskList()
|
||||
const handleArchive = async (record) => {
|
||||
try {
|
||||
const response = await supervisionTaskApi.archive(record.id)
|
||||
if (response.data && response.data.status === 'success') {
|
||||
message.success(`任务 ${record.taskName} 已归档`)
|
||||
fetchTaskList()
|
||||
} else {
|
||||
message.error(response.data?.message || '归档失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('归档任务失败:', error)
|
||||
message.error('归档任务失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 导出任务
|
||||
const handleExport = (record) => {
|
||||
message.info(`正在导出 ${record.taskName} 的数据...`)
|
||||
// 这里实现导出逻辑
|
||||
}
|
||||
// 导出单个任务(此功能已合并到handleExport)
|
||||
// const handleExportSingle = (record) => {
|
||||
// message.info(`正在导出 ${record.taskName} 的数据...`)
|
||||
// }
|
||||
|
||||
// 获取任务列表
|
||||
const fetchTaskList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
taskStatus: 'completed' // 只获取已完成的任务
|
||||
}
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
taskCode: 'RT001',
|
||||
taskName: '农场设备安全检查',
|
||||
priority: 'high',
|
||||
status: 'completed',
|
||||
assignee: '张三',
|
||||
createdAt: '2024-01-15',
|
||||
completedAt: '2024-01-20',
|
||||
duration: 5,
|
||||
description: '对农场所有设备进行安全检查,确保设备正常运行',
|
||||
completionNotes: '检查完成,发现3处需要维修的设备,已安排维修'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
taskCode: 'RT002',
|
||||
taskName: '环境监测数据审核',
|
||||
priority: 'medium',
|
||||
status: 'archived',
|
||||
assignee: '李四',
|
||||
createdAt: '2024-01-10',
|
||||
completedAt: '2024-01-18',
|
||||
duration: 8,
|
||||
description: '审核上月环境监测数据,确保数据准确性',
|
||||
completionNotes: '数据审核完成,所有数据符合标准'
|
||||
}
|
||||
]
|
||||
// 添加搜索条件
|
||||
if (searchForm.taskName) {
|
||||
params.taskName = searchForm.taskName
|
||||
}
|
||||
if (searchForm.status) {
|
||||
params.status = searchForm.status
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
params.startDate = searchForm.dateRange[0].format('YYYY-MM-DD')
|
||||
params.endDate = searchForm.dateRange[1].format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
taskList.value = mockData
|
||||
pagination.total = mockData.length
|
||||
const response = await supervisionTaskApi.getList(params)
|
||||
console.log('监管任务结项API响应:', response)
|
||||
|
||||
// 更新统计数据
|
||||
stats.total = 156
|
||||
stats.thisMonth = 23
|
||||
stats.archived = 89
|
||||
stats.avgDuration = 6.5
|
||||
if (response.data && response.data.status === 'success') {
|
||||
taskList.value = response.data.data.list || []
|
||||
pagination.total = response.data.data.total || 0
|
||||
console.log('监管任务结项数据设置成功:', taskList.value.length, '条')
|
||||
} else {
|
||||
console.log('监管任务结项响应格式错误:', response)
|
||||
message.error(response.data?.message || '获取任务列表失败')
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
await fetchStats()
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取任务列表失败:', error)
|
||||
message.error('获取任务列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await supervisionTaskApi.getStats()
|
||||
console.log('统计数据响应:', response)
|
||||
|
||||
if (response.data && response.data.status === 'success') {
|
||||
const statsData = response.data.data
|
||||
|
||||
// 从statusStats中获取已完成任务数
|
||||
const statusStats = statsData.statusStats || []
|
||||
const completedStat = statusStats.find(s => s.status === '已完成')
|
||||
stats.total = completedStat ? completedStat.count : 0
|
||||
|
||||
// 这些数据暂时从总数获取,后续可以从API补充
|
||||
stats.thisMonth = 0
|
||||
stats.archived = 0
|
||||
stats.avgDuration = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchTaskList()
|
||||
|
||||
@@ -110,11 +110,147 @@
|
||||
<div class="pagination-info">
|
||||
<span>共 {{ pagination.total }} 条记录</span>
|
||||
</div>
|
||||
|
||||
<!-- 查看/编辑安装任务弹窗 -->
|
||||
<a-modal
|
||||
:title="modalTitle"
|
||||
:open="modalVisible"
|
||||
:width="900"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
:confirmLoading="modalLoading"
|
||||
:footer="isViewMode ? null : undefined"
|
||||
>
|
||||
<a-form
|
||||
ref="modalFormRef"
|
||||
:model="modalForm"
|
||||
:rules="modalRules"
|
||||
layout="vertical"
|
||||
:disabled="isViewMode"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请单号" name="applicationNumber">
|
||||
<a-input v-model:value="modalForm.applicationNumber" placeholder="请输入申请单号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="保单编号" name="policyNumber">
|
||||
<a-input v-model:value="modalForm.policyNumber" placeholder="请输入保单编号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品名称" name="productName">
|
||||
<a-input v-model:value="modalForm.productName" placeholder="请输入产品名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户姓名" name="customerName">
|
||||
<a-input v-model:value="modalForm.customerName" placeholder="请输入客户姓名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件类型" name="idType">
|
||||
<a-select v-model:value="modalForm.idType" placeholder="请选择证件类型">
|
||||
<a-select-option value="身份证">身份证</a-select-option>
|
||||
<a-select-option value="护照">护照</a-select-option>
|
||||
<a-select-option value="军官证">军官证</a-select-option>
|
||||
<a-select-option value="其他">其他</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="证件号码" name="idNumber">
|
||||
<a-input v-model:value="modalForm.idNumber" placeholder="请输入证件号码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="养殖生资种类" name="livestockSupplyType">
|
||||
<a-input v-model:value="modalForm.livestockSupplyType" placeholder="请输入养殖生资种类" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="待安装设备" name="pendingDevices">
|
||||
<a-input v-model:value="modalForm.pendingDevices" placeholder="请输入待安装设备" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="安装状态" name="installationStatus">
|
||||
<a-select v-model:value="modalForm.installationStatus" placeholder="请选择安装状态">
|
||||
<a-select-option value="待安装">待安装</a-select-option>
|
||||
<a-select-option value="安装中">安装中</a-select-option>
|
||||
<a-select-option value="已安装">已安装</a-select-option>
|
||||
<a-select-option value="安装失败">安装失败</a-select-option>
|
||||
<a-select-option value="已取消">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="生成安装任务时间" name="taskGeneratedTime">
|
||||
<a-date-picker
|
||||
v-model:value="modalForm.taskGeneratedTime"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择生成任务时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="安装完成生效时间" name="installationCompletedTime">
|
||||
<a-date-picker
|
||||
v-model:value="modalForm.installationCompletedTime"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择完成时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="安装人员" name="installer">
|
||||
<a-input v-model:value="modalForm.installer" placeholder="请输入安装人员" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-textarea
|
||||
v-model:value="modalForm.remarks"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 查看模式下显示操作按钮 -->
|
||||
<div v-if="isViewMode" style="text-align: right; margin-top: 16px;">
|
||||
<a-space>
|
||||
<a-button @click="handleModalCancel">关闭</a-button>
|
||||
<a-button type="primary" @click="switchToEditMode">编辑</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -123,6 +259,11 @@ import installationTaskApi from '@/utils/installationTaskApi';
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const modalVisible = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
const modalFormRef = ref();
|
||||
const editingId = ref(null);
|
||||
const isViewMode = ref(false);
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
@@ -141,6 +282,48 @@ const pagination = reactive({
|
||||
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||
});
|
||||
|
||||
// 弹窗表单
|
||||
const modalForm = reactive({
|
||||
applicationNumber: '',
|
||||
policyNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
livestockSupplyType: '',
|
||||
pendingDevices: '',
|
||||
installationStatus: '',
|
||||
taskGeneratedTime: null,
|
||||
installationCompletedTime: null,
|
||||
installer: '',
|
||||
remarks: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const modalRules = {
|
||||
applicationNumber: [
|
||||
{ required: true, message: '请输入申请单号', trigger: 'blur' }
|
||||
],
|
||||
policyNumber: [
|
||||
{ required: true, message: '请输入保单编号', trigger: 'blur' }
|
||||
],
|
||||
productName: [
|
||||
{ required: true, message: '请输入产品名称', trigger: 'blur' }
|
||||
],
|
||||
customerName: [
|
||||
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||
],
|
||||
installationStatus: [
|
||||
{ required: true, message: '请选择安装状态', trigger: 'change' }
|
||||
]
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const modalTitle = computed(() => {
|
||||
if (isViewMode.value) return '查看安装任务';
|
||||
return editingId.value ? '编辑安装任务' : '新增安装任务';
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
@@ -279,12 +462,106 @@ const handleTableChange = (paginationInfo) => {
|
||||
|
||||
const handleView = (record) => {
|
||||
console.log('查看记录:', record);
|
||||
message.info('查看功能开发中');
|
||||
isViewMode.value = true;
|
||||
editingId.value = record.id;
|
||||
fillModalForm(record);
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleEdit = (record) => {
|
||||
console.log('编辑记录:', record);
|
||||
message.info('编辑功能开发中');
|
||||
isViewMode.value = false;
|
||||
editingId.value = record.id;
|
||||
fillModalForm(record);
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const fillModalForm = (record) => {
|
||||
Object.assign(modalForm, {
|
||||
applicationNumber: record.applicationNumber || '',
|
||||
policyNumber: record.policyNumber || '',
|
||||
productName: record.productName || '',
|
||||
customerName: record.customerName || '',
|
||||
idType: record.idType || '',
|
||||
idNumber: record.idNumber || '',
|
||||
livestockSupplyType: record.livestockSupplyType || '',
|
||||
pendingDevices: record.pendingDevices || '',
|
||||
installationStatus: record.installationStatus || '',
|
||||
taskGeneratedTime: record.taskGeneratedTime ? dayjs(record.taskGeneratedTime) : null,
|
||||
installationCompletedTime: record.installationCompletedTime ? dayjs(record.installationCompletedTime) : null,
|
||||
installer: record.installer || '',
|
||||
remarks: record.remarks || ''
|
||||
});
|
||||
};
|
||||
|
||||
const resetModalForm = () => {
|
||||
Object.assign(modalForm, {
|
||||
applicationNumber: '',
|
||||
policyNumber: '',
|
||||
productName: '',
|
||||
customerName: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
livestockSupplyType: '',
|
||||
pendingDevices: '',
|
||||
installationStatus: '',
|
||||
taskGeneratedTime: null,
|
||||
installationCompletedTime: null,
|
||||
installer: '',
|
||||
remarks: ''
|
||||
});
|
||||
};
|
||||
|
||||
const switchToEditMode = () => {
|
||||
isViewMode.value = false;
|
||||
};
|
||||
|
||||
const handleModalOk = async () => {
|
||||
if (isViewMode.value) {
|
||||
modalVisible.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await modalFormRef.value.validate();
|
||||
modalLoading.value = true;
|
||||
|
||||
const submitData = {
|
||||
...modalForm,
|
||||
taskGeneratedTime: modalForm.taskGeneratedTime ?
|
||||
dayjs(modalForm.taskGeneratedTime).format('YYYY-MM-DD HH:mm:ss') : null,
|
||||
installationCompletedTime: modalForm.installationCompletedTime ?
|
||||
dayjs(modalForm.installationCompletedTime).format('YYYY-MM-DD HH:mm:ss') : null
|
||||
};
|
||||
|
||||
const response = editingId.value
|
||||
? await installationTaskApi.updateInstallationTask(editingId.value, submitData)
|
||||
: await installationTaskApi.createInstallationTask(submitData);
|
||||
|
||||
if (response.code === 200 || response.code === 201) {
|
||||
message.success(editingId.value ? '更新成功' : '创建成功');
|
||||
modalVisible.value = false;
|
||||
fetchInstallationTasks();
|
||||
} else {
|
||||
message.error(response.message || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
if (error.errorFields) {
|
||||
message.error('请完善表单信息');
|
||||
} else {
|
||||
message.error('操作失败');
|
||||
}
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false;
|
||||
isViewMode.value = false;
|
||||
editingId.value = null;
|
||||
resetModalForm();
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
@@ -306,16 +583,15 @@ const handleExportTasks = async () => {
|
||||
try {
|
||||
const response = await installationTaskApi.exportInstallationTasks(searchForm);
|
||||
|
||||
if (response) {
|
||||
// 处理文件下载
|
||||
const blob = new Blob([response], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
if (response && response.data) {
|
||||
// 处理文件下载 - response.data是Blob对象
|
||||
const url = window.URL.createObjectURL(response.data);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `安装任务导出_${dayjs().format('YYYY-MM-DD_HH-mm-ss')}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
message.success('导出成功');
|
||||
@@ -324,7 +600,7 @@ const handleExportTasks = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
message.error('导出失败');
|
||||
message.error('导出失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -446,4 +722,22 @@ const getInstallationStatusColor = (status) => {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 查看模式下表单样式优化 */
|
||||
:deep(.ant-form-item-control-input-content) {
|
||||
.ant-input[disabled],
|
||||
.ant-select-disabled .ant-select-selector,
|
||||
.ant-picker-disabled,
|
||||
.ant-input-number-disabled {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background-color: #fafafa;
|
||||
border-color: #d9d9d9;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ant-input-disabled,
|
||||
.ant-select-disabled .ant-select-selection-item {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,10 +5,16 @@
|
||||
sub-title="管理系统支持的保险产品险种"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<PlusOutlined />
|
||||
新增险种
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<PlusOutlined />
|
||||
新增险种
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
@@ -326,7 +332,8 @@ import { insuranceTypeAPI } from '@/utils/api'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
RedoOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -1274,6 +1281,35 @@ const handleOnSaleStatusChange = (checked) => {
|
||||
console.log(`险种销售状态${statusText},时间:${new Date().toLocaleString()}`)
|
||||
}
|
||||
|
||||
// 导出Excel
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
...searchForm
|
||||
}
|
||||
|
||||
const response = await insuranceTypeAPI.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `保险类型列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件卸载状态跟踪
|
||||
const isComponentMounted = ref(true)
|
||||
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
<div class="livestock-policy-management">
|
||||
<div class="page-header">
|
||||
<h2>生资保单管理</h2>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<PlusOutlined />
|
||||
新建保单
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<PlusOutlined />
|
||||
新建保单
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
@@ -332,7 +338,8 @@ import dayjs from 'dayjs'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
DownOutlined
|
||||
DownOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { livestockPolicyApi, livestockTypeApi } from '@/utils/api'
|
||||
|
||||
@@ -769,6 +776,44 @@ const getCurrentPremiumRate = () => {
|
||||
return '请先选择牲畜类型'
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
policy_no: searchForm.policy_no || undefined,
|
||||
policyholder_name: searchForm.policyholder_name || undefined,
|
||||
policy_status: searchForm.policy_status || undefined,
|
||||
payment_status: searchForm.payment_status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await livestockPolicyApi.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `生资保单列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
|
||||
@@ -117,6 +117,10 @@
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="markAllAsRead" :disabled="!hasUnreadAlerts">
|
||||
<check-outlined />
|
||||
全部标记已读
|
||||
@@ -322,7 +326,8 @@ import {
|
||||
FireOutlined,
|
||||
CloudOutlined,
|
||||
WifiOutlined,
|
||||
ToolOutlined
|
||||
ToolOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { deviceAlertAPI } from '@/utils/api'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -605,6 +610,42 @@ const refreshData = async () => {
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
// 导出Excel
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
...filters
|
||||
}
|
||||
|
||||
// 移除undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await deviceAlertAPI.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `消息通知_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchStats()
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
sub-title="管理系统所有保单信息"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增保单
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增保单
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
@@ -322,7 +328,8 @@ import dayjs from 'dayjs'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
RedoOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { policyAPI } from '@/utils/api'
|
||||
|
||||
@@ -639,6 +646,44 @@ const handleDelete = async (id) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
policy_number: searchForm.policy_number || undefined,
|
||||
policyholder_name: searchForm.policyholder_name || undefined,
|
||||
insurance_type_id: searchForm.insurance_type_id || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await policyAPI.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `保单列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPolicies()
|
||||
})
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="action-buttons">
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<DownloadOutlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleCreateTask">
|
||||
新增监管任务
|
||||
</a-button>
|
||||
@@ -108,6 +112,69 @@
|
||||
<span>页共 0 条</span>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<a-modal
|
||||
title="查看监管任务详情"
|
||||
:open="viewModalVisible"
|
||||
:width="900"
|
||||
@cancel="handleViewModalCancel"
|
||||
:footer="null"
|
||||
>
|
||||
<a-descriptions bordered :column="2" size="middle">
|
||||
<a-descriptions-item label="申请单号">
|
||||
{{ viewRecord.applicationNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保单编号">
|
||||
{{ viewRecord.policyNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ viewRecord.productName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="保险期间">
|
||||
{{ viewRecord.insurancePeriod || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户姓名">
|
||||
{{ viewRecord.customerName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件类型">
|
||||
{{ viewRecord.idType || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="证件号码">
|
||||
{{ viewRecord.idNumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="适用金额">
|
||||
{{ viewRecord.applicableAmount ? formatAmount(viewRecord.applicableAmount) : '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="监管生资数量">
|
||||
{{ viewRecord.supervisorySuppliesQuantity || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务类型">
|
||||
<a-tag :color="getTaskTypeColor(viewRecord.taskType)">
|
||||
{{ getTaskTypeText(viewRecord.taskType) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务状态">
|
||||
<a-tag :color="getStatusColor(viewRecord.taskStatus)">
|
||||
{{ getStatusText(viewRecord.taskStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="优先级">
|
||||
<a-tag :color="getPriorityColor(viewRecord.priority)">
|
||||
{{ getPriorityText(viewRecord.priority) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" :span="2">
|
||||
{{ viewRecord.createdAt || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间" :span="2">
|
||||
{{ viewRecord.updatedAt || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">
|
||||
{{ viewRecord.remarks || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
|
||||
<!-- 创建/编辑监管任务弹窗 -->
|
||||
<a-modal
|
||||
:title="modalTitle"
|
||||
@@ -223,13 +290,14 @@
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import { supervisionTaskApi } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'SupervisionTaskManagement',
|
||||
components: {
|
||||
SearchOutlined
|
||||
SearchOutlined,
|
||||
DownloadOutlined
|
||||
},
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
@@ -239,6 +307,8 @@ export default {
|
||||
const modalLoading = ref(false)
|
||||
const modalFormRef = ref()
|
||||
const editingId = ref(null)
|
||||
const viewModalVisible = ref(false)
|
||||
const viewRecord = ref({})
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
@@ -493,7 +563,13 @@ export default {
|
||||
|
||||
const handleView = (record) => {
|
||||
console.log('查看详情:', record)
|
||||
message.info('查看功能开发中...')
|
||||
viewRecord.value = { ...record }
|
||||
viewModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleViewModalCancel = () => {
|
||||
viewModalVisible.value = false
|
||||
viewRecord.value = {}
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
@@ -564,6 +640,42 @@ export default {
|
||||
resetModalForm()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
policyNumber: searchForm.policyNumber || undefined,
|
||||
customerName: searchForm.customerName || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await supervisionTaskApi.export(params)
|
||||
|
||||
// 创建下载链接 - response.data已经是Blob对象,不需要再包装
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `监管任务列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
@@ -583,6 +695,8 @@ export default {
|
||||
modalFormRef,
|
||||
modalRules,
|
||||
modalTitle,
|
||||
viewModalVisible,
|
||||
viewRecord,
|
||||
getStatusColor,
|
||||
getStatusText,
|
||||
getTaskTypeColor,
|
||||
@@ -597,10 +711,12 @@ export default {
|
||||
handleTaskGuidance,
|
||||
handleBatchCreate,
|
||||
handleView,
|
||||
handleViewModalCancel,
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
handleModalOk,
|
||||
handleModalCancel
|
||||
handleModalCancel,
|
||||
handleExport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,13 +92,19 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchLogs">
|
||||
<SearchOutlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetLogFilters">
|
||||
重置
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExportLogs">
|
||||
<DownloadOutlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="searchLogs">
|
||||
<SearchOutlined />
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="resetLogFilters">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -170,7 +176,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { SearchOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
||||
import { operationLogAPI } from '@/utils/api'
|
||||
import dayjs from 'dayjs'
|
||||
import PermissionManagement from '@/components/PermissionManagement.vue'
|
||||
@@ -329,6 +335,48 @@ const resetLogFilters = () => {
|
||||
searchLogs()
|
||||
}
|
||||
|
||||
const handleExportLogs = async () => {
|
||||
try {
|
||||
logLoading.value = true
|
||||
const params = {
|
||||
username: logFilters.username || undefined,
|
||||
action: logFilters.action || undefined
|
||||
}
|
||||
|
||||
// 处理日期范围
|
||||
if (logFilters.dateRange && logFilters.dateRange.length === 2) {
|
||||
params.startDate = dayjs(logFilters.dateRange[0]).format('YYYY-MM-DD')
|
||||
params.endDate = dayjs(logFilters.dateRange[1]).format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await operationLogAPI.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `操作日志_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
logLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表格变化处理
|
||||
const handleLogTableChange = (pagination) => {
|
||||
logPagination.current = pagination.current
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
sub-title="管理系统用户和权限"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增用户
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button @click="handleExport">
|
||||
<download-outlined />
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showModal">
|
||||
<plus-outlined />
|
||||
新增用户
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
@@ -137,7 +143,8 @@ import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
RedoOutlined
|
||||
RedoOutlined,
|
||||
DownloadOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { userAPI } from '@/utils/api'
|
||||
|
||||
@@ -406,6 +413,42 @@ const handleDelete = async (id) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
search: searchForm.username || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
|
||||
// 过滤掉undefined值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await userAPI.export(params)
|
||||
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(response.data)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `用户列表_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
message.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
|
||||
@@ -18,10 +18,10 @@ export default defineConfig(({ mode }) => {
|
||||
port: parseInt(env.VITE_PORT) || 3001,
|
||||
historyApiFallback: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: env.VITE_API_BASE_URL || 'https://ad.ningmuyun.com/insurace/',
|
||||
'/insurance/api': {
|
||||
target: env.VITE_API_BASE_URL || 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path
|
||||
rewrite: (path) => path.replace(/^\/insurance/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { DeviceAlert, Device, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
/**
|
||||
* 设备预警控制器
|
||||
@@ -416,6 +417,103 @@ class DeviceAlertController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出设备预警列表到Excel
|
||||
*/
|
||||
static async exportToExcel(req, res) {
|
||||
try {
|
||||
const { alert_level, alert_type, status, is_read } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
if (alert_level) where.alert_level = alert_level;
|
||||
if (alert_type) where.alert_type = alert_type;
|
||||
if (status) where.status = status;
|
||||
if (is_read !== undefined) where.is_read = is_read === 'true';
|
||||
|
||||
// 查询所有符合条件的数据
|
||||
const alerts = await DeviceAlert.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: Device,
|
||||
as: 'Device',
|
||||
attributes: ['device_name', 'device_number', 'installation_location']
|
||||
}],
|
||||
order: [['alert_time', 'DESC']],
|
||||
raw: true,
|
||||
nest: true
|
||||
});
|
||||
|
||||
// 状态映射
|
||||
const levelMap = {
|
||||
info: '信息',
|
||||
warning: '警告',
|
||||
critical: '严重'
|
||||
};
|
||||
|
||||
const typeMap = {
|
||||
temperature: '温度异常',
|
||||
humidity: '湿度异常',
|
||||
offline: '设备离线',
|
||||
maintenance: '设备维护'
|
||||
};
|
||||
|
||||
const statusMap = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
resolved: '已解决'
|
||||
};
|
||||
|
||||
// 准备导出数据
|
||||
const exportData = alerts.map(alert => ({
|
||||
alert_title: alert.alert_title || '',
|
||||
alert_level: ExcelExport.formatStatus(alert.alert_level, levelMap),
|
||||
alert_type: ExcelExport.formatStatus(alert.alert_type, typeMap),
|
||||
alert_content: alert.alert_content || '',
|
||||
device_name: alert.Device?.device_name || '',
|
||||
device_number: alert.Device?.device_number || '',
|
||||
installation_location: alert.Device?.installation_location || '',
|
||||
status: ExcelExport.formatStatus(alert.status, statusMap),
|
||||
is_read: alert.is_read ? '已读' : '未读',
|
||||
alert_time: ExcelExport.formatDate(alert.alert_time),
|
||||
read_time: ExcelExport.formatDate(alert.read_time)
|
||||
}));
|
||||
|
||||
// 定义列
|
||||
const columns = [
|
||||
{ header: '预警标题', key: 'alert_title', width: 30 },
|
||||
{ header: '预警级别', key: 'alert_level', width: 12 },
|
||||
{ header: '预警类型', key: 'alert_type', width: 15 },
|
||||
{ header: '预警内容', key: 'alert_content', width: 40 },
|
||||
{ header: '设备名称', key: 'device_name', width: 20 },
|
||||
{ header: '设备编号', key: 'device_number', width: 20 },
|
||||
{ header: '安装位置', key: 'installation_location', width: 30 },
|
||||
{ header: '处理状态', key: 'status', width: 12 },
|
||||
{ header: '阅读状态', key: 'is_read', width: 10 },
|
||||
{ header: '预警时间', key: 'alert_time', width: 20 },
|
||||
{ header: '阅读时间', key: 'read_time', width: 20 }
|
||||
];
|
||||
|
||||
// 生成Excel
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '设备预警列表');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=device_alerts_${Date.now()}.xlsx`);
|
||||
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('导出设备预警失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeviceAlertController;
|
||||
@@ -2,6 +2,7 @@ const InstallationTask = require('../models/InstallationTask');
|
||||
const User = require('../models/User');
|
||||
const { Op, sequelize } = require('sequelize');
|
||||
const logger = require('../utils/logger');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
class InstallationTaskController {
|
||||
|
||||
@@ -472,6 +473,99 @@ class InstallationTaskController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导出待安装任务列表到Excel
|
||||
async exportToExcel(req, res) {
|
||||
try {
|
||||
const { policyNumber, keyword } = req.query;
|
||||
|
||||
// 构建查询条件(使用数据库字段名,因为raw:true模式)
|
||||
const whereConditions = {};
|
||||
|
||||
if (policyNumber) {
|
||||
whereConditions.policy_number = { [Op.like]: `%${policyNumber}%` };
|
||||
}
|
||||
|
||||
// 关键字搜索
|
||||
if (keyword) {
|
||||
whereConditions[Op.or] = [
|
||||
{ application_number: { [Op.like]: `%${keyword}%` } },
|
||||
{ policy_number: { [Op.like]: `%${keyword}%` } },
|
||||
{ customer_name: { [Op.like]: `%${keyword}%` } },
|
||||
{ installation_address: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 查询所有符合条件的数据
|
||||
const tasks = await InstallationTask.findAll({
|
||||
where: whereConditions,
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`导出查询到 ${tasks.length} 条数据`);
|
||||
|
||||
// 准备导出数据(Sequelize自动转换为驼峰格式)
|
||||
const exportData = tasks.map(task => {
|
||||
const data = task.toJSON ? task.toJSON() : task;
|
||||
return {
|
||||
applicationNumber: data.applicationNumber || '',
|
||||
policyNumber: data.policyNumber || '',
|
||||
productName: data.productName || '',
|
||||
customerName: data.customerName || '',
|
||||
idType: data.idType || '',
|
||||
idNumber: data.idNumber || '',
|
||||
livestockSupplyType: data.livestockSupplyType || '',
|
||||
installationStatus: data.installationStatus || '',
|
||||
priority: data.priority || '',
|
||||
installationAddress: data.installationAddress || '',
|
||||
contactPhone: data.contactPhone || '',
|
||||
taskGeneratedTime: ExcelExport.formatDate(data.taskGeneratedTime),
|
||||
installationCompletedTime: ExcelExport.formatDate(data.installationCompletedTime),
|
||||
createdAt: ExcelExport.formatDate(data.created_at || data.createdAt),
|
||||
updatedAt: ExcelExport.formatDate(data.updated_at || data.updatedAt)
|
||||
};
|
||||
});
|
||||
|
||||
// 定义列
|
||||
const columns = [
|
||||
{ header: '申请单号', key: 'applicationNumber', width: 20 },
|
||||
{ header: '保单编号', key: 'policyNumber', width: 20 },
|
||||
{ header: '产品名称', key: 'productName', width: 25 },
|
||||
{ header: '客户姓名', key: 'customerName', width: 15 },
|
||||
{ header: '证件类型', key: 'idType', width: 12 },
|
||||
{ header: '证件号码', key: 'idNumber', width: 20 },
|
||||
{ header: '养殖生资种类', key: 'livestockSupplyType', width: 20 },
|
||||
{ header: '安装状态', key: 'installationStatus', width: 12 },
|
||||
{ header: '优先级', key: 'priority', width: 10 },
|
||||
{ header: '安装地址', key: 'installationAddress', width: 30 },
|
||||
{ header: '联系电话', key: 'contactPhone', width: 15 },
|
||||
{ header: '任务生成时间', key: 'taskGeneratedTime', width: 20 },
|
||||
{ header: '安装完成时间', key: 'installationCompletedTime', width: 20 },
|
||||
{ header: '创建时间', key: 'createdAt', width: 20 },
|
||||
{ header: '更新时间', key: 'updatedAt', width: 20 }
|
||||
];
|
||||
|
||||
// 生成Excel
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '待安装任务列表');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=installation_tasks_${Date.now()}.xlsx`);
|
||||
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出待安装任务失败:', error);
|
||||
logger.error('导出待安装任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new InstallationTaskController();
|
||||
@@ -315,9 +315,10 @@ const getInsuranceCategories = async (req, res) => {
|
||||
// 导出保险申请数据
|
||||
const exportApplications = async (req, res) => {
|
||||
try {
|
||||
const ExcelJS = require('exceljs');
|
||||
const {
|
||||
page = 1,
|
||||
limit = 1000,
|
||||
limit = 10000, // 增加导出数量限制
|
||||
applicantName,
|
||||
insuranceType,
|
||||
insuranceCategory,
|
||||
@@ -344,13 +345,15 @@ const exportApplications = async (req, res) => {
|
||||
include: [
|
||||
{
|
||||
model: InsuranceType,
|
||||
as: 'insuranceTypeInfo',
|
||||
attributes: ['name', 'description']
|
||||
as: 'insurance_type',
|
||||
attributes: ['id', 'name', 'description'],
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'createdByUser',
|
||||
attributes: ['username', 'real_name']
|
||||
as: 'reviewer',
|
||||
attributes: ['id', 'username', 'real_name'],
|
||||
required: false
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
@@ -358,38 +361,101 @@ const exportApplications = async (req, res) => {
|
||||
offset: (parseInt(page) - 1) * parseInt(limit)
|
||||
});
|
||||
|
||||
// 简单的CSV格式导出
|
||||
const csvHeader = '申请编号,申请人姓名,身份证号,联系电话,参保类型,保险类型,保险金额,保险期限,地址,状态,申请时间,备注\n';
|
||||
const csvData = applications.map(app => {
|
||||
const statusMap = {
|
||||
'pending': '待审核',
|
||||
'initial_approved': '初审通过',
|
||||
'under_review': '复审中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝'
|
||||
};
|
||||
|
||||
return [
|
||||
app.application_number || '',
|
||||
app.applicant_name || '',
|
||||
app.id_card || '',
|
||||
app.phone || '',
|
||||
app.insurance_category || '',
|
||||
app.insurance_type || '',
|
||||
app.insurance_amount || '',
|
||||
app.insurance_period || '',
|
||||
app.address || '',
|
||||
statusMap[app.status] || app.status,
|
||||
app.created_at ? new Date(app.created_at).toLocaleString('zh-CN') : '',
|
||||
app.remarks || ''
|
||||
].map(field => `"${String(field).replace(/"/g, '""')}"`).join(',');
|
||||
}).join('\n');
|
||||
// 将Sequelize实例转换为纯对象
|
||||
const plainApplications = applications.map(app => app.toJSON());
|
||||
|
||||
const csvContent = csvHeader + csvData;
|
||||
// 创建Excel工作簿
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('保险申请列表');
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
'pending': '待审核',
|
||||
'initial_approved': '初审通过',
|
||||
'under_review': '复审中',
|
||||
'approved': '已通过',
|
||||
'rejected': '已拒绝'
|
||||
};
|
||||
|
||||
// 定义列(包含所有字段)
|
||||
worksheet.columns = [
|
||||
{ header: 'ID', key: 'id', width: 8 },
|
||||
{ header: '申请编号', key: 'application_no', width: 18 },
|
||||
{ header: '客户姓名', key: 'customer_name', width: 12 },
|
||||
{ header: '身份证号', key: 'customer_id_card', width: 20 },
|
||||
{ header: '联系电话', key: 'customer_phone', width: 15 },
|
||||
{ header: '客户地址', key: 'customer_address', width: 30 },
|
||||
{ header: '参保类型', key: 'insurance_category', width: 12 },
|
||||
{ header: '保险类型ID', key: 'insurance_type_id', width: 12 },
|
||||
{ header: '保险类型名称', key: 'insurance_type_name', width: 15 },
|
||||
{ header: '保险类型说明', key: 'insurance_type_description', width: 25 },
|
||||
{ header: '申请数量', key: 'application_quantity', width: 12 },
|
||||
{ header: '申请金额', key: 'application_amount', width: 15 },
|
||||
{ header: '申请日期', key: 'application_date', width: 20 },
|
||||
{ header: '状态', key: 'status', width: 12 },
|
||||
{ header: '审核人ID', key: 'reviewer_id', width: 10 },
|
||||
{ header: '审核人姓名', key: 'reviewer_name', width: 12 },
|
||||
{ header: '审核日期', key: 'review_date', width: 20 },
|
||||
{ header: '审核意见', key: 'review_notes', width: 30 },
|
||||
{ header: '文档附件', key: 'documents', width: 30 },
|
||||
{ header: '备注', key: 'remarks', width: 30 },
|
||||
{ header: '创建时间', key: 'createdAt', width: 20 },
|
||||
{ header: '更新时间', key: 'updatedAt', width: 20 }
|
||||
];
|
||||
|
||||
// 设置表头样式
|
||||
worksheet.getRow(1).font = { bold: true, size: 12 };
|
||||
worksheet.getRow(1).fill = {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFE0E0E0' }
|
||||
};
|
||||
worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
|
||||
// 添加数据行
|
||||
plainApplications.forEach(app => {
|
||||
worksheet.addRow({
|
||||
id: app.id || '',
|
||||
application_no: app.application_no || '',
|
||||
customer_name: app.customer_name || '',
|
||||
customer_id_card: app.customer_id_card || '',
|
||||
customer_phone: app.customer_phone || '',
|
||||
customer_address: app.customer_address || '',
|
||||
insurance_category: app.insurance_category || '',
|
||||
insurance_type_id: app.insurance_type_id || '',
|
||||
insurance_type_name: app.insurance_type?.name || '',
|
||||
insurance_type_description: app.insurance_type?.description || '',
|
||||
application_quantity: app.application_quantity || '',
|
||||
application_amount: app.application_amount || '',
|
||||
application_date: app.application_date || '',
|
||||
status: statusMap[app.status] || app.status || '',
|
||||
reviewer_id: app.reviewer_id || '',
|
||||
reviewer_name: app.reviewer?.real_name || '',
|
||||
review_date: app.review_date || '',
|
||||
review_notes: app.review_notes || '',
|
||||
documents: app.documents || '',
|
||||
remarks: app.remarks || '',
|
||||
createdAt: app.createdAt || '',
|
||||
updatedAt: app.updatedAt || ''
|
||||
});
|
||||
});
|
||||
|
||||
// 设置数据行样式
|
||||
worksheet.eachRow((row, rowNumber) => {
|
||||
if (rowNumber > 1) {
|
||||
row.alignment = { vertical: 'middle', horizontal: 'left' };
|
||||
}
|
||||
});
|
||||
|
||||
// 生成Excel文件
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="insurance_applications_${new Date().toISOString().slice(0, 10)}.xlsx"`);
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="insurance_applications_${new Date().toISOString().slice(0, 10)}.csv"`);
|
||||
res.send('\uFEFF' + csvContent); // 添加BOM以支持中文
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('导出保险申请数据错误:', error);
|
||||
res.status(500).json(responseFormat.error('导出保险申请数据失败'));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { InsuranceType } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const responseFormat = require('../utils/response');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
// 获取保险类型列表
|
||||
const getInsuranceTypes = async (req, res) => {
|
||||
@@ -457,11 +458,59 @@ const updateInsuranceTypeStatus = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出保险类型列表到Excel
|
||||
const exportToExcel = async (req, res) => {
|
||||
try {
|
||||
const { name, status } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (name) where.name = { [Op.like]: `%${name}%` };
|
||||
if (status) where.status = status;
|
||||
|
||||
const types = await InsuranceType.findAll({
|
||||
where,
|
||||
order: [['created_at', 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const statusMap = { active: '启用', inactive: '停用' };
|
||||
|
||||
const exportData = types.map(type => ({
|
||||
name: type.name || '',
|
||||
coverage_amount: type.coverage_amount || 0,
|
||||
premium_rate: type.premium_rate || 0,
|
||||
applicable_livestock: type.applicable_livestock || '',
|
||||
description: type.description || '',
|
||||
status: ExcelExport.formatStatus(type.status, statusMap),
|
||||
created_at: ExcelExport.formatDate(type.created_at)
|
||||
}));
|
||||
|
||||
const columns = [
|
||||
{ header: '险种名称', key: 'name', width: 20 },
|
||||
{ header: '保额', key: 'coverage_amount', width: 15 },
|
||||
{ header: '费率', key: 'premium_rate', width: 10 },
|
||||
{ header: '适用牲畜', key: 'applicable_livestock', width: 20 },
|
||||
{ header: '描述', key: 'description', width: 40 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 }
|
||||
];
|
||||
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '保险类型列表');
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=insurance_types_${Date.now()}.xlsx`);
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('导出保险类型失败:', error);
|
||||
res.status(500).json(responseFormat.error('导出失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getInsuranceTypes,
|
||||
getInsuranceTypeById,
|
||||
createInsuranceType,
|
||||
updateInsuranceType,
|
||||
deleteInsuranceType,
|
||||
updateInsuranceTypeStatus
|
||||
updateInsuranceTypeStatus,
|
||||
exportToExcel
|
||||
};
|
||||
@@ -4,6 +4,7 @@ const LivestockType = require('../models/LivestockType');
|
||||
const User = require('../models/User');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
// 获取生资理赔列表
|
||||
const getLivestockClaims = async (req, res) => {
|
||||
@@ -540,6 +541,151 @@ const deleteLivestockClaim = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出理赔列表到Excel
|
||||
const exportToExcel = async (req, res) => {
|
||||
try {
|
||||
const ExcelJS = require('exceljs');
|
||||
const { claim_no, reporter_name, claim_status } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (claim_no) where.claim_no = { [Op.like]: `%${claim_no}%` };
|
||||
if (reporter_name) where.reporter_name = { [Op.like]: `%${reporter_name}%` };
|
||||
if (claim_status) where.claim_status = claim_status;
|
||||
|
||||
const claims = await LivestockClaim.findAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: LivestockPolicy,
|
||||
as: 'policy',
|
||||
attributes: ['id', 'policy_no', 'policyholder_name'],
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'reviewer',
|
||||
attributes: ['id', 'real_name', 'username'],
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'real_name', 'username'],
|
||||
required: false
|
||||
}
|
||||
],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: 10000
|
||||
});
|
||||
|
||||
// 将Sequelize实例转换为纯对象
|
||||
const plainClaims = claims.map(claim => claim.toJSON());
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
'pending': '待审核',
|
||||
'investigating': '调查中',
|
||||
'approved': '已批准',
|
||||
'rejected': '已拒绝',
|
||||
'paid': '已支付'
|
||||
};
|
||||
|
||||
// 理赔类型映射
|
||||
const claimTypeMap = {
|
||||
'death': '死亡',
|
||||
'disease': '疾病',
|
||||
'accident': '意外事故',
|
||||
'natural_disaster': '自然灾害'
|
||||
};
|
||||
|
||||
// 创建Excel工作簿
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('生资理赔列表');
|
||||
|
||||
// 定义列(包含所有字段)
|
||||
worksheet.columns = [
|
||||
{ header: 'ID', key: 'id', width: 8 },
|
||||
{ header: '理赔编号', key: 'claim_no', width: 18 },
|
||||
{ header: '报案人姓名', key: 'reporter_name', width: 12 },
|
||||
{ header: '联系电话', key: 'contact_phone', width: 15 },
|
||||
{ header: '保单号', key: 'policy_no', width: 18 },
|
||||
{ header: '保单持有人', key: 'policy_holder', width: 12 },
|
||||
{ header: '理赔类型', key: 'claim_type', width: 12 },
|
||||
{ header: '受影响数量', key: 'affected_count', width: 12 },
|
||||
{ header: '理赔金额', key: 'claim_amount', width: 15 },
|
||||
{ header: '事故日期', key: 'incident_date', width: 20 },
|
||||
{ header: '报案日期', key: 'report_date', width: 20 },
|
||||
{ header: '事故描述', key: 'incident_description', width: 35 },
|
||||
{ header: '事故地点', key: 'incident_location', width: 25 },
|
||||
{ header: '理赔状态', key: 'claim_status', width: 12 },
|
||||
{ header: '调查报告', key: 'investigation_report', width: 30 },
|
||||
{ header: '审核人', key: 'reviewer_name', width: 12 },
|
||||
{ header: '审核备注', key: 'review_notes', width: 30 },
|
||||
{ header: '审核日期', key: 'review_date', width: 20 },
|
||||
{ header: '赔付日期', key: 'payment_date', width: 20 },
|
||||
{ header: '赔付金额', key: 'payment_amount', width: 15 },
|
||||
{ header: '创建人', key: 'creator_name', width: 12 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 },
|
||||
{ header: '更新时间', key: 'updated_at', width: 20 }
|
||||
];
|
||||
|
||||
// 设置表头样式
|
||||
worksheet.getRow(1).font = { bold: true, size: 12 };
|
||||
worksheet.getRow(1).fill = {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFE0E0E0' }
|
||||
};
|
||||
worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
|
||||
// 添加数据行
|
||||
plainClaims.forEach(claim => {
|
||||
worksheet.addRow({
|
||||
id: claim.id || '',
|
||||
claim_no: claim.claim_no || '',
|
||||
reporter_name: claim.reporter_name || '',
|
||||
contact_phone: claim.contact_phone || '',
|
||||
policy_no: claim.policy_no || '',
|
||||
policy_holder: claim.policy?.policyholder_name || '',
|
||||
claim_type: claimTypeMap[claim.claim_type] || claim.claim_type || '',
|
||||
affected_count: claim.affected_count || '',
|
||||
claim_amount: claim.claim_amount || '',
|
||||
incident_date: claim.incident_date || '',
|
||||
report_date: claim.report_date || '',
|
||||
incident_description: claim.incident_description || '',
|
||||
incident_location: claim.incident_location || '',
|
||||
claim_status: statusMap[claim.claim_status] || claim.claim_status || '',
|
||||
investigation_report: claim.investigation_report || '',
|
||||
reviewer_name: claim.reviewer?.real_name || '',
|
||||
review_notes: claim.review_notes || '',
|
||||
review_date: claim.review_date || '',
|
||||
payment_date: claim.payment_date || '',
|
||||
payment_amount: claim.payment_amount || '',
|
||||
creator_name: claim.creator?.real_name || '',
|
||||
created_at: claim.created_at || '',
|
||||
updated_at: claim.updated_at || ''
|
||||
});
|
||||
});
|
||||
|
||||
// 设置数据行样式
|
||||
worksheet.eachRow((row, rowNumber) => {
|
||||
if (rowNumber > 1) {
|
||||
row.alignment = { vertical: 'middle', horizontal: 'left' };
|
||||
}
|
||||
});
|
||||
|
||||
// 生成Excel文件
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=livestock_claims_${Date.now()}.xlsx`);
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('导出生资理赔记录失败:', error);
|
||||
res.status(500).json(responseFormat.error('导出失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getLivestockClaims,
|
||||
createLivestockClaim,
|
||||
@@ -548,5 +694,6 @@ module.exports = {
|
||||
deleteLivestockClaim,
|
||||
reviewLivestockClaim,
|
||||
updateLivestockClaimPayment,
|
||||
getLivestockClaimStats
|
||||
getLivestockClaimStats,
|
||||
exportToExcel
|
||||
};
|
||||
@@ -3,6 +3,7 @@ const LivestockType = require('../models/LivestockType');
|
||||
const User = require('../models/User');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
// 获取生资保单列表
|
||||
const getLivestockPolicies = async (req, res) => {
|
||||
@@ -337,6 +338,69 @@ const deleteLivestockPolicy = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出生资保单列表到Excel
|
||||
const exportToExcel = async (req, res) => {
|
||||
try {
|
||||
const { policy_no, farmer_name, policy_status } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (policy_no) where.policy_no = { [Op.like]: `%${policy_no}%` };
|
||||
if (farmer_name) where.farmer_name = { [Op.like]: `%${farmer_name}%` };
|
||||
if (policy_status) where.policy_status = policy_status;
|
||||
|
||||
const policies = await LivestockPolicy.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: LivestockType,
|
||||
as: 'livestockType',
|
||||
attributes: ['name']
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
raw: true,
|
||||
nest: true
|
||||
});
|
||||
|
||||
const statusMap = { active: '生效中', pending: '待生效', expired: '已过期', cancelled: '已取消' };
|
||||
const paymentMap = { unpaid: '未支付', paid: '已支付', partial: '部分支付' };
|
||||
|
||||
const exportData = policies.map(policy => ({
|
||||
policy_no: policy.policy_no || '',
|
||||
farmer_name: policy.farmer_name || '',
|
||||
farmer_phone: policy.farmer_phone || '',
|
||||
livestock_type: policy.livestockType?.name || '',
|
||||
insured_amount: policy.insured_amount || 0,
|
||||
premium_amount: policy.premium_amount || 0,
|
||||
policy_status: ExcelExport.formatStatus(policy.policy_status, statusMap),
|
||||
payment_status: ExcelExport.formatStatus(policy.payment_status, paymentMap),
|
||||
start_date: ExcelExport.formatDate(policy.start_date),
|
||||
end_date: ExcelExport.formatDate(policy.end_date),
|
||||
created_at: ExcelExport.formatDate(policy.created_at)
|
||||
}));
|
||||
|
||||
const columns = [
|
||||
{ header: '保单号', key: 'policy_no', width: 20 },
|
||||
{ header: '农户姓名', key: 'farmer_name', width: 15 },
|
||||
{ header: '联系电话', key: 'farmer_phone', width: 15 },
|
||||
{ header: '牲畜类型', key: 'livestock_type', width: 15 },
|
||||
{ header: '保额', key: 'insured_amount', width: 15 },
|
||||
{ header: '保费', key: 'premium_amount', width: 15 },
|
||||
{ header: '保单状态', key: 'policy_status', width: 12 },
|
||||
{ header: '支付状态', key: 'payment_status', width: 12 },
|
||||
{ header: '起始日期', key: 'start_date', width: 15 },
|
||||
{ header: '结束日期', key: 'end_date', width: 15 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 }
|
||||
];
|
||||
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '生资保单列表');
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=livestock_policies_${Date.now()}.xlsx`);
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('导出生资保单失败:', error);
|
||||
res.status(500).json(responseFormat.error('导出失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getLivestockPolicies,
|
||||
createLivestockPolicy,
|
||||
@@ -344,5 +408,6 @@ module.exports = {
|
||||
updateLivestockPolicy,
|
||||
updateLivestockPolicyStatus,
|
||||
deleteLivestockPolicy,
|
||||
getLivestockPolicyStats
|
||||
getLivestockPolicyStats,
|
||||
exportToExcel
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
const { OperationLog, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelJS = require('exceljs');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
/**
|
||||
* 操作日志控制器
|
||||
@@ -339,6 +340,99 @@ class OperationLogController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出操作日志列表到Excel
|
||||
*/
|
||||
async exportToExcel(req, res) {
|
||||
try {
|
||||
const { username, operation_type, operation_module, startDate, endDate } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
if (username) {
|
||||
where['$User.username$'] = { [Op.like]: `%${username}%` };
|
||||
}
|
||||
if (operation_type) where.operation_type = operation_type;
|
||||
if (operation_module) where.operation_module = operation_module;
|
||||
if (startDate && endDate) {
|
||||
where.created_at = {
|
||||
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||||
};
|
||||
}
|
||||
|
||||
// 查询所有符合条件的数据
|
||||
const logs = await OperationLog.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'User',
|
||||
attributes: ['username', 'real_name']
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
raw: true,
|
||||
nest: true
|
||||
});
|
||||
|
||||
// 操作类型映射
|
||||
const typeMap = {
|
||||
login: '登录',
|
||||
logout: '登出',
|
||||
create: '创建',
|
||||
update: '更新',
|
||||
delete: '删除',
|
||||
view: '查看',
|
||||
export: '导出'
|
||||
};
|
||||
|
||||
const statusMap = {
|
||||
success: '成功',
|
||||
failure: '失败'
|
||||
};
|
||||
|
||||
// 准备导出数据
|
||||
const exportData = logs.map(log => ({
|
||||
username: log.User?.username || '',
|
||||
real_name: log.User?.real_name || '',
|
||||
operation_type: ExcelExport.formatStatus(log.operation_type, typeMap),
|
||||
operation_module: log.operation_module || '',
|
||||
operation_desc: log.operation_desc || '',
|
||||
ip_address: log.ip_address || '',
|
||||
status: ExcelExport.formatStatus(log.status, statusMap),
|
||||
created_at: ExcelExport.formatDate(log.created_at)
|
||||
}));
|
||||
|
||||
// 定义列
|
||||
const columns = [
|
||||
{ header: '用户名', key: 'username', width: 15 },
|
||||
{ header: '真实姓名', key: 'real_name', width: 15 },
|
||||
{ header: '操作类型', key: 'operation_type', width: 12 },
|
||||
{ header: '操作模块', key: 'operation_module', width: 20 },
|
||||
{ header: '操作描述', key: 'operation_desc', width: 40 },
|
||||
{ header: 'IP地址', key: 'ip_address', width: 20 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '操作时间', key: 'created_at', width: 20 }
|
||||
];
|
||||
|
||||
// 生成Excel
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '操作日志');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=operation_logs_${Date.now()}.xlsx`);
|
||||
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出操作日志失败:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: '导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new OperationLogController();
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Policy, InsuranceApplication, InsuranceType, User } = require('../models');
|
||||
const responseFormat = require('../utils/response');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
// 获取保单列表
|
||||
const getPolicies = async (req, res) => {
|
||||
@@ -363,6 +364,118 @@ const deletePolicy = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出保单列表到Excel
|
||||
const exportToExcel = async (req, res) => {
|
||||
try {
|
||||
const ExcelJS = require('exceljs');
|
||||
const { policy_number, policyholder_name, status } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (policy_number) where.policy_number = { [Op.like]: `%${policy_number}%` };
|
||||
if (policyholder_name) where.policyholder_name = { [Op.like]: `%${policyholder_name}%` };
|
||||
if (status) where.status = status;
|
||||
|
||||
const policies = await Policy.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: InsuranceType,
|
||||
as: 'insurance_type', // 修正:使用正确的关联别名
|
||||
attributes: ['id', 'name', 'description'],
|
||||
required: false
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: 10000
|
||||
});
|
||||
|
||||
// 将Sequelize实例转换为纯对象
|
||||
const plainPolicies = policies.map(policy => policy.toJSON());
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
'active': '生效中',
|
||||
'pending': '待生效',
|
||||
'expired': '已过期',
|
||||
'cancelled': '已取消'
|
||||
};
|
||||
|
||||
// 创建Excel工作簿
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('保单列表');
|
||||
|
||||
// 定义列(包含所有字段)
|
||||
worksheet.columns = [
|
||||
{ header: 'ID', key: 'id', width: 8 },
|
||||
{ header: '保单编号', key: 'policy_number', width: 18 },
|
||||
{ header: '投保人姓名', key: 'policyholder_name', width: 15 },
|
||||
{ header: '被保人姓名', key: 'insured_name', width: 15 },
|
||||
{ header: '保险类型ID', key: 'insurance_type_id', width: 12 },
|
||||
{ header: '保险类型名称', key: 'insurance_type_name', width: 18 },
|
||||
{ header: '保险类型说明', key: 'insurance_type_description', width: 25 },
|
||||
{ header: '保障金额', key: 'coverage_amount', width: 15 },
|
||||
{ header: '保费金额', key: 'premium_amount', width: 15 },
|
||||
{ header: '开始日期', key: 'start_date', width: 20 },
|
||||
{ header: '结束日期', key: 'end_date', width: 20 },
|
||||
{ header: '状态', key: 'status', width: 12 },
|
||||
{ header: '联系电话', key: 'phone', width: 15 },
|
||||
{ header: '电子邮箱', key: 'email', width: 25 },
|
||||
{ header: '地址', key: 'address', width: 30 },
|
||||
{ header: '备注', key: 'remarks', width: 30 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 },
|
||||
{ header: '更新时间', key: 'updated_at', width: 20 }
|
||||
];
|
||||
|
||||
// 设置表头样式
|
||||
worksheet.getRow(1).font = { bold: true, size: 12 };
|
||||
worksheet.getRow(1).fill = {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFE0E0E0' }
|
||||
};
|
||||
worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
|
||||
// 添加数据行
|
||||
plainPolicies.forEach(policy => {
|
||||
worksheet.addRow({
|
||||
id: policy.id || '',
|
||||
policy_number: policy.policy_no || '', // 修正:使用 policy_no
|
||||
policyholder_name: policy.policyholder_name || '',
|
||||
insured_name: policy.insured_name || '',
|
||||
insurance_type_id: policy.insurance_type_id || '',
|
||||
insurance_type_name: policy.insurance_type?.name || '',
|
||||
insurance_type_description: policy.insurance_type?.description || '',
|
||||
coverage_amount: policy.coverage_amount || '',
|
||||
premium_amount: policy.premium_amount || '',
|
||||
start_date: policy.start_date || '',
|
||||
end_date: policy.end_date || '',
|
||||
status: statusMap[policy.policy_status] || policy.policy_status || '', // 修正:使用 policy_status
|
||||
phone: policy.phone || '',
|
||||
email: policy.email || '',
|
||||
address: policy.address || '',
|
||||
remarks: policy.remarks || '',
|
||||
created_at: policy.created_at || '',
|
||||
updated_at: policy.updated_at || ''
|
||||
});
|
||||
});
|
||||
|
||||
// 设置数据行样式
|
||||
worksheet.eachRow((row, rowNumber) => {
|
||||
if (rowNumber > 1) {
|
||||
row.alignment = { vertical: 'middle', horizontal: 'left' };
|
||||
}
|
||||
});
|
||||
|
||||
// 生成Excel文件
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=policies_${Date.now()}.xlsx`);
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('导出保单失败:', error);
|
||||
res.status(500).json(responseFormat.error('导出失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getPolicies,
|
||||
createPolicy,
|
||||
@@ -370,5 +483,6 @@ module.exports = {
|
||||
updatePolicy,
|
||||
updatePolicyStatus,
|
||||
deletePolicy,
|
||||
getPolicyStats
|
||||
getPolicyStats,
|
||||
exportToExcel
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
const { SupervisoryTask, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
/**
|
||||
* 监管任务控制器
|
||||
@@ -23,6 +24,22 @@ class SupervisoryTaskController {
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
// 状态映射:英文到中文
|
||||
const statusMap = {
|
||||
'pending': '待处理',
|
||||
'processing': '处理中',
|
||||
'completed': '已完成',
|
||||
'rejected': '已取消'
|
||||
};
|
||||
|
||||
// 优先级映射:英文到中文
|
||||
const priorityMap = {
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'urgent': '紧急'
|
||||
};
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
|
||||
@@ -35,11 +52,13 @@ class SupervisoryTaskController {
|
||||
}
|
||||
|
||||
if (taskStatus) {
|
||||
where.taskStatus = taskStatus;
|
||||
// 如果传入的是英文状态,转换为中文
|
||||
where.taskStatus = statusMap[taskStatus] || taskStatus;
|
||||
}
|
||||
|
||||
if (priority) {
|
||||
where.priority = priority;
|
||||
// 如果传入的是英文优先级,转换为中文
|
||||
where.priority = priorityMap[priority] || priority;
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
@@ -522,6 +541,93 @@ class SupervisoryTaskController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出监管任务列表到Excel
|
||||
*/
|
||||
static async exportToExcel(req, res) {
|
||||
try {
|
||||
const { policyNumber, customerName } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
|
||||
if (policyNumber) {
|
||||
where.policyNumber = { [Op.like]: `%${policyNumber}%` };
|
||||
}
|
||||
|
||||
if (customerName) {
|
||||
where.customerName = { [Op.like]: `%${customerName}%` };
|
||||
}
|
||||
|
||||
// 查询所有符合条件的数据
|
||||
const tasks = await SupervisoryTask.findAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
rejected: '已拒绝'
|
||||
};
|
||||
|
||||
const priorityMap = {
|
||||
low: '低',
|
||||
medium: '中',
|
||||
high: '高',
|
||||
urgent: '紧急'
|
||||
};
|
||||
|
||||
// 准备导出数据
|
||||
const exportData = tasks.map(task => ({
|
||||
policyNumber: task.policyNumber || '',
|
||||
customerName: task.customerName || '',
|
||||
productName: task.productName || '',
|
||||
insurancePeriod: task.insurancePeriod || '',
|
||||
applicableAmount: task.applicableAmount || '',
|
||||
taskStatus: ExcelExport.formatStatus(task.taskStatus, statusMap),
|
||||
priority: ExcelExport.formatStatus(task.priority, priorityMap),
|
||||
createdAt: ExcelExport.formatDate(task.createdAt),
|
||||
updatedAt: ExcelExport.formatDate(task.updatedAt)
|
||||
}));
|
||||
|
||||
// 定义列
|
||||
const columns = [
|
||||
{ header: '保单编号', key: 'policyNumber', width: 20 },
|
||||
{ header: '客户姓名', key: 'customerName', width: 15 },
|
||||
{ header: '产品名称', key: 'productName', width: 25 },
|
||||
{ header: '保险期间', key: 'insurancePeriod', width: 25 },
|
||||
{ header: '适用金额', key: 'applicableAmount', width: 15 },
|
||||
{ header: '任务状态', key: 'taskStatus', width: 12 },
|
||||
{ header: '优先级', key: 'priority', width: 12 },
|
||||
{ header: '创建时间', key: 'createdAt', width: 20 },
|
||||
{ header: '更新时间', key: 'updatedAt', width: 20 }
|
||||
];
|
||||
|
||||
// 生成Excel
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '监管任务列表');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=supervision_tasks_${Date.now()}.xlsx`);
|
||||
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出监管任务失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
status: 'error',
|
||||
message: '导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SupervisoryTaskController;
|
||||
@@ -2,6 +2,7 @@ const { User, Role } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const responseFormat = require('../utils/response');
|
||||
const crypto = require('crypto');
|
||||
const ExcelExport = require('../utils/excelExport');
|
||||
|
||||
// 获取用户列表
|
||||
const getUsers = async (req, res) => {
|
||||
@@ -486,6 +487,84 @@ const getFixedTokenInfo = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出用户列表到Excel
|
||||
const exportToExcel = async (req, res) => {
|
||||
try {
|
||||
const { search, status } = req.query;
|
||||
|
||||
const whereClause = {};
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ username: { [Op.like]: `%${search}%` } },
|
||||
{ real_name: { [Op.like]: `%${search}%` } },
|
||||
{ email: { [Op.like]: `%${search}%` } },
|
||||
{ phone: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
if (status) whereClause.status = status;
|
||||
|
||||
// 查询所有符合条件的数据
|
||||
const users = await User.findAll({
|
||||
where: whereClause,
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['name']
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
raw: true,
|
||||
nest: true
|
||||
});
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
active: '活跃',
|
||||
inactive: '禁用',
|
||||
suspended: '暂停'
|
||||
};
|
||||
|
||||
// 准备导出数据
|
||||
const exportData = users.map(user => ({
|
||||
id: user.id || '',
|
||||
username: user.username || '',
|
||||
real_name: user.real_name || '',
|
||||
email: user.email || '',
|
||||
phone: user.phone || '',
|
||||
role_name: user.role?.name || '',
|
||||
status: ExcelExport.formatStatus(user.status, statusMap),
|
||||
created_at: ExcelExport.formatDate(user.created_at),
|
||||
updated_at: ExcelExport.formatDate(user.updated_at)
|
||||
}));
|
||||
|
||||
// 定义列
|
||||
const columns = [
|
||||
{ header: 'ID', key: 'id', width: 10 },
|
||||
{ header: '用户名', key: 'username', width: 15 },
|
||||
{ header: '真实姓名', key: 'real_name', width: 15 },
|
||||
{ header: '邮箱', key: 'email', width: 25 },
|
||||
{ header: '手机号', key: 'phone', width: 15 },
|
||||
{ header: '角色', key: 'role_name', width: 12 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '创建时间', key: 'created_at', width: 20 },
|
||||
{ header: '更新时间', key: 'updated_at', width: 20 }
|
||||
];
|
||||
|
||||
// 生成Excel
|
||||
const buffer = await ExcelExport.exportToExcel(exportData, columns, '用户列表');
|
||||
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', `attachment; filename=users_${Date.now()}.xlsx`);
|
||||
|
||||
// 发送文件
|
||||
res.send(buffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出用户列表失败:', error);
|
||||
res.status(500).json(responseFormat.error('导出失败'));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getUsers,
|
||||
getUser,
|
||||
@@ -500,5 +579,6 @@ module.exports = {
|
||||
generateFixedToken,
|
||||
regenerateFixedToken,
|
||||
deleteFixedToken,
|
||||
getFixedTokenInfo
|
||||
getFixedTokenInfo,
|
||||
exportToExcel
|
||||
};
|
||||
326
insurance_backend/package-lock.json
generated
326
insurance_backend/package-lock.json
generated
@@ -2386,6 +2386,42 @@
|
||||
"node": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/builtins": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/builtins/-/builtins-5.1.0.tgz",
|
||||
"integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/builtins/node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -3645,6 +3681,35 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-compat-utils": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
|
||||
"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-compat-utils/node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-standard": {
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
|
||||
@@ -3739,6 +3804,28 @@
|
||||
"eslint": ">=4.19.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-es-x": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
|
||||
"integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/ota-meshi",
|
||||
"https://opencollective.com/eslint"
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.1.2",
|
||||
"@eslint-community/regexpp": "^4.11.0",
|
||||
"eslint-compat-utils": "^0.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import": {
|
||||
"version": "2.32.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||
@@ -3793,6 +3880,48 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n": {
|
||||
"version": "16.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
|
||||
"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"builtins": "^5.0.1",
|
||||
"eslint-plugin-es-x": "^7.5.0",
|
||||
"get-tsconfig": "^4.7.0",
|
||||
"globals": "^13.24.0",
|
||||
"ignore": "^5.2.4",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-core-module": "^2.12.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.2",
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mysticatea"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n/node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-node": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
|
||||
@@ -4637,6 +4766,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
|
||||
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
|
||||
@@ -5113,6 +5255,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
|
||||
@@ -7225,6 +7383,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-types": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/openapi-types/-/openapi-types-12.1.3.tgz",
|
||||
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -7995,6 +8159,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve.exports": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.3.tgz",
|
||||
@@ -10869,7 +11043,8 @@
|
||||
"@redis/bloom": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.2.0.tgz",
|
||||
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="
|
||||
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.6.1",
|
||||
@@ -10891,22 +11066,26 @@
|
||||
"@redis/graph": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/@redis/graph/-/graph-1.1.1.tgz",
|
||||
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="
|
||||
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@redis/json/-/json-1.0.7.tgz",
|
||||
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="
|
||||
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@redis/search/-/search-1.2.0.tgz",
|
||||
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="
|
||||
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@redis/time-series/-/time-series-1.1.0.tgz",
|
||||
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="
|
||||
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
|
||||
"requires": {}
|
||||
},
|
||||
"@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
@@ -11127,7 +11306,8 @@
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
@@ -11654,6 +11834,32 @@
|
||||
"resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"builtins": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/builtins/-/builtins-5.1.0.tgz",
|
||||
"integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"semver": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -12129,7 +12335,8 @@
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/dedent/-/dedent-1.7.0.tgz",
|
||||
"integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.4",
|
||||
@@ -12593,11 +12800,31 @@
|
||||
"text-table": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"eslint-compat-utils": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
|
||||
"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-config-standard": {
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
|
||||
"integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-import-resolver-node": {
|
||||
"version": "0.3.9",
|
||||
@@ -12651,6 +12878,18 @@
|
||||
"regexpp": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-es-x": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
|
||||
"integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.1.2",
|
||||
"@eslint-community/regexpp": "^4.11.0",
|
||||
"eslint-compat-utils": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-import": {
|
||||
"version": "2.32.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||
@@ -12698,6 +12937,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-n": {
|
||||
"version": "16.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
|
||||
"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"builtins": "^5.0.1",
|
||||
"eslint-plugin-es-x": "^7.5.0",
|
||||
"get-tsconfig": "^4.7.0",
|
||||
"globals": "^13.24.0",
|
||||
"ignore": "^5.2.4",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-core-module": "^2.12.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.2",
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-node": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
|
||||
@@ -12716,7 +12984,8 @@
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
|
||||
"integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "7.2.2",
|
||||
@@ -13314,6 +13583,16 @@
|
||||
"get-intrinsic": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"get-tsconfig": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
|
||||
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
|
||||
@@ -13632,6 +13911,16 @@
|
||||
"has-tostringtag": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
|
||||
@@ -14156,7 +14445,8 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
|
||||
"integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "29.6.3",
|
||||
@@ -15219,6 +15509,12 @@
|
||||
"mimic-fn": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"openapi-types": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/openapi-types/-/openapi-types-12.1.3.tgz",
|
||||
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
|
||||
"peer": true
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -15768,6 +16064,13 @@
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true
|
||||
},
|
||||
"resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"resolve.exports": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.3.tgz",
|
||||
@@ -17020,7 +17323,8 @@
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"xmlchars": {
|
||||
"version": "2.2.0",
|
||||
|
||||
@@ -83,6 +83,47 @@ const { jwtAuth } = require('../middleware/auth');
|
||||
*/
|
||||
router.get('/stats', jwtAuth, deviceAlertController.getAlertStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/device-alerts/export:
|
||||
* get:
|
||||
* tags:
|
||||
* - 设备预警
|
||||
* summary: 导出设备预警列表到Excel
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: alert_level
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 预警级别
|
||||
* - in: query
|
||||
* name: alert_type
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 预警类型
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 处理状态
|
||||
* - in: query
|
||||
* name: is_read
|
||||
* schema:
|
||||
* type: boolean
|
||||
* description: 是否已读
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
* content:
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
*/
|
||||
router.get('/export', jwtAuth, deviceAlertController.exportToExcel);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/device-alerts:
|
||||
|
||||
@@ -10,6 +10,50 @@ const { jwtAuth, requirePermission } = require('../middleware/auth');
|
||||
* description: 待安装任务管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/installation-tasks/stats:
|
||||
* get:
|
||||
* summary: 获取安装任务统计数据
|
||||
* tags: [InstallationTasks]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
router.get('/stats', jwtAuth, requirePermission('installation_tasks:read'), installationTaskController.getInstallationTaskStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/installation-tasks/export:
|
||||
* get:
|
||||
* summary: 导出待安装任务到Excel
|
||||
* tags: [InstallationTasks]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: policyNumber
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 保单编号
|
||||
* - in: query
|
||||
* name: keyword
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 关键字搜索
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
* content:
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
*/
|
||||
router.get('/export', jwtAuth, requirePermission('installation_tasks:read'), installationTaskController.exportToExcel);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/installation-tasks:
|
||||
@@ -230,38 +274,4 @@ router.delete('/:id', jwtAuth, requirePermission('installation_tasks:delete'), i
|
||||
*/
|
||||
router.post('/batch/operate', jwtAuth, requirePermission('installation_tasks:update'), installationTaskController.batchOperateInstallationTasks);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/installation-tasks/export:
|
||||
* get:
|
||||
* summary: 导出待安装任务数据
|
||||
* tags: [InstallationTasks]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: ids
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 任务ID列表(逗号分隔)
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
*/
|
||||
router.get('/export', jwtAuth, requirePermission('installation_tasks:read'), installationTaskController.exportInstallationTasks);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/installation-tasks/stats:
|
||||
* get:
|
||||
* summary: 获取安装任务统计数据
|
||||
* tags: [InstallationTasks]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
router.get('/stats', jwtAuth, requirePermission('installation_tasks:read'), installationTaskController.getInstallationTaskStats);
|
||||
|
||||
module.exports = router;
|
||||
@@ -8,6 +8,16 @@ router.get('/applications', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.getApplications
|
||||
);
|
||||
|
||||
// 获取保险申请统计(必须在 :id 路由之前)
|
||||
router.get('/applications-stats', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.getApplicationStats
|
||||
);
|
||||
|
||||
// 导出保险申请数据(必须在 :id 路由之前)
|
||||
router.get('/applications/export', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.exportApplications
|
||||
);
|
||||
|
||||
// 创建保险申请
|
||||
router.post('/applications', jwtAuth, checkPermission('insurance', 'create'),
|
||||
insuranceController.createApplication
|
||||
@@ -33,16 +43,6 @@ router.delete('/applications/:id', jwtAuth, checkPermission('insurance', 'delete
|
||||
insuranceController.deleteApplication
|
||||
);
|
||||
|
||||
// 获取保险申请统计
|
||||
router.get('/applications-stats', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.getApplicationStats
|
||||
);
|
||||
|
||||
// 导出保险申请数据
|
||||
router.get('/applications/export', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.exportApplications
|
||||
);
|
||||
|
||||
// 获取参保类型选项
|
||||
router.get('/categories', jwtAuth, checkPermission('insurance', 'read'),
|
||||
insuranceController.getInsuranceCategories
|
||||
|
||||
@@ -174,6 +174,9 @@ router.get('/', jwtAuth, checkPermission('insurance_type', 'read'),
|
||||
* '500':
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 导出险种列表到Excel
|
||||
router.get('/export', jwtAuth, checkPermission('insurance_type', 'read'), insuranceTypeController.exportToExcel);
|
||||
|
||||
// 获取单个险种详情
|
||||
router.get('/:id', jwtAuth, checkPermission('insurance_type', 'read'),
|
||||
insuranceTypeController.getInsuranceTypeById
|
||||
|
||||
@@ -8,7 +8,8 @@ const {
|
||||
updateLivestockClaimPayment,
|
||||
getLivestockClaimStats,
|
||||
updateLivestockClaim,
|
||||
deleteLivestockClaim
|
||||
deleteLivestockClaim,
|
||||
exportToExcel
|
||||
} = require('../controllers/livestockClaimController');
|
||||
const { authenticateToken, requirePermission } = require('../middleware/auth');
|
||||
|
||||
@@ -33,6 +34,9 @@ router.get('/', authenticateToken, requirePermission('livestock_claim:read'), ge
|
||||
// 获取生资理赔统计
|
||||
router.get('/stats', authenticateToken, requirePermission('livestock_claim:read'), getLivestockClaimStats);
|
||||
|
||||
// 导出理赔列表到Excel
|
||||
router.get('/export', authenticateToken, requirePermission('livestock_claim:read'), exportToExcel);
|
||||
|
||||
// 获取单个生资理赔详情
|
||||
router.get('/:id', authenticateToken, requirePermission('livestock_claim:read'), getLivestockClaimById);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user