# Conflicts:
#	.env.development
#	src/views/project-management/bidding/information-retrieval/index.vue
This commit is contained in:
wuxueyu 2025-08-06 00:05:20 +08:00
commit 48f4020fc8
232 changed files with 14954 additions and 16734 deletions

BIN
.env

Binary file not shown.

22
.env.development Normal file
View File

@ -0,0 +1,22 @@
# 环境变量 (命名必须以 VITE_ 开头)
# 接口前缀
VITE_API_PREFIX = '/dev-api'
# 接口地址
# VITE_API_BASE_URL = 'http://pms.dtyx.net:9158/'
VITE_API_BASE_URL = 'http://localhost:8888/'
# 接口地址 (WebSocket)
VITE_API_WS_URL = 'ws://localhost:8000'
# 地址前缀
VITE_BASE = '/'
# 是否开启开发者工具
VITE_OPEN_DEVTOOLS = false
# 应用配置面板
VITE_APP_SETTING = true
# 客户端ID
VITE_CLIENT_ID = 'ef51c9a3e9046c4f2ea45142c8a8344a'

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>

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

@ -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,126 @@
// @/apis/bussiness/bussiness.js - 根据后端接口重新编写
import http from '@/utils/http';
const { request } = http;
// 获取文件夹列表(分页)
export function getFolderListApi(params) {
return request({
url: '/businessData/folder/list',
method: 'get',
params: {
page: params?.page || 1,
pageSize: params?.pageSize || 10,
folderName: params?.folderName
}
});
}
// 获取文件列表(分页)
export function getFilesApi(params) {
return request({
url: '/businessData/file/list',
method: 'get',
params: {
page: params?.page || 1,
pageSize: params?.pageSize || 10,
folderId: params?.folderId || 0
}
});
}
// 创建文件夹
export function createFolderApi(data) {
return request({
url: '/businessData/folder/creatFolder',
method: 'post',
data: {
name: data.name,
parentId: data.parentId || 0
}
});
}
// 重命名文件夹
export function updateFolderApi(folderId, newName) {
return request({
url: '/businessData/folder/rename',
method: 'put',
params: {
folderId: folderId,
newName: newName
}
});
}
// 删除文件夹
export function deleteFolderApi(folderId) {
return request({
url: '/businessData/folder/delete',
method: 'delete',
params: {
folderId: folderId
}
});
}
// 上传文件
export function uploadFileApi(file, folderId, onUploadProgress, cancelToken) {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/businessData/file/add',
method: 'post',
params: {
folderId: folderId
},
data: formData,
onUploadProgress,
cancelToken,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
// 下载文件
export function downloadFileApi(fileId) {
return request({
url: '/businessData/file/download',
method: 'get',
params: {
fileId: fileId
},
responseType: 'blob'
});
}
// 删除文件
export function deleteFileApi(fileId) {
return request({
url: '/businessData/file/delete',
method: 'delete',
params: {
fileId: fileId
}
});
}
// 预览文件(后端没有提供预览接口,使用下载接口)
export function previewFileApi(fileId) {
return request({
url: '/businessData/file/download',
method: 'get',
params: {
fileId: fileId
},
responseType: 'blob'
});
}
// 重命名文件(后端没有提供重命名接口,需要先删除再上传)
export function updateFileNameApi(fileId, data) {
// 注意后端没有提供文件重命名接口这里返回一个Promise.reject
return Promise.reject(new Error('后端暂不支持文件重命名功能'));
}

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

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

View File

@ -1,207 +0,0 @@
/**
*
*/
export interface EquipmentListReq {
/** 设备名称 */
equipmentName?: string
/** 设备类型 */
equipmentType?: string
/** 设备状态 */
equipmentStatus?: string
/** 设备序列号 */
equipmentSn?: string
/** 资产编号 */
assetCode?: string
/** 品牌 */
brand?: string
/** 位置状态 */
locationStatus?: string
/** 健康状态 */
healthStatus?: string
/** 负责人 */
responsiblePerson?: string
/** 使用状态 */
useStatus?: string
/** 项目ID */
projectId?: string
/** 使用人ID */
userId?: string
/** 当前页码 */
pageNum?: number
/** 每页大小 */
pageSize?: number
/** 排序字段 */
orderBy?: string
/** 排序方向 */
orderDirection?: string
}
/**
*
*/
export interface PageResult<T> {
code: number
msg: string
rows: T[]
total: number
}
/**
*
*/
export interface EquipmentResp {
/** 设备ID */
equipmentId: string
/** 资产编号 */
assetCode?: string
/** 设备名称 */
equipmentName: string
/** 设备类型 */
equipmentType: string
/** 设备类型描述 */
equipmentTypeLabel?: string
/** 设备型号 */
equipmentModel: string
/** 设备SN */
equipmentSn: string
/** 品牌 */
brand?: string
/** 配置规格/参数 */
specification?: string
/** 设备状态 */
equipmentStatus: string
/** 设备状态描述 */
equipmentStatusLabel?: string
/** 使用状态 */
useStatus: string
/** 位置状态 */
locationStatus?: string
/** 位置状态描述 */
locationStatusLabel?: string
/** 设备当前物理位置 */
physicalLocation?: string
/** 负责人 */
responsiblePerson?: string
/** 健康状态 */
healthStatus?: string
/** 健康状态描述 */
healthStatusLabel?: string
/** 采购时间 */
purchaseTime?: string
/** 入库时间 */
inStockTime?: string
/** 启用时间 */
activationTime?: string
/** 预计报废时间 */
expectedScrapTime?: string
/** 实际报废时间 */
actualScrapTime?: string
/** 状态变更时间 */
statusChangeTime?: string
/** 采购订单 */
purchaseOrder?: string
/** 供应商名称 */
supplierName?: string
/** 采购价格 */
purchasePrice?: number
/** 当前净值 */
currentNetValue?: number
/** 折旧方法 */
depreciationMethod?: string
/** 折旧年限 */
depreciationYears?: number
/** 残值 */
salvageValue?: number
/** 保修截止日期 */
warrantyExpireDate?: string
/** 上次维护日期 */
lastMaintenanceDate?: string
/** 下次维护日期 */
nextMaintenanceDate?: string
/** 维护人员 */
maintenancePerson?: string
/** 库存条码 */
inventoryBarcode?: string
/** 资产备注 */
assetRemark?: string
/** 项目ID */
projectId?: string
/** 项目名称 */
projectName?: string
/** 使用人ID */
userId?: string
/** 使用人 */
name?: string
/** 创建时间 */
createTime?: string
/** 更新时间 */
updateTime?: string
}
/**
*
*/
export interface EquipmentReq {
/** 设备名称 */
equipmentName: string
/** 设备型号 */
equipmentModel: string
/** 设备类型 */
equipmentType: string
/** 设备状态 */
equipmentStatus: string
/** 使用状态 */
useStatus: string
/** 设备序列号 */
equipmentSn: string
/** 资产编号 */
assetCode?: string
/** 品牌 */
brand?: string
/** 配置规格/参数 */
specification?: string
/** 位置状态 */
locationStatus?: string
/** 设备当前物理位置 */
physicalLocation?: string
/** 负责人 */
responsiblePerson?: string
/** 健康状态 */
healthStatus?: string
/** 采购时间 */
purchaseTime?: string
/** 入库时间 */
inStockTime?: string
/** 启用时间 */
activationTime?: string
/** 预计报废时间 */
expectedScrapTime?: string
/** 实际报废时间 */
actualScrapTime?: string
/** 采购订单 */
purchaseOrder?: string
/** 供应商名称 */
supplierName?: string
/** 采购价格 */
purchasePrice?: number
/** 当前净值 */
currentNetValue?: number
/** 折旧方法 */
depreciationMethod?: string
/** 折旧年限 */
depreciationYears?: number
/** 残值 */
salvageValue?: number
/** 保修截止日期 */
warrantyExpireDate?: string
/** 上次维护日期 */
lastMaintenanceDate?: string
/** 下次维护日期 */
nextMaintenanceDate?: string
/** 维护人员 */
maintenancePerson?: string
/** 库存条码 */
inventoryBarcode?: string
/** 资产备注 */
assetRemark?: 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

@ -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'

View File

@ -5,12 +5,6 @@ const Layout = () => import('@/layout/index.vue')
/** 系统路由 */ /** 系统路由 */
export const systemRoutes: RouteRecordRaw[] = [ export const systemRoutes: RouteRecordRaw[] = [
{
path: '/test-console',
name: 'TestConsole',
component: () => import('@/test-console.vue'),
meta: { title: 'Console测试', hidden: true },
},
{ {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
@ -32,299 +26,334 @@ export const systemRoutes: RouteRecordRaw[] = [
// } // }
// ], // ],
// }, // },
{
path: '/regulation',
name: 'Regulation',
component: Layout,
redirect: '/regulation/system-regulation',
meta: { title: '制度管理', icon: 'file-text', hidden: false, sort: 1.5 },
children: [
{
path: '/regulation/system-regulation',
name: 'SystemRegulation',
component: () => import('@/views/regulation/repository.vue'),
meta: { title: '制度公告', icon: 'file-text', hidden: false },
},
{
path: '/regulation/process-management',
name: 'ProcessManagement',
component: () => import('@/views/regulation/confirm.vue'),
meta: { title: '制度公示', icon: 'workflow', hidden: false },
},
{
path: '/regulation/proposal',
name: 'RegulationProposal',
component: () => import('@/views/regulation/proposal/index.vue'),
meta: { title: '制度提案', icon: 'edit', hidden: false },
},
{
path: '/regulation/type',
name: 'RegulationType',
component: () => import('@/views/regulation/type/index.vue'),
meta: { title: '制度类型', icon: 'tag', hidden: false },
},
],
},
{ {
path: '/organization', path: '/organization',
name: 'Organization', name: 'Organization',
component: Layout, component: Layout,
redirect: '/organization/hr/member', redirect: '/organization/dept',
meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 }, meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
children: [ children: [
{ {
path: '/organization/hr', path: '/organization/user',
name: 'HRManagement', name: 'OrganizationUser',
component: () => import('@/components/ParentView/index.vue'), component: () => import('@/views/system/user/index.vue'),
redirect: '/organization/hr/member', meta: { title: '用户管理', icon: 'user', hidden: false, sort: 2.25 },
meta: { title: '人员管理', icon: 'user', hidden: false },
children: [
{
path: '/organization/hr/member',
name: 'HRMember',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '成员', icon: 'user', hidden: false },
},
{
path: '/organization/hr/dept',
name: 'HRDept',
component: () => import('@/views/system/dept/index.vue'),
meta: { title: '部门', icon: 'dept', hidden: false },
},
{
path: '/organization/hr/workload',
name: 'HRWorkload',
component: () => import('@/views/hr/workload/index.vue'),
meta: { title: '工作量', icon: 'workload', hidden: false },
},
{
path: '/organization/hr/attendance',
name: 'HRAttendance',
component: () => import('@/views/hr/attendance/index.vue'),
meta: { title: '考勤', icon: 'attendance', hidden: false },
},
{
path: '/organization/hr/performance',
name: 'HRPerformance',
component: () => import('@/components/ParentView/index.vue'),
meta: { title: '绩效', icon: 'performance', hidden: false },
children: [
{
path: '/organization/hr/performance/dimention',
name: 'Dimention',
component: () => import('@/views/performance/setting/index.vue'),
meta: { title: '绩效维度', icon: 'performance', hidden: false },
},
{
path: '/organization/hr/performance/rule',
name: 'Rule',
component: () => import('@/views/performance/rule.vue'),
meta: { title: '绩效细则', icon: 'performance', hidden: false },
},
{
path: '/organization/hr/performance/my',
name: 'MyPerformance',
component: () => import('@/views/performance/my.vue'),
meta: { title: '我的绩效', icon: 'performance', hidden: false },
},
],
},
{
path: '/organization/hr/salary',
name: 'HRSalary',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/organization/hr/salary/overview',
meta: { title: '工资', icon: 'salary', hidden: false },
children: [
{
path: '/organization/hr/salary/overview',
name: 'HRSalaryOverview',
component: () => import('@/components/ParentView/index.vue'),
meta: { title: '工资概览', icon: 'salary', hidden: false },
children: [
{
path: '/organization/hr/salary/payroll',
name: 'Payroll',
component: () => import('@/views/salary-management/index.vue'),
meta: { title: '工资单', icon: 'salary', hidden: false },
},
],
},
],
},
// {
// path: '/organization/hr/salary/insurance',
// name: 'HRInsurance',
// component: () => import('@/components/ParentView/index.vue'),
// redirect: '/organization/hr/salary/insurance/overview',
// meta: { title: '保险', icon: 'safety', hidden: false },
// children: [
// {
// path: '/organization/hr/salary/insurance/overview',
// name: 'HRInsuranceOverview',
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/my-insurance',
// name: 'HRMyInsurance',
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
// meta: { title: '我的保险', icon: 'shield', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/health-records',
// name: 'HRHealthRecords',
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
// meta: { title: '健康档案', icon: 'heart', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/policy-files',
// name: 'HRPolicyFiles',
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
// meta: { title: '保单文件', icon: 'file', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/personal-info',
// name: 'HRPersonalInfo',
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
// meta: { title: '个人信息', icon: 'user', hidden: false },
// }
// ]
// },
{
path: '/organization/hr/salary/system-insurance/health-management',
name: 'HRSystemHealthManagement',
component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
meta: { title: '健康档案管理', icon: 'heart', hidden: false },
},
{
path: '/organization/hr/salary/system-insurance',
name: 'HRSystemInsurance',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/organization/hr/salary/system-insurance/overview',
meta: { title: '人员保险', icon: 'settings', hidden: false },
children: [
{
path: '/organization/hr/salary/system-insurance/overview',
name: 'HRSystemInsuranceOverview',
component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
},
{
path: '/organization/hr/salary/system-insurance/management',
name: 'HRSystemInsuranceManagement',
component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
meta: { title: '保险管理', icon: 'shield', hidden: false },
},
{
path: '/organization/hr/salary/system-insurance/file-management',
name: 'HRSystemFileManagement',
component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
meta: { title: '保单文件管理', icon: 'file', hidden: false },
},
{
path: '/organization/hr/salary/system-insurance/company-management',
name: 'HRSystemCompanyManagement',
component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
meta: { title: '保险公司管理', icon: 'building', hidden: false },
},
{
path: '/organization/hr/salary/system-insurance/type-management',
name: 'HRSystemTypeManagement',
component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
meta: { title: '保险类型管理', icon: 'category', hidden: false },
},
],
},
{
path: '/organization/hr/salary/certification',
name: 'HRCertification',
component: () => import('@/views/hr/salary/certification/index.vue'),
meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
},
{
path: '/organization/hr/contribution',
name: 'HRContribution',
component: () => import('@/views/hr/contribution/index.vue'),
meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
},
],
}, },
{ {
path: '/organization/role', path: '/organization/dept',
name: 'OrganizationRole', name: 'OrganizationDept',
component: () => import('@/views/system/role/index.vue'), component: () => import('@/views/system/dept/index.vue'),
meta: { title: '角色管理', icon: 'role', hidden: false }, meta: { title: '部门管理', icon: 'mind-mapping', hidden: false, sort: 2.5 },
}, },
{
path: '/organization/post',
name: 'OrganizationPost',
component: () => import('@/views/system/post/index.vue'),
meta: { title: '岗位管理', icon: 'nav', hidden: false, sort: 2.75 },
},
// {
// path: '/organization/hr/workload',
// name: 'HRWorkload',
// component: () => import('@/views/hr/workload/index.vue'),
// meta: { title: '任务管理', icon: 'bookmark', hidden: false },
// },
], ],
}, },
// {
// path: '/organization',
// name: 'Organization',
// component: Layout,
// redirect: '/organization/hr/member',
// meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
// children: [
// {
// path: '/organization/hr',
// name: 'HRManagement',
// component: () => import('@/components/ParentView/index.vue'),
// redirect: '/organization/hr/member',
// meta: { title: '人员管理', icon: 'user', hidden: false },
// children: [
// {
// path: '/organization/hr/member',
// name: 'HRMember',
// component: () => import('@/views/system/user/index.vue'),
// meta: { title: '成员', icon: 'user', hidden: false },
// },
// {
// path: '/organization/hr/dept',
// name: 'HRDept',
// component: () => import('@/views/system/dept/index.vue'),
// meta: { title: '部门', icon: 'dept', hidden: false },
// },
// {
// path: '/organization/hr/workload',
// name: 'HRWorkload',
// component: () => import('@/views/hr/workload/index.vue'),
// meta: { title: '任务管理', icon: 'workload', hidden: false },
// },
// {
// path: '/organization/hr/attendance',
// name: 'HRAttendance',
// component: () => import('@/views/hr/attendance/index.vue'),
// meta: { title: '考勤', icon: 'attendance', hidden: false },
// },
// {
// path: '/organization/hr/performance',
// name: 'HRPerformance',
// component: () => import('@/components/ParentView/index.vue'),
// meta: { title: '绩效', icon: 'performance', hidden: false },
// children: [
// {
// path: '/organization/hr/performance/dimention',
// name: 'Dimention',
// component: () => import('@/views/performance/setting/index.vue'),
// meta: { title: '绩效维度', icon: 'performance', hidden: false },
//
// },
// {
// path: '/organization/hr/performance/rule',
// name: 'Rule',
// component: () => import('@/views/performance/rule.vue'),
// meta: { title: '绩效细则', icon: 'performance', hidden: false },
//
// },
// {
// path: '/organization/hr/performance/my',
// name: 'MyPerformance',
// component: () => import('@/views/performance/my.vue'),
// meta: { title: '我的绩效', icon: 'performance', hidden: false },
//
// },
// ],
// },
// {
// path: '/organization/hr/salary',
// name: 'HRSalary',
// component: () => import('@/components/ParentView/index.vue'),
// redirect: '/organization/hr/salary/overview',
// meta: { title: '工资', icon: 'salary', hidden: false },
// children: [
// {
// path: '/organization/hr/salary/overview',
// name: 'HRSalaryOverview',
// component: () => import('@/components/ParentView/index.vue'),
// meta: { title: '工资概览', icon: 'salary', hidden: false },
// children: [
// {
// path: '/organization/hr/salary/payroll',
// name: 'Payroll',
// component: () => import('@/views/salary-management/index.vue'),
// meta: { title: '工资单', icon: 'salary', hidden: false },
//
// },
// ],
// },
// ],
// },
//
// {
// path: '/organization/hr/salary/insurance',
// name: 'HRInsurance',
// component: () => import('@/components/ParentView/index.vue'),
// redirect: '/organization/hr/salary/insurance/overview',
// meta: { title: '保险', icon: 'safety', hidden: false },
// children: [
// {
// path: '/organization/hr/salary/insurance/overview',
// name: 'HRInsuranceOverview',
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/my-insurance',
// name: 'HRMyInsurance',
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
// meta: { title: '我的保险', icon: 'shield', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/health-records',
// name: 'HRHealthRecords',
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
// meta: { title: '健康档案', icon: 'heart', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/policy-files',
// name: 'HRPolicyFiles',
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
// meta: { title: '保单文件', icon: 'file', hidden: false },
// },
// {
// path: '/organization/hr/salary/insurance/personal-info',
// name: 'HRPersonalInfo',
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
// meta: { title: '个人信息', icon: 'user', hidden: false },
// },
// ],
// },
//
// {
// path: '/organization/hr/salary/system-insurance/health-management',
// name: 'HRSystemHealthManagement',
// component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
// meta: { title: '健康档案管理', icon: 'heart', hidden: false },
// },
// {
// path: '/organization/hr/salary/system-insurance',
// name: 'HRSystemInsurance',
// component: () => import('@/components/ParentView/index.vue'),
// redirect: '/organization/hr/salary/system-insurance/overview',
// meta: { title: '人员保险', icon: 'settings', hidden: false },
// children: [
// {
// path: '/organization/hr/salary/system-insurance/overview',
// name: 'HRSystemInsuranceOverview',
// component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
// },
// {
// path: '/organization/hr/salary/system-insurance/management',
// name: 'HRSystemInsuranceManagement',
// component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
// meta: { title: '保险管理', icon: 'shield', hidden: false },
// },
// {
// path: '/organization/hr/salary/system-insurance/file-management',
// name: 'HRSystemFileManagement',
// component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
// meta: { title: '保单文件管理', icon: 'file', hidden: false },
// },
// {
// path: '/organization/hr/salary/system-insurance/company-management',
// name: 'HRSystemCompanyManagement',
// component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
// meta: { title: '保险公司管理', icon: 'building', hidden: false },
// },
// {
// path: '/organization/hr/salary/system-insurance/type-management',
// name: 'HRSystemTypeManagement',
// component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
// meta: { title: '保险类型管理', icon: 'category', hidden: false },
// },
// ],
// },
// {
// path: '/organization/hr/salary/certification',
// name: 'HRCertification',
// component: () => import('@/views/hr/salary/certification/index.vue'),
// meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
// },
// {
// path: '/organization/hr/contribution',
// name: 'HRContribution',
// component: () => import('@/views/hr/contribution/index.vue'),
// meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
// },
// ],
// },
// {
// path: '/organization/role',
// name: 'OrganizationRole',
// component: () => import('@/views/system/role/index.vue'),
// meta: { title: '角色管理', icon: 'role', hidden: false },
// },
// ],
// },
{ {
path: '/asset-management', path: '/asset-management',
name: 'AssetManagement', name: 'AssetManagement',
component: Layout, component: Layout,
redirect: '/asset-management/device-management/device-center', redirect: '/asset-management/device/inventory',
meta: { title: '资产管理', icon: 'property-safety', hidden: false, sort: 3 }, meta: { title: '资产管理', icon: 'property-safety', hidden: false, sort: 3 },
children: [ children: [
{ {
path: '/asset-management/device-management', path: '/asset-management/intellectual-property1',
name: 'DeviceManagement', name: 'IntellectualProperty1',
component: () => import('@/components/ParentView/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
redirect: '/asset-management/device-management/device-center', meta: { title: '设备管理', icon: 'copyright', hidden: false },
meta: {
title: '设备管理',
icon: 'device',
hidden: false,
},
children: [ children: [
{ {
path: '/asset-management/device-management/device-center', path: '/asset-management/intellectual-property1',
name: 'DeviceCenter', name: 'IntellectualProperty11',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '库存管理', hidden: false },
title: '设备中心',
icon: 'appstore',
hidden: false,
},
}, },
{ {
path: '/asset-management/device-management/device-detail/:id', path: '/asset-management/intellectual-property1',
name: 'DeviceDetail', name: 'IntellectualProperty12',
component: () => import('@/views/system-resource/device-management/detail.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '设备采购', hidden: false },
title: '设备详情',
icon: 'info-circle',
hidden: true,
},
}, },
{ {
path: '/asset-management/device-management/procurement', path: '/asset-management/intellectual-property1',
name: 'DeviceProcurement', name: 'IntellectualProperty13',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '在线管理', hidden: false },
title: '设备采购',
icon: 'shopping-cart',
hidden: false,
},
},
{
path: '/asset-management/device-management/online',
name: 'DeviceOnline',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/asset-management/device-management/online/drone',
meta: {
title: '在线管理',
icon: 'cloud',
hidden: false,
},
children: [ children: [
{ {
path: '/asset-management/device-management/online/drone', path: '/asset-management/intellectual-property11',
name: 'DeviceDrone', name: 'IntellectualProperty14',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '无人机', hidden: false },
title: '无人机',
icon: 'drone',
hidden: false,
},
}, },
{ {
path: '/asset-management/device-management/online/nest', path: '/asset-management/intellectual-property12',
name: 'DeviceNest', name: 'IntellectualProperty15',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '机巢', hidden: false },
title: '机巢',
icon: 'nest',
hidden: false,
},
}, },
{ {
path: '/asset-management/device-management/online/smart-terminal', path: '/asset-management/intellectual-property13',
name: 'DeviceSmartTerminal', name: 'IntellectualProperty16',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { meta: { title: '其他智能终端', hidden: false },
title: '其他智能终端', },
icon: 'terminal', {
hidden: false, path: '/asset-management/intellectual-property14',
}, name: 'IntellectualProperty17',
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { title: '车辆管理', hidden: false },
}, },
], ],
}, },
], ],
}, },
{ {
path: '/asset-management/other-assets', path: '/asset-management/intellectual-property',
name: 'OtherAssets', name: 'IntellectualProperty',
component: () => import('@/views/system-resource/information-system/software-management/index.vue'), component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
meta: { title: '其他资产', icon: 'copyright', hidden: false }, meta: { title: '其他资产', icon: 'copyright', hidden: false },
}, },
@ -529,9 +558,9 @@ export const systemRoutes: RouteRecordRaw[] = [
}, },
}, },
{ {
path: 'project-management/project-template/information-retrieval', path: '/project-management/project-template/information-retrieval',
name: 'InformationRetrieval', name: 'InformationRetrieval',
component: () => import ('@/views/default/error/404.vue'), component: () => import ('@/views/project-management/bidding/information-retrieval/index.vue'),
meta: { meta: {
title: '信息检索(N)', title: '信息检索(N)',
icon: 'trophy', icon: 'trophy',
@ -643,11 +672,11 @@ export const systemRoutes: RouteRecordRaw[] = [
{ {
path: '/project-management/projects/device', path: '/project-management/projects/device',
name: 'ProjectDeviceManagement', name: 'DeviceManagement',
component: () => import('@/views/system-resource/device-management/index.vue'), component: () => import('@/views/system-resource/device-management/index.vue'),
meta: { meta: {
title: '项目设备管理', title: '设备管理',
icon: 'device', icon: 'none',
hidden: false, hidden: false,
}, },
}, },
@ -674,6 +703,26 @@ export const systemRoutes: RouteRecordRaw[] = [
], ],
}, },
// 添加商务知识库
{
path: '/bussiness-knowledge',
name: 'bussinesskonwledge',
component: Layout,
redirect: '/bussiness-knowledge/data',
meta: { title: '商务资料知识库', icon: 'message', hidden: false, sort: 6 },
children: [
{
path: '/bussiness-konwledge/data',
name: 'bussiness-knowledge',
component: () => import('@/views/bussiness-data/bussiness.vue'),
meta: {
title: '商务数据库信息',
icon: 'info-circle',
hidden: false,
},
},
],
},
{ {
path: 'project-management/project-implementation/', path: 'project-management/project-implementation/',
name: 'Project-Implementation', name: 'Project-Implementation',
@ -780,11 +829,11 @@ export const systemRoutes: RouteRecordRaw[] = [
{ {
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment', path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment',
name: 'AttachmentManagement', name: 'AttachmentManagement',
component: () => import('@/views/operation-platform/data-processing/data-storage/index.vue'), component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-storage/index.vue'),
meta: { title: '附件管理', icon: 'attachment', hidden: false }, meta: { title: '附件管理', icon: 'attachment', hidden: false },
}, },
{ {
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/model-config', path: '/construction-operation-platform/implementation-workflow/data-processing/model-config',
name: 'ModelConfig', name: 'ModelConfig',
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'), component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'),
meta: { title: '模型配置', icon: 'robot', hidden: false }, meta: { title: '模型配置', icon: 'robot', hidden: false },
@ -795,8 +844,23 @@ export const systemRoutes: RouteRecordRaw[] = [
{ {
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data', path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data',
name: 'PreprocessedData', name: 'PreprocessedData',
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'), component: () => import('@/components/ParentView/index.vue'),
redirect: '/construction-operation-platform/implementation-workflow/data-processing/data-storage',
meta: { title: '数据预处理', icon: 'filter', hidden: false }, meta: { title: '数据预处理', icon: 'filter', hidden: false },
children: [
{
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageBatchUpload',
name: 'ImageBatchUpload',
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'),
meta: { title: '批量上传', icon: 'file', hidden: false },
},
{
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageSorting',
name: 'ImageSorting',
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/image-sorting/index.vue'),
meta: { title: '图像分拣', icon: 'attachment', hidden: false },
},
],
}, },
{ {
path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection', path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection',
@ -972,6 +1036,30 @@ export const systemRoutes: RouteRecordRaw[] = [
// } // }
], ],
}, },
{
path: '/user/profile',
name: 'UserProfile',
component: Layout,
redirect: '/user/profile',
meta: {
title: '个人中心',
icon: 'user',
hidden: false,
sort: 100,
},
children: [
{
path: '/user/profile',
name: 'UserProfile',
component: () => import('@/views/user/profile/index.vue'),
meta: {
title: '个人中心',
icon: 'user',
hidden: false,
},
},
],
},
{ {
path: '/enterprise-settings', path: '/enterprise-settings',
name: 'EnterpriseSettings', name: 'EnterpriseSettings',
@ -1070,32 +1158,66 @@ export const systemRoutes: RouteRecordRaw[] = [
}, },
], ],
}, },
{
path: '/training',
name: 'Training',
component: Layout,
redirect: '/training/plan',
meta: { title: '培训管理', icon: 'book', hidden: false, sort: 9 },
children: [
{
path: '/training/plan',
name: 'TrainingPlan',
component: () => import('@/views/training/plan/index.vue'),
meta: {
title: '培训计划',
icon: 'calendar',
hidden: false,
},
},
],
},
{ {
path: '/system-resource', path: '/system-resource',
name: 'SystemResource', name: 'SystemResource',
component: Layout, component: Layout,
redirect: '/system-resource/information-system/software-management', redirect: '/system-resource/device-management/warehouse',
meta: { title: '系统资源', icon: 'server', hidden: false, sort: 9 }, meta: { title: '关于平台', icon: 'server', hidden: false, sort: 9 },
children: [ children: [
{
path: '/system-resource/device-management/warehouse',
name: 'DeviceWarehouse',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '库存管理',
icon: 'warehouse',
hidden: false,
},
},
{
path: '/system-resource/device-management/online',
name: 'DeviceOnline',
component: () => import('@/components/ParentView/index.vue'),
redirect: '/system-resource/device-management/online/drone',
meta: {
title: '在线管理',
icon: 'cloud',
hidden: false,
},
children: [
{
path: '/system-resource/device-management/online/drone',
name: 'DeviceDrone',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '无人机',
icon: 'drone',
hidden: false,
},
},
{
path: '/system-resource/device-management/online/nest',
name: 'DeviceNest',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '机巢',
icon: 'nest',
hidden: false,
},
},
{
path: '/system-resource/device-management/online/smart-terminal',
name: 'DeviceSmartTerminal',
component: () => import('@/views/system-resource/device-management/index.vue'),
meta: {
title: '其他智能终端',
icon: 'terminal',
hidden: false,
},
},
],
},
{ {
path: '/system-resource/information-system', path: '/system-resource/information-system',
name: 'InformationSystem', name: 'InformationSystem',
@ -1141,7 +1263,6 @@ export const systemRoutes: RouteRecordRaw[] = [
}, },
], ],
}, },
{ {
path: '/', path: '/',
redirect: '/project-management/project-template/project-aproval', redirect: '/project-management/project-template/project-aproval',
@ -1167,6 +1288,11 @@ export const constantRoutes: RouteRecordRaw[] = [
}, },
], ],
}, },
// {
// path: '/user/profile',
// component: () => import('@/views/user/profile/index.vue'),
// meta: { hidden: true },
// },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
component: () => import('@/views/default/error/404.vue'), component: () => import('@/views/default/error/404.vue'),

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

@ -149,34 +149,6 @@ const storeSetup = () => {
isHidden: false, isHidden: false,
sort: 3, sort: 3,
}, },
{
id: 1070,
parentId: 1000,
title: '部门管理',
type: 2,
path: '/system/dept',
name: 'SystemDept',
component: 'system/dept/index',
icon: 'mind-mapping',
isExternal: false,
isCache: false,
isHidden: false,
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 +0,0 @@
export interface EquipmentPageQuery {
equipmentName?: string
equipmentType?: string
equipmentStatus?: string
equipmentSn?: string
assetCode?: string
brand?: string
locationStatus?: string
healthStatus?: string
responsiblePerson?: string
useStatus?: string
projectId?: string
userId?: string
equipmentModel?: string
specification?: string
physicalLocation?: string
supplierName?: string
maintenancePerson?: string
inventoryBarcode?: string
assetRemark?: string
// 新增搜索字段
usingDepartment?: string
invoice?: string
barcode?: string
importer?: string
page?: number
pageSize?: number
orderBy?: string
orderDirection?: string
}
export interface EquipmentReq {
equipmentName: string
equipmentModel: string
equipmentType: string
equipmentStatus: string
useStatus: string
equipmentSn: string
assetCode?: string
brand?: string
specification?: string
locationStatus?: string
physicalLocation?: string
responsiblePerson?: string
healthStatus?: string
purchaseTime?: string
inStockTime?: string
activationTime?: string
expectedScrapTime?: string
actualScrapTime?: string
statusChangeTime?: string
purchaseOrder?: string
supplierName?: string
purchasePrice?: number
currentNetValue?: number
depreciationMethod?: string
depreciationYears?: number
salvageValue?: number
warrantyExpireDate?: string
lastMaintenanceDate?: string
nextMaintenanceDate?: string
maintenancePerson?: string
inventoryBarcode?: string
assetRemark?: string
// 新增字段
usingDepartment?: string
borrowingTime?: string
returnTime?: string
outStockTime?: string
totalUsageTime?: string
depreciationRate?: number
depreciationMethodDesc?: string
invoice?: string
invoiceStatus?: string
attachments?: string
photos?: string
barcode?: string
importer?: string
inventoryTimeStatus1?: string
inventoryTimeStatus2?: string
inventoryTimeStatus3?: string
inventoryCheckTimeStatus1?: string
inventoryCheckTimeStatus2?: string
inventoryCheckTimeStatus3?: string
}
export interface EquipmentResp {
equipmentId: string
assetCode?: string
equipmentName: string
equipmentType: string
equipmentTypeLabel?: string
equipmentModel: string
equipmentSn: string
brand?: string
specification?: string
equipmentStatus: string
equipmentStatusLabel?: string
useStatus: string
locationStatus?: string
locationStatusLabel?: string
physicalLocation?: string
responsiblePerson?: string
healthStatus?: string
healthStatusLabel?: string
purchaseTime?: string
inStockTime?: string
activationTime?: string
expectedScrapTime?: string
actualScrapTime?: string
statusChangeTime?: string
purchaseOrder?: string
supplierName?: string
purchasePrice?: number
currentNetValue?: number
depreciationMethod?: string
depreciationYears?: number
salvageValue?: number
warrantyExpireDate?: string
lastMaintenanceDate?: string
nextMaintenanceDate?: string
maintenancePerson?: string
inventoryBarcode?: string
assetRemark?: string
// 新增字段
usingDepartment?: string
borrowingTime?: string
returnTime?: string
outStockTime?: string
totalUsageTime?: string
depreciationRate?: number
depreciationMethodDesc?: string
invoice?: string
invoiceStatus?: string
attachments?: string
photos?: string
barcode?: string
importer?: string
inventoryTimeStatus1?: string
inventoryTimeStatus2?: string
inventoryTimeStatus3?: string
inventoryCheckTimeStatus1?: string
inventoryCheckTimeStatus2?: string
inventoryCheckTimeStatus3?: string
projectId?: string
projectName?: string
userId?: string
name?: string
createTime?: string
updateTime?: string
}
export interface EquipmentTypeOption {
label: string
value: string
}
export interface EquipmentStatusOption {
label: string
value: string
color: string
}
export interface LocationStatusOption {
label: string
value: string
color: string
}
export interface HealthStatusOption {
label: string
value: string
color: string
}
export interface DepreciationMethodOption {
label: 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));
} }

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

@ -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)

View File

@ -4,50 +4,50 @@
<!-- 缩放控制工具栏 --> <!-- 缩放控制工具栏 -->
<div v-if="selectedImage" class="zoom-controls"> <div v-if="selectedImage" class="zoom-controls">
<a-space> <a-space>
<a-button size="small" :disabled="scale <= MIN_SCALE" @click="zoomOut"> <a-button size="small" @click="zoomOut" :disabled="scale <= MIN_SCALE">
<template #icon> <template #icon>
<IconMinus /> <icon-minus />
</template> </template>
</a-button> </a-button>
<span class="zoom-text">{{ Math.round(scale * 100) }}%</span> <span class="zoom-text">{{ Math.round(scale * 100) }}%</span>
<a-button size="small" :disabled="scale >= MAX_SCALE" @click="zoomIn"> <a-button size="small" @click="zoomIn" :disabled="scale >= MAX_SCALE">
<template #icon> <template #icon>
<IconPlus /> <icon-plus />
</template> </template>
</a-button> </a-button>
<a-button size="small" @click="resetZoom"> <a-button size="small" @click="resetZoom">
<template #icon> <template #icon>
<IconRefresh /> <icon-refresh />
</template> </template>
重置 重置
</a-button> </a-button>
</a-space> </a-space>
</div> </div>
<div v-if="!selectedImage" class="preview-header"> <div v-if="!selectedImage" class="preview-header">
<h3>暂无图像预览</h3> <h3>暂无图像预览</h3>
<p>请从左侧选择具体查看图像</p> <p>请从左侧选择具体查看图像</p>
</div> </div>
<div class="preview-content"> <div class="preview-content">
<div <div
v-if="selectedImage" v-if="selectedImage"
class="image-viewer" class="image-viewer"
:class="{ dragging: isDragging }" :class="{ 'dragging': isDragging }"
@wheel="handleWheel" @wheel="handleWheel"
@mousedown="handleMouseDown" @mousedown="handleMouseDown"
@mousemove="handleMouseMove" @mousemove="handleMouseMove"
@mouseup="handleMouseUp" @mouseup="handleMouseUp"
@mouseleave="handleMouseLeave" @mouseleave="handleMouseLeave"
> >
<div <div
class="image-container" class="image-container"
:style="{ :style="{
transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`, transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`,
transition: isDragging ? 'none' : 'transform 0.2s ease-out', transition: isDragging ? 'none' : 'transform 0.2s ease-out'
}" }"
> >
<img <img
:src="getImageUrl(selectedImage.imagePath)" :src="getImageUrl(selectedImage.imagePath)"
:alt="selectedImage.imageName" :alt="selectedImage.imageName"
draggable="false" draggable="false"
@load="onImageLoad" @load="onImageLoad"
@ -55,7 +55,7 @@
</div> </div>
</div> </div>
<div v-else class="empty-preview"> <div v-else class="empty-preview">
<IconImage class="empty-icon" /> <icon-image class="empty-icon" />
<p>请从下方缩略图中选择图像</p> <p>请从下方缩略图中选择图像</p>
</div> </div>
</div> </div>
@ -64,7 +64,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, ref, watch } from 'vue' import { ref, watch, nextTick } from 'vue'
import { IconImage, IconMinus, IconPlus, IconRefresh } from '@arco-design/web-vue/es/icon' import { IconImage, IconMinus, IconPlus, IconRefresh } from '@arco-design/web-vue/es/icon'
const props = defineProps<{ const props = defineProps<{
@ -114,7 +114,7 @@ const zoomIn = () => {
const zoomOut = () => { const zoomOut = () => {
const newScale = Math.max(MIN_SCALE, scale.value - SCALE_STEP) const newScale = Math.max(MIN_SCALE, scale.value - SCALE_STEP)
scale.value = newScale scale.value = newScale
// 1 // 1
if (newScale <= 1) { if (newScale <= 1) {
translateX.value = 0 translateX.value = 0
@ -127,7 +127,7 @@ const onImageLoad = (event: Event) => {
const img = event.target as HTMLImageElement const img = event.target as HTMLImageElement
imageWidth.value = img.naturalWidth imageWidth.value = img.naturalWidth
imageHeight.value = img.naturalHeight imageHeight.value = img.naturalHeight
nextTick(() => { nextTick(() => {
const container = img.parentElement?.parentElement const container = img.parentElement?.parentElement
if (container) { if (container) {
@ -142,25 +142,25 @@ const getBoundaryLimits = () => {
if (!imageWidth.value || !imageHeight.value || !containerWidth.value || !containerHeight.value) { if (!imageWidth.value || !imageHeight.value || !containerWidth.value || !containerHeight.value) {
return { maxX: 0, maxY: 0, minX: 0, minY: 0 } return { maxX: 0, maxY: 0, minX: 0, minY: 0 }
} }
const scaledWidth = imageWidth.value * scale.value const scaledWidth = imageWidth.value * scale.value
const scaledHeight = imageHeight.value * scale.value const scaledHeight = imageHeight.value * scale.value
const maxX = Math.max(0, (scaledWidth - containerWidth.value) / 2) const maxX = Math.max(0, (scaledWidth - containerWidth.value) / 2)
const maxY = Math.max(0, (scaledHeight - containerHeight.value) / 2) const maxY = Math.max(0, (scaledHeight - containerHeight.value) / 2)
return { return {
maxX, maxX,
maxY, maxY,
minX: -maxX, minX: -maxX,
minY: -maxY, minY: -maxY
} }
} }
// //
const applyBoundaryLimits = () => { const applyBoundaryLimits = () => {
const { maxX, maxY, minX, minY } = getBoundaryLimits() const { maxX, maxY, minX, minY } = getBoundaryLimits()
translateX.value = Math.max(minX, Math.min(maxX, translateX.value)) translateX.value = Math.max(minX, Math.min(maxX, translateX.value))
translateY.value = Math.max(minY, Math.min(maxY, translateY.value)) translateY.value = Math.max(minY, Math.min(maxY, translateY.value))
} }
@ -168,13 +168,13 @@ const applyBoundaryLimits = () => {
// //
const handleWheel = (event: WheelEvent) => { const handleWheel = (event: WheelEvent) => {
event.preventDefault() event.preventDefault()
const delta = event.deltaY > 0 ? -SCALE_STEP : SCALE_STEP const delta = event.deltaY > 0 ? -SCALE_STEP : SCALE_STEP
const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale.value + delta)) const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale.value + delta))
if (newScale !== scale.value) { if (newScale !== scale.value) {
scale.value = newScale scale.value = newScale
// 1 // 1
if (newScale <= 1) { if (newScale <= 1) {
translateX.value = 0 translateX.value = 0
@ -202,10 +202,10 @@ const handleMouseMove = (event: MouseEvent) => {
event.preventDefault() event.preventDefault()
const deltaX = event.clientX - dragStartX.value const deltaX = event.clientX - dragStartX.value
const deltaY = event.clientY - dragStartY.value const deltaY = event.clientY - dragStartY.value
translateX.value = initialTranslateX.value + deltaX translateX.value = initialTranslateX.value + deltaX
translateY.value = initialTranslateY.value + deltaY translateY.value = initialTranslateY.value + deltaY
// //
applyBoundaryLimits() applyBoundaryLimits()
} }
@ -226,7 +226,7 @@ const handleMouseLeave = () => {
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}`
} }
</script> </script>
@ -249,7 +249,7 @@ const getImageUrl = (imagePath: string): string => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
.zoom-text { .zoom-text {
min-width: 50px; min-width: 50px;
text-align: center; text-align: center;
@ -356,4 +356,4 @@ const getImageUrl = (imagePath: string): string => {
} }
} }
} }
</style> </style>

View File

@ -15,10 +15,10 @@
<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'" />
<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>
@ -30,11 +30,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import {
IconApps, IconFolder,
IconFolder, IconSettings,
IconSettings, IconTool,
IconTool, IconApps
} from '@arco-design/web-vue/es/icon' } from '@arco-design/web-vue/es/icon'
import type { ProjectTreeNode } from '@/apis/industrial-image' import type { ProjectTreeNode } from '@/apis/industrial-image'
@ -87,26 +87,26 @@ const handleLoadMore = (node: ProjectTreeNode) => {
overflow-x: hidden; overflow-x: hidden;
height: 0; /* 让 flex: 1 生效,自动计算高度 */ height: 0; /* 让 flex: 1 生效,自动计算高度 */
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;
@ -136,4 +136,4 @@ const handleLoadMore = (node: ProjectTreeNode) => {
} }
} }
} }
</style> </style>

View File

@ -4,11 +4,11 @@
<h3>识别结果</h3> <h3>识别结果</h3>
<div class="header-actions"> <div class="header-actions">
<a-button type="text" @click="handleSaveResults"> <a-button type="text" @click="handleSaveResults">
<template #icon><IconSave /></template> <template #icon><icon-save /></template>
保存结果 保存结果
</a-button> </a-button>
<a-button type="text" @click="handleExportResults"> <a-button type="text" @click="handleExportResults">
<template #icon><IconExport /></template> <template #icon><icon-export /></template>
导出 导出
</a-button> </a-button>
</div> </div>
@ -43,28 +43,28 @@
<div class="results-list"> <div class="results-list">
<h4>详细结果</h4> <h4>详细结果</h4>
<div class="result-items"> <div class="result-items">
<div <div
v-for="(result, index) in results" v-for="(result, index) in results"
:key="index" :key="index"
class="result-item" class="result-item"
:class="{ active: selectedResultIndex === index }" :class="{ active: selectedResultIndex === index }"
@click="handleResultSelect(index)" @click="handleResultSelect(index)"
> >
<div class="result-info"> <div class="result-info">
<span class="result-type" :class="getDefectTypeClass(result.defectType)"> <span class="result-type" :class="getDefectTypeClass(result.defectType)">
{{ getDefectTypeName(result.defectType) }} {{ getDefectTypeName(result.defectType) }}
</span> </span>
<span class="result-confidence">{{ ((result.markInfo?.confidence || 0) * 100).toFixed(1) }}%</span> <span class="result-confidence">{{ ((result.markInfo?.confidence || 0) * 100).toFixed(1) }}%</span>
</div>
<div class="result-position">
位置: {{ formatPosition(result.markInfo?.bbox) }}
</div>
<div class="result-size">
尺寸: {{ formatSize(result.markInfo?.bbox) }}
</div>
<div class="result-recommendation">
建议: {{ getRecommendation(result.defectType, result.markInfo?.confidence || 0) }}
</div> </div>
<div class="result-position">
位置: {{ formatPosition(result.markInfo?.bbox) }}
</div>
<div class="result-size">
尺寸: {{ formatSize(result.markInfo?.bbox) }}
</div>
<div class="result-recommendation">
建议: {{ getRecommendation(result.defectType, result.markInfo?.confidence || 0) }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -73,10 +73,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { ref, computed } from 'vue'
import { import {
IconExport,
IconSave, IconSave,
IconExport
} from '@arco-design/web-vue/es/icon' } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
@ -105,7 +105,7 @@ const statistics = computed(() => {
wear: 0, wear: 0,
wearConfidence: 0, wearConfidence: 0,
deformation: 0, deformation: 0,
deformationConfidence: 0, deformationConfidence: 0
} }
if (props.results.length === 0) return stats if (props.results.length === 0) return stats
@ -113,7 +113,7 @@ const statistics = computed(() => {
// //
const typeMapping: Record<string, string> = { const typeMapping: Record<string, string> = {
bmlw: 'crack', // -> bmlw: 'crack', // ->
hfms: 'wear', // -> hfms: 'wear', // ->
mpps: 'deformation', // -> mpps: 'deformation', // ->
bcps: 'deformation', // -> bcps: 'deformation', // ->
jbps: 'deformation', // -> jbps: 'deformation', // ->
@ -127,38 +127,38 @@ const statistics = computed(() => {
const categorizedResults = props.results.reduce((acc, result) => { const categorizedResults = props.results.reduce((acc, result) => {
// //
const standardType = typeMapping[result.defectType] || result.defectType const standardType = typeMapping[result.defectType] || result.defectType
// //
if (!acc[standardType]) acc[standardType] = [] if (!acc[standardType]) acc[standardType] = []
// //
acc[standardType].push(result) acc[standardType].push(result)
return acc return acc
}, {} as Record<string, DefectDetectionResult[]>) }, {} as Record<string, DefectDetectionResult[]>)
// //
const crackResults = categorizedResults.crack || [] const crackResults = categorizedResults['crack'] || []
stats.crack = crackResults.length stats.crack = crackResults.length
stats.crackConfidence = crackResults.length > 0 stats.crackConfidence = crackResults.length > 0
? Math.round(crackResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / crackResults.length) ? Math.round(crackResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / crackResults.length)
: 0 : 0
// //
const corrosionResults = categorizedResults.corrosion || [] const corrosionResults = categorizedResults['corrosion'] || []
stats.corrosion = corrosionResults.length stats.corrosion = corrosionResults.length
stats.corrosionConfidence = corrosionResults.length > 0 stats.corrosionConfidence = corrosionResults.length > 0
? Math.round(corrosionResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / corrosionResults.length) ? Math.round(corrosionResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / corrosionResults.length)
: 0 : 0
// //
const wearResults = categorizedResults.wear || [] const wearResults = categorizedResults['wear'] || []
stats.wear = wearResults.length stats.wear = wearResults.length
stats.wearConfidence = wearResults.length > 0 stats.wearConfidence = wearResults.length > 0
? Math.round(wearResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / wearResults.length) ? Math.round(wearResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / wearResults.length)
: 0 : 0
// //
const deformationResults = categorizedResults.deformation || [] const deformationResults = categorizedResults['deformation'] || []
stats.deformation = deformationResults.length stats.deformation = deformationResults.length
stats.deformationConfidence = deformationResults.length > 0 stats.deformationConfidence = deformationResults.length > 0
? Math.round(deformationResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / deformationResults.length) ? Math.round(deformationResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / deformationResults.length)
@ -219,7 +219,7 @@ const getDefectTypeClass = (type: string): string => {
// //
const getRecommendation = (type: string, confidence: number): string => { const getRecommendation = (type: string, confidence: number): string => {
if (confidence < 60) return '建议人工复核' if (confidence < 60) return '建议人工复核'
const recommendations: Record<string, string> = { const recommendations: Record<string, string> = {
crack: '立即维修,防止扩散', crack: '立即维修,防止扩散',
corrosion: '清洁并涂保护层', corrosion: '清洁并涂保护层',
@ -227,7 +227,7 @@ const getRecommendation = (type: string, confidence: number): string => {
deformation: '检查结构完整性', deformation: '检查结构完整性',
scratch: '轻微处理即可', scratch: '轻微处理即可',
hole: '立即修补', hole: '立即修补',
dirt: '清洁处理', dirt: '清洁处理'
} }
return recommendations[type] || '建议进一步检查' return recommendations[type] || '建议进一步检查'
} }
@ -442,10 +442,10 @@ const handleExportResults = () => {
.results-content::-webkit-scrollbar-thumb { .results-content::-webkit-scrollbar-thumb {
background: #cbd5e1; background: #cbd5e1;
border-radius: 3px; border-radius: 3px;
&:hover { &:hover {
background: #94a3b8; background: #94a3b8;
} }
} }
} }
</style> </style>

View File

@ -1,18 +1,35 @@
import { computed, ref } from 'vue' import { ref, reactive, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue' import { Message, Modal } from '@arco-design/web-vue'
import { import {
type PartInfo,
type ProjectInfo,
type ProjectTreeNode,
type TurbineInfo,
deleteImage,
getImageList,
getPartList,
getProjectList, getProjectList,
getTurbineList, getTurbineList,
getPartList,
getImageList,
deleteImage,
autoAnnotateImage,
generateReport,
batchUploadImages,
uploadImageToPartV2, uploadImageToPartV2,
type ProjectTreeNode,
type IndustrialImage,
type ProjectInfo,
type TurbineInfo,
type PartInfo
} from '@/apis/industrial-image' } from '@/apis/industrial-image'
import type { type DefectDetectionRequest, type DefectDetectionResult, DefectInfo, type ManualDefectAddRequest, addDefect, addManualDefect, deleteDefect, detectDefects, getDefectList, updateDefect } from '@/apis/industrial-image/defect' import {
detectDefects,
getDefectList,
addDefect,
updateDefect,
deleteDefect,
addManualDefect,
type DefectDetectionRequest,
type DefectDetectionResult,
type ManualDefectAddRequest
} from '@/apis/industrial-image/defect'
import type { TreeNodeData, ImageInfo } from '@/apis/industrial-image/type'
import type { DefectInfo } from '@/apis/industrial-image/defect'
import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
export function useIndustrialImage() { export function useIndustrialImage() {
// 项目树数据 // 项目树数据
@ -48,7 +65,7 @@ export function useIndustrialImage() {
const isManualAnnotationMode = ref(false) const isManualAnnotationMode = ref(false)
const defectList = ref<DefectInfo[]>([]) const defectList = ref<DefectInfo[]>([])
const selectedDefect = ref<DefectInfo | null>(null) const selectedDefect = ref<DefectInfo | null>(null)
// Canvas 标注相关状态 // Canvas 标注相关状态
const selectedDefectAnnotations = computed(() => { const selectedDefectAnnotations = computed(() => {
if (!selectedDefect.value) return [] if (!selectedDefect.value) return []
@ -56,14 +73,13 @@ export function useIndustrialImage() {
return [{ return [{
id: selectedDefect.value.id, id: selectedDefect.value.id,
type: 'rectangle' as const, type: 'rectangle' as const,
points: selectedDefect.value.boundingBox points: selectedDefect.value.boundingBox ? [
? [ { x: selectedDefect.value.boundingBox.x, y: selectedDefect.value.boundingBox.y },
{ x: selectedDefect.value.boundingBox.x, y: selectedDefect.value.boundingBox.y }, { x: selectedDefect.value.boundingBox.x + selectedDefect.value.boundingBox.width,
{ x: selectedDefect.value.boundingBox.x + selectedDefect.value.boundingBox.width, y: selectedDefect.value.boundingBox.y + selectedDefect.value.boundingBox.height }, y: selectedDefect.value.boundingBox.y + selectedDefect.value.boundingBox.height }
] ] : [],
: [],
color: selectedDefect.value.severity === 'high' ? '#ff4d4f' : '#faad14', color: selectedDefect.value.severity === 'high' ? '#ff4d4f' : '#faad14',
label: selectedDefect.value.defectType, label: selectedDefect.value.defectType
}] }]
}) })
@ -86,7 +102,7 @@ export function useIndustrialImage() {
try { try {
loading.value = true loading.value = true
const response = await getProjectList({ page: 1, pageSize: 1000 }) const response = await getProjectList({ page: 1, pageSize: 1000 })
// 处理不同的返回数据结构 // 处理不同的返回数据结构
let projects: ProjectInfo[] = [] let projects: ProjectInfo[] = []
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
@ -96,8 +112,8 @@ export function useIndustrialImage() {
} else { } else {
projects = [] projects = []
} }
projectTreeData.value = projects.map((project) => ({ projectTreeData.value = projects.map(project => ({
id: project.projectId, id: project.projectId,
name: project.projectName, name: project.projectName,
type: 'project' as const, type: 'project' as const,
@ -107,16 +123,16 @@ export function useIndustrialImage() {
expanded: false, expanded: false,
status: project.status, status: project.status,
createTime: project.createTime, createTime: project.createTime,
rawData: project, rawData: project
})) }))
// 默认选中第一个项目 // 默认选中第一个项目
if (projectTreeData.value.length > 0) { if (projectTreeData.value.length > 0) {
const firstProject = projectTreeData.value[0] const firstProject = projectTreeData.value[0]
selectedKeys.value = [firstProject.id] selectedKeys.value = [firstProject.id]
currentProjectId.value = firstProject.id currentProjectId.value = firstProject.id
console.log('默认选中第一个项目:', firstProject.name) console.log('默认选中第一个项目:', firstProject.name)
// 加载图像列表 // 加载图像列表
loadImageList() loadImageList()
} }
@ -131,13 +147,13 @@ export function useIndustrialImage() {
// 懒加载子节点 // 懒加载子节点
const handleLoadMore = async (node: ProjectTreeNode) => { const handleLoadMore = async (node: ProjectTreeNode) => {
if (!node || node.loaded) return if (!node || node.loaded) return
try { try {
if (node.type === 'project') { if (node.type === 'project') {
const response = await getTurbineList({ projectId: node.id }) const response = await getTurbineList({ projectId: node.id })
const turbines = response.data || [] const turbines = response.data || []
node.children = turbines.map((turbine) => ({ node.children = turbines.map(turbine => ({
id: turbine.turbineId || turbine.projectId, id: turbine.turbineId || turbine.projectId,
name: turbine.turbineName || turbine.turbineDesc || `机组${turbine.turbineId || turbine.projectId}`, name: turbine.turbineName || turbine.turbineDesc || `机组${turbine.turbineId || turbine.projectId}`,
type: 'turbine' as const, type: 'turbine' as const,
@ -148,16 +164,16 @@ export function useIndustrialImage() {
expanded: false, expanded: false,
status: turbine.status, status: turbine.status,
createTime: turbine.createTime, createTime: turbine.createTime,
rawData: turbine, rawData: turbine
})) }))
} else if (node.type === 'turbine') { } else if (node.type === 'turbine') {
const turbineData = node.rawData as TurbineInfo const turbineData = node.rawData as TurbineInfo
const response = await getPartList({ const response = await getPartList({
turbineId: turbineData?.turbineId || node.id, turbineId: turbineData?.turbineId || node.id
}) })
const parts = response.data || [] const parts = response.data || []
node.children = parts.map((part) => ({ node.children = parts.map(part => ({
id: part.partId, id: part.partId,
name: part.partName || part.partType || `部件${part.partId}`, name: part.partName || part.partType || `部件${part.partId}`,
type: 'part' as const, type: 'part' as const,
@ -168,10 +184,10 @@ export function useIndustrialImage() {
expanded: false, expanded: false,
status: part.partType, status: part.partType,
createTime: part.createTime, createTime: part.createTime,
rawData: part, rawData: part
})) }))
} }
node.loaded = true node.loaded = true
} catch (error) { } catch (error) {
console.error('加载子节点失败:', error) console.error('加载子节点失败:', error)
@ -184,11 +200,11 @@ export function useIndustrialImage() {
if (keys.length > 0) { if (keys.length > 0) {
selectedKeys.value = keys selectedKeys.value = keys
currentProjectId.value = keys[0] currentProjectId.value = keys[0]
const selectedNode = findSelectedNode(projectTreeData.value, keys[0]) const selectedNode = findSelectedNode(projectTreeData.value, keys[0])
if (selectedNode) { if (selectedNode) {
console.log('选中节点:', selectedNode) console.log('选中节点:', selectedNode)
// 如果选中的是机组节点,设置为当前选中的机组 // 如果选中的是机组节点,设置为当前选中的机组
if (selectedNode.type === 'turbine') { if (selectedNode.type === 'turbine') {
selectedTurbineId.value = selectedNode.id selectedTurbineId.value = selectedNode.id
@ -196,7 +212,7 @@ export function useIndustrialImage() {
loadTurbineParts(selectedNode) loadTurbineParts(selectedNode)
} }
} }
loadImageList() loadImageList()
} }
} }
@ -205,13 +221,13 @@ export function useIndustrialImage() {
const loadTurbineParts = async (turbineNode: ProjectTreeNode) => { const loadTurbineParts = async (turbineNode: ProjectTreeNode) => {
try { try {
const turbineData = turbineNode.rawData as TurbineInfo const turbineData = turbineNode.rawData as TurbineInfo
const response = await getPartList({ const response = await getPartList({
turbineId: turbineData?.turbineId || turbineNode.id, turbineId: turbineData?.turbineId || turbineNode.id
}) })
const parts = response.data || [] const parts = response.data || []
console.log('加载的部件信息:', parts) console.log('加载的部件信息:', parts)
currentTurbineParts.value = parts currentTurbineParts.value = parts
console.log('设置部件信息完成,数量:', currentTurbineParts.value.length) console.log('设置部件信息完成,数量:', currentTurbineParts.value.length)
} catch (error) { } catch (error) {
@ -225,13 +241,13 @@ export function useIndustrialImage() {
const loadImageList = async () => { const loadImageList = async () => {
try { try {
loading.value = true loading.value = true
const selectedNode = findSelectedNode(projectTreeData.value, currentProjectId.value) const selectedNode = findSelectedNode(projectTreeData.value, currentProjectId.value)
if (!selectedNode) { if (!selectedNode) {
imageList.value = [] imageList.value = []
return return
} }
// 构建查询参数 // 构建查询参数
const params: { const params: {
imageTypes?: string[] imageTypes?: string[]
@ -239,12 +255,12 @@ export function useIndustrialImage() {
partId?: string partId?: string
turbineId?: string turbineId?: string
} = {} } = {}
// 添加关键字搜索 // 添加关键字搜索
if (searchKeyword.value) { if (searchKeyword.value) {
params.keyword = searchKeyword.value params.keyword = searchKeyword.value
} }
// 根据选中节点类型设置查询参数 // 根据选中节点类型设置查询参数
if (selectedNode.type === 'project') { if (selectedNode.type === 'project') {
// 选中项目时,不设置特定的过滤条件,查询所有图像 // 选中项目时,不设置特定的过滤条件,查询所有图像
@ -259,7 +275,7 @@ export function useIndustrialImage() {
params.turbineId = turbineNode.id params.turbineId = turbineNode.id
} }
} }
console.log('查询图像列表参数:', params) console.log('查询图像列表参数:', params)
// 调用真实API // 调用真实API
const response = await getImageList(params) const response = await getImageList(params)
@ -272,13 +288,15 @@ export function useIndustrialImage() {
} }
} }
// 查找选中的节点 // 查找选中的节点
const findSelectedNode = (nodes: ProjectTreeNode[], nodeId: string): ProjectTreeNode | null => { const findSelectedNode = (nodes: ProjectTreeNode[], nodeId: string): ProjectTreeNode | null => {
if (!nodes || !Array.isArray(nodes) || !nodeId) return null if (!nodes || !Array.isArray(nodes) || !nodeId) return null
for (const node of nodes) { for (const node of nodes) {
if (!node) continue if (!node) continue
if (node.id === nodeId) { if (node.id === nodeId) {
return node return node
} }
@ -293,14 +311,14 @@ export function useIndustrialImage() {
// 查找父节点 // 查找父节点
const findParentNode = (nodes: ProjectTreeNode[], childId: string): ProjectTreeNode | null => { const findParentNode = (nodes: ProjectTreeNode[], childId: string): ProjectTreeNode | null => {
if (!nodes || !Array.isArray(nodes) || !childId) return null if (!nodes || !Array.isArray(nodes) || !childId) return null
for (const node of nodes) { for (const node of nodes) {
if (!node) continue if (!node) continue
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
const childFound = node.children.find((child) => child && child.id === childId) const childFound = node.children.find(child => child && child.id === childId)
if (childFound) return node if (childFound) return node
const found = findParentNode(node.children, childId) const found = findParentNode(node.children, childId)
if (found) return found if (found) return found
} }
@ -312,18 +330,18 @@ export function useIndustrialImage() {
const handleImageSelect = (image: any) => { const handleImageSelect = (image: any) => {
console.log('选中图像:', image) console.log('选中图像:', image)
selectedImage.value = image selectedImage.value = image
// 尝试多种可能的图像ID字段名 // 尝试多种可能的图像ID字段名
const imageId = image.imageId || image.id || image.image_id || image.key || image.attachId const imageId = image.imageId || image.id || image.image_id || image.key || image.attachId
selectedImageId.value = imageId || '' selectedImageId.value = imageId || ''
console.log('设置图像ID:', selectedImageId.value) console.log('设置图像ID:', selectedImageId.value)
if (!selectedImageId.value) { if (!selectedImageId.value) {
console.warn('图像ID为空图像数据结构:', image) console.warn('图像ID为空图像数据结构:', image)
Message.warning('图像ID为空标注功能可能无法正常使用') Message.warning('图像ID为空标注功能可能无法正常使用')
} }
// 确保图像对象有必要的字段 // 确保图像对象有必要的字段
if (!image.attachId && imageId) { if (!image.attachId && imageId) {
image.attachId = imageId image.attachId = imageId
@ -331,7 +349,7 @@ export function useIndustrialImage() {
} }
const handleImagePreview = (image: any) => { const handleImagePreview = (image: any) => {
console.log('image:', image) console.log("image:",image);
previewImage.value = image previewImage.value = image
previewModalVisible.value = true previewModalVisible.value = true
} }
@ -354,7 +372,7 @@ export function useIndustrialImage() {
console.error('删除图像失败:', error) console.error('删除图像失败:', error)
Message.error('删除图像失败') Message.error('删除图像失败')
} }
}, }
}) })
} }
@ -379,7 +397,7 @@ export function useIndustrialImage() {
Message.warning('请先选择一个项目或机组') Message.warning('请先选择一个项目或机组')
return return
} }
if (selectedNode.type === 'turbine') { if (selectedNode.type === 'turbine') {
// 如果选中的是机组,直接打开向导 // 如果选中的是机组,直接打开向导
importWizardVisible.value = true importWizardVisible.value = true
@ -397,13 +415,13 @@ export function useIndustrialImage() {
Message.warning('请先选择一张图像') Message.warning('请先选择一张图像')
return return
} }
// 切换到自动识别模式 // 切换到自动识别模式
isAutoRecognitionMode.value = true isAutoRecognitionMode.value = true
// 清空之前的识别结果 // 清空之前的识别结果
recognitionResults.value = [] recognitionResults.value = []
Message.info('已切换到自动识别模式') Message.info('已切换到自动识别模式')
} }
@ -412,13 +430,13 @@ export function useIndustrialImage() {
Message.warning('请先选择一张图像') Message.warning('请先选择一张图像')
return return
} }
// 切换到手动标注模式 // 切换到手动标注模式
isManualAnnotationMode.value = true isManualAnnotationMode.value = true
// 加载对应的缺陷列表 // 加载对应的缺陷列表
loadDefectList() loadDefectList()
Message.info('已切换到手动标注模式') Message.info('已切换到手动标注模式')
} }
@ -428,14 +446,14 @@ export function useIndustrialImage() {
Message.warning('请先选择一个项目、机组或部件') Message.warning('请先选择一个项目、机组或部件')
return return
} }
// 设置报告单位数据 // 设置报告单位数据
reportUnitData.value = { reportUnitData.value = {
id: selectedNode.id, id: selectedNode.id,
name: selectedNode.name, name: selectedNode.name,
type: selectedNode.type, type: selectedNode.type
} }
// 显示报告生成对话框 // 显示报告生成对话框
reportGenerationVisible.value = true reportGenerationVisible.value = true
} }
@ -458,7 +476,7 @@ export function useIndustrialImage() {
// 向导导入成功处理 // 向导导入成功处理
const handleWizardImportSuccess = async (data: any) => { const handleWizardImportSuccess = async (data: any) => {
console.log('向导导入成功:', data) console.log('向导导入成功:', data)
try { try {
// 构建上传参数 // 构建上传参数
const uploadParams = { const uploadParams = {
@ -471,22 +489,22 @@ export function useIndustrialImage() {
temperatureMax: data.imageInfo.maxTemperature, temperatureMax: data.imageInfo.maxTemperature,
temperatureMin: data.imageInfo.minTemperature, temperatureMin: data.imageInfo.minTemperature,
weather: data.imageInfo.weather, weather: data.imageInfo.weather,
windLevel: data.imageInfo.windPower, windLevel: data.imageInfo.windPower
} }
// 使用新的API接口上传图像 // 使用新的API接口上传图像
const response = await uploadImageToPartV2( const response = await uploadImageToPartV2(
'default', // 图像源 'default', // 图像源
data.part.partId, // 部件ID data.part.partId, // 部件ID
data.images, // 文件列表 data.images, // 文件列表
uploadParams, uploadParams
) )
console.log('批量上传响应:', response) console.log('批量上传响应:', response)
// 重新加载图像列表 // 重新加载图像列表
loadImageList() loadImageList()
Message.success(`成功导入${data.images.length}张图像到${data.part.name}`) Message.success(`成功导入${data.images.length}张图像到${data.part.name}`)
} catch (error) { } catch (error) {
console.error('批量上传失败:', error) console.error('批量上传失败:', error)
@ -511,14 +529,14 @@ export function useIndustrialImage() {
if (selectedKeys.value && selectedKeys.value.length > 0) { if (selectedKeys.value && selectedKeys.value.length > 0) {
const selectedNodeId = selectedKeys.value[0] const selectedNodeId = selectedKeys.value[0]
const selectedNode = findSelectedNode(projectTreeData.value, selectedNodeId) const selectedNode = findSelectedNode(projectTreeData.value, selectedNodeId)
if (!selectedNode) { if (!selectedNode) {
console.warn('找不到选中的节点:', selectedNodeId) console.warn('找不到选中的节点:', selectedNodeId)
} else if (selectedNode.type === 'turbine') { } else if (selectedNode.type === 'turbine') {
params.turbineId = selectedNode.id params.turbineId = selectedNode.id
} else if (selectedNode.type === 'part') { } else if (selectedNode.type === 'part') {
params.partId = selectedNode.id params.partId = selectedNode.id
// 添加机组ID // 添加机组ID
const turbineNode = findParentNode(projectTreeData.value, selectedNode.id) const turbineNode = findParentNode(projectTreeData.value, selectedNode.id)
if (turbineNode) { if (turbineNode) {
@ -529,30 +547,30 @@ export function useIndustrialImage() {
console.log('加载缺陷列表参数:', params) console.log('加载缺陷列表参数:', params)
const response = await getDefectList(params) const response = await getDefectList(params)
if (response.data && response.data.code === 0) { if (response.data && response.data.code === 0) {
// 检查返回数据结构 // 检查返回数据结构
const resultData = response.data.data const resultData = response.data.data
// 将结果转换为DefectInfo[]类型 // 将结果转换为DefectInfo[]类型
let defects: DefectInfo[] = [] let defects: DefectInfo[] = []
if (resultData) { if (resultData) {
if ('list' in resultData && Array.isArray(resultData.list)) { if ('list' in resultData && Array.isArray(resultData.list)) {
// 分页结构 // 分页结构
defects = resultData.list.map((item: any) => ({ defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item, ...item
})) }))
} else if (Array.isArray(resultData)) { } else if (Array.isArray(resultData)) {
// 直接数组结构 // 直接数组结构
defects = resultData.map((item: any) => ({ defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item, ...item
})) }))
} }
} }
console.log('处理后的缺陷列表数据:', defects) console.log('处理后的缺陷列表数据:', defects)
defectList.value = defects defectList.value = defects
} else { } else {
@ -572,7 +590,7 @@ export function useIndustrialImage() {
selectedDefectAnnotations.value = [] selectedDefectAnnotations.value = []
return return
} }
try { try {
// 尝试解析labelInfo // 尝试解析labelInfo
const labelInfo = JSON.parse(selectedDefect.value.labelInfo) const labelInfo = JSON.parse(selectedDefect.value.labelInfo)
@ -586,7 +604,7 @@ export function useIndustrialImage() {
selectedDefectAnnotations.value = [] selectedDefectAnnotations.value = []
} }
} }
// 选择缺陷 // 选择缺陷
const handleDefectSelect = (defect: DefectInfo) => { const handleDefectSelect = (defect: DefectInfo) => {
selectedDefect.value = defect selectedDefect.value = defect
@ -611,7 +629,7 @@ export function useIndustrialImage() {
repairIdea: defectForm.repairIdea, repairIdea: defectForm.repairIdea,
imageId: selectedImage.value?.imageId || '', imageId: selectedImage.value?.imageId || '',
detectionDate: new Date().toISOString(), detectionDate: new Date().toISOString(),
source: 'manual', source: 'manual'
} }
// 根据节点类型设置相关ID // 根据节点类型设置相关ID
@ -625,7 +643,7 @@ export function useIndustrialImage() {
} }
const response = await addDefect(defectData) const response = await addDefect(defectData)
if (response.data.success) { if (response.data.success) {
Message.success('缺陷添加成功') Message.success('缺陷添加成功')
loadDefectList() // 重新加载缺陷列表 loadDefectList() // 重新加载缺陷列表
@ -641,7 +659,7 @@ export function useIndustrialImage() {
const handleEditDefect = async (defect: DefectDetectionResult) => { const handleEditDefect = async (defect: DefectDetectionResult) => {
try { try {
const response = await updateDefect(defect.defectId, defect) const response = await updateDefect(defect.defectId, defect)
if (response.data.success) { if (response.data.success) {
Message.success('缺陷更新成功') Message.success('缺陷更新成功')
loadDefectList() // 重新加载缺陷列表 loadDefectList() // 重新加载缺陷列表
@ -657,7 +675,7 @@ export function useIndustrialImage() {
const handleDeleteDefectById = async (defectId: string) => { const handleDeleteDefectById = async (defectId: string) => {
try { try {
const response = await deleteDefect(defectId) const response = await deleteDefect(defectId)
if (response.data.success) { if (response.data.success) {
Message.success('缺陷删除成功') Message.success('缺陷删除成功')
loadDefectList() // 重新加载缺陷列表 loadDefectList() // 重新加载缺陷列表
@ -678,19 +696,19 @@ export function useIndustrialImage() {
Message.error('请先选择一张图像') Message.error('请先选择一张图像')
return return
} }
// 获取有效的图像ID // 获取有效的图像ID
const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id
if (!imageId) { if (!imageId) {
console.error('无法获取图像ID选中的图像数据:', selectedImage.value) console.error('无法获取图像ID选中的图像数据:', selectedImage.value)
Message.error('无法获取图像ID请重新选择图像') Message.error('无法获取图像ID请重新选择图像')
return return
} }
console.log('添加标注 - 图像ID:', imageId) console.log('添加标注 - 图像ID:', imageId)
console.log('添加标注 - 标注数据:', annotation) console.log('添加标注 - 标注数据:', annotation)
// 构造符合API要求的缺陷数据 // 构造符合API要求的缺陷数据
const defectData: ManualDefectAddRequest = { const defectData: ManualDefectAddRequest = {
attachId: selectedImage.value?.attachId || '', attachId: selectedImage.value?.attachId || '',
@ -705,31 +723,29 @@ export function useIndustrialImage() {
detectionDate: new Date().toISOString(), detectionDate: new Date().toISOString(),
labelInfo: JSON.stringify(annotation), labelInfo: JSON.stringify(annotation),
markInfo: { markInfo: {
bbox: annotation.type === 'rectangle' bbox: annotation.type === 'rectangle' ? [
? [ Math.min(annotation.points[0].x, annotation.points[1].x),
Math.min(annotation.points[0].x, annotation.points[1].x), Math.min(annotation.points[0].y, annotation.points[1].y),
Math.min(annotation.points[0].y, annotation.points[1].y), Math.abs(annotation.points[1].x - annotation.points[0].x),
Math.abs(annotation.points[1].x - annotation.points[0].x), Math.abs(annotation.points[1].y - annotation.points[0].y)
Math.abs(annotation.points[1].y - annotation.points[0].y), ] : [],
]
: [],
clsId: 1, clsId: 1,
confidence: 1.0, confidence: 1.0,
label: annotation.defectType || '手动标注', label: annotation.defectType || '手动标注'
}, },
repairIdea: '', repairIdea: '',
repairStatus: 'PENDING', repairStatus: 'PENDING',
source: 'MANUAL', source: 'MANUAL'
} }
console.log('发送给后端的缺陷数据:', defectData) console.log('发送给后端的缺陷数据:', defectData)
// 调用API添加缺陷传递两个参数缺陷数据和图像ID // 调用API添加缺陷传递两个参数缺陷数据和图像ID
await addManualDefect(defectData, imageId) await addManualDefect(defectData, imageId)
// 重新加载缺陷列表 // 重新加载缺陷列表
await loadDefectList() await loadDefectList()
Message.success('标注添加成功') Message.success('标注添加成功')
} catch (error) { } catch (error) {
console.error('添加标注失败:', error) console.error('添加标注失败:', error)
@ -764,10 +780,10 @@ export function useIndustrialImage() {
const handleReportGenerated = async (reportData: any) => { const handleReportGenerated = async (reportData: any) => {
try { try {
console.log('生成报告数据:', reportData) console.log('生成报告数据:', reportData)
// 这里可以调用后端API生成报告 // 这里可以调用后端API生成报告
// await generateReport(reportData) // await generateReport(reportData)
Message.success('报告生成成功') Message.success('报告生成成功')
reportGenerationVisible.value = false reportGenerationVisible.value = false
} catch (error) { } catch (error) {
@ -802,20 +818,20 @@ export function useIndustrialImage() {
try { try {
isRecognizing.value = true isRecognizing.value = true
// 调用真实的缺陷检测API // 调用真实的缺陷检测API
const detectParams: DefectDetectionRequest = { const detectParams: DefectDetectionRequest = {
confThreshold: settings.confidence / 100, // 转换为0-1的置信度 confThreshold: settings.confidence / 100, // 转换为0-1的置信度
defectTypeList: settings.defectTypes, defectTypeList: settings.defectTypes,
imageId: selectedImage.value.imageId, imageId: selectedImage.value.imageId,
modelId: settings.algorithm, // 使用算法作为模型ID modelId: settings.algorithm // 使用算法作为模型ID
} }
const response = await detectDefects(detectParams) const response = await detectDefects(detectParams)
// 检查返回结构适配API实际返回的数据格式 // 检查返回结构适配API实际返回的数据格式
let detectedDefects: DefectDetectionResult[] = [] let detectedDefects: DefectDetectionResult[] = []
if (response && response.data) { if (response && response.data) {
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
// 直接使用返回的数组 // 直接使用返回的数组
@ -825,28 +841,29 @@ export function useIndustrialImage() {
detectedDefects = response.data.data detectedDefects = response.data.data
} }
} }
if (detectedDefects.length > 0) { if (detectedDefects.length > 0) {
recognitionResults.value = detectedDefects recognitionResults.value = detectedDefects
// 检查是否有attachPath如果有则更新当前显示的图片路径 // 检查是否有attachPath如果有则更新当前显示的图片路径
const firstResult = detectedDefects[0] const firstResult = detectedDefects[0]
if (firstResult.attachPath && firstResult.attachPath !== selectedImage.value.imagePath) { if (firstResult.attachPath && firstResult.attachPath !== selectedImage.value.imagePath) {
// 更新当前选中图像的路径为识别后返回的attachPath // 更新当前选中图像的路径为识别后返回的attachPath
selectedImage.value = { selectedImage.value = {
...selectedImage.value, ...selectedImage.value,
imagePath: firstResult.attachPath, imagePath: firstResult.attachPath
} }
console.log('图片路径已更新为识别后的路径:', firstResult.attachPath) console.log('图片路径已更新为识别后的路径:', firstResult.attachPath)
} }
Message.success(`识别完成,发现${detectedDefects.length}个缺陷`) Message.success(`识别完成,发现${detectedDefects.length}个缺陷`)
} else { } else {
recognitionResults.value = [] recognitionResults.value = []
Message.info('未发现缺陷') Message.info('未发现缺陷')
} }
// 不再需要类型映射因为RecognitionResults组件已经处理了自定义类型 // 不再需要类型映射因为RecognitionResults组件已经处理了自定义类型
} catch (error) { } catch (error) {
console.error('识别失败:', error) console.error('识别失败:', error)
Message.error('识别失败') Message.error('识别失败')
@ -879,7 +896,7 @@ export function useIndustrialImage() {
try { try {
console.log('开始保存识别结果:', results) console.log('开始保存识别结果:', results)
let successCount = 0 let successCount = 0
let failCount = 0 let failCount = 0
const errors: string[] = [] const errors: string[] = []
@ -887,7 +904,7 @@ export function useIndustrialImage() {
// 为每个识别结果创建缺陷记录 // 为每个识别结果创建缺陷记录
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
const result = results[i] const result = results[i]
try { try {
// 构建缺陷数据,转换识别结果为手动添加缺陷的格式 // 构建缺陷数据,转换识别结果为手动添加缺陷的格式
const defectData: ManualDefectAddRequest = { const defectData: ManualDefectAddRequest = {
@ -907,16 +924,16 @@ export function useIndustrialImage() {
bbox: [0, 0, 0, 0], bbox: [0, 0, 0, 0],
clsId: 0, clsId: 0,
confidence: 0.8, confidence: 0.8,
label: result.defectType || '自动识别', label: result.defectType || '自动识别'
}, },
repairIdea: result.repairIdea || '建议进一步检查确认', repairIdea: result.repairIdea || '建议进一步检查确认',
repairStatus: result.repairStatus || 'pending', repairStatus: result.repairStatus || 'pending',
source: 'auto', // 标记为自动识别来源 source: 'auto' // 标记为自动识别来源
} }
// 调用手动添加缺陷接口 // 调用手动添加缺陷接口
const response = await addManualDefect(defectData, selectedImage.value.imageId) const response = await addManualDefect(defectData,selectedImage.value.imageId)
if (response.data.success) { if (response.data.success) {
successCount++ successCount++
console.log(`缺陷 ${i + 1} 保存成功:`, response.data.data) console.log(`缺陷 ${i + 1} 保存成功:`, response.data.data)
@ -950,6 +967,7 @@ export function useIndustrialImage() {
if (successCount > 0) { if (successCount > 0) {
loadDefectList() loadDefectList()
} }
} catch (error) { } catch (error) {
console.error('保存识别结果失败:', error) console.error('保存识别结果失败:', error)
Message.error('保存识别结果失败') Message.error('保存识别结果失败')
@ -987,27 +1005,27 @@ export function useIndustrialImage() {
annotationModalVisible, annotationModalVisible,
loading, loading,
currentImageId, currentImageId,
// 向导相关状态 // 向导相关状态
importWizardVisible, importWizardVisible,
selectedTurbineId, selectedTurbineId,
currentTurbineParts, currentTurbineParts,
// 自动识别相关状态 // 自动识别相关状态
isAutoRecognitionMode, isAutoRecognitionMode,
recognitionResults, recognitionResults,
isRecognizing, isRecognizing,
// 手动标注相关状态 // 手动标注相关状态
isManualAnnotationMode, isManualAnnotationMode,
defectList, defectList,
selectedDefect, selectedDefect,
selectedDefectAnnotations, selectedDefectAnnotations,
// 报告生成相关状态 // 报告生成相关状态
reportGenerationVisible, reportGenerationVisible,
reportUnitData, reportUnitData,
// 方法 // 方法
loadProjectTree, loadProjectTree,
handleLoadMore, handleLoadMore,
@ -1028,14 +1046,14 @@ export function useIndustrialImage() {
handleAnnotationSaved, handleAnnotationSaved,
handleProcessSuccess, handleProcessSuccess,
handleWizardImportSuccess, handleWizardImportSuccess,
// 自动识别相关方法 // 自动识别相关方法
handleCloseAutoRecognition, handleCloseAutoRecognition,
handleStartRecognition, handleStartRecognition,
handleRecognitionResultSelect, handleRecognitionResultSelect,
handleSaveRecognitionResults, handleSaveRecognitionResults,
handleExportRecognitionResults, handleExportRecognitionResults,
// 手动标注相关方法 // 手动标注相关方法
handleCloseManualAnnotation, handleCloseManualAnnotation,
loadDefectList, loadDefectList,
@ -1043,15 +1061,15 @@ export function useIndustrialImage() {
handleAddDefect, handleAddDefect,
handleEditDefect, handleEditDefect,
handleDeleteDefectById, handleDeleteDefectById,
// Canvas 标注相关方法 // Canvas 标注相关方法
handleAnnotationAdd, handleAnnotationAdd,
handleAnnotationUpdate, handleAnnotationUpdate,
handleAnnotationDelete, handleAnnotationDelete,
// 报告生成相关方法 // 报告生成相关方法
handleReportGenerated, handleReportGenerated,
init, init
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="industrial-image-container"> <div class="industrial-image-container">
<!-- 头部按钮栏 --> <!-- 头部按钮栏 -->
<HeaderToolbar <HeaderToolbar
:current-image-id="currentImageId" :current-image-id="currentImageId"
@start="handleStart" @start="handleStart"
@auto-annotate="handleAutoAnnotate" @auto-annotate="handleAutoAnnotate"
@ -12,13 +12,11 @@
<!-- 主体内容区域 --> <!-- 主体内容区域 -->
<div class="main-content"> <div class="main-content">
<!-- 上部区域 --> <!-- 上部区域 -->
<div <div class="top-section" :class="{
class="top-section" :class="{ 'auto-recognition-layout': isAutoRecognitionMode,
'auto-recognition-layout': isAutoRecognitionMode, 'manual-annotation-layout': isManualAnnotationMode,
'manual-annotation-layout': isManualAnnotationMode, 'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode
'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode, }">
}"
>
<!-- 自动识别模式三列布局 --> <!-- 自动识别模式三列布局 -->
<template v-if="isAutoRecognitionMode"> <template v-if="isAutoRecognitionMode">
<!-- 左侧自动识别设置面板 --> <!-- 左侧自动识别设置面板 -->
@ -29,17 +27,17 @@
@start-recognition="handleStartRecognition" @start-recognition="handleStartRecognition"
/> />
</div> </div>
<!-- 中间图像显示区域 --> <!-- 中间图像显示区域 -->
<div class="auto-center-panel"> <div class="auto-center-panel">
<ImagePreview <ImagePreview
:selected-image="selectedImage" :selected-image="selectedImage"
/> />
</div> </div>
<!-- 右侧识别结果区域 --> <!-- 右侧识别结果区域 -->
<div class="auto-right-panel"> <div class="auto-right-panel">
<RecognitionResults <RecognitionResults
:results="recognitionResults" :results="recognitionResults"
:is-processing="isRecognizing" :is-processing="isRecognizing"
@result-select="handleRecognitionResultSelect" @result-select="handleRecognitionResultSelect"
@ -48,7 +46,7 @@
/> />
</div> </div>
</template> </template>
<!-- 手动标注模式三列布局 --> <!-- 手动标注模式三列布局 -->
<template v-else-if="isManualAnnotationMode"> <template v-else-if="isManualAnnotationMode">
<!-- 左侧缺陷列表面板 --> <!-- 左侧缺陷列表面板 -->
@ -66,10 +64,10 @@
@close="handleCloseManualAnnotation" @close="handleCloseManualAnnotation"
/> />
</div> </div>
<!-- 中间图像标注区域 --> <!-- 中间图像标注区域 -->
<div class="manual-center-panel"> <div class="manual-center-panel">
<ImageCanvas <ImageCanvas
:selected-image="selectedImage" :selected-image="selectedImage"
:annotations="selectedDefectAnnotations" :annotations="selectedDefectAnnotations"
@annotation-finish="handleAnnotationFinish" @annotation-finish="handleAnnotationFinish"
@ -77,7 +75,7 @@
@annotation-delete="handleAnnotationDelete" @annotation-delete="handleAnnotationDelete"
/> />
</div> </div>
<!-- 右侧缺陷详情编辑区域 --> <!-- 右侧缺陷详情编辑区域 -->
<div class="manual-right-panel"> <div class="manual-right-panel">
<DefectDetailsForm <DefectDetailsForm
@ -87,18 +85,18 @@
@submit="handleDefectFormSubmit" @submit="handleDefectFormSubmit"
/> />
<div v-else class="no-annotation-prompt"> <div v-else class="no-annotation-prompt">
<IconBug class="prompt-icon" /> <icon-bug class="prompt-icon" />
<p>请在图像上绘制矩形标注缺陷</p> <p>请在图像上绘制矩形标注缺陷</p>
</div> </div>
</div> </div>
</template> </template>
<!-- 普通模式双列布局 --> <!-- 普通模式双列布局 -->
<template v-else> <template v-else>
<!-- 左侧面板 --> <!-- 左侧面板 -->
<div class="left-panel"> <div class="left-panel">
<!-- 项目管理 --> <!-- 项目管理 -->
<ProjectTree <ProjectTree
v-if="!isManualAnnotationMode" v-if="!isManualAnnotationMode"
:project-tree-data="projectTreeData" :project-tree-data="projectTreeData"
:selected-keys="selectedKeys" :selected-keys="selectedKeys"
@ -106,7 +104,7 @@
@load-more="handleLoadMore" @load-more="handleLoadMore"
/> />
<!-- 手动标注 --> <!-- 手动标注 -->
<ManualAnnotation <ManualAnnotation
v-if="isManualAnnotationMode" v-if="isManualAnnotationMode"
:tree-data="projectTreeData" :tree-data="projectTreeData"
:selected-keys="selectedKeys" :selected-keys="selectedKeys"
@ -123,7 +121,7 @@
<!-- 右侧面板 --> <!-- 右侧面板 -->
<div class="right-panel"> <div class="right-panel">
<!-- 图像预览区域 --> <!-- 图像预览区域 -->
<ImagePreview <ImagePreview
:selected-image="selectedImage" :selected-image="selectedImage"
/> />
</div> </div>
@ -131,8 +129,8 @@
</div> </div>
<!-- 下部工业图像列表区域 - 仅在非自动识别和非手动标注模式下显示 --> <!-- 下部工业图像列表区域 - 仅在非自动识别和非手动标注模式下显示 -->
<div v-if="!isAutoRecognitionMode && !isManualAnnotationMode" class="bottom-section" :class="{ collapsed: isImageListCollapsed }"> <div v-if="!isAutoRecognitionMode && !isManualAnnotationMode" class="bottom-section" :class="{ 'collapsed': isImageListCollapsed }">
<IndustrialImageList <IndustrialImageList
:image-list="imageList" :image-list="imageList"
:selected-image-id="selectedImageId" :selected-image-id="selectedImageId"
@import-images="handleImportImages" @import-images="handleImportImages"
@ -148,23 +146,25 @@
<!-- 模态框 --> <!-- 模态框 -->
<ImageModals <ImageModals
:preview-modal-visible="previewModalVisible" :preview-modal-visible="previewModalVisible"
:preview-image="previewImage" :preview-image="previewImage"
:process-image="processImage" :process-image="processImage"
:selected-image="selectedImage" :selected-image="selectedImage"
@update:preview-modal-visible="previewModalVisible = $event" @update:preview-modal-visible="previewModalVisible = $event"
@save="handleSaveImageSuccess" @save="handleSaveImageSuccess"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { onMounted, ref, computed } from 'vue'
import { IconBug } from '@arco-design/web-vue/es/icon' import { IconBug } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import HeaderToolbar from './components/HeaderToolbar.vue' import HeaderToolbar from './components/HeaderToolbar.vue'
import ProjectTree from './components/ProjectTree.vue' import ProjectTree from './components/ProjectTree.vue'
import ImagePreview from './components/ImagePreview.vue' import ImagePreview from './components/ImagePreview.vue'
import ImageCanvas from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
import IndustrialImageList from '@/components/IndustrialImageList/index.vue'
import ImageModals from './components/ImageModals.vue' import ImageModals from './components/ImageModals.vue'
import RecognitionResults from './components/RecognitionResults.vue' import RecognitionResults from './components/RecognitionResults.vue'
import DefectListPanel from './components/DefectListPanel.vue' import DefectListPanel from './components/DefectListPanel.vue'
@ -172,10 +172,9 @@ import DefectDetailsForm from './components/DefectDetailsForm.vue'
import AutoRecognitionSettings from './components/AutoRecognitionSettings.vue' import AutoRecognitionSettings from './components/AutoRecognitionSettings.vue'
import { useIndustrialImage } from './hooks/useIndustrialImage' import { useIndustrialImage } from './hooks/useIndustrialImage'
import type { TreeNode } from './components/DefectListPanel.vue' import type { TreeNode } from './components/DefectListPanel.vue'
import IndustrialImageList from '@/components/IndustrialImageList/index.vue'
import ImageCanvas from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue' import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
import type { DefectInfo, type DefectLevelType, type DefectType, ManualDefectAddRequest, addManualDefect, getDefectLevels, getDefectList, getDefectTypes, uploadAnnotatedImage } from '@/apis/industrial-image/defect' import type { ManualDefectAddRequest, DefectInfo, AttachInfoData } from '@/apis/industrial-image/defect'
import { addManualDefect, getDefectList, uploadAnnotatedImage, getDefectTypes, getDefectLevels, type DefectType, type DefectLevelType } from '@/apis/industrial-image/defect'
defineOptions({ name: 'IndustrialImageProcessing' }) defineOptions({ name: 'IndustrialImageProcessing' })
@ -252,27 +251,27 @@ const {
annotationModalVisible, annotationModalVisible,
loading, loading,
currentImageId, currentImageId,
// //
importWizardVisible, importWizardVisible,
selectedTurbineId, selectedTurbineId,
currentTurbineParts, currentTurbineParts,
// //
isAutoRecognitionMode, isAutoRecognitionMode,
recognitionResults, recognitionResults,
isRecognizing, isRecognizing,
// //
isManualAnnotationMode, isManualAnnotationMode,
defectList, defectList,
selectedDefect, selectedDefect,
selectedDefectAnnotations, selectedDefectAnnotations,
// //
reportGenerationVisible, reportGenerationVisible,
reportUnitData, reportUnitData,
// //
loadProjectTree, loadProjectTree,
handleLoadMore, handleLoadMore,
@ -292,36 +291,36 @@ const {
handleAnnotationSaved, handleAnnotationSaved,
handleProcessSuccess, handleProcessSuccess,
handleWizardImportSuccess, handleWizardImportSuccess,
// //
handleCloseAutoRecognition, handleCloseAutoRecognition,
handleStartRecognition, handleStartRecognition,
handleRecognitionResultSelect, handleRecognitionResultSelect,
handleSaveRecognitionResults, handleSaveRecognitionResults,
handleExportRecognitionResults, handleExportRecognitionResults,
// //
handleCloseManualAnnotation, handleCloseManualAnnotation,
handleDefectSelect, handleDefectSelect,
handleAddDefect, handleAddDefect,
handleEditDefect, handleEditDefect,
handleDeleteDefectById, handleDeleteDefectById,
// Canvas // Canvas
handleAnnotationAdd, handleAnnotationAdd,
handleAnnotationUpdate, handleAnnotationUpdate,
handleAnnotationDelete, handleAnnotationDelete,
// //
handleReportGenerated, handleReportGenerated,
init, init
} = useIndustrialImage() } = useIndustrialImage()
// //
const treeData = computed(() => { const treeData = computed(() => {
console.log('计算treeData, projectTreeData:', projectTreeData.value) console.log('计算treeData, projectTreeData:', projectTreeData.value)
if (!projectTreeData.value || !Array.isArray(projectTreeData.value)) { if (!projectTreeData.value || !Array.isArray(projectTreeData.value)) {
console.log('projectTreeData为空或不是数组返回测试数据') console.log('projectTreeData为空或不是数组返回测试数据')
// //
@ -342,60 +341,60 @@ const treeData = computed(() => {
key: 'test-part-1', key: 'test-part-1',
title: '测试部件1', title: '测试部件1',
type: 'part', type: 'part',
defectCount: 0, defectCount: 0
}, }
], ]
}, }
], ]
}, }
] as TreeNode[] ] as TreeNode[]
} }
const result = projectTreeData.value.map((project) => { const result = projectTreeData.value.map(project => {
if (!project) { if (!project) {
console.warn('发现空的project对象') console.warn('发现空的project对象')
return null return null
} }
const projectNode = { const projectNode = {
key: project.id || `project-${Math.random().toString(36).substr(2, 9)}`, key: project.id || `project-${Math.random().toString(36).substr(2, 9)}`,
title: project.name || '未命名项目', title: project.name || '未命名项目',
type: 'project' as const, type: 'project' as const,
defectCount: 0, defectCount: 0,
children: project.children?.map((turbine) => { children: project.children?.map(turbine => {
if (!turbine) { if (!turbine) {
console.warn('发现空的turbine对象') console.warn('发现空的turbine对象')
return null return null
} }
const turbineNode = { const turbineNode = {
key: turbine.id || `turbine-${Math.random().toString(36).substr(2, 9)}`, key: turbine.id || `turbine-${Math.random().toString(36).substr(2, 9)}`,
title: turbine.name || '未命名机组', title: turbine.name || '未命名机组',
type: 'turbine' as const, type: 'turbine' as const,
defectCount: 0, defectCount: 0,
children: turbine.children?.map((part) => { children: turbine.children?.map(part => {
if (!part) { if (!part) {
console.warn('发现空的part对象') console.warn('发现空的part对象')
return null return null
} }
return { return {
key: part.id || `part-${Math.random().toString(36).substr(2, 9)}`, key: part.id || `part-${Math.random().toString(36).substr(2, 9)}`,
title: part.name || '未命名部件', title: part.name || '未命名部件',
type: 'part' as const, type: 'part' as const,
defectCount: 0, defectCount: 0
} }
}).filter(Boolean) || [], }).filter(Boolean) || []
} }
return turbineNode return turbineNode
}).filter(Boolean) || [], }).filter(Boolean) || []
} }
console.log('创建的project节点:', projectNode) console.log('创建的project节点:', projectNode)
return projectNode return projectNode
}).filter((item) => item !== null) as TreeNode[] }).filter(item => item !== null) as TreeNode[]
console.log('最终的treeData:', result) console.log('最终的treeData:', result)
return result return result
}) })
@ -413,19 +412,19 @@ const handleAnnotationFinish = async (annotations: Annotation[], imageBlob: Blob
Message.error('请先选择一张图像') Message.error('请先选择一张图像')
return return
} }
// //
const fileName = `annotated_${selectedImage.value?.imageName || 'image'}_${Date.now()}.png` const fileName = `annotated_${selectedImage.value?.imageName || 'image'}_${Date.now()}.png`
const uploadResponse = await uploadAnnotatedImage(imageBlob, fileName) const uploadResponse = await uploadAnnotatedImage(imageBlob, fileName)
if (!uploadResponse.data) { if ( !uploadResponse.data) {
Message.error('上传标注图片失败') Message.error('上传标注图片失败')
return return
} }
const attachInfo = uploadResponse.data const attachInfo = uploadResponse.data
console.log('上传成功,附件信息:', attachInfo) console.log('上传成功,附件信息:', attachInfo)
// //
const combinedAnnotation: Annotation = { const combinedAnnotation: Annotation = {
id: `multi-annotation-${Date.now()}`, id: `multi-annotation-${Date.now()}`,
@ -438,13 +437,14 @@ const handleAnnotationFinish = async (annotations: Annotation[], imageBlob: Blob
attachId: attachInfo, attachId: attachInfo,
// attachPath: attachInfo.attachPath, // attachPath: attachInfo.attachPath,
// attachInfo: attachInfo, // attachInfo: attachInfo,
allAnnotations: annotations, allAnnotations: annotations
}, }
} }
// //
currentAnnotation.value = combinedAnnotation currentAnnotation.value = combinedAnnotation
Message.success(`成功绘制${annotations.length}个区域,请填写缺陷详情`) Message.success(`成功绘制${annotations.length}个区域,请填写缺陷详情`)
} catch (error) { } catch (error) {
console.error('处理标注完成失败:', error) console.error('处理标注完成失败:', error)
Message.error('处理标注失败') Message.error('处理标注失败')
@ -463,25 +463,25 @@ const handleAddDefectFromPanel = () => {
Message.warning('请先选择一个项目节点') Message.warning('请先选择一个项目节点')
return return
} }
// //
if (!selectedImage.value) { if (!selectedImage.value) {
Message.warning('请先选择一张图像') Message.warning('请先选择一张图像')
return return
} }
// //
const virtualAnnotation: Annotation = { const virtualAnnotation: Annotation = {
id: `virtual-${Date.now()}`, id: `virtual-${Date.now()}`,
type: 'rectangle', type: 'rectangle',
points: [ points: [
{ x: 100, y: 100 }, { x: 100, y: 100 },
{ x: 200, y: 200 }, { x: 200, y: 200 }
], ],
color: '#ff4d4f', color: '#ff4d4f',
label: '手动添加的缺陷', label: '手动添加的缺陷'
} }
// //
currentAnnotation.value = virtualAnnotation currentAnnotation.value = virtualAnnotation
Message.info('请在右侧表单中填写缺陷详情') Message.info('请在右侧表单中填写缺陷详情')
@ -491,35 +491,35 @@ const handleAddDefectFromPanel = () => {
const handleTurbineSelect = async (turbineId: string) => { const handleTurbineSelect = async (turbineId: string) => {
try { try {
console.log('选中机组:', turbineId, '开始查询缺陷列表') console.log('选中机组:', turbineId, '开始查询缺陷列表')
// API使loadDefectList // API使loadDefectList
const response = await getDefectList({ turbineId }) const response = await getDefectList({ turbineId })
console.log('API响应:', response) console.log('API响应:', response)
if (response.data && response.data.code === 0 && response.data.data) { if (response.data && response.data.code === 0 && response.data.data) {
// //
const resultData = response.data.data const resultData = response.data.data
// DefectInfo[] // DefectInfo[]
let defects: DefectInfo[] = [] let defects: DefectInfo[] = []
if ('list' in resultData && Array.isArray(resultData.list)) { if ('list' in resultData && Array.isArray(resultData.list)) {
// //
defects = resultData.list.map((item: any) => ({ defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
turbineId, turbineId: turbineId,
...item, ...item
})) }))
} else if (Array.isArray(resultData)) { } else if (Array.isArray(resultData)) {
// //
defects = resultData.map((item: any) => ({ defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
turbineId, turbineId: turbineId,
...item, ...item
})) }))
} }
// //
defectList.value = defects defectList.value = defects
console.log('查询到缺陷列表:', defects.length, '条记录') console.log('查询到缺陷列表:', defects.length, '条记录')
@ -542,39 +542,39 @@ const loadDefectList = async () => {
console.warn('未选择图像,无法加载缺陷列表') console.warn('未选择图像,无法加载缺陷列表')
return return
} }
// ID // ID
const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id
if (!imageId) { if (!imageId) {
console.error('无法获取图像ID选中的图像数据:', selectedImage.value) console.error('无法获取图像ID选中的图像数据:', selectedImage.value)
return return
} }
// API // API
const response = await getDefectList({ imageId }) const response = await getDefectList({ imageId })
if (response.data && response.data.code === 0 && response.data.data) { if (response.data && response.data.code === 0 && response.data.data) {
// //
const resultData = response.data.data const resultData = response.data.data
// DefectInfo[] // DefectInfo[]
let defects: DefectInfo[] = [] let defects: DefectInfo[] = []
if ('list' in resultData && Array.isArray(resultData.list)) { if ('list' in resultData && Array.isArray(resultData.list)) {
// //
defects = resultData.list.map((item: any) => ({ defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item, ...item
})) }))
} else if (Array.isArray(resultData)) { } else if (Array.isArray(resultData)) {
// //
defects = resultData.map((item: any) => ({ defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item, ...item
})) }))
} }
// //
defectList.value = defects defectList.value = defects
} else { } else {
@ -596,24 +596,24 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
Message.error('请先选择一张图像') Message.error('请先选择一张图像')
return return
} }
// ID // ID
const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id const imageId = selectedImageId.value || selectedImage.value?.imageId || selectedImage.value?.id
if (!imageId) { if (!imageId) {
console.error('无法获取图像ID选中的图像数据:', selectedImage.value) console.error('无法获取图像ID选中的图像数据:', selectedImage.value)
Message.error('无法获取图像ID请重新选择图像') Message.error('无法获取图像ID请重新选择图像')
return return
} }
// //
const isMultiAnnotation = annotation.metadata?.isMultiAnnotation const isMultiAnnotation = annotation.metadata?.isMultiAnnotation
const attachId = annotation.metadata?.attachId || '' const attachId = annotation.metadata?.attachId || ''
// const attachPath = annotation.metadata?.attachPath || '' // const attachPath = annotation.metadata?.attachPath || ''
// API // API
const defectData = { const defectData = {
attachId, attachId: attachId,
attachPath: '', attachPath: '',
axial: formData.axialDimension, axial: formData.axialDimension,
chordwise: formData.chordDimension, chordwise: formData.chordDimension,
@ -627,62 +627,61 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
defectTypeLabel: formData.defectTypeLabel || getDefectTypeLabel(formData.defectType), defectTypeLabel: formData.defectTypeLabel || getDefectTypeLabel(formData.defectType),
description: formData.description, description: formData.description,
detectionDate: new Date().toISOString().split('T')[0], detectionDate: new Date().toISOString().split('T')[0],
imageId, imageId: imageId,
labelInfo: '', labelInfo: '',
// labelInfo: isMultiAnnotation ? // labelInfo: isMultiAnnotation ?
// JSON.stringify(annotation.metadata?.allAnnotations || []) : // JSON.stringify(annotation.metadata?.allAnnotations || []) :
// JSON.stringify(annotation), // JSON.stringify(annotation),
markInfo: { markInfo: {
bbox: isMultiAnnotation bbox: isMultiAnnotation ?
// bbox // bbox
? (annotation.metadata?.allAnnotations || []).map((ann) => [ (annotation.metadata?.allAnnotations || []).map(ann => [
Math.min(ann.points[0].x, ann.points[1].x), Math.min(ann.points[0].x, ann.points[1].x),
Math.min(ann.points[0].y, ann.points[1].y), Math.min(ann.points[0].y, ann.points[1].y),
Math.abs(ann.points[1].x - ann.points[0].x), Math.abs(ann.points[1].x - ann.points[0].x),
Math.abs(ann.points[1].y - ann.points[0].y), Math.abs(ann.points[1].y - ann.points[0].y)
]) ]) :
// //
: annotation.type === 'rectangle' annotation.type === 'rectangle' ? [
? [ Math.min(annotation.points[0].x, annotation.points[1].x),
Math.min(annotation.points[0].x, annotation.points[1].x), Math.min(annotation.points[0].y, annotation.points[1].y),
Math.min(annotation.points[0].y, annotation.points[1].y), Math.abs(annotation.points[1].x - annotation.points[0].x),
Math.abs(annotation.points[1].x - annotation.points[0].x), Math.abs(annotation.points[1].y - annotation.points[0].y)
Math.abs(annotation.points[1].y - annotation.points[0].y), ] : [],
]
: [],
clsId: 1, clsId: 1,
confidence: 1.0, confidence: 1.0,
label: formData.defectType, label: formData.defectType
}, },
repairIdea: formData.repairIdea, repairIdea: formData.repairIdea,
repairStatus: 'PENDING', repairStatus: 'PENDING',
repairStatusLabel: '待处理', repairStatusLabel: '待处理',
source: 'MANUAL', source: 'MANUAL',
sourceLabel: '手动标注', sourceLabel: '手动标注'
} }
console.log('发送给后端的缺陷数据:', defectData) console.log('发送给后端的缺陷数据:', defectData)
// API // API
await addManualDefect(defectData as ManualDefectAddRequest, imageId) await addManualDefect(defectData as ManualDefectAddRequest, imageId)
// loading // loading
Message.clear() Message.clear()
// //
await loadDefectList() await loadDefectList()
// //
currentAnnotation.value = null currentAnnotation.value = null
// //
const annotationCount = annotation.metadata?.allAnnotations?.length || 1 const annotationCount = annotation.metadata?.allAnnotations?.length || 1
Message.success({ Message.success({
content: isMultiAnnotation content: isMultiAnnotation ?
? `成功保存包含${annotationCount}个标注区域的缺陷信息!` `成功保存包含${annotationCount}个标注区域的缺陷信息!` :
: '缺陷信息保存成功!', '缺陷信息保存成功!',
duration: 3000, duration: 3000
}) })
} catch (error) { } catch (error) {
console.error('添加缺陷失败:', error) console.error('添加缺陷失败:', error)
Message.clear() Message.clear()
@ -693,10 +692,10 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
// //
const calculateBoundingBox = (annotations: Annotation[]): number[] => { const calculateBoundingBox = (annotations: Annotation[]): number[] => {
if (!annotations || annotations.length === 0) return [0, 0, 0, 0] if (!annotations || annotations.length === 0) return [0, 0, 0, 0]
let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
annotations.forEach((annotation) => { annotations.forEach(annotation => {
if (annotation.points && annotation.points.length >= 2) { if (annotation.points && annotation.points.length >= 2) {
const [p1, p2] = annotation.points const [p1, p2] = annotation.points
minX = Math.min(minX, p1.x, p2.x) minX = Math.min(minX, p1.x, p2.x)
@ -705,19 +704,19 @@ const calculateBoundingBox = (annotations: Annotation[]): number[] => {
maxY = Math.max(maxY, p1.y, p2.y) maxY = Math.max(maxY, p1.y, p2.y)
} }
}) })
return [minX, minY, maxX - minX, maxY - minY] return [minX, minY, maxX - minX, maxY - minY]
} }
// //
const getDefectLevelLabel = (levelCode: string): string => { const getDefectLevelLabel = (levelCode: string): string => {
const level = defectLevels.value.find((l) => l.code === levelCode) const level = defectLevels.value.find(l => l.code === levelCode)
return level ? level.name : levelCode return level ? level.name : levelCode
} }
// //
const getDefectTypeLabel = (typeCode: string): string => { const getDefectTypeLabel = (typeCode: string): string => {
const type = defectTypes.value.find((t) => t.code === typeCode) const type = defectTypes.value.find(t => t.code === typeCode)
return type ? type.name : typeCode return type ? type.name : typeCode
} }
@ -775,7 +774,7 @@ onMounted(() => {
.top-section.auto-recognition-layout { .top-section.auto-recognition-layout {
display: flex; display: flex;
height: 100%; /* 自动识别模式占据全部高度 */ height: 100%; /* 自动识别模式占据全部高度 */
.auto-left-panel { .auto-left-panel {
width: 280px; width: 280px;
border-right: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;
@ -785,7 +784,7 @@ onMounted(() => {
background: #f8f9fa; background: #f8f9fa;
flex-shrink: 0; flex-shrink: 0;
} }
.auto-center-panel { .auto-center-panel {
flex: 1; flex: 1;
border-right: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;
@ -795,12 +794,12 @@ onMounted(() => {
background: #ffffff; background: #ffffff;
position: relative; position: relative;
min-width: 0; /* 确保flex项目可以收缩 */ min-width: 0; /* 确保flex项目可以收缩 */
/* 让图像区域居中显示 */ /* 让图像区域居中显示 */
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.auto-right-panel { .auto-right-panel {
width: 320px; width: 320px;
overflow: hidden; overflow: hidden;
@ -815,7 +814,7 @@ onMounted(() => {
.top-section.manual-annotation-layout { .top-section.manual-annotation-layout {
display: flex; display: flex;
height: 100%; /* 手动标注模式占据全部高度 */ height: 100%; /* 手动标注模式占据全部高度 */
.manual-left-panel { .manual-left-panel {
width: 280px; width: 280px;
border-right: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;
@ -825,7 +824,7 @@ onMounted(() => {
background: #f8f9fa; background: #f8f9fa;
flex-shrink: 0; flex-shrink: 0;
} }
.manual-center-panel { .manual-center-panel {
flex: 1; flex: 1;
border-right: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb;
@ -835,12 +834,12 @@ onMounted(() => {
background: #ffffff; background: #ffffff;
position: relative; position: relative;
min-width: 0; /* 确保flex项目可以收缩 */ min-width: 0; /* 确保flex项目可以收缩 */
/* 让图像区域居中显示 */ /* 让图像区域居中显示 */
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.manual-right-panel { .manual-right-panel {
width: 320px; width: 320px;
overflow: hidden; overflow: hidden;
@ -857,7 +856,7 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
transition: flex 0.3s ease, height 0.3s ease; transition: flex 0.3s ease, height 0.3s ease;
&.collapsed { &.collapsed {
flex: 0 0 0; flex: 0 0 0;
min-height: 0; min-height: 0;
@ -876,17 +875,17 @@ onMounted(() => {
margin: 20px; margin: 20px;
border-radius: 8px; border-radius: 8px;
border: 2px dashed #d1d5db; border: 2px dashed #d1d5db;
.prompt-icon { .prompt-icon {
font-size: 48px; font-size: 48px;
margin-bottom: 16px; margin-bottom: 16px;
color: #9ca3af; color: #9ca3af;
} }
p { p {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@ -6,11 +6,11 @@
<div class="operation-bar"> <div class="operation-bar">
<a-space> <a-space>
<a-button type="primary" @click="handleAdd"> <a-button type="primary" @click="handleAdd">
<template #icon><IconPlus /></template> <template #icon><icon-plus /></template>
新建配置 新建配置
</a-button> </a-button>
<a-button @click="refreshList"> <a-button @click="refreshList">
<template #icon><IconRefresh /></template> <template #icon><icon-refresh /></template>
刷新 刷新
</a-button> </a-button>
</a-space> </a-space>
@ -31,9 +31,9 @@
:data="tableData" :data="tableData"
:loading="loading" :loading="loading"
:pagination="pagination" :pagination="pagination"
row-key="modelId"
@page-change="onPageChange" @page-change="onPageChange"
@page-size-change="onPageSizeChange" @page-size-change="onPageSizeChange"
row-key="modelId"
> >
<template #columns> <template #columns>
<!-- <a-table-column title="模型ID" data-index="modelId" /> --> <!-- <a-table-column title="模型ID" data-index="modelId" /> -->
@ -53,11 +53,11 @@
<template #cell="{ record }"> <template #cell="{ record }">
<a-space> <a-space>
<a-button type="text" size="small" @click="handleEdit(record)"> <a-button type="text" size="small" @click="handleEdit(record)">
<template #icon><IconEdit /></template> <template #icon><icon-edit /></template>
编辑 编辑
</a-button> </a-button>
<a-button type="text" size="small" @click="handleView(record)"> <a-button type="text" size="small" @click="handleView(record)">
<template #icon><IconEye /></template> <template #icon><icon-eye /></template>
查看 查看
</a-button> </a-button>
<a-popconfirm <a-popconfirm
@ -65,7 +65,7 @@
@ok="handleDelete(record)" @ok="handleDelete(record)"
> >
<a-button type="text" status="danger" size="small"> <a-button type="text" status="danger" size="small">
<template #icon><IconDelete /></template> <template #icon><icon-delete /></template>
删除 删除
</a-button> </a-button>
</a-popconfirm> </a-popconfirm>
@ -81,9 +81,9 @@
<a-modal <a-modal
v-model:visible="formVisible" v-model:visible="formVisible"
:title="isEdit ? '编辑模型配置' : '新增模型配置'" :title="isEdit ? '编辑模型配置' : '新增模型配置'"
unmount-on-close
@cancel="closeForm" @cancel="closeForm"
@before-ok="handleSubmit" @before-ok="handleSubmit"
unmount-on-close
> >
<a-form <a-form
ref="formRef" ref="formRef"
@ -125,24 +125,24 @@
:custom-request="uploadModelFile" :custom-request="uploadModelFile"
> >
<a-button type="primary"> <a-button type="primary">
<template #icon><IconUpload /></template> <template #icon><icon-upload /></template>
上传模型文件 上传模型文件
</a-button> </a-button>
</a-upload> </a-upload>
<a-button <a-button
v-if="formData.attachId" v-if="formData.attachId"
status="danger" status="danger"
@click="formData.attachId = ''" @click="formData.attachId = ''"
> >
<template #icon><IconDelete /></template> <template #icon><icon-delete /></template>
清除 清除
</a-button> </a-button>
</a-space> </a-space>
<div v-if="uploadingFile" class="upload-tip"> <div class="upload-tip" v-if="uploadingFile">
<a-spin /> 上传中...{{ uploadProgress }}% <a-spin /> 上传中...{{ uploadProgress }}%
</div> </div>
<div v-if="uploadedFileName && !uploadingFile" class="upload-tip success"> <div class="upload-tip success" v-if="uploadedFileName && !uploadingFile">
<IconCheckCircle /> 已上传: {{ uploadedFileName }} <icon-check-circle /> 已上传: {{ uploadedFileName }}
</div> </div>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -151,13 +151,13 @@
:validate-trigger="['change', 'blur']" :validate-trigger="['change', 'blur']"
:rules="[ :rules="[
{ required: true, message: '请输入置信度阈值' }, { required: true, message: '请输入置信度阈值' },
{ {
validator: (value, cb) => { validator: (value, cb) => {
if (value < 0 || value > 1) { if (value < 0 || value > 1) {
cb('置信度阈值必须在0-1之间'); cb('置信度阈值必须在0-1之间');
} }
}, }
}, }
]" ]"
> >
<a-input-number <a-input-number
@ -176,13 +176,13 @@
:validate-trigger="['change', 'blur']" :validate-trigger="['change', 'blur']"
:rules="[ :rules="[
{ required: true, message: '请输入NMS阈值' }, { required: true, message: '请输入NMS阈值' },
{ {
validator: (value, cb) => { validator: (value, cb) => {
if (value < 0 || value > 1) { if (value < 0 || value > 1) {
cb('NMS阈值必须在0-1之间'); cb('NMS阈值必须在0-1之间');
} }
}, }
}, }
]" ]"
> >
<a-input-number <a-input-number
@ -202,9 +202,9 @@
<a-modal <a-modal
v-model:visible="detailVisible" v-model:visible="detailVisible"
title="模型配置详情" title="模型配置详情"
@cancel="closeDetail"
:footer="false" :footer="false"
unmount-on-close unmount-on-close
@cancel="closeDetail"
> >
<a-descriptions <a-descriptions
:data="detailData" :data="detailData"
@ -217,33 +217,33 @@
</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 { import {
IconCheckCircle, IconPlus,
IconRefresh,
IconEdit,
IconEye,
IconDelete, IconDelete,
IconEdit,
IconEye,
IconPlus,
IconRefresh,
IconUpload, IconUpload,
} from '@arco-design/web-vue/es/icon' IconCheckCircle
import { } from '@arco-design/web-vue/es/icon';
createModelConfig, import {
deleteModelConfig, getModelConfigList,
getModelConfigDetail, getModelConfigDetail,
getModelConfigList, createModelConfig,
updateModelConfig, updateModelConfig,
} from '@/apis/model-config' deleteModelConfig
import { addAttachment } from '@/apis/attach-info' } from '@/apis/model-config';
import type { ModelConfigRequest, ModelConfigResponse } from '@/apis/model-config/type' import { addAttachment } from '@/apis/attach-info';
import type { ModelConfigRequest, ModelConfigResponse } from '@/apis/model-config/type';
defineOptions({ name: 'ModelConfig' }) defineOptions({ name: 'ModelConfig' });
// //
const tableData = ref<any[]>([]) const tableData = ref<any[]>([]);
const loading = ref(false) const loading = ref(false);
const searchKeyword = ref('') const searchKeyword = ref('');
// //
const pagination = reactive({ const pagination = reactive({
@ -252,117 +252,117 @@ const pagination = reactive({
total: 0, total: 0,
showTotal: true, showTotal: true,
showJumper: true, showJumper: true,
}) });
// //
const formRef = ref() const formRef = ref();
const formVisible = ref(false) const formVisible = ref(false);
const isEdit = ref(false) const isEdit = ref(false);
const formData = reactive<ModelConfigRequest>({ const formData = reactive<ModelConfigRequest>({
attachId: '', attachId: '',
confThreshold: 0, confThreshold: 0,
modelId: '', modelId: '',
modelName: '', modelName: '',
nmsThreshold: 0, nmsThreshold: 0,
}) });
// //
const detailVisible = ref(false) const detailVisible = ref(false);
const detailData = ref<{ label: string, value: any }[]>([]) const detailData = ref<{ label: string; value: any }[]>([]);
// //
const uploadingFile = ref(false) const uploadingFile = ref(false);
const uploadProgress = ref(0) const uploadProgress = ref(0);
const uploadedFileName = ref('') const uploadedFileName = ref('');
// //
const fetchModelConfigList = async () => { const fetchModelConfigList = async () => {
loading.value = true loading.value = true;
try { try {
const res = await getModelConfigList({ const res = await getModelConfigList({
keyword: searchKeyword.value, keyword: searchKeyword.value,
page: pagination.current, page: pagination.current,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
}) });
if (res.data) { if (res.data) {
tableData.value = Array.isArray(res.data) ? res.data : [] tableData.value = Array.isArray(res.data) ? res.data : [];
pagination.total = Array.isArray(res.data) ? res.data.length : 0 pagination.total = Array.isArray(res.data) ? res.data.length : 0;
} else { } else {
tableData.value = [] tableData.value = [];
pagination.total = 0 pagination.total = 0;
} }
} catch (error) { } catch (error) {
console.error('获取模型配置列表失败:', error) console.error('获取模型配置列表失败:', error);
Message.error('获取模型配置列表失败') Message.error('获取模型配置列表失败');
} finally { } finally {
loading.value = false loading.value = false;
} }
} };
// //
onMounted(() => { onMounted(() => {
fetchModelConfigList() fetchModelConfigList();
}) });
// //
const refreshList = () => { const refreshList = () => {
fetchModelConfigList() fetchModelConfigList();
} };
// //
const handleSearch = () => { const handleSearch = () => {
pagination.current = 1 pagination.current = 1;
fetchModelConfigList() fetchModelConfigList();
} };
// //
const onPageChange = (page: number) => { const onPageChange = (page: number) => {
pagination.current = page pagination.current = page;
fetchModelConfigList() fetchModelConfigList();
} };
const onPageSizeChange = (pageSize: number) => { const onPageSizeChange = (pageSize: number) => {
pagination.pageSize = pageSize pagination.pageSize = pageSize;
fetchModelConfigList() fetchModelConfigList();
} };
// //
const handleAdd = () => { const handleAdd = () => {
isEdit.value = false isEdit.value = false;
resetForm() resetForm();
formVisible.value = true formVisible.value = true;
} };
// //
const handleEdit = async (record: ModelConfigResponse) => { const handleEdit = async (record: ModelConfigResponse) => {
isEdit.value = true isEdit.value = true;
resetForm() resetForm();
try { try {
const res = await getModelConfigDetail(record.modelId) const res = await getModelConfigDetail(record.modelId);
if (res.data) { if (res.data) {
const modelData = res.data.data const modelData = res.data.data;
formData.modelId = modelData.modelId formData.modelId = modelData.modelId;
formData.modelName = modelData.modelName formData.modelName = modelData.modelName;
formData.attachId = modelData.attachId formData.attachId = modelData.attachId;
formData.confThreshold = modelData.confThreshold formData.confThreshold = modelData.confThreshold;
formData.nmsThreshold = modelData.nmsThreshold formData.nmsThreshold = modelData.nmsThreshold;
formVisible.value = true formVisible.value = true;
} }
} catch (error) { } catch (error) {
console.error('获取详情失败:', error) console.error('获取详情失败:', error);
Message.error('获取详情失败') Message.error('获取详情失败');
} }
} };
// //
const handleView = async (record: ModelConfigResponse) => { const handleView = async (record: ModelConfigResponse) => {
try { try {
const res = await getModelConfigDetail(record.modelId) const res = await getModelConfigDetail(record.modelId);
if (res.data) { if (res.data) {
const modelData = res.data.data const modelData = res.data.data;
detailData.value = [ detailData.value = [
// { label: 'ID', value: modelData.modelId }, // { label: 'ID', value: modelData.modelId },
{ label: '模型名称', value: modelData.modelName }, { label: '模型名称', value: modelData.modelName },
@ -370,111 +370,111 @@ const handleView = async (record: ModelConfigResponse) => {
{ label: '模型附件ID', value: modelData.attachId || '-' }, { label: '模型附件ID', value: modelData.attachId || '-' },
{ label: '置信度阈值', value: modelData.confThreshold ? modelData.confThreshold.toFixed(2) : '-' }, { label: '置信度阈值', value: modelData.confThreshold ? modelData.confThreshold.toFixed(2) : '-' },
{ label: 'NMS阈值', value: modelData.nmsThreshold ? modelData.nmsThreshold.toFixed(2) : '-' }, { label: 'NMS阈值', value: modelData.nmsThreshold ? modelData.nmsThreshold.toFixed(2) : '-' },
] ];
detailVisible.value = true detailVisible.value = true;
} }
} catch (error) { } catch (error) {
console.error('获取详情失败:', error) console.error('获取详情失败:', error);
Message.error('获取详情失败') Message.error('获取详情失败');
} }
} };
// //
const handleDelete = async (record: ModelConfigResponse) => { const handleDelete = async (record: ModelConfigResponse) => {
try { try {
await deleteModelConfig(record.modelId) await deleteModelConfig(record.modelId);
Message.success('删除成功') Message.success('删除成功');
fetchModelConfigList() fetchModelConfigList();
} catch (error) { } catch (error) {
console.error('删除失败:', error) console.error('删除失败:', error);
Message.error('删除失败') Message.error('删除失败');
} }
} };
// //
const handleSubmit = async () => { const handleSubmit = async () => {
if (!formRef.value) return false if (!formRef.value) return false;
try { try {
await formRef.value.validate() await formRef.value.validate();
const submitData = { const submitData = {
modelId: formData.modelId, modelId: formData.modelId,
modelName: formData.modelName, modelName: formData.modelName,
attachId: formData.attachId, attachId: formData.attachId,
confThreshold: formData.confThreshold, confThreshold: formData.confThreshold,
nmsThreshold: formData.nmsThreshold, nmsThreshold: formData.nmsThreshold,
} };
if (isEdit.value) { if (isEdit.value) {
await updateModelConfig(submitData) await updateModelConfig(submitData);
Message.success('更新成功') Message.success('更新成功');
} else { } else {
await createModelConfig(submitData) await createModelConfig(submitData);
Message.success('创建成功') Message.success('创建成功');
} }
closeForm() closeForm();
fetchModelConfigList() fetchModelConfigList();
return true return true;
} catch (error) { } catch (error) {
console.error('提交失败:', error) console.error('提交失败:', error);
Message.error(`提交失败: ${(error as any)?.msg}` || '未知错误') Message.error('提交失败: ' + (error as any)?.msg || '未知错误');
return false return false;
} }
} };
// //
const resetForm = () => { const resetForm = () => {
// formData.modelId = ''; // formData.modelId = '';
formData.modelName = '' formData.modelName = '';
formData.attachId = '' formData.attachId = '';
formData.confThreshold = 0 formData.confThreshold = 0;
formData.nmsThreshold = 0 formData.nmsThreshold = 0;
} };
// //
const closeForm = () => { const closeForm = () => {
formVisible.value = false formVisible.value = false;
resetForm() resetForm();
} };
// //
const closeDetail = () => { const closeDetail = () => {
detailVisible.value = false detailVisible.value = false;
detailData.value = [] detailData.value = [];
} };
// //
const uploadModelFile = async (options: any) => { const uploadModelFile = async (options: any) => {
const { onProgress, onError, onSuccess, fileItem, name } = options const {onProgress, onError, onSuccess, fileItem, name} = options;
try { try {
// FormData // FormData
const uploadFormData = new FormData() const uploadFormData = new FormData();
uploadFormData.append(name || 'file', fileItem.file) uploadFormData.append(name || 'file', fileItem.file);
// API /attach-info/{businessType} // API /attach-info/{businessType}
const res = await addAttachment(uploadFormData) const res = await addAttachment(uploadFormData);
if (res && res.data) { if (res && res.data) {
// ID // ID
const attachId = res.data const attachId = res.data;
formData.attachId = attachId formData.attachId = attachId;
uploadedFileName.value = fileItem.file.name uploadedFileName.value = fileItem.file.name;
Message.success('模型文件上传成功') Message.success('模型文件上传成功');
onSuccess(res) onSuccess(res);
} else { } else {
Message.error('模型文件上传失败') Message.error('模型文件上传失败');
onError(new Error('上传失败')) onError(new Error('上传失败'));
} }
} catch (error) { } catch (error) {
console.error('上传失败:', error) console.error('上传失败:', error);
Message.error(`上传失败: ${(error as any)?.msg}` || '未知错误') Message.error('上传失败: ' + (error as any)?.msg || '未知错误');
onError(error) onError(error);
} finally { } finally {
uploadingFile.value = false uploadingFile.value = false;
} }
} };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -489,10 +489,10 @@ const uploadModelFile = async (options: any) => {
margin-top: 8px; margin-top: 8px;
color: #86909c; color: #86909c;
font-size: 14px; font-size: 14px;
&.success { &.success {
color: #00b42a; color: #00b42a;
} }
} }
} }
</style> </style>

View File

@ -1,481 +1,484 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<a-card class="general-card" title="应用使用数据" :bordered="false"> <a-card class="general-card" title="应用使用数据" :bordered="false">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col v-for="(stat, index) in appStatistics" :key="index" :span="6"> <a-col :span="6" v-for="(stat, index) in appStatistics" :key="index">
<a-card class="stat-card"> <a-card class="stat-card">
<a-statistic <a-statistic
:title="stat.title" :title="stat.title"
:value="stat.value" :value="stat.value"
:precision="stat.precision || 0" :precision="stat.precision || 0"
:suffix="stat.suffix || ''" :suffix="stat.suffix || ''"
> >
<template #prefix> <template #prefix>
<component :is="stat.icon" /> <component :is="stat.icon" />
</template>
</a-statistic>
</a-card>
</a-col>
</a-row>
<a-divider />
<a-row :gutter="16">
<a-col :span="12">
<a-card title="应用访问量趋势" :bordered="false">
<div ref="appVisitChart" style="height: 350px"></div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="应用使用时长分布" :bordered="false">
<div ref="appTimeChart" style="height: 350px"></div>
</a-card>
</a-col>
</a-row>
<a-divider />
<a-card title="应用使用详情" :bordered="false">
<a-tabs default-active-key="1">
<a-tab-pane key="1" tab="Web端">
<a-table :columns="appColumns" :data-source="webAppData" :pagination="{ pageSize: 5 }">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'usageRate'">
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
</template> </template>
<template v-if="column.dataIndex === 'trend'"> </a-statistic>
<span :style="{ color: getTrendColor(record.trend) }"> </a-card>
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}% </a-col>
<IconUp v-if="record.trend > 0" style="color: #52c41a" /> </a-row>
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" />
<IconMinus v-if="record.trend === 0" style="color: #1890ff" /> <a-divider />
</span>
<a-row :gutter="16">
<a-col :span="12">
<a-card title="应用访问量趋势" :bordered="false">
<div ref="appVisitChart" style="height: 350px"></div>
</a-card>
</a-col>
<a-col :span="12">
<a-card title="应用使用时长分布" :bordered="false">
<div ref="appTimeChart" style="height: 350px"></div>
</a-card>
</a-col>
</a-row>
<a-divider />
<a-card title="应用使用详情" :bordered="false">
<a-tabs default-active-key="1">
<a-tab-pane key="1" tab="Web端">
<a-table :columns="appColumns" :data-source="webAppData" :pagination="{ pageSize: 5 }">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'usageRate'">
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
</template>
<template v-if="column.dataIndex === 'trend'">
<span :style="{ color: getTrendColor(record.trend) }">
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template> </template>
</template> </a-table>
</a-table> </a-tab-pane>
</a-tab-pane> <a-tab-pane key="2" tab="移动端">
<a-tab-pane key="2" tab="移动端"> <a-table :columns="appColumns" :data-source="mobileAppData" :pagination="{ pageSize: 5 }">
<a-table :columns="appColumns" :data-source="mobileAppData" :pagination="{ pageSize: 5 }"> <template #bodyCell="{ column, record }">
<template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'usageRate'">
<template v-if="column.dataIndex === 'usageRate'"> <a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" /> </template>
<template v-if="column.dataIndex === 'trend'">
<span :style="{ color: getTrendColor(record.trend) }">
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template> </template>
<template v-if="column.dataIndex === 'trend'"> </a-table>
<span :style="{ color: getTrendColor(record.trend) }"> </a-tab-pane>
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}% <a-tab-pane key="3" tab="微信小程序">
<IconUp v-if="record.trend > 0" style="color: #52c41a" /> <a-table :columns="appColumns" :data-source="miniAppData" :pagination="{ pageSize: 5 }">
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" /> <template #bodyCell="{ column, record }">
<IconMinus v-if="record.trend === 0" style="color: #1890ff" /> <template v-if="column.dataIndex === 'usageRate'">
</span> <a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
</template>
<template v-if="column.dataIndex === 'trend'">
<span :style="{ color: getTrendColor(record.trend) }">
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template> </template>
</template> </a-table>
</a-table> </a-tab-pane>
</a-tab-pane> </a-tabs>
<a-tab-pane key="3" tab="微信小程序"> </a-card>
<a-table :columns="appColumns" :data-source="miniAppData" :pagination="{ pageSize: 5 }">
<template #bodyCell="{ column, record }"> <a-divider />
<template v-if="column.dataIndex === 'usageRate'">
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" /> <a-row :gutter="16">
</template> <a-col :span="24">
<template v-if="column.dataIndex === 'trend'"> <a-card title="终端设备分布" :bordered="false">
<span :style="{ color: getTrendColor(record.trend) }"> <div ref="deviceDistributionChart" style="height: 400px"></div>
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}% </a-card>
<IconUp v-if="record.trend > 0" style="color: #52c41a" /> </a-col>
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" /> </a-row>
<IconMinus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-card> </a-card>
</div>
<a-divider /> </template>
<a-row :gutter="16"> <script lang="ts" setup>
<a-col :span="24"> import { ref, onMounted, reactive, nextTick } from 'vue'
<a-card title="终端设备分布" :bordered="false"> import { Statistic } from '@arco-design/web-vue'
<div ref="deviceDistributionChart" style="height: 400px"></div> import * as echarts from 'echarts'
</a-card> import {
</a-col> IconApps,
</a-row> IconMobile,
</a-card> IconUser,
</div> IconClockCircle,
</template> IconUp,
IconDown,
<script lang="ts" setup> IconMinus
import { nextTick, onMounted, reactive, ref } from 'vue' } from '@arco-design/web-vue/es/icon'
import * as echarts from 'echarts'
import {
IconApps, const appVisitChart = ref(null)
IconClockCircle, const appTimeChart = ref(null)
IconDown, const deviceDistributionChart = ref(null)
IconMinus,
IconUp, //
IconUser, const appStatistics = reactive([
} from '@arco-design/web-vue/es/icon' {
title: '应用总数',
const appVisitChart = ref(null) value: 12,
const appTimeChart = ref(null) icon: IconApps
const deviceDistributionChart = ref(null) },
{
// title: '活跃应用',
const appStatistics = reactive([ value: 8,
{ icon: IconApps
title: '应用总数', },
value: 12, {
icon: IconApps, title: '日均访问量',
}, value: 1258,
{ icon: IconUser
title: '活跃应用', },
value: 8, {
icon: IconApps, title: '日均使用时长',
}, value: 3.5,
{ precision: 1,
title: '日均访问量', suffix: '小时',
value: 1258, icon: IconClockCircle
icon: IconUser, }
}, ])
{
title: '日均使用时长', // 使
value: 3.5, const appColumns = [
precision: 1, {
suffix: '小时', title: '应用名称',
icon: IconClockCircle, dataIndex: 'appName',
}, key: 'appName',
]) },
{
// 使 title: '访问量',
const appColumns = [ dataIndex: 'visitCount',
{ key: 'visitCount',
title: '应用名称', sorter: (a, b) => a.visitCount - b.visitCount,
dataIndex: 'appName', },
key: 'appName', {
}, title: '用户数',
{ dataIndex: 'userCount',
title: '访问量', key: 'userCount',
dataIndex: 'visitCount', sorter: (a, b) => a.userCount - b.userCount,
key: 'visitCount', },
sorter: (a, b) => a.visitCount - b.visitCount, {
}, title: '使用率',
{ dataIndex: 'usageRate',
title: '用户数', key: 'usageRate',
dataIndex: 'userCount', sorter: (a, b) => a.usageRate - b.usageRate,
key: 'userCount', },
sorter: (a, b) => a.userCount - b.userCount, {
}, title: '平均使用时长',
{ dataIndex: 'averageTime',
title: '使用率', key: 'averageTime',
dataIndex: 'usageRate', },
key: 'usageRate', {
sorter: (a, b) => a.usageRate - b.usageRate, title: '环比上月',
}, dataIndex: 'trend',
{ key: 'trend',
title: '平均使用时长', sorter: (a, b) => a.trend - b.trend,
dataIndex: 'averageTime', }
key: 'averageTime', ]
},
{ // Web
title: '环比上月', const webAppData = [
dataIndex: 'trend', {
key: 'trend', key: '1',
sorter: (a, b) => a.trend - b.trend, appName: '企业管理后台',
}, visitCount: 3560,
] userCount: 320,
usageRate: 95,
// Web averageTime: '2.5小时',
const webAppData = [ trend: 8.5
{ },
key: '1', {
appName: '企业管理后台', key: '2',
visitCount: 3560, appName: '项目管理系统',
userCount: 320, visitCount: 2980,
usageRate: 95, userCount: 285,
averageTime: '2.5小时', usageRate: 90,
trend: 8.5, averageTime: '3小时',
}, trend: 5.2
{ },
key: '2', {
appName: '项目管理系统', key: '3',
visitCount: 2980, appName: '数据分析平台',
userCount: 285, visitCount: 2450,
usageRate: 90, userCount: 210,
averageTime: '3小时', usageRate: 85,
trend: 5.2, averageTime: '2小时',
}, trend: 3.8
{ },
key: '3', {
appName: '数据分析平台', key: '4',
visitCount: 2450, appName: '客户管理系统',
userCount: 210, visitCount: 1980,
usageRate: 85, userCount: 180,
averageTime: '2小时', usageRate: 75,
trend: 3.8, averageTime: '1.5小时',
}, trend: -2.1
{ },
key: '4', {
appName: '客户管理系统', key: '5',
visitCount: 1980, appName: '知识库系统',
userCount: 180, visitCount: 1560,
usageRate: 75, userCount: 150,
averageTime: '1.5小时', usageRate: 65,
trend: -2.1, averageTime: '1小时',
}, trend: 0
{ }
key: '5', ]
appName: '知识库系统',
visitCount: 1560, //
userCount: 150, const mobileAppData = [
usageRate: 65, {
averageTime: '1小时', key: '1',
trend: 0, appName: '移动办公APP',
}, visitCount: 4250,
] userCount: 350,
usageRate: 98,
// averageTime: '3.5小时',
const mobileAppData = [ trend: 12.5
{ },
key: '1', {
appName: '移动办公APP', key: '2',
visitCount: 4250, appName: '外勤管理APP',
userCount: 350, visitCount: 3680,
usageRate: 98, userCount: 320,
averageTime: '3.5小时', usageRate: 92,
trend: 12.5, averageTime: '4小时',
}, trend: 9.8
{ },
key: '2', {
appName: '外勤管理APP', key: '3',
visitCount: 3680, appName: '项目跟踪APP',
userCount: 320, visitCount: 2850,
usageRate: 92, userCount: 260,
averageTime: '4小时', usageRate: 88,
trend: 9.8, averageTime: '3小时',
}, trend: 7.5
{ },
key: '3', {
appName: '项目跟踪APP', key: '4',
visitCount: 2850, appName: '客户拜访APP',
userCount: 260, visitCount: 2120,
usageRate: 88, userCount: 180,
averageTime: '3小时', usageRate: 72,
trend: 7.5, averageTime: '2小时',
}, trend: -1.5
{ }
key: '4', ]
appName: '客户拜访APP',
visitCount: 2120, //
userCount: 180, const miniAppData = [
usageRate: 72, {
averageTime: '2小时', key: '1',
trend: -1.5, appName: '企业服务小程序',
}, visitCount: 5680,
] userCount: 420,
usageRate: 96,
// averageTime: '1.5小时',
const miniAppData = [ trend: 15.8
{ },
key: '1', {
appName: '企业服务小程序', key: '2',
visitCount: 5680, appName: '客户自助小程序',
userCount: 420, visitCount: 4850,
usageRate: 96, userCount: 380,
averageTime: '1.5小时', usageRate: 90,
trend: 15.8, averageTime: '1小时',
}, trend: 10.5
{ },
key: '2', {
appName: '客户自助小程序', key: '3',
visitCount: 4850, appName: '员工工具小程序',
userCount: 380, visitCount: 3920,
usageRate: 90, userCount: 345,
averageTime: '1小时', usageRate: 85,
trend: 10.5, averageTime: '0.8小时',
}, trend: 8.2
{ }
key: '3', ]
appName: '员工工具小程序',
visitCount: 3920, //
userCount: 345, const getUsageRateColor = (rate) => {
usageRate: 85, if (rate >= 90) return '#52c41a'
averageTime: '0.8小时', if (rate >= 70) return '#1890ff'
trend: 8.2, return '#faad14'
}, }
]
const getTrendColor = (trend) => {
// if (trend > 0) return '#52c41a'
const getUsageRateColor = (rate) => { if (trend < 0) return '#ff4d4f'
if (rate >= 90) return '#52c41a' return '#1890ff'
if (rate >= 70) return '#1890ff' }
return '#faad14'
} onMounted(() => {
// 使 nextTick DOM
const getTrendColor = (trend) => { nextTick(() => {
if (trend > 0) return '#52c41a'
if (trend < 0) return '#ff4d4f'
return '#1890ff'
}
onMounted(() => {
// 使 nextTick DOM
nextTick(() => {
// 访 // 访
if (appVisitChart.value) { if (appVisitChart.value) {
const visitChart = echarts.init(appVisitChart.value) const visitChart = echarts.init(appVisitChart.value)
visitChart.setOption({ visitChart.setOption({
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis'
},
legend: {
data: ['Web端', '移动端', '小程序']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Web端',
type: 'line',
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200]
}, },
legend: { {
data: ['Web端', '移动端', '小程序'], name: '移动端',
type: 'line',
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500]
}, },
grid: { {
left: '3%', name: '小程序',
right: '4%', type: 'line',
bottom: '3%', data: [4500, 4800, 5200, 4900, 4700, 3900, 3500]
containLabel: true, }
}, ]
xAxis: { })
type: 'category', }
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: 'Web端',
type: 'line',
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200],
},
{
name: '移动端',
type: 'line',
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500],
},
{
name: '小程序',
type: 'line',
data: [4500, 4800, 5200, 4900, 4700, 3900, 3500],
},
],
})
}
// 使 // 使
if (appTimeChart.value) { if (appTimeChart.value) {
const timeChart = echarts.init(appTimeChart.value) const timeChart = echarts.init(appTimeChart.value)
timeChart.setOption({ timeChart.setOption({
tooltip: { tooltip: {
trigger: 'item', trigger: 'item'
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
left: 'left', left: 'left'
}, },
series: [ series: [
{ {
name: '使用时长分布', name: '使用时长分布',
type: 'pie', type: 'pie',
radius: '70%', radius: '70%',
data: [ data: [
{ value: 35, name: 'Web端' }, { value: 35, name: 'Web端' },
{ value: 45, name: '移动端' }, { value: 45, name: '移动端' },
{ value: 20, name: '小程序' }, { value: 20, name: '小程序' }
], ],
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 10, shadowBlur: 10,
shadowOffsetX: 0, shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)', shadowColor: 'rgba(0, 0, 0, 0.5)'
}, }
}, }
}, }
], ]
}) })
} }
// //
if (deviceDistributionChart.value) { if (deviceDistributionChart.value) {
const deviceChart = echarts.init(deviceDistributionChart.value) const deviceChart = echarts.init(deviceDistributionChart.value)
deviceChart.setOption({ deviceChart.setOption({
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow', type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信']
},
series: [
{
name: '访问量',
type: 'bar',
stack: 'total',
label: {
show: true
}, },
}, emphasis: {
legend: {}, focus: 'series'
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信'],
},
series: [
{
name: '访问量',
type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [5200, 3800, 6500, 8200, 9500],
}, },
{ data: [5200, 3800, 6500, 8200, 9500]
name: '用户数', },
type: 'bar', {
stack: 'total', name: '用户数',
label: { type: 'bar',
show: true, stack: 'total',
}, label: {
emphasis: { show: true
focus: 'series',
},
data: [280, 220, 320, 380, 420],
}, },
], emphasis: {
}) focus: 'series'
} },
data: [280, 220, 320, 380, 420]
}
]
})
}
// //
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
if (appVisitChart.value) { if (appVisitChart.value) {
const visitChart = echarts.getInstanceByDom(appVisitChart.value) const visitChart = echarts.getInstanceByDom(appVisitChart.value)
visitChart?.resize() visitChart?.resize()
} }
if (appTimeChart.value) { if (appTimeChart.value) {
const timeChart = echarts.getInstanceByDom(appTimeChart.value) const timeChart = echarts.getInstanceByDom(appTimeChart.value)
timeChart?.resize() timeChart?.resize()
} }
if (deviceDistributionChart.value) { if (deviceDistributionChart.value) {
const deviceChart = echarts.getInstanceByDom(deviceDistributionChart.value) const deviceChart = echarts.getInstanceByDom(deviceDistributionChart.value)
deviceChart?.resize() deviceChart?.resize()
} }
})
}) })
}) })
}) </script>
</script>
<style scoped> <style scoped>
.general-card { .general-card {
margin-bottom: 20px; margin-bottom: 20px;
} }
.stat-card { .stat-card {
margin-bottom: 20px; margin-bottom: 20px;
text-align: center; text-align: center;
} }
</style> </style>

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