Compare commits

...

73 Commits
jgqweb ... main

Author SHA1 Message Date
Maple 36b076f987 fix:收入合同 2025-08-07 16:05:17 +08:00
Maple 895bfcbeaf fix:将岗位与服务器的内容匹配(目前是岗位中的部门/所属部门) 2025-08-06 16:02:00 +08:00
Maple 7f654625aa fix:修改了一部分菜单栏的问题,但是更多的还没有修复 2025-08-06 15:20:23 +08:00
Maple 864f926f2e fix:菜单栏问题 2025-08-06 10:05:40 +08:00
wuxueyu 69b4bf86bd 修复各种打包错误 2025-08-06 01:01:43 +08:00
wuxueyu 48f4020fc8 Merge branch 'main' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web
# Conflicts:
#	.env.development
#	src/views/project-management/bidding/information-retrieval/index.vue
2025-08-06 00:05:20 +08:00
wuxueyu 0eb12b4df9 信息检索模块新增爬虫设置、检索信息列表展示、信息详情查看 2025-08-06 00:03:43 +08:00
chabai 77238c3d03 route 商务添加 2025-08-05 22:50:37 +08:00
chabai 7e006acaa6 business 2025-08-05 22:44:39 +08:00
chabai 2065389094 Merge remote-tracking branch 'origin/main' 2025-08-05 21:37:05 +08:00
chabai 4972dcaf4d fix 2025-08-05 21:36:46 +08:00
Mr.j c7043dbdb1 备注 2025-08-05 21:21:06 +08:00
Maple 02c705ae3d fix:优化了岗位详情信息 2025-08-05 17:16:45 +08:00
Maple 69ff1bba7d add:添加了岗位信息,实现了通过岗位查询到该岗位的角色的功能 2025-08-05 15:40:37 +08:00
wangna0328 c47c8e48aa 制度代码修改版 2025-08-04 22:15:48 +08:00
Maple c64166a7c0 add:添加了岗位信息,实现了通过岗位查询到该岗位的角色的功能 2025-08-04 17:27:42 +08:00
wxy 1307eaf651 Revert "Merge remote-tracking branch 'origin/jgqweb'"
This reverts commit 1648f3df96, reversing
changes made to c76d6beeb0.
2025-08-04 17:10:11 +08:00
wxy d25a34e579 Revert "add:添加了岗位信息,实现了通过岗位查询到该岗位的角色的功能"
This reverts commit a72aaafdfb.
2025-08-04 17:07:06 +08:00
wxy fcd9604272 Revert "Merge branch 'ljy/master'"
This reverts commit 2492a57b88, reversing
changes made to 1648f3df96.
2025-08-04 16:59:54 +08:00
wxy 4ca2696896 Revert "信息检索、爬虫设置相关组件提交"
This reverts commit 11c9d33f94.
2025-08-04 16:57:59 +08:00
wxy 11c9d33f94 信息检索、爬虫设置相关组件提交 2025-08-04 16:25:43 +08:00
Maple 2492a57b88 Merge branch 'ljy/master'
# Conflicts:
#	src/apis/system/post.ts
2025-08-04 14:55:23 +08:00
Maple a72aaafdfb add:添加了岗位信息,实现了通过岗位查询到该岗位的角色的功能 2025-08-04 14:50:06 +08:00
Mr.j 1648f3df96 Merge remote-tracking branch 'origin/jgqweb' 2025-08-04 11:54:12 +08:00
Maple c76d6beeb0 Merge remote-tracking branch 'origin/main' 2025-08-04 10:51:45 +08:00
Maple 2bf9c6a5eb none 2025-08-04 10:51:14 +08:00
chabai ca1c3947c1 Merge remote-tracking branch 'refs/remotes/origin/main' into ccd 2025-08-04 09:42:01 +08:00
chabai 011e1c5337 完成制度管理模块开发,完善搜索功能 2025-08-01 17:22:47 +08:00
chabai 7f8ccb76cb 完成制度管理模块开发 2025-08-01 15:37:53 +08:00
Vic b8ffc08513 Merge remote-tracking branch 'origin/main' 2025-07-31 22:20:22 +08:00
Vic 23fb041a08 信息检索模块新增爬虫设置、检索信息列表展示、信息详情查看 2025-07-31 22:16:28 +08:00
chabai cc65882b8d 优化制度管理模块,新建制度提案页面 2025-07-31 17:00:32 +08:00
Maple 54d9557a89 fix:修复了个人中心,将前端的字段与后端对应,确保数据能够完全显示 2025-07-31 15:26:28 +08:00
chabai 327064b7e5 完成制度管理模块的制度公告,制度公示,制度管理 2025-07-31 09:11:39 +08:00
Maple 1d96aad163 change: 将菜单栏优化了一下,添加了个人信息,用来查看个人信息,但是并没有调用对应的api接口 2025-07-31 09:09:37 +08:00
chabai 64a82a6775 修改下载文件类型的备份 2025-07-30 15:58:23 +08:00
Maple 124a04c43d change: 将菜单栏优化了一下
delete: 删除了一些多余的菜单栏(注释掉了)
2025-07-30 14:31:18 +08:00
chabai 1dc321ba5c 完成制度管理模块页面搭建,包含发布流程等功能 2025-07-30 10:21:02 +08:00
wangna0328 1feb4832c5 Merge branch 'main' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web 2025-07-29 20:16:19 +08:00
wangna0328 1f56b0a30f 制度相关页面开发初步实现 2025-07-29 20:12:55 +08:00
Maple db047b4e60 init:添加了岗位管理中的岗位详情按钮,可以点击查看岗位详情
fix: 修复了角色功能,可以通过角色检索该角色的用户
2025-07-29 17:53:31 +08:00
Maple 9741192bee fix:将部门管理中的树形结构改为受控的模式,不在是点一个而全部展开或者收缩 2025-07-28 15:12:10 +08:00
Maple 8c202c45dc Merge remote-tracking branch 'origin/main' 2025-07-28 12:39:07 +08:00
Maple 38d888f24e fix:任务管理 2025-07-26 21:32:37 +08:00
Maple fae3b7b05d fix:任务管理 2025-07-26 21:32:21 +08:00
Maple 236cf1489b style 2025-07-26 19:35:14 +08:00
wxy 0108dd6068 完成原始数据回流后查看界面开发和功能开发,支持对所选图片的关联音频进行播放|修复典型图片设置不生效的问题 2025-07-26 16:58:21 +08:00
Maple 5b673f9525 style 2025-07-26 11:24:29 +08:00
Maple 708e8b47d1 style 2025-07-26 11:14:22 +08:00
何德超 7728cfd8aa logo修改 2025-07-25 21:34:29 +08:00
Vic fe6e84f5f7 新增缺陷增加轴向和弦向字段|缺陷检测界面新增图像预览功能和增加典型图片设置功能|去除可视化菜单 2025-07-25 00:13:51 +08:00
何德超 70fca26e10 证书类型改为与后端枚举类型匹配的下拉框选择 2025-07-24 22:02:13 +08:00
何德超 7f24af3272 立项添加项目经理,风场名称和业主名称绑定一致 2025-07-24 20:56:11 +08:00
何德超 cf780323e0 人员资质修改正常接收数据 2025-07-24 20:53:50 +08:00
何德超 b028f2a7bd 上传 2025-07-23 22:46:47 +08:00
何德超 15d50b997d 项目管理菜单修改 2025-07-22 16:43:51 +08:00
何德超 c3fb37aa6b test connect 2025-07-22 09:38:23 +08:00
何德超 b9b1da2186 fix 2025-07-21 17:51:21 +08:00
何德超 b7e5a4a82f 保险与健康档案分开 2025-07-21 16:29:17 +08:00
何德超 d669624fbe route fix 2025-07-21 14:35:34 +08:00
何德超 f8aeb0caac 按吴总要求不要市场商务管理了,直接把项目来源提上去 2025-07-21 13:21:28 +08:00
何德超 afe4a6d5ba xlsx添加"xlsx": "^0.18.5" 2025-07-21 12:35:34 +08:00
何德超 ab8afb02eb 市场商务管理项目菜单修改 2025-07-21 12:31:26 +08:00
wxy 30eca5da60 Merge branch 'main' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web 2025-07-21 10:52:21 +08:00
wxy a6b7b343a9 侧边栏结构修改 2025-07-21 10:50:02 +08:00
何德超 253b6ffcca 工资单 2025-07-21 10:49:05 +08:00
何德超 a3b11c9971 hdc,绩效和工资单 2025-07-21 10:04:56 +08:00
wxy ba041b3f3a 平台修改 2025-07-14 11:11:33 +08:00
游离 e6ad6ad6fb 修改登录 2025-06-30 18:15:55 +08:00
游离 3378ab4a49 修改密码加密 2025-06-30 17:51:39 +08:00
游离 41b9474300 增加菜单 2025-06-30 09:14:46 +08:00
游离 edb8aad10f 增加项目 2025-06-27 19:54:42 +08:00
游离 55ebab5b69
Initial commit 2025-06-15 13:55:44 +00:00
248 changed files with 20783 additions and 16717 deletions

BIN
.env

Binary file not shown.

View File

@ -2,6 +2,5 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Industrial-image-management-system---web" vcs="Git" />
</component> </component>
</project> </project>

91
BACKEND_SEARCH_API.md Normal file
View File

@ -0,0 +1,91 @@
# 制度类型搜索接口实现指南
## 接口定义
### 请求接口
```
GET /api/regulation/types
Content-Type: application/json
```
### 请求参数
```json
{
"page": number, // 页码可选默认1
"size": number, // 每页大小可选默认10
"typeName": "string", // 类型名称(模糊搜索,可选)
"status": "string", // 状态筛选("1"启用,"0"禁用,可选)
"remark": "string" // 备注内容(模糊搜索,可选)
}
```
### 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"typeId": "string",
"typeName": "string",
"sortOrder": number,
"isEnabled": "string",
"remark": "string",
"createBy": "string",
"createTime": "string",
"updateBy": "string",
"updateTime": "string",
"delFlag": "string"
}
],
"total": number, // 总记录数
"current": number, // 当前页码
"size": number, // 每页大小
"pages": number // 总页数
}
}
```
## 后端实现说明
后端已实现以下接口:
```java
@ApiOperation(value = "获取制度类型列表", httpMethod = "GET")
@GetMapping
public Result getRegulationTypes(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String typeName,
@RequestParam(required = false) String status,
@RequestParam(required = false) String remark
) {
return regulationTypeService.getRegulationTypes(page, size, typeName, status, remark);
}
```
## 前端集成说明
前端已完成以下功能:
1. ✅ 调整为GET请求接口
2. ✅ 参数名匹配后端接口isEnabled → status
3. ✅ 移除排序参数(后端不支持)
4. ✅ 简化搜索表单,只支持手动搜索
5. ✅ 保持原有功能不受影响
## 搜索流程
1. 用户在搜索表单中输入条件
2. 点击"搜索"按钮触发搜索
3. 调用后端GET接口 `/api/regulation/types`
4. 后端返回搜索结果
5. 前端展示搜索结果
## 参数说明
- **page**: 页码默认1
- **size**: 每页大小默认10
- **typeName**: 类型名称,支持模糊搜索
- **status**: 状态筛选,"1"表示启用,"0"表示禁用
- **remark**: 备注内容,支持模糊搜索

View File

@ -1,147 +0,0 @@
# 设备中心模块问题修复和改进总结
## 发现的问题
### 1. 弹窗组件问题
- **缺少查看模式**:原弹窗组件没有区分查看、编辑、新增模式
- **表单验证不完善**:缺少详细的验证规则和错误处理
- **缺少加载状态**提交时没有loading状态管理
- **表单重置逻辑不完善**:取消时没有正确重置表单
- **缺少调试功能**:开发环境下缺少调试工具
### 2. 主页面问题
- **数据转换逻辑复杂**:存在过多的兼容性处理,可能导致数据不一致
- **错误处理不够详细**:错误信息不够具体
- **缺少详情页面跳转**:没有设备详情页面的入口
- **对象引用问题**:直接传递对象引用可能导致数据污染
### 3. 功能缺失
- **缺少设备详情页面**:没有专门的设备详情展示页面
- **缺少维护记录管理**:没有设备维护记录的展示和管理
- **缺少使用记录**:没有设备使用历史的展示
- **缺少文件管理**:没有设备相关文件的管理功能
## 修复和改进内容
### 1. 弹窗组件优化 (`DeviceModal.vue`)
#### 新增功能
- ✅ **查看模式支持**:添加了查看模式,禁用所有输入框
- ✅ **完善的表单验证**:添加了详细的验证规则和长度限制
- ✅ **加载状态管理**添加了提交时的loading状态
- ✅ **表单重置优化**:完善了表单重置逻辑
- ✅ **调试功能**:开发环境下添加了调试按钮
- ✅ **错误处理优化**:更详细的错误信息处理
#### 技术改进
- ✅ **响应式表单数据**使用reactive管理表单数据
- ✅ **计算属性优化**:添加了表单有效性计算
- ✅ **监听器优化**:优化了数据变化监听逻辑
- ✅ **类型安全**完善了TypeScript类型定义
### 2. 主页面优化 (`index.vue`)
#### 功能改进
- ✅ **数据转换优化**:简化了数据转换逻辑,提高一致性
- ✅ **错误处理增强**:添加了更详细的错误信息处理
- ✅ **详情页面跳转**:添加了设备详情页面的跳转功能
- ✅ **对象深拷贝**:使用展开运算符避免对象引用问题
#### 用户体验改进
- ✅ **删除确认优化**:添加了更明确的删除确认提示
- ✅ **操作反馈优化**:改进了操作成功/失败的提示信息
### 3. 新增设备详情页面 (`detail.vue`)
#### 功能特性
- ✅ **基本信息展示**:设备的基本信息展示
- ✅ **状态信息展示**:设备的各种状态信息
- ✅ **维护记录管理**:设备维护记录的展示和管理
- ✅ **使用记录展示**:设备使用历史的展示
- ✅ **位置变更记录**:设备位置变更历史
- ✅ **文件管理**:设备相关文件的上传和管理
#### 技术实现
- ✅ **响应式数据管理**使用ref管理页面数据
- ✅ **路由参数处理**正确处理路由参数获取设备ID
- ✅ **API集成**集成设备详情API
- ✅ **状态文本转换**:统一的状态文本转换函数
### 4. 路由配置优化
#### 新增路由
- ✅ **设备详情路由**:添加了设备详情页面的路由配置
- ✅ **参数传递**支持通过URL参数传递设备ID
## 参考training模块的实现
### 借鉴的设计模式
1. **弹窗组件设计**参考了TrainingPlanModal的弹窗设计模式
2. **表单验证机制**:采用了相同的表单验证和错误处理机制
3. **数据管理方式**:使用了相同的响应式数据管理方式
4. **调试功能**:借鉴了开发环境下的调试工具设计
5. **详情页面设计**参考了TrainingDetail页面的布局和功能设计
### 技术实现对比
| 功能 | Training模块 | 设备管理模块 | 改进状态 |
|------|-------------|-------------|----------|
| 弹窗模式 | 查看/编辑/新增 | 查看/编辑/新增 | ✅ 已实现 |
| 表单验证 | 完善 | 完善 | ✅ 已实现 |
| 加载状态 | 有 | 有 | ✅ 已实现 |
| 调试功能 | 有 | 有 | ✅ 已实现 |
| 详情页面 | 有 | 有 | ✅ 已实现 |
| 错误处理 | 详细 | 详细 | ✅ 已实现 |
## 使用说明
### 1. 设备列表页面
- **搜索功能**:支持按设备名称、类型、状态等条件搜索
- **新增设备**:点击"新增设备"按钮打开新增弹窗
- **查看设备**:点击"查看"按钮以只读模式查看设备信息
- **编辑设备**:点击"编辑"按钮修改设备信息
- **详情页面**:点击"详情"按钮跳转到设备详情页面
- **设备操作**:支持分配、归还、删除等操作
### 2. 设备详情页面
- **基本信息**:展示设备的基本信息
- **状态信息**:展示设备的各种状态
- **维护记录**:查看和管理设备维护记录
- **使用记录**:查看设备使用历史
- **位置变更**:查看设备位置变更历史
- **文件管理**:上传和管理设备相关文件
### 3. 开发调试
- **调试按钮**:开发环境下显示调试按钮
- **表单测试**:可以测试表单数据绑定
- **测试数据**:可以填充测试数据
- **数据清空**:可以清空表单数据
## 后续优化建议
### 1. 功能扩展
- [ ] 添加设备维护记录的增删改功能
- [ ] 实现设备使用记录的实时更新
- [ ] 添加设备状态变更的审批流程
- [ ] 实现设备文件的在线预览功能
### 2. 性能优化
- [ ] 添加数据缓存机制
- [ ] 实现分页加载优化
- [ ] 添加数据预加载功能
### 3. 用户体验
- [ ] 添加操作确认的快捷键支持
- [ ] 实现批量操作功能
- [ ] 添加数据导出功能
- [ ] 实现高级搜索功能
## 总结
通过参考training模块的实现成功修复了设备中心模块的主要问题并添加了缺失的功能。主要改进包括
1. **弹窗组件**:完善了查看、编辑、新增模式,添加了完善的表单验证和错误处理
2. **主页面**:优化了数据转换逻辑,改进了错误处理,添加了详情页面跳转
3. **详情页面**:新增了完整的设备详情展示页面,包含维护记录、使用记录等功能
4. **路由配置**:添加了设备详情页面的路由配置
这些改进大大提升了设备中心模块的功能完整性和用户体验使其与training模块保持了一致的设计标准和实现质量。

View File

@ -0,0 +1,125 @@
# 制度公告搜索接口实现指南
## 接口定义
### 请求接口
```
GET /api/regulation
Content-Type: application/json
```
### 请求参数
```json
{
"page": number, // 页码可选默认1
"size": number, // 每页大小可选默认10
"status": "string", // 状态筛选(精确匹配,固定为"PUBLISHED"
"title": "string", // 制度标题(模糊搜索,可选)
"proposer": "string", // 公示人(模糊搜索,可选)
"confirmStatus": "string" // 确认状态(精确匹配,可选)
}
```
### 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"regulationId": "string",
"title": "string",
"content": "string",
"regulationType": "string",
"status": "string",
"publishTime": "string",
"effectiveTime": "string",
"expireTime": "string",
"scope": "string",
"level": "string",
"version": "string",
"remark": "string",
"createBy": "string",
"updateBy": "string",
"createTime": "string",
"updateTime": "string",
"delFlag": "string",
"confirmStatus": "string"
}
],
"total": number, // 总记录数
"current": number, // 当前页码
"size": number, // 每页大小
"pages": number // 总页数
}
}
```
## 后端实现说明
后端已实现以下接口:
```java
@ApiOperation(value = "获取制度列表", httpMethod = "GET")
@GetMapping
public Result getRegulationList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String status,
@RequestParam(required = false) String type,
@RequestParam(required = false) String title,
@RequestParam(required = false) String proposer,
@RequestParam(required = false) String confirmStatus
) {
return regulationService.getRegulationList(page, size, status, type, title, proposer, confirmStatus);
}
```
## 前端集成说明
前端已完成以下功能:
1. ✅ 删除前端搜索逻辑(防抖、实时搜索等)
2. ✅ 调整为GET请求接口
3. ✅ 参数名匹配后端接口createByName → proposer
4. ✅ 简化搜索表单,只支持手动搜索
5. ✅ 移除前端过滤逻辑,由后端处理状态筛选
6. ✅ 保持原有功能不受影响
## 搜索流程
1. 用户在搜索表单中输入条件
2. 点击"搜索"按钮触发搜索
3. 调用后端GET接口 `/api/regulation`
4. 后端返回搜索结果已过滤为PUBLISHED状态的制度
5. 前端展示搜索结果
## 参数说明
- **page**: 页码默认1
- **size**: 每页大小默认10
- **status**: 状态筛选,固定为"PUBLISHED"(已公告状态)
- **title**: 制度标题,支持模糊搜索
- **proposer**: 公示人,支持模糊搜索
- **confirmStatus**: 确认状态精确匹配confirmed、pending
## 搜索功能特性
- **模糊搜索**title 和 proposer 字段支持模糊匹配
- **精确筛选**confirmStatus 字段精确匹配
- **分页查询**:支持分页和排序
- **状态过滤**后端自动过滤为PUBLISHED状态的制度
- **性能优化**:使用数据库索引提升查询性能
## 业务逻辑说明
制度公告页面专门用于展示已经公告的制度,因此:
- 后端需要自动过滤为 `status = 'PUBLISHED'` 的制度
- 支持按确认状态confirmStatus进行筛选
- 前端不再需要手动过滤,完全依赖后端处理
- 用户可以查看制度详情、下载PDF文件、确认知晓制度
## 确认状态说明
- **confirmed**: 已确认 - 用户已确认知晓并遵守该制度
- **pending**: 待确认 - 用户尚未确认知晓该制度
- 空值: 全部 - 显示所有确认状态的制度

View File

@ -0,0 +1,117 @@
# 制度公示搜索接口实现指南
## 接口定义
### 请求接口
```
GET /api/regulation
Content-Type: application/json
```
### 请求参数
```json
{
"page": number, // 页码可选默认1
"size": number, // 每页大小可选默认10
"status": "string", // 状态筛选(精确匹配,可选)
"type": "string", // 提案类型(精确匹配,可选)
"title": "string", // 提案标题(模糊搜索,可选)
"proposer": "string" // 提案人(模糊搜索,可选)
}
```
### 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"regulationId": "string",
"title": "string",
"content": "string",
"regulationType": "string",
"status": "string",
"publishTime": "string",
"effectiveTime": "string",
"expireTime": "string",
"scope": "string",
"level": "string",
"version": "string",
"remark": "string",
"createBy": "string",
"updateBy": "string",
"createTime": "string",
"updateTime": "string",
"delFlag": "string",
"confirmStatus": "string"
}
],
"total": number, // 总记录数
"current": number, // 当前页码
"size": number, // 每页大小
"pages": number // 总页数
}
}
```
## 后端实现说明
后端已实现以下接口:
```java
@ApiOperation(value = "获取制度列表", httpMethod = "GET")
@GetMapping
public Result getRegulationList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String status,
@RequestParam(required = false) String type,
@RequestParam(required = false) String title,
@RequestParam(required = false) String proposer
) {
return regulationService.getRegulationList(page, size, status, type, title, proposer);
}
```
## 前端集成说明
前端已完成以下功能:
1. ✅ 删除前端搜索逻辑(防抖、实时搜索等)
2. ✅ 调整为GET请求接口
3. ✅ 参数名匹配后端接口createByName → proposer, regulationType → type
4. ✅ 简化搜索表单,只支持手动搜索
5. ✅ 移除前端过滤逻辑,由后端处理状态筛选
6. ✅ 保持原有功能不受影响
## 搜索流程
1. 用户在搜索表单中输入条件
2. 点击"搜索"按钮触发搜索
3. 调用后端GET接口 `/api/regulation`
4. 后端返回搜索结果(已过滤掉草稿状态的提案)
5. 前端展示搜索结果
## 参数说明
- **page**: 页码默认1
- **size**: 每页大小默认10
- **status**: 状态筛选精确匹配PUBLISHED、APPROVED等
- **type**: 提案类型,精确匹配(如:管理规范、操作流程、安全制度、其他)
- **title**: 提案标题,支持模糊搜索
- **proposer**: 提案人,支持模糊搜索
## 搜索功能特性
- **模糊搜索**title 和 proposer 字段支持模糊匹配
- **精确筛选**type 和 status 字段精确匹配
- **分页查询**:支持分页和排序
- **状态过滤**:后端自动过滤掉草稿状态的提案,只显示已公示及以上的提案
- **性能优化**:使用数据库索引提升查询性能
## 业务逻辑说明
制度公示页面专门用于展示已经公示或已通过的制度提案,因此:
- 后端需要自动过滤掉 `status = 'DRAFT'` 的提案
- 只返回 `status = 'PUBLISHED'``status = 'APPROVED'` 的提案
- 前端不再需要手动过滤,完全依赖后端处理

View File

@ -0,0 +1,108 @@
# 制度提案搜索接口实现指南
## 接口定义
### 请求接口
```
GET /api/regulation
Content-Type: application/json
```
### 请求参数
```json
{
"page": number, // 页码可选默认1
"size": number, // 每页大小可选默认10
"status": "string", // 状态筛选(精确匹配,可选)
"type": "string", // 提案类型(精确匹配,可选)
"title": "string", // 提案标题(模糊搜索,可选)
"proposer": "string" // 提案人(模糊搜索,可选)
}
```
### 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"records": [
{
"regulationId": "string",
"title": "string",
"content": "string",
"regulationType": "string",
"status": "string",
"publishTime": "string",
"effectiveTime": "string",
"expireTime": "string",
"scope": "string",
"level": "string",
"version": "string",
"remark": "string",
"createBy": "string",
"updateBy": "string",
"createTime": "string",
"updateTime": "string",
"delFlag": "string",
"confirmStatus": "string"
}
],
"total": number, // 总记录数
"current": number, // 当前页码
"size": number, // 每页大小
"pages": number // 总页数
}
}
```
## 后端实现说明
后端已实现以下接口:
```java
@ApiOperation(value = "获取制度列表", httpMethod = "GET")
@GetMapping
public Result getRegulationList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String status,
@RequestParam(required = false) String type,
@RequestParam(required = false) String title,
@RequestParam(required = false) String proposer
) {
return regulationService.getRegulationList(page, size, status, type, title, proposer);
}
```
## 前端集成说明
前端已完成以下功能:
1. ✅ 删除前端搜索逻辑(防抖、实时搜索等)
2. ✅ 调整为GET请求接口
3. ✅ 参数名匹配后端接口createByName → proposer, regulationType → type
4. ✅ 简化搜索表单,只支持手动搜索
5. ✅ 保持原有功能不受影响
## 搜索流程
1. 用户在搜索表单中输入条件
2. 点击"搜索"按钮触发搜索
3. 调用后端GET接口 `/api/regulation`
4. 后端返回搜索结果
5. 前端展示搜索结果
## 参数说明
- **page**: 页码默认1
- **size**: 每页大小默认10
- **status**: 状态筛选精确匹配DRAFT、PUBLISHED、APPROVED等
- **type**: 提案类型,精确匹配(如:管理规范、操作流程、安全制度、其他)
- **title**: 提案标题,支持模糊搜索
- **proposer**: 提案人,支持模糊搜索
## 搜索功能特性
- **模糊搜索**title 和 proposer 字段支持模糊匹配
- **精确筛选**type 和 status 字段精确匹配
- **分页查询**:支持分页和排序
- **性能优化**:使用数据库索引提升查询性能

View File

@ -8,7 +8,7 @@ export default function appInfo(): Plugin {
apply: 'serve', apply: 'serve',
async buildStart() { async buildStart() {
const { bold, green, cyan, bgGreen, underline } = picocolors const { bold, green, cyan, bgGreen, underline } = picocolors
// eslint-disable-next-line no-console
console.log( console.log(
boxen( boxen(
`${bold(green(`${bgGreen('ContiNew Admin v4.0.0-SNAPSHOT')}`))}\n${cyan('在线文档:')}${underline('https://continew.top')}\n${cyan('常见问题:')}${underline('https://continew.top/admin/faq.html')}\n${cyan('持续迭代优化的前后端分离中后台管理系统框架。')}`, `${bold(green(`${bgGreen('ContiNew Admin v4.0.0-SNAPSHOT')}`))}\n${cyan('在线文档:')}${underline('https://continew.top')}\n${cyan('常见问题:')}${underline('https://continew.top/admin/faq.html')}\n${cyan('持续迭代优化的前后端分离中后台管理系统框架。')}`,

View File

@ -35,7 +35,7 @@ export default antfu(
rules: { rules: {
'curly': ['off', 'all'], // 对所有控制语句强制使用一致的大括号样式 'curly': ['off', 'all'], // 对所有控制语句强制使用一致的大括号样式
'no-new': 'off', // 不允许在赋值或比较之外使用 new 运算符 'no-new': 'off', // 不允许在赋值或比较之外使用 new 运算符
'no-console': 'off', // 允许使用 console // 'no-console': 'error', // 禁止使用 console
'style/arrow-parens': ['error', 'always'], // 箭头函数参数需要括号 'style/arrow-parens': ['error', 'always'], // 箭头函数参数需要括号
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式
'regexp/no-unused-capturing-group': 'off', 'regexp/no-unused-capturing-group': 'off',

4301
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,9 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.4", "dayjs": "^1.11.4",
"echarts": "^5.4.2", "echarts": "^5.4.2",
"html2canvas": "^1.4.1",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"jspdf": "^3.0.1",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.0", "mitt": "^3.0.0",

View File

@ -68,9 +68,15 @@ importers:
echarts: echarts:
specifier: ^5.4.2 specifier: ^5.4.2
version: 5.5.0 version: 5.5.0
html2canvas:
specifier: ^1.4.1
version: 1.4.1
jsencrypt: jsencrypt:
specifier: ^3.3.2 specifier: ^3.3.2
version: 3.3.2 version: 3.3.2
jspdf:
specifier: ^3.0.1
version: 3.0.1
lint-staged: lint-staged:
specifier: ^15.2.10 specifier: ^15.2.10
version: 15.2.10 version: 15.2.10
@ -463,6 +469,10 @@ packages:
resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/runtime@7.28.2':
resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==}
engines: {node: '>=6.9.0'}
'@babel/template@7.24.0': '@babel/template@7.24.0':
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -1192,12 +1202,18 @@ packages:
resolution: {integrity: sha512-yuIv/WRffRzL7cBW+sla4HwBZrEXRNf1MKQ5SklPEadth+BKbDxiVG8A3iISN5B3yC4EeSCzMZP8llHTcUhOzQ==} resolution: {integrity: sha512-yuIv/WRffRzL7cBW+sla4HwBZrEXRNf1MKQ5SklPEadth+BKbDxiVG8A3iISN5B3yC4EeSCzMZP8llHTcUhOzQ==}
deprecated: This is a stub types definition. query-string provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. query-string provides its own type definitions, so you do not need this installed.
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/sortablejs@1.15.8': '@types/sortablejs@1.15.8':
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
'@types/svgo@2.6.4': '@types/svgo@2.6.4':
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/unist@2.0.10': '@types/unist@2.0.10':
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
@ -1608,6 +1624,10 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base@0.11.2: base@0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -1652,6 +1672,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
btoa@1.2.1:
resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
engines: {node: '>= 0.4.0'}
hasBin: true
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -1692,6 +1717,10 @@ packages:
caniuse-lite@1.0.30001620: caniuse-lite@1.0.30001620:
resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==} resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==}
canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'}
capital-case@1.0.4: capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@ -1883,6 +1912,9 @@ packages:
crypto-js@4.2.0: crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
css-select@4.3.0: css-select@4.3.0:
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
@ -2059,6 +2091,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
domutils@1.7.0: domutils@1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
@ -2612,6 +2647,9 @@ packages:
fastq@1.17.1: fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -2865,6 +2903,10 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@3.10.1: htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@ -3168,6 +3210,9 @@ packages:
jsonfile@6.1.0: jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jspdf@3.0.1:
resolution: {integrity: sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==}
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@ -3633,6 +3678,9 @@ packages:
perfect-debounce@1.0.0: perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.0.1: picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@ -3814,6 +3862,9 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
randombytes@2.1.0: randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -3837,6 +3888,9 @@ packages:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
regenerator-runtime@0.14.1: regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@ -3911,6 +3965,10 @@ packages:
rfdc@1.4.1: rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
rollup@4.17.2: rollup@4.17.2:
resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -4138,6 +4196,10 @@ packages:
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
static-extend@0.1.2: static-extend@0.1.2:
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4237,6 +4299,10 @@ packages:
svg-baker@1.7.0: svg-baker@1.7.0:
resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==} resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==}
svg-pathdata@6.0.3:
resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
engines: {node: '>=12.0.0'}
svg-tags@1.0.0: svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
@ -4274,6 +4340,9 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -4491,6 +4560,9 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
v-viewer@3.0.13: v-viewer@3.0.13:
resolution: {integrity: sha512-T8pgGzlF0ZCHVpD/32OKsD8MlpI6tqYP3n1XLcSjvGQMc0ABn8nJ4AumxvzAKVQrLRWtDTG6qRGAyCPCmi7ceA==} resolution: {integrity: sha512-T8pgGzlF0ZCHVpD/32OKsD8MlpI6tqYP3n1XLcSjvGQMc0ABn8nJ4AumxvzAKVQrLRWtDTG6qRGAyCPCmi7ceA==}
peerDependencies: peerDependencies:
@ -5071,6 +5143,8 @@ snapshots:
dependencies: dependencies:
regenerator-runtime: 0.14.1 regenerator-runtime: 0.14.1
'@babel/runtime@7.28.2': {}
'@babel/template@7.24.0': '@babel/template@7.24.0':
dependencies: dependencies:
'@babel/code-frame': 7.24.2 '@babel/code-frame': 7.24.2
@ -5797,12 +5871,18 @@ snapshots:
dependencies: dependencies:
query-string: 9.0.0 query-string: 9.0.0
'@types/raf@3.4.3':
optional: true
'@types/sortablejs@1.15.8': {} '@types/sortablejs@1.15.8': {}
'@types/svgo@2.6.4': '@types/svgo@2.6.4':
dependencies: dependencies:
'@types/node': 20.12.12 '@types/node': 20.12.12
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.10': {} '@types/unist@2.0.10': {}
'@types/web-bluetooth@0.0.20': {} '@types/web-bluetooth@0.0.20': {}
@ -6347,6 +6427,8 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-arraybuffer@1.0.2: {}
base@0.11.2: base@0.11.2:
dependencies: dependencies:
cache-base: 1.0.1 cache-base: 1.0.1
@ -6415,6 +6497,8 @@ snapshots:
node-releases: 2.0.14 node-releases: 2.0.14
update-browserslist-db: 1.0.16(browserslist@4.23.0) update-browserslist-db: 1.0.16(browserslist@4.23.0)
btoa@1.2.1: {}
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
builtin-modules@3.3.0: {} builtin-modules@3.3.0: {}
@ -6458,6 +6542,18 @@ snapshots:
caniuse-lite@1.0.30001620: {} caniuse-lite@1.0.30001620: {}
canvg@3.0.11:
dependencies:
'@babel/runtime': 7.28.2
'@types/raf': 3.4.3
core-js: 3.40.0
raf: 3.4.1
regenerator-runtime: 0.13.11
rgbcolor: 1.0.1
stackblur-canvas: 2.7.0
svg-pathdata: 6.0.3
optional: true
capital-case@1.0.4: capital-case@1.0.4:
dependencies: dependencies:
no-case: 3.0.4 no-case: 3.0.4
@ -6674,6 +6770,10 @@ snapshots:
crypto-js@4.2.0: {} crypto-js@4.2.0: {}
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-select@4.3.0: css-select@4.3.0:
dependencies: dependencies:
boolbase: 1.0.0 boolbase: 1.0.0
@ -6843,6 +6943,11 @@ snapshots:
dependencies: dependencies:
domelementtype: 2.3.0 domelementtype: 2.3.0
dompurify@3.2.6:
optionalDependencies:
'@types/trusted-types': 2.0.7
optional: true
domutils@1.7.0: domutils@1.7.0:
dependencies: dependencies:
dom-serializer: 0.2.2 dom-serializer: 0.2.2
@ -7530,6 +7635,8 @@ snapshots:
dependencies: dependencies:
reusify: 1.0.4 reusify: 1.0.4
fflate@0.8.2: {}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
@ -7777,6 +7884,11 @@ snapshots:
html-tags@3.3.1: {} html-tags@3.3.1: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
htmlparser2@3.10.1: htmlparser2@3.10.1:
dependencies: dependencies:
domelementtype: 1.3.1 domelementtype: 1.3.1
@ -8039,6 +8151,18 @@ snapshots:
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jspdf@3.0.1:
dependencies:
'@babel/runtime': 7.28.2
atob: 2.1.2
btoa: 1.2.1
fflate: 0.8.2
optionalDependencies:
canvg: 3.0.11
core-js: 3.40.0
dompurify: 3.2.6
html2canvas: 1.4.1
keyv@4.5.4: keyv@4.5.4:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
@ -8548,6 +8672,9 @@ snapshots:
perfect-debounce@1.0.0: {} perfect-debounce@1.0.0: {}
performance-now@2.1.0:
optional: true
picocolors@1.0.1: {} picocolors@1.0.1: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
@ -8764,6 +8891,11 @@ snapshots:
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
optional: true
randombytes@2.1.0: randombytes@2.1.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@ -8795,6 +8927,9 @@ snapshots:
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.10.0
regenerator-runtime@0.13.11:
optional: true
regenerator-runtime@0.14.1: {} regenerator-runtime@0.14.1: {}
regex-not@1.0.2: regex-not@1.0.2:
@ -8855,6 +8990,9 @@ snapshots:
rfdc@1.4.1: {} rfdc@1.4.1: {}
rgbcolor@1.0.1:
optional: true
rollup@4.17.2: rollup@4.17.2:
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
@ -9109,6 +9247,9 @@ snapshots:
stable@0.1.8: {} stable@0.1.8: {}
stackblur-canvas@2.7.0:
optional: true
static-extend@0.1.2: static-extend@0.1.2:
dependencies: dependencies:
define-property: 0.2.5 define-property: 0.2.5
@ -9225,6 +9366,9 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
svg-pathdata@6.0.3:
optional: true
svg-tags@1.0.0: {} svg-tags@1.0.0: {}
svgo@2.8.0: svgo@2.8.0:
@ -9259,6 +9403,10 @@ snapshots:
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
text-table@0.2.0: {} text-table@0.2.0: {}
tippy.js@6.3.7: tippy.js@6.3.7:
@ -9506,6 +9654,10 @@ snapshots:
utils-merge@1.0.1: {} utils-merge@1.0.1: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
v-viewer@3.0.13(viewerjs@1.11.6)(vue@3.5.12(typescript@5.0.4)): v-viewer@3.0.13(viewerjs@1.11.6)(vue@3.5.12(typescript@5.0.4)):
dependencies: dependencies:
lodash-es: 4.17.21 lodash-es: 4.17.21

View File

@ -17,14 +17,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore, useUserStore } from '@/stores' import { useAppStore, useUserStore } from '@/stores'
// 1
defineOptions({ name: 'App' }) defineOptions({ name: 'App' })
const userStore = useUserStore() const userStore = useUserStore()
const appStore = useAppStore() const appStore = useAppStore()
appStore.initTheme() appStore.initTheme()
appStore.initSiteConfig() appStore.initSiteConfig()
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.loading-icon { .loading-icon {
animation: arco-loading-circle 1s infinite cubic-bezier(0,0,1,1); animation: arco-loading-circle 1s infinite cubic-bezier(0,0,1,1);

View File

@ -1,12 +1,12 @@
import type { AttachInfoData, BusinessTypeResult } from './type'
import http from '@/utils/http' import http from '@/utils/http'
const { request } = http const { request } = http
import type { AttachInfoData, BusinessTypeResult } from './type'
/** /**
* *
* @param businessType * @param businessType
* @param files * @param files
* @returns
*/ */
export function batchAddAttachment(businessType: string, formData: FormData) { export function batchAddAttachment(businessType: string, formData: FormData) {
return request<AttachInfoData[]>({ return request<AttachInfoData[]>({
@ -14,8 +14,8 @@ export function batchAddAttachment(businessType: string, formData: FormData) {
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -30,8 +30,8 @@ export function addAttachment(formData: FormData) {
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
/** /**
@ -45,8 +45,8 @@ export function addAttachmentByDefectMarkPic(formData: FormData) {
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
/** /**
@ -60,8 +60,8 @@ export function addAttachInsurance(formData: FormData) {
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
/** /**
@ -70,7 +70,7 @@ export function addAttachInsurance(formData: FormData) {
export function getAttachBusinessTypes() { export function getAttachBusinessTypes() {
return request<BusinessTypeResult>({ return request<BusinessTypeResult>({
url: '/common/list/attach-business_type', url: '/common/list/attach-business_type',
method: 'get', method: 'get'
}) })
} }
@ -81,7 +81,7 @@ export function getAttachBusinessTypes() {
export function getAttachmentList(businessType: string) { export function getAttachmentList(businessType: string) {
return request<AttachInfoData[]>({ return request<AttachInfoData[]>({
url: `/attach-info/list/${businessType}`, url: `/attach-info/list/${businessType}`,
method: 'get', method: 'get'
}) })
} }
@ -92,6 +92,6 @@ export function getAttachmentList(businessType: string) {
export function deleteAttachment(id: string | number) { export function deleteAttachment(id: string | number) {
return request<boolean>({ return request<boolean>({
url: `/attach-info/${id}`, url: `/attach-info/${id}`,
method: 'delete', method: 'delete'
}) })
} }

View File

@ -31,4 +31,4 @@ export interface BusinessType {
name: string name: string
code: string code: string
description?: string description?: string
} }

View File

@ -1,5 +1,5 @@
import type { AttendanceRecordReq, AttendanceRecordResp } from './type'
import http from '@/utils/http' import http from '@/utils/http'
import type { AttendanceRecordReq, AttendanceRecordResp } from './type'
const BASE_URL = '/attendance-record' const BASE_URL = '/attendance-record'
@ -7,7 +7,7 @@ const BASE_URL = '/attendance-record'
export function addAttendanceRecord(data: AttendanceRecordReq) { export function addAttendanceRecord(data: AttendanceRecordReq) {
return http.post<AttendanceRecordResp>(BASE_URL, data, { return http.post<AttendanceRecordResp>(BASE_URL, data, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, }
}) })
} }

View File

@ -1,15 +1,15 @@
/** 新增考勤记录请求体 */ /** 新增考勤记录请求体 */
export interface AttendanceRecordReq { export interface AttendanceRecordReq {
recordImage?: string recordImage?: string
recordPosition?: string recordPosition?: string
recordPositionLabel?: string recordPositionLabel?: string
} }
/** 新增考勤记录响应体 */ /** 新增考勤记录响应体 */
export interface AttendanceRecordResp { export interface AttendanceRecordResp {
code: number code: number
data: object data: object
msg: string msg: string
status: number status: number
success: boolean success: boolean
} }

View File

@ -1,6 +1,6 @@
import type * as T from './type' import type * as T from './type'
import http from '@/utils/http' import http from '@/utils/http'
import { type ApiMenuItem, convertMenuData } from '@/utils/menuConverter' import { convertMenuData, type ApiMenuItem } from '@/utils/menuConverter'
export type * from './type' export type * from './type'

View File

@ -0,0 +1,62 @@
import http from '@/utils/http'
const { request } = http
// 文件信息
export interface KnowledgeFile {
id: string
name: string
size: string
type: string
uploadTime: string
}
// 文件夹信息
export interface KnowledgeFolder {
id: string
name: string
children?: KnowledgeFolder[]
}
// 获取文件夹树
export function getFolderTreeApi() {
return request<KnowledgeFolder[]>({
url: '/knowledge/folders',
method: 'get',
})
}
// 获取文件列表(按文件夹)
export function getFilesApi(folderId: string) {
return request<KnowledgeFile[]>({
url: '/knowledge/files',
method: 'get',
params: { folderId },
})
}
// 创建文件夹
export function createFolderApi(data: { name: string; parentId?: string }) {
return request({
url: '/knowledge/create-folder',
method: 'post',
data,
})
}
// 删除文件
export function deleteFileApi(fileId: string) {
return request({
url: `/knowledge/delete-file/${fileId}`,
method: 'delete',
})
}
// 下载文件
export function downloadFileApi(fileId: string) {
return request<Blob>({
url: `/knowledge/download/${fileId}`,
method: 'get',
responseType: 'blob',
})
}

View File

@ -66,9 +66,9 @@ export interface DefectTypeResp {
/** 缺陷类型选项类型 - 用于前端组件 */ /** 缺陷类型选项类型 - 用于前端组件 */
export interface DefectTypeOption { export interface DefectTypeOption {
code: string code: string;
label: string label: string;
value: string value: string;
name?: string // 兼容性字段 name?: string; // 兼容性字段
sort?: number // 兼容性字段 sort?: number; // 兼容性字段
} }

View File

@ -1,5 +1,5 @@
import type { CertificationInfo, CertificationListParams, CertificationListResponse, CertificationPageResponse, CertificationReq, SimpleUserInfo } from './type'
import http from '@/utils/http' import http from '@/utils/http'
import type { CertificationInfo, CertificationListParams, CertificationListResponse, SimpleUserInfo,CertificationPageResponse, CertificationReq } from './type'
const { request } = http const { request } = http
@ -11,7 +11,7 @@ export function createCertification(data: CertificationReq) {
return request({ return request({
url: '/certification', url: '/certification',
method: 'post', method: 'post',
data, data
}) })
} }
@ -20,7 +20,7 @@ export function getCertificationList(params: CertificationListParams) {
return request<CertificationListResponse>({ return request<CertificationListResponse>({
url: '/certification/list', url: '/certification/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -28,7 +28,7 @@ export function getCertificationList(params: CertificationListParams) {
export function getCertificationDetail(certificationId: string) { export function getCertificationDetail(certificationId: string) {
return request<CertificationInfo>({ return request<CertificationInfo>({
url: `/certification/detail/${certificationId}`, url: `/certification/detail/${certificationId}`,
method: 'get', method: 'get'
}) })
} }
@ -37,7 +37,7 @@ export function updateCertification(certificationId: string, data: Certification
return request({ return request({
url: `/certification/${certificationId}`, url: `/certification/${certificationId}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -45,7 +45,7 @@ export function updateCertification(certificationId: string, data: Certification
export function deleteCertification(certificationId: string) { export function deleteCertification(certificationId: string) {
return request({ return request({
url: `/certification/${certificationId}`, url: `/certification/${certificationId}`,
method: 'delete', method: 'delete'
}) })
} }
@ -54,7 +54,7 @@ export function batchDeleteCertification(ids: string[]) {
return request({ return request({
url: '/certification/batch', url: '/certification/batch',
method: 'delete', method: 'delete',
data: { ids }, data: { ids }
}) })
} }
@ -64,7 +64,7 @@ export function exportCertification(params: CertificationListParams) {
url: '/certification/export', url: '/certification/export',
method: 'get', method: 'get',
params, params,
responseType: 'blob', responseType: 'blob'
}) })
} }
@ -72,14 +72,14 @@ export function exportCertification(params: CertificationListParams) {
export function getUserList() { export function getUserList() {
return request<SimpleUserInfo[]>({ return request<SimpleUserInfo[]>({
url: '/user/list', url: '/user/list',
method: 'get', method: 'get'
}) })
} }
// 查询人员资质信息分页列表(新接口) // 查询人员资质信息分页列表(新接口)
export function getCertificationPage(params: CertificationListParams) { export function getCertificationPage(params: CertificationListParams) {
return request<CertificationPageResponse>({ return request<CertificationPageResponse>({
url: '/certification/page', url: '/certification/page',
method: 'get', method: 'get',
params, params
}) })
} }

View File

@ -40,7 +40,7 @@ export interface SimpleUserInfo {
userId: string userId: string
name: string name: string
account: string account: string
} }
export interface CertificationInfo { export interface CertificationInfo {
certificationId?: string certificationId?: string
certificationCode: string certificationCode: string
@ -56,4 +56,4 @@ export interface CertificationPageResponse {
code?: number code?: number
msg?: string msg?: string
[key: string]: any [key: string]: any
} }

View File

@ -5,45 +5,45 @@ const BASE_URL = '/equipment'
/** @desc 分页查询设备列表 */ /** @desc 分页查询设备列表 */
export function pageEquipment(query: T.EquipmentPageQuery) { export function pageEquipment(query: T.EquipmentPageQuery) {
return http.get<T.EquipmentResp[]>(`${BASE_URL}/page`, query) return http.get<T.EquipmentResp[]>(`${BASE_URL}/page`, query)
} }
/** @desc 查询设备列表 */ /** @desc 查询设备列表 */
export function listEquipment(query?: T.EquipmentPageQuery) { export function listEquipment(query?: T.EquipmentPageQuery) {
return http.get<T.EquipmentResp[]>(`${BASE_URL}/list`, query) return http.get<T.EquipmentResp[]>(`${BASE_URL}/list`, query)
} }
/** @desc 查询设备详情 */ /** @desc 查询设备详情 */
export function getEquipmentDetail(equipmentId: string) { export function getEquipmentDetail(equipmentId: string) {
return http.get<T.EquipmentResp>(`${BASE_URL}/detail/${equipmentId}`) return http.get<T.EquipmentResp>(`${BASE_URL}/detail/${equipmentId}`)
} }
/** @desc 新增设备 */ /** @desc 新增设备 */
export function createEquipment(data: T.EquipmentReq) { export function createEquipment(data: T.EquipmentReq) {
return http.post(`${BASE_URL}`, data) return http.post(`${BASE_URL}`, data)
} }
/** @desc 更新设备 */ /** @desc 更新设备 */
export function updateEquipment(equipmentId: string, data: T.EquipmentReq) { export function updateEquipment(equipmentId: string, data: T.EquipmentReq) {
return http.put(`${BASE_URL}/${equipmentId}`, data) return http.put(`${BASE_URL}/${equipmentId}`, data)
} }
/** @desc 删除设备 */ /** @desc 删除设备 */
export function deleteEquipment(equipmentId: string) { export function deleteEquipment(equipmentId: string) {
return http.del(`${BASE_URL}/${equipmentId}`) return http.del(`${BASE_URL}/${equipmentId}`)
} }
/** @desc 设备状态变更 */ /** @desc 设备状态变更 */
export function changeEquipmentStatus(equipmentId: string, status: string) { export function changeEquipmentStatus(equipmentId: string, status: string) {
return http.put(`${BASE_URL}/${equipmentId}/status`, { status }) return http.put(`${BASE_URL}/${equipmentId}/status`, { status })
} }
/** @desc 设备分配 */ /** @desc 设备分配 */
export function assignEquipment(equipmentId: string, userId: string) { export function assignEquipment(equipmentId: string, userId: string) {
return http.put(`${BASE_URL}/${equipmentId}/assign`, { userId }) return http.put(`${BASE_URL}/${equipmentId}/assign`, { userId })
} }
/** @desc 设备归还 */ /** @desc 设备归还 */
export function returnEquipment(equipmentId: string) { export function returnEquipment(equipmentId: string) {
return http.put(`${BASE_URL}/${equipmentId}/return`) return http.put(`${BASE_URL}/${equipmentId}/return`)
} }

View File

@ -2,206 +2,276 @@
* *
*/ */
export interface EquipmentListReq { export interface EquipmentListReq {
/** 设备名称 */ /** 最低价格 */
equipmentName?: string minPrice?: number
/** 设备类型 */ /** 最高价格 */
equipmentType?: string maxPrice?: number
/** 设备状态 */ /** 设备名称 */
equipmentStatus?: string equipmentName?: string
/** 设备序列号 */ /** 设备类型 */
equipmentSn?: string equipmentType?: string
/** 资产编号 */ /** 设备状态 */
assetCode?: string equipmentStatus?: string
/** 品牌 */ /** 设备序列号 */
brand?: string equipmentSn?: string
/** 位置状态 */ /** 资产编号 */
locationStatus?: string assetCode?: string
/** 健康状态 */ /** 品牌 */
healthStatus?: string brand?: string
/** 负责人 */ /** 位置状态 */
responsiblePerson?: string locationStatus?: string
/** 使用状态 */ /** 健康状态 */
useStatus?: string healthStatus?: string
/** 项目ID */ /** 负责人 */
projectId?: string responsiblePerson?: string
/** 使用人ID */ /** 使用状态 */
userId?: string useStatus?: string
/** 当前页码 */ /** 项目ID */
pageNum?: number projectId?: string
/** 每页大小 */ /** 使用人ID */
pageSize?: number userId?: string
/** 排序字段 */ /** 设备型号 */
orderBy?: string equipmentModel?: string
/** 排序方向 */ /** 配置规格/参数 */
orderDirection?: string specification?: string
/** 设备当前物理位置 */
physicalLocation?: string
/** 供应商名称 */
supplierName?: string
/** 采购订单号 */
purchaseOrder?: string
/** 维护人员 */
maintenancePerson?: string
/** 次户号 */
accountNumber?: string
/** 数量 */
quantity?: number
/** 单价 */
unitPrice?: number
/** 总价 */
totalPrice?: number
/** 盘点依据 */
inventoryBasis?: string
/** 动态记录 */
dynamicRecord?: string
/** 资产备注 */
assetRemark?: string
/** 采购时间开始 */
purchaseTimeStart?: string
/** 采购时间结束 */
purchaseTimeEnd?: string
/** 入库时间开始 */
inStockTimeStart?: string
/** 入库时间结束 */
inStockTimeEnd?: string
/** 启用时间开始 */
activationTimeStart?: string
/** 启用时间结束 */
activationTimeEnd?: string
/** 当前页码 */
pageNum?: number
/** 每页大小 */
pageSize?: number
/** 排序字段 */
orderBy?: string
/** 排序方向 */
orderDirection?: string
/** 页码 */
page?: number
/** 库存条码 */
inventoryBarcode?: string
} }
/** /**
* *
*/ */
export interface PageResult<T> { export interface PageResult<T> {
code: number code: number
msg: string msg: string
rows: T[] rows: T[]
total: number total: number
} }
/** /**
* *
*/ */
export interface EquipmentResp { export interface EquipmentResp {
/** 设备ID */ /** 设备ID */
equipmentId: string equipmentId: string
/** 资产编号 */ /** 资产编号 */
assetCode?: string assetCode?: string
/** 设备名称 */ /** 设备名称 */
equipmentName: string equipmentName: string
/** 设备类型 */ /** 设备类型 */
equipmentType: string equipmentType: string
/** 设备类型描述 */ /** 设备类型描述 */
equipmentTypeLabel?: string equipmentTypeLabel?: string
/** 设备型号 */ /** 设备型号 */
equipmentModel: string equipmentModel: string
/** 设备SN */ /** 设备SN */
equipmentSn: string equipmentSn: string
/** 品牌 */ /** 品牌 */
brand?: string brand?: string
/** 配置规格/参数 */ /** 配置规格/参数 */
specification?: string specification?: string
/** 设备状态 */ /** 设备状态 */
equipmentStatus: string equipmentStatus: string
/** 设备状态描述 */ /** 设备状态描述 */
equipmentStatusLabel?: string equipmentStatusLabel?: string
/** 使用状态 */ /** 使用状态 */
useStatus: string useStatus: string
/** 位置状态 */ /** 位置状态 */
locationStatus?: string locationStatus?: string
/** 位置状态描述 */ /** 位置状态描述 */
locationStatusLabel?: string locationStatusLabel?: string
/** 设备当前物理位置 */ /** 设备当前物理位置 */
physicalLocation?: string physicalLocation?: string
/** 负责人 */ /** 负责人 */
responsiblePerson?: string responsiblePerson?: string
/** 健康状态 */ /** 健康状态 */
healthStatus?: string healthStatus?: string
/** 健康状态描述 */ /** 健康状态描述 */
healthStatusLabel?: string healthStatusLabel?: string
/** 采购时间 */ /** 采购时间 */
purchaseTime?: string purchaseTime?: string
/** 入库时间 */ /** 入库时间 */
inStockTime?: string inStockTime?: string
/** 启用时间 */ /** 启用时间 */
activationTime?: string activationTime?: string
/** 预计报废时间 */ /** 预计报废时间 */
expectedScrapTime?: string expectedScrapTime?: string
/** 实际报废时间 */ /** 实际报废时间 */
actualScrapTime?: string actualScrapTime?: string
/** 状态变更时间 */ /** 状态变更时间 */
statusChangeTime?: string statusChangeTime?: string
/** 采购订单 */ /** 采购订单 */
purchaseOrder?: string purchaseOrder?: string
/** 供应商名称 */ /** 供应商名称 */
supplierName?: string supplierName?: string
/** 采购价格 */ /** 采购价格 */
purchasePrice?: number purchasePrice?: number
/** 当前净值 */ /** 当前净值 */
currentNetValue?: number currentNetValue?: number
/** 折旧方法 */ /** 折旧方法 */
depreciationMethod?: string depreciationMethod?: string
/** 折旧年限 */ /** 折旧年限 */
depreciationYears?: number depreciationYears?: number
/** 残值 */ /** 残值 */
salvageValue?: number salvageValue?: number
/** 保修截止日期 */ /** 保修截止日期 */
warrantyExpireDate?: string warrantyExpireDate?: string
/** 上次维护日期 */ /** 上次维护日期 */
lastMaintenanceDate?: string lastMaintenanceDate?: string
/** 下次维护日期 */ /** 下次维护日期 */
nextMaintenanceDate?: string nextMaintenanceDate?: string
/** 维护人员 */ /** 维护人员 */
maintenancePerson?: string maintenancePerson?: string
/** 库存条码 */ /** 库存条码 */
inventoryBarcode?: string inventoryBarcode?: string
/** 资产备注 */ /** 资产备注 */
assetRemark?: string assetRemark?: string
/** 项目ID */ /** 项目ID */
projectId?: string projectId?: string
/** 项目名称 */ /** 项目名称 */
projectName?: string projectName?: string
/** 使用人ID */ /** 使用人ID */
userId?: string userId?: string
/** 使用人 */ /** 使用人 */
name?: string name?: string
/** 创建时间 */ /** 创建时间 */
createTime?: string createTime?: string
/** 更新时间 */ /** 更新时间 */
updateTime?: string updateTime?: string
/** 次户号 */
accountNumber?: string
/** 数量 */
quantity?: number
/** 单价 */
unitPrice?: number
/** 总价 */
totalPrice?: number
/** 盘点依据 */
inventoryBasis?: string
/** 动态记录 */
dynamicRecord?: string
} }
/** /**
* *
*/ */
export interface EquipmentReq { export interface EquipmentReq {
/** 设备名称 */ /** 设备名称 */
equipmentName: string equipmentName: string
/** 设备型号 */ /** 设备型号 */
equipmentModel: string equipmentModel: string
/** 设备类型 */ /** 设备类型 */
equipmentType: string equipmentType: string
/** 设备状态 */ /** 设备状态 */
equipmentStatus: string equipmentStatus: string
/** 使用状态 */ /** 使用状态 */
useStatus: string useStatus: string
/** 设备序列号 */ /** 设备序列号 */
equipmentSn: string equipmentSn: string
/** 资产编号 */ /** 资产编号 */
assetCode?: string assetCode?: string
/** 品牌 */ /** 品牌 */
brand?: string brand?: string
/** 配置规格/参数 */ /** 配置规格/参数 */
specification?: string specification?: string
/** 位置状态 */ /** 位置状态 */
locationStatus?: string locationStatus?: string
/** 设备当前物理位置 */ /** 设备当前物理位置 */
physicalLocation?: string physicalLocation?: string
/** 负责人 */ /** 负责人 */
responsiblePerson?: string responsiblePerson?: string
/** 健康状态 */ /** 健康状态 */
healthStatus?: string healthStatus?: string
/** 采购时间 */ /** 采购时间 */
purchaseTime?: string purchaseTime?: string
/** 入库时间 */ /** 入库时间 */
inStockTime?: string inStockTime?: string
/** 启用时间 */ /** 启用时间 */
activationTime?: string activationTime?: string
/** 预计报废时间 */ /** 预计报废时间 */
expectedScrapTime?: string expectedScrapTime?: string
/** 实际报废时间 */ /** 实际报废时间 */
actualScrapTime?: string actualScrapTime?: string
/** 采购订单 */ /** 采购订单 */
purchaseOrder?: string purchaseOrder?: string
/** 供应商名称 */ /** 供应商名称 */
supplierName?: string supplierName?: string
/** 采购价格 */ /** 采购价格 */
purchasePrice?: number purchasePrice?: number
/** 当前净值 */ /** 当前净值 */
currentNetValue?: number currentNetValue?: number
/** 折旧方法 */ /** 折旧方法 */
depreciationMethod?: string depreciationMethod?: string
/** 折旧年限 */ /** 折旧年限 */
depreciationYears?: number depreciationYears?: number
/** 残值 */ /** 残值 */
salvageValue?: number salvageValue?: number
/** 保修截止日期 */ /** 保修截止日期 */
warrantyExpireDate?: string warrantyExpireDate?: string
/** 上次维护日期 */ /** 上次维护日期 */
lastMaintenanceDate?: string lastMaintenanceDate?: string
/** 下次维护日期 */ /** 下次维护日期 */
nextMaintenanceDate?: string nextMaintenanceDate?: string
/** 维护人员 */ /** 维护人员 */
maintenancePerson?: string maintenancePerson?: string
/** 库存条码 */ /** 库存条码 */
inventoryBarcode?: string inventoryBarcode?: string
/** 资产备注 */ /** 资产备注 */
assetRemark?: string assetRemark?: string
/** 次户号 */
accountNumber?: string
/** 数量 */
quantity?: number
/** 单价 */
unitPrice?: number
/** 总价 */
totalPrice?: number
/** 盘点依据 */
inventoryBasis?: string
/** 动态记录 */
dynamicRecord?: string
} }

View File

@ -40,7 +40,7 @@ export function createHealthRecord(data: HealthRecord) {
return request({ return request({
url: '/health-record', url: '/health-record',
method: 'post', method: 'post',
data, data
}) })
} }
@ -49,7 +49,7 @@ export function getHealthRecordList(params: HealthRecordListParams) {
return request<HealthRecordListResponse>({ return request<HealthRecordListResponse>({
url: '/health-record/list', url: '/health-record/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -57,7 +57,7 @@ export function getHealthRecordList(params: HealthRecordListParams) {
export function getHealthRecordDetail(id: string) { export function getHealthRecordDetail(id: string) {
return request<HealthRecord>({ return request<HealthRecord>({
url: `/health-record/detail/${id}`, url: `/health-record/detail/${id}`,
method: 'get', method: 'get'
}) })
} }
@ -66,7 +66,7 @@ export function updateHealthRecord(id: string, data: HealthRecord) {
return request({ return request({
url: `/health-record/${id}`, url: `/health-record/${id}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -74,7 +74,7 @@ export function updateHealthRecord(id: string, data: HealthRecord) {
export function deleteHealthRecord(id: string) { export function deleteHealthRecord(id: string) {
return request({ return request({
url: `/health-record/${id}`, url: `/health-record/${id}`,
method: 'delete', method: 'delete'
}) })
} }
@ -83,14 +83,14 @@ export function uploadHealthReport(file: File, recordId: string) {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
formData.append('recordId', recordId) formData.append('recordId', recordId)
return request({ return request({
url: '/health-record/upload-report', url: '/health-record/upload-report',
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -99,7 +99,7 @@ export function downloadHealthReport(fileId: string) {
return request({ return request({
url: `/health-record/download-report/${fileId}`, url: `/health-record/download-report/${fileId}`,
method: 'get', method: 'get',
responseType: 'blob', responseType: 'blob'
}) })
} }
@ -107,7 +107,7 @@ export function downloadHealthReport(fileId: string) {
export function getEmployeeHealthHistory(employeeId: string) { export function getEmployeeHealthHistory(employeeId: string) {
return request<HealthRecord[]>({ return request<HealthRecord[]>({
url: `/health-record/employee/${employeeId}`, url: `/health-record/employee/${employeeId}`,
method: 'get', method: 'get'
}) })
} }
@ -117,7 +117,7 @@ export function exportHealthRecords(params: HealthRecordListParams) {
url: '/health-record/export', url: '/health-record/export',
method: 'get', method: 'get',
params, params,
responseType: 'blob', responseType: 'blob'
}) })
} }
@ -131,6 +131,6 @@ export function scheduleHealthCheck(data: {
return request({ return request({
url: '/health-record/schedule', url: '/health-record/schedule',
method: 'post', method: 'post',
data, data
}) })
} }

View File

@ -16,6 +16,7 @@ export * as InsuranceTypeAPI from './insurance-type'
export * as HealthRecordAPI from './health-record' export * as HealthRecordAPI from './health-record'
export * as InsuranceFileAPI from './insurance-file' export * as InsuranceFileAPI from './insurance-file'
export * as EmployeeAPI from './employee' export * as EmployeeAPI from './employee'
export * as RegulationAPI from './regulation'
export * from './area/type' export * from './area/type'
export * from './auth/type' export * from './auth/type'

View File

@ -87,7 +87,7 @@ export const detectDefects = (params: DefectDetectionRequest) => {
} }
/** @desc 手动添加缺陷记录 */ /** @desc 手动添加缺陷记录 */
export const addManualDefect = (params: ManualDefectAddRequest, imageId: string) => { export const addManualDefect = (params: ManualDefectAddRequest,imageId:string) => {
return http.post<ManualDefectAddResponse>(`/defect/${imageId}`, params) return http.post<ManualDefectAddResponse>(`/defect/${imageId}`, params)
} }
@ -95,12 +95,12 @@ export const addManualDefect = (params: ManualDefectAddRequest, imageId: string)
// 缺陷列表查询参数接口 // 缺陷列表查询参数接口
export interface DefectListParams { export interface DefectListParams {
defectId?: string defectId?: string;
defectLevel?: string defectLevel?: string;
defectType?: string defectType?: string;
keyword?: string keyword?: string;
turbineId?: string turbineId?: string;
imageId?: string // 添加imageId参数用于按图像筛选缺陷 imageId?: string; // 添加imageId参数用于按图像筛选缺陷
} }
/** @desc 获取缺陷列表 */ /** @desc 获取缺陷列表 */
@ -111,7 +111,7 @@ export const getDefectList = (params: DefectListParams) => {
msg: string msg: string
status: number status: number
success: boolean success: boolean
}>('/defect/list', params) }>('/defect/list', params )
} }
/** @desc 添加缺陷 */ /** @desc 添加缺陷 */
@ -150,7 +150,7 @@ export const deleteDefect = (defectId: string) => {
export const uploadAnnotatedImage = (imageBlob: Blob, fileName: string) => { export const uploadAnnotatedImage = (imageBlob: Blob, fileName: string) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', imageBlob, fileName) formData.append('file', imageBlob, fileName)
return http.post<{ return http.post<{
code: number code: number
data: AttachInfoData data: AttachInfoData
@ -159,8 +159,8 @@ export const uploadAnnotatedImage = (imageBlob: Blob, fileName: string) => {
success: boolean success: boolean
}>('/attach-info/defect_mark_pic', formData, { }>('/attach-info/defect_mark_pic', formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -177,63 +177,63 @@ export interface AttachInfoData {
// 缺陷信息接口 // 缺陷信息接口
export interface DefectInfo { export interface DefectInfo {
id: string id: string;
defectId?: string defectId?: string;
defectName?: string defectName?: string;
defectLevel?: string defectLevel?: string;
defectType?: string defectType?: string;
defectPosition?: string defectPosition?: string;
detectionDate?: string detectionDate?: string;
description?: string description?: string;
repairStatus?: string repairStatus?: string;
repairIdea?: string repairIdea?: string;
labelInfo?: string labelInfo?: string;
markInfo?: { markInfo?: {
bbox?: number[] bbox?: number[];
clsId?: number clsId?: number;
confidence?: number confidence?: number;
label?: string label?: string;
[key: string]: any [key: string]: any;
} };
[key: string]: any [key: string]: any;
} }
// 缺陷等级类型 // 缺陷等级类型
export interface DefectLevelType { export interface DefectLevelType {
code: string code: string;
name: string name: string;
value: string value: string;
sort: number sort: number;
description?: string description?: string;
} }
// 缺陷类型 // 缺陷类型
export interface DefectType { export interface DefectType {
code: string code: string;
name: string name: string;
value: string value: string;
sort: number sort: number;
description?: string description?: string;
} }
// 获取缺陷等级列表 // 获取缺陷等级列表
export const getDefectLevels = () => { export const getDefectLevels = () => {
return http.get<{ return http.get<{
code: number code: number;
data: DefectLevelType[] data: DefectLevelType[];
msg: string msg: string;
status: number status: number;
success: boolean success: boolean;
}>('/common/list/defect-level') }>('/common/list/defect-level')
} }
// 获取缺陷类型列表 // 获取缺陷类型列表
export const getDefectTypes = () => { export const getDefectTypes = () => {
return http.get<{ return http.get<{
code: number code: number;
data: DefectType[] data: DefectType[];
msg: string msg: string;
status: number status: number;
success: boolean success: boolean;
}>('/common/list/defect-type') }>('/common/list/defect-type')
} }

View File

@ -114,7 +114,7 @@ export const uploadSingleImage = (imageSource: string, file: File, params?: {
}) => { }) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
// 构建查询参数 // 构建查询参数
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params?.altitude) queryParams.append('altitude', params.altitude) if (params?.altitude) queryParams.append('altitude', params.altitude)
@ -122,13 +122,13 @@ export const uploadSingleImage = (imageSource: string, file: File, params?: {
if (params?.longitude) queryParams.append('longitude', params.longitude) if (params?.longitude) queryParams.append('longitude', params.longitude)
if (params?.partId) queryParams.append('partId', params.partId) if (params?.partId) queryParams.append('partId', params.partId)
if (params?.uploadUser) queryParams.append('uploadUser', params.uploadUser) if (params?.uploadUser) queryParams.append('uploadUser', params.uploadUser)
const url = `/common/upload-image/${imageSource}${queryParams.toString() ? `?${queryParams.toString()}` : ''}` const url = `/common/upload-image/${imageSource}${queryParams.toString() ? '?' + queryParams.toString() : ''}`
return http.post(url, formData, { return http.post(url, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -141,12 +141,12 @@ export const batchUploadImages = (imageSource: string, files: File[], params?: {
uploadUser?: string uploadUser?: string
}) => { }) => {
const formData = new FormData() const formData = new FormData()
// 添加文件 // 添加文件
files.forEach((file) => { files.forEach(file => {
formData.append('files', file) formData.append('files', file)
}) })
// 构建查询参数 // 构建查询参数
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params?.altitude) queryParams.append('altitude', params.altitude) if (params?.altitude) queryParams.append('altitude', params.altitude)
@ -154,13 +154,13 @@ export const batchUploadImages = (imageSource: string, files: File[], params?: {
if (params?.longitude) queryParams.append('longitude', params.longitude) if (params?.longitude) queryParams.append('longitude', params.longitude)
if (params?.partId) queryParams.append('partId', params.partId) if (params?.partId) queryParams.append('partId', params.partId)
if (params?.uploadUser) queryParams.append('uploadUser', params.uploadUser) if (params?.uploadUser) queryParams.append('uploadUser', params.uploadUser)
const url = `/common/batch-upload-image/${imageSource}${queryParams.toString() ? `?${queryParams.toString()}` : ''}` const url = `/common/batch-upload-image/${imageSource}${queryParams.toString() ? '?' + queryParams.toString() : ''}`
return http.post(url, formData, { return http.post(url, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -173,8 +173,8 @@ export const detectDefects = (params: {
}) => { }) => {
return http.post('/defect/detect', params, { return http.post('/defect/detect', params, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, }
}) })
} }
@ -183,15 +183,15 @@ export const uploadImageToPartV2 = (
imageSource: string, imageSource: string,
partId: string, partId: string,
files: File[], files: File[],
params: Partial<T.ImageUploadParams>, params: Partial<T.ImageUploadParams>
) => { ) => {
const formData = new FormData() const formData = new FormData()
// 添加文件 // 添加文件
files.forEach((file) => { files.forEach(file => {
formData.append('files', file) formData.append('files', file)
}) })
// 添加其他参数 // 添加其他参数
if (params.collectorId) formData.append('collectorId', params.collectorId) if (params.collectorId) formData.append('collectorId', params.collectorId)
if (params.collectorName) formData.append('collectorName', params.collectorName) if (params.collectorName) formData.append('collectorName', params.collectorName)
@ -223,23 +223,23 @@ export const uploadImageToPartV2 = (
if (params.temperatureMin !== undefined) formData.append('temperatureMin', params.temperatureMin.toString()) if (params.temperatureMin !== undefined) formData.append('temperatureMin', params.temperatureMin.toString())
if (params.weather) formData.append('weather', params.weather) if (params.weather) formData.append('weather', params.weather)
if (params.windLevel !== undefined) formData.append('windLevel', params.windLevel.toString()) if (params.windLevel !== undefined) formData.append('windLevel', params.windLevel.toString())
return http.post(`/image/${imageSource}/upload/${partId}`, formData, { return http.post(`/image/${imageSource}/upload/${partId}`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
/** @desc 图像导入接口(更新为使用真实接口) */ /** @desc 图像导入接口(更新为使用真实接口) */
export const importImages = (files: FileList | File[], params: T.ImageImportParams) => { export const importImages = (files: FileList | File[], params: T.ImageImportParams) => {
const fileArray = Array.from(files) const fileArray = Array.from(files)
// 使用批量上传接口 // 使用批量上传接口
return batchUploadImages(params.imageSource || 'default', fileArray, { return batchUploadImages(params.imageSource || 'default', fileArray, {
partId: params.componentId, partId: params.componentId,
uploadUser: params.uploadUser, uploadUser: params.uploadUser
}).then((response) => { }).then(response => {
// 如果需要自动标注 // 如果需要自动标注
if (params.autoAnnotate && params.annotationTypes && params.annotationTypes.length > 0) { if (params.autoAnnotate && params.annotationTypes && params.annotationTypes.length > 0) {
// 这里可以添加自动标注逻辑 // 这里可以添加自动标注逻辑
@ -255,7 +255,7 @@ export const autoAnnotateImage = (params: T.AutoAnnotationParams) => {
confThreshold: params.confidenceThreshold || 0.5, confThreshold: params.confidenceThreshold || 0.5,
defectTypeList: params.annotationTypes, defectTypeList: params.annotationTypes,
imageId: params.imageId, imageId: params.imageId,
modelId: params.params?.modelId || 'default', modelId: params.params?.modelId || 'default'
}) })
} }
@ -275,10 +275,10 @@ export const confirmAnnotation = (imageId: string, annotationId: string) => {
} }
/** @desc 上传图像(保留旧接口兼容性) */ /** @desc 上传图像(保留旧接口兼容性) */
export const uploadImage = (file: File, params: { projectId: string, componentId?: string }) => { export const uploadImage = (file: File, params: { projectId: string; componentId?: string }) => {
return uploadSingleImage('default', file, { return uploadSingleImage('default', file, {
partId: params.componentId, partId: params.componentId,
uploadUser: 'current-user', uploadUser: 'current-user'
}) })
} }
@ -321,20 +321,21 @@ export function reprocessImage(params: T.ImageProcessParams) {
export const batchProcessImages = (imageIds: string[], processType: string) => { export const batchProcessImages = (imageIds: string[], processType: string) => {
return http.post<T.ImageProcessResult[]>(`/industrial-image/batch-process`, { return http.post<T.ImageProcessResult[]>(`/industrial-image/batch-process`, {
imageIds, imageIds,
processType, processType
}) })
} }
/** @desc 导出处理结果 */ /** @desc 导出处理结果 */
export function exportProcessResults(query: T.ImageQuery) { export function exportProcessResults(query: T.ImageQuery) {
return http.get(`/industrial-image/export/results`, query, { return http.get(`/industrial-image/export/results`, query, {
responseType: 'blob', responseType: 'blob'
}) })
} }
/** @desc 生成检测报告 */ /** @desc 生成检测报告 */
export function generateReport(projectId: string) { export function generateReport(projectId: string) {
return http.post(`/industrial-image/report/generate`, { projectId }, { return http.post(`/industrial-image/report/generate`, { projectId }, {
responseType: 'blob', responseType: 'blob'
}) })
} }

View File

@ -153,7 +153,7 @@ export interface IndustrialImage {
name: string name: string
/** 图像路径 */ /** 图像路径 */
path: string path: string
/** 图像路径API返回字段 */ /** 图像路径API返回字段*/
imagePath?: string imagePath?: string
/** 缩略图路径 */ /** 缩略图路径 */
thumbnailPath?: string thumbnailPath?: string
@ -185,7 +185,7 @@ export interface IndustrialImage {
createTime?: string createTime?: string
/** 更新时间 */ /** 更新时间 */
updateTime?: string updateTime?: string
// 扩展字段 - 来自真实API // 扩展字段 - 来自真实API
/** 部件名称 */ /** 部件名称 */
partName?: string partName?: string
@ -412,4 +412,4 @@ export interface ImageProcessResult {
createTime?: string createTime?: string
/** 完成时间 */ /** 完成时间 */
completeTime?: string completeTime?: string
} }

View File

@ -34,7 +34,7 @@ export function createInsuranceCompany(data: InsuranceCompany) {
return request({ return request({
url: '/insurance-company', url: '/insurance-company',
method: 'post', method: 'post',
data, data
}) })
} }
@ -43,7 +43,7 @@ export function getInsuranceCompanyList(params: InsuranceCompanyListParams) {
return request<InsuranceCompanyListResponse>({ return request<InsuranceCompanyListResponse>({
url: '/insurance-company/list', url: '/insurance-company/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -51,7 +51,7 @@ export function getInsuranceCompanyList(params: InsuranceCompanyListParams) {
export function getInsuranceCompanyDetail(id: string) { export function getInsuranceCompanyDetail(id: string) {
return request<InsuranceCompany>({ return request<InsuranceCompany>({
url: `/insurance-company/detail/${id}`, url: `/insurance-company/detail/${id}`,
method: 'get', method: 'get'
}) })
} }
@ -60,7 +60,7 @@ export function updateInsuranceCompany(id: string, data: InsuranceCompany) {
return request({ return request({
url: `/insurance-company/${id}`, url: `/insurance-company/${id}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -68,7 +68,7 @@ export function updateInsuranceCompany(id: string, data: InsuranceCompany) {
export function deleteInsuranceCompany(id: string) { export function deleteInsuranceCompany(id: string) {
return request({ return request({
url: `/insurance-company/${id}`, url: `/insurance-company/${id}`,
method: 'delete', method: 'delete'
}) })
} }
@ -76,7 +76,7 @@ export function deleteInsuranceCompany(id: string) {
export function terminateCooperation(id: string) { export function terminateCooperation(id: string) {
return request({ return request({
url: `/insurance-company/terminate/${id}`, url: `/insurance-company/terminate/${id}`,
method: 'post', method: 'post'
}) })
} }
@ -84,7 +84,7 @@ export function terminateCooperation(id: string) {
export function resumeCooperation(id: string) { export function resumeCooperation(id: string) {
return request({ return request({
url: `/insurance-company/resume/${id}`, url: `/insurance-company/resume/${id}`,
method: 'post', method: 'post'
}) })
} }

View File

@ -48,14 +48,14 @@ export function uploadInsuranceFile(data: UploadInsuranceFileParams) {
if (data.description) { if (data.description) {
formData.append('description', data.description) formData.append('description', data.description)
} }
return request({ return request({
url: '/insurance-file/upload', url: '/insurance-file/upload',
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -64,7 +64,7 @@ export function getInsuranceFileList(params: InsuranceFileListParams) {
return request<InsuranceFileListResponse>({ return request<InsuranceFileListResponse>({
url: '/insurance-file/list', url: '/insurance-file/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -72,7 +72,7 @@ export function getInsuranceFileList(params: InsuranceFileListParams) {
export function getInsuranceFileDetail(id: string) { export function getInsuranceFileDetail(id: string) {
return request<InsuranceFile>({ return request<InsuranceFile>({
url: `/insurance-file/detail/${id}`, url: `/insurance-file/detail/${id}`,
method: 'get', method: 'get'
}) })
} }
@ -81,7 +81,7 @@ export function updateInsuranceFile(id: string, data: Partial<InsuranceFile>) {
return request({ return request({
url: `/insurance-file/${id}`, url: `/insurance-file/${id}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -89,7 +89,7 @@ export function updateInsuranceFile(id: string, data: Partial<InsuranceFile>) {
export function deleteInsuranceFile(id: string) { export function deleteInsuranceFile(id: string) {
return request({ return request({
url: `/insurance-file/${id}`, url: `/insurance-file/${id}`,
method: 'delete', method: 'delete'
}) })
} }
@ -98,7 +98,7 @@ export function batchDeleteInsuranceFiles(ids: string[]) {
return request({ return request({
url: '/insurance-file/batch', url: '/insurance-file/batch',
method: 'delete', method: 'delete',
data: { ids }, data: { ids }
}) })
} }
@ -107,7 +107,7 @@ export function downloadInsuranceFile(id: string) {
return request({ return request({
url: `/insurance-file/download/${id}`, url: `/insurance-file/download/${id}`,
method: 'get', method: 'get',
responseType: 'blob', responseType: 'blob'
}) })
} }
@ -116,7 +116,7 @@ export function previewInsuranceFile(id: string) {
return request({ return request({
url: `/insurance-file/preview/${id}`, url: `/insurance-file/preview/${id}`,
method: 'get', method: 'get',
responseType: 'blob', responseType: 'blob'
}) })
} }
@ -124,7 +124,7 @@ export function previewInsuranceFile(id: string) {
export function getEmployeeFiles(employeeId: string) { export function getEmployeeFiles(employeeId: string) {
return request<InsuranceFile[]>({ return request<InsuranceFile[]>({
url: `/insurance-file/employee/${employeeId}`, url: `/insurance-file/employee/${employeeId}`,
method: 'get', method: 'get'
}) })
} }
@ -136,7 +136,7 @@ export function getInsuranceFileStatistics() {
totalSize: number totalSize: number
}[]>({ }[]>({
url: '/insurance-file/statistics', url: '/insurance-file/statistics',
method: 'get', method: 'get'
}) })
} }
@ -156,13 +156,13 @@ export function batchUploadFiles(data: {
if (data.description) { if (data.description) {
formData.append('description', data.description) formData.append('description', data.description)
} }
return request({ return request({
url: '/insurance-file/batch-upload', url: '/insurance-file/batch-upload',
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }

View File

@ -28,7 +28,7 @@ export function createInsuranceType(data: InsuranceType) {
return request({ return request({
url: '/insurance-type', url: '/insurance-type',
method: 'post', method: 'post',
data, data
}) })
} }
@ -37,7 +37,7 @@ export function getInsuranceTypeList(params?: InsuranceTypeListParams) {
return request<InsuranceTypeListResponse>({ return request<InsuranceTypeListResponse>({
url: '/insurance-type/list', url: '/insurance-type/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -45,7 +45,7 @@ export function getInsuranceTypeList(params?: InsuranceTypeListParams) {
export function getInsuranceTypeDetail(insuranceTypeId: string) { export function getInsuranceTypeDetail(insuranceTypeId: string) {
return request<InsuranceType>({ return request<InsuranceType>({
url: `/insurance-type/detail/${insuranceTypeId}`, url: `/insurance-type/detail/${insuranceTypeId}`,
method: 'get', method: 'get'
}) })
} }
@ -54,7 +54,7 @@ export function updateInsuranceType(id: string, data: InsuranceType) {
return request({ return request({
url: `/insurance-type/${id}`, url: `/insurance-type/${id}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -62,7 +62,7 @@ export function updateInsuranceType(id: string, data: InsuranceType) {
export function deleteInsuranceType(id: string) { export function deleteInsuranceType(id: string) {
return request({ return request({
url: `/insurance-type/${id}`, url: `/insurance-type/${id}`,
method: 'delete', method: 'delete'
}) })
} }
@ -71,6 +71,6 @@ export function batchDeleteInsuranceType(ids: string[]) {
return request({ return request({
url: '/insurance-type/batch', url: '/insurance-type/batch',
method: 'delete', method: 'delete',
data: { ids }, data: { ids }
}) })
} }

View File

@ -1,5 +1,5 @@
import type { InsuranceInfo, InsuranceListParams, InsuranceListResponse, RenewInsuranceParams } from './type'
import http from '@/utils/http' import http from '@/utils/http'
import type { InsuranceInfo, InsuranceListParams, InsuranceListResponse, RenewInsuranceParams } from './type'
const { request } = http const { request } = http
@ -11,7 +11,7 @@ export function createInsurance(data: InsuranceInfo) {
return request({ return request({
url: '/insurance-info', url: '/insurance-info',
method: 'post', method: 'post',
data, data
}) })
} }
@ -20,7 +20,7 @@ export function getInsuranceList(params: InsuranceListParams) {
return request<InsuranceListResponse>({ return request<InsuranceListResponse>({
url: '/insurance-info/list', url: '/insurance-info/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -28,7 +28,7 @@ export function getInsuranceList(params: InsuranceListParams) {
export function getInsuranceDetail(id: string) { export function getInsuranceDetail(id: string) {
return request<InsuranceInfo>({ return request<InsuranceInfo>({
url: `/insurance-info/detail/${id}`, url: `/insurance-info/detail/${id}`,
method: 'get', method: 'get'
}) })
} }
@ -37,7 +37,7 @@ export function updateInsurance(id: string, data: InsuranceInfo) {
return request({ return request({
url: `/insurance-info/${id}`, url: `/insurance-info/${id}`,
method: 'put', method: 'put',
data, data
}) })
} }
@ -45,7 +45,7 @@ export function updateInsurance(id: string, data: InsuranceInfo) {
export function deleteInsurance(id: string) { export function deleteInsurance(id: string) {
return request({ return request({
url: `/insurance-info/${id}`, url: `/insurance-info/${id}`,
method: 'delete', method: 'delete'
}) })
} }
@ -54,7 +54,7 @@ export function renewInsurance(id: string, data: RenewInsuranceParams) {
return request({ return request({
url: `/insurance-info/renew/${id}`, url: `/insurance-info/renew/${id}`,
method: 'post', method: 'post',
data, data
}) })
} }
@ -63,7 +63,7 @@ export function batchDeleteInsurance(ids: string[]) {
return request({ return request({
url: '/insurance-info/batch', url: '/insurance-info/batch',
method: 'delete', method: 'delete',
data: { ids }, data: { ids }
}) })
} }
@ -73,6 +73,6 @@ export function exportInsurance(params: InsuranceListParams) {
url: '/insurance-info/export', url: '/insurance-info/export',
method: 'get', method: 'get',
params, params,
responseType: 'blob', responseType: 'blob'
}) })
} }

View File

@ -1,7 +1,7 @@
/** 保险信息接口 */ /** 保险信息接口 */
export interface InsuranceInfo { export interface InsuranceInfo {
id?: string id?: string
attachInfoId: string attachInfoId:string
insuranceCompanyId: string insuranceCompanyId: string
insuranceTypeId: string insuranceTypeId: string
userId: string userId: string
@ -38,4 +38,4 @@ export interface InsuranceListResponse {
export interface RenewInsuranceParams { export interface RenewInsuranceParams {
expireDate: string expireDate: string
insurancePremium: number insurancePremium: number
} }

View File

@ -1,5 +1,5 @@
import type { ModelConfigDetailResponse, ModelConfigListResponse, ModelConfigRequest, ModelConfigResponse } from './type'
import http from '@/utils/http' import http from '@/utils/http'
import type { ModelConfigRequest, ModelConfigResponse, ModelConfigListResponse, ModelConfigDetailResponse } from './type'
const { request } = http const { request } = http
@ -11,7 +11,7 @@ export function createModelConfig(data: ModelConfigRequest) {
return request<ModelConfigResponse>({ return request<ModelConfigResponse>({
url: '/model-config', url: '/model-config',
method: 'post', method: 'post',
data, data
}) })
} }
@ -23,7 +23,7 @@ export function updateModelConfig(data: ModelConfigRequest) {
return request<ModelConfigResponse>({ return request<ModelConfigResponse>({
url: '/model-config', url: '/model-config',
method: 'put', method: 'put',
data, data
}) })
} }
@ -44,7 +44,7 @@ export function getModelConfigList(params?: {
return request<ModelConfigListResponse>({ return request<ModelConfigListResponse>({
url: '/model-config/list', url: '/model-config/list',
method: 'get', method: 'get',
params, params
}) })
} }
@ -55,7 +55,7 @@ export function getModelConfigList(params?: {
export function getModelConfigDetail(modelId: string) { export function getModelConfigDetail(modelId: string) {
return request<ModelConfigDetailResponse>({ return request<ModelConfigDetailResponse>({
url: `/model-config/${modelId}`, url: `/model-config/${modelId}`,
method: 'get', method: 'get'
}) })
} }
@ -66,6 +66,6 @@ export function getModelConfigDetail(modelId: string) {
export function deleteModelConfig(modelId: string) { export function deleteModelConfig(modelId: string) {
return request<any>({ return request<any>({
url: `/model-config/${modelId}`, url: `/model-config/${modelId}`,
method: 'delete', method: 'delete'
}) })
} }

View File

@ -49,4 +49,4 @@ export interface ModelConfigDetailResponse {
data: ModelConfigResponse data: ModelConfigResponse
msg: string msg: string
status: number status: number
} }

View File

@ -1,5 +1,5 @@
import type { DimensionQuery, PerformanceDimension, PerformanceRule, RuleQuery } from './type'
import http from '@/utils/http' import http from '@/utils/http'
import type { PerformanceDimension, PerformanceRule, DimensionQuery, RuleQuery } from './type'
/** 维度相关 */ /** 维度相关 */
export function getDimensionList(params?: DimensionQuery) { export function getDimensionList(params?: DimensionQuery) {
@ -36,5 +36,5 @@ export function deleteRule(id: string) {
} }
// 我的绩效 // 我的绩效
export function getMyEvaluation() { export function getMyEvaluation() {
return http.get('/performance-evaluation/my') return http.get('/performance-evaluation/my')
} }

View File

@ -1,39 +1,39 @@
/** 绩效维度 */ /** 绩效维度 */
export interface PerformanceDimension { export interface PerformanceDimension {
dimensionId: string dimensionId: string
dimensionName: string dimensionName: string
description?: string description?: string
deptName: string deptName: string
status: 0 | 1 status: 0 | 1
createBy?: string createBy?: string
createTime?: string createTime?: string
updateBy?: string updateBy?: string
updateTime?: string updateTime?: string
} }
/** 绩效细则 */ /** 绩效细则 */
export interface PerformanceRule { export interface PerformanceRule {
ruleId: string ruleId: string
ruleName: string ruleName: string
description?: string description?: string
dimensionName: string dimensionName: string
bonus?: string bonus?: string
score?: number score?: number
weight?: number weight?: number
status: 0 | 1 status: 0 | 1
createBy?: string createBy?: string
createTime?: string createTime?: string
updateBy?: string updateBy?: string
updateTime?: string updateTime?: string
} }
/** 查询参数 */ /** 查询参数 */
export interface DimensionQuery { export interface DimensionQuery {
dimensionName?: string dimensionName?: string
status?: 0 | 1 status?: 0 | 1
} }
export interface RuleQuery { export interface RuleQuery {
dimensionName?: string dimensionName?: string
ruleName?: string ruleName?: string
status?: 0 | 1 status?: 0 | 1
} }

View File

@ -89,17 +89,17 @@ export function auditBudget(id: string, data: BudgetAuditReq) {
/** @desc 获取预算类型选项 */ /** @desc 获取预算类型选项 */
export function getBudgetTypes() { export function getBudgetTypes() {
return http.get<Array<{ label: string, value: string }>>(`${BASE_URL}/types`) return http.get<Array<{ label: string; value: string }>>(`${BASE_URL}/types`)
} }
/** @desc 上传预算附件 */ /** @desc 上传预算附件 */
export function uploadBudgetAttachment(file: File) { export function uploadBudgetAttachment(file: File) {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
return http.post<{ id: string, name: string, url: string }>(`${BASE_URL}/upload`, formData, { return http.post<{ id: string; name: string; url: string }>(`${BASE_URL}/upload`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
@ -111,6 +111,6 @@ export function deleteBudgetAttachment(id: string) {
/** @desc 导出预算记录 */ /** @desc 导出预算记录 */
export function exportBudgetRecord(query: BudgetQuery) { export function exportBudgetRecord(query: BudgetQuery) {
return http.get(`${BASE_URL}/export`, query, { return http.get(`${BASE_URL}/export`, query, {
responseType: 'blob', responseType: 'blob'
}) })
} }

View File

@ -41,12 +41,12 @@ export function importProject(file: File) {
formData.append('file', file) formData.append('file', file)
return http.post(`${BASE_URL}/import`, formData, { return http.post(`${BASE_URL}/import`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }
/** @desc 导出项目 */ /** @desc 导出项目 */
export function exportProject(query: T.ProjectQuery) { export function exportProject(query: T.ProjectQuery) {
return http.download(`${BASE_URL}/export`, query) return http.download(`${BASE_URL}/export`, query)
} }

View File

@ -23,9 +23,9 @@ export function deleteTaskGroup(id: number) {
return http.del(`${BASE_URL}/group/${id}`) return http.del(`${BASE_URL}/group/${id}`)
} }
/** @desc 查询任务列表 */ /** @desc 查询任务列表(标准导出) */
export function listTask(query: T.TaskPageQuery) { export const listTask = (params: any) => {
return http.get<PageRes<T.TaskResp[]>>(`${BASE_URL}`, query) return http.get('/project-task/list', params)
} }
/** @desc 获取任务详情 */ /** @desc 获取任务详情 */
@ -65,7 +65,7 @@ export function importTask(file: File, projectId: number) {
formData.append('projectId', projectId.toString()) formData.append('projectId', projectId.toString())
return http.post(`${BASE_URL}/import`, formData, { return http.post(`${BASE_URL}/import`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data'
}, }
}) })
} }

View File

@ -1,10 +1,10 @@
/** 项目类型 */ /** 项目类型 */
export interface ProjectResp { export interface ProjectResp {
projectId: string // 项目ID (API返回的是字符串) projectId: string // 项目ID (API返回的是字符串)
projectCode?: string // 项目编号 projectCode?: string // 项目编号
projectName: string // 项目名称 projectName: string // 项目名称
projectIntro?: string // 项目简介 projectIntro?: string // 项目简介
farmName: string // 风场名称 (API字段名是farmName) farmName: string // 风场名称 (API字段名是farmName)
farmAddress?: string // 风场地址 (API字段名是farmAddress) farmAddress?: string // 风场地址 (API字段名是farmAddress)
client?: string // 委托单位 client?: string // 委托单位
clientContact?: string // 委托单位联系人 clientContact?: string // 委托单位联系人
@ -28,10 +28,10 @@ export interface ProjectResp {
coverUrl?: string // 封面URL coverUrl?: string // 封面URL
createDt?: Date createDt?: Date
updateDt?: Date updateDt?: Date
// 为了保持向后兼容,添加一些别名字段 // 为了保持向后兼容,添加一些别名字段
id?: string // projectId的别名 id?: string // projectId的别名
fieldName?: string // farmName的别名 fieldName?: string // farmName的别名
fieldLocation?: string // farmAddress的别名 fieldLocation?: string // farmAddress的别名
commissionUnit?: string // client的别名 commissionUnit?: string // client的别名
commissionContact?: string // clientContact的别名 commissionContact?: string // clientContact的别名
@ -85,4 +85,4 @@ export interface TaskQuery {
status?: string status?: string
} }
export interface TaskPageQuery extends TaskQuery, PageQuery {} export interface TaskPageQuery extends TaskQuery, PageQuery {}

View File

@ -0,0 +1,108 @@
import http from '@/utils/http'
import {
type RegulationTypeSearchRequest,
type RegulationTypeSearchResponse,
type RegulationProposalSearchRequest,
type RegulationProposalSearchResponse
} from './type'
// 制度管理API接口
export const regulationApi = {
// 获取制度列表
getRegulationList: (params: {
page?: number
size?: number
status?: string
type?: string
title?: string
proposer?: string
}): Promise<RegulationProposalSearchResponse> => {
return http.get('/regulation', params)
},
// 获取制度详情
getRegulationDetail: (regulationId: string) => {
return http.get(`/regulation/${regulationId}`)
},
// 创建制度提案
createProposal: (data: {
title: string
content: string
regulationType: string
scope: string
level: string
remark?: string
createBy?: string
}) => {
return http.post('/regulation/proposal', data)
},
// 更新制度提案
updateProposal: (regulationId: string, data: any) => {
return http.put(`/regulation/proposal/${regulationId}`, data)
},
// 公示提案
publishProposal: (regulationId: string) => {
return http.post(`/regulation/proposal/${regulationId}/publish`)
},
// 删除制度提案
deleteProposal: (regulationId: string) => {
return http.del(`/regulation/proposal/${regulationId}`)
},
// 公示制度
publishRegulation: (regulationId: string) => {
return http.post(`/regulation/${regulationId}/approve`)
},
// 获取已公示制度列表
getPublishedRegulationList: (params: {
page: number
size: number
status: string
}) => {
return http.get('/regulation', params)
},
// 确认制度知晓
confirmRegulation: (regulationId: string) => {
return http.post(`/regulation/${regulationId}/confirm`)
},
// 搜索制度类型(后端搜索接口)
searchRegulationTypes: (params: {
page?: number
size?: number
typeName?: string
status?: string
remark?: string
}) => {
return http.get('/regulation/types', params)
},
// 创建制度类型
createRegulationType: (data: {
typeName: string
sortOrder?: number
isEnabled?: string
}) => {
return http.post('/regulation/types', data)
},
// 更新制度类型
updateRegulationType: (typeId: string, data: {
typeName: string
sortOrder?: number
isEnabled?: string
}) => {
return http.put(`/regulation/types/${typeId}`, data)
},
// 删除制度类型
deleteRegulationType: (typeId: string) => {
return http.del(`/regulation/types/${typeId}`)
}
}

121
src/apis/regulation/type.ts Normal file
View File

@ -0,0 +1,121 @@
// 制度状态枚举
export enum RegulationStatus {
DRAFT = 'DRAFT', // 草稿
APPROVED = 'APPROVED', // 已通过
PUBLISHED = 'PUBLISHED', // 已公示
}
// 制度级别枚举
export enum RegulationLevel {
LOW = 'LOW', // 低
MEDIUM = 'MEDIUM', // 中
HIGH = 'HIGH' // 高
}
// 制度信息接口
export interface Regulation {
regulationId: string
title: string
content: string
regulationType: string
status: RegulationStatus
publishTime: string // 公示时间
effectiveTime: string
expireTime: string
scope: string
level: RegulationLevel
version: string
remark?: string
createBy: string
updateBy: string
createTime: string
updateTime: string
page: number
pageSize: number
delFlag: string
confirmStatus?: string
}
// 创建提案请求接口
export interface CreateProposalRequest {
title: string
content: string
regulationType: string
scope: string
level: RegulationLevel
remark?: string
createBy?: string
}
// 分页参数接口
export interface PaginationParams {
page: number
size: number
}
// 制度类型接口
export interface RegulationType {
typeId: string
typeName: string
sortOrder: number
isEnabled: string
remark?: string
createBy: string
createTime: string
updateBy: string
updateTime: string
delFlag: string
}
// 创建制度类型请求接口
export interface CreateRegulationTypeRequest {
typeName: string
sortOrder?: number
isEnabled?: string
remark?: string
}
// 更新制度类型请求接口
export interface UpdateRegulationTypeRequest {
typeName: string
sortOrder?: number
isEnabled?: string
remark?: string
}
// 制度类型搜索请求接口
export interface RegulationTypeSearchRequest {
page?: number
size?: number
typeName?: string
status?: string
remark?: string
}
// 制度类型搜索响应接口
export interface RegulationTypeSearchResponse {
records: RegulationType[]
total: number
current: number
size: number
pages: number
}
// 制度提案搜索请求接口
export interface RegulationProposalSearchRequest {
page?: number
size?: number
status?: string
type?: string
title?: string
proposer?: string
}
// 制度提案搜索响应接口
export interface RegulationProposalSearchResponse {
records: Regulation[]
total: number
current: number
size: number
pages: number
}

View File

@ -1,4 +1,4 @@
import type { SalaryCreateRequest, SalaryQuery, SalaryRecord } from '@/views/salary-management/types' import type { SalaryRecord, SalaryQuery, SalaryCreateRequest } from '@/views/salary-management/types'
import http from '@/utils/http' import http from '@/utils/http'
const BASE_URL = '/salary' const BASE_URL = '/salary'
@ -34,7 +34,7 @@ export const submitApproval = (id: string) => {
} }
// 审批工资单 // 审批工资单
export const approveSalary = (id: string, data: { status: string, comment?: string }) => { export const approveSalary = (id: string, data: { status: string; comment?: string }) => {
return http.put<boolean>(`${BASE_URL}/${id}/approve`, data) return http.put<boolean>(`${BASE_URL}/${id}/approve`, data)
} }

View File

@ -1,3 +1,4 @@
import type * as T from './type'
import http from '@/utils/http' import http from '@/utils/http'
import { convertMenuData } from '@/utils/menuConverter' import { convertMenuData } from '@/utils/menuConverter'
@ -5,13 +6,13 @@ import { convertMenuData } from '@/utils/menuConverter'
* API获取菜单树形数据 * API获取菜单树形数据
*/ */
export function getMenuTreeForRole(query?: { terminalType?: string }) { export function getMenuTreeForRole(query?: { terminalType?: string }) {
return http.get<any[]>('/menu/tree', query).then((res) => { return http.get<any[]>('/menu/tree', query).then(res => {
// 假设响应格式为 { data: [...菜单数据], success: true, msg: "", code: 200 } // 假设响应格式为 { data: [...菜单数据], success: true, msg: "", code: 200 }
const data = res.data || [] const data = res.data || [];
// 转换菜单数据为角色管理组件需要的格式 // 转换菜单数据为角色管理组件需要的格式
const convertedData = convertMenuData(data) const convertedData = convertMenuData(data);
return convertedData return convertedData;
}) });
} }
/** /**
@ -22,31 +23,31 @@ export function getMenuTreeForRole(query?: { terminalType?: string }) {
*/ */
export function transformMenusWithPermissions(menus: any[], selectedMenuIds: string[] = []) { export function transformMenusWithPermissions(menus: any[], selectedMenuIds: string[] = []) {
// 深拷贝菜单数据,避免修改原始数据 // 深拷贝菜单数据,避免修改原始数据
const result = JSON.parse(JSON.stringify(menus)) const result = JSON.parse(JSON.stringify(menus));
// 递归处理菜单树,添加权限标记 // 递归处理菜单树,添加权限标记
const processMenus = (items: any[]) => { const processMenus = (items: any[]) => {
return items.map((item) => { return items.map(item => {
// 设置选中状态 // 设置选中状态
item.isChecked = selectedMenuIds.includes(item.id.toString()) item.isChecked = selectedMenuIds.includes(item.id.toString());
// 如果有子菜单,递归处理 // 如果有子菜单,递归处理
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
item.children = processMenus(item.children) item.children = processMenus(item.children);
} }
return item return item;
}) });
} };
return processMenus(result) return processMenus(result);
} }
/** /**
* ID列表 * ID列表
*/ */
export function getRoleMenuIds(roleId: string) { export function getRoleMenuIds(roleId: string) {
return http.get<string[]>(`/role/get-menus/${roleId}`) return http.get<string[]>(`/role/get-menus/${roleId}`);
} }
/** /**
@ -55,6 +56,6 @@ export function getRoleMenuIds(roleId: string) {
export function assignRoleMenus(roleId: string, menuIds: string[]) { export function assignRoleMenus(roleId: string, menuIds: string[]) {
return http.post('/role/bind-menu', { return http.post('/role/bind-menu', {
roleId, roleId,
menuIds, menuIds
}) });
} }

View File

@ -1,46 +1,52 @@
import type * as T from './type' import type * as T from './type';
import http from '@/utils/http' import http from '@/utils/http';
const BASE_URL = '/post' const BASE_URL = '/post';
/** /**
* *
*/ */
export function addPost(data: T.PostAddReq) { export function addPost(data: T.PostAddReq) {
return http.post<any>(BASE_URL, data) return http.post<any>(BASE_URL, data);
} }
/** /**
* *
*/ */
export function getPostDetail(postId: string) { export function getPostDetail(postId: string) {
return http.get<T.PostVO>(`${BASE_URL}/detail/${postId}`) return http.get<T.PostVO>(`${BASE_URL}/detail/${postId}`);
} }
/** /**
* *
*/ */
export function listPost(params?: T.PostPageQuery) { export function listPost(params?: T.PostPageQuery) {
return http.get<T.PostVO[]>(`${BASE_URL}/list`, params) return http.get<T.PostVO[]>(`${BASE_URL}/list`, params);
} }
/** /**
* *
*/ */
export function pagePost(params?: T.PostPageQuery) { export function pagePost(params?: T.PostPageQuery) {
return http.get<PageRes<T.PostVO[]>>(`${BASE_URL}/page`, params) return http.get<PageRes<T.PostVO[]>>(`${BASE_URL}/page`, params);
} }
/** /**
* *
*/ */
export function updatePost(postId: string, data: T.PostUpdateReq) { export function updatePost(postId: string, data: T.PostUpdateReq) {
return http.put<any>(`${BASE_URL}/${postId}`, data) return http.put<any>(`${BASE_URL}/${postId}`, data);
} }
/** /**
* *
*/ */
export function deletePost(postId: string) { export function deletePost(postId: string) {
return http.del<any>(`${BASE_URL}/${postId}`)
} }
/**
*
*/
export function getPostUsers(postId: string) {
return http.get<T.UserNewResp[]>(`${BASE_URL}/${postId}/user`);
}

View File

@ -4,6 +4,7 @@ import http from '@/utils/http'
export type * from './type' export type * from './type'
const BASE_URL = '/system/role' const BASE_URL = '/system/role'
const BASE_URL_NEW = '/role'
/** @desc 查询角色列表(已废弃) */ /** @desc 查询角色列表(已废弃) */
export function listRole(query: T.RoleQuery) { export function listRole(query: T.RoleQuery) {
@ -72,7 +73,7 @@ export function updateRolePermission(id: string, data: any) {
/** @desc 查询角色关联用户 */ /** @desc 查询角色关联用户 */
export function listRoleUser(id: string, query: T.RoleUserPageQuery) { export function listRoleUser(id: string, query: T.RoleUserPageQuery) {
return http.get<PageRes<T.RoleUserResp[]>>(`${BASE_URL}/${id}/user`, query) return http.get<PageRes<T.RoleUserResp[]>>(`${BASE_URL_NEW}/${id}/user`, query)
} }
/** @desc 分配角色给用户 */ /** @desc 分配角色给用户 */
@ -87,5 +88,5 @@ export function unassignFromUsers(userRoleIds: Array<string | number>) {
/** @desc 查询角色关联用户 ID */ /** @desc 查询角色关联用户 ID */
export function listRoleUserId(id: string) { export function listRoleUserId(id: string) {
return http.get(`${BASE_URL}/${id}/user/id`) return http.get(`${BASE_URL_NEW}/${id}/user`)
} }

View File

@ -564,113 +564,113 @@ export interface MessagePageQuery extends MessageQuery, PageQuery {
/** 新增菜单请求参数 */ /** 新增菜单请求参数 */
export interface MenuAddReq { export interface MenuAddReq {
menuName: string menuName: string;
menuType: string menuType: string;
orderNum: number orderNum: number;
parentId: string parentId: string;
perms: string perms: string;
terminalType: string terminalType: string;
url: string url: string;
visible: string visible: string;
} }
/** 新菜单树查询参数 */ /** 新菜单树查询参数 */
export interface MenuTreeQuery { export interface MenuTreeQuery {
menuName?: string menuName?: string;
terminalType?: string terminalType?: string;
} }
/** 新菜单详情响应类型 */ /** 新菜单详情响应类型 */
export interface MenuDetailResp { export interface MenuDetailResp {
menuId: string menuId: string;
menuName: string menuName: string;
menuType: string menuType: string;
orderNum: number orderNum: number;
parentId: string parentId: string;
perms: string perms: string;
url: string url: string;
visible: string visible: string;
} }
/** 菜单更新请求参数 */ /** 菜单更新请求参数 */
export interface MenuUpdateReq { export interface MenuUpdateReq {
menuName: string menuName: string;
menuType: string menuType: string;
orderNum: number orderNum: number;
parentId: string parentId: string;
perms: string perms: string;
terminalType: string terminalType: string;
url: string url: string;
visible: string visible: string;
} }
/** 新角色信息请求实体 */ /** 新角色信息请求实体 */
export interface RoleAddReq { export interface RoleAddReq {
remark: string remark: string;
roleCode: string roleCode: string;
roleKey: string roleKey: string;
roleName: string roleName: string;
status: number status: number;
} }
/** 角色信息更新请求实体 */ /** 角色信息更新请求实体 */
export interface RoleUpdateReq { export interface RoleUpdateReq {
remark: string remark: string;
roleCode: string roleCode: string;
roleKey: string roleKey: string;
roleName: string roleName: string;
status: number status: number;
} }
/** 新角色信息响应实体 */ /** 新角色信息响应实体 */
export interface RoleNewResp { export interface RoleNewResp {
remark: string remark: string;
roleCode: string roleCode: string;
roleId: string roleId: string;
roleKey: string roleKey: string;
roleName: string roleName: string;
status: string status: string;
isSystem?: boolean isSystem?: boolean;
} }
/** 角色菜单绑定请求 */ /** 角色菜单绑定请求 */
export interface RoleBindMenuReq { export interface RoleBindMenuReq {
menuIds: string[] menuIds: string[];
roleId: string roleId: string;
} }
/** 角色查询参数(新接口) */ /** 角色查询参数(新接口) */
export interface RoleNewQuery { export interface RoleNewQuery {
roleName?: string roleName?: string;
} }
// 岗位相关类型定义 // 岗位相关类型定义
export interface PostVO { export interface PostVO {
postId: string postId: string;
postName: string postName: string;
postSort: number postSort: number;
remark: string remark: string;
status: string | number status: string | number;
createTime?: string createTime?: string;
updateTime?: string updateTime?: string;
} }
export interface PostPageQuery { export interface PostPageQuery {
postName?: string postName?: string;
page?: number page?: number;
size?: number size?: number;
} }
export interface PostAddReq { export interface PostAddReq {
postName: string postName: string;
postSort: number postSort: number;
remark: string remark: string;
status: number status: number;
} }
export interface PostUpdateReq { export interface PostUpdateReq {
postName: string postName: string;
postSort: number postSort: number;
remark: string remark: string;
status: number status: number;
} }

View File

@ -36,4 +36,4 @@ export function updateUserNew(userId: string, data: T.UserNewUpdateReq) {
/** @desc 删除用户信息 */ /** @desc 删除用户信息 */
export function deleteUserNew(userId: string) { export function deleteUserNew(userId: string) {
return http.del(`${BASE_URL}/${userId}`) return http.del(`${BASE_URL}/${userId}`)
} }

View File

@ -1,44 +0,0 @@
import http from '@/utils/http'
import type * as T from '@/types/training.d'
const BASE_URL = '/training'
/** @desc 分页查询培训计划列表 */
export function pageTrainingPlan(query: T.TrainingPlanPageQuery) {
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/page`, query)
}
/** @desc 查询培训计划列表 */
export function listTrainingPlan(query?: T.TrainingPlanPageQuery) {
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/list`, query)
}
/** @desc 查询培训计划详情 */
export function getTrainingPlanDetail(planId: string) {
return http.get<T.TrainingPlanResp>(`${BASE_URL}/plan/detail/${planId}`)
}
/** @desc 新增培训计划 */
export function createTrainingPlan(data: T.TrainingPlanReq) {
return http.post(`${BASE_URL}/plan`, data)
}
/** @desc 更新培训计划 */
export function updateTrainingPlan(planId: string, data: T.TrainingPlanReq) {
return http.put(`${BASE_URL}/plan/${planId}`, data)
}
/** @desc 删除培训计划 */
export function deleteTrainingPlan(planId: string) {
return http.del(`${BASE_URL}/plan/${planId}`)
}
/** @desc 发布培训计划 */
export function publishTrainingPlan(planId: string) {
return http.put(`${BASE_URL}/plan/${planId}/publish`)
}
/** @desc 取消培训计划 */
export function cancelTrainingPlan(planId: string) {
return http.put(`${BASE_URL}/plan/${planId}/cancel`)
}

View File

@ -39,18 +39,18 @@ const breadcrumbList = computed(() => {
if (route.path.startsWith('/redirect/')) { if (route.path.startsWith('/redirect/')) {
return [] return []
} }
const cloneRoutes = JSON.parse(JSON.stringify(routes)) as RouteLocationMatched[] const cloneRoutes = JSON.parse(JSON.stringify(routes)) as RouteLocationMatched[]
const obj = findTree(cloneRoutes, (i) => i.path === route.path) const obj = findTree(cloneRoutes, (i) => i.path === route.path)
// //
const arr = obj ? obj.nodes.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false) : [] const arr = obj ? obj.nodes.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false) : []
// home // home
if (home.value && !arr.some((item) => item.path === home.value?.path)) { if (home.value && !arr.some(item => item.path === home.value?.path)) {
return [home.value, ...arr] return [home.value, ...arr]
} }
return arr return arr
}) })

View File

@ -19,7 +19,7 @@
:loading="loadingImageSources" :loading="loadingImageSources"
/> />
</a-form-item> </a-form-item>
<a-form-item label="目标项目" required> <a-form-item label="目标项目" required>
<a-tree-select <a-tree-select
v-model="form.projectId" v-model="form.projectId"
@ -31,7 +31,7 @@
@change="onProjectChange" @change="onProjectChange"
/> />
</a-form-item> </a-form-item>
<a-form-item label="目标组件"> <a-form-item label="目标组件">
<a-select <a-select
v-model="form.componentId" v-model="form.componentId"
@ -40,11 +40,11 @@
allow-clear allow-clear
/> />
</a-form-item> </a-form-item>
<a-form-item label="上传用户"> <a-form-item label="上传用户">
<a-input v-model="form.uploadUser" placeholder="请输入上传用户" /> <a-input v-model="form.uploadUser" placeholder="请输入上传用户" />
</a-form-item> </a-form-item>
<a-form-item label="位置信息"> <a-form-item label="位置信息">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :span="8"> <a-col :span="8">
@ -58,15 +58,15 @@
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
<a-form-item label="导入设置"> <a-form-item label="导入设置">
<a-checkbox-group v-model="form.settings"> <a-checkbox-group v-model="form.settings">
<a-checkbox value="autoAnnotate">导入后自动标注</a-checkbox> <a-checkbox value="autoAnnotate">导入后自动标注</a-checkbox>
<a-checkbox value="overwrite">覆盖同名文件</a-checkbox> <a-checkbox value="overwrite">覆盖同名文件</a-checkbox>
</a-checkbox-group> </a-checkbox-group>
</a-form-item> </a-form-item>
<a-form-item v-if="form.settings.includes('autoAnnotate')" label="标注类型"> <a-form-item label="标注类型" v-if="form.settings.includes('autoAnnotate')">
<a-select <a-select
v-model="form.annotationTypes" v-model="form.annotationTypes"
:options="defectTypeOptions" :options="defectTypeOptions"
@ -75,7 +75,7 @@
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- 文件上传区域 --> <!-- 文件上传区域 -->
<div class="upload-section"> <div class="upload-section">
<a-upload <a-upload
@ -88,9 +88,9 @@
> >
<template #upload-button> <template #upload-button>
<div class="upload-area"> <div class="upload-area">
<div class="upload-drag-icon"> <div class="upload-drag-icon">
<IconUpload size="48" /> <icon-upload size="48" />
</div> </div>
<div class="upload-text"> <div class="upload-text">
<p>点击或拖拽图像文件到此区域</p> <p>点击或拖拽图像文件到此区域</p>
<p class="upload-hint">支持 JPGPNGJPEG 格式可同时选择多个文件</p> <p class="upload-hint">支持 JPGPNGJPEG 格式可同时选择多个文件</p>
@ -99,14 +99,14 @@
</template> </template>
</a-upload> </a-upload>
</div> </div>
<!-- 文件列表 --> <!-- 文件列表 -->
<div v-if="fileList.length > 0" class="file-list"> <div class="file-list" v-if="fileList.length > 0">
<div class="list-header"> <div class="list-header">
<h4>待导入文件 ({{ fileList.length }})</h4> <h4>待导入文件 ({{ fileList.length }})</h4>
<a-button type="text" @click="clearFiles"> <a-button type="text" @click="clearFiles">
<template #icon> <template #icon>
<IconDelete /> <icon-delete />
</template> </template>
清空 清空
</a-button> </a-button>
@ -133,16 +133,16 @@
@click="removeFile(index)" @click="removeFile(index)"
> >
<template #icon> <template #icon>
<IconClose /> <icon-close />
</template> </template>
</a-button> </a-button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 导入进度 --> <!-- 导入进度 -->
<div v-if="importing" class="import-progress"> <div class="import-progress" v-if="importing">
<a-progress <a-progress
:percent="importProgress" :percent="importProgress"
:status="importStatus" :status="importStatus"
@ -150,17 +150,17 @@
/> />
<p class="progress-text">{{ progressText }}</p> <p class="progress-text">{{ progressText }}</p>
</div> </div>
<!-- 导入结果 --> <!-- 导入结果 -->
<div v-if="importResult" class="import-result"> <div class="import-result" v-if="importResult">
<a-alert <a-alert
:type="importResult.failed.length > 0 ? 'warning' : 'success'" :type="importResult.failed.length > 0 ? 'warning' : 'success'"
:title="getResultTitle()" :title="getResultTitle()"
:description="getResultDescription()" :description="getResultDescription()"
show-icon show-icon
/> />
<div v-if="importResult.failed.length > 0" class="result-details"> <div class="result-details" v-if="importResult.failed.length > 0">
<h4>失败文件列表:</h4> <h4>失败文件列表:</h4>
<div class="failed-list"> <div class="failed-list">
<div <div
@ -180,22 +180,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { import {
IconClose, IconUpload,
IconDelete, IconDelete,
IconUpload, IconClose
} from '@arco-design/web-vue/es/icon' } from '@arco-design/web-vue/es/icon'
import { import {
getImageSources, getProjectTree,
getProjectTree, importImages,
importImages, getImageSources
} from '@/apis/industrial-image' } from '@/apis/industrial-image'
import type { import type {
ImageImportParams, ProjectTreeNode,
IndustrialImage, IndustrialImage,
ProjectTreeNode, ImageImportParams
} from '@/apis/industrial-image/type' } from '@/apis/industrial-image/type'
interface Props { interface Props {
@ -233,54 +233,54 @@ const form = ref({
latitude: '', latitude: '',
longitude: '', longitude: '',
settings: [] as string[], settings: [] as string[],
annotationTypes: [] as string[], annotationTypes: [] as string[]
}) })
const fileList = ref<FileItem[]>([]) const fileList = ref<FileItem[]>([])
const projectTree = ref<ProjectTreeNode[]>([]) const projectTree = ref<ProjectTreeNode[]>([])
const defectTypes = ref<Array<{ id: string, name: string, description?: string, color?: string }>>([]) const defectTypes = ref<Array<{ id: string; name: string; description?: string; color?: string }>>([])
const imageSources = ref<Array<{ id: string, name: string, code: string }>>([]) const imageSources = ref<Array<{ id: string; name: string; code: string }>>([])
const loadingImageSources = ref(false) const loadingImageSources = ref(false)
// //
const visible = computed({ const visible = computed({
get: () => props.visible, get: () => props.visible,
set: (value) => emit('update:visible', value), set: (value) => emit('update:visible', value)
}) })
const componentOptions = computed(() => { const componentOptions = computed(() => {
const findComponents = (nodes: ProjectTreeNode[]): Array<{ label: string, value: string }> => { const findComponents = (nodes: ProjectTreeNode[]): Array<{ label: string; value: string }> => {
const options: Array<{ label: string, value: string }> = [] const options: Array<{ label: string; value: string }> = []
nodes.forEach((node) => { nodes.forEach(node => {
if (node.type === 'component' || node.type === 'blade' || node.type === 'tower') { if (node.type === 'component' || node.type === 'blade' || node.type === 'tower') {
options.push({ options.push({
label: node.name, label: node.name,
value: node.id, value: node.id
}) })
} }
if (node.children) { if (node.children) {
options.push(...findComponents(node.children)) options.push(...findComponents(node.children))
} }
}) })
return options return options
} }
return form.value.projectId ? findComponents(projectTree.value) : [] return form.value.projectId ? findComponents(projectTree.value) : []
}) })
const defectTypeOptions = computed(() => { const defectTypeOptions = computed(() => {
return defectTypes.value.map((type) => ({ return defectTypes.value.map(type => ({
label: type.name, label: type.name,
value: type.id, value: type.id
})) }))
}) })
const imageSourceOptions = computed(() => { const imageSourceOptions = computed(() => {
return imageSources.value.map((source) => ({ return imageSources.value.map(source => ({
label: source.name, label: source.name,
value: source.code, value: source.code
})) }))
}) })
@ -326,25 +326,25 @@ const onProjectChange = (value: string) => {
const handleFileChange = (fileList: any) => { const handleFileChange = (fileList: any) => {
const files = Array.from(fileList.target?.files || []) as File[] const files = Array.from(fileList.target?.files || []) as File[]
files.forEach((file) => { files.forEach(file => {
if (!file.type.startsWith('image/')) { if (!file.type.startsWith('image/')) {
Message.warning(`文件 ${file.name} 不是图像文件`) Message.warning(`文件 ${file.name} 不是图像文件`)
return return
} }
if (file.size > 10 * 1024 * 1024) { // 10MB if (file.size > 10 * 1024 * 1024) { // 10MB
Message.warning(`文件 ${file.name} 大小超过10MB`) Message.warning(`文件 ${file.name} 大小超过10MB`)
return return
} }
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = (e) => {
const fileItem: FileItem = { const fileItem: FileItem = {
file, file,
name: file.name, name: file.name,
size: file.size, size: file.size,
preview: e.target?.result as string, preview: e.target?.result as string
} }
fileList.value.push(fileItem) fileList.value.push(fileItem)
} }
@ -365,7 +365,7 @@ const formatFileSize = (bytes: number): string => {
const k = 1024 const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB'] const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k)) const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}` return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
} }
const handleImport = async () => { const handleImport = async () => {
@ -373,25 +373,25 @@ const handleImport = async () => {
Message.warning('请选择图像来源') Message.warning('请选择图像来源')
return return
} }
if (!form.value.projectId) { if (!form.value.projectId) {
Message.warning('请选择目标项目') Message.warning('请选择目标项目')
return return
} }
if (fileList.value.length === 0) { if (fileList.value.length === 0) {
Message.warning('请选择要导入的图像文件') Message.warning('请选择要导入的图像文件')
return return
} }
importing.value = true importing.value = true
importProgress.value = 0 importProgress.value = 0
importStatus.value = 'normal' importStatus.value = 'normal'
progressText.value = '正在导入图像...' progressText.value = '正在导入图像...'
importResult.value = null importResult.value = null
try { try {
const files = fileList.value.map((item) => item.file) const files = fileList.value.map(item => item.file)
const params: ImageImportParams = { const params: ImageImportParams = {
imageSource: form.value.imageSource, imageSource: form.value.imageSource,
projectId: form.value.projectId, projectId: form.value.projectId,
@ -401,23 +401,23 @@ const handleImport = async () => {
latitude: form.value.latitude || undefined, latitude: form.value.latitude || undefined,
longitude: form.value.longitude || undefined, longitude: form.value.longitude || undefined,
autoAnnotate: form.value.settings.includes('autoAnnotate'), autoAnnotate: form.value.settings.includes('autoAnnotate'),
annotationTypes: form.value.settings.includes('autoAnnotate') ? form.value.annotationTypes : undefined, annotationTypes: form.value.settings.includes('autoAnnotate') ? form.value.annotationTypes : undefined
} }
// //
const progressInterval = setInterval(() => { const progressInterval = setInterval(() => {
if (importProgress.value < 90) { if (importProgress.value < 90) {
importProgress.value += 10 importProgress.value += 10
} }
}, 200) }, 200)
const res = await importImages(files, params) const res = await importImages(files, params)
clearInterval(progressInterval) clearInterval(progressInterval)
importProgress.value = 100 importProgress.value = 100
importStatus.value = 'success' importStatus.value = 'success'
progressText.value = '导入完成!' progressText.value = '导入完成!'
// API // API
const mockResult = { const mockResult = {
success: files.map((file, index) => ({ success: files.map((file, index) => ({
@ -428,15 +428,16 @@ const handleImport = async () => {
type: file.type, type: file.type,
projectId: form.value.projectId, projectId: form.value.projectId,
componentId: form.value.componentId, componentId: form.value.componentId,
createTime: new Date().toISOString(), createTime: new Date().toISOString()
})), })),
failed: [], failed: []
} }
importResult.value = mockResult importResult.value = mockResult
emit('importSuccess', mockResult) emit('importSuccess', mockResult)
Message.success(`成功导入 ${files.length} 个图像文件`) Message.success(`成功导入 ${files.length} 个图像文件`)
} catch (error) { } catch (error) {
console.error('导入失败:', error) console.error('导入失败:', error)
importProgress.value = 100 importProgress.value = 100
@ -453,7 +454,7 @@ const handleCancel = () => {
Message.warning('正在导入中,请稍后再试') Message.warning('正在导入中,请稍后再试')
return return
} }
resetForm() resetForm()
visible.value = false visible.value = false
} }
@ -468,7 +469,7 @@ const resetForm = () => {
latitude: '', latitude: '',
longitude: '', longitude: '',
settings: [], settings: [],
annotationTypes: [], annotationTypes: []
} }
fileList.value = [] fileList.value = []
importResult.value = null importResult.value = null
@ -479,7 +480,7 @@ const resetForm = () => {
const getResultTitle = () => { const getResultTitle = () => {
if (!importResult.value) return '' if (!importResult.value) return ''
const { success, failed } = importResult.value const { success, failed } = importResult.value
if (failed.length === 0) { if (failed.length === 0) {
return `导入成功!共导入 ${success.length} 个图像文件` return `导入成功!共导入 ${success.length} 个图像文件`
@ -490,7 +491,7 @@ const getResultTitle = () => {
const getResultDescription = () => { const getResultDescription = () => {
if (!importResult.value) return '' if (!importResult.value) return ''
const { success, failed } = importResult.value const { success, failed } = importResult.value
if (failed.length === 0) { if (failed.length === 0) {
return '所有图像文件都已成功导入到指定项目中' return '所有图像文件都已成功导入到指定项目中'
@ -517,11 +518,11 @@ watch(visible, (newValue) => {
max-height: 70vh; max-height: 70vh;
overflow-y: auto; overflow-y: auto;
} }
.upload-section { .upload-section {
margin: 20px 0; margin: 20px 0;
} }
.upload-area { .upload-area {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -533,24 +534,24 @@ watch(visible, (newValue) => {
background: #fafafa; background: #fafafa;
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
border-color: #1890ff; border-color: #1890ff;
background: #f0f8ff; background: #f0f8ff;
} }
} }
.upload-drag-icon { .upload-drag-icon {
margin-bottom: 16px; margin-bottom: 16px;
color: #999; color: #999;
} }
.upload-text { .upload-text {
text-align: center; text-align: center;
p { p {
margin: 0; margin: 0;
&.upload-hint { &.upload-hint {
margin-top: 8px; margin-top: 8px;
font-size: 12px; font-size: 12px;
@ -558,14 +559,14 @@ watch(visible, (newValue) => {
} }
} }
} }
.file-list { .file-list {
margin-top: 20px; margin-top: 20px;
border: 1px solid #e8e8e8; border: 1px solid #e8e8e8;
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
} }
.list-header { .list-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -573,37 +574,37 @@ watch(visible, (newValue) => {
padding: 12px 16px; padding: 12px 16px;
background: #fafafa; background: #fafafa;
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8;
h4 { h4 {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
} }
} }
.list-content { .list-content {
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;
} }
.file-item { .file-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 16px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
} }
.file-info { .file-info {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.file-preview { .file-preview {
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -611,81 +612,81 @@ watch(visible, (newValue) => {
overflow: hidden; overflow: hidden;
border-radius: 4px; border-radius: 4px;
border: 1px solid #e8e8e8; border: 1px solid #e8e8e8;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
} }
.file-details { .file-details {
flex: 1; flex: 1;
} }
.file-name { .file-name {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
margin-bottom: 4px; margin-bottom: 4px;
} }
.file-size { .file-size {
font-size: 12px; font-size: 12px;
color: #999; color: #999;
} }
.import-progress { .import-progress {
margin-top: 20px; margin-top: 20px;
text-align: center; text-align: center;
} }
.progress-text { .progress-text {
margin-top: 8px; margin-top: 8px;
font-size: 14px; font-size: 14px;
color: #666; color: #666;
} }
.import-result { .import-result {
margin-top: 20px; margin-top: 20px;
} }
.result-details { .result-details {
margin-top: 16px; margin-top: 16px;
h4 { h4 {
margin-bottom: 8px; margin-bottom: 8px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
} }
} }
.failed-list { .failed-list {
max-height: 150px; max-height: 150px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #f0f0f0; border: 1px solid #f0f0f0;
border-radius: 6px; border-radius: 6px;
} }
.failed-item { .failed-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 12px; padding: 8px 12px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
} }
.filename { .filename {
font-size: 12px; font-size: 12px;
color: #333; color: #333;
} }
.error { .error {
font-size: 12px; font-size: 12px;
color: #ff4d4f; color: #ff4d4f;
} }
} }
</style> </style>

View File

@ -8,7 +8,7 @@
<button class="dialog-close-btn" @click="closeDialog">×</button> <button class="dialog-close-btn" @click="closeDialog">×</button>
</div> </div>
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
<!-- 左侧步骤指示器 --> <!-- 左侧步骤指示器 -->
<div class="steps-sidebar"> <div class="steps-sidebar">
@ -28,22 +28,22 @@
<span class="step-text">设置信息</span> <span class="step-text">设置信息</span>
</div> </div>
</div> </div>
<!-- 右侧内容区域 --> <!-- 右侧内容区域 -->
<div class="dialog-content-container"> <div class="dialog-content-container">
<!-- 步骤1: 选择部件 --> <!-- 步骤1: 选择部件 -->
<div v-if="currentStep === 1" class="dialog-content"> <div v-if="currentStep === 1" class="dialog-content">
<div class="select-part-content"> <div class="select-part-content">
<div class="section-title">选择要导入图像的部件</div> <div class="section-title">选择要导入图像的部件</div>
<div class="parts-container"> <div class="parts-container">
<div <div
v-for="part in availableParts" v-for="part in availableParts"
:key="getPartId(part)" :key="getPartId(part)"
class="part-item" class="part-item"
:class="{ selected: String(selectedPartId) === String(getPartId(part)) }" :class="{ selected: String(selectedPartId) === String(getPartId(part)) }"
:title="`部件ID: ${getPartId(part)}, 选中: ${String(selectedPartId) === String(getPartId(part))}`"
@click="selectPart(part)" @click="selectPart(part)"
:title="`部件ID: ${getPartId(part)}, 选中: ${String(selectedPartId) === String(getPartId(part))}`"
> >
<div class="part-icon"> <div class="part-icon">
<svg v-if="part.partType === 'engine'" width="40" height="40" viewBox="0 0 70 70" xmlns="http://www.w3.org/2000/svg"> <svg v-if="part.partType === 'engine'" width="40" height="40" viewBox="0 0 70 70" xmlns="http://www.w3.org/2000/svg">
@ -66,8 +66,8 @@
<div class="part-name">{{ getPartName(part) }}</div> <div class="part-name">{{ getPartName(part) }}</div>
</div> </div>
</div> </div>
<div v-if="selectedPart" class="part-info"> <div class="part-info" v-if="selectedPart">
<div class="info-line"> <div class="info-line">
<div class="info-label">部件:</div> <div class="info-label">部件:</div>
<div class="info-value">{{ getPartName(selectedPart) }}</div> <div class="info-value">{{ getPartName(selectedPart) }}</div>
@ -79,38 +79,38 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 步骤2: 导入图像 --> <!-- 步骤2: 导入图像 -->
<div v-if="currentStep === 2" class="dialog-content"> <div v-if="currentStep === 2" class="dialog-content">
<div class="import-image-content"> <div class="import-image-content">
<div class="section-title">导入图像到部件"{{ selectedPart ? getPartName(selectedPart) : '' }}"</div> <div class="section-title">导入图像到部件"{{ selectedPart ? getPartName(selectedPart) : '' }}"</div>
<div class="image-actions"> <div class="image-actions">
<button class="action-button" @click="handleAddImages"> <button class="action-button" @click="handleAddImages">
<span class="button-icon">+</span> <span class="button-icon">+</span>
添加图像 添加图像
</button> </button>
<button class="action-button" :disabled="!hasSelectedImages" @click="handleRemoveImages"> <button class="action-button" @click="handleRemoveImages" :disabled="!hasSelectedImages">
<span class="button-icon">-</span> <span class="button-icon">-</span>
移除图像 移除图像
</button> </button>
<!-- 隐藏的文件输入框 --> <!-- 隐藏的文件输入框 -->
<input <input
ref="fileInput" type="file"
type="file" ref="fileInput"
accept="image/*" accept="image/*"
style="display: none;" style="display: none;"
multiple multiple
@change="handleFileSelected" @change="handleFileSelected"
/> />
</div> </div>
<div class="image-list-container"> <div class="image-list-container">
<table class="image-list"> <table class="image-list">
<thead> <thead>
<tr> <tr>
<th class="checkbox-column"> <th class="checkbox-column">
<input type="checkbox" :checked="allImagesSelected" @change="toggleSelectAll"> <input type="checkbox" @change="toggleSelectAll" :checked="allImagesSelected">
</th> </th>
<th class="preview-column">预览</th> <th class="preview-column">预览</th>
<th>图像名称</th> <th>图像名称</th>
@ -123,7 +123,7 @@
<tbody> <tbody>
<tr v-for="(image, index) in importImages" :key="index" @click="toggleImageSelection(image)"> <tr v-for="(image, index) in importImages" :key="index" @click="toggleImageSelection(image)">
<td> <td>
<input v-model="image.selected" type="checkbox" @click.stop> <input type="checkbox" v-model="image.selected" @click.stop>
</td> </td>
<td class="preview-cell"> <td class="preview-cell">
<img v-if="image.previewUrl" :src="image.previewUrl" class="preview-thumbnail" alt="预览"> <img v-if="image.previewUrl" :src="image.previewUrl" class="preview-thumbnail" alt="预览">
@ -140,22 +140,22 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 步骤3: 设置图像采集信息 --> <!-- 步骤3: 设置图像采集信息 -->
<div v-if="currentStep === 3" class="dialog-content"> <div v-if="currentStep === 3" class="dialog-content">
<div class="image-info-content"> <div class="image-info-content">
<div class="section-title">步骤3: 设置图像采集信息</div> <div class="section-title">步骤3: 设置图像采集信息</div>
<div class="form-container"> <div class="form-container">
<div class="form-row"> <div class="form-row">
<div class="form-label">拍摄时间范围</div> <div class="form-label">拍摄时间范围</div>
<div class="form-input datetime-range"> <div class="form-input datetime-range">
<input v-model="imageInfo.startTime" type="text" placeholder="开始时间"> <input type="text" v-model="imageInfo.startTime" placeholder="开始时间">
<span class="range-separator"></span> <span class="range-separator"></span>
<input v-model="imageInfo.endTime" type="text" placeholder="结束时间"> <input type="text" v-model="imageInfo.endTime" placeholder="结束时间">
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">天气</div> <div class="form-label">天气</div>
<div class="form-input"> <div class="form-input">
@ -167,35 +167,35 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">环境温度(°C)</div> <div class="form-label">环境温度(°C)</div>
<div class="form-input temperature-range"> <div class="form-input temperature-range">
<div class="range-input-group"> <div class="range-input-group">
<button class="range-btn" @click="imageInfo.minTemperature = Math.max(0, imageInfo.minTemperature - 1)">-</button> <button class="range-btn" @click="imageInfo.minTemperature = Math.max(0, imageInfo.minTemperature - 1)">-</button>
<input v-model="imageInfo.minTemperature" type="number" step="0.1" min="0" max="50"> <input type="number" v-model="imageInfo.minTemperature" step="0.1" min="0" max="50">
<button class="range-btn" @click="imageInfo.minTemperature = Math.min(50, imageInfo.minTemperature + 1)">+</button> <button class="range-btn" @click="imageInfo.minTemperature = Math.min(50, imageInfo.minTemperature + 1)">+</button>
</div> </div>
<span class="range-separator"></span> <span class="range-separator"></span>
<div class="range-input-group"> <div class="range-input-group">
<button class="range-btn" @click="imageInfo.maxTemperature = Math.max(0, imageInfo.maxTemperature - 1)">-</button> <button class="range-btn" @click="imageInfo.maxTemperature = Math.max(0, imageInfo.maxTemperature - 1)">-</button>
<input v-model="imageInfo.maxTemperature" type="number" step="0.1" min="0" max="50"> <input type="number" v-model="imageInfo.maxTemperature" step="0.1" min="0" max="50">
<button class="range-btn" @click="imageInfo.maxTemperature = Math.min(50, imageInfo.maxTemperature + 1)">+</button> <button class="range-btn" @click="imageInfo.maxTemperature = Math.min(50, imageInfo.maxTemperature + 1)">+</button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">湿度(%)</div> <div class="form-label">湿度(%)</div>
<div class="form-input"> <div class="form-input">
<div class="range-input-group"> <div class="range-input-group">
<button class="range-btn" @click="imageInfo.humidity = Math.max(0, imageInfo.humidity - 1)">-</button> <button class="range-btn" @click="imageInfo.humidity = Math.max(0, imageInfo.humidity - 1)">-</button>
<input v-model="imageInfo.humidity" type="number" min="0" max="100"> <input type="number" v-model="imageInfo.humidity" min="0" max="100">
<button class="range-btn" @click="imageInfo.humidity = Math.min(100, imageInfo.humidity + 1)">+</button> <button class="range-btn" @click="imageInfo.humidity = Math.min(100, imageInfo.humidity + 1)">+</button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">风力</div> <div class="form-label">风力</div>
<div class="form-input"> <div class="form-input">
@ -212,43 +212,43 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">拍摄方式</div> <div class="form-label">拍摄方式</div>
<div class="form-input capture-method"> <div class="form-input capture-method">
<label class="radio-option"> <label class="radio-option">
<input v-model="imageInfo.captureMethod" type="radio" value="无人机航拍"> <input type="radio" v-model="imageInfo.captureMethod" value="无人机航拍">
<span class="radio-label">无人机航拍</span> <span class="radio-label">无人机航拍</span>
</label> </label>
<label class="radio-option"> <label class="radio-option">
<input v-model="imageInfo.captureMethod" type="radio" value="人工拍摄"> <input type="radio" v-model="imageInfo.captureMethod" value="人工拍摄">
<span class="radio-label">人工拍摄</span> <span class="radio-label">人工拍摄</span>
</label> </label>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">拍摄距离()</div> <div class="form-label">拍摄距离()</div>
<div class="form-input"> <div class="form-input">
<div class="range-input-group"> <div class="range-input-group">
<button class="range-btn" @click="imageInfo.captureDistance = Math.max(0, imageInfo.captureDistance - 1)">-</button> <button class="range-btn" @click="imageInfo.captureDistance = Math.max(0, imageInfo.captureDistance - 1)">-</button>
<input v-model="imageInfo.captureDistance" type="number" min="0"> <input type="number" v-model="imageInfo.captureDistance" min="0">
<button class="range-btn" @click="imageInfo.captureDistance = imageInfo.captureDistance + 1">+</button> <button class="range-btn" @click="imageInfo.captureDistance = imageInfo.captureDistance + 1">+</button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">采集员</div> <div class="form-label">采集员</div>
<div class="form-input"> <div class="form-input">
<input v-model="imageInfo.operator" type="text"> <input type="text" v-model="imageInfo.operator">
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-label">相机型号</div> <div class="form-label">相机型号</div>
<div class="form-input"> <div class="form-input">
<input v-model="imageInfo.cameraModel" type="text"> <input type="text" v-model="imageInfo.cameraModel">
</div> </div>
</div> </div>
</div> </div>
@ -256,30 +256,24 @@
</div> </div>
</div> </div>
</div> </div>
<div class="dialog-footer"> <div class="dialog-footer">
<button <button
v-if="currentStep > 1" v-if="currentStep > 1"
class="dialog-button" class="dialog-button"
@click="currentStep--" @click="currentStep--"
> >上一步</button>
上一步 <button
</button> v-if="currentStep < 3"
<button class="dialog-button"
v-if="currentStep < 3"
class="dialog-button"
:disabled="!canGoNext"
@click="nextStep" @click="nextStep"
> :disabled="!canGoNext"
下一步 >下一步</button>
</button> <button
<button v-if="currentStep === 3"
v-if="currentStep === 3" class="dialog-button primary"
class="dialog-button primary"
@click="finishImport" @click="finishImport"
> >完成导入</button>
完成导入
</button>
<button class="dialog-button" @click="closeDialog">取消</button> <button class="dialog-button" @click="closeDialog">取消</button>
</div> </div>
</div> </div>
@ -287,7 +281,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, reactive, ref } from 'vue' import { ref, computed, reactive, onBeforeUnmount } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
// //
@ -358,7 +352,7 @@ const selectedPartId = ref('')
// //
const selectedPart = computed(() => { const selectedPart = computed(() => {
if (!selectedPartId.value) return null if (!selectedPartId.value) return null
return props.availableParts?.find((part) => String(getPartId(part)) === String(selectedPartId.value)) return props.availableParts?.find(part => String(getPartId(part)) === String(selectedPartId.value))
}) })
// //
@ -366,8 +360,8 @@ const importImages = ref<ImportImage[]>([])
// //
const imageInfo = reactive<ImageInfo>({ const imageInfo = reactive<ImageInfo>({
startTime: `${formatCurrentDate()} 00:00`, startTime: formatCurrentDate() + ' 00:00',
endTime: `${formatCurrentDate()} 23:59`, endTime: formatCurrentDate() + ' 23:59',
weather: '晴天', weather: '晴天',
humidity: 50, humidity: 50,
minTemperature: 15, minTemperature: 15,
@ -377,7 +371,7 @@ const imageInfo = reactive<ImageInfo>({
captureMethod: '无人机航拍', captureMethod: '无人机航拍',
captureDistance: 50, captureDistance: 50,
operator: '', operator: '',
cameraModel: 'ILCE-7RM4', cameraModel: 'ILCE-7RM4'
}) })
// //
@ -422,12 +416,12 @@ function getPartName(part: any): string {
// //
function selectPart(part: any) { function selectPart(part: any) {
const partId = getPartId(part) const partId = getPartId(part)
if (!part || !partId) { if (!part || !partId) {
console.error('部件数据无效:', part) console.error('部件数据无效:', part)
return return
} }
selectedPartId.value = String(partId) selectedPartId.value = String(partId)
} }
@ -440,10 +434,10 @@ function handleAddImages() {
function handleFileSelected(event: Event) { function handleFileSelected(event: Event) {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
if (target.files && target.files.length > 0) { if (target.files && target.files.length > 0) {
const newImages: ImportImage[] = Array.from(target.files).map((file) => { const newImages: ImportImage[] = Array.from(target.files).map(file => {
// URL // URL
const previewUrl = URL.createObjectURL(file) const previewUrl = URL.createObjectURL(file)
// //
return { return {
file, file,
@ -454,14 +448,14 @@ function handleFileSelected(event: Event) {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
pixelSize: '155.00', pixelSize: '155.00',
selected: false, selected: false,
previewUrl, previewUrl
} }
}) })
// //
importImages.value = [...importImages.value, ...newImages] importImages.value = [...importImages.value, ...newImages]
} }
// //
if (fileInput.value) fileInput.value.value = '' if (fileInput.value) fileInput.value.value = ''
} }
@ -469,13 +463,13 @@ function handleFileSelected(event: Event) {
// //
function handleRemoveImages() { function handleRemoveImages() {
// URL // URL
importImages.value.filter((image) => image.selected).forEach((image) => { importImages.value.filter(image => image.selected).forEach(image => {
if (image.previewUrl) { if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl) URL.revokeObjectURL(image.previewUrl)
} }
}) })
importImages.value = importImages.value.filter((image) => !image.selected) importImages.value = importImages.value.filter(image => !image.selected)
} }
// //
@ -486,17 +480,17 @@ function toggleImageSelection(image: ImportImage) {
// / // /
function toggleSelectAll(event: Event) { function toggleSelectAll(event: Event) {
const checked = (event.target as HTMLInputElement).checked const checked = (event.target as HTMLInputElement).checked
importImages.value.forEach((image) => image.selected = checked) importImages.value.forEach(image => image.selected = checked)
} }
// //
const hasSelectedImages = computed(() => { const hasSelectedImages = computed(() => {
return importImages.value.some((image) => image.selected) return importImages.value.some(image => image.selected)
}) })
// //
const allImagesSelected = computed(() => { const allImagesSelected = computed(() => {
return importImages.value.length > 0 && importImages.value.every((image) => image.selected) return importImages.value.length > 0 && importImages.value.every(image => image.selected)
}) })
// //
@ -531,33 +525,33 @@ function finishImport() {
Message.error('请选择部件') Message.error('请选择部件')
return return
} }
if (importImages.value.length === 0) { if (importImages.value.length === 0) {
Message.error('请添加图像') Message.error('请添加图像')
return return
} }
// //
const files = importImages.value.map((image) => image.file!).filter(Boolean) const files = importImages.value.map(image => image.file!).filter(Boolean)
// //
const partData = { const partData = {
partId: getPartId(selectedPart.value), partId: getPartId(selectedPart.value),
id: getPartId(selectedPart.value), // id: getPartId(selectedPart.value), //
name: getPartName(selectedPart.value), name: getPartName(selectedPart.value),
partName: getPartName(selectedPart.value), // partName: getPartName(selectedPart.value), //
partType: selectedPart.value.partType, partType: selectedPart.value.partType
} }
// //
emit('import-success', { emit('import-success', {
part: partData, part: partData,
images: files, images: files,
imageInfo: { ...imageInfo }, imageInfo: { ...imageInfo }
}) })
Message.success('图像导入成功') Message.success('图像导入成功')
// //
closeDialog() closeDialog()
} }
@ -573,19 +567,19 @@ function closeDialog() {
function resetState() { function resetState() {
currentStep.value = 1 currentStep.value = 1
selectedPartId.value = '' selectedPartId.value = ''
// URL // URL
importImages.value.forEach((image) => { importImages.value.forEach(image => {
if (image.previewUrl) { if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl) URL.revokeObjectURL(image.previewUrl)
} }
}) })
importImages.value = [] importImages.value = []
// //
Object.assign(imageInfo, { Object.assign(imageInfo, {
startTime: `${formatCurrentDate()} 00:00`, startTime: formatCurrentDate() + ' 00:00',
endTime: `${formatCurrentDate()} 23:59`, endTime: formatCurrentDate() + ' 23:59',
weather: '晴天', weather: '晴天',
humidity: 70, humidity: 70,
minTemperature: 20, minTemperature: 20,
@ -593,14 +587,14 @@ function resetState() {
windPower: 0, windPower: 0,
captureMethod: '无人机拍摄', captureMethod: '无人机拍摄',
captureDistance: 15, captureDistance: 15,
operator: '', operator: ''
}) })
} }
// URL // URL
onBeforeUnmount(() => { onBeforeUnmount(() => {
// URL // URL
importImages.value.forEach((image) => { importImages.value.forEach(image => {
if (image.previewUrl) { if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl) URL.revokeObjectURL(image.previewUrl)
} }
@ -1133,4 +1127,4 @@ onBeforeUnmount(() => {
.dialog-button.primary:hover { .dialog-button.primary:hover {
background-color: #2563eb; background-color: #2563eb;
} }
</style> </style>

View File

@ -1,4 +1,4 @@
import IndustrialImageList from './index.vue' import IndustrialImageList from './index.vue'
export type { IndustrialImage } from './index.vue' export type { IndustrialImage } from './index.vue'
export default IndustrialImageList export default IndustrialImageList

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="industrial-image-list" :class="{ collapsed: isCollapsed }"> <div class="industrial-image-list" :class="{ 'collapsed': isCollapsed }">
<div v-if="!isCollapsed" class="header-actions"> <div class="header-actions" v-if="!isCollapsed">
<slot name="header-left"> <slot name="header-left">
<a-button v-if="showImportButton" type="primary" @click="handleImportImages"> <a-button v-if="showImportButton" type="primary" @click="handleImportImages">
<template #icon><IconUpload /></template> <template #icon><icon-upload /></template>
导入图像 导入图像
</a-button> </a-button>
</slot> </slot>
<div v-if="showSearch" class="search-bar"> <div class="search-bar" v-if="showSearch">
<a-input-search <a-input-search
v-model="searchKeyword" v-model="searchKeyword"
placeholder="输入关键字搜索" placeholder="输入关键字搜索"
@ -18,24 +18,24 @@
</div> </div>
<slot name="header-right"></slot> <slot name="header-right"></slot>
<div class="collapse-button"> <div class="collapse-button">
<a-button <a-button
type="text" type="text"
@click="toggleCollapse" @click="toggleCollapse"
> >
<template #icon> <template #icon>
<IconUp /> <icon-up />
</template> </template>
收起 收起
</a-button> </a-button>
</div> </div>
</div> </div>
<div v-show="!isCollapsed" class="image-grid"> <div class="image-grid" v-show="!isCollapsed">
<div v-if="imageList.length === 0" class="empty-data"> <div v-if="imageList.length === 0" class="empty-data">
<IconImage class="empty-icon" /> <icon-image class="empty-icon" />
<p>{{ emptyText }}</p> <p>{{ emptyText }}</p>
</div> </div>
<div v-else class="image-thumbnails"> <div v-else class="image-thumbnails">
<div <div
v-for="image in imageList" v-for="image in imageList"
@ -45,14 +45,14 @@
@click="handleImageSelect(image)" @click="handleImageSelect(image)"
> >
<div class="thumbnail-image"> <div class="thumbnail-image">
<img <img
:src="getImageUrl(image.imagePath)" :src="getImageUrl(image.imagePath)"
:alt="image.imageName" :alt="image.imageName"
@error="handleImageError" @error="handleImageError"
@load="handleImageLoad" @load="handleImageLoad"
/> />
<div v-if="!image.imagePath" class="image-placeholder"> <div class="image-placeholder" v-if="!image.imagePath">
<IconImage /> <icon-image />
<span>暂无图像</span> <span>暂无图像</span>
</div> </div>
<div class="thumbnail-overlay"> <div class="thumbnail-overlay">
@ -62,13 +62,13 @@
</div> </div>
<div class="image-actions"> <div class="image-actions">
<a-button v-if="showPreviewAction" type="text" size="small" @click.stop="handleImagePreview(image)"> <a-button v-if="showPreviewAction" type="text" size="small" @click.stop="handleImagePreview(image)">
<IconEye /> <icon-eye />
</a-button> </a-button>
<a-button v-if="showProcessAction" type="text" size="small" @click.stop="handleImageProcess(image)"> <a-button v-if="showProcessAction" type="text" size="small" @click.stop="handleImageProcess(image)">
<IconSettings /> <icon-settings />
</a-button> </a-button>
<a-button v-if="showDeleteAction" type="text" size="small" status="danger" @click.stop="handleImageDelete(image)"> <a-button v-if="showDeleteAction" type="text" size="small" status="danger" @click.stop="handleImageDelete(image)">
<IconDelete /> <icon-delete />
</a-button> </a-button>
<slot name="item-actions" :image="image"></slot> <slot name="item-actions" :image="image"></slot>
</div> </div>
@ -81,7 +81,7 @@
<span v-if="image.defectCount" class="defect-count">缺陷: {{ image.defectCount }}</span> <span v-if="image.defectCount" class="defect-count">缺陷: {{ image.defectCount }}</span>
<slot name="item-meta" :image="image"></slot> <slot name="item-meta" :image="image"></slot>
</div> </div>
<div v-if="image.partName || image.shootingTime" class="thumbnail-extra"> <div class="thumbnail-extra" v-if="image.partName || image.shootingTime">
<span v-if="image.partName" class="part-name">{{ image.partName }}</span> <span v-if="image.partName" class="part-name">{{ image.partName }}</span>
<span v-if="image.shootingTime" class="capture-time">{{ formatTime(image.shootingTime) }}</span> <span v-if="image.shootingTime" class="capture-time">{{ formatTime(image.shootingTime) }}</span>
</div> </div>
@ -90,15 +90,15 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 收起状态下的展开按钮 --> <!-- 收起状态下的展开按钮 -->
<div v-if="isCollapsed" class="expand-button-container"> <div v-if="isCollapsed" class="expand-button-container">
<a-button <a-button
type="primary" type="primary"
@click="toggleCollapse" @click="toggleCollapse"
> >
<template #icon> <template #icon>
<IconDown /> <icon-down />
</template> </template>
展开图像列表 展开图像列表
</a-button> </a-button>
@ -108,14 +108,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { import {
IconDelete,
IconDown,
IconEye,
IconImage,
IconSettings,
IconUp,
IconUpload, IconUpload,
IconImage,
IconEye,
IconSettings,
IconDelete,
IconUp,
IconDown
} from '@arco-design/web-vue/es/icon' } from '@arco-design/web-vue/es/icon'
export interface IndustrialImage { export interface IndustrialImage {
@ -133,40 +133,40 @@ export interface IndustrialImage {
const props = defineProps({ const props = defineProps({
imageList: { imageList: {
type: Array as () => IndustrialImage[], type: Array as () => IndustrialImage[],
default: () => [], default: () => []
}, },
selectedImageId: { selectedImageId: {
type: String, type: String,
default: '', default: ''
}, },
baseUrl: { baseUrl: {
type: String, type: String,
default: 'http://localhost:8080', default: 'http://pms.dtyx.net:9158'
}, },
emptyText: { emptyText: {
type: String, type: String,
default: '暂无图像数据', default: '暂无图像数据'
}, },
showImportButton: { showImportButton: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showSearch: { showSearch: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showPreviewAction: { showPreviewAction: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showProcessAction: { showProcessAction: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showDeleteAction: { showDeleteAction: {
type: Boolean, type: Boolean,
default: true, default: true
}, }
}) })
const emit = defineEmits<{ const emit = defineEmits<{
@ -252,7 +252,7 @@ const formatTime = (timeString: string): string => {
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}) })
} catch { } catch {
return timeString return timeString
@ -297,7 +297,7 @@ const formatTime = (timeString: string): string => {
margin-left: auto; margin-left: auto;
} }
} }
/* 收起状态下的展开按钮容器 */ /* 收起状态下的展开按钮容器 */
.expand-button-container { .expand-button-container {
position: absolute; position: absolute;
@ -312,21 +312,21 @@ const formatTime = (timeString: string): string => {
overflow-y: auto; overflow-y: auto;
padding: 16px; padding: 16px;
height: calc(100% - 60px); /* 减去header-actions的高度 */ height: calc(100% - 60px); /* 减去header-actions的高度 */
// //
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 8px; width: 8px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background: #f1f5f9; background: #f1f5f9;
border-radius: 4px; border-radius: 4px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #cbd5e1; background: #cbd5e1;
border-radius: 4px; border-radius: 4px;
&:hover { &:hover {
background: #94a3b8; background: #94a3b8;
} }
@ -526,4 +526,4 @@ const formatTime = (timeString: string): string => {
} }
} }
} }
</style> </style>

View File

@ -1,105 +1,102 @@
<template> <template>
<div class="turbine-grid-container"> <div class="turbine-grid-container">
<div class="turbine-grid"> <div class="turbine-grid">
<div <div v-for="turbine in turbines" :key="turbine.id" class="turbine-card"
v-for="turbine in turbines" :key="turbine.id" class="turbine-card" :class="getStatusClass(turbine.status)">
:class="getStatusClass(turbine.status)" <div class="turbine-status-badge" :class="`status-${turbine.status}`">
> {{ getStatusText(turbine.status) }}
<div class="turbine-status-badge" :class="`status-${turbine.status}`"> </div>
{{ getStatusText(turbine.status) }}
<div class="turbine-icon">
<img src="/static/images/wind-turbine-icon.svg" alt="风机图标" class="turbine-image" />
</div>
<div class="turbine-info">
<div class="turbine-number">
<a-input v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
@change="handleTurbineNoChange(turbine)" />
</div>
</div>
<div class="turbine-actions">
<a-button type="text" size="mini" @click="openMapModal(turbine)" title="地图选点">
<template #icon><icon-location /></template>
</a-button>
<a-button type="text" size="mini" @click="editTurbine(turbine)" title="编辑">
<template #icon><icon-edit /></template>
</a-button>
</div>
</div>
</div> </div>
<div class="turbine-icon"> <!-- 添加新机组按钮 -->
<img src="/static/images/wind-turbine-icon.svg" alt="风机图标" class="turbine-image" /> <div v-if="showAddButton" class="turbine-card add-turbine-card" @click="addTurbine">
<div class="add-icon">
<icon-plus />
</div>
<div class="add-text">添加机组</div>
</div> </div>
<div class="turbine-info">
<div class="turbine-number">
<a-input
v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
@change="handleTurbineNoChange(turbine)"
/>
</div>
</div>
<div class="turbine-actions">
<a-button type="text" size="mini" title="地图选点" @click="openMapModal(turbine)">
<template #icon><icon-location /></template>
</a-button>
<a-button type="text" size="mini" title="编辑" @click="editTurbine(turbine)">
<template #icon><icon-edit /></template>
</a-button>
</div>
</div>
</div> </div>
<!-- 添加新机组按钮 -->
<div v-if="showAddButton" class="turbine-card add-turbine-card" @click="addTurbine">
<div class="add-icon">
<icon-plus />
</div>
<div class="add-text">添加机组</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
interface Turbine { interface Turbine {
id: number id: number
turbineNo: string turbineNo: string
status: 0 | 1 | 2 // 0: , 1: , 2: status: 0 | 1 | 2 // 0: , 1: , 2:
lat?: number lat?: number
lng?: number lng?: number
} }
interface Props { interface Props {
turbines: Turbine[] turbines: Turbine[]
showAddButton?: boolean showAddButton?: boolean
} }
interface Emits { interface Emits {
(e: 'update:turbines', turbines: Turbine[]): void (e: 'update:turbines', turbines: Turbine[]): void
(e: 'turbine-change', turbine: Turbine): void (e: 'turbine-change', turbine: Turbine): void
(e: 'add-turbine'): void (e: 'add-turbine'): void
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
showAddButton: false, showAddButton: false
}) })
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const getStatusText = (status: number) => { const getStatusText = (status: number) => {
const statusMap = { const statusMap = {
0: '待施工', 0: '待施工',
1: '施工中', 1: '施工中',
2: '已完成', 2: '已完成'
} }
return statusMap[status] || '未知状态' return statusMap[status] || '未知状态'
} }
const getStatusClass = (status: number) => { const getStatusClass = (status: number) => {
return `status-${status}` return `status-${status}`
} }
const handleTurbineNoChange = (turbine: Turbine) => { const handleTurbineNoChange = (turbine: Turbine) => {
emit('turbine-change', turbine) emit('turbine-change', turbine)
emit('update:turbines', props.turbines) emit('update:turbines', props.turbines)
} }
const openMapModal = (turbine: Turbine) => { const openMapModal = (turbine: Turbine) => {
Message.info(`地图选点功能待开发,当前机组编号:${turbine.turbineNo}`) Message.info(`地图选点功能待开发,当前机组编号:${turbine.turbineNo}`)
} }
const editTurbine = (turbine: Turbine) => { const editTurbine = (turbine: Turbine) => {
// //
Message.info(`编辑机组:${turbine.turbineNo}`) Message.info(`编辑机组:${turbine.turbineNo}`)
} }
const addTurbine = () => { const addTurbine = () => {
emit('add-turbine') emit('add-turbine')
} }
</script> </script>

View File

@ -3,7 +3,7 @@
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :span="24" :md="17"> <a-col :span="24" :md="17">
<GiTable <GiTable
v-model:selected-keys="selectedKeys" v-model:selectedKeys="selectedKeys"
row-key="id" row-key="id"
:data="dataList" :data="dataList"
:columns="listColumns" :columns="listColumns"

View File

@ -225,9 +225,9 @@ export default {
// //
const captchaVerification = secretKey.value const captchaVerification = secretKey.value
? encryptByAes( ? encryptByAes(
`${backToken.value}---${JSON.stringify(checkPosArr)}`, `${backToken.value}---${JSON.stringify(checkPosArr)}`,
secretKey.value, secretKey.value,
) )
: `${backToken.value}---${JSON.stringify(checkPosArr)}` : `${backToken.value}---${JSON.stringify(checkPosArr)}`
const data = { const data = {
captchaType: captchaType.value, captchaType: captchaType.value,

View File

@ -231,7 +231,7 @@ export default {
) { ) {
move_block_left move_block_left
= barArea.value.offsetWidth = barArea.value.offsetWidth
- Number.parseInt(blockSize.value.width, 10) / 2 - 2 - Number.parseInt(blockSize.value.width, 10) / 2 - 2
} }
if (move_block_left <= 0) { if (move_block_left <= 0) {
move_block_left = Number.parseInt(blockSize.value.width, 10) / 2 move_block_left = Number.parseInt(blockSize.value.width, 10) / 2
@ -281,9 +281,9 @@ export default {
captchaType: captchaType.value, captchaType: captchaType.value,
pointJson: secretKey.value pointJson: secretKey.value
? encryptByAes( ? encryptByAes(
JSON.stringify({ x: moveLeftDistance, y: 5.0 }), JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
secretKey.value, secretKey.value,
) )
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }), : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
token: backToken.value, token: backToken.value,
} }
@ -303,21 +303,21 @@ export default {
} }
passFlag.value = true passFlag.value = true
tipWords.value = `${( tipWords.value = `${(
(endMovetime.value - startMoveTime.value) (endMovetime.value - startMoveTime.value)
/ 1000 / 1000
).toFixed(2)}s验证成功` ).toFixed(2)}s验证成功`
const captchaVerification = secretKey.value const captchaVerification = secretKey.value
? encryptByAes( ? encryptByAes(
`${backToken.value}---${JSON.stringify({ `${backToken.value}---${JSON.stringify({
x: moveLeftDistance, x: moveLeftDistance,
y: 5.0, y: 5.0,
})}`, })}`,
secretKey.value, secretKey.value,
) )
: `${backToken.value}---${JSON.stringify({ : `${backToken.value}---${JSON.stringify({
x: moveLeftDistance, x: moveLeftDistance,
y: 5.0, y: 5.0,
})}` })}`
setTimeout(() => { setTimeout(() => {
tipWords.value = '' tipWords.value = ''
proxy.$parent.closeBox() proxy.$parent.closeBox()

View File

@ -11,18 +11,18 @@ export function useDept(options?: { onSuccess?: () => void }) {
try { try {
loading.value = true loading.value = true
const res = await getDeptTree({ deptName }) const res = await getDeptTree({ deptName })
// 处理部门树数据确保有title字段用于显示 // 处理部门树数据确保有title字段用于显示
const processDeptData = (data: any[]): TreeNodeData[] => { const processDeptData = (data: any[]): TreeNodeData[] => {
if (!data || !data.length) return [] if (!data || !data.length) return []
return data.map((item) => ({ return data.map(item => ({
key: item.deptId, key: item.deptId,
title: item.deptName || '未命名部门', // 将deptName映射为title title: item.deptName || '未命名部门', // 将deptName映射为title
children: item.children ? processDeptData(item.children) : [], children: item.children ? processDeptData(item.children) : []
})) }))
} }
deptList.value = processDeptData(res.data || []) deptList.value = processDeptData(res.data || [])
options?.onSuccess && options.onSuccess() options?.onSuccess && options.onSuccess()
} finally { } finally {

View File

@ -2,7 +2,7 @@ import { listPost } from '@/apis/system/post'
import type { PostVO } from '@/apis/system/type' import type { PostVO } from '@/apis/system/type'
export function usePost() { export function usePost() {
const postList = ref<{ label: string, value: string }[]>([]) const postList = ref<{ label: string; value: string }[]>([])
const loading = ref(false) const loading = ref(false)
// 获取岗位列表 // 获取岗位列表
@ -24,6 +24,6 @@ export function usePost() {
return { return {
postList, postList,
loading, loading,
getPostList, getPostList
} }
} }

View File

@ -11,16 +11,16 @@ export function useRole(options?: { onSuccess?: () => void }) {
try { try {
loading.value = true loading.value = true
const res = await fetchRoleList() const res = await fetchRoleList()
// 将新的角色数据格式转换为表单需要的 LabelValueState 格式 // 将新的角色数据格式转换为表单需要的 LabelValueState 格式
if (res && res.data) { if (res && res.data) {
roleList.value = (res.data || []).map((role) => ({ roleList.value = (res.data || []).map(role => ({
label: role.roleName, label: role.roleName,
value: role.roleId, value: role.roleId,
disabled: role.status !== '1', // 假设状态为1表示启用 disabled: role.status !== '1' // 假设状态为1表示启用
})) }))
} }
options?.onSuccess && options.onSuccess() options?.onSuccess && options.onSuccess()
} finally { } finally {
loading.value = false loading.value = false

View File

@ -19,6 +19,7 @@ import Asider from './components/Asider/index.vue'
import Header from './components/Header/index.vue' import Header from './components/Header/index.vue'
import Main from './components/Main.vue' import Main from './components/Main.vue'
import Tabs from './components/Tabs/index.vue' import Tabs from './components/Tabs/index.vue'
import GiFooter from '@/components/GiFooter/index.vue'
import NoticePopup from '@/views/user/message/components/NoticePopup.vue' import NoticePopup from '@/views/user/message/components/NoticePopup.vue'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'

View File

@ -19,6 +19,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Menu from '../Menu/index.vue' import Menu from '../Menu/index.vue'
import Logo from '../Logo.vue' import Logo from '../Logo.vue'
import WwAds from '../WwAds.vue'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'

View File

@ -76,10 +76,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { Modal } from '@arco-design/web-vue' import { Modal } from '@arco-design/web-vue'
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
import { nextTick, onMounted, ref } from 'vue' import { onMounted, ref, nextTick } from 'vue'
import Message from './Message.vue' import Message from './Message.vue'
import SettingDrawer from './SettingDrawer.vue' import SettingDrawer from './SettingDrawer.vue'
import Search from './Search.vue' import Search from './Search.vue'
import { getUnreadMessageCount } from '@/apis'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { useBreakpoint, useDevice } from '@/hooks' import { useBreakpoint, useDevice } from '@/hooks'
@ -106,17 +107,17 @@ const initWebSocket = (token: string) => {
if (initTimer) { if (initTimer) {
clearTimeout(initTimer) clearTimeout(initTimer)
} }
initTimer = setTimeout(() => { initTimer = setTimeout(() => {
// //
if (socket) { if (socket) {
socket.close() socket.close()
socket = null socket = null
} }
try { try {
socket = new WebSocket(`${import.meta.env.VITE_API_WS_URL}/websocket?token=${token}`) socket = new WebSocket(`${import.meta.env.VITE_API_WS_URL}/websocket?token=${token}`)
socket.onopen = () => { socket.onopen = () => {
// console.log('WebSocket connection opened') // console.log('WebSocket connection opened')
} }
@ -139,7 +140,7 @@ const initWebSocket = (token: string) => {
} catch (error) { } catch (error) {
console.error('Failed to create WebSocket connection:', error) console.error('Failed to create WebSocket connection:', error)
} }
initTimer = null initTimer = null
}, 100) // 100ms }, 100) // 100ms
} }

View File

@ -18,9 +18,10 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const appStore = useAppStore() const appStore = useAppStore()
// const title = computed(() => appStore.getTitle()) // const title = computed(() => appStore.getTitle())
const title = '数智平台' const title = "数智平台"
const logo = '/logo.png' const logo = "/logo.png"
// computed(() => appStore.getLogo()) //computed(() => appStore.getLogo())
interface Props { interface Props {
collapsed?: boolean collapsed?: boolean

View File

@ -52,7 +52,7 @@ watchEffect(() => {
const children = props.item?.children?.length ? props.item.children : [] const children = props.item?.children?.length ? props.item.children : []
// //
const showingChildren = children.filter((i) => i.meta?.hidden === false) const showingChildren = children.filter((i) => i.meta?.hidden === false)
if (showingChildren.length) { if (showingChildren.length) {
// hidden: false // hidden: false
onlyOneChild.value = showingChildren[showingChildren.length - 1] onlyOneChild.value = showingChildren[showingChildren.length - 1]

View File

@ -2,7 +2,7 @@ import { createApp } from 'vue'
import ArcoVue, { Card, Drawer, Modal } from '@arco-design/web-vue' import ArcoVue, { Card, Drawer, Modal } from '@arco-design/web-vue'
import '@/styles/arco-ui/index.less' import '@/styles/arco-ui/index.less'
// import '@arco-themes/vue-gi-demo/index.less' // import '@arco-themes/vue-gi-demo/index.less'
import '@arco-design/web-vue/dist/arco.css' // import '@arco-design/web-vue/dist/arco.css'
// 额外引入 Arco Design Icon图标库 // 额外引入 Arco Design Icon图标库
import ArcoVueIcon from '@arco-design/web-vue/es/icon' import ArcoVueIcon from '@arco-design/web-vue/es/icon'

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, reactive, toRefs, watchEffect } from 'vue' import { computed, reactive, toRefs, watch, watchEffect } from 'vue'
import { generate, getRgbStr } from '@arco-design/color' import { generate, getRgbStr } from '@arco-design/color'
import type { BasicConfig } from '@/apis' import { type BasicConfig, listSiteOptionDict } from '@/apis'
import { getSettings } from '@/config/setting' import { getSettings } from '@/config/setting'
const storeSetup = () => { const storeSetup = () => {
@ -81,18 +81,18 @@ const storeSetup = () => {
document.title = config.SITE_TITLE || '' document.title = config.SITE_TITLE || ''
document.querySelector('link[rel="shortcut icon"]')?.setAttribute('href', config.SITE_FAVICON || '/favicon.ico') document.querySelector('link[rel="shortcut icon"]')?.setAttribute('href', config.SITE_FAVICON || '/favicon.ico')
} }
// 使用watchEffect优化监听避免递归更新 // 使用watchEffect优化监听避免递归更新
watchEffect(() => { watchEffect(() => {
const filters = [] as string[] const filters = [] as string[]
if (settingConfig.enableMourningMode) { if (settingConfig.enableMourningMode) {
filters.push('grayscale(100%)') filters.push('grayscale(100%)')
} }
if (settingConfig.enableColorWeaknessMode) { if (settingConfig.enableColorWeaknessMode) {
filters.push('invert(80%)') filters.push('invert(80%)')
} }
// 如果没有任何滤镜条件,移除 `filter` 样式 // 如果没有任何滤镜条件,移除 `filter` 样式
if (filters.length === 0) { if (filters.length === 0) {
document.documentElement.style.removeProperty('filter') document.documentElement.style.removeProperty('filter')

View File

@ -0,0 +1,49 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface Regulation {
id: string
name: string
type: string
typeName: string
publisher: string
publishTime: string
effectiveDate: string
confirmStatus: 'pending' | 'confirmed'
content: string
scope: string
requirements: string
notes: string
}
export const useRegulationStore = defineStore('regulation', () => {
// 已发布的制度列表
const publishedRegulations = ref<Regulation[]>([])
// 添加新发布的制度
const addPublishedRegulation = (regulation: Regulation) => {
publishedRegulations.value.unshift(regulation)
}
// 更新制度确认状态
const updateRegulationConfirmStatus = (id: string, status: 'pending' | 'confirmed') => {
const regulation = publishedRegulations.value.find(item => item.id === id)
if (regulation) {
regulation.confirmStatus = status
}
}
// 批量确认所有制度
const confirmAllRegulations = () => {
publishedRegulations.value.forEach(regulation => {
regulation.confirmStatus = 'confirmed'
})
}
return {
publishedRegulations,
addPublishedRegulation,
updateRegulationConfirmStatus,
confirmAllRegulations
}
})

View File

@ -152,31 +152,17 @@ const storeSetup = () => {
{ {
id: 1070, id: 1070,
parentId: 1000, parentId: 1000,
title: '部门管理', title: '个人中心',
type: 2, type: 2,
path: '/system/dept', path: '/user/profile',
name: 'SystemDept', name: 'UserProfile',
component: 'system/dept/index', component: 'user/profile/index',
icon: 'mind-mapping', icon: 'user',
isExternal: false, isExternal: false,
isCache: false, isCache: false,
isHidden: false, isHidden: false,
sort: 4, sort: 4,
}, },
{
id: 1090,
parentId: 1000,
title: '岗位管理',
type: 2,
path: '/system/post',
name: 'SystemPost',
component: 'system/post/index',
icon: 'settings',
isExternal: false,
isCache: false,
isHidden: false,
sort: 5,
},
], ],
}] }]
// 使用已转换的数据生成路由 // 使用已转换的数据生成路由

View File

@ -5,10 +5,10 @@ import {
type AccountLoginReq, type AccountLoginReq,
AuthTypeConstants, AuthTypeConstants,
type DeptDetail,
type PhoneLoginReq, type PhoneLoginReq,
type RoleDetail,
type UserDetail, type UserDetail,
type DeptDetail,
type RoleDetail,
type UserInfo, type UserInfo,
accountLogin as accountLoginApi, accountLogin as accountLoginApi,
@ -21,10 +21,10 @@ import { clearToken, getToken, setToken } from '@/utils/auth'
import { resetHasRouteFlag } from '@/router/guard' import { resetHasRouteFlag } from '@/router/guard'
interface NewUserInfoData { interface NewUserInfoData {
user: UserDetail user: UserDetail;
dept: DeptDetail dept: DeptDetail;
roles: RoleDetail[] roles: RoleDetail[];
posts: any[] posts: any[];
} }
const storeSetup = () => { const storeSetup = () => {
@ -43,7 +43,7 @@ const storeSetup = () => {
deptName: '', deptName: '',
avatar: '', avatar: '',
roles: [] as string[], roles: [] as string[],
permissions: [] as string[], permissions: [] as string[]
}) })
const nickname = computed(() => userInfo.name) const nickname = computed(() => userInfo.name)
const username = computed(() => userInfo.account) const username = computed(() => userInfo.account)
@ -68,6 +68,8 @@ const storeSetup = () => {
token.value = res.data.tokenValue token.value = res.data.tokenValue
} }
// 手机号登录 // 手机号登录
const phoneLogin = async (req: PhoneLoginReq) => { const phoneLogin = async (req: PhoneLoginReq) => {
const res = await phoneLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.PHONE }) const res = await phoneLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.PHONE })
@ -75,6 +77,8 @@ const storeSetup = () => {
token.value = res.data.token token.value = res.data.token
} }
// 退出登录回调 // 退出登录回调
const logoutCallBack = async () => { const logoutCallBack = async () => {
roles.value = [] roles.value = []
@ -98,12 +102,12 @@ const storeSetup = () => {
// 获取用户信息 // 获取用户信息
const getInfo = async () => { const getInfo = async () => {
const res = await getUserInfoApi() const res = await getUserInfoApi()
// 检查返回数据格式适配新旧API // 检查返回数据格式适配新旧API
if (res.data && 'user' in res.data) { if (res.data && 'user' in res.data) {
// 新API结构 // 新API结构
const { user, dept, roles: userRoles } = res.data as unknown as NewUserInfoData const { user, dept, roles: userRoles } = res.data as unknown as NewUserInfoData
// 更新用户基本信息 // 更新用户基本信息
userInfo.userId = user.userId userInfo.userId = user.userId
userInfo.account = user.account userInfo.account = user.account
@ -114,7 +118,7 @@ const storeSetup = () => {
userInfo.userType = user.userType userInfo.userType = user.userType
userInfo.mobile = user.mobile userInfo.mobile = user.mobile
userInfo.createTime = user.createTime userInfo.createTime = user.createTime
// 更新部门信息 // 更新部门信息
if (dept) { if (dept) {
userInfo.deptId = dept.deptId userInfo.deptId = dept.deptId
@ -124,23 +128,23 @@ const storeSetup = () => {
// 处理角色信息 // 处理角色信息
if (userRoles && userRoles.length) { if (userRoles && userRoles.length) {
// 提取角色键作为权限标识 // 提取角色键作为权限标识
const roleKeys = userRoles.map((role) => role.roleKey).filter(Boolean) as string[] const roleKeys = userRoles.map(role => role.roleKey).filter(Boolean) as string[]
roles.value = roleKeys roles.value = roleKeys
// 由于新API没有直接提供permissions这里默认给管理员全部权限 // 由于新API没有直接提供permissions这里默认给管理员全部权限
permissions.value = roleKeys.includes('admin') ? ['*:*:*'] : [] permissions.value = roleKeys.includes('admin') ? ['*:*:*'] : []
} }
} else if (res.data) { } else if (res.data) {
// 旧API结构保留兼容 // 旧API结构保留兼容
const oldData = res.data as unknown as UserInfo const oldData = res.data as unknown as UserInfo
// 映射旧结构到新结构 // 映射旧结构到新结构
userInfo.userId = oldData.id userInfo.userId = oldData.id
userInfo.account = oldData.username userInfo.account = oldData.username
userInfo.name = oldData.nickname userInfo.name = oldData.nickname
userInfo.avatar = oldData.avatar userInfo.avatar = oldData.avatar
userInfo.deptName = oldData.deptName userInfo.deptName = oldData.deptName
if (oldData.roles && oldData.roles.length) { if (oldData.roles && oldData.roles.length) {
roles.value = oldData.roles roles.value = oldData.roles
permissions.value = oldData.permissions permissions.value = oldData.permissions

View File

@ -1,4 +1,4 @@
@use './var.scss' as *; @import './var.scss';
body { body {
--margin: 14px; // 通用外边距 --margin: 14px; // 通用外边距

View File

@ -1,5 +1,5 @@
/* 全局样式 */ /* 全局样式 */
@use './var.scss' as *; @import './var.scss';
.w-full { .w-full {
width: 100%; width: 100%;

View File

@ -1,17 +1,17 @@
// 基础样式 // 基础样式
@use './base.scss'; @import './base.scss';
// 全局类名样式 // 全局类名样式
@use './global.scss'; @import './global.scss';
// 自定义原生滚动条样式 // 自定义原生滚动条样式
@use './scrollbar-reset.scss'; @import './scrollbar-reset.scss';
// 自定义 nprogress 插件进度条颜色 // 自定义 nprogress 插件进度条颜色
@use './nprogress.scss'; @import './nprogress.scss';
// 富文本的css主题颜色变量 // 富文本的css主题颜色变量
@use './editor.scss'; @import './editor.scss';
// 动画类名 // 动画类名
@use './animated.scss'; @import './animated.scss';

View File

@ -1,41 +0,0 @@
<template>
<div>
<h2>Console.log 测试</h2>
<button @click="testConsole">测试 Console.log</button>
<p>{{ message }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const message = ref('')
const testConsole = () => {
console.log('测试 console.log 是否正常工作')
console.warn('测试 console.warn')
console.error('测试 console.error')
message.value = '请查看浏览器控制台,应该能看到上述日志信息'
}
</script>
<style scoped>
div {
padding: 20px;
text-align: center;
}
button {
padding: 10px 20px;
margin: 10px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #40a9ff;
}
</style>

10
src/types/api.d.ts vendored
View File

@ -1,10 +1,10 @@
/** API响应通用类型 */ /** API响应通用类型 */
interface ApiRes<T> { interface ApiRes<T> {
code: number | string code: number | string;
status?: number status?: number;
success: boolean success: boolean;
msg: string msg: string;
data: T data: T;
} }
/** 分页响应数据格式 */ /** 分页响应数据格式 */

View File

@ -70,6 +70,6 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue') import('vue')
} }

View File

@ -1,179 +1,179 @@
export interface EquipmentPageQuery { export interface EquipmentPageQuery {
equipmentName?: string equipmentName?: string
equipmentType?: string equipmentType?: string
equipmentStatus?: string equipmentStatus?: string
equipmentSn?: string equipmentSn?: string
assetCode?: string assetCode?: string
brand?: string brand?: string
locationStatus?: string locationStatus?: string
healthStatus?: string healthStatus?: string
responsiblePerson?: string responsiblePerson?: string
useStatus?: string useStatus?: string
projectId?: string projectId?: string
userId?: string userId?: string
equipmentModel?: string equipmentModel?: string
specification?: string specification?: string
physicalLocation?: string physicalLocation?: string
supplierName?: string supplierName?: string
maintenancePerson?: string maintenancePerson?: string
inventoryBarcode?: string inventoryBarcode?: string
assetRemark?: string assetRemark?: string
// 新增搜索字段 // 新增搜索字段
usingDepartment?: string usingDepartment?: string
invoice?: string invoice?: string
barcode?: string barcode?: string
importer?: string importer?: string
page?: number page?: number
pageSize?: number pageSize?: number
orderBy?: string orderBy?: string
orderDirection?: string orderDirection?: string
} }
export interface EquipmentReq { export interface EquipmentReq {
equipmentName: string equipmentName: string
equipmentModel: string equipmentModel: string
equipmentType: string equipmentType: string
equipmentStatus: string equipmentStatus: string
useStatus: string useStatus: string
equipmentSn: string equipmentSn: string
assetCode?: string assetCode?: string
brand?: string brand?: string
specification?: string specification?: string
locationStatus?: string locationStatus?: string
physicalLocation?: string physicalLocation?: string
responsiblePerson?: string responsiblePerson?: string
healthStatus?: string healthStatus?: string
purchaseTime?: string purchaseTime?: string
inStockTime?: string inStockTime?: string
activationTime?: string activationTime?: string
expectedScrapTime?: string expectedScrapTime?: string
actualScrapTime?: string actualScrapTime?: string
statusChangeTime?: string statusChangeTime?: string
purchaseOrder?: string purchaseOrder?: string
supplierName?: string supplierName?: string
purchasePrice?: number purchasePrice?: number
currentNetValue?: number currentNetValue?: number
depreciationMethod?: string depreciationMethod?: string
depreciationYears?: number depreciationYears?: number
salvageValue?: number salvageValue?: number
warrantyExpireDate?: string warrantyExpireDate?: string
lastMaintenanceDate?: string lastMaintenanceDate?: string
nextMaintenanceDate?: string nextMaintenanceDate?: string
maintenancePerson?: string maintenancePerson?: string
inventoryBarcode?: string inventoryBarcode?: string
assetRemark?: string assetRemark?: string
// 新增字段 // 新增字段
usingDepartment?: string usingDepartment?: string
borrowingTime?: string borrowingTime?: string
returnTime?: string returnTime?: string
outStockTime?: string outStockTime?: string
totalUsageTime?: string totalUsageTime?: string
depreciationRate?: number depreciationRate?: number
depreciationMethodDesc?: string depreciationMethodDesc?: string
invoice?: string invoice?: string
invoiceStatus?: string invoiceStatus?: string
attachments?: string attachments?: string
photos?: string photos?: string
barcode?: string barcode?: string
importer?: string importer?: string
inventoryTimeStatus1?: string inventoryTimeStatus1?: string
inventoryTimeStatus2?: string inventoryTimeStatus2?: string
inventoryTimeStatus3?: string inventoryTimeStatus3?: string
inventoryCheckTimeStatus1?: string inventoryCheckTimeStatus1?: string
inventoryCheckTimeStatus2?: string inventoryCheckTimeStatus2?: string
inventoryCheckTimeStatus3?: string inventoryCheckTimeStatus3?: string
} }
export interface EquipmentResp { export interface EquipmentResp {
equipmentId: string equipmentId: string
assetCode?: string assetCode?: string
equipmentName: string equipmentName: string
equipmentType: string equipmentType: string
equipmentTypeLabel?: string equipmentTypeLabel?: string
equipmentModel: string equipmentModel: string
equipmentSn: string equipmentSn: string
brand?: string brand?: string
specification?: string specification?: string
equipmentStatus: string equipmentStatus: string
equipmentStatusLabel?: string equipmentStatusLabel?: string
useStatus: string useStatus: string
locationStatus?: string locationStatus?: string
locationStatusLabel?: string locationStatusLabel?: string
physicalLocation?: string physicalLocation?: string
responsiblePerson?: string responsiblePerson?: string
healthStatus?: string healthStatus?: string
healthStatusLabel?: string healthStatusLabel?: string
purchaseTime?: string purchaseTime?: string
inStockTime?: string inStockTime?: string
activationTime?: string activationTime?: string
expectedScrapTime?: string expectedScrapTime?: string
actualScrapTime?: string actualScrapTime?: string
statusChangeTime?: string statusChangeTime?: string
purchaseOrder?: string purchaseOrder?: string
supplierName?: string supplierName?: string
purchasePrice?: number purchasePrice?: number
currentNetValue?: number currentNetValue?: number
depreciationMethod?: string depreciationMethod?: string
depreciationYears?: number depreciationYears?: number
salvageValue?: number salvageValue?: number
warrantyExpireDate?: string warrantyExpireDate?: string
lastMaintenanceDate?: string lastMaintenanceDate?: string
nextMaintenanceDate?: string nextMaintenanceDate?: string
maintenancePerson?: string maintenancePerson?: string
inventoryBarcode?: string inventoryBarcode?: string
assetRemark?: string assetRemark?: string
// 新增字段 // 新增字段
usingDepartment?: string usingDepartment?: string
borrowingTime?: string borrowingTime?: string
returnTime?: string returnTime?: string
outStockTime?: string outStockTime?: string
totalUsageTime?: string totalUsageTime?: string
depreciationRate?: number depreciationRate?: number
depreciationMethodDesc?: string depreciationMethodDesc?: string
invoice?: string invoice?: string
invoiceStatus?: string invoiceStatus?: string
attachments?: string attachments?: string
photos?: string photos?: string
barcode?: string barcode?: string
importer?: string importer?: string
inventoryTimeStatus1?: string inventoryTimeStatus1?: string
inventoryTimeStatus2?: string inventoryTimeStatus2?: string
inventoryTimeStatus3?: string inventoryTimeStatus3?: string
inventoryCheckTimeStatus1?: string inventoryCheckTimeStatus1?: string
inventoryCheckTimeStatus2?: string inventoryCheckTimeStatus2?: string
inventoryCheckTimeStatus3?: string inventoryCheckTimeStatus3?: string
projectId?: string projectId?: string
projectName?: string projectName?: string
userId?: string userId?: string
name?: string name?: string
createTime?: string createTime?: string
updateTime?: string updateTime?: string
} }
export interface EquipmentTypeOption { export interface EquipmentTypeOption {
label: string label: string
value: string value: string
} }
export interface EquipmentStatusOption { export interface EquipmentStatusOption {
label: string label: string
value: string value: string
color: string color: string
} }
export interface LocationStatusOption { export interface LocationStatusOption {
label: string label: string
value: string value: string
color: string color: string
} }
export interface HealthStatusOption { export interface HealthStatusOption {
label: string label: string
value: string value: string
color: string color: string
} }
export interface DepreciationMethodOption { export interface DepreciationMethodOption {
label: string label: string
value: string value: string
} }

View File

@ -1,71 +0,0 @@
export interface TrainingPlanPageQuery {
planName?: string
trainingType?: string
trainingLevel?: string
status?: string
trainer?: string
startTime?: string
endTime?: string
page?: number
pageSize?: number
}
export interface TrainingPlanReq {
planName: string
trainingType: string
trainingLevel: string
trainingContent?: string
trainer?: string
trainingLocation?: string
startTime: string
endTime: string
status?: string
maxParticipants?: number
requirements?: string
remark?: string
}
export interface TrainingPlanResp {
planId: string
planName: string
trainingType: string
trainingLevel: string
trainingContent?: string
trainer?: string
trainingLocation?: string
startTime: string
endTime: string
status: string
maxParticipants?: number
currentParticipants?: number
requirements?: string
remark?: string
createTime: string
createBy: string
materials?: TrainingMaterialResp[]
records?: TrainingRecordResp[]
}
export interface TrainingMaterialResp {
materialId: string
materialName: string
materialType: string
materialPath?: string
materialSize?: number
description?: string
sortOrder?: number
}
export interface TrainingRecordResp {
recordId: string
userId: string
userName: string
deptId?: string
deptName?: string
attendanceStatus: string
signInTime?: string
signOutTime?: string
score?: number
feedback?: string
certificateId?: string
}

View File

@ -18,7 +18,7 @@ export function encryptByMd5(txt: string) {
const publicKey const publicKey
= 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u' = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u'
+ 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ==' + 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
export function encryptByRsa(txt: string) { export function encryptByRsa(txt: string) {
const encryptor = new JSEncrypt() const encryptor = new JSEncrypt()
@ -36,7 +36,7 @@ export function encryptByAes(word: string, account: string) {
// 对账号做md5计算然后取8-24位作为密钥16个字符 // 对账号做md5计算然后取8-24位作为密钥16个字符
const accountMd5 = md5(account).toString() const accountMd5 = md5(account).toString()
const keyWord = accountMd5.substring(8, 24) // 取8-24位索引8-23共16位 const keyWord = accountMd5.substring(8, 24) // 取8-24位索引8-23共16位
const key = CryptoJS.enc.Utf8.parse(keyWord) const key = CryptoJS.enc.Utf8.parse(keyWord)
const arcs = CryptoJS.enc.Utf8.parse(word) const arcs = CryptoJS.enc.Utf8.parse(word)
const encrypted = CryptoJS.AES.encrypt(arcs, key, { const encrypted = CryptoJS.AES.encrypt(arcs, key, {

View File

@ -30,7 +30,7 @@ const StatusCodeMessage: ICodeMessage = {
} }
const http: AxiosInstance = axios.create({ const http: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
timeout: 30 * 1000, timeout: 30 * 1000,
}) })
@ -70,10 +70,10 @@ http.interceptors.response.use(
if (data && data.rows !== undefined && data.data === undefined) { if (data && data.rows !== undefined && data.data === undefined) {
data.data = data.rows data.data = data.rows
} }
// 兼容不同的API响应结构 // 兼容不同的API响应结构
const { success, code, msg } = data const { success, code, msg } = data
// 检查响应类型是否是blob // 检查响应类型是否是blob
if (response.request.responseType === 'blob') { if (response.request.responseType === 'blob') {
const contentType = data.type const contentType = data.type
@ -96,7 +96,7 @@ http.interceptors.response.use(
return response return response
} }
} }
// 判断请求是否成功明确的success字段为true或者code为200都视为成功 // 判断请求是否成功明确的success字段为true或者code为200都视为成功
const isSuccess = success !== undefined ? success : (code === 200 || code === '200') const isSuccess = success !== undefined ? success : (code === 200 || code === '200')
if (isSuccess) { if (isSuccess) {
@ -139,22 +139,17 @@ const request = async <T = unknown>(config: AxiosRequestConfig): Promise<ApiRes<
.then((res: AxiosResponse) => { .then((res: AxiosResponse) => {
// 处理返回数据结构兼容rows和data字段 // 处理返回数据结构兼容rows和data字段
const responseData = res.data const responseData = res.data
// 如果返回的数据中有rows字段但没有data字段将rows赋值给data // 如果返回的数据中有rows字段但没有data字段将rows赋值给data
if (responseData.rows !== undefined && responseData.data === undefined) { if (responseData.rows !== undefined && responseData.data === undefined) {
responseData.data = responseData.rows responseData.data = responseData.rows
} }
// 兼容后端返回的 status/code 格式
if (responseData.status === 200 || responseData.code === 200 || responseData.code === '200') {
responseData.success = true
}
// 如果返回的code是200但没有设置success字段将success设置为true // 如果返回的code是200但没有设置success字段将success设置为true
if ((responseData.code === 200 || responseData.code === '200') && responseData.success === undefined) { if ((responseData.code === 200 || responseData.code === '200') && responseData.success === undefined) {
responseData.success = true responseData.success = true
} }
return responseData return responseData
}) })
.catch((err: { msg: string }) => Promise.reject(err)) .catch((err: { msg: string }) => Promise.reject(err))

View File

@ -4,31 +4,31 @@
// API返回的菜单项类型 // API返回的菜单项类型
export interface ApiMenuItem { export interface ApiMenuItem {
menuId: string menuId: string;
parentId: string parentId: string;
menuName: string menuName: string;
menuType: string // 'catalog' | 'route' menuType: string; // 'catalog' | 'route'
orderNum: number orderNum: number;
visible: string visible: string;
children?: ApiMenuItem[] children?: ApiMenuItem[];
[key: string]: any // 其他可能的字段 [key: string]: any; // 其他可能的字段
} }
// 前端需要的菜单项类型 // 前端需要的菜单项类型
export interface FrontendMenuItem { export interface FrontendMenuItem {
id: number | string id: number | string;
parentId: number | string parentId: number | string;
title: string title: string;
type: number // 1表示目录2表示菜单 type: number; // 1表示目录2表示菜单
path: string path: string;
name: string name: string;
component: string component: string;
icon: string icon: string;
isExternal: boolean isExternal: boolean;
isCache: boolean isCache: boolean;
isHidden: boolean isHidden: boolean;
sort: number sort: number;
children?: FrontendMenuItem[] children?: FrontendMenuItem[];
} }
/** /**
@ -37,14 +37,14 @@ export interface FrontendMenuItem {
const convertMenuType = (menuType: string): number => { const convertMenuType = (menuType: string): number => {
switch (menuType.toLowerCase()) { switch (menuType.toLowerCase()) {
case 'catalog': case 'catalog':
return 1 return 1;
case 'route': case 'route':
return 2 return 2;
case 'button': case 'button':
return 3 return 3;
default: default:
// 默认为菜单类型 // 默认为菜单类型
return 2 return 2;
} }
} }
@ -52,7 +52,7 @@ const convertMenuType = (menuType: string): number => {
* : '0' -> false, '1' -> true * : '0' -> false, '1' -> true
*/ */
const convertVisible = (visible: string): boolean => { const convertVisible = (visible: string): boolean => {
return visible === '1' // '1'为隐藏,'0'为显示 return visible === '1'; // '1'为隐藏,'0'为显示
} }
/** /**
@ -60,27 +60,27 @@ const convertVisible = (visible: string): boolean => {
*/ */
const convertMenuItem = (apiItem: ApiMenuItem): FrontendMenuItem => { const convertMenuItem = (apiItem: ApiMenuItem): FrontendMenuItem => {
// 根据menuType生成默认的path和component // 根据menuType生成默认的path和component
let path = '' let path = '';
let component = '' let component = '';
let name = '' let name = '';
// 简单的名称生成,去掉空格,保持首字母大写,非首字母小写 // 简单的名称生成,去掉空格,保持首字母大写,非首字母小写
const generateName = (menuName: string): string => { const generateName = (menuName: string): string => {
return menuName.replace(/\s+/g, '') return menuName.replace(/\s+/g, '')
.replace(/^./, (match) => match.toUpperCase()) .replace(/^./, (match) => match.toUpperCase())
.replace(/[\u4E00-\u9FA5]/g, '') // 移除中文字符 .replace(/[\u4e00-\u9fa5]/g, ''); // 移除中文字符
} };
if (apiItem.menuType.toLowerCase() === 'catalog') { if (apiItem.menuType.toLowerCase() === 'catalog') {
path = `/${apiItem.menuName.toLowerCase().replace(/\s+/g, '-')}` path = `/${apiItem.menuName.toLowerCase().replace(/\s+/g, '-')}`;
component = 'Layout' component = 'Layout';
name = generateName(apiItem.menuName) name = generateName(apiItem.menuName);
} else { } else {
// 假设route类型菜单都在某个catalog下 // 假设route类型菜单都在某个catalog下
const parentName = apiItem.menuName.toLowerCase().replace(/\s+/g, '-') const parentName = apiItem.menuName.toLowerCase().replace(/\s+/g, '-');
path = `/system/${parentName}` path = `/system/${parentName}`;
component = `system/${parentName}/index` component = `system/${parentName}/index`;
name = `System${generateName(apiItem.menuName)}` name = `System${generateName(apiItem.menuName)}`;
} }
return { return {
@ -88,21 +88,21 @@ const convertMenuItem = (apiItem: ApiMenuItem): FrontendMenuItem => {
parentId: apiItem.parentId, parentId: apiItem.parentId,
title: apiItem.menuName, title: apiItem.menuName,
type: convertMenuType(apiItem.menuType), type: convertMenuType(apiItem.menuType),
path, path: path,
name, name: name,
component, component: component,
icon: 'settings', // 默认图标 icon: 'settings', // 默认图标
isExternal: false, isExternal: false,
isCache: false, isCache: false,
isHidden: convertVisible(apiItem.visible), isHidden: convertVisible(apiItem.visible),
sort: apiItem.orderNum || 0, sort: apiItem.orderNum || 0,
children: apiItem.children ? apiItem.children.map((child) => convertMenuItem(child)) : [], children: apiItem.children ? apiItem.children.map(child => convertMenuItem(child)) : []
} };
} }
/** /**
* API返回的菜单数据为前端需要的格式 * API返回的菜单数据为前端需要的格式
*/ */
export const convertMenuData = (apiMenuData: ApiMenuItem[]): FrontendMenuItem[] => { export const convertMenuData = (apiMenuData: ApiMenuItem[]): FrontendMenuItem[] => {
return apiMenuData.map((item) => convertMenuItem(item)) return apiMenuData.map(item => convertMenuItem(item));
} }

169
src/utils/pdfGenerator.ts Normal file
View File

@ -0,0 +1,169 @@
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
// PDF生成器类
export class PDFGenerator {
// 生成制度PDF
static async generateRegulationPDF(regulation: any): Promise<Blob> {
// 创建临时HTML元素
const tempDiv = document.createElement('div')
tempDiv.style.position = 'absolute'
tempDiv.style.left = '-9999px'
tempDiv.style.top = '-9999px'
tempDiv.style.width = '800px'
tempDiv.style.padding = '40px'
tempDiv.style.backgroundColor = 'white'
tempDiv.style.fontFamily = 'Arial, sans-serif'
tempDiv.style.fontSize = '14px'
tempDiv.style.lineHeight = '1.6'
tempDiv.style.color = '#333'
// 添加水印样式
tempDiv.style.position = 'relative'
tempDiv.style.overflow = 'hidden'
// 生成HTML内容
tempDiv.innerHTML = `
<div style="position: relative; z-index: 1;">
<!-- -->
<div style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 48px;
color: rgba(200, 200, 200, 0.3);
font-weight: bold;
z-index: 0;
pointer-events: none;
white-space: nowrap;
"></div>
<!-- -->
<h1 style="
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
">${regulation.title || '制度文档'}</h1>
<!-- -->
<div style="margin-bottom: 30px;">
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.regulationType || '-'}</td>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.createByName || regulation.createBy || '-'}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.publishTime || '-'}</td>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.effectiveTime || '-'}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.scope || '-'}</td>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.level || '-'}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><strong></strong>${regulation.version || '1.0'}</td>
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"></td>
</tr>
</table>
</div>
<!-- 线 -->
<hr style="border: none; border-top: 2px solid #ddd; margin: 30px 0;">
<!-- -->
<div style="margin-bottom: 30px;">
<h2 style="font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333;"></h2>
<div style="
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.8;
text-align: justify;
">${regulation.content || ''}</div>
</div>
${regulation.remark ? `
<!-- -->
<div style="margin-bottom: 30px;">
<h2 style="font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333;"></h2>
<div style="
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.8;
text-align: justify;
">${regulation.remark}</div>
</div>
` : ''}
<!-- -->
<div style="
margin-top: 50px;
padding-top: 20px;
border-top: 1px solid #ddd;
text-align: center;
font-size: 12px;
color: #666;
">
${new Date().toLocaleString('zh-CN')} | ID${regulation.regulationId || '-'}
</div>
</div>
`
// 添加到DOM
document.body.appendChild(tempDiv)
try {
// 使用html2canvas转换为图片
const canvas = await html2canvas(tempDiv, {
scale: 2, // 提高清晰度
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
width: 800,
height: tempDiv.scrollHeight
})
// 创建PDF
const imgData = canvas.toDataURL('image/png')
const pdf = new jsPDF('p', 'mm', 'a4')
const imgWidth = 210 // A4宽度
const pageHeight = 295 // A4高度
const imgHeight = (canvas.height * imgWidth) / canvas.width
let heightLeft = imgHeight
let position = 0
// 添加第一页
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)
heightLeft -= pageHeight
// 添加后续页面
while (heightLeft >= 0) {
position = heightLeft - imgHeight
pdf.addPage()
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)
heightLeft -= pageHeight
}
// 返回PDF blob
return pdf.output('blob')
} finally {
// 清理临时元素
document.body.removeChild(tempDiv)
}
}
// 下载PDF文件
static downloadPDF(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<template> <template>
<GiPageLayout> <GiPageLayout>
<GiTable <GiTable
v-model:selected-keys="selectedKeys" v-model:selectedKeys="selectedKeys"
row-key="tableName" row-key="tableName"
:data="dataList" :data="dataList"
:columns="columns" :columns="columns"

View File

@ -15,7 +15,7 @@
</a-descriptions> </a-descriptions>
</a-card> </a-card>
</a-col> </a-col>
<!-- 经营状况 --> <!-- 经营状况 -->
<a-col :span="8"> <a-col :span="8">
<a-card title="经营状况" size="small"> <a-card title="经营状况" size="small">
@ -28,7 +28,7 @@
</a-descriptions> </a-descriptions>
</a-card> </a-card>
</a-col> </a-col>
<!-- 发展目标 --> <!-- 发展目标 -->
<a-col :span="8"> <a-col :span="8">
<a-card title="发展目标" size="small"> <a-card title="发展目标" size="small">
@ -73,4 +73,4 @@
.company-overview { .company-overview {
padding: 16px; padding: 16px;
} }
</style> </style>

View File

@ -37,11 +37,11 @@
<a-form-item> <a-form-item>
<a-space> <a-space>
<a-button type="primary" @click="handleSearch"> <a-button type="primary" @click="handleSearch">
<template #icon><IconSearch /></template> <template #icon><icon-search /></template>
搜索 搜索
</a-button> </a-button>
<a-button @click="resetSearch"> <a-button @click="resetSearch">
<template #icon><IconRefresh /></template> <template #icon><icon-refresh /></template>
重置 重置
</a-button> </a-button>
</a-space> </a-space>
@ -55,17 +55,17 @@
<template #extra> <template #extra>
<a-space> <a-space>
<a-button type="primary" size="small" :disabled="selectedRowKeys.length === 0"> <a-button type="primary" size="small" :disabled="selectedRowKeys.length === 0">
<template #icon><IconDownload /></template> <template #icon><icon-download /></template>
批量下载 批量下载
</a-button> </a-button>
<a-button <a-button
type="primary" type="primary"
status="danger" status="danger"
size="small" size="small"
:disabled="selectedRowKeys.length === 0" :disabled="selectedRowKeys.length === 0"
@click="handleBatchDelete" @click="handleBatchDelete"
> >
<template #icon><IconDelete /></template> <template #icon><icon-delete /></template>
批量删除 批量删除
</a-button> </a-button>
</a-space> </a-space>
@ -75,14 +75,14 @@
:loading="loading" :loading="loading"
:data="tableData" :data="tableData"
:pagination="pagination" :pagination="pagination"
@page-change="onPageChange"
row-key="id" row-key="id"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
showCheckedAll: true, showCheckedAll: true,
selectedRowKeys, selectedRowKeys: selectedRowKeys,
onChange: onSelectionChange, onChange: onSelectionChange
}" }"
@page-change="onPageChange"
> >
<template #columns> <template #columns>
<a-table-column title="文件名" data-index="fileName"> <a-table-column title="文件名" data-index="fileName">
@ -116,11 +116,11 @@
<template #cell="{ record }"> <template #cell="{ record }">
<a-space> <a-space>
<a-button size="small" @click="previewFile(record)"> <a-button size="small" @click="previewFile(record)">
<template #icon><IconEye /></template> <template #icon><icon-eye /></template>
预览 预览
</a-button> </a-button>
<a-button size="small" @click="downloadFile(record)"> <a-button size="small" @click="downloadFile(record)">
<template #icon><IconDownload /></template> <template #icon><icon-download /></template>
下载 下载
</a-button> </a-button>
<a-popconfirm <a-popconfirm
@ -128,7 +128,7 @@
@ok="deleteFile(record)" @ok="deleteFile(record)"
> >
<a-button size="small" status="danger"> <a-button size="small" status="danger">
<template #icon><IconDelete /></template> <template #icon><icon-delete /></template>
删除 删除
</a-button> </a-button>
</a-popconfirm> </a-popconfirm>
@ -160,18 +160,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue' import { Message, Modal } from '@arco-design/web-vue'
import {
IconDelete,
IconDownload,
IconEye,
IconFile,
IconRefresh,
IconSearch,
} from '@arco-design/web-vue/es/icon'
import FilePreview from '@/components/FilePreview/index.vue' import FilePreview from '@/components/FilePreview/index.vue'
import { deleteAttachment, getAttachBusinessTypes, getAttachmentList } from '@/apis/attach-info' import {
IconSearch,
IconRefresh,
IconDownload,
IconDelete,
IconEye,
IconFile
} from '@arco-design/web-vue/es/icon'
import { getAttachBusinessTypes, getAttachmentList, deleteAttachment } from '@/apis/attach-info'
import type { AttachInfoData, BusinessType } from '@/apis/attach-info/type' import type { AttachInfoData, BusinessType } from '@/apis/attach-info/type'
defineOptions({ name: 'AttachmentManagement' }) defineOptions({ name: 'AttachmentManagement' })
@ -222,7 +222,7 @@ const fetchAttachmentList = async () => {
Message.warning('请先选择业务类型') Message.warning('请先选择业务类型')
return return
} }
loading.value = true loading.value = true
try { try {
const res = await getAttachmentList(filterForm.businessType) const res = await getAttachmentList(filterForm.businessType)
@ -293,7 +293,7 @@ const deleteFile = async (file: AttachInfoData) => {
if (res) { if (res) {
Message.success(`已删除: ${file.fileName}`) Message.success(`已删除: ${file.fileName}`)
// //
tableData.value = tableData.value.filter((item) => item.id !== file.id) tableData.value = tableData.value.filter(item => item.id !== file.id)
pagination.total = tableData.value.length pagination.total = tableData.value.length
} else { } else {
Message.error('删除文件失败') Message.error('删除文件失败')
@ -308,7 +308,7 @@ const handleBatchDelete = () => {
if (selectedRowKeys.value.length === 0) { if (selectedRowKeys.value.length === 0) {
return return
} }
Modal.confirm({ Modal.confirm({
title: '确认删除', title: '确认删除',
content: `确定要删除选中的 ${selectedRowKeys.value.length} 个文件吗?`, content: `确定要删除选中的 ${selectedRowKeys.value.length} 个文件吗?`,
@ -316,7 +316,7 @@ const handleBatchDelete = () => {
cancelText: '取消', cancelText: '取消',
onOk: async () => { onOk: async () => {
try { try {
const promises = selectedRowKeys.value.map((id) => deleteAttachment(id)) const promises = selectedRowKeys.value.map(id => deleteAttachment(id))
await Promise.all(promises) await Promise.all(promises)
Message.success('批量删除成功') Message.success('批量删除成功')
fetchAttachmentList() fetchAttachmentList()
@ -325,7 +325,7 @@ const handleBatchDelete = () => {
console.error('批量删除失败:', error) console.error('批量删除失败:', error)
Message.error('批量删除失败') Message.error('批量删除失败')
} }
}, }
}) })
} }
@ -334,13 +334,13 @@ const getFileTypeText = (type: string) => {
image: '图片', image: '图片',
document: '文档', document: '文档',
video: '视频', video: '视频',
other: '其他', other: '其他'
} }
return typeMap[type] || '未知' return typeMap[type] || '未知'
} }
const getBusinessTypeName = (code: string) => { const getBusinessTypeName = (code: string) => {
const businessType = businessTypes.value.find((item) => item.code === code) const businessType = businessTypes.value.find(item => item.code === code)
return businessType ? businessType.name : code return businessType ? businessType.name : code
} }
@ -349,12 +349,12 @@ const formatFileSize = (size: number) => {
const units = ['B', 'KB', 'MB', 'GB', 'TB'] const units = ['B', 'KB', 'MB', 'GB', 'TB']
let index = 0 let index = 0
let tempSize = size let tempSize = size
while (tempSize >= 1024 && index < units.length - 1) { while (tempSize >= 1024 && index < units.length - 1) {
tempSize /= 1024 tempSize /= 1024
index++ index++
} }
return `${tempSize.toFixed(2)} ${units[index]}` return `${tempSize.toFixed(2)} ${units[index]}`
} }
@ -377,4 +377,4 @@ const getFileIcon = (fileType: string) => {
margin-bottom: 16px; margin-bottom: 16px;
} }
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="attachment-upload"> <div class="attachment-upload">
<a-space direction="vertical" :size="16" style="width: 100%"> <a-space direction="vertical" :size="16" style="width: 100%">
<a-form ref="formRef" :model="formData"> <a-form :model="formData" ref="formRef">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item field="businessType" label="业务类型" required> <a-form-item field="businessType" label="业务类型" required>
@ -64,7 +64,7 @@
</a-form-item> </a-form-item>
<div class="form-actions"> <div class="form-actions">
<a-button type="primary" :loading="submitting" @click="handleSubmit">提交</a-button> <a-button type="primary" @click="handleSubmit" :loading="submitting">提交</a-button>
<a-button style="margin-left: 10px" @click="resetForm">重置</a-button> <a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
</div> </div>
</a-form> </a-form>
@ -73,10 +73,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { IconPlus } from '@arco-design/web-vue/es/icon' import { IconPlus } from '@arco-design/web-vue/es/icon'
import { batchAddAttachment, getAttachBusinessTypes } from '@/apis/attach-info' import { getAttachBusinessTypes, batchAddAttachment } from '@/apis/attach-info'
import type { BusinessType } from '@/apis/attach-info/type' import type { BusinessType } from '@/apis/attach-info/type'
defineOptions({ name: 'AttachmentUpload' }) defineOptions({ name: 'AttachmentUpload' })
@ -98,15 +98,23 @@ const formData = reactive({
fileType: '', fileType: '',
remark: '', remark: '',
userDefinedPath: '', userDefinedPath: '',
files: [] as FileItem[], files: [] as FileItem[]
}) })
// //
const fetchBusinessTypes = async () => { const fetchBusinessTypes = async () => {
try { try {
const res = await getAttachBusinessTypes() const res = await getAttachBusinessTypes()
console.log("res:",res);
if (res.data) { if (res.data) {
businessTypes.value = res.data res.data.forEach(item => {
const key = Object.keys(item)[0];
const value = item[key];
businessTypes.value.push({
name: value,
code:key
});
});
} }
} catch (error) { } catch (error) {
console.error('获取业务类型失败:', error) console.error('获取业务类型失败:', error)
@ -120,23 +128,23 @@ onMounted(() => {
const customRequest = (options: any) => { const customRequest = (options: any) => {
const { file, onProgress, onSuccess, onError } = options const { file, onProgress, onSuccess, onError } = options
// //
const fileItem = { const fileItem = {
file, file,
status: 'ready', status: 'ready',
uid: options.fileItem.uid, uid: options.fileItem.uid,
name: options.fileItem.name, name: options.fileItem.name
} }
formData.files.push(fileItem) formData.files.push(fileItem)
// //
onProgress(0) onProgress(0)
let percent = 0 let percent = 0
const timer = setInterval(() => { const timer = setInterval(() => {
percent += 10 percent += 10
onProgress(percent > 100 ? 100 : percent) onProgress(percent > 100 ? 100 : percent)
if (percent >= 100) { if (percent >= 100) {
clearInterval(timer) clearInterval(timer)
onSuccess() onSuccess()
@ -151,32 +159,32 @@ const handleChange = (fileList) => {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await formRef.value.validate() await formRef.value.validate()
if (!formData.businessType) { if (!formData.businessType) {
Message.error('请选择业务类型') Message.error('请选择业务类型')
return return
} }
if (formData.files.length === 0) { if (formData.files.length === 0) {
Message.error('请选择要上传的文件') Message.error('请选择要上传的文件')
return return
} }
submitting.value = true submitting.value = true
const formDataToSend = new FormData() const formDataToSend = new FormData()
formData.files.forEach((item, index) => { formData.files.forEach((item, index) => {
formDataToSend.append(`files[${index}]`, item.file) formDataToSend.append(`files[${index}]`, item.file)
}) })
const params = { const params = {
fileType: formData.fileType, fileType: formData.fileType,
remark: formData.remark, remark: formData.remark,
userDefinedPath: formData.userDefinedPath, userDefinedPath: formData.userDefinedPath
} }
const res = await batchAddAttachment(formData.businessType, formDataToSend, params) const res = await batchAddAttachment(formData.businessType, formDataToSend, params)
if (res) { if (res) {
Message.success('文件上传成功') Message.success('文件上传成功')
resetForm() resetForm()
@ -185,7 +193,7 @@ const handleSubmit = async () => {
} }
} catch (error: any) { } catch (error: any) {
console.error('上传失败:', error) console.error('上传失败:', error)
Message.error(`上传失败: ${error.msg || '未知错误'}`) Message.error('上传失败: ' + (error.msg || '未知错误'))
} finally { } finally {
submitting.value = false submitting.value = false
} }
@ -209,16 +217,16 @@ const resetForm = () => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&-box { &-box {
text-align: center; text-align: center;
} }
&-text { &-text {
color: rgb(var(--primary-6)); color: rgb(var(--primary-6));
font-size: 14px; font-size: 14px;
} }
&-tip { &-tip {
margin-top: 10px; margin-top: 10px;
color: rgb(var(--gray-6)); color: rgb(var(--gray-6));
@ -231,4 +239,4 @@ const resetForm = () => {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@ -24,4 +24,4 @@ defineOptions({ name: 'DataStorage' })
.data-storage-container { .data-storage-container {
padding: 20px; padding: 20px;
} }
</style> </style>

View File

@ -0,0 +1,603 @@
<template>
<GiPageLayout>
<div class="data-preprocessing-container">
<!-- 上传进度框 -->
<div v-if="uploadState.visible" class="upload-progress-fixed">
<a-card :title="`上传进度 (${uploadState.percent}%)`" :bordered="false">
<a-progress
:percent="uploadState.percent"
:status="uploadState.status"
:stroke-width="16"
/>
<div class="progress-details">
<p><icon-file /> {{ uploadState.currentFile || '准备中...' }}</p>
<p><icon-check-circle /> 已完成 {{ uploadState.uploadedCount }}/{{ uploadState.totalCount }}</p>
<p><icon-clock-circle /> 状态: {{ getStatusText(uploadState.status) }}</p>
</div>
</a-card>
</div>
<div class="step-panel">
<div class="step-header">
<h3>批量上传图片</h3>
</div>
<div class="data-selection">
<a-form :model="form" layout="vertical">
<!-- 项目选择 -->
<a-form-item label="所属项目" required>
<a-select
v-model="form.projectId"
placeholder="请选择项目"
allow-search
:filter-option="filterProjectOption"
>
<a-option
v-for="project in projectList"
:key="project.id"
:value="project.id"
:label="project.name"
/>
</a-select>
</a-form-item>
<!-- 图片来源选择 -->
<a-form-item label="图片来源" required>
<a-select
v-model="form.imageSource"
placeholder="请选择图片来源"
allow-search
:filter-option="filterSourceOption"
>
<a-option
v-for="source in imageSources"
:key="source.value"
:value="source.value"
:label="source.label"
/>
</a-select>
</a-form-item>
<!-- 文件夹操作 -->
<a-form-item label="文件操作">
<div class="folder-actions">
<a-upload
ref="uploadRef"
directory
:multiple="true"
:show-file-list="false"
accept="image/*"
:key="uploadKey"
@change="handleFolderSelect"
>
<template #upload-button>
<a-button type="outline">
<template #icon>
<icon-folder />
</template>
选择文件夹
</a-button>
</template>
</a-upload>
<a-button
type="outline"
status="warning"
@click="clearFileList"
:disabled="selectedFiles.length === 0"
>
<template #icon>
<icon-delete />
</template>
清空列表
</a-button>
<a-button
type="primary"
:loading="uploading"
:disabled="!canUpload"
@click="handleUpload"
>
<template #icon>
<icon-upload />
</template>
开始上传
</a-button>
</div>
</a-form-item>
<!-- 文件列表 -->
<a-form-item v-if="selectedFiles.length > 0">
<div class="file-list-container">
<div class="file-list-header">
<span>已选择 {{ selectedFiles.length }} 个文件选中 {{ checkedFiles.length }} </span>
<a-checkbox
v-model="selectAll"
:indeterminate="indeterminate"
@change="handleSelectAllChange"
>
全选
</a-checkbox>
</div>
<div class="file-table-wrapper">
<a-table
:data="selectedFiles"
:columns="fileColumns"
:row-selection="rowSelection"
:pagination="false"
row-key="uid"
size="small"
bordered
>
<!-- 表格列模板 -->
<template #thumbnail="{ record }">
<div class="thumbnail-cell">
<img
v-if="isImage(record.type)"
:src="record.preview"
class="thumbnail-image"
alt="预览"
/>
<div v-else class="file-icon">
<icon-file />
</div>
</div>
</template>
<template #fileType="{ record }">
<a-tag :color="getFileTypeColor(record.type)" size="small">
{{ record.type }}
</a-tag>
</template>
<template #fileSize="{ record }">
{{ formatFileSize(record.size) }}
</template>
</a-table>
</div>
</div>
</a-form-item>
</a-form>
</div>
</div>
</div>
</GiPageLayout>
</template>
<script setup lang="ts">
import { ref, reactive, computed, nextTick } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { UploadItem } from '@arco-design/web-vue/es/upload'
import type { SelectOptionData, TableColumnData, TableRowSelection } from '@arco-design/web-vue'
import axios from 'axios'
import {
getProjectList,
getImageSources
} from '@/apis/industrial-image'
//
interface FileItem {
uid: string
name: string
type: string
size: number
file: File
preview?: string
}
type UploadStatus = 'waiting' | 'uploading' | 'success' | 'error'
//
const projectList = ref([])
const imageSources = ref([])
//
const fetchProjectList = async () => {
try {
const res = await getProjectList({ page: 1, pageSize: 1000 });
projectList.value = res.data.map(item => ({
name: item.projectName,
id: item.projectId
}))
} catch (error) {
Message.error('获取项目列表失败')
} finally {
}
}
//
const fetchImageSourceList = async () => {
try {
const res = await getImageSources();
res.data.forEach(item => {
const key = Object.keys(item)[0];
const value = item[key];
imageSources.value.push({
label: value,
value:key
});
});
} catch (error) {
Message.error('获取项目列表失败')
} finally {
}
}
const form = reactive({
projectId: undefined as number | undefined,
imageSource: undefined as string | undefined
})
const selectedFiles = ref<FileItem[]>([])
const checkedFiles = ref<string[]>([])
const uploading = ref(false)
const uploadRef = ref()
const uploadKey = ref(0)
const uploadState = reactive({
visible: false,
percent: 0,
status: 'waiting' as UploadStatus,
currentFile: '',
uploadedCount: 0,
totalCount: 0
})
//
const canUpload = computed(() => {
return checkedFiles.value.length > 0
&& !!form.projectId
&& !!form.imageSource
&& !uploading.value
})
const selectAll = ref(false)
const indeterminate = computed(() => {
return checkedFiles.value.length > 0 &&
checkedFiles.value.length < selectedFiles.value.length
})
//
const fileColumns: TableColumnData[] = [
{
title: '选择',
dataIndex: 'selection',
type: 'selection',
width: 60,
align: 'center'
},
{
title: '预览',
dataIndex: 'thumbnail',
slotName: 'thumbnail',
width: 100,
align: 'center'
},
{
title: '文件名',
dataIndex: 'name',
ellipsis: true,
tooltip: true,
width: 300
},
{
title: '类型',
dataIndex: 'type',
slotName: 'fileType',
width: 100,
align: 'center'
},
{
title: '大小',
dataIndex: 'size',
slotName: 'fileSize',
width: 100,
align: 'center'
}
]
//
const rowSelection = reactive<TableRowSelection>({
type: 'checkbox',
showCheckedAll: false,
selectedRowKeys: checkedFiles,
onChange: (rowKeys: string[]) => {
checkedFiles.value = rowKeys
selectAll.value = rowKeys.length === selectedFiles.value.length
}
})
//
const filterProjectOption = (inputValue: string, option: SelectOptionData) => {
return option.label.toLowerCase().includes(inputValue.toLowerCase())
}
const filterSourceOption = (inputValue: string, option: SelectOptionData) => {
return option.label.toLowerCase().includes(inputValue.toLowerCase())
}
//
const handleFolderSelect = async (fileList: UploadItem[]) => {
// 1.
clearFileList()
// 2.
const newFiles: FileItem[] = []
for (const item of fileList) {
const file = item.file
if (!file) continue
const fileType = file.type.split('/')[0] || 'unknown'
const preview = fileType === 'image' ? URL.createObjectURL(file) : undefined
newFiles.push({
uid: item.uid,
name: file.name,
type: fileType,
size: file.size,
file: file,
preview: preview
})
}
// 3.
selectedFiles.value = newFiles
checkedFiles.value = newFiles.map(f => f.uid)
selectAll.value = true
// 4. key
uploadKey.value++
}
//
const clearFileList = () => {
// URL
selectedFiles.value.forEach(file => {
if (file.preview) URL.revokeObjectURL(file.preview)
})
//
selectedFiles.value = []
checkedFiles.value = []
selectAll.value = false
}
//
const handleSelectAllChange = (checked: boolean) => {
checkedFiles.value = checked ? selectedFiles.value.map(f => f.uid) : []
}
//
const handleUpload = async () => {
if (!canUpload.value) {
Message.error('请完成所有必填项并选择文件')
return
}
//
Object.assign(uploadState, {
visible: true,
percent: 0,
status: 'uploading',
currentFile: '',
uploadedCount: 0,
totalCount: checkedFiles.value.length
})
uploading.value = true
try {
const filesToUpload = selectedFiles.value.filter(f => checkedFiles.value.includes(f.uid))
const formData = new FormData()
// FormData
filesToUpload.forEach(file => {
formData.append('files', file);
});
let url =`http://pms.dtyx.net:9158/image/${form.projectId}/${form.imageSource}/upload-batch`;
let res = await axios.post(
url,
formData,
{
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
uploadedBytes = progressEvent.loaded
const elapsedTime = (Date.now() - startTime) / 1000
const speed = uploadedBytes / elapsedTime
uploadState.speed = `${formatFileSize(speed)}/s`
const remainingBytes = totalBytes - uploadedBytes
uploadState.remainingTime = formatTime(remainingBytes / speed)
uploadState.percent = Math.round((uploadedBytes / totalBytes) * 100)
}
},
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
console.log("res:",res);
uploadState.status = 'success'
Message.success(`成功上传 ${filesToUpload.length} 个文件`)
} catch (error) {
uploadState.status = 'error'
Message.error('上传失败: ' + (error as Error).message)
} finally {
uploading.value = false
// 5
setTimeout(() => uploadState.visible = false, 5000)
}
}
//
const getStatusText = (status: UploadStatus) => {
const statusMap = {
waiting: '等待上传',
uploading: '上传中',
success: '上传成功',
error: '上传失败'
}
return statusMap[status] || status
}
const getFileTypeColor = (type: string) => {
const colors: Record<string, string> = {
image: 'arcoblue',
video: 'green',
audio: 'orange',
document: 'purple'
}
return colors[type.toLowerCase()] || 'gray'
}
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
const isImage = (type: string) => type === 'image'
//
onMounted(() => {
fetchProjectList()
fetchImageSourceList()
})
</script>
<style scoped lang="less">
.data-preprocessing-container {
position: relative;
padding: 20px;
min-height: calc(100vh - 40px);
}
.step-panel {
background: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.step-header {
margin-bottom: 24px;
border-bottom: 1px solid var(--color-border);
padding-bottom: 16px;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--color-text-1);
}
}
.folder-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
> * {
flex-shrink: 0;
}
}
.file-list-container {
border: 1px solid var(--color-border);
border-radius: 4px;
padding: 12px;
margin-top: 16px;
}
.file-list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding: 0 8px;
}
.file-table-wrapper {
max-height: 500px;
overflow-y: auto;
border: 1px solid var(--color-border);
border-radius: 4px;
}
.file-table {
width: 100%;
:deep(.arco-table-th) {
position: sticky;
top: 0;
z-index: 1;
background-color: var(--color-fill-2);
}
}
.thumbnail-cell {
display: flex;
justify-content: center;
align-items: center;
height: 60px;
.thumbnail-image {
max-height: 60px;
max-width: 80px;
object-fit: contain;
border-radius: 2px;
}
.file-icon {
font-size: 24px;
color: var(--color-text-3);
}
}
/* 上传进度框样式 */
.upload-progress-fixed {
position: fixed;
bottom: 24px;
right: 24px;
width: 360px;
z-index: 1000;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
border-radius: 8px;
animation: fadeIn 0.3s ease;
:deep(.arco-card-header) {
border-bottom: 1px solid var(--color-border);
padding-bottom: 12px;
}
:deep(.arco-progress-text) {
font-size: 14px;
}
}
.progress-details {
margin-top: 12px;
font-size: 13px;
color: var(--color-text-2);
p {
display: flex;
align-items: center;
margin: 6px 0;
.arco-icon {
margin-right: 8px;
font-size: 16px;
}
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>

View File

@ -3,7 +3,7 @@
<div class="panel-header"> <div class="panel-header">
<h3>自动识别设置</h3> <h3>自动识别设置</h3>
<a-button type="text" @click="$emit('close')"> <a-button type="text" @click="$emit('close')">
<template #icon><IconClose /></template> <template #icon><icon-close /></template>
</a-button> </a-button>
</div> </div>
@ -30,10 +30,10 @@
<div class="section-header"> <div class="section-header">
<h4>置信度</h4> <h4>置信度</h4>
</div> </div>
<a-slider <a-slider
v-model:model-value="confidence" v-model:model-value="confidence"
:min="0" :min="0"
:max="100" :max="100"
:step="10" :step="10"
show-tooltip show-tooltip
:format-tooltip="(value) => `${value}%`" :format-tooltip="(value) => `${value}%`"
@ -50,8 +50,8 @@
<span>加载缺陷类型...</span> <span>加载缺陷类型...</span>
</div> </div>
<a-checkbox-group v-else v-model:model-value="selectedDefectTypes"> <a-checkbox-group v-else v-model:model-value="selectedDefectTypes">
<div <div
v-for="defectType in defectTypes" v-for="defectType in defectTypes"
:key="defectType.value" :key="defectType.value"
class="defect-item" class="defect-item"
> >
@ -64,10 +64,10 @@
</div> </div>
<div class="panel-actions"> <div class="panel-actions">
<a-button type="primary" :loading="isRecognizing" block @click="handleStartRecognition"> <a-button type="primary" @click="handleStartRecognition" :loading="isRecognizing" block>
开始识别 开始识别
</a-button> </a-button>
<a-button block @click="handleResetSettings"> <a-button @click="handleResetSettings" block>
重置设置 重置设置
</a-button> </a-button>
</div> </div>
@ -76,14 +76,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { ref, computed, watch, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { IconClose } from '@arco-design/web-vue/es/icon' import { IconClose } from '@arco-design/web-vue/es/icon'
import { useRouter } from 'vue-router'
import { listDefectType } from '@/apis/common/common' import { listDefectType } from '@/apis/common/common'
import { getModelConfigList } from '@/apis/model-config' import { getModelConfigList } from '@/apis/model-config'
import type { DefectTypeOption, DefectTypeResp } from '@/apis/common/type' import type { DefectTypeResp, DefectTypeOption } from '@/apis/common/type'
import type { ModelConfigResponse } from '@/apis/model-config/type' import type { ModelConfigResponse } from '@/apis/model-config/type'
import { useRouter } from 'vue-router'
const props = defineProps<{ const props = defineProps<{
currentImage?: { currentImage?: {
@ -122,16 +122,16 @@ const loadModelList = async () => {
try { try {
loadingModels.value = true loadingModels.value = true
const response = await getModelConfigList() const response = await getModelConfigList()
if (response && response.rows) { if (response && response.rows) {
// //
if (Array.isArray(response.rows)) { if (Array.isArray(response.rows)) {
modelList.value = response.rows modelList.value = response.rows;
} else { } else {
// //
const responseData = response.rows const responseData = response.rows;
modelList.value = [] modelList.value = [];
// API // API
if (responseData && Array.isArray(responseData)) { if (responseData && Array.isArray(responseData)) {
modelList.value = responseData.map((item: any) => ({ modelList.value = responseData.map((item: any) => ({
@ -140,11 +140,11 @@ const loadModelList = async () => {
attachId: item.attachId || '', attachId: item.attachId || '',
confThreshold: item.confThreshold || 0.5, confThreshold: item.confThreshold || 0.5,
nmsThreshold: item.nmsThreshold || 0.5, nmsThreshold: item.nmsThreshold || 0.5,
modelPath: item.modelPath || '', modelPath: item.modelPath || ''
})) }));
} }
} }
// //
if (modelList.value.length > 0) { if (modelList.value.length > 0) {
selectedAlgorithm.value = modelList.value[0].modelId selectedAlgorithm.value = modelList.value[0].modelId
@ -164,10 +164,10 @@ const loadDefectTypes = async () => {
try { try {
loadingDefectTypes.value = true loadingDefectTypes.value = true
const response = await listDefectType() const response = await listDefectType()
// - API // - API
const defectTypeOptions: DefectTypeOption[] = [] const defectTypeOptions: DefectTypeOption[] = []
// //
response.data.forEach((item: DefectTypeResp) => { response.data.forEach((item: DefectTypeResp) => {
// //
@ -175,16 +175,16 @@ const loadDefectTypes = async () => {
defectTypeOptions.push({ defectTypeOptions.push({
value: code, value: code,
label: name, label: name,
code, code: code
}) })
}) })
}) })
defectTypes.value = defectTypeOptions defectTypes.value = defectTypeOptions
// //
if (defectTypes.value.length > 0) { if (defectTypes.value.length > 0) {
selectedDefectTypes.value = defectTypes.value.slice(0, 3).map((item) => item.value) selectedDefectTypes.value = defectTypes.value.slice(0, 3).map(item => item.value)
} }
} catch (error) { } catch (error) {
console.error('获取缺陷类型失败:', error) console.error('获取缺陷类型失败:', error)
@ -216,7 +216,7 @@ const handleStartRecognition = async () => {
const settings = { const settings = {
algorithm: selectedAlgorithm.value, algorithm: selectedAlgorithm.value,
confidence: confidence.value, confidence: confidence.value,
defectTypes: selectedDefectTypes.value, defectTypes: selectedDefectTypes.value
} }
try { try {
@ -235,7 +235,7 @@ const handleStartRecognition = async () => {
const handleResetSettings = () => { const handleResetSettings = () => {
selectedAlgorithm.value = modelList.value.length > 0 ? modelList.value[0].modelId : '' selectedAlgorithm.value = modelList.value.length > 0 ? modelList.value[0].modelId : ''
confidence.value = 80 confidence.value = 80
selectedDefectTypes.value = defectTypes.value.length > 0 ? defectTypes.value.slice(0, 3).map((item) => item.value) : [] selectedDefectTypes.value = defectTypes.value.length > 0 ? defectTypes.value.slice(0, 3).map(item => item.value) : []
} }
// //
@ -258,7 +258,7 @@ const goToModelManagement = () => {
} }
defineExpose({ defineExpose({
setRecognizing, setRecognizing
}) })
</script> </script>
@ -275,7 +275,7 @@ defineExpose({
align-items: center; align-items: center;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
h3 { h3 {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
@ -290,11 +290,11 @@ defineExpose({
flex-direction: column; flex-direction: column;
overflow-y: scroll; overflow-y: scroll;
height: 600px; height: 600px;
.panel-section { .panel-section {
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #f3f4f6; border-bottom: 1px solid #f3f4f6;
.section-header { .section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -351,4 +351,4 @@ defineExpose({
} }
} }
} }
</style> </style>

View File

@ -3,10 +3,10 @@
<div class="form-header"> <div class="form-header">
<h3>缺陷详情</h3> <h3>缺陷详情</h3>
<a-button type="text" @click="$emit('close')"> <a-button type="text" @click="$emit('close')">
<template #icon><IconClose /></template> <template #icon><icon-close /></template>
</a-button> </a-button>
</div> </div>
<div class="form-body"> <div class="form-body">
<a-form :model="form" layout="vertical"> <a-form :model="form" layout="vertical">
<a-form-item label="缺陷名称" field="defectName"> <a-form-item label="缺陷名称" field="defectName">
@ -15,12 +15,12 @@
<!-- 缺陷位置 --> <!-- 缺陷位置 -->
<a-form-item <a-form-item
field="defectPosition" field="defectPosition"
label="缺陷位置" label="缺陷位置"
> >
<a-input v-model="form.defectPosition" placeholder="请输入缺陷位置" /> <a-input v-model="form.defectPosition" placeholder="请输入缺陷位置" />
</a-form-item> </a-form-item>
<!-- 缺陷类型 --> <!-- 缺陷类型 -->
<a-form-item <a-form-item
field="defectType" field="defectType"
@ -36,42 +36,42 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<!-- 轴向尺寸 --> <!--轴向尺寸-->
<div class="dimension-fields"> <div class="dimension-fields">
<a-form-item <a-form-item
field="axialDimension" field="axialDimension"
label="轴向尺寸 (mm)" label="轴向尺寸 (mm)"
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]" :rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
> >
<a-input-number <a-input-number
v-model="form.axialDimension" v-model="form.axialDimension"
:min="0" :min="0"
:step="1" :step="1"
placeholder="请输入轴向尺寸" placeholder="请输入轴向尺寸"
mode="button" mode="button"
size="large" size="large"
> >
</a-input-number> </a-input-number>
</a-form-item> </a-form-item>
<!-- 弦向尺寸 --> <!--弦向尺寸 -->
<a-form-item <a-form-item
field="chordDimension" field="chordDimension"
label="弦向尺寸 (mm)" label="弦向尺寸 (mm)"
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]" :rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
> >
<a-input-number <a-input-number
v-model="form.chordDimension" v-model="form.chordDimension"
:min="0" :min="0"
:step="1" :step="1"
placeholder="请输入弦向尺寸" placeholder="请输入弦向尺寸"
mode="button" mode="button"
size="large" size="large"
> >
</a-input-number> </a-input-number>
</a-form-item> </a-form-item>
</div> </div>
<!-- 缺陷等级 --> <!-- 缺陷等级 -->
<a-form-item <a-form-item
field="defectLevel" field="defectLevel"
@ -87,46 +87,49 @@
<a-option v-for="level in defectLevels" :key="level.code" :value="level.code">{{ level.name }}</a-option> <a-option v-for="level in defectLevels" :key="level.code" :value="level.code">{{ level.name }}</a-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="描述" field="description"> <a-form-item label="描述" field="description">
<a-textarea <a-textarea
v-model="form.description" v-model="form.description"
placeholder="请输入缺陷描述" placeholder="请输入缺陷描述"
:rows="3" :rows="3"
/> />
</a-form-item> </a-form-item>
<a-form-item label="维修建议" field="repairIdea"> <a-form-item label="维修建议" field="repairIdea">
<a-textarea <a-textarea
v-model="form.repairIdea" v-model="form.repairIdea"
placeholder="请输入维修建议" placeholder="请输入维修建议"
:rows="3" :rows="3"
/> />
</a-form-item> </a-form-item>
<div class="form-footer"> <div class="form-footer">
<a-button size="large" @click="$emit('close')">取消</a-button> <a-button @click="$emit('close')" size="large">取消</a-button>
<a-button <a-button
type="primary" type="primary"
size="large" size="large"
:loading="submitting" @click="handleSubmit"
@click="handleSubmit" :loading="submitting"
> >
<template #icon><IconSave /></template> <template #icon><icon-save /></template>
保存缺陷 保存缺陷
</a-button> </a-button>
</div> </div>
</a-form> </a-form>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue' import { ref, reactive, watch, onMounted } from 'vue'
import { IconClose, IconSave } from '@arco-design/web-vue/es/icon' import { IconClose, IconSave } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { getDefectLevels } from '@/apis/industrial-image/defect' import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
import { getDefectLevels, getDefectTypes, type DefectLevelType, type DefectType } from '@/apis/industrial-image/defect'
import { listDefectType } from '@/apis/common/common' import { listDefectType } from '@/apis/common/common'
import type { DefectTypeOption } from '@/apis/common/type' import type { DefectTypeResp, DefectTypeOption } from '@/apis/common/type'
interface DefectFormData { interface DefectFormData {
defectName: string defectName: string
@ -164,15 +167,15 @@ const loadingDefectLevels = ref(false)
// //
const form = reactive<DefectFormData>({ const form = reactive<DefectFormData>({
defectName: '', defectName: '',
defectType: '', defectType: '',
defectTypeLabel: '', defectTypeLabel: '',
defectLevel: '', defectLevel: '',
defectLevelLabel: '', defectLevelLabel: '',
defectPosition: '', defectPosition: '',
description: '', description: '',
repairIdea: '建议进行进一步检查', repairIdea: '建议进行进一步检查',
axialDimension: 0, // null axialDimension: 0, // null
chordDimension: 0, // null chordDimension: 0 // null
}) })
// //
@ -180,10 +183,10 @@ const loadDefectTypes = async () => {
try { try {
loadingDefectTypes.value = true loadingDefectTypes.value = true
const response = await listDefectType() const response = await listDefectType()
// - API // - API
const defectTypeOptions: DefectTypeOption[] = [] const defectTypeOptions: DefectTypeOption[] = []
// //
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
response.data.forEach((item: any) => { response.data.forEach((item: any) => {
@ -194,15 +197,15 @@ const loadDefectTypes = async () => {
defectTypeOptions.push({ defectTypeOptions.push({
value: code, value: code,
label: name as string, label: name as string,
code, code: code
}) })
} }
}) })
} }
defectTypes.value = defectTypeOptions defectTypes.value = defectTypeOptions
console.log('缺陷类型选项:', defectTypeOptions) console.log('缺陷类型选项:', defectTypeOptions)
// //
if (defectTypeOptions.length > 0 && !form.defectType) { if (defectTypeOptions.length > 0 && !form.defectType) {
form.defectType = defectTypeOptions[0].code form.defectType = defectTypeOptions[0].code
@ -217,25 +220,26 @@ const loadDefectTypes = async () => {
} }
} }
// //
const loadDefectLevels = async () => { const loadDefectLevels = async () => {
try { try {
loadingDefectLevels.value = true loadingDefectLevels.value = true
const response = await getDefectLevels() const response = await getDefectLevels()
// //
const defectLevelOptions: DefectTypeOption[] = [] const defectLevelOptions: DefectTypeOption[] = []
// //
if (response.data && response.data.code === 0 && Array.isArray(response.data.data)) { if (response.data && response.data.code === 0 && Array.isArray(response.data.data)) {
// API // API
response.data.data.forEach((item) => { response.data.data.forEach(item => {
defectLevelOptions.push({ defectLevelOptions.push({
code: item.code, code: item.code,
label: item.name, // 使namelabel label: item.name, // 使namelabel
value: item.value, value: item.value,
name: item.name, name: item.name,
sort: item.sort, sort: item.sort
}) })
}) })
defectLevels.value = defectLevelOptions defectLevels.value = defectLevelOptions
@ -246,10 +250,10 @@ const loadDefectLevels = async () => {
if (entries.length > 0) { if (entries.length > 0) {
const [code, name] = entries[0] const [code, name] = entries[0]
defectLevelOptions.push({ defectLevelOptions.push({
code, code: code,
label: name as string, label: name as string,
value: code, value: code,
name: name as string, name: name as string
}) })
} }
}) })
@ -260,17 +264,17 @@ const loadDefectLevels = async () => {
defectLevels.value = [ defectLevels.value = [
{ code: 'low', label: '轻微', value: 'low', name: '轻微' }, { code: 'low', label: '轻微', value: 'low', name: '轻微' },
{ code: 'medium', label: '中等', value: 'medium', name: '中等' }, { code: 'medium', label: '中等', value: 'medium', name: '中等' },
{ code: 'high', label: '严重', value: 'high', name: '严重' }, { code: 'high', label: '严重', value: 'high', name: '严重' }
] ]
} }
console.log('缺陷等级选项:', defectLevels.value) console.log('缺陷等级选项:', defectLevels.value)
// //
if (defectLevels.value.length > 0 && !form.defectLevel) { if (defectLevels.value.length > 0 && !form.defectLevel) {
const mediumLevel = defectLevels.value.find((l) => const mediumLevel = defectLevels.value.find(l =>
l.code.toLowerCase().includes('medium') l.code.toLowerCase().includes('medium') ||
|| (l.name && l.name.includes('中')), (l.name && l.name.includes('中'))
) )
form.defectLevel = mediumLevel?.code || defectLevels.value[0].code form.defectLevel = mediumLevel?.code || defectLevels.value[0].code
form.defectLevelLabel = mediumLevel?.name || mediumLevel?.label || defectLevels.value[0].label form.defectLevelLabel = mediumLevel?.name || mediumLevel?.label || defectLevels.value[0].label
@ -281,7 +285,7 @@ const loadDefectLevels = async () => {
defectLevels.value = [ defectLevels.value = [
{ code: 'low', label: '轻微', value: 'low', name: '轻微' }, { code: 'low', label: '轻微', value: 'low', name: '轻微' },
{ code: 'medium', label: '中等', value: 'medium', name: '中等' }, { code: 'medium', label: '中等', value: 'medium', name: '中等' },
{ code: 'high', label: '严重', value: 'high', name: '严重' }, { code: 'high', label: '严重', value: 'high', name: '严重' }
] ]
} finally { } finally {
loadingDefectLevels.value = false loadingDefectLevels.value = false
@ -295,7 +299,7 @@ watch(() => props.annotation, (newAnnotation) => {
const isVirtualAnnotation = newAnnotation.id.startsWith('virtual-') const isVirtualAnnotation = newAnnotation.id.startsWith('virtual-')
// //
const isMultiAnnotation = newAnnotation.metadata?.isMultiAnnotation const isMultiAnnotation = newAnnotation.metadata?.isMultiAnnotation
if (isVirtualAnnotation) { if (isVirtualAnnotation) {
// //
form.description = '手动添加的缺陷,请填写详细描述' form.description = '手动添加的缺陷,请填写详细描述'
@ -312,7 +316,7 @@ watch(() => props.annotation, (newAnnotation) => {
form.description = `标注区域大小: ${Math.round(width)}x${Math.round(height)} 像素` form.description = `标注区域大小: ${Math.round(width)}x${Math.round(height)} 像素`
} }
} }
// //
if (newAnnotation.label) { if (newAnnotation.label) {
form.defectName = newAnnotation.label form.defectName = newAnnotation.label
@ -325,45 +329,46 @@ const handleSubmit = async () => {
Message.warning('没有标注数据') Message.warning('没有标注数据')
return return
} }
// //
if (!form.defectName.trim()) { if (!form.defectName.trim()) {
Message.warning('请输入缺陷名称') Message.warning('请输入缺陷名称')
return return
} }
if (!form.defectType) { if (!form.defectType) {
Message.warning('请选择缺陷类型') Message.warning('请选择缺陷类型')
return return
} }
if (!form.defectLevel) { if (!form.defectLevel) {
Message.warning('请选择严重程度') Message.warning('请选择严重程度')
return return
} }
try { try {
submitting.value = true submitting.value = true
// //
const isMultiAnnotation = props.annotation.metadata?.isMultiAnnotation const isMultiAnnotation = props.annotation.metadata?.isMultiAnnotation
const annotationCount = props.annotation.metadata?.allAnnotations?.length || 0 const annotationCount = props.annotation.metadata?.allAnnotations?.length || 0
if (isMultiAnnotation && annotationCount > 0) { if (isMultiAnnotation && annotationCount > 0) {
Message.loading({ Message.loading({
content: `正在保存包含${annotationCount}个标注区域的缺陷信息...`, content: `正在保存包含${annotationCount}个标注区域的缺陷信息...`,
duration: 0, duration: 0
}) })
} else { } else {
Message.loading({ Message.loading({
content: '正在保存缺陷信息...', content: '正在保存缺陷信息...',
duration: 0, duration: 0
}) })
} }
// //
console.log('form:', form) console.log("form:",form);
emit('submit', form, props.annotation) emit('submit', form, props.annotation)
} catch (error) { } catch (error) {
console.error('提交缺陷失败:', error) console.error('提交缺陷失败:', error)
Message.error('提交失败,请重试') Message.error('提交失败,请重试')
@ -374,7 +379,7 @@ const handleSubmit = async () => {
// //
const handleDefectTypeChange = (value: string) => { const handleDefectTypeChange = (value: string) => {
const selectedType = defectTypes.value.find((type) => type.code === value) const selectedType = defectTypes.value.find(type => type.code === value)
if (selectedType) { if (selectedType) {
form.defectTypeLabel = selectedType.name form.defectTypeLabel = selectedType.name
} }
@ -382,7 +387,7 @@ const handleDefectTypeChange = (value: string) => {
// //
const handleDefectLevelChange = (value: string) => { const handleDefectLevelChange = (value: string) => {
const selectedLevel = defectLevels.value.find((level) => level.code === value) const selectedLevel = defectLevels.value.find(level => level.code === value)
if (selectedLevel) { if (selectedLevel) {
form.defectLevelLabel = selectedLevel.name form.defectLevelLabel = selectedLevel.name
} }
@ -395,13 +400,13 @@ onMounted(() => {
if (props.annotation.label) { if (props.annotation.label) {
form.defectName = props.annotation.label form.defectName = props.annotation.label
} }
// position // position
if ('position' in props.annotation && typeof props.annotation.position === 'string') { if ('position' in props.annotation && typeof props.annotation.position === 'string') {
form.defectPosition = props.annotation.position form.defectPosition = props.annotation.position
} }
} }
// //
loadDefectLevels() loadDefectLevels()
loadDefectTypes() loadDefectTypes()
@ -424,7 +429,7 @@ onMounted(() => {
align-items: center; align-items: center;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
h3 { h3 {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
@ -446,22 +451,22 @@ onMounted(() => {
justify-content: flex-end; justify-content: flex-end;
gap: 12px; gap: 12px;
background: #f8f9fa; background: #f8f9fa;
.arco-btn { .arco-btn {
min-width: 80px; min-width: 80px;
font-weight: 500; font-weight: 500;
&.arco-btn-primary { &.arco-btn-primary {
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border-color: #1890ff; border-color: #1890ff;
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3); box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3);
&:hover { &:hover {
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%); background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4); box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4);
transform: translateY(-1px); transform: translateY(-1px);
} }
&:active { &:active {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3); box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3);
@ -497,4 +502,4 @@ onMounted(() => {
font-weight: 500; font-weight: 500;
color: #374151; color: #374151;
} }
</style> </style>

View File

@ -76,20 +76,20 @@
<div class="form-group"> <div class="form-group">
<label class="form-label">轴向尺寸</label> <label class="form-label">轴向尺寸</label>
<div class="size-input-group"> <div class="size-input-group">
<a-button <a-button
size="small" size="small"
@click="defectForm.axial = Math.max(0, (defectForm.axial || 0) - 1)" @click="defectForm.axial = Math.max(0, (defectForm.axial || 0) - 1)"
> >
</a-button> </a-button>
<a-input-number <a-input-number
v-model="defectForm.axial" v-model="defectForm.axial"
:min="0" :min="0"
size="small" size="small"
style="width: 70px" style="width: 70px"
/> />
<a-button <a-button
size="small" size="small"
@click="defectForm.axial = (defectForm.axial || 0) + 1" @click="defectForm.axial = (defectForm.axial || 0) + 1"
> >
+ +
@ -100,20 +100,20 @@
<div class="form-group"> <div class="form-group">
<label class="form-label">弦向尺寸</label> <label class="form-label">弦向尺寸</label>
<div class="size-input-group"> <div class="size-input-group">
<a-button <a-button
size="small" size="small"
@click="defectForm.chordwise = Math.max(0, (defectForm.chordwise || 0) - 1)" @click="defectForm.chordwise = Math.max(0, (defectForm.chordwise || 0) - 1)"
> >
</a-button> </a-button>
<a-input-number <a-input-number
v-model="defectForm.chordwise" v-model="defectForm.chordwise"
:min="0" :min="0"
size="small" size="small"
style="width: 70px" style="width: 70px"
/> />
<a-button <a-button
size="small" size="small"
@click="defectForm.chordwise = (defectForm.chordwise || 0) + 1" @click="defectForm.chordwise = (defectForm.chordwise || 0) + 1"
> >
+ +
@ -128,20 +128,20 @@
<div class="form-group"> <div class="form-group">
<label class="form-label">面积(mm²)</label> <label class="form-label">面积(mm²)</label>
<div class="size-input-group"> <div class="size-input-group">
<a-button <a-button
size="small" size="small"
@click="defectForm.area = Math.max(0, (defectForm.area || 0) - 1)" @click="defectForm.area = Math.max(0, (defectForm.area || 0) - 1)"
> >
</a-button> </a-button>
<a-input-number <a-input-number
v-model="defectForm.area" v-model="defectForm.area"
:min="0" :min="0"
size="small" size="small"
style="width: 70px" style="width: 70px"
/> />
<a-button <a-button
size="small" size="small"
@click="defectForm.area = (defectForm.area || 0) + 1" @click="defectForm.area = (defectForm.area || 0) + 1"
> >
+ +
@ -154,16 +154,16 @@
<div class="form-row"> <div class="form-row">
<div class="form-group full-width"> <div class="form-group full-width">
<label class="form-label">描述</label> <label class="form-label">描述</label>
<a-textarea <a-textarea
v-model="defectForm.description" v-model="defectForm.description"
:rows="3" :rows="3"
placeholder="叶片前缘纵向裂纹长度约15mm宽度约2mm" placeholder="叶片前缘纵向裂纹长度约15mm宽度约2mm"
/> />
<div class="action-button-container"> <div class="action-button-container">
<a-button <a-button
size="small" size="small"
class="standard-library-btn"
@click="handleSelectFromStandardDescription" @click="handleSelectFromStandardDescription"
class="standard-library-btn"
> >
从标准描述库选择 从标准描述库选择
</a-button> </a-button>
@ -175,16 +175,16 @@
<div class="form-row"> <div class="form-row">
<div class="form-group full-width"> <div class="form-group full-width">
<label class="form-label">维修建议</label> <label class="form-label">维修建议</label>
<a-textarea <a-textarea
v-model="defectForm.repairIdea" v-model="defectForm.repairIdea"
:rows="3" :rows="3"
placeholder="建议进行表面修复处理,防止裂纹扩散" placeholder="建议进行表面修复处理,防止裂纹扩散"
/> />
<div class="action-button-container"> <div class="action-button-container">
<a-button <a-button
size="small" size="small"
class="standard-library-btn"
@click="handleSelectFromStandardInfo" @click="handleSelectFromStandardInfo"
class="standard-library-btn"
> >
从标准信息库选择 从标准信息库选择
</a-button> </a-button>
@ -228,8 +228,8 @@
<div class="form-row"> <div class="form-row">
<div class="form-group full-width"> <div class="form-group full-width">
<label class="form-label">技术备注</label> <label class="form-label">技术备注</label>
<a-textarea <a-textarea
v-model="defectForm.technicalNotes" v-model="defectForm.technicalNotes"
:rows="4" :rows="4"
placeholder="记录技术细节、处理方案、注意事项等" placeholder="记录技术细节、处理方案、注意事项等"
/> />
@ -239,8 +239,8 @@
<div class="form-row"> <div class="form-row">
<div class="form-group full-width"> <div class="form-group full-width">
<label class="form-label">修复记录</label> <label class="form-label">修复记录</label>
<a-textarea <a-textarea
v-model="defectForm.repairRecord" v-model="defectForm.repairRecord"
:rows="3" :rows="3"
placeholder="记录修复过程、使用材料、处理结果等" placeholder="记录修复过程、使用材料、处理结果等"
/> />
@ -257,7 +257,7 @@
<!-- 无缺陷选中状态 --> <!-- 无缺陷选中状态 -->
<div v-else class="no-defect-selected"> <div v-else class="no-defect-selected">
<IconFile class="empty-icon" /> <icon-file class="empty-icon" />
<p>请从左侧选择缺陷进行编辑</p> <p>请从左侧选择缺陷进行编辑</p>
</div> </div>
@ -272,10 +272,10 @@
<div class="standard-library-content"> <div class="standard-library-content">
<a-list :data="standardDescriptions" :bordered="false"> <a-list :data="standardDescriptions" :bordered="false">
<template #item="{ item }"> <template #item="{ item }">
<a-list-item <a-list-item
:class="{ selected: selectedStandardDescription === item }" :class="{ 'selected': selectedStandardDescription === item }"
class="clickable-item"
@click="selectedStandardDescription = item" @click="selectedStandardDescription = item"
class="clickable-item"
> >
<div class="description-item"> <div class="description-item">
<div class="description-title">{{ item.title }}</div> <div class="description-title">{{ item.title }}</div>
@ -298,10 +298,10 @@
<div class="standard-library-content"> <div class="standard-library-content">
<a-list :data="standardRepairIdeas" :bordered="false"> <a-list :data="standardRepairIdeas" :bordered="false">
<template #item="{ item }"> <template #item="{ item }">
<a-list-item <a-list-item
:class="{ selected: selectedStandardInfo === item }" :class="{ 'selected': selectedStandardInfo === item }"
class="clickable-item"
@click="selectedStandardInfo = item" @click="selectedStandardInfo = item"
class="clickable-item"
> >
<div class="description-item"> <div class="description-item">
<div class="description-title">{{ item.title }}</div> <div class="description-title">{{ item.title }}</div>
@ -316,7 +316,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from 'vue' import { ref, watch, reactive } from 'vue'
import { IconFile } from '@arco-design/web-vue/es/icon' import { IconFile } from '@arco-design/web-vue/es/icon'
import { Message, Modal } from '@arco-design/web-vue' import { Message, Modal } from '@arco-design/web-vue'
import type { DefectDetectionResult } from '@/apis/industrial-image/defect' import type { DefectDetectionResult } from '@/apis/industrial-image/defect'
@ -348,7 +348,7 @@ const defectForm = reactive({
inspector: '', inspector: '',
recheckStatus: '', recheckStatus: '',
technicalNotes: '', technicalNotes: '',
repairRecord: '', repairRecord: ''
}) })
// //
@ -357,20 +357,20 @@ const selectedStandardDescription = ref<any>(null)
const standardDescriptions = ref([ const standardDescriptions = ref([
{ {
title: '前缘裂纹模板', title: '前缘裂纹模板',
content: '叶片前缘纵向裂纹长度约15mm宽度约2mm', content: '叶片前缘纵向裂纹长度约15mm宽度约2mm'
}, },
{ {
title: '表面磨损模板', title: '表面磨损模板',
content: '叶片表面出现明显磨损痕迹,影响空气动力学性能', content: '叶片表面出现明显磨损痕迹,影响空气动力学性能'
}, },
{ {
title: '截蚀损伤模板', title: '截蚀损伤模板',
content: '叶片前缘截蚀损伤表面粗糙度增加深度约1-3mm', content: '叶片前缘截蚀损伤表面粗糙度增加深度约1-3mm'
}, },
{ {
title: '腐蚀斑点模板', title: '腐蚀斑点模板',
content: '叶片表面出现腐蚀斑点直径约5-10mm深度轻微', content: '叶片表面出现腐蚀斑点直径约5-10mm深度轻微'
}, }
]) ])
// //
@ -379,24 +379,24 @@ const selectedStandardInfo = ref<any>(null)
const standardRepairIdeas = ref([ const standardRepairIdeas = ref([
{ {
title: '裂纹修复建议', title: '裂纹修复建议',
content: '建议进行表面修复处理,防止裂纹扩散', content: '建议进行表面修复处理,防止裂纹扩散'
}, },
{ {
title: '磨损处理建议', title: '磨损处理建议',
content: '定期监测磨损程度,必要时进行表面打磨和重新涂层', content: '定期监测磨损程度,必要时进行表面打磨和重新涂层'
}, },
{ {
title: '截蚀修复建议', title: '截蚀修复建议',
content: '建议进行前缘修复,使用专用胶泥填补并重新整形', content: '建议进行前缘修复,使用专用胶泥填补并重新整形'
}, },
{ {
title: '腐蚀处理建议', title: '腐蚀处理建议',
content: '清理腐蚀区域,涂抹防腐涂层,定期检查', content: '清理腐蚀区域,涂抹防腐涂层,定期检查'
}, },
{ {
title: '严重损伤建议', title: '严重损伤建议',
content: '建议立即停机检修,更换受损部件,避免安全隐患', content: '建议立即停机检修,更换受损部件,避免安全隐患'
}, }
]) ])
// //
@ -413,7 +413,7 @@ watch(() => props.selectedDefect, (newDefect) => {
chordwise: newDefect.chordwise || 0, chordwise: newDefect.chordwise || 0,
area: calculateArea(newDefect.axial || 0, newDefect.chordwise || 0), area: calculateArea(newDefect.axial || 0, newDefect.chordwise || 0),
description: newDefect.description || '', description: newDefect.description || '',
repairIdea: newDefect.repairIdea || '', repairIdea: newDefect.repairIdea || ''
}) })
} }
}, { immediate: true }) }, { immediate: true })
@ -437,7 +437,7 @@ const handleSave = () => {
const updatedDefect = { const updatedDefect = {
...props.selectedDefect, ...props.selectedDefect,
...defectForm, ...defectForm
} }
emit('edit-defect', updatedDefect) emit('edit-defect', updatedDefect)
@ -457,7 +457,7 @@ const handleDelete = () => {
onOk: () => { onOk: () => {
emit('delete-defect', props.selectedDefect!.defectId) emit('delete-defect', props.selectedDefect!.defectId)
Message.success('缺陷已删除') Message.success('缺陷已删除')
}, }
}) })
} }
@ -476,7 +476,7 @@ const handleCancel = () => {
chordwise: props.selectedDefect.chordwise || 0, chordwise: props.selectedDefect.chordwise || 0,
area: calculateArea(props.selectedDefect.axial || 0, props.selectedDefect.chordwise || 0), area: calculateArea(props.selectedDefect.axial || 0, props.selectedDefect.chordwise || 0),
description: props.selectedDefect.description || '', description: props.selectedDefect.description || '',
repairIdea: props.selectedDefect.repairIdea || '', repairIdea: props.selectedDefect.repairIdea || ''
}) })
} }
} }
@ -706,4 +706,4 @@ const handleCancelStandardInfo = () => {
border-bottom: none; border-bottom: none;
} }
} }
</style> </style>

View File

@ -3,21 +3,21 @@
<div class="panel-header"> <div class="panel-header">
<h2>缺陷管理</h2> <h2>缺陷管理</h2>
<div class="header-actions"> <div class="header-actions">
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
:disabled="!canAddDefect"
@click="handleAddDefect" @click="handleAddDefect"
:disabled="!canAddDefect"
> >
<template #icon><IconPlus /></template> <template #icon><icon-plus /></template>
新增缺陷 新增缺陷
</a-button> </a-button>
<a-button type="text" @click="$emit('close')"> <a-button type="text" @click="$emit('close')">
<template #icon><IconClose /></template> <template #icon><icon-close /></template>
</a-button> </a-button>
</div> </div>
</div> </div>
<div class="panel-content"> <div class="panel-content">
<!-- 项目树形结构 --> <!-- 项目树形结构 -->
<div class="tree-section"> <div class="tree-section">
@ -34,11 +34,11 @@
<template #title="node"> <template #title="node">
<div class="tree-node"> <div class="tree-node">
<span class="node-icon"> <span class="node-icon">
<IconFolder v-if="node.type === 'project'" /> <icon-folder v-if="node.type === 'project'" />
<IconSettings v-else-if="node.type === 'turbine'" /> <icon-settings v-else-if="node.type === 'turbine'" />
<IconTool v-else-if="node.type === 'part'" /> <icon-tool v-else-if="node.type === 'part'" />
<IconBug v-else-if="node.type === 'defect'" /> <icon-bug v-else-if="node.type === 'defect'" />
<IconApps v-else /> <icon-apps v-else />
</span> </span>
<span class="node-title">{{ node.name }}</span> <span class="node-title">{{ node.name }}</span>
<span v-if="node.imageCount" class="node-count">({{ node.imageCount }})</span> <span v-if="node.imageCount" class="node-count">({{ node.imageCount }})</span>
@ -53,9 +53,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { IconApps, IconBug, IconClose, IconFolder, IconPlus, IconSettings, IconTool } from '@arco-design/web-vue/es/icon' import { IconClose, IconBug, IconFolder, IconPlus, IconSettings, IconTool, IconApps } from '@arco-design/web-vue/es/icon'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { computed } from 'vue' import { ref, computed, watch } from 'vue'
// //
export interface TreeNode { export interface TreeNode {
@ -91,28 +91,28 @@ const props = defineProps({
// //
defectList: { defectList: {
type: Array as PropType<DefectInfo[]>, type: Array as PropType<DefectInfo[]>,
default: () => [], default: () => []
}, },
// //
selectedDefect: { selectedDefect: {
type: Object as PropType<DefectInfo | null>, type: Object as PropType<DefectInfo | null>,
default: null, default: null
}, },
// //
treeData: { treeData: {
type: Array as PropType<TreeNode[]>, type: Array as PropType<TreeNode[]>,
default: () => [], default: () => []
}, },
// //
selectedKeys: { selectedKeys: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => []
}, },
// //
loading: { loading: {
type: Boolean, type: Boolean,
default: false, default: false
}, }
}) })
// //
@ -122,7 +122,7 @@ const emit = defineEmits([
'load-more', 'load-more',
'add-defect', 'add-defect',
'turbine-select', 'turbine-select',
'close', 'close'
]) ])
// //
@ -130,64 +130,64 @@ const enhancedTreeData = computed(() => {
if (!props.treeData || !Array.isArray(props.treeData)) { if (!props.treeData || !Array.isArray(props.treeData)) {
return [] return []
} }
return props.treeData.map((project) => enhanceTreeNode(project)) return props.treeData.map(project => enhanceTreeNode(project))
}) })
// //
const enhanceTreeNode = (node: TreeNode): TreeNode => { const enhanceTreeNode = (node: TreeNode): TreeNode => {
const enhancedNode = { ...node } const enhancedNode = { ...node }
if (node.type === 'turbine') { if (node.type === 'turbine') {
// //
const defectNodes = getDefectNodesForTurbine(node.id) const defectNodes = getDefectNodesForTurbine(node.id)
enhancedNode.children = [ enhancedNode.children = [
...(node.children || []).map((child) => enhanceTreeNode(child)), ...(node.children || []).map(child => enhanceTreeNode(child)),
...defectNodes, ...defectNodes
] ]
} else if (node.children) { } else if (node.children) {
enhancedNode.children = node.children.map((child) => enhanceTreeNode(child)) enhancedNode.children = node.children.map(child => enhanceTreeNode(child))
} }
return enhancedNode return enhancedNode
} }
// //
const getDefectNodesForTurbine = (turbineId: string): TreeNode[] => { const getDefectNodesForTurbine = (turbineId: string): TreeNode[] => {
// ID // ID
const turbineDefects = props.defectList.filter((defect) => const turbineDefects = props.defectList.filter(defect =>
defect.turbineId === turbineId || defect.imageId === turbineId, defect.turbineId === turbineId || defect.imageId === turbineId
) )
// //
return turbineDefects.map((defect) => ({ return turbineDefects.map(defect => ({
id: defect.id, id: defect.id,
name: defect.defectName || '未命名缺陷', name: defect.defectName || '未命名缺陷',
type: 'defect', type: 'defect',
defectLevel: defect.defectLevel, defectLevel: defect.defectLevel,
defectType: defect.defectType, defectType: defect.defectType,
detectionDate: defect.detectionDate, detectionDate: defect.detectionDate,
defectData: defect, // defectData: defect //
})) }))
} }
// //
const handleNodeSelect = (selectedKeys: string[], e: any) => { const handleNodeSelect = (selectedKeys: string[], e: any) => {
const selectedNode = e.node const selectedNode = e.node
// //
if (selectedNode?.type === 'turbine') { if (selectedNode?.type === 'turbine') {
emit('turbine-select', selectedNode.id) emit('turbine-select', selectedNode.id)
} }
// //
if (selectedNode?.type === 'defect') { if (selectedNode?.type === 'defect') {
const defect = selectedNode.defectData || props.defectList.find((d) => d.id === selectedNode.id) const defect = selectedNode.defectData || props.defectList.find(d => d.id === selectedNode.id)
if (defect) { if (defect) {
emit('defect-select', defect) emit('defect-select', defect)
} }
} }
emit('node-select', selectedKeys, e) emit('node-select', selectedKeys, e)
} }
@ -216,34 +216,34 @@ const canAddDefect = computed(() => {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
background-color: #f8f9fa; background-color: #f8f9fa;
.panel-header { .panel-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 16px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
h2 { h2 {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
} }
.header-actions { .header-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
} }
.panel-content { .panel-content {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.tree-section { .tree-section {
flex: 1; flex: 1;
background-color: white; background-color: white;
@ -253,7 +253,7 @@ const canAddDefect = computed(() => {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
h3 { h3 {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
@ -263,7 +263,7 @@ const canAddDefect = computed(() => {
border-bottom: 1px solid #f2f3f5; border-bottom: 1px solid #f2f3f5;
background: white; background: white;
} }
.project-tree { .project-tree {
flex: 1; flex: 1;
padding: 16px; padding: 16px;
@ -271,26 +271,26 @@ const canAddDefect = computed(() => {
overflow-x: hidden; overflow-x: hidden;
height: 0; height: 0;
min-height: 0; min-height: 0;
// //
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background: #f1f1f1; background: #f1f1f1;
border-radius: 3px; border-radius: 3px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #c1c1c1; background: #c1c1c1;
border-radius: 3px; border-radius: 3px;
&:hover { &:hover {
background: #a8a8a8; background: #a8a8a8;
} }
} }
// Firefox // Firefox
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1; scrollbar-color: #c1c1c1 #f1f1f1;
@ -317,7 +317,7 @@ const canAddDefect = computed(() => {
color: #6b7280; color: #6b7280;
flex-shrink: 0; flex-shrink: 0;
} }
.defect-count { .defect-count {
font-size: 12px; font-size: 12px;
color: #ff4d4f; color: #ff4d4f;
@ -328,4 +328,4 @@ const canAddDefect = computed(() => {
} }
} }
} }
</style> </style>

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="header-toolbar"> <div class="header-toolbar">
<div class="toolbar-buttons"> <div class="toolbar-buttons">
<a-button size="large" :disabled="!currentImageId" @click="handleAutoAnnotate"> <a-button size="large" @click="handleAutoAnnotate" :disabled="!currentImageId">
<template #icon><IconRobot /></template> <template #icon><icon-robot /></template>
自动标注 自动标注
</a-button> </a-button>
<a-button size="large" :disabled="!currentImageId" @click="handleManualAnnotate"> <a-button size="large" @click="handleManualAnnotate" :disabled="!currentImageId">
<template #icon><IconEdit /></template> <template #icon><icon-edit /></template>
手动标注 手动标注
</a-button> </a-button>
<a-button size="large" @click="handleGenerateReport"> <a-button size="large" @click="handleGenerateReport">
<template #icon><IconFileImage /></template> <template #icon><icon-file-image /></template>
生成检测报告 生成检测报告
</a-button> </a-button>
</div> </div>
@ -18,10 +18,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import {
IconEdit, IconPlayArrow,
IconFileImage,
IconRobot, IconRobot,
IconEdit,
IconFileImage
} from '@arco-design/web-vue/es/icon' } from '@arco-design/web-vue/es/icon'
defineProps<{ defineProps<{
@ -39,6 +40,8 @@ const handleStart = () => {
emit('start') emit('start')
} }
const handleAutoAnnotate = () => { const handleAutoAnnotate = () => {
emit('autoAnnotate') emit('autoAnnotate')
} }
@ -66,11 +69,11 @@ const handleGenerateReport = () => {
.arco-btn { .arco-btn {
font-weight: 500; font-weight: 500;
border-radius: 6px; border-radius: 6px;
&.arco-btn-primary { &.arco-btn-primary {
background: #3b82f6; background: #3b82f6;
border-color: #3b82f6; border-color: #3b82f6;
&:hover { &:hover {
background: #2563eb; background: #2563eb;
border-color: #2563eb; border-color: #2563eb;
@ -90,4 +93,4 @@ const handleGenerateReport = () => {
} }
} }
} }
</style> </style>

View File

@ -1,12 +1,12 @@
<template> <template>
<a-modal <a-modal
:visible="previewModalVisible" :visible="previewModalVisible"
title="图像详情" title="图像详情"
width="80%" width="80%"
:footer="footerButtons" :footer="footerButtons"
:mask-closable="true" :mask-closable="true"
:confirm-loading="loading" @update:visible="emit('update:previewModalVisible', $event)"
@update:visible="emit('update:previewModalVisible', $event)" :confirm-loading="loading"
> >
<div v-if="previewImage" class="modal-image-viewer"> <div v-if="previewImage" class="modal-image-viewer">
<img :src="getImageUrl(previewImage.imagePath)" :alt="editingData.imageName" class="preview-image" /> <img :src="getImageUrl(previewImage.imagePath)" :alt="editingData.imageName" class="preview-image" />
@ -62,8 +62,8 @@
<a-form-item label="图片类型描述" field="imageTypeLabel"> <a-form-item label="图片类型描述" field="imageTypeLabel">
<a-textarea <a-textarea
v-model="editingData.imageTypeLabel" v-model="editingData.imageTypeLabel"
:auto-size="{ minRows: 2, maxRows: 4 }" :auto-size="{ minRows: 2, maxRows: 4 }"
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -77,7 +77,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch, computed } from 'vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import axios from 'axios' import axios from 'axios'
@ -115,7 +115,7 @@ const editingData = ref<{
imageType?: string imageType?: string
imageTypeLabel?: string imageTypeLabel?: string
partId?: string partId?: string
imageId?: string imageId?:string
}>({ }>({
imagePath: '', imagePath: '',
imageName: '', imageName: '',
@ -127,7 +127,7 @@ const editingData = ref<{
imageType: '', imageType: '',
imageTypeLabel: '', imageTypeLabel: '',
partId: '', partId: '',
imageId: '', imageId:''
}) })
// //
@ -140,7 +140,7 @@ watch(() => props.previewImage, (newVal) => {
const getImageUrl = (imagePath: string): string => { const getImageUrl = (imagePath: string): string => {
if (!imagePath) return '' if (!imagePath) return ''
if (imagePath.startsWith('http')) return imagePath if (imagePath.startsWith('http')) return imagePath
const baseUrl = 'http://localhost:8080' const baseUrl = 'http://pms.dtyx.net:9158'
return `${baseUrl}${imagePath}` return `${baseUrl}${imagePath}`
} }
@ -177,18 +177,17 @@ const handleSave = async () => {
imageId: editingData.value.imageId, imageId: editingData.value.imageId,
imageName: editingData.value.imageName, imageName: editingData.value.imageName,
imageResolution: editingData.value.imageResolution, imageResolution: editingData.value.imageResolution,
}, }
], ],
} }
console.log('requestData:', requestData) console.log("requestData:",requestData);
// //
const response = await axios.put( const response = await axios.post(
`http://localhost:8080/image/setting-info/${editingData.value.partId}`, `http://pms.dtyx.net:9158/image/setting-info/${editingData.value.partId}`,
requestData, requestData
) )
if (response.data && response.data.code === 200 && response.data.success) {
if (response.data && response.data.code === 0) {
Message.success('图片信息保存成功') Message.success('图片信息保存成功')
emit('save', editingData.value) emit('save', editingData.value)
emit('update:previewModalVisible', false) emit('update:previewModalVisible', false)

Some files were not shown because too many files have changed in this diff Show More