Compare commits

..

2 Commits
main ... jgqweb

Author SHA1 Message Date
Mr.j 60deb39de8 优化设备采购界面搜索 2025-08-05 21:41:13 +08:00
Mr.j 749b24a17f 实现设备采购模块的数据库更新,页面搭建和分页查询 2025-08-05 16:22:09 +08:00
257 changed files with 20504 additions and 20887 deletions

BIN
.env Normal file

Binary file not shown.

View File

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

View File

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

View File

@ -0,0 +1,147 @@
# 设备中心模块问题修复和改进总结
## 发现的问题
### 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

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ export default function appInfo(): Plugin {
apply: 'serve',
async buildStart() {
const { bold, green, cyan, bgGreen, underline } = picocolors
// eslint-disable-next-line no-console
console.log(
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('持续迭代优化的前后端分离中后台管理系统框架。')}`,

View File

@ -1,48 +1,11 @@
import antfu from '@antfu/eslint-config'
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import { defineConfig } from "eslint/config";
// https://github.com/antfu/eslint-config
export default antfu(
{
vue: {
overrides: {
'vue/block-order': ['error', {
order: [['script', 'template'], 'style'],
}], // 强制组件顶级元素的顺序
'vue/define-macros-order': ['error', {
order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots'],
defineExposeLast: true,
}], // 强制执行定义限制和定义弹出编译器宏的顺序
'vue/singleline-html-element-content-newline': 'off', // 要求在单行元素的内容前后换行
'vue/html-self-closing': ['off', {
html: {
void: 'never',
normal: 'always',
component: 'never',
},
}], // 强制自结束样式
'vue/custom-event-name-casing': ['error', 'kebab-case'], // 对自定义事件名称强制使用特定大小写
},
},
typescript: true,
ignores: [
'**/*.md',
'.github',
'.image',
'src/types/shims-vue.d.ts',
],
},
{
rules: {
'curly': ['off', 'all'], // 对所有控制语句强制使用一致的大括号样式
'no-new': 'off', // 不允许在赋值或比较之外使用 new 运算符
// 'no-console': 'error', // 禁止使用 console
'style/arrow-parens': ['error', 'always'], // 箭头函数参数需要括号
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式
'regexp/no-unused-capturing-group': 'off',
'regexp/no-super-linear-backtracking': 'off',
'node/prefer-global/process': 'off',
'antfu/top-level-function': 'off',
'antfu/if-newline': 'off',
},
},
)
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
]);

4841
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,9 +34,7 @@
"crypto-js": "^4.2.0",
"dayjs": "^1.11.4",
"echarts": "^5.4.2",
"html2canvas": "^1.4.1",
"jsencrypt": "^3.3.2",
"jspdf": "^3.0.1",
"lint-staged": "^15.2.10",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
@ -66,21 +64,27 @@
"devDependencies": {
"@antfu/eslint-config": "^2.16.3",
"@arco-design/web-vue": "^2.57.0",
"@eslint/js": "^9.32.0",
"@types/crypto-js": "^4.2.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.2.5",
"@types/query-string": "^6.3.0",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/tsconfig": "^0.1.3",
"boxen": "^7.1.1",
"eslint": "^9.0.0",
"eslint": "^9.32.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"picocolors": "^1.0.0",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"typescript": "~5.0.4",
"typescript-eslint": "^8.39.0",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.1",
"vite": "^5.1.5",

View File

@ -68,15 +68,9 @@ importers:
echarts:
specifier: ^5.4.2
version: 5.5.0
html2canvas:
specifier: ^1.4.1
version: 1.4.1
jsencrypt:
specifier: ^3.3.2
version: 3.3.2
jspdf:
specifier: ^3.0.1
version: 3.0.1
lint-staged:
specifier: ^15.2.10
version: 15.2.10
@ -469,10 +463,6 @@ packages:
resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.28.2':
resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==}
engines: {node: '>=6.9.0'}
'@babel/template@7.24.0':
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
engines: {node: '>=6.9.0'}
@ -1202,18 +1192,12 @@ packages:
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.
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/sortablejs@1.15.8':
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
'@types/svgo@2.6.4':
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/unist@2.0.10':
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
@ -1624,10 +1608,6 @@ packages:
balanced-match@1.0.2:
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:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'}
@ -1672,11 +1652,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
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:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -1717,10 +1692,6 @@ packages:
caniuse-lite@1.0.30001620:
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:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@ -1912,9 +1883,6 @@ packages:
crypto-js@4.2.0:
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:
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
@ -2091,9 +2059,6 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
domutils@1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
@ -2647,9 +2612,6 @@ packages:
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@ -2903,10 +2865,6 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@ -3210,9 +3168,6 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jspdf@3.0.1:
resolution: {integrity: sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@ -3678,9 +3633,6 @@ packages:
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@ -3862,9 +3814,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -3888,9 +3837,6 @@ packages:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
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:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@ -3965,10 +3911,6 @@ packages:
rfdc@1.4.1:
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:
resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -4196,10 +4138,6 @@ packages:
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'
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
static-extend@0.1.2:
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
engines: {node: '>=0.10.0'}
@ -4299,10 +4237,6 @@ packages:
svg-baker@1.7.0:
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:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
@ -4340,9 +4274,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -4560,9 +4491,6 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
v-viewer@3.0.13:
resolution: {integrity: sha512-T8pgGzlF0ZCHVpD/32OKsD8MlpI6tqYP3n1XLcSjvGQMc0ABn8nJ4AumxvzAKVQrLRWtDTG6qRGAyCPCmi7ceA==}
peerDependencies:
@ -5143,8 +5071,6 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@babel/runtime@7.28.2': {}
'@babel/template@7.24.0':
dependencies:
'@babel/code-frame': 7.24.2
@ -5871,18 +5797,12 @@ snapshots:
dependencies:
query-string: 9.0.0
'@types/raf@3.4.3':
optional: true
'@types/sortablejs@1.15.8': {}
'@types/svgo@2.6.4':
dependencies:
'@types/node': 20.12.12
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.10': {}
'@types/web-bluetooth@0.0.20': {}
@ -6427,8 +6347,6 @@ snapshots:
balanced-match@1.0.2: {}
base64-arraybuffer@1.0.2: {}
base@0.11.2:
dependencies:
cache-base: 1.0.1
@ -6497,8 +6415,6 @@ snapshots:
node-releases: 2.0.14
update-browserslist-db: 1.0.16(browserslist@4.23.0)
btoa@1.2.1: {}
buffer-from@1.1.2: {}
builtin-modules@3.3.0: {}
@ -6542,18 +6458,6 @@ snapshots:
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:
dependencies:
no-case: 3.0.4
@ -6770,10 +6674,6 @@ snapshots:
crypto-js@4.2.0: {}
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
css-select@4.3.0:
dependencies:
boolbase: 1.0.0
@ -6943,11 +6843,6 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.2.6:
optionalDependencies:
'@types/trusted-types': 2.0.7
optional: true
domutils@1.7.0:
dependencies:
dom-serializer: 0.2.2
@ -7635,8 +7530,6 @@ snapshots:
dependencies:
reusify: 1.0.4
fflate@0.8.2: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@ -7884,11 +7777,6 @@ snapshots:
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:
dependencies:
domelementtype: 1.3.1
@ -8151,18 +8039,6 @@ snapshots:
optionalDependencies:
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:
dependencies:
json-buffer: 3.0.1
@ -8672,9 +8548,6 @@ snapshots:
perfect-debounce@1.0.0: {}
performance-now@2.1.0:
optional: true
picocolors@1.0.1: {}
picocolors@1.1.1: {}
@ -8891,11 +8764,6 @@ snapshots:
queue-microtask@1.2.3: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
optional: true
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@ -8927,9 +8795,6 @@ snapshots:
dependencies:
'@eslint-community/regexpp': 4.10.0
regenerator-runtime@0.13.11:
optional: true
regenerator-runtime@0.14.1: {}
regex-not@1.0.2:
@ -8990,9 +8855,6 @@ snapshots:
rfdc@1.4.1: {}
rgbcolor@1.0.1:
optional: true
rollup@4.17.2:
dependencies:
'@types/estree': 1.0.5
@ -9247,9 +9109,6 @@ snapshots:
stable@0.1.8: {}
stackblur-canvas@2.7.0:
optional: true
static-extend@0.1.2:
dependencies:
define-property: 0.2.5
@ -9366,9 +9225,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
svg-pathdata@6.0.3:
optional: true
svg-tags@1.0.0: {}
svgo@2.8.0:
@ -9403,10 +9259,6 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
text-table@0.2.0: {}
tippy.js@6.3.7:
@ -9654,10 +9506,6 @@ snapshots:
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)):
dependencies:
lodash-es: 4.17.21

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,62 +0,0 @@
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 {
code: string;
label: string;
value: string;
name?: string; // 兼容性字段
sort?: number; // 兼容性字段
code: string
label: string
value: string
name?: string // 兼容性字段
sort?: number // 兼容性字段
}

View File

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

View File

@ -0,0 +1,125 @@
import http from '@/utils/http'
import type { EquipmentListReq, EquipmentReq, EquipmentResp } from './type'
/**
* API
*/
export const equipmentProcurementApi = {
/**
*
*/
page: (params: EquipmentListReq) => {
console.log('🔍 API - equipmentProcurementApi.page 被调用')
console.log('🔍 API - 接收到的参数:', params)
console.log('🔍 API - 参数类型:', typeof params)
console.log('🔍 API - 参数的键值对:')
Object.entries(params).forEach(([key, value]) => {
console.log(` ${key}: ${value} (${typeof value})`)
})
// 确保参数格式正确
const requestParams = {
...params,
// 确保分页参数存在
page: params.page || 1,
pageSize: params.pageSize || 10,
}
console.log('🔍 API - 最终请求参数:', requestParams)
console.log('🔍 API - 准备发送GET请求到 /equipment/procurement/page')
console.log('🔍 API - 请求参数序列化前:', requestParams)
// 手动序列化参数进行调试使用URLSearchParams
const searchParams = new URLSearchParams()
Object.entries(requestParams).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
searchParams.append(key, String(value))
}
})
console.log('🔍 API - 手动序列化后的参数:', searchParams.toString())
// 参考设备模块的调用方式,直接将参数作为第二个参数传递
return http.get<ApiRes<PageRes<EquipmentResp>>>('/equipment/procurement/page', requestParams)
},
/**
* - 使API文档中的参数格式
*/
testPage: () => {
console.log('🧪 API - 测试参数传递')
// 使用API文档中的参数格式进行测试
const testParams = {
page: 1,
pageSize: 10,
equipmentName: '测试设备',
supplierName: '测试供应商',
quantity: 10,
unitPrice: 100.50,
totalPrice: 1005.00,
accountNumber: 'TEST001',
brand: '测试品牌',
locationStatus: 'spare',
physicalLocation: '测试位置',
purchaseOrder: 'PO001',
inventoryBasis: '测试依据',
dynamicRecord: '测试记录'
}
console.log('🧪 API - 测试参数:', testParams)
return http.get<ApiRes<PageRes<EquipmentResp>>>('/equipment/procurement/page', testParams)
},
/**
*
*/
add: (data: EquipmentReq) => {
return http.post<ApiRes<null>>('/equipment/procurement', data)
},
/**
*
*/
update: (equipmentId: string, data: EquipmentReq) => {
return http.put<ApiRes<null>>(`/equipment/procurement/${equipmentId}`, data)
},
/**
*
*/
delete: (equipmentId: string) => {
return http.del<ApiRes<null>>(`/equipment/procurement/${equipmentId}`)
},
/**
*
*/
detail: (equipmentId: string) => {
return http.get<ApiRes<EquipmentResp>>(`/equipment/procurement/detail/${equipmentId}`)
},
/**
*
*/
getStats: () => {
return http.get<ApiRes<unknown>>('/equipment/procurement/stats')
},
/**
*
*/
batchDelete: (equipmentIds: string[]) => {
return http.del<ApiRes<null>>('/equipment/procurement/batch', { data: equipmentIds })
},
/**
*
*/
export: (params: EquipmentListReq) => {
return http.get<Blob>('/equipment/procurement/export', {
params,
responseType: 'blob'
})
}
}

View File

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

View File

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

View File

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

View File

@ -123,12 +123,12 @@ export const uploadSingleImage = (imageSource: string, file: File, params?: {
if (params?.partId) queryParams.append('partId', params.partId)
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, {
headers: {
'Content-Type': 'multipart/form-data'
}
'Content-Type': 'multipart/form-data',
},
})
}
@ -143,7 +143,7 @@ export const batchUploadImages = (imageSource: string, files: File[], params?: {
const formData = new FormData()
// 添加文件
files.forEach(file => {
files.forEach((file) => {
formData.append('files', file)
})
@ -155,12 +155,12 @@ export const batchUploadImages = (imageSource: string, files: File[], params?: {
if (params?.partId) queryParams.append('partId', params.partId)
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, {
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, {
headers: {
'Content-Type': 'application/json'
}
'Content-Type': 'application/json',
},
})
}
@ -183,12 +183,12 @@ export const uploadImageToPartV2 = (
imageSource: string,
partId: string,
files: File[],
params: Partial<T.ImageUploadParams>
params: Partial<T.ImageUploadParams>,
) => {
const formData = new FormData()
// 添加文件
files.forEach(file => {
files.forEach((file) => {
formData.append('files', file)
})
@ -226,8 +226,8 @@ export const uploadImageToPartV2 = (
return http.post(`/image/${imageSource}/upload/${partId}`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
'Content-Type': 'multipart/form-data',
},
})
}
@ -238,8 +238,8 @@ export const importImages = (files: FileList | File[], params: T.ImageImportPara
// 使用批量上传接口
return batchUploadImages(params.imageSource || 'default', fileArray, {
partId: params.componentId,
uploadUser: params.uploadUser
}).then(response => {
uploadUser: params.uploadUser,
}).then((response) => {
// 如果需要自动标注
if (params.autoAnnotate && params.annotationTypes && params.annotationTypes.length > 0) {
// 这里可以添加自动标注逻辑
@ -255,7 +255,7 @@ export const autoAnnotateImage = (params: T.AutoAnnotationParams) => {
confThreshold: params.confidenceThreshold || 0.5,
defectTypeList: params.annotationTypes,
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 上传图像(保留旧接口兼容性) */
export const uploadImage = (file: File, params: { projectId: string; componentId?: string }) => {
export const uploadImage = (file: File, params: { projectId: string, componentId?: string }) => {
return uploadSingleImage('default', file, {
partId: params.componentId,
uploadUser: 'current-user'
uploadUser: 'current-user',
})
}
@ -321,21 +321,20 @@ export function reprocessImage(params: T.ImageProcessParams) {
export const batchProcessImages = (imageIds: string[], processType: string) => {
return http.post<T.ImageProcessResult[]>(`/industrial-image/batch-process`, {
imageIds,
processType
processType,
})
}
/** @desc 导出处理结果 */
export function exportProcessResults(query: T.ImageQuery) {
return http.get(`/industrial-image/export/results`, query, {
responseType: 'blob'
responseType: 'blob',
})
}
/** @desc 生成检测报告 */
export function generateReport(projectId: string) {
return http.post(`/industrial-image/report/generate`, { projectId }, {
responseType: 'blob'
responseType: 'blob',
})
}

View File

@ -153,7 +153,7 @@ export interface IndustrialImage {
name: string
/** 图像路径 */
path: string
/** 图像路径API返回字段*/
/** 图像路径API返回字段 */
imagePath?: string
/** 缩略图路径 */
thumbnailPath?: string

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/** 保险信息接口 */
export interface InsuranceInfo {
id?: string
attachInfoId:string
attachInfoId: string
insuranceCompanyId: string
insuranceTypeId: string
userId: string

View File

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

View File

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

View File

@ -9,10 +9,10 @@ export interface PerformanceDimension {
createTime?: string
updateBy?: string
updateTime?: string
}
}
/** 绩效细则 */
export interface PerformanceRule {
/** 绩效细则 */
export interface PerformanceRule {
ruleId: string
ruleName: string
description?: string
@ -25,15 +25,15 @@ export interface PerformanceDimension {
createTime?: string
updateBy?: string
updateTime?: string
}
}
/** 查询参数 */
export interface DimensionQuery {
/** 查询参数 */
export interface DimensionQuery {
dimensionName?: string
status?: 0 | 1
}
export interface RuleQuery {
}
export interface RuleQuery {
dimensionName?: string
ruleName?: string
status?: 0 | 1
}
}

View File

@ -89,17 +89,17 @@ export function auditBudget(id: string, data: BudgetAuditReq) {
/** @desc 获取预算类型选项 */
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 上传预算附件 */
export function uploadBudgetAttachment(file: File) {
const formData = new FormData()
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: {
'Content-Type': 'multipart/form-data'
}
'Content-Type': 'multipart/form-data',
},
})
}
@ -111,6 +111,6 @@ export function deleteBudgetAttachment(id: string) {
/** @desc 导出预算记录 */
export function exportBudgetRecord(query: BudgetQuery) {
return http.get(`${BASE_URL}/export`, query, {
responseType: 'blob'
responseType: 'blob',
})
}

View File

@ -41,8 +41,8 @@ export function importProject(file: File) {
formData.append('file', file)
return http.post(`${BASE_URL}/import`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
'Content-Type': 'multipart/form-data',
},
})
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import type { SalaryRecord, SalaryQuery, SalaryCreateRequest } from '@/views/salary-management/types'
import type { SalaryCreateRequest, SalaryQuery, SalaryRecord } from '@/views/salary-management/types'
import http from '@/utils/http'
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)
}

View File

@ -1,4 +1,3 @@
import type * as T from './type'
import http from '@/utils/http'
import { convertMenuData } from '@/utils/menuConverter'
@ -6,13 +5,13 @@ import { convertMenuData } from '@/utils/menuConverter'
* API获取菜单树形数据
*/
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 }
const data = res.data || [];
const data = res.data || []
// 转换菜单数据为角色管理组件需要的格式
const convertedData = convertMenuData(data);
return convertedData;
});
const convertedData = convertMenuData(data)
return convertedData
})
}
/**
@ -23,31 +22,31 @@ export function getMenuTreeForRole(query?: { terminalType?: 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[]) => {
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) {
item.children = processMenus(item.children);
item.children = processMenus(item.children)
}
return item;
});
};
return item
})
}
return processMenus(result);
return processMenus(result)
}
/**
* ID列表
*/
export function getRoleMenuIds(roleId: string) {
return http.get<string[]>(`/role/get-menus/${roleId}`);
return http.get<string[]>(`/role/get-menus/${roleId}`)
}
/**
@ -56,6 +55,6 @@ export function getRoleMenuIds(roleId: string) {
export function assignRoleMenus(roleId: string, menuIds: string[]) {
return http.post('/role/bind-menu', {
roleId,
menuIds
});
menuIds,
})
}

View File

@ -1,52 +1,46 @@
import type * as T from './type';
import http from '@/utils/http';
import type * as T from './type'
import http from '@/utils/http'
const BASE_URL = '/post';
const BASE_URL = '/post'
/**
*
*/
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) {
return http.get<T.PostVO>(`${BASE_URL}/detail/${postId}`);
return http.get<T.PostVO>(`${BASE_URL}/detail/${postId}`)
}
/**
*
*/
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) {
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) {
return http.put<any>(`${BASE_URL}/${postId}`, data);
return http.put<any>(`${BASE_URL}/${postId}`, data)
}
/**
*
*/
export function deletePost(postId: string) {
}
/**
*
*/
export function getPostUsers(postId: string) {
return http.get<T.UserNewResp[]>(`${BASE_URL}/${postId}/user`);
return http.del<any>(`${BASE_URL}/${postId}`)
}

View File

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

View File

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

View File

@ -0,0 +1,44 @@
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

@ -47,7 +47,7 @@ const breadcrumbList = computed(() => {
const arr = obj ? obj.nodes.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false) : []
// 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]
}

View File

@ -66,7 +66,7 @@
</a-checkbox-group>
</a-form-item>
<a-form-item label="标注类型" v-if="form.settings.includes('autoAnnotate')">
<a-form-item v-if="form.settings.includes('autoAnnotate')" label="标注类型">
<a-select
v-model="form.annotationTypes"
:options="defectTypeOptions"
@ -89,7 +89,7 @@
<template #upload-button>
<div class="upload-area">
<div class="upload-drag-icon">
<icon-upload size="48" />
<IconUpload size="48" />
</div>
<div class="upload-text">
<p>点击或拖拽图像文件到此区域</p>
@ -101,12 +101,12 @@
</div>
<!-- 文件列表 -->
<div class="file-list" v-if="fileList.length > 0">
<div v-if="fileList.length > 0" class="file-list">
<div class="list-header">
<h4>待导入文件 ({{ fileList.length }})</h4>
<a-button type="text" @click="clearFiles">
<template #icon>
<icon-delete />
<IconDelete />
</template>
清空
</a-button>
@ -133,7 +133,7 @@
@click="removeFile(index)"
>
<template #icon>
<icon-close />
<IconClose />
</template>
</a-button>
</div>
@ -142,7 +142,7 @@
</div>
<!-- 导入进度 -->
<div class="import-progress" v-if="importing">
<div v-if="importing" class="import-progress">
<a-progress
:percent="importProgress"
:status="importStatus"
@ -152,7 +152,7 @@
</div>
<!-- 导入结果 -->
<div class="import-result" v-if="importResult">
<div v-if="importResult" class="import-result">
<a-alert
:type="importResult.failed.length > 0 ? 'warning' : 'success'"
:title="getResultTitle()"
@ -160,7 +160,7 @@
show-icon
/>
<div class="result-details" v-if="importResult.failed.length > 0">
<div v-if="importResult.failed.length > 0" class="result-details">
<h4>失败文件列表:</h4>
<div class="failed-list">
<div
@ -180,22 +180,22 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import {
IconUpload,
IconClose,
IconDelete,
IconClose
IconUpload,
} from '@arco-design/web-vue/es/icon'
import {
getImageSources,
getProjectTree,
importImages,
getImageSources
} from '@/apis/industrial-image'
import type {
ProjectTreeNode,
ImageImportParams,
IndustrialImage,
ImageImportParams
ProjectTreeNode,
} from '@/apis/industrial-image/type'
interface Props {
@ -233,30 +233,30 @@ const form = ref({
latitude: '',
longitude: '',
settings: [] as string[],
annotationTypes: [] as string[]
annotationTypes: [] as string[],
})
const fileList = ref<FileItem[]>([])
const projectTree = ref<ProjectTreeNode[]>([])
const defectTypes = ref<Array<{ id: string; name: string; description?: string; color?: string }>>([])
const imageSources = ref<Array<{ id: string; name: string; code: string }>>([])
const defectTypes = ref<Array<{ id: string, name: string, description?: string, color?: string }>>([])
const imageSources = ref<Array<{ id: string, name: string, code: string }>>([])
const loadingImageSources = ref(false)
//
const visible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
set: (value) => emit('update:visible', value),
})
const componentOptions = computed(() => {
const findComponents = (nodes: ProjectTreeNode[]): Array<{ label: string; value: string }> => {
const options: Array<{ label: string; value: string }> = []
const findComponents = (nodes: ProjectTreeNode[]): 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') {
options.push({
label: node.name,
value: node.id
value: node.id,
})
}
if (node.children) {
@ -271,16 +271,16 @@ const componentOptions = computed(() => {
})
const defectTypeOptions = computed(() => {
return defectTypes.value.map(type => ({
return defectTypes.value.map((type) => ({
label: type.name,
value: type.id
value: type.id,
}))
})
const imageSourceOptions = computed(() => {
return imageSources.value.map(source => ({
return imageSources.value.map((source) => ({
label: source.name,
value: source.code
value: source.code,
}))
})
@ -327,7 +327,7 @@ const onProjectChange = (value: string) => {
const handleFileChange = (fileList: any) => {
const files = Array.from(fileList.target?.files || []) as File[]
files.forEach(file => {
files.forEach((file) => {
if (!file.type.startsWith('image/')) {
Message.warning(`文件 ${file.name} 不是图像文件`)
return
@ -344,7 +344,7 @@ const handleFileChange = (fileList: any) => {
file,
name: file.name,
size: file.size,
preview: e.target?.result as string
preview: e.target?.result as string,
}
fileList.value.push(fileItem)
}
@ -365,7 +365,7 @@ const formatFileSize = (bytes: number): string => {
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
}
const handleImport = async () => {
@ -391,7 +391,7 @@ const handleImport = async () => {
importResult.value = null
try {
const files = fileList.value.map(item => item.file)
const files = fileList.value.map((item) => item.file)
const params: ImageImportParams = {
imageSource: form.value.imageSource,
projectId: form.value.projectId,
@ -401,7 +401,7 @@ const handleImport = async () => {
latitude: form.value.latitude || undefined,
longitude: form.value.longitude || undefined,
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,
}
//
@ -428,16 +428,15 @@ const handleImport = async () => {
type: file.type,
projectId: form.value.projectId,
componentId: form.value.componentId,
createTime: new Date().toISOString()
createTime: new Date().toISOString(),
})),
failed: []
failed: [],
}
importResult.value = mockResult
emit('importSuccess', mockResult)
Message.success(`成功导入 ${files.length} 个图像文件`)
} catch (error) {
console.error('导入失败:', error)
importProgress.value = 100
@ -469,7 +468,7 @@ const resetForm = () => {
latitude: '',
longitude: '',
settings: [],
annotationTypes: []
annotationTypes: [],
}
fileList.value = []
importResult.value = null

View File

@ -42,8 +42,8 @@
:key="getPartId(part)"
class="part-item"
:class="{ selected: String(selectedPartId) === String(getPartId(part)) }"
@click="selectPart(part)"
:title="`部件ID: ${getPartId(part)}, 选中: ${String(selectedPartId) === String(getPartId(part))}`"
@click="selectPart(part)"
>
<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">
@ -67,7 +67,7 @@
</div>
</div>
<div class="part-info" v-if="selectedPart">
<div v-if="selectedPart" class="part-info">
<div class="info-line">
<div class="info-label">部件:</div>
<div class="info-value">{{ getPartName(selectedPart) }}</div>
@ -90,14 +90,14 @@
<span class="button-icon">+</span>
添加图像
</button>
<button class="action-button" @click="handleRemoveImages" :disabled="!hasSelectedImages">
<button class="action-button" :disabled="!hasSelectedImages" @click="handleRemoveImages">
<span class="button-icon">-</span>
移除图像
</button>
<!-- 隐藏的文件输入框 -->
<input
type="file"
ref="fileInput"
type="file"
accept="image/*"
style="display: none;"
multiple
@ -110,7 +110,7 @@
<thead>
<tr>
<th class="checkbox-column">
<input type="checkbox" @change="toggleSelectAll" :checked="allImagesSelected">
<input type="checkbox" :checked="allImagesSelected" @change="toggleSelectAll">
</th>
<th class="preview-column">预览</th>
<th>图像名称</th>
@ -123,7 +123,7 @@
<tbody>
<tr v-for="(image, index) in importImages" :key="index" @click="toggleImageSelection(image)">
<td>
<input type="checkbox" v-model="image.selected" @click.stop>
<input v-model="image.selected" type="checkbox" @click.stop>
</td>
<td class="preview-cell">
<img v-if="image.previewUrl" :src="image.previewUrl" class="preview-thumbnail" alt="预览">
@ -150,9 +150,9 @@
<div class="form-row">
<div class="form-label">拍摄时间范围</div>
<div class="form-input datetime-range">
<input type="text" v-model="imageInfo.startTime" placeholder="开始时间">
<input v-model="imageInfo.startTime" type="text" placeholder="开始时间">
<span class="range-separator"></span>
<input type="text" v-model="imageInfo.endTime" placeholder="结束时间">
<input v-model="imageInfo.endTime" type="text" placeholder="结束时间">
</div>
</div>
@ -173,13 +173,13 @@
<div class="form-input temperature-range">
<div class="range-input-group">
<button class="range-btn" @click="imageInfo.minTemperature = Math.max(0, imageInfo.minTemperature - 1)">-</button>
<input type="number" v-model="imageInfo.minTemperature" step="0.1" min="0" max="50">
<input v-model="imageInfo.minTemperature" type="number" step="0.1" min="0" max="50">
<button class="range-btn" @click="imageInfo.minTemperature = Math.min(50, imageInfo.minTemperature + 1)">+</button>
</div>
<span class="range-separator"></span>
<div class="range-input-group">
<button class="range-btn" @click="imageInfo.maxTemperature = Math.max(0, imageInfo.maxTemperature - 1)">-</button>
<input type="number" v-model="imageInfo.maxTemperature" step="0.1" min="0" max="50">
<input v-model="imageInfo.maxTemperature" type="number" step="0.1" min="0" max="50">
<button class="range-btn" @click="imageInfo.maxTemperature = Math.min(50, imageInfo.maxTemperature + 1)">+</button>
</div>
</div>
@ -190,7 +190,7 @@
<div class="form-input">
<div class="range-input-group">
<button class="range-btn" @click="imageInfo.humidity = Math.max(0, imageInfo.humidity - 1)">-</button>
<input type="number" v-model="imageInfo.humidity" min="0" max="100">
<input v-model="imageInfo.humidity" type="number" min="0" max="100">
<button class="range-btn" @click="imageInfo.humidity = Math.min(100, imageInfo.humidity + 1)">+</button>
</div>
</div>
@ -217,11 +217,11 @@
<div class="form-label">拍摄方式</div>
<div class="form-input capture-method">
<label class="radio-option">
<input type="radio" v-model="imageInfo.captureMethod" value="无人机航拍">
<input v-model="imageInfo.captureMethod" type="radio" value="无人机航拍">
<span class="radio-label">无人机航拍</span>
</label>
<label class="radio-option">
<input type="radio" v-model="imageInfo.captureMethod" value="人工拍摄">
<input v-model="imageInfo.captureMethod" type="radio" value="人工拍摄">
<span class="radio-label">人工拍摄</span>
</label>
</div>
@ -232,7 +232,7 @@
<div class="form-input">
<div class="range-input-group">
<button class="range-btn" @click="imageInfo.captureDistance = Math.max(0, imageInfo.captureDistance - 1)">-</button>
<input type="number" v-model="imageInfo.captureDistance" min="0">
<input v-model="imageInfo.captureDistance" type="number" min="0">
<button class="range-btn" @click="imageInfo.captureDistance = imageInfo.captureDistance + 1">+</button>
</div>
</div>
@ -241,14 +241,14 @@
<div class="form-row">
<div class="form-label">采集员</div>
<div class="form-input">
<input type="text" v-model="imageInfo.operator">
<input v-model="imageInfo.operator" type="text">
</div>
</div>
<div class="form-row">
<div class="form-label">相机型号</div>
<div class="form-input">
<input type="text" v-model="imageInfo.cameraModel">
<input v-model="imageInfo.cameraModel" type="text">
</div>
</div>
</div>
@ -262,18 +262,24 @@
v-if="currentStep > 1"
class="dialog-button"
@click="currentStep--"
>上一步</button>
>
上一步
</button>
<button
v-if="currentStep < 3"
class="dialog-button"
@click="nextStep"
:disabled="!canGoNext"
>下一步</button>
@click="nextStep"
>
下一步
</button>
<button
v-if="currentStep === 3"
class="dialog-button primary"
@click="finishImport"
>完成导入</button>
>
完成导入
</button>
<button class="dialog-button" @click="closeDialog">取消</button>
</div>
</div>
@ -281,7 +287,7 @@
</template>
<script setup lang="ts">
import { ref, computed, reactive, onBeforeUnmount } from 'vue'
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
//
@ -352,7 +358,7 @@ const selectedPartId = ref('')
//
const selectedPart = computed(() => {
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))
})
//
@ -360,8 +366,8 @@ const importImages = ref<ImportImage[]>([])
//
const imageInfo = reactive<ImageInfo>({
startTime: formatCurrentDate() + ' 00:00',
endTime: formatCurrentDate() + ' 23:59',
startTime: `${formatCurrentDate()} 00:00`,
endTime: `${formatCurrentDate()} 23:59`,
weather: '晴天',
humidity: 50,
minTemperature: 15,
@ -371,7 +377,7 @@ const imageInfo = reactive<ImageInfo>({
captureMethod: '无人机航拍',
captureDistance: 50,
operator: '',
cameraModel: 'ILCE-7RM4'
cameraModel: 'ILCE-7RM4',
})
//
@ -434,7 +440,7 @@ function handleAddImages() {
function handleFileSelected(event: Event) {
const target = event.target as HTMLInputElement
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
const previewUrl = URL.createObjectURL(file)
@ -448,7 +454,7 @@ function handleFileSelected(event: Event) {
timestamp: new Date().toISOString(),
pixelSize: '155.00',
selected: false,
previewUrl
previewUrl,
}
})
@ -463,13 +469,13 @@ function handleFileSelected(event: Event) {
//
function handleRemoveImages() {
// URL
importImages.value.filter(image => image.selected).forEach(image => {
importImages.value.filter((image) => image.selected).forEach((image) => {
if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl)
}
})
importImages.value = importImages.value.filter(image => !image.selected)
importImages.value = importImages.value.filter((image) => !image.selected)
}
//
@ -480,17 +486,17 @@ function toggleImageSelection(image: ImportImage) {
// /
function toggleSelectAll(event: Event) {
const checked = (event.target as HTMLInputElement).checked
importImages.value.forEach(image => image.selected = checked)
importImages.value.forEach((image) => image.selected = checked)
}
//
const hasSelectedImages = computed(() => {
return importImages.value.some(image => image.selected)
return importImages.value.some((image) => image.selected)
})
//
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)
})
//
@ -532,7 +538,7 @@ function finishImport() {
}
//
const files = importImages.value.map(image => image.file!).filter(Boolean)
const files = importImages.value.map((image) => image.file!).filter(Boolean)
//
const partData = {
@ -540,14 +546,14 @@ function finishImport() {
id: getPartId(selectedPart.value), //
name: getPartName(selectedPart.value),
partName: getPartName(selectedPart.value), //
partType: selectedPart.value.partType
partType: selectedPart.value.partType,
}
//
emit('import-success', {
part: partData,
images: files,
imageInfo: { ...imageInfo }
imageInfo: { ...imageInfo },
})
Message.success('图像导入成功')
@ -569,7 +575,7 @@ function resetState() {
selectedPartId.value = ''
// URL
importImages.value.forEach(image => {
importImages.value.forEach((image) => {
if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl)
}
@ -578,8 +584,8 @@ function resetState() {
//
Object.assign(imageInfo, {
startTime: formatCurrentDate() + ' 00:00',
endTime: formatCurrentDate() + ' 23:59',
startTime: `${formatCurrentDate()} 00:00`,
endTime: `${formatCurrentDate()} 23:59`,
weather: '晴天',
humidity: 70,
minTemperature: 20,
@ -587,14 +593,14 @@ function resetState() {
windPower: 0,
captureMethod: '无人机拍摄',
captureDistance: 15,
operator: ''
operator: '',
})
}
// URL
onBeforeUnmount(() => {
// URL
importImages.value.forEach(image => {
importImages.value.forEach((image) => {
if (image.previewUrl) {
URL.revokeObjectURL(image.previewUrl)
}

View File

@ -1,13 +1,13 @@
<template>
<div class="industrial-image-list" :class="{ 'collapsed': isCollapsed }">
<div class="header-actions" v-if="!isCollapsed">
<div class="industrial-image-list" :class="{ collapsed: isCollapsed }">
<div v-if="!isCollapsed" class="header-actions">
<slot name="header-left">
<a-button v-if="showImportButton" type="primary" @click="handleImportImages">
<template #icon><icon-upload /></template>
<template #icon><IconUpload /></template>
导入图像
</a-button>
</slot>
<div class="search-bar" v-if="showSearch">
<div v-if="showSearch" class="search-bar">
<a-input-search
v-model="searchKeyword"
placeholder="输入关键字搜索"
@ -23,16 +23,16 @@
@click="toggleCollapse"
>
<template #icon>
<icon-up />
<IconUp />
</template>
收起
</a-button>
</div>
</div>
<div class="image-grid" v-show="!isCollapsed">
<div v-show="!isCollapsed" class="image-grid">
<div v-if="imageList.length === 0" class="empty-data">
<icon-image class="empty-icon" />
<IconImage class="empty-icon" />
<p>{{ emptyText }}</p>
</div>
@ -51,8 +51,8 @@
@error="handleImageError"
@load="handleImageLoad"
/>
<div class="image-placeholder" v-if="!image.imagePath">
<icon-image />
<div v-if="!image.imagePath" class="image-placeholder">
<IconImage />
<span>暂无图像</span>
</div>
<div class="thumbnail-overlay">
@ -62,13 +62,13 @@
</div>
<div class="image-actions">
<a-button v-if="showPreviewAction" type="text" size="small" @click.stop="handleImagePreview(image)">
<icon-eye />
<IconEye />
</a-button>
<a-button v-if="showProcessAction" type="text" size="small" @click.stop="handleImageProcess(image)">
<icon-settings />
<IconSettings />
</a-button>
<a-button v-if="showDeleteAction" type="text" size="small" status="danger" @click.stop="handleImageDelete(image)">
<icon-delete />
<IconDelete />
</a-button>
<slot name="item-actions" :image="image"></slot>
</div>
@ -81,7 +81,7 @@
<span v-if="image.defectCount" class="defect-count">缺陷: {{ image.defectCount }}</span>
<slot name="item-meta" :image="image"></slot>
</div>
<div class="thumbnail-extra" v-if="image.partName || image.shootingTime">
<div v-if="image.partName || image.shootingTime" class="thumbnail-extra">
<span v-if="image.partName" class="part-name">{{ image.partName }}</span>
<span v-if="image.shootingTime" class="capture-time">{{ formatTime(image.shootingTime) }}</span>
</div>
@ -98,7 +98,7 @@
@click="toggleCollapse"
>
<template #icon>
<icon-down />
<IconDown />
</template>
展开图像列表
</a-button>
@ -109,13 +109,13 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
IconUpload,
IconImage,
IconEye,
IconSettings,
IconDelete,
IconDown,
IconEye,
IconImage,
IconSettings,
IconUp,
IconDown
IconUpload,
} from '@arco-design/web-vue/es/icon'
export interface IndustrialImage {
@ -133,40 +133,40 @@ export interface IndustrialImage {
const props = defineProps({
imageList: {
type: Array as () => IndustrialImage[],
default: () => []
default: () => [],
},
selectedImageId: {
type: String,
default: ''
default: '',
},
baseUrl: {
type: String,
default: 'http://pms.dtyx.net:9158'
default: 'http://localhost:8080',
},
emptyText: {
type: String,
default: '暂无图像数据'
default: '暂无图像数据',
},
showImportButton: {
type: Boolean,
default: true
default: true,
},
showSearch: {
type: Boolean,
default: true
default: true,
},
showPreviewAction: {
type: Boolean,
default: true
default: true,
},
showProcessAction: {
type: Boolean,
default: true
default: true,
},
showDeleteAction: {
type: Boolean,
default: true
}
default: true,
},
})
const emit = defineEmits<{
@ -252,7 +252,7 @@ const formatTime = (timeString: string): string => {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
minute: '2-digit',
})
} catch {
return timeString

View File

@ -1,8 +1,10 @@
<template>
<div class="turbine-grid-container">
<div class="turbine-grid">
<div v-for="turbine in turbines" :key="turbine.id" class="turbine-card"
:class="getStatusClass(turbine.status)">
<div
v-for="turbine in turbines" :key="turbine.id" class="turbine-card"
:class="getStatusClass(turbine.status)"
>
<div class="turbine-status-badge" :class="`status-${turbine.status}`">
{{ getStatusText(turbine.status) }}
</div>
@ -13,16 +15,18 @@
<div class="turbine-info">
<div class="turbine-number">
<a-input v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
@change="handleTurbineNoChange(turbine)" />
<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="地图选点">
<a-button type="text" size="mini" title="地图选点" @click="openMapModal(turbine)">
<template #icon><icon-location /></template>
</a-button>
<a-button type="text" size="mini" @click="editTurbine(turbine)" title="编辑">
<a-button type="text" size="mini" title="编辑" @click="editTurbine(turbine)">
<template #icon><icon-edit /></template>
</a-button>
</div>
@ -40,7 +44,6 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Message } from '@arco-design/web-vue'
interface Turbine {
@ -63,7 +66,7 @@ interface Emits {
}
const props = withDefaults(defineProps<Props>(), {
showAddButton: false
showAddButton: false,
})
const emit = defineEmits<Emits>()
@ -72,7 +75,7 @@ const getStatusText = (status: number) => {
const statusMap = {
0: '待施工',
1: '施工中',
2: '已完成'
2: '已完成',
}
return statusMap[status] || '未知状态'
}

View File

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

View File

@ -16,10 +16,10 @@ export function useDept(options?: { onSuccess?: () => void }) {
const processDeptData = (data: any[]): TreeNodeData[] => {
if (!data || !data.length) return []
return data.map(item => ({
return data.map((item) => ({
key: item.deptId,
title: item.deptName || '未命名部门', // 将deptName映射为title
children: item.children ? processDeptData(item.children) : []
children: item.children ? processDeptData(item.children) : [],
}))
}

View File

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

View File

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

View File

@ -19,7 +19,6 @@ import Asider from './components/Asider/index.vue'
import Header from './components/Header/index.vue'
import Main from './components/Main.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 { useAppStore } from '@/stores'
import { useDevice } from '@/hooks'

View File

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

View File

@ -76,11 +76,10 @@
<script setup lang="ts">
import { Modal } from '@arco-design/web-vue'
import { useFullscreen } from '@vueuse/core'
import { onMounted, ref, nextTick } from 'vue'
import { nextTick, onMounted, ref } from 'vue'
import Message from './Message.vue'
import SettingDrawer from './SettingDrawer.vue'
import Search from './Search.vue'
import { getUnreadMessageCount } from '@/apis'
import { useUserStore } from '@/stores'
import { getToken } from '@/utils/auth'
import { useBreakpoint, useDevice } from '@/hooks'

View File

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

View File

@ -2,7 +2,7 @@ import { createApp } from 'vue'
import ArcoVue, { Card, Drawer, Modal } from '@arco-design/web-vue'
import '@/styles/arco-ui/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图标库
import ArcoVueIcon from '@arco-design/web-vue/es/icon'

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import { computed, reactive, toRefs, watch, watchEffect } from 'vue'
import { computed, reactive, toRefs, watchEffect } from 'vue'
import { generate, getRgbStr } from '@arco-design/color'
import { type BasicConfig, listSiteOptionDict } from '@/apis'
import type { BasicConfig } from '@/apis'
import { getSettings } from '@/config/setting'
const storeSetup = () => {

View File

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

View File

@ -83,7 +83,7 @@ const storeSetup = () => {
// 合并路由
const setRoutes = (data: RouteRecordRaw[]) => {
// 合并路由并排序
routes.value = [...constantRoutes, ...systemRoutes].concat(data)
routes.value = [...constantRoutes, ...systemRoutes, ...data]
.sort((a, b) => (a.meta?.sort ?? 0) - (b.meta?.sort ?? 0))
asyncRoutes.value = data
}
@ -152,23 +152,39 @@ const storeSetup = () => {
{
id: 1070,
parentId: 1000,
title: '个人中心',
title: '部门管理',
type: 2,
path: '/user/profile',
name: 'UserProfile',
component: 'user/profile/index',
icon: 'user',
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,
},
],
}]
// 使用已转换的数据生成路由
const asyncRoutes = formatAsyncRoutes(data as unknown as RouteItem[])
const flatRoutes = flatMultiLevelRoutes(cloneDeep(asyncRoutes))
setRoutes(asyncRoutes)
// 合并路由,避免重复
const allRoutes = [...asyncRoutes]
const flatRoutes = flatMultiLevelRoutes(cloneDeep(allRoutes))
setRoutes(allRoutes)
return flatRoutes
}

View File

@ -5,10 +5,10 @@ import {
type AccountLoginReq,
AuthTypeConstants,
type PhoneLoginReq,
type UserDetail,
type DeptDetail,
type PhoneLoginReq,
type RoleDetail,
type UserDetail,
type UserInfo,
accountLogin as accountLoginApi,
@ -21,10 +21,10 @@ import { clearToken, getToken, setToken } from '@/utils/auth'
import { resetHasRouteFlag } from '@/router/guard'
interface NewUserInfoData {
user: UserDetail;
dept: DeptDetail;
roles: RoleDetail[];
posts: any[];
user: UserDetail
dept: DeptDetail
roles: RoleDetail[]
posts: any[]
}
const storeSetup = () => {
@ -43,7 +43,7 @@ const storeSetup = () => {
deptName: '',
avatar: '',
roles: [] as string[],
permissions: [] as string[]
permissions: [] as string[],
})
const nickname = computed(() => userInfo.name)
const username = computed(() => userInfo.account)
@ -68,8 +68,6 @@ const storeSetup = () => {
token.value = res.data.tokenValue
}
// 手机号登录
const phoneLogin = async (req: PhoneLoginReq) => {
const res = await phoneLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.PHONE })
@ -77,8 +75,6 @@ const storeSetup = () => {
token.value = res.data.token
}
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
@ -128,7 +124,7 @@ const storeSetup = () => {
// 处理角色信息
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
// 由于新API没有直接提供permissions这里默认给管理员全部权限

View File

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

View File

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

View File

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

41
src/test-console.vue Normal file
View File

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

View File

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

71
src/types/training.d.ts vendored Normal file
View File

@ -0,0 +1,71 @@
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
}

44
src/utils/date.ts Normal file
View File

@ -0,0 +1,44 @@
import dayjs from 'dayjs'
/**
*
* @param date
* @param format 'YYYY-MM-DD HH:mm:ss'
* @returns
*/
export function formatDateTime(date: string | Date | null | undefined, format = 'YYYY-MM-DD HH:mm:ss'): string {
if (!date) return '-'
return dayjs(date).format(format)
}
/**
*
* @param date
* @param format 'YYYY-MM-DD'
* @returns
*/
export function formatDate(date: string | Date | null | undefined, format = 'YYYY-MM-DD'): string {
if (!date) return '-'
return dayjs(date).format(format)
}
/**
*
* @param date
* @param format 'HH:mm:ss'
* @returns
*/
export function formatTime(date: string | Date | null | undefined, format = 'HH:mm:ss'): string {
if (!date) return '-'
return dayjs(date).format(format)
}
/**
*
* @param date
* @returns
*/
export function getRelativeTime(date: string | Date | null | undefined): string {
if (!date) return '-'
return dayjs(date).fromNow()
}

View File

@ -30,7 +30,7 @@ const StatusCodeMessage: ICodeMessage = {
}
const http: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 30 * 1000,
})
@ -145,6 +145,11 @@ const request = async <T = unknown>(config: AxiosRequestConfig): Promise<ApiRes<
responseData.data = responseData.rows
}
// 兼容后端返回的 status/code 格式
if (responseData.status === 200 || responseData.code === 200 || responseData.code === '200') {
responseData.success = true
}
// 如果返回的code是200但没有设置success字段将success设置为true
if ((responseData.code === 200 || responseData.code === '200') && responseData.success === undefined) {
responseData.success = true

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -37,11 +37,11 @@
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">
<template #icon><icon-search /></template>
<template #icon><IconSearch /></template>
搜索
</a-button>
<a-button @click="resetSearch">
<template #icon><icon-refresh /></template>
<template #icon><IconRefresh /></template>
重置
</a-button>
</a-space>
@ -55,7 +55,7 @@
<template #extra>
<a-space>
<a-button type="primary" size="small" :disabled="selectedRowKeys.length === 0">
<template #icon><icon-download /></template>
<template #icon><IconDownload /></template>
批量下载
</a-button>
<a-button
@ -65,7 +65,7 @@
:disabled="selectedRowKeys.length === 0"
@click="handleBatchDelete"
>
<template #icon><icon-delete /></template>
<template #icon><IconDelete /></template>
批量删除
</a-button>
</a-space>
@ -75,14 +75,14 @@
:loading="loading"
:data="tableData"
:pagination="pagination"
@page-change="onPageChange"
row-key="id"
:row-selection="{
type: 'checkbox',
showCheckedAll: true,
selectedRowKeys: selectedRowKeys,
onChange: onSelectionChange
selectedRowKeys,
onChange: onSelectionChange,
}"
@page-change="onPageChange"
>
<template #columns>
<a-table-column title="文件名" data-index="fileName">
@ -116,11 +116,11 @@
<template #cell="{ record }">
<a-space>
<a-button size="small" @click="previewFile(record)">
<template #icon><icon-eye /></template>
<template #icon><IconEye /></template>
预览
</a-button>
<a-button size="small" @click="downloadFile(record)">
<template #icon><icon-download /></template>
<template #icon><IconDownload /></template>
下载
</a-button>
<a-popconfirm
@ -128,7 +128,7 @@
@ok="deleteFile(record)"
>
<a-button size="small" status="danger">
<template #icon><icon-delete /></template>
<template #icon><IconDelete /></template>
删除
</a-button>
</a-popconfirm>
@ -160,18 +160,18 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { onMounted, reactive, ref } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import FilePreview from '@/components/FilePreview/index.vue'
import {
IconSearch,
IconRefresh,
IconDownload,
IconDelete,
IconDownload,
IconEye,
IconFile
IconFile,
IconRefresh,
IconSearch,
} from '@arco-design/web-vue/es/icon'
import { getAttachBusinessTypes, getAttachmentList, deleteAttachment } from '@/apis/attach-info'
import FilePreview from '@/components/FilePreview/index.vue'
import { deleteAttachment, getAttachBusinessTypes, getAttachmentList } from '@/apis/attach-info'
import type { AttachInfoData, BusinessType } from '@/apis/attach-info/type'
defineOptions({ name: 'AttachmentManagement' })
@ -293,7 +293,7 @@ const deleteFile = async (file: AttachInfoData) => {
if (res) {
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
} else {
Message.error('删除文件失败')
@ -316,7 +316,7 @@ const handleBatchDelete = () => {
cancelText: '取消',
onOk: async () => {
try {
const promises = selectedRowKeys.value.map(id => deleteAttachment(id))
const promises = selectedRowKeys.value.map((id) => deleteAttachment(id))
await Promise.all(promises)
Message.success('批量删除成功')
fetchAttachmentList()
@ -325,7 +325,7 @@ const handleBatchDelete = () => {
console.error('批量删除失败:', error)
Message.error('批量删除失败')
}
}
},
})
}
@ -334,13 +334,13 @@ const getFileTypeText = (type: string) => {
image: '图片',
document: '文档',
video: '视频',
other: '其他'
other: '其他',
}
return typeMap[type] || '未知'
}
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
}

View File

@ -1,7 +1,7 @@
<template>
<div class="attachment-upload">
<a-space direction="vertical" :size="16" style="width: 100%">
<a-form :model="formData" ref="formRef">
<a-form ref="formRef" :model="formData">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="businessType" label="业务类型" required>
@ -64,7 +64,7 @@
</a-form-item>
<div class="form-actions">
<a-button type="primary" @click="handleSubmit" :loading="submitting">提交</a-button>
<a-button type="primary" :loading="submitting" @click="handleSubmit">提交</a-button>
<a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
</div>
</a-form>
@ -73,10 +73,10 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { onMounted, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconPlus } from '@arco-design/web-vue/es/icon'
import { getAttachBusinessTypes, batchAddAttachment } from '@/apis/attach-info'
import { batchAddAttachment, getAttachBusinessTypes } from '@/apis/attach-info'
import type { BusinessType } from '@/apis/attach-info/type'
defineOptions({ name: 'AttachmentUpload' })
@ -98,23 +98,15 @@ const formData = reactive({
fileType: '',
remark: '',
userDefinedPath: '',
files: [] as FileItem[]
files: [] as FileItem[],
})
//
const fetchBusinessTypes = async () => {
try {
const res = await getAttachBusinessTypes()
console.log("res:",res);
if (res.data) {
res.data.forEach(item => {
const key = Object.keys(item)[0];
const value = item[key];
businessTypes.value.push({
name: value,
code:key
});
});
businessTypes.value = res.data
}
} catch (error) {
console.error('获取业务类型失败:', error)
@ -134,7 +126,7 @@ const customRequest = (options: any) => {
file,
status: 'ready',
uid: options.fileItem.uid,
name: options.fileItem.name
name: options.fileItem.name,
}
formData.files.push(fileItem)
@ -180,7 +172,7 @@ const handleSubmit = async () => {
const params = {
fileType: formData.fileType,
remark: formData.remark,
userDefinedPath: formData.userDefinedPath
userDefinedPath: formData.userDefinedPath,
}
const res = await batchAddAttachment(formData.businessType, formDataToSend, params)
@ -193,7 +185,7 @@ const handleSubmit = async () => {
}
} catch (error: any) {
console.error('上传失败:', error)
Message.error('上传失败: ' + (error.msg || '未知错误'))
Message.error(`上传失败: ${error.msg || '未知错误'}`)
} finally {
submitting.value = false
}

View File

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

View File

@ -3,7 +3,7 @@
<div class="panel-header">
<h3>自动识别设置</h3>
<a-button type="text" @click="$emit('close')">
<template #icon><icon-close /></template>
<template #icon><IconClose /></template>
</a-button>
</div>
@ -64,10 +64,10 @@
</div>
<div class="panel-actions">
<a-button type="primary" @click="handleStartRecognition" :loading="isRecognizing" block>
<a-button type="primary" :loading="isRecognizing" block @click="handleStartRecognition">
开始识别
</a-button>
<a-button @click="handleResetSettings" block>
<a-button block @click="handleResetSettings">
重置设置
</a-button>
</div>
@ -76,14 +76,14 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { onMounted, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconClose } from '@arco-design/web-vue/es/icon'
import { useRouter } from 'vue-router'
import { listDefectType } from '@/apis/common/common'
import { getModelConfigList } from '@/apis/model-config'
import type { DefectTypeResp, DefectTypeOption } from '@/apis/common/type'
import type { DefectTypeOption, DefectTypeResp } from '@/apis/common/type'
import type { ModelConfigResponse } from '@/apis/model-config/type'
import { useRouter } from 'vue-router'
const props = defineProps<{
currentImage?: {
@ -126,11 +126,11 @@ const loadModelList = async () => {
if (response && response.rows) {
//
if (Array.isArray(response.rows)) {
modelList.value = response.rows;
modelList.value = response.rows
} else {
//
const responseData = response.rows;
modelList.value = [];
const responseData = response.rows
modelList.value = []
// API
if (responseData && Array.isArray(responseData)) {
@ -140,8 +140,8 @@ const loadModelList = async () => {
attachId: item.attachId || '',
confThreshold: item.confThreshold || 0.5,
nmsThreshold: item.nmsThreshold || 0.5,
modelPath: item.modelPath || ''
}));
modelPath: item.modelPath || '',
}))
}
}
@ -175,7 +175,7 @@ const loadDefectTypes = async () => {
defectTypeOptions.push({
value: code,
label: name,
code: code
code,
})
})
})
@ -184,7 +184,7 @@ const loadDefectTypes = async () => {
//
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) {
console.error('获取缺陷类型失败:', error)
@ -216,7 +216,7 @@ const handleStartRecognition = async () => {
const settings = {
algorithm: selectedAlgorithm.value,
confidence: confidence.value,
defectTypes: selectedDefectTypes.value
defectTypes: selectedDefectTypes.value,
}
try {
@ -235,7 +235,7 @@ const handleStartRecognition = async () => {
const handleResetSettings = () => {
selectedAlgorithm.value = modelList.value.length > 0 ? modelList.value[0].modelId : ''
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({
setRecognizing
setRecognizing,
})
</script>

View File

@ -3,7 +3,7 @@
<div class="form-header">
<h3>缺陷详情</h3>
<a-button type="text" @click="$emit('close')">
<template #icon><icon-close /></template>
<template #icon><IconClose /></template>
</a-button>
</div>
@ -36,7 +36,7 @@
</a-select>
</a-form-item>
<!--轴向尺寸-->
<!-- 轴向尺寸 -->
<div class="dimension-fields">
<a-form-item
field="axialDimension"
@ -54,7 +54,7 @@
</a-input-number>
</a-form-item>
<!--弦向尺寸 -->
<!-- 弦向尺寸 -->
<a-form-item
field="chordDimension"
label="弦向尺寸 (mm)"
@ -104,32 +104,29 @@
/>
</a-form-item>
<div class="form-footer">
<a-button @click="$emit('close')" size="large">取消</a-button>
<a-button size="large" @click="$emit('close')">取消</a-button>
<a-button
type="primary"
size="large"
@click="handleSubmit"
:loading="submitting"
@click="handleSubmit"
>
<template #icon><icon-save /></template>
<template #icon><IconSave /></template>
保存缺陷
</a-button>
</div>
</a-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch, onMounted } from 'vue'
import { onMounted, reactive, ref, watch } from 'vue'
import { IconClose, IconSave } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
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 { getDefectLevels } from '@/apis/industrial-image/defect'
import { listDefectType } from '@/apis/common/common'
import type { DefectTypeResp, DefectTypeOption } from '@/apis/common/type'
import type { DefectTypeOption } from '@/apis/common/type'
interface DefectFormData {
defectName: string
@ -175,7 +172,7 @@ const form = reactive<DefectFormData>({
description: '',
repairIdea: '建议进行进一步检查',
axialDimension: 0, // null
chordDimension: 0 // null
chordDimension: 0, // null
})
//
@ -197,7 +194,7 @@ const loadDefectTypes = async () => {
defectTypeOptions.push({
value: code,
label: name as string,
code: code
code,
})
}
})
@ -220,7 +217,6 @@ const loadDefectTypes = async () => {
}
}
//
const loadDefectLevels = async () => {
try {
@ -233,13 +229,13 @@ const loadDefectLevels = async () => {
//
if (response.data && response.data.code === 0 && Array.isArray(response.data.data)) {
// API
response.data.data.forEach(item => {
response.data.data.forEach((item) => {
defectLevelOptions.push({
code: item.code,
label: item.name, // 使namelabel
value: item.value,
name: item.name,
sort: item.sort
sort: item.sort,
})
})
defectLevels.value = defectLevelOptions
@ -250,10 +246,10 @@ const loadDefectLevels = async () => {
if (entries.length > 0) {
const [code, name] = entries[0]
defectLevelOptions.push({
code: code,
code,
label: name as string,
value: code,
name: name as string
name: name as string,
})
}
})
@ -264,7 +260,7 @@ const loadDefectLevels = async () => {
defectLevels.value = [
{ code: 'low', label: '轻微', value: 'low', name: '轻微' },
{ code: 'medium', label: '中等', value: 'medium', name: '中等' },
{ code: 'high', label: '严重', value: 'high', name: '严重' }
{ code: 'high', label: '严重', value: 'high', name: '严重' },
]
}
@ -272,9 +268,9 @@ const loadDefectLevels = async () => {
//
if (defectLevels.value.length > 0 && !form.defectLevel) {
const mediumLevel = defectLevels.value.find(l =>
l.code.toLowerCase().includes('medium') ||
(l.name && l.name.includes('中'))
const mediumLevel = defectLevels.value.find((l) =>
l.code.toLowerCase().includes('medium')
|| (l.name && l.name.includes('中')),
)
form.defectLevel = mediumLevel?.code || defectLevels.value[0].code
form.defectLevelLabel = mediumLevel?.name || mediumLevel?.label || defectLevels.value[0].label
@ -285,7 +281,7 @@ const loadDefectLevels = async () => {
defectLevels.value = [
{ code: 'low', label: '轻微', value: 'low', name: '轻微' },
{ code: 'medium', label: '中等', value: 'medium', name: '中等' },
{ code: 'high', label: '严重', value: 'high', name: '严重' }
{ code: 'high', label: '严重', value: 'high', name: '严重' },
]
} finally {
loadingDefectLevels.value = false
@ -356,19 +352,18 @@ const handleSubmit = async () => {
if (isMultiAnnotation && annotationCount > 0) {
Message.loading({
content: `正在保存包含${annotationCount}个标注区域的缺陷信息...`,
duration: 0
duration: 0,
})
} else {
Message.loading({
content: '正在保存缺陷信息...',
duration: 0
duration: 0,
})
}
//
console.log("form:",form);
console.log('form:', form)
emit('submit', form, props.annotation)
} catch (error) {
console.error('提交缺陷失败:', error)
Message.error('提交失败,请重试')
@ -379,7 +374,7 @@ const handleSubmit = async () => {
//
const handleDefectTypeChange = (value: string) => {
const selectedType = defectTypes.value.find(type => type.code === value)
const selectedType = defectTypes.value.find((type) => type.code === value)
if (selectedType) {
form.defectTypeLabel = selectedType.name
}
@ -387,7 +382,7 @@ const handleDefectTypeChange = (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) {
form.defectLevelLabel = selectedLevel.name
}

View File

@ -162,8 +162,8 @@
<div class="action-button-container">
<a-button
size="small"
@click="handleSelectFromStandardDescription"
class="standard-library-btn"
@click="handleSelectFromStandardDescription"
>
从标准描述库选择
</a-button>
@ -183,8 +183,8 @@
<div class="action-button-container">
<a-button
size="small"
@click="handleSelectFromStandardInfo"
class="standard-library-btn"
@click="handleSelectFromStandardInfo"
>
从标准信息库选择
</a-button>
@ -257,7 +257,7 @@
<!-- 无缺陷选中状态 -->
<div v-else class="no-defect-selected">
<icon-file class="empty-icon" />
<IconFile class="empty-icon" />
<p>请从左侧选择缺陷进行编辑</p>
</div>
@ -273,9 +273,9 @@
<a-list :data="standardDescriptions" :bordered="false">
<template #item="{ item }">
<a-list-item
:class="{ 'selected': selectedStandardDescription === item }"
@click="selectedStandardDescription = item"
:class="{ selected: selectedStandardDescription === item }"
class="clickable-item"
@click="selectedStandardDescription = item"
>
<div class="description-item">
<div class="description-title">{{ item.title }}</div>
@ -299,9 +299,9 @@
<a-list :data="standardRepairIdeas" :bordered="false">
<template #item="{ item }">
<a-list-item
:class="{ 'selected': selectedStandardInfo === item }"
@click="selectedStandardInfo = item"
:class="{ selected: selectedStandardInfo === item }"
class="clickable-item"
@click="selectedStandardInfo = item"
>
<div class="description-item">
<div class="description-title">{{ item.title }}</div>
@ -316,7 +316,7 @@
</template>
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
import { reactive, ref, watch } from 'vue'
import { IconFile } from '@arco-design/web-vue/es/icon'
import { Message, Modal } from '@arco-design/web-vue'
import type { DefectDetectionResult } from '@/apis/industrial-image/defect'
@ -348,7 +348,7 @@ const defectForm = reactive({
inspector: '',
recheckStatus: '',
technicalNotes: '',
repairRecord: ''
repairRecord: '',
})
//
@ -357,20 +357,20 @@ const selectedStandardDescription = ref<any>(null)
const standardDescriptions = ref([
{
title: '前缘裂纹模板',
content: '叶片前缘纵向裂纹长度约15mm宽度约2mm'
content: '叶片前缘纵向裂纹长度约15mm宽度约2mm',
},
{
title: '表面磨损模板',
content: '叶片表面出现明显磨损痕迹,影响空气动力学性能'
content: '叶片表面出现明显磨损痕迹,影响空气动力学性能',
},
{
title: '截蚀损伤模板',
content: '叶片前缘截蚀损伤表面粗糙度增加深度约1-3mm'
content: '叶片前缘截蚀损伤表面粗糙度增加深度约1-3mm',
},
{
title: '腐蚀斑点模板',
content: '叶片表面出现腐蚀斑点直径约5-10mm深度轻微'
}
content: '叶片表面出现腐蚀斑点直径约5-10mm深度轻微',
},
])
//
@ -379,24 +379,24 @@ const selectedStandardInfo = ref<any>(null)
const standardRepairIdeas = ref([
{
title: '裂纹修复建议',
content: '建议进行表面修复处理,防止裂纹扩散'
content: '建议进行表面修复处理,防止裂纹扩散',
},
{
title: '磨损处理建议',
content: '定期监测磨损程度,必要时进行表面打磨和重新涂层'
content: '定期监测磨损程度,必要时进行表面打磨和重新涂层',
},
{
title: '截蚀修复建议',
content: '建议进行前缘修复,使用专用胶泥填补并重新整形'
content: '建议进行前缘修复,使用专用胶泥填补并重新整形',
},
{
title: '腐蚀处理建议',
content: '清理腐蚀区域,涂抹防腐涂层,定期检查'
content: '清理腐蚀区域,涂抹防腐涂层,定期检查',
},
{
title: '严重损伤建议',
content: '建议立即停机检修,更换受损部件,避免安全隐患'
}
content: '建议立即停机检修,更换受损部件,避免安全隐患',
},
])
//
@ -413,7 +413,7 @@ watch(() => props.selectedDefect, (newDefect) => {
chordwise: newDefect.chordwise || 0,
area: calculateArea(newDefect.axial || 0, newDefect.chordwise || 0),
description: newDefect.description || '',
repairIdea: newDefect.repairIdea || ''
repairIdea: newDefect.repairIdea || '',
})
}
}, { immediate: true })
@ -437,7 +437,7 @@ const handleSave = () => {
const updatedDefect = {
...props.selectedDefect,
...defectForm
...defectForm,
}
emit('edit-defect', updatedDefect)
@ -457,7 +457,7 @@ const handleDelete = () => {
onOk: () => {
emit('delete-defect', props.selectedDefect!.defectId)
Message.success('缺陷已删除')
}
},
})
}
@ -476,7 +476,7 @@ const handleCancel = () => {
chordwise: props.selectedDefect.chordwise || 0,
area: calculateArea(props.selectedDefect.axial || 0, props.selectedDefect.chordwise || 0),
description: props.selectedDefect.description || '',
repairIdea: props.selectedDefect.repairIdea || ''
repairIdea: props.selectedDefect.repairIdea || '',
})
}
}

View File

@ -6,14 +6,14 @@
<a-button
type="primary"
size="small"
@click="handleAddDefect"
:disabled="!canAddDefect"
@click="handleAddDefect"
>
<template #icon><icon-plus /></template>
<template #icon><IconPlus /></template>
新增缺陷
</a-button>
<a-button type="text" @click="$emit('close')">
<template #icon><icon-close /></template>
<template #icon><IconClose /></template>
</a-button>
</div>
</div>
@ -34,11 +34,11 @@
<template #title="node">
<div class="tree-node">
<span class="node-icon">
<icon-folder v-if="node.type === 'project'" />
<icon-settings v-else-if="node.type === 'turbine'" />
<icon-tool v-else-if="node.type === 'part'" />
<icon-bug v-else-if="node.type === 'defect'" />
<icon-apps v-else />
<IconFolder v-if="node.type === 'project'" />
<IconSettings v-else-if="node.type === 'turbine'" />
<IconTool v-else-if="node.type === 'part'" />
<IconBug v-else-if="node.type === 'defect'" />
<IconApps v-else />
</span>
<span class="node-title">{{ node.name }}</span>
<span v-if="node.imageCount" class="node-count">({{ node.imageCount }})</span>
@ -53,9 +53,9 @@
</template>
<script setup lang="ts">
import { IconClose, IconBug, IconFolder, IconPlus, IconSettings, IconTool, IconApps } from '@arco-design/web-vue/es/icon'
import { IconApps, IconBug, IconClose, IconFolder, IconPlus, IconSettings, IconTool } from '@arco-design/web-vue/es/icon'
import type { PropType } from 'vue'
import { ref, computed, watch } from 'vue'
import { computed } from 'vue'
//
export interface TreeNode {
@ -91,28 +91,28 @@ const props = defineProps({
//
defectList: {
type: Array as PropType<DefectInfo[]>,
default: () => []
default: () => [],
},
//
selectedDefect: {
type: Object as PropType<DefectInfo | null>,
default: null
default: null,
},
//
treeData: {
type: Array as PropType<TreeNode[]>,
default: () => []
default: () => [],
},
//
selectedKeys: {
type: Array as PropType<string[]>,
default: () => []
default: () => [],
},
//
loading: {
type: Boolean,
default: false
}
default: false,
},
})
//
@ -122,7 +122,7 @@ const emit = defineEmits([
'load-more',
'add-defect',
'turbine-select',
'close'
'close',
])
//
@ -131,7 +131,7 @@ const enhancedTreeData = computed(() => {
return []
}
return props.treeData.map(project => enhanceTreeNode(project))
return props.treeData.map((project) => enhanceTreeNode(project))
})
//
@ -142,11 +142,11 @@ const enhanceTreeNode = (node: TreeNode): TreeNode => {
//
const defectNodes = getDefectNodesForTurbine(node.id)
enhancedNode.children = [
...(node.children || []).map(child => enhanceTreeNode(child)),
...defectNodes
...(node.children || []).map((child) => enhanceTreeNode(child)),
...defectNodes,
]
} else if (node.children) {
enhancedNode.children = node.children.map(child => enhanceTreeNode(child))
enhancedNode.children = node.children.map((child) => enhanceTreeNode(child))
}
return enhancedNode
@ -155,19 +155,19 @@ const enhanceTreeNode = (node: TreeNode): TreeNode => {
//
const getDefectNodesForTurbine = (turbineId: string): TreeNode[] => {
// ID
const turbineDefects = props.defectList.filter(defect =>
defect.turbineId === turbineId || defect.imageId === turbineId
const turbineDefects = props.defectList.filter((defect) =>
defect.turbineId === turbineId || defect.imageId === turbineId,
)
//
return turbineDefects.map(defect => ({
return turbineDefects.map((defect) => ({
id: defect.id,
name: defect.defectName || '未命名缺陷',
type: 'defect',
defectLevel: defect.defectLevel,
defectType: defect.defectType,
detectionDate: defect.detectionDate,
defectData: defect //
defectData: defect, //
}))
}
@ -182,7 +182,7 @@ const handleNodeSelect = (selectedKeys: string[], e: any) => {
//
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) {
emit('defect-select', defect)
}

View File

@ -1,16 +1,16 @@
<template>
<div class="header-toolbar">
<div class="toolbar-buttons">
<a-button size="large" @click="handleAutoAnnotate" :disabled="!currentImageId">
<template #icon><icon-robot /></template>
<a-button size="large" :disabled="!currentImageId" @click="handleAutoAnnotate">
<template #icon><IconRobot /></template>
自动标注
</a-button>
<a-button size="large" @click="handleManualAnnotate" :disabled="!currentImageId">
<template #icon><icon-edit /></template>
<a-button size="large" :disabled="!currentImageId" @click="handleManualAnnotate">
<template #icon><IconEdit /></template>
手动标注
</a-button>
<a-button size="large" @click="handleGenerateReport">
<template #icon><icon-file-image /></template>
<template #icon><IconFileImage /></template>
生成检测报告
</a-button>
</div>
@ -19,10 +19,9 @@
<script setup lang="ts">
import {
IconPlayArrow,
IconRobot,
IconEdit,
IconFileImage
IconFileImage,
IconRobot,
} from '@arco-design/web-vue/es/icon'
defineProps<{
@ -40,8 +39,6 @@ const handleStart = () => {
emit('start')
}
const handleAutoAnnotate = () => {
emit('autoAnnotate')
}

View File

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

View File

@ -4,20 +4,20 @@
<!-- 缩放控制工具栏 -->
<div v-if="selectedImage" class="zoom-controls">
<a-space>
<a-button size="small" @click="zoomOut" :disabled="scale <= MIN_SCALE">
<a-button size="small" :disabled="scale <= MIN_SCALE" @click="zoomOut">
<template #icon>
<icon-minus />
<IconMinus />
</template>
</a-button>
<span class="zoom-text">{{ Math.round(scale * 100) }}%</span>
<a-button size="small" @click="zoomIn" :disabled="scale >= MAX_SCALE">
<a-button size="small" :disabled="scale >= MAX_SCALE" @click="zoomIn">
<template #icon>
<icon-plus />
<IconPlus />
</template>
</a-button>
<a-button size="small" @click="resetZoom">
<template #icon>
<icon-refresh />
<IconRefresh />
</template>
重置
</a-button>
@ -32,7 +32,7 @@
<div
v-if="selectedImage"
class="image-viewer"
:class="{ 'dragging': isDragging }"
:class="{ dragging: isDragging }"
@wheel="handleWheel"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@ -43,7 +43,7 @@
class="image-container"
:style="{
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
@ -55,7 +55,7 @@
</div>
</div>
<div v-else class="empty-preview">
<icon-image class="empty-icon" />
<IconImage class="empty-icon" />
<p>请从下方缩略图中选择图像</p>
</div>
</div>
@ -64,7 +64,7 @@
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { nextTick, ref, watch } from 'vue'
import { IconImage, IconMinus, IconPlus, IconRefresh } from '@arco-design/web-vue/es/icon'
const props = defineProps<{
@ -153,7 +153,7 @@ const getBoundaryLimits = () => {
maxX,
maxY,
minX: -maxX,
minY: -maxY
minY: -maxY,
}
}
@ -226,7 +226,7 @@ const handleMouseLeave = () => {
const getImageUrl = (imagePath: string): string => {
if (!imagePath) return ''
if (imagePath.startsWith('http')) return imagePath
const baseUrl = 'http://pms.dtyx.net:9158'
const baseUrl = 'http://localhost:8080'
return `${baseUrl}${imagePath}`
}
</script>

View File

@ -15,10 +15,10 @@
<template #title="node">
<div class="tree-node">
<span class="node-icon">
<icon-folder v-if="node.type === 'project'" />
<icon-settings v-else-if="node.type === 'turbine'" />
<icon-tool v-else-if="node.type === 'part'" />
<icon-apps v-else />
<IconFolder v-if="node.type === 'project'" />
<IconSettings v-else-if="node.type === 'turbine'" />
<IconTool v-else-if="node.type === 'part'" />
<IconApps v-else />
</span>
<span class="node-title">{{ node.name }}</span>
<span v-if="node.imageCount" class="node-count">({{ node.imageCount }})</span>
@ -31,10 +31,10 @@
<script setup lang="ts">
import {
IconApps,
IconFolder,
IconSettings,
IconTool,
IconApps
} from '@arco-design/web-vue/es/icon'
import type { ProjectTreeNode } from '@/apis/industrial-image'

View File

@ -4,11 +4,11 @@
<h3>识别结果</h3>
<div class="header-actions">
<a-button type="text" @click="handleSaveResults">
<template #icon><icon-save /></template>
<template #icon><IconSave /></template>
保存结果
</a-button>
<a-button type="text" @click="handleExportResults">
<template #icon><icon-export /></template>
<template #icon><IconExport /></template>
导出
</a-button>
</div>
@ -73,10 +73,10 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { computed, ref } from 'vue'
import {
IconExport,
IconSave,
IconExport
} from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
@ -105,7 +105,7 @@ const statistics = computed(() => {
wear: 0,
wearConfidence: 0,
deformation: 0,
deformationConfidence: 0
deformationConfidence: 0,
}
if (props.results.length === 0) return stats
@ -137,28 +137,28 @@ const statistics = computed(() => {
}, {} as Record<string, DefectDetectionResult[]>)
//
const crackResults = categorizedResults['crack'] || []
const crackResults = categorizedResults.crack || []
stats.crack = crackResults.length
stats.crackConfidence = crackResults.length > 0
? Math.round(crackResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / crackResults.length)
: 0
//
const corrosionResults = categorizedResults['corrosion'] || []
const corrosionResults = categorizedResults.corrosion || []
stats.corrosion = corrosionResults.length
stats.corrosionConfidence = corrosionResults.length > 0
? Math.round(corrosionResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / corrosionResults.length)
: 0
//
const wearResults = categorizedResults['wear'] || []
const wearResults = categorizedResults.wear || []
stats.wear = wearResults.length
stats.wearConfidence = wearResults.length > 0
? Math.round(wearResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / wearResults.length)
: 0
//
const deformationResults = categorizedResults['deformation'] || []
const deformationResults = categorizedResults.deformation || []
stats.deformation = deformationResults.length
stats.deformationConfidence = deformationResults.length > 0
? Math.round(deformationResults.reduce((sum, r) => sum + ((r.markInfo?.confidence || 0) * 100), 0) / deformationResults.length)
@ -227,7 +227,7 @@ const getRecommendation = (type: string, confidence: number): string => {
deformation: '检查结构完整性',
scratch: '轻微处理即可',
hole: '立即修补',
dirt: '清洁处理'
dirt: '清洁处理',
}
return recommendations[type] || '建议进一步检查'
}

View File

@ -1,35 +1,18 @@
import { ref, reactive, computed } from 'vue'
import { computed, ref } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import {
type PartInfo,
type ProjectInfo,
type ProjectTreeNode,
type TurbineInfo,
deleteImage,
getImageList,
getPartList,
getProjectList,
getTurbineList,
getPartList,
getImageList,
deleteImage,
autoAnnotateImage,
generateReport,
batchUploadImages,
uploadImageToPartV2,
type ProjectTreeNode,
type IndustrialImage,
type ProjectInfo,
type TurbineInfo,
type PartInfo
} from '@/apis/industrial-image'
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'
import type { type DefectDetectionRequest, type DefectDetectionResult, DefectInfo, type ManualDefectAddRequest, addDefect, addManualDefect, deleteDefect, detectDefects, getDefectList, updateDefect } from '@/apis/industrial-image/defect'
export function useIndustrialImage() {
// 项目树数据
@ -73,13 +56,14 @@ export function useIndustrialImage() {
return [{
id: selectedDefect.value.id,
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 + selectedDefect.value.boundingBox.width,
y: selectedDefect.value.boundingBox.y + selectedDefect.value.boundingBox.height }
] : [],
{ x: selectedDefect.value.boundingBox.x + selectedDefect.value.boundingBox.width, y: selectedDefect.value.boundingBox.y + selectedDefect.value.boundingBox.height },
]
: [],
color: selectedDefect.value.severity === 'high' ? '#ff4d4f' : '#faad14',
label: selectedDefect.value.defectType
label: selectedDefect.value.defectType,
}]
})
@ -113,7 +97,7 @@ export function useIndustrialImage() {
projects = []
}
projectTreeData.value = projects.map(project => ({
projectTreeData.value = projects.map((project) => ({
id: project.projectId,
name: project.projectName,
type: 'project' as const,
@ -123,7 +107,7 @@ export function useIndustrialImage() {
expanded: false,
status: project.status,
createTime: project.createTime,
rawData: project
rawData: project,
}))
// 默认选中第一个项目
@ -153,7 +137,7 @@ export function useIndustrialImage() {
const response = await getTurbineList({ projectId: node.id })
const turbines = response.data || []
node.children = turbines.map(turbine => ({
node.children = turbines.map((turbine) => ({
id: turbine.turbineId || turbine.projectId,
name: turbine.turbineName || turbine.turbineDesc || `机组${turbine.turbineId || turbine.projectId}`,
type: 'turbine' as const,
@ -164,16 +148,16 @@ export function useIndustrialImage() {
expanded: false,
status: turbine.status,
createTime: turbine.createTime,
rawData: turbine
rawData: turbine,
}))
} else if (node.type === 'turbine') {
const turbineData = node.rawData as TurbineInfo
const response = await getPartList({
turbineId: turbineData?.turbineId || node.id
turbineId: turbineData?.turbineId || node.id,
})
const parts = response.data || []
node.children = parts.map(part => ({
node.children = parts.map((part) => ({
id: part.partId,
name: part.partName || part.partType || `部件${part.partId}`,
type: 'part' as const,
@ -184,7 +168,7 @@ export function useIndustrialImage() {
expanded: false,
status: part.partType,
createTime: part.createTime,
rawData: part
rawData: part,
}))
}
@ -222,7 +206,7 @@ export function useIndustrialImage() {
try {
const turbineData = turbineNode.rawData as TurbineInfo
const response = await getPartList({
turbineId: turbineData?.turbineId || turbineNode.id
turbineId: turbineData?.turbineId || turbineNode.id,
})
const parts = response.data || []
@ -288,8 +272,6 @@ export function useIndustrialImage() {
}
}
// 查找选中的节点
const findSelectedNode = (nodes: ProjectTreeNode[], nodeId: string): ProjectTreeNode | null => {
if (!nodes || !Array.isArray(nodes) || !nodeId) return null
@ -316,7 +298,7 @@ export function useIndustrialImage() {
if (!node) continue
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
const found = findParentNode(node.children, childId)
@ -349,7 +331,7 @@ export function useIndustrialImage() {
}
const handleImagePreview = (image: any) => {
console.log("image:",image);
console.log('image:', image)
previewImage.value = image
previewModalVisible.value = true
}
@ -372,7 +354,7 @@ export function useIndustrialImage() {
console.error('删除图像失败:', error)
Message.error('删除图像失败')
}
}
},
})
}
@ -451,7 +433,7 @@ export function useIndustrialImage() {
reportUnitData.value = {
id: selectedNode.id,
name: selectedNode.name,
type: selectedNode.type
type: selectedNode.type,
}
// 显示报告生成对话框
@ -489,7 +471,7 @@ export function useIndustrialImage() {
temperatureMax: data.imageInfo.maxTemperature,
temperatureMin: data.imageInfo.minTemperature,
weather: data.imageInfo.weather,
windLevel: data.imageInfo.windPower
windLevel: data.imageInfo.windPower,
}
// 使用新的API接口上传图像
@ -497,7 +479,7 @@ export function useIndustrialImage() {
'default', // 图像源
data.part.partId, // 部件ID
data.images, // 文件列表
uploadParams
uploadParams,
)
console.log('批量上传响应:', response)
@ -560,13 +542,13 @@ export function useIndustrialImage() {
// 分页结构
defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item
...item,
}))
} else if (Array.isArray(resultData)) {
// 直接数组结构
defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item
...item,
}))
}
}
@ -629,7 +611,7 @@ export function useIndustrialImage() {
repairIdea: defectForm.repairIdea,
imageId: selectedImage.value?.imageId || '',
detectionDate: new Date().toISOString(),
source: 'manual'
source: 'manual',
}
// 根据节点类型设置相关ID
@ -723,19 +705,21 @@ export function useIndustrialImage() {
detectionDate: new Date().toISOString(),
labelInfo: JSON.stringify(annotation),
markInfo: {
bbox: annotation.type === 'rectangle' ? [
bbox: annotation.type === 'rectangle'
? [
Math.min(annotation.points[0].x, annotation.points[1].x),
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].y - annotation.points[0].y)
] : [],
Math.abs(annotation.points[1].y - annotation.points[0].y),
]
: [],
clsId: 1,
confidence: 1.0,
label: annotation.defectType || '手动标注'
label: annotation.defectType || '手动标注',
},
repairIdea: '',
repairStatus: 'PENDING',
source: 'MANUAL'
source: 'MANUAL',
}
console.log('发送给后端的缺陷数据:', defectData)
@ -824,7 +808,7 @@ export function useIndustrialImage() {
confThreshold: settings.confidence / 100, // 转换为0-1的置信度
defectTypeList: settings.defectTypes,
imageId: selectedImage.value.imageId,
modelId: settings.algorithm // 使用算法作为模型ID
modelId: settings.algorithm, // 使用算法作为模型ID
}
const response = await detectDefects(detectParams)
@ -851,7 +835,7 @@ export function useIndustrialImage() {
// 更新当前选中图像的路径为识别后返回的attachPath
selectedImage.value = {
...selectedImage.value,
imagePath: firstResult.attachPath
imagePath: firstResult.attachPath,
}
console.log('图片路径已更新为识别后的路径:', firstResult.attachPath)
}
@ -863,7 +847,6 @@ export function useIndustrialImage() {
}
// 不再需要类型映射因为RecognitionResults组件已经处理了自定义类型
} catch (error) {
console.error('识别失败:', error)
Message.error('识别失败')
@ -924,15 +907,15 @@ export function useIndustrialImage() {
bbox: [0, 0, 0, 0],
clsId: 0,
confidence: 0.8,
label: result.defectType || '自动识别'
label: result.defectType || '自动识别',
},
repairIdea: result.repairIdea || '建议进一步检查确认',
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) {
successCount++
@ -967,7 +950,6 @@ export function useIndustrialImage() {
if (successCount > 0) {
loadDefectList()
}
} catch (error) {
console.error('保存识别结果失败:', error)
Message.error('保存识别结果失败')
@ -1070,6 +1052,6 @@ export function useIndustrialImage() {
// 报告生成相关方法
handleReportGenerated,
init
init,
}
}

View File

@ -12,11 +12,13 @@
<!-- 主体内容区域 -->
<div class="main-content">
<!-- 上部区域 -->
<div class="top-section" :class="{
<div
class="top-section" :class="{
'auto-recognition-layout': isAutoRecognitionMode,
'manual-annotation-layout': isManualAnnotationMode,
'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode
}">
'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode,
}"
>
<!-- 自动识别模式三列布局 -->
<template v-if="isAutoRecognitionMode">
<!-- 左侧自动识别设置面板 -->
@ -85,7 +87,7 @@
@submit="handleDefectFormSubmit"
/>
<div v-else class="no-annotation-prompt">
<icon-bug class="prompt-icon" />
<IconBug class="prompt-icon" />
<p>请在图像上绘制矩形标注缺陷</p>
</div>
</div>
@ -129,7 +131,7 @@
</div>
<!-- 下部工业图像列表区域 - 仅在非自动识别和非手动标注模式下显示 -->
<div v-if="!isAutoRecognitionMode && !isManualAnnotationMode" class="bottom-section" :class="{ 'collapsed': isImageListCollapsed }">
<div v-if="!isAutoRecognitionMode && !isManualAnnotationMode" class="bottom-section" :class="{ collapsed: isImageListCollapsed }">
<IndustrialImageList
:image-list="imageList"
:selected-image-id="selectedImageId"
@ -157,14 +159,12 @@
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { IconBug } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
import HeaderToolbar from './components/HeaderToolbar.vue'
import ProjectTree from './components/ProjectTree.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 RecognitionResults from './components/RecognitionResults.vue'
import DefectListPanel from './components/DefectListPanel.vue'
@ -172,9 +172,10 @@ import DefectDetailsForm from './components/DefectDetailsForm.vue'
import AutoRecognitionSettings from './components/AutoRecognitionSettings.vue'
import { useIndustrialImage } from './hooks/useIndustrialImage'
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 { ManualDefectAddRequest, DefectInfo, AttachInfoData } from '@/apis/industrial-image/defect'
import { addManualDefect, getDefectList, uploadAnnotatedImage, getDefectTypes, getDefectLevels, type DefectType, type DefectLevelType } from '@/apis/industrial-image/defect'
import type { DefectInfo, type DefectLevelType, type DefectType, ManualDefectAddRequest, addManualDefect, getDefectLevels, getDefectList, getDefectTypes, uploadAnnotatedImage } from '@/apis/industrial-image/defect'
defineOptions({ name: 'IndustrialImageProcessing' })
@ -314,7 +315,7 @@ const {
//
handleReportGenerated,
init
init,
} = useIndustrialImage()
//
@ -341,16 +342,16 @@ const treeData = computed(() => {
key: 'test-part-1',
title: '测试部件1',
type: 'part',
defectCount: 0
}
]
}
]
}
defectCount: 0,
},
],
},
],
},
] as TreeNode[]
}
const result = projectTreeData.value.map(project => {
const result = projectTreeData.value.map((project) => {
if (!project) {
console.warn('发现空的project对象')
return null
@ -361,7 +362,7 @@ const treeData = computed(() => {
title: project.name || '未命名项目',
type: 'project' as const,
defectCount: 0,
children: project.children?.map(turbine => {
children: project.children?.map((turbine) => {
if (!turbine) {
console.warn('发现空的turbine对象')
return null
@ -372,7 +373,7 @@ const treeData = computed(() => {
title: turbine.name || '未命名机组',
type: 'turbine' as const,
defectCount: 0,
children: turbine.children?.map(part => {
children: turbine.children?.map((part) => {
if (!part) {
console.warn('发现空的part对象')
return null
@ -382,18 +383,18 @@ const treeData = computed(() => {
key: part.id || `part-${Math.random().toString(36).substr(2, 9)}`,
title: part.name || '未命名部件',
type: 'part' as const,
defectCount: 0
defectCount: 0,
}
}).filter(Boolean) || []
}).filter(Boolean) || [],
}
return turbineNode
}).filter(Boolean) || []
}).filter(Boolean) || [],
}
console.log('创建的project节点:', projectNode)
return projectNode
}).filter(item => item !== null) as TreeNode[]
}).filter((item) => item !== null) as TreeNode[]
console.log('最终的treeData:', result)
return result
@ -417,7 +418,7 @@ const handleAnnotationFinish = async (annotations: Annotation[], imageBlob: Blob
const fileName = `annotated_${selectedImage.value?.imageName || 'image'}_${Date.now()}.png`
const uploadResponse = await uploadAnnotatedImage(imageBlob, fileName)
if ( !uploadResponse.data) {
if (!uploadResponse.data) {
Message.error('上传标注图片失败')
return
}
@ -437,14 +438,13 @@ const handleAnnotationFinish = async (annotations: Annotation[], imageBlob: Blob
attachId: attachInfo,
// attachPath: attachInfo.attachPath,
// attachInfo: attachInfo,
allAnnotations: annotations
}
allAnnotations: annotations,
},
}
//
currentAnnotation.value = combinedAnnotation
Message.success(`成功绘制${annotations.length}个区域,请填写缺陷详情`)
} catch (error) {
console.error('处理标注完成失败:', error)
Message.error('处理标注失败')
@ -476,10 +476,10 @@ const handleAddDefectFromPanel = () => {
type: 'rectangle',
points: [
{ x: 100, y: 100 },
{ x: 200, y: 200 }
{ x: 200, y: 200 },
],
color: '#ff4d4f',
label: '手动添加的缺陷'
label: '手动添加的缺陷',
}
//
@ -508,15 +508,15 @@ const handleTurbineSelect = async (turbineId: string) => {
//
defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
turbineId: turbineId,
...item
turbineId,
...item,
}))
} else if (Array.isArray(resultData)) {
//
defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
turbineId: turbineId,
...item
turbineId,
...item,
}))
}
@ -565,13 +565,13 @@ const loadDefectList = async () => {
//
defects = resultData.list.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item
...item,
}))
} else if (Array.isArray(resultData)) {
//
defects = resultData.map((item: any) => ({
id: item.id || item.defectId || `defect-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
...item
...item,
}))
}
@ -613,7 +613,7 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
// API
const defectData = {
attachId: attachId,
attachId,
attachPath: '',
axial: formData.axialDimension,
chordwise: formData.chordDimension,
@ -627,36 +627,38 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
defectTypeLabel: formData.defectTypeLabel || getDefectTypeLabel(formData.defectType),
description: formData.description,
detectionDate: new Date().toISOString().split('T')[0],
imageId: imageId,
imageId,
labelInfo: '',
// labelInfo: isMultiAnnotation ?
// JSON.stringify(annotation.metadata?.allAnnotations || []) :
// JSON.stringify(annotation),
markInfo: {
bbox: isMultiAnnotation ?
bbox: isMultiAnnotation
// 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].y, ann.points[1].y),
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].y, annotation.points[1].y),
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,
confidence: 1.0,
label: formData.defectType
label: formData.defectType,
},
repairIdea: formData.repairIdea,
repairStatus: 'PENDING',
repairStatusLabel: '待处理',
source: 'MANUAL',
sourceLabel: '手动标注'
sourceLabel: '手动标注',
}
console.log('发送给后端的缺陷数据:', defectData)
@ -676,12 +678,11 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
//
const annotationCount = annotation.metadata?.allAnnotations?.length || 1
Message.success({
content: isMultiAnnotation ?
`成功保存包含${annotationCount}个标注区域的缺陷信息!` :
'缺陷信息保存成功!',
duration: 3000
content: isMultiAnnotation
? `成功保存包含${annotationCount}个标注区域的缺陷信息!`
: '缺陷信息保存成功!',
duration: 3000,
})
} catch (error) {
console.error('添加缺陷失败:', error)
Message.clear()
@ -693,9 +694,9 @@ const handleDefectFormSubmit = async (formData: any, annotation: Annotation) =>
const calculateBoundingBox = (annotations: Annotation[]): number[] => {
if (!annotations || annotations.length === 0) return [0, 0, 0, 0]
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity
annotations.forEach(annotation => {
annotations.forEach((annotation) => {
if (annotation.points && annotation.points.length >= 2) {
const [p1, p2] = annotation.points
minX = Math.min(minX, p1.x, p2.x)
@ -710,13 +711,13 @@ const calculateBoundingBox = (annotations: Annotation[]): number[] => {
//
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
}
//
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
}

View File

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

View File

@ -2,7 +2,7 @@
<div class="app-container">
<a-card class="general-card" title="应用使用数据" :bordered="false">
<a-row :gutter="16">
<a-col :span="6" v-for="(stat, index) in appStatistics" :key="index">
<a-col v-for="(stat, index) in appStatistics" :key="index" :span="6">
<a-card class="stat-card">
<a-statistic
:title="stat.title"
@ -46,9 +46,9 @@
<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" />
<IconUp v-if="record.trend > 0" style="color: #52c41a" />
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" />
<IconMinus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template>
@ -63,9 +63,9 @@
<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" />
<IconUp v-if="record.trend > 0" style="color: #52c41a" />
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" />
<IconMinus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template>
@ -80,9 +80,9 @@
<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" />
<IconUp v-if="record.trend > 0" style="color: #52c41a" />
<IconDown v-if="record.trend < 0" style="color: #ff4d4f" />
<IconMinus v-if="record.trend === 0" style="color: #1890ff" />
</span>
</template>
</template>
@ -102,55 +102,52 @@
</a-row>
</a-card>
</div>
</template>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive, nextTick } from 'vue'
import { Statistic } from '@arco-design/web-vue'
import * as echarts from 'echarts'
import {
<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import * as echarts from 'echarts'
import {
IconApps,
IconMobile,
IconUser,
IconClockCircle,
IconUp,
IconDown,
IconMinus
} from '@arco-design/web-vue/es/icon'
IconMinus,
IconUp,
IconUser,
} from '@arco-design/web-vue/es/icon'
const appVisitChart = ref(null)
const appTimeChart = ref(null)
const deviceDistributionChart = ref(null)
const appVisitChart = ref(null)
const appTimeChart = ref(null)
const deviceDistributionChart = ref(null)
//
const appStatistics = reactive([
//
const appStatistics = reactive([
{
title: '应用总数',
value: 12,
icon: IconApps
icon: IconApps,
},
{
title: '活跃应用',
value: 8,
icon: IconApps
icon: IconApps,
},
{
title: '日均访问量',
value: 1258,
icon: IconUser
icon: IconUser,
},
{
title: '日均使用时长',
value: 3.5,
precision: 1,
suffix: '小时',
icon: IconClockCircle
}
])
icon: IconClockCircle,
},
])
// 使
const appColumns = [
// 使
const appColumns = [
{
title: '应用名称',
dataIndex: 'appName',
@ -184,11 +181,11 @@
dataIndex: 'trend',
key: 'trend',
sorter: (a, b) => a.trend - b.trend,
}
]
},
]
// Web
const webAppData = [
// Web
const webAppData = [
{
key: '1',
appName: '企业管理后台',
@ -196,7 +193,7 @@
userCount: 320,
usageRate: 95,
averageTime: '2.5小时',
trend: 8.5
trend: 8.5,
},
{
key: '2',
@ -205,7 +202,7 @@
userCount: 285,
usageRate: 90,
averageTime: '3小时',
trend: 5.2
trend: 5.2,
},
{
key: '3',
@ -214,7 +211,7 @@
userCount: 210,
usageRate: 85,
averageTime: '2小时',
trend: 3.8
trend: 3.8,
},
{
key: '4',
@ -223,7 +220,7 @@
userCount: 180,
usageRate: 75,
averageTime: '1.5小时',
trend: -2.1
trend: -2.1,
},
{
key: '5',
@ -232,12 +229,12 @@
userCount: 150,
usageRate: 65,
averageTime: '1小时',
trend: 0
}
]
trend: 0,
},
]
//
const mobileAppData = [
//
const mobileAppData = [
{
key: '1',
appName: '移动办公APP',
@ -245,7 +242,7 @@
userCount: 350,
usageRate: 98,
averageTime: '3.5小时',
trend: 12.5
trend: 12.5,
},
{
key: '2',
@ -254,7 +251,7 @@
userCount: 320,
usageRate: 92,
averageTime: '4小时',
trend: 9.8
trend: 9.8,
},
{
key: '3',
@ -263,7 +260,7 @@
userCount: 260,
usageRate: 88,
averageTime: '3小时',
trend: 7.5
trend: 7.5,
},
{
key: '4',
@ -272,12 +269,12 @@
userCount: 180,
usageRate: 72,
averageTime: '2小时',
trend: -1.5
}
]
trend: -1.5,
},
]
//
const miniAppData = [
//
const miniAppData = [
{
key: '1',
appName: '企业服务小程序',
@ -285,7 +282,7 @@
userCount: 420,
usageRate: 96,
averageTime: '1.5小时',
trend: 15.8
trend: 15.8,
},
{
key: '2',
@ -294,7 +291,7 @@
userCount: 380,
usageRate: 90,
averageTime: '1小时',
trend: 10.5
trend: 10.5,
},
{
key: '3',
@ -303,24 +300,24 @@
userCount: 345,
usageRate: 85,
averageTime: '0.8小时',
trend: 8.2
}
]
trend: 8.2,
},
]
//
const getUsageRateColor = (rate) => {
//
const getUsageRateColor = (rate) => {
if (rate >= 90) return '#52c41a'
if (rate >= 70) return '#1890ff'
return '#faad14'
}
}
const getTrendColor = (trend) => {
const getTrendColor = (trend) => {
if (trend > 0) return '#52c41a'
if (trend < 0) return '#ff4d4f'
return '#1890ff'
}
}
onMounted(() => {
onMounted(() => {
// 使 nextTick DOM
nextTick(() => {
// 访
@ -328,42 +325,42 @@
const visitChart = echarts.init(appVisitChart.value)
visitChart.setOption({
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
legend: {
data: ['Web端', '移动端', '小程序']
data: ['Web端', '移动端', '小程序'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value'
type: 'value',
},
series: [
{
name: 'Web端',
type: 'line',
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200]
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200],
},
{
name: '移动端',
type: 'line',
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500]
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500],
},
{
name: '小程序',
type: 'line',
data: [4500, 4800, 5200, 4900, 4700, 3900, 3500]
}
]
data: [4500, 4800, 5200, 4900, 4700, 3900, 3500],
},
],
})
}
@ -372,11 +369,11 @@
const timeChart = echarts.init(appTimeChart.value)
timeChart.setOption({
tooltip: {
trigger: 'item'
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left'
left: 'left',
},
series: [
{
@ -386,17 +383,17 @@
data: [
{ value: 35, name: 'Web端' },
{ value: 45, name: '移动端' },
{ value: 20, name: '小程序' }
{ value: 20, name: '小程序' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
}
@ -407,22 +404,22 @@
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
type: 'shadow',
},
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'value'
type: 'value',
},
yAxis: {
type: 'category',
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信']
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信'],
},
series: [
{
@ -430,26 +427,26 @@
type: 'bar',
stack: 'total',
label: {
show: true
show: true,
},
emphasis: {
focus: 'series'
focus: 'series',
},
data: [5200, 3800, 6500, 8200, 9500]
data: [5200, 3800, 6500, 8200, 9500],
},
{
name: '用户数',
type: 'bar',
stack: 'total',
label: {
show: true
show: true,
},
emphasis: {
focus: 'series'
focus: 'series',
},
data: [280, 220, 320, 380, 420]
}
]
data: [280, 220, 320, 380, 420],
},
],
})
}
@ -469,8 +466,8 @@
}
})
})
})
</script>
})
</script>
<style scoped>
.general-card {

View File

@ -45,20 +45,20 @@
</a-card>
</a-card>
</div>
</template>
</template>
<script lang="ts" setup>
import { ref, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
// @ant-design/icons-vue
// import { ArrowUpOutlined, ArrowDownOutlined, MinusOutlined } from '@ant-design/icons-vue'
<script lang="ts" setup>
import { nextTick, onMounted, ref } from 'vue'
import * as echarts from 'echarts'
// @ant-design/icons-vue
// import { ArrowUpOutlined, ArrowDownOutlined, MinusOutlined } from '@ant-design/icons-vue'
const moduleUsageChart = ref(null)
const departmentUsageChart = ref(null)
const usageTimeChart = ref(null)
const moduleUsageChart = ref(null)
const departmentUsageChart = ref(null)
const usageTimeChart = ref(null)
// 使
const functionColumns = [
// 使
const functionColumns = [
{
title: '功能模块',
dataIndex: 'module',
@ -92,10 +92,10 @@
dataIndex: 'trend',
key: 'trend',
sorter: (a, b) => a.trend - b.trend,
}
]
},
]
const functionData = [
const functionData = [
{
key: '1',
module: '组织架构',
@ -103,7 +103,7 @@
usageCount: 1245,
usageRate: 92,
averageTime: '15分钟',
trend: 5.2
trend: 5.2,
},
{
key: '2',
@ -112,7 +112,7 @@
usageCount: 865,
usageRate: 78,
averageTime: '12分钟',
trend: 3.8
trend: 3.8,
},
{
key: '3',
@ -121,7 +121,7 @@
usageCount: 1056,
usageRate: 85,
averageTime: '18分钟',
trend: 7.5
trend: 7.5,
},
{
key: '4',
@ -130,7 +130,7 @@
usageCount: 932,
usageRate: 80,
averageTime: '14分钟',
trend: -2.1
trend: -2.1,
},
{
key: '5',
@ -139,7 +139,7 @@
usageCount: 1120,
usageRate: 88,
averageTime: '20分钟',
trend: 4.3
trend: 4.3,
},
{
key: '6',
@ -148,7 +148,7 @@
usageCount: 986,
usageRate: 82,
averageTime: '16分钟',
trend: 0
trend: 0,
},
{
key: '7',
@ -157,7 +157,7 @@
usageCount: 1320,
usageRate: 95,
averageTime: '25分钟',
trend: 8.7
trend: 8.7,
},
{
key: '8',
@ -166,7 +166,7 @@
usageCount: 1150,
usageRate: 90,
averageTime: '22分钟',
trend: 6.2
trend: 6.2,
},
{
key: '9',
@ -175,7 +175,7 @@
usageCount: 1280,
usageRate: 93,
averageTime: '30分钟',
trend: 9.5
trend: 9.5,
},
{
key: '10',
@ -184,7 +184,7 @@
usageCount: 1180,
usageRate: 91,
averageTime: '28分钟',
trend: 5.8
trend: 5.8,
},
{
key: '11',
@ -193,7 +193,7 @@
usageCount: 1420,
usageRate: 98,
averageTime: '35分钟',
trend: 12.3
trend: 12.3,
},
{
key: '12',
@ -202,24 +202,24 @@
usageCount: 720,
usageRate: 65,
averageTime: '10分钟',
trend: -1.5
}
]
trend: -1.5,
},
]
//
const getUsageRateColor = (rate) => {
//
const getUsageRateColor = (rate) => {
if (rate >= 90) return '#52c41a'
if (rate >= 70) return '#1890ff'
return '#faad14'
}
}
const getTrendColor = (trend) => {
const getTrendColor = (trend) => {
if (trend > 0) return '#52c41a'
if (trend < 0) return '#ff4d4f'
return '#1890ff'
}
}
onMounted(() => {
onMounted(() => {
// 使 nextTick DOM
nextTick(() => {
// 使
@ -229,37 +229,37 @@
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
type: 'shadow',
},
},
legend: {
data: ['使用次数', '使用人数']
data: ['使用次数', '使用人数'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'value'
type: 'value',
},
yAxis: {
type: 'category',
data: ['组织架构', '资产管理', '产品与服务', '项目管理', '施工操作台', '聊天平台', '企业设置', '系统资源管理']
data: ['组织架构', '资产管理', '产品与服务', '项目管理', '施工操作台', '聊天平台', '企业设置', '系统资源管理'],
},
series: [
{
name: '使用次数',
type: 'bar',
data: [2110, 1988, 2106, 2470, 2460, 1420, 720, 650]
data: [2110, 1988, 2106, 2470, 2460, 1420, 720, 650],
},
{
name: '使用人数',
type: 'bar',
data: [320, 302, 315, 335, 340, 356, 120, 85]
}
]
data: [320, 302, 315, 335, 340, 356, 120, 85],
},
],
})
}
@ -268,11 +268,11 @@
const departmentChart = echarts.init(departmentUsageChart.value)
departmentChart.setOption({
tooltip: {
trigger: 'item'
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left'
left: 'left',
},
series: [
{
@ -284,17 +284,17 @@
{ value: 25, name: '市场部' },
{ value: 20, name: '销售部' },
{ value: 10, name: '人事部' },
{ value: 10, name: '财务部' }
{ value: 10, name: '财务部' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
}
@ -303,11 +303,11 @@
const timeChart = echarts.init(usageTimeChart.value)
timeChart.setOption({
tooltip: {
trigger: 'item'
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left'
left: 'left',
},
series: [
{
@ -318,21 +318,21 @@
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
borderWidth: 2,
},
label: {
show: false,
position: 'center'
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
fontWeight: 'bold',
},
},
labelLine: {
show: false
show: false,
},
data: [
{ value: 20, name: '组织架构' },
@ -342,10 +342,10 @@
{ value: 20, name: '施工操作台' },
{ value: 5, name: '聊天平台' },
{ value: 3, name: '企业设置' },
{ value: 2, name: '系统资源管理' }
]
}
]
{ value: 2, name: '系统资源管理' },
],
},
],
})
}
@ -365,8 +365,8 @@
}
})
})
})
</script>
})
</script>
<style scoped>
.general-card {

View File

@ -73,19 +73,19 @@
</a-tabs>
</a-card>
</div>
</template>
</template>
<script lang="ts" setup>
import { ref, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
<script lang="ts" setup>
import { nextTick, onMounted, ref } from 'vue'
import * as echarts from 'echarts'
const departmentActivityChart = ref(null)
const dailyActiveUsersChart = ref(null)
const onlineTimeChart = ref(null)
const attendanceTrendChart = ref(null)
const departmentActivityChart = ref(null)
const dailyActiveUsersChart = ref(null)
const onlineTimeChart = ref(null)
const attendanceTrendChart = ref(null)
//
const rankColumns = [
//
const rankColumns = [
{
title: '排名',
dataIndex: 'rank',
@ -116,10 +116,10 @@
title: '操作次数',
dataIndex: 'operationCount',
key: 'operationCount',
}
]
},
]
const rankData = [
const rankData = [
{
key: '1',
rank: 1,
@ -127,7 +127,7 @@
department: '技术部',
activityScore: 98,
loginCount: 45,
operationCount: 532
operationCount: 532,
},
{
key: '2',
@ -136,7 +136,7 @@
department: '市场部',
activityScore: 95,
loginCount: 42,
operationCount: 498
operationCount: 498,
},
{
key: '3',
@ -145,7 +145,7 @@
department: '销售部',
activityScore: 92,
loginCount: 40,
operationCount: 475
operationCount: 475,
},
{
key: '4',
@ -154,7 +154,7 @@
department: '人事部',
activityScore: 88,
loginCount: 38,
operationCount: 450
operationCount: 450,
},
{
key: '5',
@ -163,7 +163,7 @@
department: '财务部',
activityScore: 85,
loginCount: 36,
operationCount: 420
operationCount: 420,
},
{
key: '6',
@ -172,7 +172,7 @@
department: '技术部',
activityScore: 82,
loginCount: 34,
operationCount: 405
operationCount: 405,
},
{
key: '7',
@ -181,7 +181,7 @@
department: '市场部',
activityScore: 79,
loginCount: 32,
operationCount: 380
operationCount: 380,
},
{
key: '8',
@ -190,7 +190,7 @@
department: '销售部',
activityScore: 76,
loginCount: 30,
operationCount: 365
operationCount: 365,
},
{
key: '9',
@ -199,7 +199,7 @@
department: '人事部',
activityScore: 73,
loginCount: 28,
operationCount: 350
operationCount: 350,
},
{
key: '10',
@ -208,12 +208,12 @@
department: '财务部',
activityScore: 70,
loginCount: 26,
operationCount: 335
}
]
operationCount: 335,
},
]
//
const attendanceColumns = [
//
const attendanceColumns = [
{
title: '部门',
dataIndex: 'department',
@ -243,10 +243,10 @@
title: '缺勤次数',
dataIndex: 'absentCount',
key: 'absentCount',
}
]
},
]
const attendanceData = [
const attendanceData = [
{
key: '1',
department: '技术部',
@ -254,7 +254,7 @@
attendanceRate: 98,
lateCount: 3,
earlyLeaveCount: 1,
absentCount: 0
absentCount: 0,
},
{
key: '2',
@ -263,7 +263,7 @@
attendanceRate: 96,
lateCount: 5,
earlyLeaveCount: 2,
absentCount: 1
absentCount: 1,
},
{
key: '3',
@ -272,7 +272,7 @@
attendanceRate: 95,
lateCount: 6,
earlyLeaveCount: 3,
absentCount: 1
absentCount: 1,
},
{
key: '4',
@ -281,7 +281,7 @@
attendanceRate: 97,
lateCount: 2,
earlyLeaveCount: 1,
absentCount: 0
absentCount: 0,
},
{
key: '5',
@ -290,36 +290,36 @@
attendanceRate: 99,
lateCount: 1,
earlyLeaveCount: 0,
absentCount: 0
}
]
absentCount: 0,
},
]
//
const getRankColor = (rank) => {
//
const getRankColor = (rank) => {
if (rank <= 3) return '#f50'
if (rank <= 10) return '#2db7f5'
return '#87d068'
}
}
const getScoreColor = (score) => {
const getScoreColor = (score) => {
if (score >= 90) return '#52c41a'
if (score >= 70) return '#1890ff'
return '#faad14'
}
}
const getAttendanceColor = (rate) => {
const getAttendanceColor = (rate) => {
if (rate >= 95) return '#52c41a'
if (rate >= 90) return '#1890ff'
return '#faad14'
}
}
const getLateCountColor = (count) => {
const getLateCountColor = (count) => {
if (count <= 2) return 'green'
if (count <= 5) return 'orange'
return 'red'
}
}
onMounted(() => {
onMounted(() => {
// 使 nextTick DOM
nextTick(() => {
//
@ -329,40 +329,40 @@
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
type: 'shadow',
},
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'category',
data: ['技术部', '市场部', '销售部', '人事部', '财务部']
data: ['技术部', '市场部', '销售部', '人事部', '财务部'],
},
yAxis: {
type: 'value'
type: 'value',
},
series: [
{
name: '活跃度',
type: 'bar',
data: [92, 85, 88, 79, 82]
data: [92, 85, 88, 79, 82],
},
{
name: '登录次数',
type: 'bar',
data: [320, 280, 310, 240, 260]
data: [320, 280, 310, 240, 260],
},
{
name: '操作次数',
type: 'bar',
data: [2800, 2100, 2400, 1800, 2000]
}
]
data: [2800, 2100, 2400, 1800, 2000],
},
],
})
}
@ -371,22 +371,22 @@
const dailyActiveChart = echarts.init(dailyActiveUsersChart.value)
dailyActiveChart.setOption({
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value'
type: 'value',
},
series: [
{
data: [120, 132, 145, 135, 128, 68, 42],
type: 'line',
areaStyle: {}
}
]
areaStyle: {},
},
],
})
}
@ -395,25 +395,25 @@
const onlineChart = echarts.init(onlineTimeChart.value)
onlineChart.setOption({
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
xAxis: {
type: 'category',
data: ['技术部', '市场部', '销售部', '人事部', '财务部']
data: ['技术部', '市场部', '销售部', '人事部', '财务部'],
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} 小时'
}
formatter: '{value} 小时',
},
},
series: [
{
name: '平均在线时长',
type: 'bar',
data: [7.5, 6.8, 7.2, 6.5, 6.9]
}
]
data: [7.5, 6.8, 7.2, 6.5, 6.9],
},
],
})
}
@ -422,45 +422,45 @@
const attendanceChart = echarts.init(attendanceTrendChart.value)
attendanceChart.setOption({
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
legend: {
data: ['出勤率', '迟到率', '早退率']
data: ['出勤率', '迟到率', '早退率'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月']
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}%'
}
formatter: '{value}%',
},
},
series: [
{
name: '出勤率',
type: 'line',
data: [96.2, 97.1, 96.8, 97.5, 98.2, 97.8]
data: [96.2, 97.1, 96.8, 97.5, 98.2, 97.8],
},
{
name: '迟到率',
type: 'line',
data: [2.8, 2.2, 2.5, 1.8, 1.2, 1.5]
data: [2.8, 2.2, 2.5, 1.8, 1.2, 1.5],
},
{
name: '早退率',
type: 'line',
data: [1.0, 0.7, 0.7, 0.7, 0.6, 0.7]
}
]
data: [1.0, 0.7, 0.7, 0.7, 0.6, 0.7],
},
],
})
}
@ -484,8 +484,8 @@
}
})
})
})
</script>
})
</script>
<style scoped>
.general-card {

View File

@ -11,7 +11,7 @@
style="margin-right: 50px"
>
<template #prefix>
<icon-file />
<IconFile />
</template>
</a-statistic>
</a-card>
@ -24,7 +24,7 @@
:precision="0"
>
<template #prefix>
<icon-user-group />
<IconUserGroup />
</template>
</a-statistic>
</a-card>
@ -37,7 +37,7 @@
:precision="0"
>
<template #prefix>
<icon-computer />
<IconComputer />
</template>
</a-statistic>
</a-card>
@ -50,7 +50,7 @@
:precision="0"
>
<template #prefix>
<icon-check-circle />
<IconCheckCircle />
</template>
</a-statistic>
</a-card>
@ -83,26 +83,25 @@
</a-row>
</a-card>
</div>
</template>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive, nextTick } from 'vue'
import { Statistic } from '@arco-design/web-vue'
import { IconFile, IconUserGroup, IconComputer, IconCheckCircle } from '@arco-design/web-vue/es/icon'
import * as echarts from 'echarts'
<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { IconCheckCircle, IconComputer, IconFile, IconUserGroup } from '@arco-design/web-vue/es/icon'
import * as echarts from 'echarts'
const projectProgressChart = ref(null)
const resourceUsageChart = ref(null)
const businessTrendChart = ref(null)
const projectProgressChart = ref(null)
const resourceUsageChart = ref(null)
const businessTrendChart = ref(null)
const statistics = reactive({
const statistics = reactive({
projectCount: 128,
memberCount: 356,
deviceCount: 243,
completedProjectCount: 15
})
completedProjectCount: 15,
})
onMounted(() => {
onMounted(() => {
// 使 nextTick DOM
nextTick(() => {
//
@ -110,11 +109,11 @@
const projectChart = echarts.init(projectProgressChart.value)
projectChart.setOption({
tooltip: {
trigger: 'item'
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left'
left: 'left',
},
series: [
{
@ -125,17 +124,17 @@
{ value: 48, name: '进行中' },
{ value: 65, name: '已完成' },
{ value: 12, name: '已暂停' },
{ value: 3, name: '已取消' }
{ value: 3, name: '已取消' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
}
@ -146,22 +145,22 @@
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
type: 'shadow',
},
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'value'
type: 'value',
},
yAxis: {
type: 'category',
data: ['服务器', '存储空间', '带宽', '设备使用率', '人力资源']
data: ['服务器', '存储空间', '带宽', '设备使用率', '人力资源'],
},
series: [
{
@ -169,26 +168,26 @@
type: 'bar',
stack: 'total',
label: {
show: true
show: true,
},
emphasis: {
focus: 'series'
focus: 'series',
},
data: [65, 72, 58, 80, 75]
data: [65, 72, 58, 80, 75],
},
{
name: '剩余',
type: 'bar',
stack: 'total',
label: {
show: true
show: true,
},
emphasis: {
focus: 'series'
focus: 'series',
},
data: [35, 28, 42, 20, 25]
}
]
data: [35, 28, 42, 20, 25],
},
],
})
}
@ -197,42 +196,42 @@
const businessChart = echarts.init(businessTrendChart.value)
businessChart.setOption({
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
legend: {
data: ['项目数量', '营业收入', '新增客户']
data: ['项目数量', '营业收入', '新增客户'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月']
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
},
yAxis: {
type: 'value'
type: 'value',
},
series: [
{
name: '项目数量',
type: 'line',
data: [10, 12, 15, 18, 22, 24]
data: [10, 12, 15, 18, 22, 24],
},
{
name: '营业收入',
type: 'line',
data: [120, 132, 145, 160, 178, 190]
data: [120, 132, 145, 160, 178, 190],
},
{
name: '新增客户',
type: 'line',
data: [5, 7, 8, 10, 12, 15]
}
]
data: [5, 7, 8, 10, 12, 15],
},
],
})
}
@ -252,8 +251,8 @@
}
})
})
})
</script>
})
</script>
<style scoped>
.general-card {

View File

@ -84,8 +84,8 @@
<a-link @click="editRecord(record)">编辑</a-link>
<a-link @click="resetPassword(record)">重置密码</a-link>
<a-link
@click="toggleStatus(record)"
:class="record.status === 'active' ? 'text-red-500' : 'text-green-500'"
@click="toggleStatus(record)"
>
{{ record.status === 'active' ? '禁用' : '启用' }}
</a-link>
@ -96,18 +96,18 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { onMounted, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { TableColumnData } from '@arco-design/web-vue'
//
let searchForm = reactive({
const searchForm = reactive({
username: '',
adminType: '',
status: '',
createTime: '',
page: 1,
size: 10
size: 10,
})
//
@ -117,8 +117,8 @@ const queryFormColumns = [
label: '用户名',
type: 'input' as const,
props: {
placeholder: '请输入用户名'
}
placeholder: '请输入用户名',
},
},
{
field: 'adminType',
@ -130,9 +130,9 @@ const queryFormColumns = [
{ label: '超级管理员', value: '超级管理员' },
{ label: '系统管理员', value: '系统管理员' },
{ label: '业务管理员', value: '业务管理员' },
{ label: '财务管理员', value: '财务管理员' }
]
}
{ label: '财务管理员', value: '财务管理员' },
],
},
},
{
field: 'status',
@ -143,10 +143,10 @@ const queryFormColumns = [
options: [
{ label: '正常', value: 'active' },
{ label: '禁用', value: 'disabled' },
{ label: '锁定', value: 'locked' }
]
}
}
{ label: '锁定', value: 'locked' },
],
},
},
]
//
@ -163,7 +163,7 @@ const tableColumns: TableColumnData[] = [
{ title: '在线状态', dataIndex: 'onlineStatus', slotName: 'onlineStatus', width: 100 },
{ title: '状态', dataIndex: 'status', slotName: 'status', width: 80 },
{ title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, tooltip: true },
{ title: '操作', slotName: 'action', width: 250, fixed: 'right' }
{ title: '操作', slotName: 'action', width: 250, fixed: 'right' },
]
//
@ -182,7 +182,7 @@ const dataList = ref([
loginCount: 1256,
isOnline: true,
status: 'active',
remark: '系统超级管理员,拥有所有权限'
remark: '系统超级管理员,拥有所有权限',
},
{
id: 2,
@ -197,7 +197,7 @@ const dataList = ref([
loginCount: 892,
isOnline: false,
status: 'active',
remark: '负责项目相关业务管理'
remark: '负责项目相关业务管理',
},
{
id: 3,
@ -212,7 +212,7 @@ const dataList = ref([
loginCount: 654,
isOnline: true,
status: 'active',
remark: '负责财务相关业务管理'
remark: '负责财务相关业务管理',
},
{
id: 4,
@ -227,8 +227,8 @@ const dataList = ref([
loginCount: 432,
isOnline: false,
status: 'disabled',
remark: '技术支持人员,目前停用中'
}
remark: '技术支持人员,目前停用中',
},
])
const pagination = reactive({
@ -236,16 +236,16 @@ const pagination = reactive({
pageSize: 10,
total: 4,
showTotal: true,
showPageSize: true
showPageSize: true,
})
//
const getAdminTypeColor = (type: string) => {
const colorMap: Record<string, string> = {
'超级管理员': 'red',
'系统管理员': 'blue',
'业务管理员': 'green',
'财务管理员': 'orange'
超级管理员: 'red',
系统管理员: 'blue',
业务管理员: 'green',
财务管理员: 'orange',
}
return colorMap[type] || 'gray'
}
@ -253,9 +253,9 @@ const getAdminTypeColor = (type: string) => {
//
const getStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
'active': 'green',
'disabled': 'red',
'locked': 'orange'
active: 'green',
disabled: 'red',
locked: 'orange',
}
return colorMap[status] || 'gray'
}
@ -263,9 +263,9 @@ const getStatusColor = (status: string) => {
//
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
'active': '正常',
'disabled': '禁用',
'locked': '锁定'
active: '正常',
disabled: '禁用',
locked: '锁定',
}
return textMap[status] || status
}
@ -285,7 +285,7 @@ const reset = () => {
status: '',
createTime: '',
page: 1,
size: 10
size: 10,
})
pagination.current = 1
search()

View File

@ -93,7 +93,7 @@
<template #action="{ record }">
<a-space>
<a-link @click="editBankAccount(record)">编辑</a-link>
<a-link @click="setDefaultAccount(record)" v-if="!record.isDefault">设为默认</a-link>
<a-link v-if="!record.isDefault" @click="setDefaultAccount(record)">设为默认</a-link>
<a-link @click="deleteBankAccount(record)">删除</a-link>
</a-space>
</template>
@ -122,7 +122,7 @@
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { TableColumnData } from '@arco-design/web-vue'
@ -139,7 +139,7 @@ const companyInfo = reactive({
phone: '010-12345678',
email: 'info@windtech.com',
website: 'https://www.windtech.com',
employeeCount: '158人'
employeeCount: '158人',
})
//
@ -150,7 +150,7 @@ const certificates = ref([
description: '有效期2023-12-31',
image: '/api/placeholder/300/200',
issueDate: '2021-01-01',
expiryDate: '2023-12-31'
expiryDate: '2023-12-31',
},
{
id: 2,
@ -158,7 +158,7 @@ const certificates = ref([
description: '有效期2025-06-30',
image: '/api/placeholder/300/200',
issueDate: '2022-07-01',
expiryDate: '2025-06-30'
expiryDate: '2025-06-30',
},
{
id: 3,
@ -166,8 +166,8 @@ const certificates = ref([
description: '有效期2024-12-31',
image: '/api/placeholder/300/200',
issueDate: '2021-01-01',
expiryDate: '2024-12-31'
}
expiryDate: '2024-12-31',
},
])
//
@ -178,7 +178,7 @@ const bankAccounts = ref([
accountNumber: '1234567890123456789',
accountName: '风电智能检测技术有限公司',
accountType: '基本户',
isDefault: true
isDefault: true,
},
{
id: 2,
@ -186,8 +186,8 @@ const bankAccounts = ref([
accountNumber: '9876543210987654321',
accountName: '风电智能检测技术有限公司',
accountType: '一般户',
isDefault: false
}
isDefault: false,
},
])
//
@ -197,7 +197,7 @@ const bankColumns: TableColumnData[] = [
{ title: '账户名称', dataIndex: 'accountName', width: 250 },
{ title: '账户类型', dataIndex: 'accountType', slotName: 'accountType', width: 120 },
{ title: '默认账户', dataIndex: 'isDefault', slotName: 'isDefault', width: 100 },
{ title: '操作', slotName: 'action', width: 200 }
{ title: '操作', slotName: 'action', width: 200 },
]
//
@ -205,15 +205,15 @@ const statistics = reactive({
annualRevenue: 12580,
totalProjects: 156,
ongoingProjects: 23,
clientCount: 68
clientCount: 68,
})
//
const getBankTypeColor = (type: string) => {
const colorMap: Record<string, string> = {
'基本户': 'blue',
'一般户': 'green',
'专用户': 'orange'
基本户: 'blue',
一般户: 'green',
专用户: 'orange',
}
return colorMap[type] || 'gray'
}

View File

@ -67,20 +67,20 @@
<a-space>
<a-link @click="viewTaskDetail(record)">详情</a-link>
<a-link
@click="startTask(record)"
v-if="record.status === 'pending'"
@click="startTask(record)"
>
开始
</a-link>
<a-link
@click="pauseTask(record)"
v-if="record.status === 'running'"
@click="pauseTask(record)"
>
暂停
</a-link>
<a-link
@click="resumeTask(record)"
v-if="record.status === 'paused'"
@click="resumeTask(record)"
>
继续
</a-link>
@ -138,7 +138,7 @@
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { TableColumnData } from '@arco-design/web-vue'
@ -147,7 +147,7 @@ const migrationStats = reactive({
migratedData: 1256.8,
pendingData: 324.2,
totalTasks: 28,
successRate: 95.2
successRate: 95.2,
})
//
@ -163,7 +163,7 @@ const migrationTasks = ref([
createTime: '2024-03-01 10:00:00',
startTime: '2024-03-01 10:30:00',
endTime: '2024-03-01 12:45:00',
operator: '张管理员'
operator: '张管理员',
},
{
id: 2,
@ -176,7 +176,7 @@ const migrationTasks = ref([
createTime: '2024-03-10 09:00:00',
startTime: '2024-03-10 09:30:00',
endTime: '',
operator: '李管理员'
operator: '李管理员',
},
{
id: 3,
@ -189,8 +189,8 @@ const migrationTasks = ref([
createTime: '2024-03-15 14:00:00',
startTime: '',
endTime: '',
operator: '王管理员'
}
operator: '王管理员',
},
])
//
@ -204,7 +204,7 @@ const dataSources = ref([
database: 'old_crm',
username: 'admin',
isConnected: true,
createTime: '2024-02-15 10:00:00'
createTime: '2024-02-15 10:00:00',
},
{
id: 2,
@ -215,7 +215,7 @@ const dataSources = ref([
database: 'old_project',
username: 'admin',
isConnected: true,
createTime: '2024-02-20 11:30:00'
createTime: '2024-02-20 11:30:00',
},
{
id: 3,
@ -226,8 +226,8 @@ const dataSources = ref([
database: '/data/files',
username: 'fileuser',
isConnected: false,
createTime: '2024-03-01 09:15:00'
}
createTime: '2024-03-01 09:15:00',
},
])
//
@ -240,7 +240,7 @@ const taskColumns: TableColumnData[] = [
{ title: '状态', dataIndex: 'status', slotName: 'status', width: 100 },
{ title: '创建时间', dataIndex: 'createTime', width: 160 },
{ title: '操作人员', dataIndex: 'operator', width: 100 },
{ title: '操作', slotName: 'action', width: 200 }
{ title: '操作', slotName: 'action', width: 200 },
]
//
@ -253,7 +253,7 @@ const sourceColumns: TableColumnData[] = [
{ title: '用户名', dataIndex: 'username', width: 120 },
{ title: '连接状态', dataIndex: 'connectionStatus', slotName: 'connectionStatus', width: 120 },
{ title: '创建时间', dataIndex: 'createTime', width: 160 },
{ title: '操作', slotName: 'sourceAction', width: 200 }
{ title: '操作', slotName: 'sourceAction', width: 200 },
]
//
@ -261,7 +261,7 @@ const taskPagination = reactive({
current: 1,
pageSize: 10,
total: 3,
showTotal: true
showTotal: true,
})
const tasksLoading = ref(false)
@ -269,11 +269,11 @@ const tasksLoading = ref(false)
//
const getTaskStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
'pending': 'gray',
'running': 'blue',
'paused': 'orange',
'completed': 'green',
'failed': 'red'
pending: 'gray',
running: 'blue',
paused: 'orange',
completed: 'green',
failed: 'red',
}
return colorMap[status] || 'gray'
}
@ -281,11 +281,11 @@ const getTaskStatusColor = (status: string) => {
//
const getTaskStatusText = (status: string) => {
const textMap: Record<string, string> = {
'pending': '待开始',
'running': '运行中',
'paused': '已暂停',
'completed': '已完成',
'failed': '失败'
pending: '待开始',
running: '运行中',
paused: '已暂停',
completed: '已完成',
failed: '失败',
}
return textMap[status] || status
}
@ -301,11 +301,11 @@ const getProgressColor = (progress: number) => {
//
const getSourceTypeColor = (type: string) => {
const colorMap: Record<string, string> = {
'MySQL': 'blue',
'PostgreSQL': 'green',
'Oracle': 'orange',
'FTP': 'purple',
'SFTP': 'cyan'
MySQL: 'blue',
PostgreSQL: 'green',
Oracle: 'orange',
FTP: 'purple',
SFTP: 'cyan',
}
return colorMap[type] || 'gray'
}

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