Revert "Merge remote-tracking branch 'origin/jgqweb'"
This reverts commit1648f3df96
, reversing changes made toc76d6beeb0
.
This commit is contained in:
parent
d25a34e579
commit
1307eaf651
|
@ -4,7 +4,8 @@ VITE_API_PREFIX = '/dev-api'
|
|||
|
||||
# 接口地址
|
||||
# VITE_API_BASE_URL = 'http://pms.dtyx.net:9158/'
|
||||
VITE_API_BASE_URL = 'http://localhost:8888/'
|
||||
# VITE_API_BASE_URL = 'http://localhost:8888/'
|
||||
VITE_API_BASE_URL = 'http://10.18.34.213:8888/'
|
||||
|
||||
# 接口地址 (WebSocket)
|
||||
VITE_API_WS_URL = 'ws://localhost:8000'
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/Industrial-image-management-system---web" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,147 +0,0 @@
|
|||
# 设备中心模块问题修复和改进总结
|
||||
|
||||
## 发现的问题
|
||||
|
||||
### 1. 弹窗组件问题
|
||||
- **缺少查看模式**:原弹窗组件没有区分查看、编辑、新增模式
|
||||
- **表单验证不完善**:缺少详细的验证规则和错误处理
|
||||
- **缺少加载状态**:提交时没有loading状态管理
|
||||
- **表单重置逻辑不完善**:取消时没有正确重置表单
|
||||
- **缺少调试功能**:开发环境下缺少调试工具
|
||||
|
||||
### 2. 主页面问题
|
||||
- **数据转换逻辑复杂**:存在过多的兼容性处理,可能导致数据不一致
|
||||
- **错误处理不够详细**:错误信息不够具体
|
||||
- **缺少详情页面跳转**:没有设备详情页面的入口
|
||||
- **对象引用问题**:直接传递对象引用可能导致数据污染
|
||||
|
||||
### 3. 功能缺失
|
||||
- **缺少设备详情页面**:没有专门的设备详情展示页面
|
||||
- **缺少维护记录管理**:没有设备维护记录的展示和管理
|
||||
- **缺少使用记录**:没有设备使用历史的展示
|
||||
- **缺少文件管理**:没有设备相关文件的管理功能
|
||||
|
||||
## 修复和改进内容
|
||||
|
||||
### 1. 弹窗组件优化 (`DeviceModal.vue`)
|
||||
|
||||
#### 新增功能
|
||||
- ✅ **查看模式支持**:添加了查看模式,禁用所有输入框
|
||||
- ✅ **完善的表单验证**:添加了详细的验证规则和长度限制
|
||||
- ✅ **加载状态管理**:添加了提交时的loading状态
|
||||
- ✅ **表单重置优化**:完善了表单重置逻辑
|
||||
- ✅ **调试功能**:开发环境下添加了调试按钮
|
||||
- ✅ **错误处理优化**:更详细的错误信息处理
|
||||
|
||||
#### 技术改进
|
||||
- ✅ **响应式表单数据**:使用reactive管理表单数据
|
||||
- ✅ **计算属性优化**:添加了表单有效性计算
|
||||
- ✅ **监听器优化**:优化了数据变化监听逻辑
|
||||
- ✅ **类型安全**:完善了TypeScript类型定义
|
||||
|
||||
### 2. 主页面优化 (`index.vue`)
|
||||
|
||||
#### 功能改进
|
||||
- ✅ **数据转换优化**:简化了数据转换逻辑,提高一致性
|
||||
- ✅ **错误处理增强**:添加了更详细的错误信息处理
|
||||
- ✅ **详情页面跳转**:添加了设备详情页面的跳转功能
|
||||
- ✅ **对象深拷贝**:使用展开运算符避免对象引用问题
|
||||
|
||||
#### 用户体验改进
|
||||
- ✅ **删除确认优化**:添加了更明确的删除确认提示
|
||||
- ✅ **操作反馈优化**:改进了操作成功/失败的提示信息
|
||||
|
||||
### 3. 新增设备详情页面 (`detail.vue`)
|
||||
|
||||
#### 功能特性
|
||||
- ✅ **基本信息展示**:设备的基本信息展示
|
||||
- ✅ **状态信息展示**:设备的各种状态信息
|
||||
- ✅ **维护记录管理**:设备维护记录的展示和管理
|
||||
- ✅ **使用记录展示**:设备使用历史的展示
|
||||
- ✅ **位置变更记录**:设备位置变更历史
|
||||
- ✅ **文件管理**:设备相关文件的上传和管理
|
||||
|
||||
#### 技术实现
|
||||
- ✅ **响应式数据管理**:使用ref管理页面数据
|
||||
- ✅ **路由参数处理**:正确处理路由参数获取设备ID
|
||||
- ✅ **API集成**:集成设备详情API
|
||||
- ✅ **状态文本转换**:统一的状态文本转换函数
|
||||
|
||||
### 4. 路由配置优化
|
||||
|
||||
#### 新增路由
|
||||
- ✅ **设备详情路由**:添加了设备详情页面的路由配置
|
||||
- ✅ **参数传递**:支持通过URL参数传递设备ID
|
||||
|
||||
## 参考training模块的实现
|
||||
|
||||
### 借鉴的设计模式
|
||||
1. **弹窗组件设计**:参考了TrainingPlanModal的弹窗设计模式
|
||||
2. **表单验证机制**:采用了相同的表单验证和错误处理机制
|
||||
3. **数据管理方式**:使用了相同的响应式数据管理方式
|
||||
4. **调试功能**:借鉴了开发环境下的调试工具设计
|
||||
5. **详情页面设计**:参考了TrainingDetail页面的布局和功能设计
|
||||
|
||||
### 技术实现对比
|
||||
| 功能 | Training模块 | 设备管理模块 | 改进状态 |
|
||||
|------|-------------|-------------|----------|
|
||||
| 弹窗模式 | 查看/编辑/新增 | 查看/编辑/新增 | ✅ 已实现 |
|
||||
| 表单验证 | 完善 | 完善 | ✅ 已实现 |
|
||||
| 加载状态 | 有 | 有 | ✅ 已实现 |
|
||||
| 调试功能 | 有 | 有 | ✅ 已实现 |
|
||||
| 详情页面 | 有 | 有 | ✅ 已实现 |
|
||||
| 错误处理 | 详细 | 详细 | ✅ 已实现 |
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 设备列表页面
|
||||
- **搜索功能**:支持按设备名称、类型、状态等条件搜索
|
||||
- **新增设备**:点击"新增设备"按钮打开新增弹窗
|
||||
- **查看设备**:点击"查看"按钮以只读模式查看设备信息
|
||||
- **编辑设备**:点击"编辑"按钮修改设备信息
|
||||
- **详情页面**:点击"详情"按钮跳转到设备详情页面
|
||||
- **设备操作**:支持分配、归还、删除等操作
|
||||
|
||||
### 2. 设备详情页面
|
||||
- **基本信息**:展示设备的基本信息
|
||||
- **状态信息**:展示设备的各种状态
|
||||
- **维护记录**:查看和管理设备维护记录
|
||||
- **使用记录**:查看设备使用历史
|
||||
- **位置变更**:查看设备位置变更历史
|
||||
- **文件管理**:上传和管理设备相关文件
|
||||
|
||||
### 3. 开发调试
|
||||
- **调试按钮**:开发环境下显示调试按钮
|
||||
- **表单测试**:可以测试表单数据绑定
|
||||
- **测试数据**:可以填充测试数据
|
||||
- **数据清空**:可以清空表单数据
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
### 1. 功能扩展
|
||||
- [ ] 添加设备维护记录的增删改功能
|
||||
- [ ] 实现设备使用记录的实时更新
|
||||
- [ ] 添加设备状态变更的审批流程
|
||||
- [ ] 实现设备文件的在线预览功能
|
||||
|
||||
### 2. 性能优化
|
||||
- [ ] 添加数据缓存机制
|
||||
- [ ] 实现分页加载优化
|
||||
- [ ] 添加数据预加载功能
|
||||
|
||||
### 3. 用户体验
|
||||
- [ ] 添加操作确认的快捷键支持
|
||||
- [ ] 实现批量操作功能
|
||||
- [ ] 添加数据导出功能
|
||||
- [ ] 实现高级搜索功能
|
||||
|
||||
## 总结
|
||||
|
||||
通过参考training模块的实现,成功修复了设备中心模块的主要问题,并添加了缺失的功能。主要改进包括:
|
||||
|
||||
1. **弹窗组件**:完善了查看、编辑、新增模式,添加了完善的表单验证和错误处理
|
||||
2. **主页面**:优化了数据转换逻辑,改进了错误处理,添加了详情页面跳转
|
||||
3. **详情页面**:新增了完整的设备详情展示页面,包含维护记录、使用记录等功能
|
||||
4. **路由配置**:添加了设备详情页面的路由配置
|
||||
|
||||
这些改进大大提升了设备中心模块的功能完整性和用户体验,使其与training模块保持了一致的设计标准和实现质量。
|
|
@ -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('持续迭代优化的前后端分离中后台管理系统框架。')}`,
|
||||
|
|
|
@ -35,7 +35,7 @@ export default antfu(
|
|||
rules: {
|
||||
'curly': ['off', 'all'], // 对所有控制语句强制使用一致的大括号样式
|
||||
'no-new': 'off', // 不允许在赋值或比较之外使用 new 运算符
|
||||
'no-console': 'off', // 允许使用 console
|
||||
// 'no-console': 'error', // 禁止使用 console
|
||||
'style/arrow-parens': ['error', 'always'], // 箭头函数参数需要括号
|
||||
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], // 对块执行一致的大括号样式
|
||||
'regexp/no-unused-capturing-group': 'off',
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -34,7 +34,9 @@
|
|||
"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",
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
|
||||
<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);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import type { AttachInfoData, BusinessTypeResult } from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const { request } = http
|
||||
import type { AttachInfoData, BusinessTypeResult } from './type'
|
||||
|
||||
/**
|
||||
* 批量新增附件信息
|
||||
* @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'
|
||||
})
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import type { AttendanceRecordReq, AttendanceRecordResp } from './type'
|
||||
import http from '@/utils/http'
|
||||
import type { AttendanceRecordReq, AttendanceRecordResp } from './type'
|
||||
|
||||
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'
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
/** 新增考勤记录请求体 */
|
||||
export interface AttendanceRecordReq {
|
||||
recordImage?: string
|
||||
recordPosition?: string
|
||||
recordPositionLabel?: string
|
||||
}
|
||||
recordImage?: string
|
||||
recordPosition?: string
|
||||
recordPositionLabel?: string
|
||||
}
|
||||
|
||||
/** 新增考勤记录响应体 */
|
||||
export interface AttendanceRecordResp {
|
||||
code: number
|
||||
data: object
|
||||
msg: string
|
||||
status: number
|
||||
success: boolean
|
||||
}
|
||||
/** 新增考勤记录响应体 */
|
||||
export interface AttendanceRecordResp {
|
||||
code: number
|
||||
data: object
|
||||
msg: string
|
||||
status: number
|
||||
success: boolean
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import type * as T from './type'
|
||||
import http from '@/utils/http'
|
||||
import { type ApiMenuItem, convertMenuData } from '@/utils/menuConverter'
|
||||
import { convertMenuData, type ApiMenuItem } from '@/utils/menuConverter'
|
||||
|
||||
export type * from './type'
|
||||
|
||||
|
|
|
@ -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; // 兼容性字段
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import http from '@/utils/http'
|
||||
import type * as T from '@/types/equipment.d'
|
||||
|
||||
const BASE_URL = '/equipment'
|
||||
|
||||
/** @desc 分页查询设备列表 */
|
||||
export function pageEquipment(query: T.EquipmentPageQuery) {
|
||||
return http.get<T.EquipmentResp[]>(`${BASE_URL}/page`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询设备列表 */
|
||||
export function listEquipment(query?: T.EquipmentPageQuery) {
|
||||
return http.get<T.EquipmentResp[]>(`${BASE_URL}/list`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询设备详情 */
|
||||
export function getEquipmentDetail(equipmentId: string) {
|
||||
return http.get<T.EquipmentResp>(`${BASE_URL}/detail/${equipmentId}`)
|
||||
}
|
||||
|
||||
/** @desc 新增设备 */
|
||||
export function createEquipment(data: T.EquipmentReq) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
/** @desc 更新设备 */
|
||||
export function updateEquipment(equipmentId: string, data: T.EquipmentReq) {
|
||||
return http.put(`${BASE_URL}/${equipmentId}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除设备 */
|
||||
export function deleteEquipment(equipmentId: string) {
|
||||
return http.del(`${BASE_URL}/${equipmentId}`)
|
||||
}
|
||||
|
||||
/** @desc 设备状态变更 */
|
||||
export function changeEquipmentStatus(equipmentId: string, status: string) {
|
||||
return http.put(`${BASE_URL}/${equipmentId}/status`, { status })
|
||||
}
|
||||
|
||||
/** @desc 设备分配 */
|
||||
export function assignEquipment(equipmentId: string, userId: string) {
|
||||
return http.put(`${BASE_URL}/${equipmentId}/assign`, { userId })
|
||||
}
|
||||
|
||||
/** @desc 设备归还 */
|
||||
export function returnEquipment(equipmentId: string) {
|
||||
return http.put(`${BASE_URL}/${equipmentId}/return`)
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/**
|
||||
* 设备列表查询请求
|
||||
*/
|
||||
export interface EquipmentListReq {
|
||||
/** 设备名称 */
|
||||
equipmentName?: string
|
||||
/** 设备类型 */
|
||||
equipmentType?: string
|
||||
/** 设备状态 */
|
||||
equipmentStatus?: string
|
||||
/** 设备序列号 */
|
||||
equipmentSn?: string
|
||||
/** 资产编号 */
|
||||
assetCode?: string
|
||||
/** 品牌 */
|
||||
brand?: string
|
||||
/** 位置状态 */
|
||||
locationStatus?: string
|
||||
/** 健康状态 */
|
||||
healthStatus?: string
|
||||
/** 负责人 */
|
||||
responsiblePerson?: string
|
||||
/** 使用状态 */
|
||||
useStatus?: string
|
||||
/** 项目ID */
|
||||
projectId?: string
|
||||
/** 使用人ID */
|
||||
userId?: string
|
||||
/** 当前页码 */
|
||||
pageNum?: number
|
||||
/** 每页大小 */
|
||||
pageSize?: number
|
||||
/** 排序字段 */
|
||||
orderBy?: string
|
||||
/** 排序方向 */
|
||||
orderDirection?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应格式
|
||||
*/
|
||||
export interface PageResult<T> {
|
||||
code: number
|
||||
msg: string
|
||||
rows: T[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备信息响应
|
||||
*/
|
||||
export interface EquipmentResp {
|
||||
/** 设备ID */
|
||||
equipmentId: string
|
||||
/** 资产编号 */
|
||||
assetCode?: string
|
||||
/** 设备名称 */
|
||||
equipmentName: string
|
||||
/** 设备类型 */
|
||||
equipmentType: string
|
||||
/** 设备类型描述 */
|
||||
equipmentTypeLabel?: string
|
||||
/** 设备型号 */
|
||||
equipmentModel: string
|
||||
/** 设备SN */
|
||||
equipmentSn: string
|
||||
/** 品牌 */
|
||||
brand?: string
|
||||
/** 配置规格/参数 */
|
||||
specification?: string
|
||||
/** 设备状态 */
|
||||
equipmentStatus: string
|
||||
/** 设备状态描述 */
|
||||
equipmentStatusLabel?: string
|
||||
/** 使用状态 */
|
||||
useStatus: string
|
||||
/** 位置状态 */
|
||||
locationStatus?: string
|
||||
/** 位置状态描述 */
|
||||
locationStatusLabel?: string
|
||||
/** 设备当前物理位置 */
|
||||
physicalLocation?: string
|
||||
/** 负责人 */
|
||||
responsiblePerson?: string
|
||||
/** 健康状态 */
|
||||
healthStatus?: string
|
||||
/** 健康状态描述 */
|
||||
healthStatusLabel?: string
|
||||
/** 采购时间 */
|
||||
purchaseTime?: string
|
||||
/** 入库时间 */
|
||||
inStockTime?: string
|
||||
/** 启用时间 */
|
||||
activationTime?: string
|
||||
/** 预计报废时间 */
|
||||
expectedScrapTime?: string
|
||||
/** 实际报废时间 */
|
||||
actualScrapTime?: string
|
||||
/** 状态变更时间 */
|
||||
statusChangeTime?: string
|
||||
/** 采购订单 */
|
||||
purchaseOrder?: string
|
||||
/** 供应商名称 */
|
||||
supplierName?: string
|
||||
/** 采购价格 */
|
||||
purchasePrice?: number
|
||||
/** 当前净值 */
|
||||
currentNetValue?: number
|
||||
/** 折旧方法 */
|
||||
depreciationMethod?: string
|
||||
/** 折旧年限 */
|
||||
depreciationYears?: number
|
||||
/** 残值 */
|
||||
salvageValue?: number
|
||||
/** 保修截止日期 */
|
||||
warrantyExpireDate?: string
|
||||
/** 上次维护日期 */
|
||||
lastMaintenanceDate?: string
|
||||
/** 下次维护日期 */
|
||||
nextMaintenanceDate?: string
|
||||
/** 维护人员 */
|
||||
maintenancePerson?: string
|
||||
/** 库存条码 */
|
||||
inventoryBarcode?: string
|
||||
/** 资产备注 */
|
||||
assetRemark?: string
|
||||
/** 项目ID */
|
||||
projectId?: string
|
||||
/** 项目名称 */
|
||||
projectName?: string
|
||||
/** 使用人ID */
|
||||
userId?: string
|
||||
/** 使用人 */
|
||||
name?: string
|
||||
/** 创建时间 */
|
||||
createTime?: string
|
||||
/** 更新时间 */
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备请求
|
||||
*/
|
||||
export interface EquipmentReq {
|
||||
/** 设备名称 */
|
||||
equipmentName: string
|
||||
/** 设备型号 */
|
||||
equipmentModel: string
|
||||
/** 设备类型 */
|
||||
equipmentType: string
|
||||
/** 设备状态 */
|
||||
equipmentStatus: string
|
||||
/** 使用状态 */
|
||||
useStatus: string
|
||||
/** 设备序列号 */
|
||||
equipmentSn: string
|
||||
/** 资产编号 */
|
||||
assetCode?: string
|
||||
/** 品牌 */
|
||||
brand?: string
|
||||
/** 配置规格/参数 */
|
||||
specification?: string
|
||||
/** 位置状态 */
|
||||
locationStatus?: string
|
||||
/** 设备当前物理位置 */
|
||||
physicalLocation?: string
|
||||
/** 负责人 */
|
||||
responsiblePerson?: string
|
||||
/** 健康状态 */
|
||||
healthStatus?: string
|
||||
/** 采购时间 */
|
||||
purchaseTime?: string
|
||||
/** 入库时间 */
|
||||
inStockTime?: string
|
||||
/** 启用时间 */
|
||||
activationTime?: string
|
||||
/** 预计报废时间 */
|
||||
expectedScrapTime?: string
|
||||
/** 实际报废时间 */
|
||||
actualScrapTime?: string
|
||||
/** 采购订单 */
|
||||
purchaseOrder?: string
|
||||
/** 供应商名称 */
|
||||
supplierName?: string
|
||||
/** 采购价格 */
|
||||
purchasePrice?: number
|
||||
/** 当前净值 */
|
||||
currentNetValue?: number
|
||||
/** 折旧方法 */
|
||||
depreciationMethod?: string
|
||||
/** 折旧年限 */
|
||||
depreciationYears?: number
|
||||
/** 残值 */
|
||||
salvageValue?: number
|
||||
/** 保修截止日期 */
|
||||
warrantyExpireDate?: string
|
||||
/** 上次维护日期 */
|
||||
lastMaintenanceDate?: string
|
||||
/** 下次维护日期 */
|
||||
nextMaintenanceDate?: string
|
||||
/** 维护人员 */
|
||||
maintenancePerson?: string
|
||||
/** 库存条码 */
|
||||
inventoryBarcode?: string
|
||||
/** 资产备注 */
|
||||
assetRemark?: string
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -16,6 +16,7 @@ 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'
|
||||
|
|
|
@ -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')
|
||||
}
|
|
@ -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,20 +321,21 @@ 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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ export interface IndustrialImage {
|
|||
name: string
|
||||
/** 图像路径 */
|
||||
path: string
|
||||
/** 图像路径(API返回字段) */
|
||||
/** 图像路径(API返回字段)*/
|
||||
imagePath?: string
|
||||
/** 缩略图路径 */
|
||||
thumbnailPath?: string
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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 }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { InsuranceInfo, InsuranceListParams, InsuranceListResponse, RenewInsuranceParams } from './type'
|
||||
import http from '@/utils/http'
|
||||
import type { InsuranceInfo, InsuranceListParams, InsuranceListResponse, RenewInsuranceParams } from './type'
|
||||
|
||||
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'
|
||||
})
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/** 保险信息接口 */
|
||||
export interface InsuranceInfo {
|
||||
id?: string
|
||||
attachInfoId: string
|
||||
attachInfoId:string
|
||||
insuranceCompanyId: string
|
||||
insuranceTypeId: string
|
||||
userId: string
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
|
@ -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) {
|
||||
|
@ -36,5 +36,5 @@ export function deleteRule(id: string) {
|
|||
}
|
||||
// 我的绩效
|
||||
export function getMyEvaluation() {
|
||||
return http.get('/performance-evaluation/my')
|
||||
}
|
||||
return http.get('/performance-evaluation/my')
|
||||
}
|
|
@ -1,39 +1,39 @@
|
|||
/** 绩效维度 */
|
||||
export interface PerformanceDimension {
|
||||
dimensionId: string
|
||||
dimensionName: string
|
||||
description?: string
|
||||
deptName: string
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
dimensionId: string
|
||||
dimensionName: string
|
||||
description?: string
|
||||
deptName: string
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 绩效细则 */
|
||||
export interface PerformanceRule {
|
||||
ruleId: string
|
||||
ruleName: string
|
||||
description?: string
|
||||
dimensionName: string
|
||||
bonus?: string
|
||||
score?: number
|
||||
weight?: number
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
/** 绩效细则 */
|
||||
export interface PerformanceRule {
|
||||
ruleId: string
|
||||
ruleName: string
|
||||
description?: string
|
||||
dimensionName: string
|
||||
bonus?: string
|
||||
score?: number
|
||||
weight?: number
|
||||
status: 0 | 1
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/** 查询参数 */
|
||||
export interface DimensionQuery {
|
||||
dimensionName?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
export interface RuleQuery {
|
||||
dimensionName?: string
|
||||
ruleName?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
/** 查询参数 */
|
||||
export interface DimensionQuery {
|
||||
dimensionName?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
export interface RuleQuery {
|
||||
dimensionName?: string
|
||||
ruleName?: string
|
||||
status?: 0 | 1
|
||||
}
|
|
@ -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'
|
||||
})
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ export function deleteTaskGroup(id: number) {
|
|||
return http.del(`${BASE_URL}/group/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 查询任务列表 */
|
||||
export function listTask(query: T.TaskPageQuery) {
|
||||
return http.get<PageRes<T.TaskResp[]>>(`${BASE_URL}`, query)
|
||||
/** @desc 查询任务列表(标准导出) */
|
||||
export const listTask = (params: any) => {
|
||||
return http.get('/project-task/list', params)
|
||||
}
|
||||
|
||||
/** @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'
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
/** 项目类型 */
|
||||
export interface ProjectResp {
|
||||
projectId: string // 项目ID (API返回的是字符串)
|
||||
projectCode?: string // 项目编号
|
||||
projectName: string // 项目名称
|
||||
projectCode?: string // 项目编号
|
||||
projectName: string // 项目名称
|
||||
projectIntro?: string // 项目简介
|
||||
farmName: string // 风场名称 (API字段名是farmName)
|
||||
farmName: string // 风场名称 (API字段名是farmName)
|
||||
farmAddress?: string // 风场地址 (API字段名是farmAddress)
|
||||
client?: string // 委托单位
|
||||
clientContact?: string // 委托单位联系人
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { SalaryCreateRequest, SalaryQuery, SalaryRecord } from '@/views/salary-management/types'
|
||||
import type { SalaryRecord, SalaryQuery, SalaryCreateRequest } from '@/views/salary-management/types'
|
||||
import http from '@/utils/http'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type * as T from './type'
|
||||
import http from '@/utils/http'
|
||||
import { convertMenuData } from '@/utils/menuConverter'
|
||||
|
||||
|
@ -5,13 +6,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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,31 +23,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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,6 +56,6 @@ export function getRoleMenuIds(roleId: string) {
|
|||
export function assignRoleMenus(roleId: string, menuIds: string[]) {
|
||||
return http.post('/role/bind-menu', {
|
||||
roleId,
|
||||
menuIds,
|
||||
})
|
||||
menuIds
|
||||
});
|
||||
}
|
|
@ -1,46 +1,45 @@
|
|||
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) {
|
||||
return http.del<any>(`${BASE_URL}/${postId}`)
|
||||
}
|
|
@ -4,6 +4,7 @@ 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) {
|
||||
|
@ -72,7 +73,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}/${id}/user`, query)
|
||||
return http.get<PageRes<T.RoleUserResp[]>>(`${BASE_URL_NEW}/${id}/user`, query)
|
||||
}
|
||||
|
||||
/** @desc 分配角色给用户 */
|
||||
|
@ -87,5 +88,5 @@ export function unassignFromUsers(userRoleIds: Array<string | number>) {
|
|||
|
||||
/** @desc 查询角色关联用户 ID */
|
||||
export function listRoleUserId(id: string) {
|
||||
return http.get(`${BASE_URL}/${id}/user/id`)
|
||||
return http.get(`${BASE_URL_NEW}/${id}/user`)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import http from '@/utils/http'
|
||||
import type * as T from '@/types/training.d'
|
||||
|
||||
const BASE_URL = '/training'
|
||||
|
||||
/** @desc 分页查询培训计划列表 */
|
||||
export function pageTrainingPlan(query: T.TrainingPlanPageQuery) {
|
||||
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/page`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询培训计划列表 */
|
||||
export function listTrainingPlan(query?: T.TrainingPlanPageQuery) {
|
||||
return http.get<T.TrainingPlanResp[]>(`${BASE_URL}/plan/list`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询培训计划详情 */
|
||||
export function getTrainingPlanDetail(planId: string) {
|
||||
return http.get<T.TrainingPlanResp>(`${BASE_URL}/plan/detail/${planId}`)
|
||||
}
|
||||
|
||||
/** @desc 新增培训计划 */
|
||||
export function createTrainingPlan(data: T.TrainingPlanReq) {
|
||||
return http.post(`${BASE_URL}/plan`, data)
|
||||
}
|
||||
|
||||
/** @desc 更新培训计划 */
|
||||
export function updateTrainingPlan(planId: string, data: T.TrainingPlanReq) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除培训计划 */
|
||||
export function deleteTrainingPlan(planId: string) {
|
||||
return http.del(`${BASE_URL}/plan/${planId}`)
|
||||
}
|
||||
|
||||
/** @desc 发布培训计划 */
|
||||
export function publishTrainingPlan(planId: string) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}/publish`)
|
||||
}
|
||||
|
||||
/** @desc 取消培训计划 */
|
||||
export function cancelTrainingPlan(planId: string) {
|
||||
return http.put(`${BASE_URL}/plan/${planId}/cancel`)
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="form.settings.includes('autoAnnotate')" label="标注类型">
|
||||
<a-form-item label="标注类型" v-if="form.settings.includes('autoAnnotate')">
|
||||
<a-select
|
||||
v-model="form.annotationTypes"
|
||||
:options="defectTypeOptions"
|
||||
|
@ -88,9 +88,9 @@
|
|||
>
|
||||
<template #upload-button>
|
||||
<div class="upload-area">
|
||||
<div class="upload-drag-icon">
|
||||
<IconUpload size="48" />
|
||||
</div>
|
||||
<div class="upload-drag-icon">
|
||||
<icon-upload size="48" />
|
||||
</div>
|
||||
<div class="upload-text">
|
||||
<p>点击或拖拽图像文件到此区域</p>
|
||||
<p class="upload-hint">支持 JPG、PNG、JPEG 格式,可同时选择多个文件</p>
|
||||
|
@ -101,12 +101,12 @@
|
|||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div v-if="fileList.length > 0" class="file-list">
|
||||
<div class="file-list" v-if="fileList.length > 0">
|
||||
<div class="list-header">
|
||||
<h4>待导入文件 ({{ fileList.length }})</h4>
|
||||
<a-button type="text" @click="clearFiles">
|
||||
<template #icon>
|
||||
<IconDelete />
|
||||
<icon-delete />
|
||||
</template>
|
||||
清空
|
||||
</a-button>
|
||||
|
@ -133,7 +133,7 @@
|
|||
@click="removeFile(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconClose />
|
||||
<icon-close />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -142,7 +142,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 导入进度 -->
|
||||
<div v-if="importing" class="import-progress">
|
||||
<div class="import-progress" v-if="importing">
|
||||
<a-progress
|
||||
:percent="importProgress"
|
||||
:status="importStatus"
|
||||
|
@ -152,7 +152,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 导入结果 -->
|
||||
<div v-if="importResult" class="import-result">
|
||||
<div class="import-result" v-if="importResult">
|
||||
<a-alert
|
||||
:type="importResult.failed.length > 0 ? 'warning' : 'success'"
|
||||
:title="getResultTitle()"
|
||||
|
@ -160,7 +160,7 @@
|
|||
show-icon
|
||||
/>
|
||||
|
||||
<div v-if="importResult.failed.length > 0" class="result-details">
|
||||
<div class="result-details" v-if="importResult.failed.length > 0">
|
||||
<h4>失败文件列表:</h4>
|
||||
<div class="failed-list">
|
||||
<div
|
||||
|
@ -180,22 +180,22 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconClose,
|
||||
IconDelete,
|
||||
IconUpload,
|
||||
IconDelete,
|
||||
IconClose
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import {
|
||||
getImageSources,
|
||||
getProjectTree,
|
||||
importImages,
|
||||
getImageSources
|
||||
} from '@/apis/industrial-image'
|
||||
import type {
|
||||
ImageImportParams,
|
||||
IndustrialImage,
|
||||
ProjectTreeNode,
|
||||
IndustrialImage,
|
||||
ImageImportParams
|
||||
} 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 `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
||||
return parseFloat((bytes / Math.pow(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,15 +428,16 @@ 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
|
||||
|
@ -468,7 +469,7 @@ const resetForm = () => {
|
|||
latitude: '',
|
||||
longitude: '',
|
||||
settings: [],
|
||||
annotationTypes: [],
|
||||
annotationTypes: []
|
||||
}
|
||||
fileList.value = []
|
||||
importResult.value = null
|
||||
|
|
|
@ -42,8 +42,8 @@
|
|||
:key="getPartId(part)"
|
||||
class="part-item"
|
||||
:class="{ selected: String(selectedPartId) === String(getPartId(part)) }"
|
||||
:title="`部件ID: ${getPartId(part)}, 选中: ${String(selectedPartId) === String(getPartId(part))}`"
|
||||
@click="selectPart(part)"
|
||||
:title="`部件ID: ${getPartId(part)}, 选中: ${String(selectedPartId) === String(getPartId(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 v-if="selectedPart" class="part-info">
|
||||
<div class="part-info" v-if="selectedPart">
|
||||
<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" :disabled="!hasSelectedImages" @click="handleRemoveImages">
|
||||
<button class="action-button" @click="handleRemoveImages" :disabled="!hasSelectedImages">
|
||||
<span class="button-icon">-</span>
|
||||
移除图像
|
||||
</button>
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
accept="image/*"
|
||||
style="display: none;"
|
||||
multiple
|
||||
|
@ -110,7 +110,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="checkbox-column">
|
||||
<input type="checkbox" :checked="allImagesSelected" @change="toggleSelectAll">
|
||||
<input type="checkbox" @change="toggleSelectAll" :checked="allImagesSelected">
|
||||
</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 v-model="image.selected" type="checkbox" @click.stop>
|
||||
<input type="checkbox" v-model="image.selected" @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 v-model="imageInfo.startTime" type="text" placeholder="开始时间">
|
||||
<input type="text" v-model="imageInfo.startTime" placeholder="开始时间">
|
||||
<span class="range-separator">至</span>
|
||||
<input v-model="imageInfo.endTime" type="text" placeholder="结束时间">
|
||||
<input type="text" v-model="imageInfo.endTime" 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 v-model="imageInfo.minTemperature" type="number" step="0.1" min="0" max="50">
|
||||
<input type="number" v-model="imageInfo.minTemperature" step="0.1" min="0" max="50">
|
||||
<button class="range-btn" @click="imageInfo.minTemperature = Math.min(50, imageInfo.minTemperature + 1)">+</button>
|
||||
</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 v-model="imageInfo.maxTemperature" type="number" step="0.1" min="0" max="50">
|
||||
<input type="number" v-model="imageInfo.maxTemperature" step="0.1" min="0" max="50">
|
||||
<button class="range-btn" @click="imageInfo.maxTemperature = Math.min(50, imageInfo.maxTemperature + 1)">+</button>
|
||||
</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 v-model="imageInfo.humidity" type="number" min="0" max="100">
|
||||
<input type="number" v-model="imageInfo.humidity" min="0" max="100">
|
||||
<button class="range-btn" @click="imageInfo.humidity = Math.min(100, imageInfo.humidity + 1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -217,11 +217,11 @@
|
|||
<div class="form-label">拍摄方式</div>
|
||||
<div class="form-input capture-method">
|
||||
<label class="radio-option">
|
||||
<input v-model="imageInfo.captureMethod" type="radio" value="无人机航拍">
|
||||
<input type="radio" v-model="imageInfo.captureMethod" value="无人机航拍">
|
||||
<span class="radio-label">无人机航拍</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input v-model="imageInfo.captureMethod" type="radio" value="人工拍摄">
|
||||
<input type="radio" v-model="imageInfo.captureMethod" value="人工拍摄">
|
||||
<span class="radio-label">人工拍摄</span>
|
||||
</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 v-model="imageInfo.captureDistance" type="number" min="0">
|
||||
<input type="number" v-model="imageInfo.captureDistance" min="0">
|
||||
<button class="range-btn" @click="imageInfo.captureDistance = imageInfo.captureDistance + 1">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -241,14 +241,14 @@
|
|||
<div class="form-row">
|
||||
<div class="form-label">采集员</div>
|
||||
<div class="form-input">
|
||||
<input v-model="imageInfo.operator" type="text">
|
||||
<input type="text" v-model="imageInfo.operator">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-label">相机型号</div>
|
||||
<div class="form-input">
|
||||
<input v-model="imageInfo.cameraModel" type="text">
|
||||
<input type="text" v-model="imageInfo.cameraModel">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -262,24 +262,18 @@
|
|||
v-if="currentStep > 1"
|
||||
class="dialog-button"
|
||||
@click="currentStep--"
|
||||
>
|
||||
上一步
|
||||
</button>
|
||||
>上一步</button>
|
||||
<button
|
||||
v-if="currentStep < 3"
|
||||
class="dialog-button"
|
||||
:disabled="!canGoNext"
|
||||
@click="nextStep"
|
||||
>
|
||||
下一步
|
||||
</button>
|
||||
:disabled="!canGoNext"
|
||||
>下一步</button>
|
||||
<button
|
||||
v-if="currentStep === 3"
|
||||
class="dialog-button primary"
|
||||
@click="finishImport"
|
||||
>
|
||||
完成导入
|
||||
</button>
|
||||
>完成导入</button>
|
||||
<button class="dialog-button" @click="closeDialog">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -287,7 +281,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { ref, computed, reactive, onBeforeUnmount } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
// 定义部件接口
|
||||
|
@ -358,7 +352,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))
|
||||
})
|
||||
|
||||
// 待导入的图像列表
|
||||
|
@ -366,8 +360,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,
|
||||
|
@ -377,7 +371,7 @@ const imageInfo = reactive<ImageInfo>({
|
|||
captureMethod: '无人机航拍',
|
||||
captureDistance: 50,
|
||||
operator: '',
|
||||
cameraModel: 'ILCE-7RM4',
|
||||
cameraModel: 'ILCE-7RM4'
|
||||
})
|
||||
|
||||
// 格式化当前日期
|
||||
|
@ -440,7 +434,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)
|
||||
|
||||
|
@ -454,7 +448,7 @@ function handleFileSelected(event: Event) {
|
|||
timestamp: new Date().toISOString(),
|
||||
pixelSize: '155.00',
|
||||
selected: false,
|
||||
previewUrl,
|
||||
previewUrl
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -469,13 +463,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)
|
||||
}
|
||||
|
||||
// 切换图像选择状态
|
||||
|
@ -486,17 +480,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)
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
|
@ -538,7 +532,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 = {
|
||||
|
@ -546,14 +540,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('图像导入成功')
|
||||
|
@ -575,7 +569,7 @@ function resetState() {
|
|||
selectedPartId.value = ''
|
||||
|
||||
// 清理图像URL对象
|
||||
importImages.value.forEach((image) => {
|
||||
importImages.value.forEach(image => {
|
||||
if (image.previewUrl) {
|
||||
URL.revokeObjectURL(image.previewUrl)
|
||||
}
|
||||
|
@ -584,8 +578,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,
|
||||
|
@ -593,14 +587,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)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="industrial-image-list" :class="{ collapsed: isCollapsed }">
|
||||
<div v-if="!isCollapsed" class="header-actions">
|
||||
<div class="industrial-image-list" :class="{ 'collapsed': isCollapsed }">
|
||||
<div class="header-actions" v-if="!isCollapsed">
|
||||
<slot name="header-left">
|
||||
<a-button v-if="showImportButton" type="primary" @click="handleImportImages">
|
||||
<template #icon><IconUpload /></template>
|
||||
<template #icon><icon-upload /></template>
|
||||
导入图像
|
||||
</a-button>
|
||||
</slot>
|
||||
<div v-if="showSearch" class="search-bar">
|
||||
<div class="search-bar" v-if="showSearch">
|
||||
<a-input-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="输入关键字搜索"
|
||||
|
@ -23,16 +23,16 @@
|
|||
@click="toggleCollapse"
|
||||
>
|
||||
<template #icon>
|
||||
<IconUp />
|
||||
<icon-up />
|
||||
</template>
|
||||
收起
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="!isCollapsed" class="image-grid">
|
||||
<div class="image-grid" v-show="!isCollapsed">
|
||||
<div v-if="imageList.length === 0" class="empty-data">
|
||||
<IconImage class="empty-icon" />
|
||||
<icon-image class="empty-icon" />
|
||||
<p>{{ emptyText }}</p>
|
||||
</div>
|
||||
|
||||
|
@ -51,8 +51,8 @@
|
|||
@error="handleImageError"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
<div v-if="!image.imagePath" class="image-placeholder">
|
||||
<IconImage />
|
||||
<div class="image-placeholder" v-if="!image.imagePath">
|
||||
<icon-image />
|
||||
<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)">
|
||||
<IconEye />
|
||||
<icon-eye />
|
||||
</a-button>
|
||||
<a-button v-if="showProcessAction" type="text" size="small" @click.stop="handleImageProcess(image)">
|
||||
<IconSettings />
|
||||
<icon-settings />
|
||||
</a-button>
|
||||
<a-button v-if="showDeleteAction" type="text" size="small" status="danger" @click.stop="handleImageDelete(image)">
|
||||
<IconDelete />
|
||||
<icon-delete />
|
||||
</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 v-if="image.partName || image.shootingTime" class="thumbnail-extra">
|
||||
<div class="thumbnail-extra" v-if="image.partName || image.shootingTime">
|
||||
<span v-if="image.partName" class="part-name">{{ image.partName }}</span>
|
||||
<span v-if="image.shootingTime" class="capture-time">{{ formatTime(image.shootingTime) }}</span>
|
||||
</div>
|
||||
|
@ -98,7 +98,7 @@
|
|||
@click="toggleCollapse"
|
||||
>
|
||||
<template #icon>
|
||||
<IconDown />
|
||||
<icon-down />
|
||||
</template>
|
||||
展开图像列表
|
||||
</a-button>
|
||||
|
@ -109,13 +109,13 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
IconDelete,
|
||||
IconDown,
|
||||
IconEye,
|
||||
IconImage,
|
||||
IconSettings,
|
||||
IconUp,
|
||||
IconUpload,
|
||||
IconImage,
|
||||
IconEye,
|
||||
IconSettings,
|
||||
IconDelete,
|
||||
IconUp,
|
||||
IconDown
|
||||
} 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://localhost:8080',
|
||||
default: 'http://pms.dtyx.net:9158'
|
||||
},
|
||||
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
|
||||
|
|
|
@ -1,105 +1,102 @@
|
|||
<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 class="turbine-status-badge" :class="`status-${turbine.status}`">
|
||||
{{ getStatusText(turbine.status) }}
|
||||
<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 class="turbine-status-badge" :class="`status-${turbine.status}`">
|
||||
{{ getStatusText(turbine.status) }}
|
||||
</div>
|
||||
|
||||
<div class="turbine-icon">
|
||||
<img src="/static/images/wind-turbine-icon.svg" alt="风机图标" class="turbine-image" />
|
||||
</div>
|
||||
|
||||
<div class="turbine-info">
|
||||
<div class="turbine-number">
|
||||
<a-input v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
|
||||
@change="handleTurbineNoChange(turbine)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="turbine-actions">
|
||||
<a-button type="text" size="mini" @click="openMapModal(turbine)" title="地图选点">
|
||||
<template #icon><icon-location /></template>
|
||||
</a-button>
|
||||
<a-button type="text" size="mini" @click="editTurbine(turbine)" title="编辑">
|
||||
<template #icon><icon-edit /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="turbine-icon">
|
||||
<img src="/static/images/wind-turbine-icon.svg" alt="风机图标" class="turbine-image" />
|
||||
<!-- 添加新机组按钮 -->
|
||||
<div v-if="showAddButton" class="turbine-card add-turbine-card" @click="addTurbine">
|
||||
<div class="add-icon">
|
||||
<icon-plus />
|
||||
</div>
|
||||
<div class="add-text">添加机组</div>
|
||||
</div>
|
||||
|
||||
<div class="turbine-info">
|
||||
<div class="turbine-number">
|
||||
<a-input
|
||||
v-model="turbine.turbineNo" size="small" class="turbine-input" placeholder="请输入机组编号"
|
||||
@change="handleTurbineNoChange(turbine)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="turbine-actions">
|
||||
<a-button type="text" size="mini" title="地图选点" @click="openMapModal(turbine)">
|
||||
<template #icon><icon-location /></template>
|
||||
</a-button>
|
||||
<a-button type="text" size="mini" title="编辑" @click="editTurbine(turbine)">
|
||||
<template #icon><icon-edit /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加新机组按钮 -->
|
||||
<div v-if="showAddButton" class="turbine-card add-turbine-card" @click="addTurbine">
|
||||
<div class="add-icon">
|
||||
<icon-plus />
|
||||
</div>
|
||||
<div class="add-text">添加机组</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface Turbine {
|
||||
id: number
|
||||
turbineNo: string
|
||||
status: 0 | 1 | 2 // 0: 待施工, 1: 施工中, 2: 已完成
|
||||
lat?: number
|
||||
lng?: number
|
||||
id: number
|
||||
turbineNo: string
|
||||
status: 0 | 1 | 2 // 0: 待施工, 1: 施工中, 2: 已完成
|
||||
lat?: number
|
||||
lng?: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
turbines: Turbine[]
|
||||
showAddButton?: boolean
|
||||
turbines: Turbine[]
|
||||
showAddButton?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:turbines', turbines: Turbine[]): void
|
||||
(e: 'turbine-change', turbine: Turbine): void
|
||||
(e: 'add-turbine'): void
|
||||
(e: 'update:turbines', turbines: Turbine[]): void
|
||||
(e: 'turbine-change', turbine: Turbine): void
|
||||
(e: 'add-turbine'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showAddButton: false,
|
||||
showAddButton: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const getStatusText = (status: number) => {
|
||||
const statusMap = {
|
||||
0: '待施工',
|
||||
1: '施工中',
|
||||
2: '已完成',
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
const statusMap = {
|
||||
0: '待施工',
|
||||
1: '施工中',
|
||||
2: '已完成'
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
}
|
||||
|
||||
const getStatusClass = (status: number) => {
|
||||
return `status-${status}`
|
||||
return `status-${status}`
|
||||
}
|
||||
|
||||
const handleTurbineNoChange = (turbine: Turbine) => {
|
||||
emit('turbine-change', turbine)
|
||||
emit('update:turbines', props.turbines)
|
||||
emit('turbine-change', turbine)
|
||||
emit('update:turbines', props.turbines)
|
||||
}
|
||||
|
||||
const openMapModal = (turbine: Turbine) => {
|
||||
Message.info(`地图选点功能待开发,当前机组编号:${turbine.turbineNo}`)
|
||||
Message.info(`地图选点功能待开发,当前机组编号:${turbine.turbineNo}`)
|
||||
}
|
||||
|
||||
const editTurbine = (turbine: Turbine) => {
|
||||
// 可以打开编辑弹窗
|
||||
Message.info(`编辑机组:${turbine.turbineNo}`)
|
||||
// 可以打开编辑弹窗
|
||||
Message.info(`编辑机组:${turbine.turbineNo}`)
|
||||
}
|
||||
|
||||
const addTurbine = () => {
|
||||
emit('add-turbine')
|
||||
emit('add-turbine')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<a-row :gutter="16">
|
||||
<a-col :span="24" :md="17">
|
||||
<GiTable
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="listColumns"
|
||||
|
|
|
@ -225,9 +225,9 @@ export default {
|
|||
// 发送后端请求
|
||||
const captchaVerification = secretKey.value
|
||||
? encryptByAes(
|
||||
`${backToken.value}---${JSON.stringify(checkPosArr)}`,
|
||||
secretKey.value,
|
||||
)
|
||||
`${backToken.value}---${JSON.stringify(checkPosArr)}`,
|
||||
secretKey.value,
|
||||
)
|
||||
: `${backToken.value}---${JSON.stringify(checkPosArr)}`
|
||||
const data = {
|
||||
captchaType: captchaType.value,
|
||||
|
|
|
@ -231,7 +231,7 @@ export default {
|
|||
) {
|
||||
move_block_left
|
||||
= barArea.value.offsetWidth
|
||||
- Number.parseInt(blockSize.value.width, 10) / 2 - 2
|
||||
- Number.parseInt(blockSize.value.width, 10) / 2 - 2
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = Number.parseInt(blockSize.value.width, 10) / 2
|
||||
|
@ -281,9 +281,9 @@ export default {
|
|||
captchaType: captchaType.value,
|
||||
pointJson: secretKey.value
|
||||
? encryptByAes(
|
||||
JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
secretKey.value,
|
||||
)
|
||||
JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
secretKey.value,
|
||||
)
|
||||
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
token: backToken.value,
|
||||
}
|
||||
|
@ -303,21 +303,21 @@ export default {
|
|||
}
|
||||
passFlag.value = true
|
||||
tipWords.value = `${(
|
||||
(endMovetime.value - startMoveTime.value)
|
||||
/ 1000
|
||||
(endMovetime.value - startMoveTime.value)
|
||||
/ 1000
|
||||
).toFixed(2)}s验证成功`
|
||||
const captchaVerification = secretKey.value
|
||||
? encryptByAes(
|
||||
`${backToken.value}---${JSON.stringify({
|
||||
x: moveLeftDistance,
|
||||
y: 5.0,
|
||||
})}`,
|
||||
secretKey.value,
|
||||
)
|
||||
`${backToken.value}---${JSON.stringify({
|
||||
x: moveLeftDistance,
|
||||
y: 5.0,
|
||||
})}`,
|
||||
secretKey.value,
|
||||
)
|
||||
: `${backToken.value}---${JSON.stringify({
|
||||
x: moveLeftDistance,
|
||||
y: 5.0,
|
||||
})}`
|
||||
x: moveLeftDistance,
|
||||
y: 5.0,
|
||||
})}`
|
||||
setTimeout(() => {
|
||||
tipWords.value = ''
|
||||
proxy.$parent.closeBox()
|
||||
|
|
|
@ -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) : [],
|
||||
title: item.deptName || '未命名部门', // 将deptName映射为title
|
||||
children: item.children ? processDeptData(item.children) : []
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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表示启用
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ 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'
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<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'
|
||||
|
||||
|
|
|
@ -76,10 +76,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, nextTick } 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'
|
||||
|
|
|
@ -18,9 +18,10 @@ 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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -5,12 +5,6 @@ const Layout = () => import('@/layout/index.vue')
|
|||
|
||||
/** 系统路由 */
|
||||
export const systemRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/test-console',
|
||||
name: 'TestConsole',
|
||||
component: () => import('@/test-console.vue'),
|
||||
meta: { title: 'Console测试', hidden: true },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
|
@ -32,299 +26,334 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
// }
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/regulation',
|
||||
name: 'Regulation',
|
||||
component: Layout,
|
||||
redirect: '/regulation/system-regulation',
|
||||
meta: { title: '制度管理', icon: 'file-text', hidden: false, sort: 1.5 },
|
||||
children: [
|
||||
{
|
||||
path: '/regulation/system-regulation',
|
||||
name: 'SystemRegulation',
|
||||
component: () => import('@/views/regulation/repository.vue'),
|
||||
meta: { title: '制度公告', icon: 'file-text', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/regulation/process-management',
|
||||
name: 'ProcessManagement',
|
||||
component: () => import('@/views/regulation/confirm.vue'),
|
||||
meta: { title: '制度公示', icon: 'workflow', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/regulation/proposal',
|
||||
name: 'RegulationProposal',
|
||||
component: () => import('@/views/regulation/proposal/index.vue'),
|
||||
meta: { title: '制度提案', icon: 'edit', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/regulation/type',
|
||||
name: 'RegulationType',
|
||||
component: () => import('@/views/regulation/type/index.vue'),
|
||||
meta: { title: '制度类型', icon: 'tag', hidden: false },
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/organization',
|
||||
name: 'Organization',
|
||||
component: Layout,
|
||||
redirect: '/organization/hr/member',
|
||||
redirect: '/organization/dept',
|
||||
meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr',
|
||||
name: 'HRManagement',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/organization/hr/member',
|
||||
meta: { title: '人员管理', icon: 'user', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr/member',
|
||||
name: 'HRMember',
|
||||
component: () => import('@/views/system/user/index.vue'),
|
||||
meta: { title: '成员', icon: 'user', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/dept',
|
||||
name: 'HRDept',
|
||||
component: () => import('@/views/system/dept/index.vue'),
|
||||
meta: { title: '部门', icon: 'dept', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/workload',
|
||||
name: 'HRWorkload',
|
||||
component: () => import('@/views/hr/workload/index.vue'),
|
||||
meta: { title: '工作量', icon: 'workload', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/attendance',
|
||||
name: 'HRAttendance',
|
||||
component: () => import('@/views/hr/attendance/index.vue'),
|
||||
meta: { title: '考勤', icon: 'attendance', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/performance',
|
||||
name: 'HRPerformance',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
meta: { title: '绩效', icon: 'performance', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr/performance/dimention',
|
||||
name: 'Dimention',
|
||||
component: () => import('@/views/performance/setting/index.vue'),
|
||||
meta: { title: '绩效维度', icon: 'performance', hidden: false },
|
||||
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/performance/rule',
|
||||
name: 'Rule',
|
||||
component: () => import('@/views/performance/rule.vue'),
|
||||
meta: { title: '绩效细则', icon: 'performance', hidden: false },
|
||||
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/performance/my',
|
||||
name: 'MyPerformance',
|
||||
component: () => import('@/views/performance/my.vue'),
|
||||
meta: { title: '我的绩效', icon: 'performance', hidden: false },
|
||||
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary',
|
||||
name: 'HRSalary',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/organization/hr/salary/overview',
|
||||
meta: { title: '工资', icon: 'salary', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr/salary/overview',
|
||||
name: 'HRSalaryOverview',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
meta: { title: '工资概览', icon: 'salary', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr/salary/payroll',
|
||||
name: 'Payroll',
|
||||
component: () => import('@/views/salary-management/index.vue'),
|
||||
meta: { title: '工资单', icon: 'salary', hidden: false },
|
||||
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance',
|
||||
// name: 'HRInsurance',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// redirect: '/organization/hr/salary/insurance/overview',
|
||||
// meta: { title: '保险', icon: 'safety', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/overview',
|
||||
// name: 'HRInsuranceOverview',
|
||||
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
|
||||
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/my-insurance',
|
||||
// name: 'HRMyInsurance',
|
||||
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
|
||||
// meta: { title: '我的保险', icon: 'shield', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/health-records',
|
||||
// name: 'HRHealthRecords',
|
||||
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
|
||||
// meta: { title: '健康档案', icon: 'heart', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/policy-files',
|
||||
// name: 'HRPolicyFiles',
|
||||
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
|
||||
// meta: { title: '保单文件', icon: 'file', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/personal-info',
|
||||
// name: 'HRPersonalInfo',
|
||||
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
|
||||
// meta: { title: '个人信息', icon: 'user', hidden: false },
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/health-management',
|
||||
name: 'HRSystemHealthManagement',
|
||||
component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
|
||||
meta: { title: '健康档案管理', icon: 'heart', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance',
|
||||
name: 'HRSystemInsurance',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/organization/hr/salary/system-insurance/overview',
|
||||
meta: { title: '人员保险', icon: 'settings', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/overview',
|
||||
name: 'HRSystemInsuranceOverview',
|
||||
component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
|
||||
meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/management',
|
||||
name: 'HRSystemInsuranceManagement',
|
||||
component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
|
||||
meta: { title: '保险管理', icon: 'shield', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/file-management',
|
||||
name: 'HRSystemFileManagement',
|
||||
component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
|
||||
meta: { title: '保单文件管理', icon: 'file', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/company-management',
|
||||
name: 'HRSystemCompanyManagement',
|
||||
component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
|
||||
meta: { title: '保险公司管理', icon: 'building', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/system-insurance/type-management',
|
||||
name: 'HRSystemTypeManagement',
|
||||
component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
|
||||
meta: { title: '保险类型管理', icon: 'category', hidden: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/salary/certification',
|
||||
name: 'HRCertification',
|
||||
component: () => import('@/views/hr/salary/certification/index.vue'),
|
||||
meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/organization/hr/contribution',
|
||||
name: 'HRContribution',
|
||||
component: () => import('@/views/hr/contribution/index.vue'),
|
||||
meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
|
||||
},
|
||||
],
|
||||
path: '/organization/user',
|
||||
name: 'OrganizationUser',
|
||||
component: () => import('@/views/system/user/index.vue'),
|
||||
meta: { title: '用户管理', icon: 'user', hidden: false, sort: 2.25 },
|
||||
},
|
||||
{
|
||||
path: '/organization/role',
|
||||
name: 'OrganizationRole',
|
||||
component: () => import('@/views/system/role/index.vue'),
|
||||
meta: { title: '角色管理', icon: 'role', hidden: false },
|
||||
path: '/organization/dept',
|
||||
name: 'OrganizationDept',
|
||||
component: () => import('@/views/system/dept/index.vue'),
|
||||
meta: { title: '部门管理', icon: 'mind-mapping', hidden: false, sort: 2.5 },
|
||||
},
|
||||
{
|
||||
path: '/organization/post',
|
||||
name: 'OrganizationPost',
|
||||
component: () => import('@/views/system/post/index.vue'),
|
||||
meta: { title: '岗位管理', icon: 'nav', hidden: false, sort: 2.75 },
|
||||
},
|
||||
// {
|
||||
// path: '/organization/hr/workload',
|
||||
// name: 'HRWorkload',
|
||||
// component: () => import('@/views/hr/workload/index.vue'),
|
||||
// meta: { title: '任务管理', icon: 'bookmark', hidden: false },
|
||||
// },
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: '/organization',
|
||||
// name: 'Organization',
|
||||
// component: Layout,
|
||||
// redirect: '/organization/hr/member',
|
||||
// meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr',
|
||||
// name: 'HRManagement',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// redirect: '/organization/hr/member',
|
||||
// meta: { title: '人员管理', icon: 'user', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/member',
|
||||
// name: 'HRMember',
|
||||
// component: () => import('@/views/system/user/index.vue'),
|
||||
// meta: { title: '成员', icon: 'user', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/dept',
|
||||
// name: 'HRDept',
|
||||
// component: () => import('@/views/system/dept/index.vue'),
|
||||
// meta: { title: '部门', icon: 'dept', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/workload',
|
||||
// name: 'HRWorkload',
|
||||
// component: () => import('@/views/hr/workload/index.vue'),
|
||||
// meta: { title: '任务管理', icon: 'workload', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/attendance',
|
||||
// name: 'HRAttendance',
|
||||
// component: () => import('@/views/hr/attendance/index.vue'),
|
||||
// meta: { title: '考勤', icon: 'attendance', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/performance',
|
||||
// name: 'HRPerformance',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// meta: { title: '绩效', icon: 'performance', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/performance/dimention',
|
||||
// name: 'Dimention',
|
||||
// component: () => import('@/views/performance/setting/index.vue'),
|
||||
// meta: { title: '绩效维度', icon: 'performance', hidden: false },
|
||||
//
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/performance/rule',
|
||||
// name: 'Rule',
|
||||
// component: () => import('@/views/performance/rule.vue'),
|
||||
// meta: { title: '绩效细则', icon: 'performance', hidden: false },
|
||||
//
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/performance/my',
|
||||
// name: 'MyPerformance',
|
||||
// component: () => import('@/views/performance/my.vue'),
|
||||
// meta: { title: '我的绩效', icon: 'performance', hidden: false },
|
||||
//
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary',
|
||||
// name: 'HRSalary',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// redirect: '/organization/hr/salary/overview',
|
||||
// meta: { title: '工资', icon: 'salary', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/salary/overview',
|
||||
// name: 'HRSalaryOverview',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// meta: { title: '工资概览', icon: 'salary', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/salary/payroll',
|
||||
// name: 'Payroll',
|
||||
// component: () => import('@/views/salary-management/index.vue'),
|
||||
// meta: { title: '工资单', icon: 'salary', hidden: false },
|
||||
//
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance',
|
||||
// name: 'HRInsurance',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// redirect: '/organization/hr/salary/insurance/overview',
|
||||
// meta: { title: '保险', icon: 'safety', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/overview',
|
||||
// name: 'HRInsuranceOverview',
|
||||
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
|
||||
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/my-insurance',
|
||||
// name: 'HRMyInsurance',
|
||||
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
|
||||
// meta: { title: '我的保险', icon: 'shield', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/health-records',
|
||||
// name: 'HRHealthRecords',
|
||||
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
|
||||
// meta: { title: '健康档案', icon: 'heart', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/policy-files',
|
||||
// name: 'HRPolicyFiles',
|
||||
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
|
||||
// meta: { title: '保单文件', icon: 'file', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/insurance/personal-info',
|
||||
// name: 'HRPersonalInfo',
|
||||
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
|
||||
// meta: { title: '个人信息', icon: 'user', hidden: false },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/health-management',
|
||||
// name: 'HRSystemHealthManagement',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
|
||||
// meta: { title: '健康档案管理', icon: 'heart', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance',
|
||||
// name: 'HRSystemInsurance',
|
||||
// component: () => import('@/components/ParentView/index.vue'),
|
||||
// redirect: '/organization/hr/salary/system-insurance/overview',
|
||||
// meta: { title: '人员保险', icon: 'settings', hidden: false },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/overview',
|
||||
// name: 'HRSystemInsuranceOverview',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
|
||||
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/management',
|
||||
// name: 'HRSystemInsuranceManagement',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
|
||||
// meta: { title: '保险管理', icon: 'shield', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/file-management',
|
||||
// name: 'HRSystemFileManagement',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
|
||||
// meta: { title: '保单文件管理', icon: 'file', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/company-management',
|
||||
// name: 'HRSystemCompanyManagement',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
|
||||
// meta: { title: '保险公司管理', icon: 'building', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/system-insurance/type-management',
|
||||
// name: 'HRSystemTypeManagement',
|
||||
// component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
|
||||
// meta: { title: '保险类型管理', icon: 'category', hidden: false },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/salary/certification',
|
||||
// name: 'HRCertification',
|
||||
// component: () => import('@/views/hr/salary/certification/index.vue'),
|
||||
// meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/hr/contribution',
|
||||
// name: 'HRContribution',
|
||||
// component: () => import('@/views/hr/contribution/index.vue'),
|
||||
// meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/organization/role',
|
||||
// name: 'OrganizationRole',
|
||||
// component: () => import('@/views/system/role/index.vue'),
|
||||
// meta: { title: '角色管理', icon: 'role', hidden: false },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/asset-management',
|
||||
name: 'AssetManagement',
|
||||
component: Layout,
|
||||
redirect: '/asset-management/device-management/device-center',
|
||||
redirect: '/asset-management/device/inventory',
|
||||
meta: { title: '资产管理', icon: 'property-safety', hidden: false, sort: 3 },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/device-management',
|
||||
name: 'DeviceManagement',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/asset-management/device-management/device-center',
|
||||
meta: {
|
||||
title: '设备管理',
|
||||
icon: 'device',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty1',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '设备管理', icon: 'copyright', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/device-management/device-center',
|
||||
name: 'DeviceCenter',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '设备中心',
|
||||
icon: 'appstore',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty11',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '库存管理', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/device-management/device-detail/:id',
|
||||
name: 'DeviceDetail',
|
||||
component: () => import('@/views/system-resource/device-management/detail.vue'),
|
||||
meta: {
|
||||
title: '设备详情',
|
||||
icon: 'info-circle',
|
||||
hidden: true,
|
||||
},
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty12',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '设备采购', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/device-management/procurement',
|
||||
name: 'DeviceProcurement',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '设备采购',
|
||||
icon: 'shopping-cart',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/asset-management/device-management/online',
|
||||
name: 'DeviceOnline',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/asset-management/device-management/online/drone',
|
||||
meta: {
|
||||
title: '在线管理',
|
||||
icon: 'cloud',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty13',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '在线管理', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/device-management/online/drone',
|
||||
name: 'DeviceDrone',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '无人机',
|
||||
icon: 'drone',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property11',
|
||||
name: 'IntellectualProperty14',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '无人机', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/device-management/online/nest',
|
||||
name: 'DeviceNest',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '机巢',
|
||||
icon: 'nest',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property12',
|
||||
name: 'IntellectualProperty15',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '机巢', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/device-management/online/smart-terminal',
|
||||
name: 'DeviceSmartTerminal',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '其他智能终端',
|
||||
icon: 'terminal',
|
||||
hidden: false,
|
||||
},
|
||||
path: '/asset-management/intellectual-property13',
|
||||
name: 'IntellectualProperty16',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '其他智能终端', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property14',
|
||||
name: 'IntellectualProperty17',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '车辆管理', hidden: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/asset-management/other-assets',
|
||||
name: 'OtherAssets',
|
||||
path: '/asset-management/intellectual-property',
|
||||
name: 'IntellectualProperty',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '其他资产', icon: 'copyright', hidden: false },
|
||||
},
|
||||
|
@ -529,9 +558,9 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
path: 'project-management/project-template/information-retrieval',
|
||||
path: '/project-management/project-template/information-retrieval',
|
||||
name: 'InformationRetrieval',
|
||||
component: () => import ('@/views/default/error/404.vue'),
|
||||
component: () => import ('@/views/project-management/bidding/information-retrieval/index.vue'),
|
||||
meta: {
|
||||
title: '信息检索(N)',
|
||||
icon: 'trophy',
|
||||
|
@ -643,11 +672,11 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
|
||||
{
|
||||
path: '/project-management/projects/device',
|
||||
name: 'ProjectDeviceManagement',
|
||||
name: 'DeviceManagement',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '项目设备管理',
|
||||
icon: 'device',
|
||||
title: '设备管理',
|
||||
icon: 'none',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
|
@ -780,11 +809,11 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment',
|
||||
name: 'AttachmentManagement',
|
||||
component: () => import('@/views/operation-platform/data-processing/data-storage/index.vue'),
|
||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-storage/index.vue'),
|
||||
meta: { title: '附件管理', icon: 'attachment', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/model-config',
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/model-config',
|
||||
name: 'ModelConfig',
|
||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'),
|
||||
meta: { title: '模型配置', icon: 'robot', hidden: false },
|
||||
|
@ -795,8 +824,23 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data',
|
||||
name: 'PreprocessedData',
|
||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'),
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/construction-operation-platform/implementation-workflow/data-processing/data-storage',
|
||||
meta: { title: '数据预处理', icon: 'filter', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageBatchUpload',
|
||||
name: 'ImageBatchUpload',
|
||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'),
|
||||
meta: { title: '批量上传', icon: 'file', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageSorting',
|
||||
name: 'ImageSorting',
|
||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/image-sorting/index.vue'),
|
||||
meta: { title: '图像分拣', icon: 'attachment', hidden: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection',
|
||||
|
@ -972,6 +1016,30 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
// }
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/user/profile',
|
||||
name: 'UserProfile',
|
||||
component: Layout,
|
||||
redirect: '/user/profile',
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
icon: 'user',
|
||||
hidden: false,
|
||||
sort: 100,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/user/profile',
|
||||
name: 'UserProfile',
|
||||
component: () => import('@/views/user/profile/index.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
icon: 'user',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/enterprise-settings',
|
||||
name: 'EnterpriseSettings',
|
||||
|
@ -1070,32 +1138,66 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/training',
|
||||
name: 'Training',
|
||||
component: Layout,
|
||||
redirect: '/training/plan',
|
||||
meta: { title: '培训管理', icon: 'book', hidden: false, sort: 9 },
|
||||
children: [
|
||||
{
|
||||
path: '/training/plan',
|
||||
name: 'TrainingPlan',
|
||||
component: () => import('@/views/training/plan/index.vue'),
|
||||
meta: {
|
||||
title: '培训计划',
|
||||
icon: 'calendar',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/system-resource',
|
||||
name: 'SystemResource',
|
||||
component: Layout,
|
||||
redirect: '/system-resource/information-system/software-management',
|
||||
meta: { title: '系统资源', icon: 'server', hidden: false, sort: 9 },
|
||||
redirect: '/system-resource/device-management/warehouse',
|
||||
meta: { title: '关于平台', icon: 'server', hidden: false, sort: 9 },
|
||||
children: [
|
||||
{
|
||||
path: '/system-resource/device-management/warehouse',
|
||||
name: 'DeviceWarehouse',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '库存管理',
|
||||
icon: 'warehouse',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online',
|
||||
name: 'DeviceOnline',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/system-resource/device-management/online/drone',
|
||||
meta: {
|
||||
title: '在线管理',
|
||||
icon: 'cloud',
|
||||
hidden: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/system-resource/device-management/online/drone',
|
||||
name: 'DeviceDrone',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '无人机',
|
||||
icon: 'drone',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online/nest',
|
||||
name: 'DeviceNest',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '机巢',
|
||||
icon: 'nest',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online/smart-terminal',
|
||||
name: 'DeviceSmartTerminal',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '其他智能终端',
|
||||
icon: 'terminal',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/system-resource/information-system',
|
||||
name: 'InformationSystem',
|
||||
|
@ -1141,7 +1243,6 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/project-management/project-template/project-aproval',
|
||||
|
@ -1167,6 +1268,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: '/user/profile',
|
||||
// component: () => import('@/views/user/profile/index.vue'),
|
||||
// meta: { hidden: true },
|
||||
// },
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: () => import('@/views/default/error/404.vue'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { computed, reactive, toRefs, watchEffect } from 'vue'
|
||||
import { computed, reactive, toRefs, watch, watchEffect } from 'vue'
|
||||
import { generate, getRgbStr } from '@arco-design/color'
|
||||
import type { BasicConfig } from '@/apis'
|
||||
import { type BasicConfig, listSiteOptionDict } from '@/apis'
|
||||
import { getSettings } from '@/config/setting'
|
||||
|
||||
const storeSetup = () => {
|
||||
|
|
|
@ -149,34 +149,6 @@ const storeSetup = () => {
|
|||
isHidden: false,
|
||||
sort: 3,
|
||||
},
|
||||
{
|
||||
id: 1070,
|
||||
parentId: 1000,
|
||||
title: '部门管理',
|
||||
type: 2,
|
||||
path: '/system/dept',
|
||||
name: 'SystemDept',
|
||||
component: 'system/dept/index',
|
||||
icon: 'mind-mapping',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 4,
|
||||
},
|
||||
{
|
||||
id: 1090,
|
||||
parentId: 1000,
|
||||
title: '岗位管理',
|
||||
type: 2,
|
||||
path: '/system/post',
|
||||
name: 'SystemPost',
|
||||
component: 'system/post/index',
|
||||
icon: 'settings',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 5,
|
||||
},
|
||||
],
|
||||
}]
|
||||
// 使用已转换的数据生成路由
|
||||
|
|
|
@ -5,10 +5,10 @@ import {
|
|||
type AccountLoginReq,
|
||||
AuthTypeConstants,
|
||||
|
||||
type DeptDetail,
|
||||
type PhoneLoginReq,
|
||||
type RoleDetail,
|
||||
type UserDetail,
|
||||
type DeptDetail,
|
||||
type RoleDetail,
|
||||
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,6 +68,8 @@ 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 })
|
||||
|
@ -75,6 +77,8 @@ const storeSetup = () => {
|
|||
token.value = res.data.token
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 退出登录回调
|
||||
const logoutCallBack = async () => {
|
||||
roles.value = []
|
||||
|
@ -124,7 +128,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,这里默认给管理员全部权限
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@use './var.scss' as *;
|
||||
@import './var.scss';
|
||||
|
||||
body {
|
||||
--margin: 14px; // 通用外边距
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* 全局样式 */
|
||||
@use './var.scss' as *;
|
||||
@import './var.scss';
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// 基础样式
|
||||
@use './base.scss';
|
||||
@import './base.scss';
|
||||
|
||||
// 全局类名样式
|
||||
@use './global.scss';
|
||||
@import './global.scss';
|
||||
|
||||
// 自定义原生滚动条样式
|
||||
@use './scrollbar-reset.scss';
|
||||
@import './scrollbar-reset.scss';
|
||||
|
||||
// 自定义 nprogress 插件进度条颜色
|
||||
@use './nprogress.scss';
|
||||
@import './nprogress.scss';
|
||||
|
||||
// 富文本的css主题颜色变量
|
||||
@use './editor.scss';
|
||||
@import './editor.scss';
|
||||
|
||||
// 动画类名
|
||||
@use './animated.scss';
|
||||
@import './animated.scss';
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2>Console.log 测试</h2>
|
||||
<button @click="testConsole">测试 Console.log</button>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const message = ref('')
|
||||
|
||||
const testConsole = () => {
|
||||
console.log('测试 console.log 是否正常工作')
|
||||
console.warn('测试 console.warn')
|
||||
console.error('测试 console.error')
|
||||
message.value = '请查看浏览器控制台,应该能看到上述日志信息'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
margin: 10px;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #40a9ff;
|
||||
}
|
||||
</style>
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** 分页响应数据格式 */
|
||||
|
|
|
@ -70,6 +70,6 @@ declare global {
|
|||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
export interface EquipmentPageQuery {
|
||||
equipmentName?: string
|
||||
equipmentType?: string
|
||||
equipmentStatus?: string
|
||||
equipmentSn?: string
|
||||
assetCode?: string
|
||||
brand?: string
|
||||
locationStatus?: string
|
||||
healthStatus?: string
|
||||
responsiblePerson?: string
|
||||
useStatus?: string
|
||||
projectId?: string
|
||||
userId?: string
|
||||
equipmentModel?: string
|
||||
specification?: string
|
||||
physicalLocation?: string
|
||||
supplierName?: string
|
||||
maintenancePerson?: string
|
||||
inventoryBarcode?: string
|
||||
assetRemark?: string
|
||||
// 新增搜索字段
|
||||
usingDepartment?: string
|
||||
invoice?: string
|
||||
barcode?: string
|
||||
importer?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
orderBy?: string
|
||||
orderDirection?: string
|
||||
}
|
||||
|
||||
export interface EquipmentReq {
|
||||
equipmentName: string
|
||||
equipmentModel: string
|
||||
equipmentType: string
|
||||
equipmentStatus: string
|
||||
useStatus: string
|
||||
equipmentSn: string
|
||||
assetCode?: string
|
||||
brand?: string
|
||||
specification?: string
|
||||
locationStatus?: string
|
||||
physicalLocation?: string
|
||||
responsiblePerson?: string
|
||||
healthStatus?: string
|
||||
purchaseTime?: string
|
||||
inStockTime?: string
|
||||
activationTime?: string
|
||||
expectedScrapTime?: string
|
||||
actualScrapTime?: string
|
||||
statusChangeTime?: string
|
||||
purchaseOrder?: string
|
||||
supplierName?: string
|
||||
purchasePrice?: number
|
||||
currentNetValue?: number
|
||||
depreciationMethod?: string
|
||||
depreciationYears?: number
|
||||
salvageValue?: number
|
||||
warrantyExpireDate?: string
|
||||
lastMaintenanceDate?: string
|
||||
nextMaintenanceDate?: string
|
||||
maintenancePerson?: string
|
||||
inventoryBarcode?: string
|
||||
assetRemark?: string
|
||||
// 新增字段
|
||||
usingDepartment?: string
|
||||
borrowingTime?: string
|
||||
returnTime?: string
|
||||
outStockTime?: string
|
||||
totalUsageTime?: string
|
||||
depreciationRate?: number
|
||||
depreciationMethodDesc?: string
|
||||
invoice?: string
|
||||
invoiceStatus?: string
|
||||
attachments?: string
|
||||
photos?: string
|
||||
barcode?: string
|
||||
importer?: string
|
||||
inventoryTimeStatus1?: string
|
||||
inventoryTimeStatus2?: string
|
||||
inventoryTimeStatus3?: string
|
||||
inventoryCheckTimeStatus1?: string
|
||||
inventoryCheckTimeStatus2?: string
|
||||
inventoryCheckTimeStatus3?: string
|
||||
}
|
||||
|
||||
export interface EquipmentResp {
|
||||
equipmentId: string
|
||||
assetCode?: string
|
||||
equipmentName: string
|
||||
equipmentType: string
|
||||
equipmentTypeLabel?: string
|
||||
equipmentModel: string
|
||||
equipmentSn: string
|
||||
brand?: string
|
||||
specification?: string
|
||||
equipmentStatus: string
|
||||
equipmentStatusLabel?: string
|
||||
useStatus: string
|
||||
locationStatus?: string
|
||||
locationStatusLabel?: string
|
||||
physicalLocation?: string
|
||||
responsiblePerson?: string
|
||||
healthStatus?: string
|
||||
healthStatusLabel?: string
|
||||
purchaseTime?: string
|
||||
inStockTime?: string
|
||||
activationTime?: string
|
||||
expectedScrapTime?: string
|
||||
actualScrapTime?: string
|
||||
statusChangeTime?: string
|
||||
purchaseOrder?: string
|
||||
supplierName?: string
|
||||
purchasePrice?: number
|
||||
currentNetValue?: number
|
||||
depreciationMethod?: string
|
||||
depreciationYears?: number
|
||||
salvageValue?: number
|
||||
warrantyExpireDate?: string
|
||||
lastMaintenanceDate?: string
|
||||
nextMaintenanceDate?: string
|
||||
maintenancePerson?: string
|
||||
inventoryBarcode?: string
|
||||
assetRemark?: string
|
||||
// 新增字段
|
||||
usingDepartment?: string
|
||||
borrowingTime?: string
|
||||
returnTime?: string
|
||||
outStockTime?: string
|
||||
totalUsageTime?: string
|
||||
depreciationRate?: number
|
||||
depreciationMethodDesc?: string
|
||||
invoice?: string
|
||||
invoiceStatus?: string
|
||||
attachments?: string
|
||||
photos?: string
|
||||
barcode?: string
|
||||
importer?: string
|
||||
inventoryTimeStatus1?: string
|
||||
inventoryTimeStatus2?: string
|
||||
inventoryTimeStatus3?: string
|
||||
inventoryCheckTimeStatus1?: string
|
||||
inventoryCheckTimeStatus2?: string
|
||||
inventoryCheckTimeStatus3?: string
|
||||
projectId?: string
|
||||
projectName?: string
|
||||
userId?: string
|
||||
name?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export interface EquipmentTypeOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface EquipmentStatusOption {
|
||||
label: string
|
||||
value: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface LocationStatusOption {
|
||||
label: string
|
||||
value: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface HealthStatusOption {
|
||||
label: string
|
||||
value: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface DepreciationMethodOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
export interface TrainingPlanPageQuery {
|
||||
planName?: string
|
||||
trainingType?: string
|
||||
trainingLevel?: string
|
||||
status?: string
|
||||
trainer?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export interface TrainingPlanReq {
|
||||
planName: string
|
||||
trainingType: string
|
||||
trainingLevel: string
|
||||
trainingContent?: string
|
||||
trainer?: string
|
||||
trainingLocation?: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
status?: string
|
||||
maxParticipants?: number
|
||||
requirements?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface TrainingPlanResp {
|
||||
planId: string
|
||||
planName: string
|
||||
trainingType: string
|
||||
trainingLevel: string
|
||||
trainingContent?: string
|
||||
trainer?: string
|
||||
trainingLocation?: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
status: string
|
||||
maxParticipants?: number
|
||||
currentParticipants?: number
|
||||
requirements?: string
|
||||
remark?: string
|
||||
createTime: string
|
||||
createBy: string
|
||||
materials?: TrainingMaterialResp[]
|
||||
records?: TrainingRecordResp[]
|
||||
}
|
||||
|
||||
export interface TrainingMaterialResp {
|
||||
materialId: string
|
||||
materialName: string
|
||||
materialType: string
|
||||
materialPath?: string
|
||||
materialSize?: number
|
||||
description?: string
|
||||
sortOrder?: number
|
||||
}
|
||||
|
||||
export interface TrainingRecordResp {
|
||||
recordId: string
|
||||
userId: string
|
||||
userName: string
|
||||
deptId?: string
|
||||
deptName?: string
|
||||
attendanceStatus: string
|
||||
signInTime?: string
|
||||
signOutTime?: string
|
||||
score?: number
|
||||
feedback?: string
|
||||
certificateId?: string
|
||||
}
|
|
@ -18,7 +18,7 @@ export function encryptByMd5(txt: string) {
|
|||
|
||||
const publicKey
|
||||
= 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u'
|
||||
+ 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
|
||||
+ 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
|
||||
|
||||
export function encryptByRsa(txt: string) {
|
||||
const encryptor = new JSEncrypt()
|
||||
|
|
|
@ -30,7 +30,7 @@ const StatusCodeMessage: ICodeMessage = {
|
|||
}
|
||||
|
||||
const http: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 30 * 1000,
|
||||
})
|
||||
|
||||
|
@ -145,11 +145,6 @@ 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
|
||||
|
|
|
@ -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,
|
||||
name,
|
||||
component,
|
||||
path: path,
|
||||
name: name,
|
||||
component: 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));
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<GiPageLayout>
|
||||
<GiTable
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
row-key="tableName"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,11 +37,11 @@
|
|||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><IconSearch /></template>
|
||||
<template #icon><icon-search /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="resetSearch">
|
||||
<template #icon><IconRefresh /></template>
|
||||
<template #icon><icon-refresh /></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><IconDownload /></template>
|
||||
<template #icon><icon-download /></template>
|
||||
批量下载
|
||||
</a-button>
|
||||
<a-button
|
||||
|
@ -65,7 +65,7 @@
|
|||
:disabled="selectedRowKeys.length === 0"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
<template #icon><IconDelete /></template>
|
||||
<template #icon><icon-delete /></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,
|
||||
onChange: onSelectionChange,
|
||||
selectedRowKeys: 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><IconEye /></template>
|
||||
<template #icon><icon-eye /></template>
|
||||
预览
|
||||
</a-button>
|
||||
<a-button size="small" @click="downloadFile(record)">
|
||||
<template #icon><IconDownload /></template>
|
||||
<template #icon><icon-download /></template>
|
||||
下载
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
|
@ -128,7 +128,7 @@
|
|||
@ok="deleteFile(record)"
|
||||
>
|
||||
<a-button size="small" status="danger">
|
||||
<template #icon><IconDelete /></template>
|
||||
<template #icon><icon-delete /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
@ -160,18 +160,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconDelete,
|
||||
IconDownload,
|
||||
IconEye,
|
||||
IconFile,
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import FilePreview from '@/components/FilePreview/index.vue'
|
||||
import { deleteAttachment, getAttachBusinessTypes, getAttachmentList } from '@/apis/attach-info'
|
||||
import {
|
||||
IconSearch,
|
||||
IconRefresh,
|
||||
IconDownload,
|
||||
IconDelete,
|
||||
IconEye,
|
||||
IconFile
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import { getAttachBusinessTypes, getAttachmentList, deleteAttachment } from '@/apis/attach-info'
|
||||
import type { AttachInfoData, BusinessType } from '@/apis/attach-info/type'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="attachment-upload">
|
||||
<a-space direction="vertical" :size="16" style="width: 100%">
|
||||
<a-form ref="formRef" :model="formData">
|
||||
<a-form :model="formData" ref="formRef">
|
||||
<a-row :gutter="16">
|
||||
<a-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" :loading="submitting" @click="handleSubmit">提交</a-button>
|
||||
<a-button type="primary" @click="handleSubmit" :loading="submitting">提交</a-button>
|
||||
<a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
|
@ -73,10 +73,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||
import { batchAddAttachment, getAttachBusinessTypes } from '@/apis/attach-info'
|
||||
import { getAttachBusinessTypes, batchAddAttachment } from '@/apis/attach-info'
|
||||
import type { BusinessType } from '@/apis/attach-info/type'
|
||||
|
||||
defineOptions({ name: 'AttachmentUpload' })
|
||||
|
@ -98,15 +98,23 @@ 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) {
|
||||
businessTypes.value = res.data
|
||||
res.data.forEach(item => {
|
||||
const key = Object.keys(item)[0];
|
||||
const value = item[key];
|
||||
businessTypes.value.push({
|
||||
name: value,
|
||||
code:key
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取业务类型失败:', error)
|
||||
|
@ -126,7 +134,7 @@ const customRequest = (options: any) => {
|
|||
file,
|
||||
status: 'ready',
|
||||
uid: options.fileItem.uid,
|
||||
name: options.fileItem.name,
|
||||
name: options.fileItem.name
|
||||
}
|
||||
formData.files.push(fileItem)
|
||||
|
||||
|
@ -172,7 +180,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)
|
||||
|
@ -185,7 +193,7 @@ const handleSubmit = async () => {
|
|||
}
|
||||
} catch (error: any) {
|
||||
console.error('上传失败:', error)
|
||||
Message.error(`上传失败: ${error.msg || '未知错误'}`)
|
||||
Message.error('上传失败: ' + (error.msg || '未知错误'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="panel-header">
|
||||
<h3>自动识别设置</h3>
|
||||
<a-button type="text" @click="$emit('close')">
|
||||
<template #icon><IconClose /></template>
|
||||
<template #icon><icon-close /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
|
@ -64,10 +64,10 @@
|
|||
</div>
|
||||
|
||||
<div class="panel-actions">
|
||||
<a-button type="primary" :loading="isRecognizing" block @click="handleStartRecognition">
|
||||
<a-button type="primary" @click="handleStartRecognition" :loading="isRecognizing" block>
|
||||
开始识别
|
||||
</a-button>
|
||||
<a-button block @click="handleResetSettings">
|
||||
<a-button @click="handleResetSettings" block>
|
||||
重置设置
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -76,14 +76,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref, computed, watch, onMounted } 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 { DefectTypeOption, DefectTypeResp } from '@/apis/common/type'
|
||||
import type { DefectTypeResp, DefectTypeOption } 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>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="form-header">
|
||||
<h3>缺陷详情</h3>
|
||||
<a-button type="text" @click="$emit('close')">
|
||||
<template #icon><IconClose /></template>
|
||||
<template #icon><icon-close /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
|
@ -15,8 +15,8 @@
|
|||
|
||||
<!-- 缺陷位置 -->
|
||||
<a-form-item
|
||||
field="defectPosition"
|
||||
label="缺陷位置"
|
||||
field="defectPosition"
|
||||
label="缺陷位置"
|
||||
>
|
||||
<a-input v-model="form.defectPosition" placeholder="请输入缺陷位置" />
|
||||
</a-form-item>
|
||||
|
@ -36,37 +36,37 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 轴向尺寸 -->
|
||||
<!--轴向尺寸-->
|
||||
<div class="dimension-fields">
|
||||
<a-form-item
|
||||
field="axialDimension"
|
||||
label="轴向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
field="axialDimension"
|
||||
label="轴向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.axialDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入轴向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
v-model="form.axialDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入轴向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 弦向尺寸 -->
|
||||
<!--弦向尺寸 -->
|
||||
<a-form-item
|
||||
field="chordDimension"
|
||||
label="弦向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
field="chordDimension"
|
||||
label="弦向尺寸 (mm)"
|
||||
:rules="[{ type: 'number', min: 0, message: '必须大于等于0' }]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.chordDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入弦向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
v-model="form.chordDimension"
|
||||
:min="0"
|
||||
:step="1"
|
||||
placeholder="请输入弦向尺寸"
|
||||
mode="button"
|
||||
size="large"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
|
@ -104,29 +104,32 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
<div class="form-footer">
|
||||
<a-button size="large" @click="$emit('close')">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<template #icon><IconSave /></template>
|
||||
保存缺陷
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button @click="$emit('close')" size="large">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="handleSubmit"
|
||||
:loading="submitting"
|
||||
>
|
||||
<template #icon><icon-save /></template>
|
||||
保存缺陷
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import { IconClose, IconSave } from '@arco-design/web-vue/es/icon'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getDefectLevels } from '@/apis/industrial-image/defect'
|
||||
import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
|
||||
import { getDefectLevels, getDefectTypes, type DefectLevelType, type DefectType } from '@/apis/industrial-image/defect'
|
||||
import { listDefectType } from '@/apis/common/common'
|
||||
import type { DefectTypeOption } from '@/apis/common/type'
|
||||
import type { DefectTypeResp, DefectTypeOption } from '@/apis/common/type'
|
||||
|
||||
interface DefectFormData {
|
||||
defectName: string
|
||||
|
@ -171,8 +174,8 @@ const form = reactive<DefectFormData>({
|
|||
defectPosition: '',
|
||||
description: '',
|
||||
repairIdea: '建议进行进一步检查',
|
||||
axialDimension: 0, // 初始为null
|
||||
chordDimension: 0, // 初始为null
|
||||
axialDimension: 0, // 初始为null
|
||||
chordDimension: 0 // 初始为null
|
||||
})
|
||||
|
||||
// 获取缺陷类型列表
|
||||
|
@ -194,7 +197,7 @@ const loadDefectTypes = async () => {
|
|||
defectTypeOptions.push({
|
||||
value: code,
|
||||
label: name as string,
|
||||
code,
|
||||
code: code
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -217,6 +220,7 @@ const loadDefectTypes = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// 加载缺陷等级
|
||||
const loadDefectLevels = async () => {
|
||||
try {
|
||||
|
@ -229,13 +233,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, // 使用name作为label
|
||||
label: item.name, // 使用name作为label
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
sort: item.sort,
|
||||
sort: item.sort
|
||||
})
|
||||
})
|
||||
defectLevels.value = defectLevelOptions
|
||||
|
@ -246,10 +250,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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -260,7 +264,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: '严重' }
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -268,9 +272,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
|
||||
|
@ -281,7 +285,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
|
||||
|
@ -352,18 +356,19 @@ 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('提交失败,请重试')
|
||||
|
@ -374,7 +379,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
|
||||
}
|
||||
|
@ -382,7 +387,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
|
||||
}
|
||||
|
|
|
@ -162,8 +162,8 @@
|
|||
<div class="action-button-container">
|
||||
<a-button
|
||||
size="small"
|
||||
class="standard-library-btn"
|
||||
@click="handleSelectFromStandardDescription"
|
||||
class="standard-library-btn"
|
||||
>
|
||||
从标准描述库选择
|
||||
</a-button>
|
||||
|
@ -183,8 +183,8 @@
|
|||
<div class="action-button-container">
|
||||
<a-button
|
||||
size="small"
|
||||
class="standard-library-btn"
|
||||
@click="handleSelectFromStandardInfo"
|
||||
class="standard-library-btn"
|
||||
>
|
||||
从标准信息库选择
|
||||
</a-button>
|
||||
|
@ -257,7 +257,7 @@
|
|||
|
||||
<!-- 无缺陷选中状态 -->
|
||||
<div v-else class="no-defect-selected">
|
||||
<IconFile class="empty-icon" />
|
||||
<icon-file 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 }"
|
||||
class="clickable-item"
|
||||
:class="{ 'selected': selectedStandardDescription === item }"
|
||||
@click="selectedStandardDescription = item"
|
||||
class="clickable-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 }"
|
||||
class="clickable-item"
|
||||
:class="{ 'selected': selectedStandardInfo === item }"
|
||||
@click="selectedStandardInfo = item"
|
||||
class="clickable-item"
|
||||
>
|
||||
<div class="description-item">
|
||||
<div class="description-title">{{ item.title }}</div>
|
||||
|
@ -316,7 +316,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { IconFile } from '@arco-design/web-vue/es/icon'
|
||||
import { 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 || ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="!canAddDefect"
|
||||
@click="handleAddDefect"
|
||||
:disabled="!canAddDefect"
|
||||
>
|
||||
<template #icon><IconPlus /></template>
|
||||
<template #icon><icon-plus /></template>
|
||||
新增缺陷
|
||||
</a-button>
|
||||
<a-button type="text" @click="$emit('close')">
|
||||
<template #icon><IconClose /></template>
|
||||
<template #icon><icon-close /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,11 +34,11 @@
|
|||
<template #title="node">
|
||||
<div class="tree-node">
|
||||
<span class="node-icon">
|
||||
<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 />
|
||||
<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 />
|
||||
</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 { IconApps, IconBug, IconClose, IconFolder, IconPlus, IconSettings, IconTool } from '@arco-design/web-vue/es/icon'
|
||||
import { IconClose, IconBug, IconFolder, IconPlus, IconSettings, IconTool, IconApps } from '@arco-design/web-vue/es/icon'
|
||||
import type { PropType } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { ref, computed, watch } 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)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div class="header-toolbar">
|
||||
<div class="toolbar-buttons">
|
||||
<a-button size="large" :disabled="!currentImageId" @click="handleAutoAnnotate">
|
||||
<template #icon><IconRobot /></template>
|
||||
<a-button size="large" @click="handleAutoAnnotate" :disabled="!currentImageId">
|
||||
<template #icon><icon-robot /></template>
|
||||
自动标注
|
||||
</a-button>
|
||||
<a-button size="large" :disabled="!currentImageId" @click="handleManualAnnotate">
|
||||
<template #icon><IconEdit /></template>
|
||||
<a-button size="large" @click="handleManualAnnotate" :disabled="!currentImageId">
|
||||
<template #icon><icon-edit /></template>
|
||||
手动标注
|
||||
</a-button>
|
||||
<a-button size="large" @click="handleGenerateReport">
|
||||
<template #icon><IconFileImage /></template>
|
||||
<template #icon><icon-file-image /></template>
|
||||
生成检测报告
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -19,9 +19,10 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
IconEdit,
|
||||
IconFileImage,
|
||||
IconPlayArrow,
|
||||
IconRobot,
|
||||
IconEdit,
|
||||
IconFileImage
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
|
||||
defineProps<{
|
||||
|
@ -39,6 +40,8 @@ const handleStart = () => {
|
|||
emit('start')
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleAutoAnnotate = () => {
|
||||
emit('autoAnnotate')
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:visible="previewModalVisible"
|
||||
title="图像详情"
|
||||
width="80%"
|
||||
:footer="footerButtons"
|
||||
:mask-closable="true"
|
||||
:confirm-loading="loading"
|
||||
@update:visible="emit('update:previewModalVisible', $event)"
|
||||
:visible="previewModalVisible"
|
||||
title="图像详情"
|
||||
width="80%"
|
||||
:footer="footerButtons"
|
||||
:mask-closable="true"
|
||||
@update:visible="emit('update:previewModalVisible', $event)"
|
||||
:confirm-loading="loading"
|
||||
>
|
||||
<div v-if="previewImage" class="modal-image-viewer">
|
||||
<img :src="getImageUrl(previewImage.imagePath)" :alt="editingData.imageName" class="preview-image" />
|
||||
|
@ -62,8 +62,8 @@
|
|||
|
||||
<a-form-item label="图片类型描述" field="imageTypeLabel">
|
||||
<a-textarea
|
||||
v-model="editingData.imageTypeLabel"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
v-model="editingData.imageTypeLabel"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
@ -77,7 +77,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, computed } 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://localhost:8080'
|
||||
const baseUrl = 'http://pms.dtyx.net:9158'
|
||||
return `${baseUrl}${imagePath}`
|
||||
}
|
||||
|
||||
|
@ -177,18 +177,17 @@ 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.put(
|
||||
`http://localhost:8080/image/setting-info/${editingData.value.partId}`,
|
||||
requestData,
|
||||
const response = await axios.post(
|
||||
`http://pms.dtyx.net:9158/image/setting-info/${editingData.value.partId}`,
|
||||
requestData
|
||||
)
|
||||
|
||||
if (response.data && response.data.code === 0) {
|
||||
if (response.data && response.data.code === 200 && response.data.success) {
|
||||
Message.success('图片信息保存成功')
|
||||
emit('save', editingData.value)
|
||||
emit('update:previewModalVisible', false)
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
<!-- 缩放控制工具栏 -->
|
||||
<div v-if="selectedImage" class="zoom-controls">
|
||||
<a-space>
|
||||
<a-button size="small" :disabled="scale <= MIN_SCALE" @click="zoomOut">
|
||||
<a-button size="small" @click="zoomOut" :disabled="scale <= MIN_SCALE">
|
||||
<template #icon>
|
||||
<IconMinus />
|
||||
<icon-minus />
|
||||
</template>
|
||||
</a-button>
|
||||
<span class="zoom-text">{{ Math.round(scale * 100) }}%</span>
|
||||
<a-button size="small" :disabled="scale >= MAX_SCALE" @click="zoomIn">
|
||||
<a-button size="small" @click="zoomIn" :disabled="scale >= MAX_SCALE">
|
||||
<template #icon>
|
||||
<IconPlus />
|
||||
<icon-plus />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button size="small" @click="resetZoom">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
<icon-refresh />
|
||||
</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">
|
||||
<IconImage class="empty-icon" />
|
||||
<icon-image class="empty-icon" />
|
||||
<p>请从下方缩略图中选择图像</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { IconImage, IconMinus, IconPlus, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
|
||||
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://localhost:8080'
|
||||
const baseUrl = 'http://pms.dtyx.net:9158'
|
||||
return `${baseUrl}${imagePath}`
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<template #title="node">
|
||||
<div class="tree-node">
|
||||
<span class="node-icon">
|
||||
<IconFolder v-if="node.type === 'project'" />
|
||||
<IconSettings v-else-if="node.type === 'turbine'" />
|
||||
<IconTool v-else-if="node.type === 'part'" />
|
||||
<IconApps v-else />
|
||||
<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 />
|
||||
</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'
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<h3>识别结果</h3>
|
||||
<div class="header-actions">
|
||||
<a-button type="text" @click="handleSaveResults">
|
||||
<template #icon><IconSave /></template>
|
||||
<template #icon><icon-save /></template>
|
||||
保存结果
|
||||
</a-button>
|
||||
<a-button type="text" @click="handleExportResults">
|
||||
<template #icon><IconExport /></template>
|
||||
<template #icon><icon-export /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -51,20 +51,20 @@
|
|||
@click="handleResultSelect(index)"
|
||||
>
|
||||
<div class="result-info">
|
||||
<span class="result-type" :class="getDefectTypeClass(result.defectType)">
|
||||
{{ getDefectTypeName(result.defectType) }}
|
||||
</span>
|
||||
<span class="result-confidence">{{ ((result.markInfo?.confidence || 0) * 100).toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="result-position">
|
||||
位置: {{ formatPosition(result.markInfo?.bbox) }}
|
||||
</div>
|
||||
<div class="result-size">
|
||||
尺寸: {{ formatSize(result.markInfo?.bbox) }}
|
||||
</div>
|
||||
<div class="result-recommendation">
|
||||
建议: {{ getRecommendation(result.defectType, result.markInfo?.confidence || 0) }}
|
||||
<span class="result-type" :class="getDefectTypeClass(result.defectType)">
|
||||
{{ getDefectTypeName(result.defectType) }}
|
||||
</span>
|
||||
<span class="result-confidence">{{ ((result.markInfo?.confidence || 0) * 100).toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="result-position">
|
||||
位置: {{ formatPosition(result.markInfo?.bbox) }}
|
||||
</div>
|
||||
<div class="result-size">
|
||||
尺寸: {{ formatSize(result.markInfo?.bbox) }}
|
||||
</div>
|
||||
<div class="result-recommendation">
|
||||
建议: {{ getRecommendation(result.defectType, result.markInfo?.confidence || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,10 +73,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ref, computed } 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
|
||||
|
@ -113,7 +113,7 @@ const statistics = computed(() => {
|
|||
// 映射自定义缺陷类型到标准类型
|
||||
const typeMapping: Record<string, string> = {
|
||||
bmlw: 'crack', // 表面裂纹 -> 裂纹
|
||||
hfms: 'wear', // 合缝磨损 -> 磨损
|
||||
hfms: 'wear', // 合缝磨损 -> 磨损
|
||||
mpps: 'deformation', // 蒙皮破损 -> 变形
|
||||
bcps: 'deformation', // 布层破损 -> 变形
|
||||
jbps: 'deformation', // 局部破损 -> 变形
|
||||
|
@ -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] || '建议进一步检查'
|
||||
}
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
import { computed, ref } from 'vue'
|
||||
import { ref, reactive, computed } 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 type { type DefectDetectionRequest, type DefectDetectionResult, DefectInfo, type ManualDefectAddRequest, addDefect, addManualDefect, deleteDefect, detectDefects, getDefectList, updateDefect } from '@/apis/industrial-image/defect'
|
||||
import {
|
||||
detectDefects,
|
||||
getDefectList,
|
||||
addDefect,
|
||||
updateDefect,
|
||||
deleteDefect,
|
||||
addManualDefect,
|
||||
type DefectDetectionRequest,
|
||||
type DefectDetectionResult,
|
||||
type ManualDefectAddRequest
|
||||
} from '@/apis/industrial-image/defect'
|
||||
import type { TreeNodeData, ImageInfo } from '@/apis/industrial-image/type'
|
||||
import type { DefectInfo } from '@/apis/industrial-image/defect'
|
||||
import type { Annotation } from '@/views/project-operation-platform/data-processing/industrial-image/components/ImageCanvas.vue'
|
||||
|
||||
export function useIndustrialImage() {
|
||||
// 项目树数据
|
||||
|
@ -56,14 +73,13 @@ export function useIndustrialImage() {
|
|||
return [{
|
||||
id: selectedDefect.value.id,
|
||||
type: 'rectangle' as const,
|
||||
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 },
|
||||
]
|
||||
: [],
|
||||
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 }
|
||||
] : [],
|
||||
color: selectedDefect.value.severity === 'high' ? '#ff4d4f' : '#faad14',
|
||||
label: selectedDefect.value.defectType,
|
||||
label: selectedDefect.value.defectType
|
||||
}]
|
||||
})
|
||||
|
||||
|
@ -97,7 +113,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,
|
||||
|
@ -107,7 +123,7 @@ export function useIndustrialImage() {
|
|||
expanded: false,
|
||||
status: project.status,
|
||||
createTime: project.createTime,
|
||||
rawData: project,
|
||||
rawData: project
|
||||
}))
|
||||
|
||||
// 默认选中第一个项目
|
||||
|
@ -137,7 +153,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,
|
||||
|
@ -148,16 +164,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,
|
||||
|
@ -168,7 +184,7 @@ export function useIndustrialImage() {
|
|||
expanded: false,
|
||||
status: part.partType,
|
||||
createTime: part.createTime,
|
||||
rawData: part,
|
||||
rawData: part
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -206,7 +222,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 || []
|
||||
|
@ -272,6 +288,8 @@ export function useIndustrialImage() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 查找选中的节点
|
||||
const findSelectedNode = (nodes: ProjectTreeNode[], nodeId: string): ProjectTreeNode | null => {
|
||||
if (!nodes || !Array.isArray(nodes) || !nodeId) return null
|
||||
|
@ -298,7 +316,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)
|
||||
|
@ -331,7 +349,7 @@ export function useIndustrialImage() {
|
|||
}
|
||||
|
||||
const handleImagePreview = (image: any) => {
|
||||
console.log('image:', image)
|
||||
console.log("image:",image);
|
||||
previewImage.value = image
|
||||
previewModalVisible.value = true
|
||||
}
|
||||
|
@ -354,7 +372,7 @@ export function useIndustrialImage() {
|
|||
console.error('删除图像失败:', error)
|
||||
Message.error('删除图像失败')
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -433,7 +451,7 @@ export function useIndustrialImage() {
|
|||
reportUnitData.value = {
|
||||
id: selectedNode.id,
|
||||
name: selectedNode.name,
|
||||
type: selectedNode.type,
|
||||
type: selectedNode.type
|
||||
}
|
||||
|
||||
// 显示报告生成对话框
|
||||
|
@ -471,7 +489,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接口上传图像
|
||||
|
@ -479,7 +497,7 @@ export function useIndustrialImage() {
|
|||
'default', // 图像源
|
||||
data.part.partId, // 部件ID
|
||||
data.images, // 文件列表
|
||||
uploadParams,
|
||||
uploadParams
|
||||
)
|
||||
|
||||
console.log('批量上传响应:', response)
|
||||
|
@ -542,13 +560,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
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -611,7 +629,7 @@ export function useIndustrialImage() {
|
|||
repairIdea: defectForm.repairIdea,
|
||||
imageId: selectedImage.value?.imageId || '',
|
||||
detectionDate: new Date().toISOString(),
|
||||
source: 'manual',
|
||||
source: 'manual'
|
||||
}
|
||||
|
||||
// 根据节点类型设置相关ID
|
||||
|
@ -705,21 +723,19 @@ export function useIndustrialImage() {
|
|||
detectionDate: new Date().toISOString(),
|
||||
labelInfo: JSON.stringify(annotation),
|
||||
markInfo: {
|
||||
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),
|
||||
]
|
||||
: [],
|
||||
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)
|
||||
] : [],
|
||||
clsId: 1,
|
||||
confidence: 1.0,
|
||||
label: annotation.defectType || '手动标注',
|
||||
label: annotation.defectType || '手动标注'
|
||||
},
|
||||
repairIdea: '',
|
||||
repairStatus: 'PENDING',
|
||||
source: 'MANUAL',
|
||||
source: 'MANUAL'
|
||||
}
|
||||
|
||||
console.log('发送给后端的缺陷数据:', defectData)
|
||||
|
@ -808,7 +824,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)
|
||||
|
@ -835,7 +851,7 @@ export function useIndustrialImage() {
|
|||
// 更新当前选中图像的路径为识别后返回的attachPath
|
||||
selectedImage.value = {
|
||||
...selectedImage.value,
|
||||
imagePath: firstResult.attachPath,
|
||||
imagePath: firstResult.attachPath
|
||||
}
|
||||
console.log('图片路径已更新为识别后的路径:', firstResult.attachPath)
|
||||
}
|
||||
|
@ -847,6 +863,7 @@ export function useIndustrialImage() {
|
|||
}
|
||||
|
||||
// 不再需要类型映射,因为RecognitionResults组件已经处理了自定义类型
|
||||
|
||||
} catch (error) {
|
||||
console.error('识别失败:', error)
|
||||
Message.error('识别失败')
|
||||
|
@ -907,15 +924,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++
|
||||
|
@ -950,6 +967,7 @@ export function useIndustrialImage() {
|
|||
if (successCount > 0) {
|
||||
loadDefectList()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('保存识别结果失败:', error)
|
||||
Message.error('保存识别结果失败')
|
||||
|
@ -1052,6 +1070,6 @@ export function useIndustrialImage() {
|
|||
// 报告生成相关方法
|
||||
handleReportGenerated,
|
||||
|
||||
init,
|
||||
init
|
||||
}
|
||||
}
|
|
@ -12,13 +12,11 @@
|
|||
<!-- 主体内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 上部区域 -->
|
||||
<div
|
||||
class="top-section" :class="{
|
||||
'auto-recognition-layout': isAutoRecognitionMode,
|
||||
'manual-annotation-layout': isManualAnnotationMode,
|
||||
'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode,
|
||||
}"
|
||||
>
|
||||
<div class="top-section" :class="{
|
||||
'auto-recognition-layout': isAutoRecognitionMode,
|
||||
'manual-annotation-layout': isManualAnnotationMode,
|
||||
'top-section-expanded': isImageListCollapsed || isAutoRecognitionMode || isManualAnnotationMode
|
||||
}">
|
||||
<!-- 自动识别模式三列布局 -->
|
||||
<template v-if="isAutoRecognitionMode">
|
||||
<!-- 左侧:自动识别设置面板 -->
|
||||
|
@ -87,7 +85,7 @@
|
|||
@submit="handleDefectFormSubmit"
|
||||
/>
|
||||
<div v-else class="no-annotation-prompt">
|
||||
<IconBug class="prompt-icon" />
|
||||
<icon-bug class="prompt-icon" />
|
||||
<p>请在图像上绘制矩形标注缺陷</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -131,7 +129,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"
|
||||
|
@ -148,23 +146,25 @@
|
|||
|
||||
<!-- 模态框 -->
|
||||
<ImageModals
|
||||
:preview-modal-visible="previewModalVisible"
|
||||
:preview-image="previewImage"
|
||||
:process-image="processImage"
|
||||
:selected-image="selectedImage"
|
||||
@update:preview-modal-visible="previewModalVisible = $event"
|
||||
@save="handleSaveImageSuccess"
|
||||
:preview-modal-visible="previewModalVisible"
|
||||
:preview-image="previewImage"
|
||||
:process-image="processImage"
|
||||
:selected-image="selectedImage"
|
||||
@update:preview-modal-visible="previewModalVisible = $event"
|
||||
@save="handleSaveImageSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { IconBug } from '@arco-design/web-vue/es/icon'
|
||||
import { 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,10 +172,9 @@ 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 { DefectInfo, type DefectLevelType, type DefectType, ManualDefectAddRequest, addManualDefect, getDefectLevels, getDefectList, getDefectTypes, uploadAnnotatedImage } from '@/apis/industrial-image/defect'
|
||||
import type { ManualDefectAddRequest, DefectInfo, AttachInfoData } from '@/apis/industrial-image/defect'
|
||||
import { addManualDefect, getDefectList, uploadAnnotatedImage, getDefectTypes, getDefectLevels, type DefectType, type DefectLevelType } from '@/apis/industrial-image/defect'
|
||||
|
||||
defineOptions({ name: 'IndustrialImageProcessing' })
|
||||
|
||||
|
@ -315,7 +314,7 @@ const {
|
|||
// 报告生成相关方法
|
||||
handleReportGenerated,
|
||||
|
||||
init,
|
||||
init
|
||||
} = useIndustrialImage()
|
||||
|
||||
// 转换项目树数据为树形结构
|
||||
|
@ -342,16 +341,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
|
||||
|
@ -362,7 +361,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
|
||||
|
@ -373,7 +372,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
|
||||
|
@ -383,18 +382,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
|
||||
|
@ -418,7 +417,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
|
||||
}
|
||||
|
@ -438,13 +437,14 @@ 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,
|
||||
...item,
|
||||
turbineId: 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,
|
||||
...item,
|
||||
turbineId: 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,38 +627,36 @@ 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) => [
|
||||
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),
|
||||
])
|
||||
(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)
|
||||
]) :
|
||||
// 单区域直接计算
|
||||
: 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),
|
||||
]
|
||||
: [],
|
||||
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)
|
||||
] : [],
|
||||
clsId: 1,
|
||||
confidence: 1.0,
|
||||
label: formData.defectType,
|
||||
label: formData.defectType
|
||||
},
|
||||
repairIdea: formData.repairIdea,
|
||||
repairStatus: 'PENDING',
|
||||
repairStatusLabel: '待处理',
|
||||
source: 'MANUAL',
|
||||
sourceLabel: '手动标注',
|
||||
sourceLabel: '手动标注'
|
||||
}
|
||||
|
||||
console.log('发送给后端的缺陷数据:', defectData)
|
||||
|
@ -678,11 +676,12 @@ 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()
|
||||
|
@ -694,9 +693,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; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
|
||||
|
||||
annotations.forEach((annotation) => {
|
||||
annotations.forEach(annotation => {
|
||||
if (annotation.points && annotation.points.length >= 2) {
|
||||
const [p1, p2] = annotation.points
|
||||
minX = Math.min(minX, p1.x, p2.x)
|
||||
|
@ -711,13 +710,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
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
<div class="operation-bar">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon><IconPlus /></template>
|
||||
<template #icon><icon-plus /></template>
|
||||
新建配置
|
||||
</a-button>
|
||||
<a-button @click="refreshList">
|
||||
<template #icon><IconRefresh /></template>
|
||||
<template #icon><icon-refresh /></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><IconEdit /></template>
|
||||
<template #icon><icon-edit /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
<template #icon><IconEye /></template>
|
||||
<template #icon><icon-eye /></template>
|
||||
查看
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
|
@ -65,7 +65,7 @@
|
|||
@ok="handleDelete(record)"
|
||||
>
|
||||
<a-button type="text" status="danger" size="small">
|
||||
<template #icon><IconDelete /></template>
|
||||
<template #icon><icon-delete /></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><IconUpload /></template>
|
||||
<template #icon><icon-upload /></template>
|
||||
上传模型文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
|
@ -134,15 +134,15 @@
|
|||
status="danger"
|
||||
@click="formData.attachId = ''"
|
||||
>
|
||||
<template #icon><IconDelete /></template>
|
||||
<template #icon><icon-delete /></template>
|
||||
清除
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div v-if="uploadingFile" class="upload-tip">
|
||||
<div class="upload-tip" v-if="uploadingFile">
|
||||
<a-spin /> 上传中...{{ uploadProgress }}%
|
||||
</div>
|
||||
<div v-if="uploadedFileName && !uploadingFile" class="upload-tip success">
|
||||
<IconCheckCircle /> 已上传: {{ uploadedFileName }}
|
||||
<div class="upload-tip success" v-if="uploadedFileName && !uploadingFile">
|
||||
<icon-check-circle /> 已上传: {{ 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 { onMounted, reactive, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import {
|
||||
IconCheckCircle,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
IconEye,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconEdit,
|
||||
IconEye,
|
||||
IconDelete,
|
||||
IconUpload,
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
IconCheckCircle
|
||||
} from '@arco-design/web-vue/es/icon';
|
||||
import {
|
||||
createModelConfig,
|
||||
deleteModelConfig,
|
||||
getModelConfigDetail,
|
||||
getModelConfigList,
|
||||
getModelConfigDetail,
|
||||
createModelConfig,
|
||||
updateModelConfig,
|
||||
} from '@/apis/model-config'
|
||||
import { addAttachment } from '@/apis/attach-info'
|
||||
import type { ModelConfigRequest, ModelConfigResponse } from '@/apis/model-config/type'
|
||||
deleteModelConfig
|
||||
} 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">
|
||||
|
|
|
@ -1,473 +1,476 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="应用使用数据" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col v-for="(stat, index) in appStatistics" :key="index" :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
:title="stat.title"
|
||||
:value="stat.value"
|
||||
:precision="stat.precision || 0"
|
||||
:suffix="stat.suffix || ''"
|
||||
>
|
||||
<template #prefix>
|
||||
<component :is="stat.icon" />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<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-card class="stat-card">
|
||||
<a-statistic
|
||||
:title="stat.title"
|
||||
:value="stat.value"
|
||||
:precision="stat.precision || 0"
|
||||
:suffix="stat.suffix || ''"
|
||||
>
|
||||
<template #prefix>
|
||||
<component :is="stat.icon" />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="应用访问量趋势" :bordered="false">
|
||||
<div ref="appVisitChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="应用使用时长分布" :bordered="false">
|
||||
<div ref="appTimeChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="应用访问量趋势" :bordered="false">
|
||||
<div ref="appVisitChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="应用使用时长分布" :bordered="false">
|
||||
<div ref="appTimeChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-card title="应用使用详情" :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="Web端">
|
||||
<a-table :columns="appColumns" :data-source="webAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
<a-card title="应用使用详情" :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="Web端">
|
||||
<a-table :columns="appColumns" :data-source="webAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
|
||||
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
|
||||
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<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>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="移动端">
|
||||
<a-table :columns="appColumns" :data-source="mobileAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
|
||||
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
|
||||
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="移动端">
|
||||
<a-table :columns="appColumns" :data-source="mobileAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="微信小程序">
|
||||
<a-table :columns="appColumns" :data-source="miniAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<icon-up v-if="record.trend > 0" style="color: #52c41a" />
|
||||
<icon-down v-if="record.trend < 0" style="color: #ff4d4f" />
|
||||
<icon-minus v-if="record.trend === 0" style="color: #1890ff" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<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>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="微信小程序">
|
||||
<a-table :columns="appColumns" :data-source="miniAppData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<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>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="终端设备分布" :bordered="false">
|
||||
<div ref="deviceDistributionChart" style="height: 400px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-divider />
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, reactive, nextTick } from 'vue'
|
||||
import { Statistic } from '@arco-design/web-vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
IconApps,
|
||||
IconMobile,
|
||||
IconUser,
|
||||
IconClockCircle,
|
||||
IconUp,
|
||||
IconDown,
|
||||
IconMinus
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="终端设备分布" :bordered="false">
|
||||
<div ref="deviceDistributionChart" style="height: 400px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
IconApps,
|
||||
IconClockCircle,
|
||||
IconDown,
|
||||
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([
|
||||
{
|
||||
title: '应用总数',
|
||||
value: 12,
|
||||
icon: IconApps,
|
||||
},
|
||||
{
|
||||
title: '活跃应用',
|
||||
value: 8,
|
||||
icon: IconApps,
|
||||
},
|
||||
{
|
||||
title: '日均访问量',
|
||||
value: 1258,
|
||||
icon: IconUser,
|
||||
},
|
||||
{
|
||||
title: '日均使用时长',
|
||||
value: 3.5,
|
||||
precision: 1,
|
||||
suffix: '小时',
|
||||
icon: IconClockCircle,
|
||||
},
|
||||
])
|
||||
|
||||
// 应用使用详情表格列定义
|
||||
const appColumns = [
|
||||
{
|
||||
title: '应用名称',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
},
|
||||
{
|
||||
title: '访问量',
|
||||
dataIndex: 'visitCount',
|
||||
key: 'visitCount',
|
||||
sorter: (a, b) => a.visitCount - b.visitCount,
|
||||
},
|
||||
{
|
||||
title: '用户数',
|
||||
dataIndex: 'userCount',
|
||||
key: 'userCount',
|
||||
sorter: (a, b) => a.userCount - b.userCount,
|
||||
},
|
||||
{
|
||||
title: '使用率',
|
||||
dataIndex: 'usageRate',
|
||||
key: 'usageRate',
|
||||
sorter: (a, b) => a.usageRate - b.usageRate,
|
||||
},
|
||||
{
|
||||
title: '平均使用时长',
|
||||
dataIndex: 'averageTime',
|
||||
key: 'averageTime',
|
||||
},
|
||||
{
|
||||
title: '环比上月',
|
||||
dataIndex: 'trend',
|
||||
key: 'trend',
|
||||
sorter: (a, b) => a.trend - b.trend,
|
||||
},
|
||||
]
|
||||
|
||||
// Web端应用数据
|
||||
const webAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '企业管理后台',
|
||||
visitCount: 3560,
|
||||
userCount: 320,
|
||||
usageRate: 95,
|
||||
averageTime: '2.5小时',
|
||||
trend: 8.5,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '项目管理系统',
|
||||
visitCount: 2980,
|
||||
userCount: 285,
|
||||
usageRate: 90,
|
||||
averageTime: '3小时',
|
||||
trend: 5.2,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '数据分析平台',
|
||||
visitCount: 2450,
|
||||
userCount: 210,
|
||||
usageRate: 85,
|
||||
averageTime: '2小时',
|
||||
trend: 3.8,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
appName: '客户管理系统',
|
||||
visitCount: 1980,
|
||||
userCount: 180,
|
||||
usageRate: 75,
|
||||
averageTime: '1.5小时',
|
||||
trend: -2.1,
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
appName: '知识库系统',
|
||||
visitCount: 1560,
|
||||
userCount: 150,
|
||||
usageRate: 65,
|
||||
averageTime: '1小时',
|
||||
trend: 0,
|
||||
},
|
||||
]
|
||||
|
||||
// 移动端应用数据
|
||||
const mobileAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '移动办公APP',
|
||||
visitCount: 4250,
|
||||
userCount: 350,
|
||||
usageRate: 98,
|
||||
averageTime: '3.5小时',
|
||||
trend: 12.5,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '外勤管理APP',
|
||||
visitCount: 3680,
|
||||
userCount: 320,
|
||||
usageRate: 92,
|
||||
averageTime: '4小时',
|
||||
trend: 9.8,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '项目跟踪APP',
|
||||
visitCount: 2850,
|
||||
userCount: 260,
|
||||
usageRate: 88,
|
||||
averageTime: '3小时',
|
||||
trend: 7.5,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
appName: '客户拜访APP',
|
||||
visitCount: 2120,
|
||||
userCount: 180,
|
||||
usageRate: 72,
|
||||
averageTime: '2小时',
|
||||
trend: -1.5,
|
||||
},
|
||||
]
|
||||
|
||||
// 微信小程序数据
|
||||
const miniAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '企业服务小程序',
|
||||
visitCount: 5680,
|
||||
userCount: 420,
|
||||
usageRate: 96,
|
||||
averageTime: '1.5小时',
|
||||
trend: 15.8,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '客户自助小程序',
|
||||
visitCount: 4850,
|
||||
userCount: 380,
|
||||
usageRate: 90,
|
||||
averageTime: '1小时',
|
||||
trend: 10.5,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '员工工具小程序',
|
||||
visitCount: 3920,
|
||||
userCount: 345,
|
||||
usageRate: 85,
|
||||
averageTime: '0.8小时',
|
||||
trend: 8.2,
|
||||
},
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getUsageRateColor = (rate) => {
|
||||
if (rate >= 90) return '#52c41a'
|
||||
if (rate >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getTrendColor = (trend) => {
|
||||
if (trend > 0) return '#52c41a'
|
||||
if (trend < 0) return '#ff4d4f'
|
||||
return '#1890ff'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化应用访问量趋势图表
|
||||
if (appVisitChart.value) {
|
||||
const visitChart = echarts.init(appVisitChart.value)
|
||||
visitChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
data: ['Web端', '移动端', '小程序'],
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Web端',
|
||||
type: 'line',
|
||||
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200],
|
||||
},
|
||||
{
|
||||
name: '移动端',
|
||||
type: 'line',
|
||||
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500],
|
||||
},
|
||||
{
|
||||
name: '小程序',
|
||||
type: 'line',
|
||||
data: [4500, 4800, 5200, 4900, 4700, 3900, 3500],
|
||||
},
|
||||
],
|
||||
})
|
||||
// 应用统计数据
|
||||
const appStatistics = reactive([
|
||||
{
|
||||
title: '应用总数',
|
||||
value: 12,
|
||||
icon: IconApps
|
||||
},
|
||||
{
|
||||
title: '活跃应用',
|
||||
value: 8,
|
||||
icon: IconApps
|
||||
},
|
||||
{
|
||||
title: '日均访问量',
|
||||
value: 1258,
|
||||
icon: IconUser
|
||||
},
|
||||
{
|
||||
title: '日均使用时长',
|
||||
value: 3.5,
|
||||
precision: 1,
|
||||
suffix: '小时',
|
||||
icon: IconClockCircle
|
||||
}
|
||||
])
|
||||
|
||||
// 应用使用详情表格列定义
|
||||
const appColumns = [
|
||||
{
|
||||
title: '应用名称',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
},
|
||||
{
|
||||
title: '访问量',
|
||||
dataIndex: 'visitCount',
|
||||
key: 'visitCount',
|
||||
sorter: (a, b) => a.visitCount - b.visitCount,
|
||||
},
|
||||
{
|
||||
title: '用户数',
|
||||
dataIndex: 'userCount',
|
||||
key: 'userCount',
|
||||
sorter: (a, b) => a.userCount - b.userCount,
|
||||
},
|
||||
{
|
||||
title: '使用率',
|
||||
dataIndex: 'usageRate',
|
||||
key: 'usageRate',
|
||||
sorter: (a, b) => a.usageRate - b.usageRate,
|
||||
},
|
||||
{
|
||||
title: '平均使用时长',
|
||||
dataIndex: 'averageTime',
|
||||
key: 'averageTime',
|
||||
},
|
||||
{
|
||||
title: '环比上月',
|
||||
dataIndex: 'trend',
|
||||
key: 'trend',
|
||||
sorter: (a, b) => a.trend - b.trend,
|
||||
}
|
||||
]
|
||||
|
||||
// Web端应用数据
|
||||
const webAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '企业管理后台',
|
||||
visitCount: 3560,
|
||||
userCount: 320,
|
||||
usageRate: 95,
|
||||
averageTime: '2.5小时',
|
||||
trend: 8.5
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '项目管理系统',
|
||||
visitCount: 2980,
|
||||
userCount: 285,
|
||||
usageRate: 90,
|
||||
averageTime: '3小时',
|
||||
trend: 5.2
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '数据分析平台',
|
||||
visitCount: 2450,
|
||||
userCount: 210,
|
||||
usageRate: 85,
|
||||
averageTime: '2小时',
|
||||
trend: 3.8
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
appName: '客户管理系统',
|
||||
visitCount: 1980,
|
||||
userCount: 180,
|
||||
usageRate: 75,
|
||||
averageTime: '1.5小时',
|
||||
trend: -2.1
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
appName: '知识库系统',
|
||||
visitCount: 1560,
|
||||
userCount: 150,
|
||||
usageRate: 65,
|
||||
averageTime: '1小时',
|
||||
trend: 0
|
||||
}
|
||||
]
|
||||
|
||||
// 移动端应用数据
|
||||
const mobileAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '移动办公APP',
|
||||
visitCount: 4250,
|
||||
userCount: 350,
|
||||
usageRate: 98,
|
||||
averageTime: '3.5小时',
|
||||
trend: 12.5
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '外勤管理APP',
|
||||
visitCount: 3680,
|
||||
userCount: 320,
|
||||
usageRate: 92,
|
||||
averageTime: '4小时',
|
||||
trend: 9.8
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '项目跟踪APP',
|
||||
visitCount: 2850,
|
||||
userCount: 260,
|
||||
usageRate: 88,
|
||||
averageTime: '3小时',
|
||||
trend: 7.5
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
appName: '客户拜访APP',
|
||||
visitCount: 2120,
|
||||
userCount: 180,
|
||||
usageRate: 72,
|
||||
averageTime: '2小时',
|
||||
trend: -1.5
|
||||
}
|
||||
]
|
||||
|
||||
// 微信小程序数据
|
||||
const miniAppData = [
|
||||
{
|
||||
key: '1',
|
||||
appName: '企业服务小程序',
|
||||
visitCount: 5680,
|
||||
userCount: 420,
|
||||
usageRate: 96,
|
||||
averageTime: '1.5小时',
|
||||
trend: 15.8
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
appName: '客户自助小程序',
|
||||
visitCount: 4850,
|
||||
userCount: 380,
|
||||
usageRate: 90,
|
||||
averageTime: '1小时',
|
||||
trend: 10.5
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
appName: '员工工具小程序',
|
||||
visitCount: 3920,
|
||||
userCount: 345,
|
||||
usageRate: 85,
|
||||
averageTime: '0.8小时',
|
||||
trend: 8.2
|
||||
}
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getUsageRateColor = (rate) => {
|
||||
if (rate >= 90) return '#52c41a'
|
||||
if (rate >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getTrendColor = (trend) => {
|
||||
if (trend > 0) return '#52c41a'
|
||||
if (trend < 0) return '#ff4d4f'
|
||||
return '#1890ff'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化应用访问量趋势图表
|
||||
if (appVisitChart.value) {
|
||||
const visitChart = echarts.init(appVisitChart.value)
|
||||
visitChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['Web端', '移动端', '小程序']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Web端',
|
||||
type: 'line',
|
||||
data: [2500, 2800, 3200, 3100, 2950, 1800, 1200]
|
||||
},
|
||||
{
|
||||
name: '移动端',
|
||||
type: 'line',
|
||||
data: [3200, 3500, 3800, 3600, 3400, 2800, 2500]
|
||||
},
|
||||
{
|
||||
name: '小程序',
|
||||
type: 'line',
|
||||
data: [4500, 4800, 5200, 4900, 4700, 3900, 3500]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化应用使用时长分布图表
|
||||
if (appTimeChart.value) {
|
||||
const timeChart = echarts.init(appTimeChart.value)
|
||||
timeChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用时长分布',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 35, name: 'Web端' },
|
||||
{ value: 45, name: '移动端' },
|
||||
{ value: 20, name: '小程序' },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (appTimeChart.value) {
|
||||
const timeChart = echarts.init(appTimeChart.value)
|
||||
timeChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用时长分布',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 35, name: 'Web端' },
|
||||
{ value: 45, name: '移动端' },
|
||||
{ value: 20, name: '小程序' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化终端设备分布图表
|
||||
if (deviceDistributionChart.value) {
|
||||
const deviceChart = echarts.init(deviceDistributionChart.value)
|
||||
deviceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
if (deviceDistributionChart.value) {
|
||||
const deviceChart = echarts.init(deviceDistributionChart.value)
|
||||
deviceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['Windows PC', 'Mac', 'iOS', 'Android', '微信'],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
data: [5200, 3800, 6500, 8200, 9500],
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
{
|
||||
name: '用户数',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
data: [280, 220, 320, 380, 420],
|
||||
data: [5200, 3800, 6500, 8200, 9500]
|
||||
},
|
||||
{
|
||||
name: '用户数',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [280, 220, 320, 380, 420]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', () => {
|
||||
if (appVisitChart.value) {
|
||||
const visitChart = echarts.getInstanceByDom(appVisitChart.value)
|
||||
visitChart?.resize()
|
||||
}
|
||||
if (appTimeChart.value) {
|
||||
const timeChart = echarts.getInstanceByDom(appTimeChart.value)
|
||||
timeChart?.resize()
|
||||
}
|
||||
if (deviceDistributionChart.value) {
|
||||
const deviceChart = echarts.getInstanceByDom(deviceDistributionChart.value)
|
||||
deviceChart?.resize()
|
||||
}
|
||||
if (appVisitChart.value) {
|
||||
const visitChart = echarts.getInstanceByDom(appVisitChart.value)
|
||||
visitChart?.resize()
|
||||
}
|
||||
if (appTimeChart.value) {
|
||||
const timeChart = echarts.getInstanceByDom(appTimeChart.value)
|
||||
timeChart?.resize()
|
||||
}
|
||||
if (deviceDistributionChart.value) {
|
||||
const deviceChart = echarts.getInstanceByDom(deviceDistributionChart.value)
|
||||
deviceChart?.resize()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.general-card {
|
||||
|
|
|
@ -1,372 +1,372 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="功能使用情况" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="功能模块使用频率" :bordered="false">
|
||||
<div ref="moduleUsageChart" style="height: 400px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="功能使用情况" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="功能模块使用频率" :bordered="false">
|
||||
<div ref="moduleUsageChart" style="height: 400px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="各部门功能使用分布" :bordered="false">
|
||||
<div ref="departmentUsageChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="功能使用时长占比" :bordered="false">
|
||||
<div ref="usageTimeChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="各部门功能使用分布" :bordered="false">
|
||||
<div ref="departmentUsageChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="功能使用时长占比" :bordered="false">
|
||||
<div ref="usageTimeChart" style="height: 350px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-card title="功能使用详情" :bordered="false">
|
||||
<a-table :columns="functionColumns" :data-source="functionData" :pagination="{ pageSize: 10 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
<a-card title="功能使用详情" :bordered="false">
|
||||
<a-table :columns="functionColumns" :data-source="functionData" :pagination="{ pageSize: 10 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'usageRate'">
|
||||
<a-progress :percent="record.usageRate" :stroke-color="getUsageRateColor(record.usageRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<arrow-up-outlined v-if="record.trend > 0" style="color: #52c41a" />
|
||||
<arrow-down-outlined v-if="record.trend < 0" style="color: #ff4d4f" />
|
||||
<minus-outlined v-if="record.trend === 0" style="color: #1890ff" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'trend'">
|
||||
<span :style="{ color: getTrendColor(record.trend) }">
|
||||
{{ record.trend >= 0 ? '+' : '' }}{{ record.trend }}%
|
||||
<arrow-up-outlined v-if="record.trend > 0" style="color: #52c41a" />
|
||||
<arrow-down-outlined v-if="record.trend < 0" style="color: #ff4d4f" />
|
||||
<minus-outlined v-if="record.trend === 0" style="color: #1890ff" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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'
|
||||
<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'
|
||||
|
||||
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 = [
|
||||
{
|
||||
title: '功能模块',
|
||||
dataIndex: 'module',
|
||||
key: 'module',
|
||||
},
|
||||
{
|
||||
title: '子功能',
|
||||
dataIndex: 'function',
|
||||
key: 'function',
|
||||
},
|
||||
{
|
||||
title: '使用次数',
|
||||
dataIndex: 'usageCount',
|
||||
key: 'usageCount',
|
||||
sorter: (a, b) => a.usageCount - b.usageCount,
|
||||
},
|
||||
{
|
||||
title: '使用率',
|
||||
dataIndex: 'usageRate',
|
||||
key: 'usageRate',
|
||||
sorter: (a, b) => a.usageRate - b.usageRate,
|
||||
},
|
||||
{
|
||||
title: '平均使用时长',
|
||||
dataIndex: 'averageTime',
|
||||
key: 'averageTime',
|
||||
sorter: (a, b) => a.averageTime - b.averageTime,
|
||||
},
|
||||
{
|
||||
title: '环比上月',
|
||||
dataIndex: 'trend',
|
||||
key: 'trend',
|
||||
sorter: (a, b) => a.trend - b.trend,
|
||||
},
|
||||
]
|
||||
|
||||
const functionData = [
|
||||
{
|
||||
key: '1',
|
||||
module: '组织架构',
|
||||
function: '人员管理',
|
||||
usageCount: 1245,
|
||||
usageRate: 92,
|
||||
averageTime: '15分钟',
|
||||
trend: 5.2,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
module: '组织架构',
|
||||
function: '角色管理',
|
||||
usageCount: 865,
|
||||
usageRate: 78,
|
||||
averageTime: '12分钟',
|
||||
trend: 3.8,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
module: '资产管理',
|
||||
function: '设备管理',
|
||||
usageCount: 1056,
|
||||
usageRate: 85,
|
||||
averageTime: '18分钟',
|
||||
trend: 7.5,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
module: '资产管理',
|
||||
function: '库存管理',
|
||||
usageCount: 932,
|
||||
usageRate: 80,
|
||||
averageTime: '14分钟',
|
||||
trend: -2.1,
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
module: '产品与服务',
|
||||
function: '产品管理',
|
||||
usageCount: 1120,
|
||||
usageRate: 88,
|
||||
averageTime: '20分钟',
|
||||
trend: 4.3,
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
module: '产品与服务',
|
||||
function: '服务管理',
|
||||
usageCount: 986,
|
||||
usageRate: 82,
|
||||
averageTime: '16分钟',
|
||||
trend: 0,
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
module: '项目管理',
|
||||
function: '项目模板',
|
||||
usageCount: 1320,
|
||||
usageRate: 95,
|
||||
averageTime: '25分钟',
|
||||
trend: 8.7,
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
module: '项目管理',
|
||||
function: '合同管理',
|
||||
usageCount: 1150,
|
||||
usageRate: 90,
|
||||
averageTime: '22分钟',
|
||||
trend: 6.2,
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
module: '施工操作台',
|
||||
function: '外业施工',
|
||||
usageCount: 1280,
|
||||
usageRate: 93,
|
||||
averageTime: '30分钟',
|
||||
trend: 9.5,
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
module: '施工操作台',
|
||||
function: '数据处理',
|
||||
usageCount: 1180,
|
||||
usageRate: 91,
|
||||
averageTime: '28分钟',
|
||||
trend: 5.8,
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
module: '聊天平台',
|
||||
function: '消息管理',
|
||||
usageCount: 1420,
|
||||
usageRate: 98,
|
||||
averageTime: '35分钟',
|
||||
trend: 12.3,
|
||||
},
|
||||
{
|
||||
key: '12',
|
||||
module: '企业设置',
|
||||
function: '企业信息',
|
||||
usageCount: 720,
|
||||
usageRate: 65,
|
||||
averageTime: '10分钟',
|
||||
trend: -1.5,
|
||||
},
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getUsageRateColor = (rate) => {
|
||||
if (rate >= 90) return '#52c41a'
|
||||
if (rate >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getTrendColor = (trend) => {
|
||||
if (trend > 0) return '#52c41a'
|
||||
if (trend < 0) return '#ff4d4f'
|
||||
return '#1890ff'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化功能模块使用频率图表
|
||||
if (moduleUsageChart.value) {
|
||||
const moduleChart = echarts.init(moduleUsageChart.value)
|
||||
moduleChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['使用次数', '使用人数'],
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['组织架构', '资产管理', '产品与服务', '项目管理', '施工操作台', '聊天平台', '企业设置', '系统资源管理'],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用次数',
|
||||
type: 'bar',
|
||||
data: [2110, 1988, 2106, 2470, 2460, 1420, 720, 650],
|
||||
},
|
||||
{
|
||||
name: '使用人数',
|
||||
type: 'bar',
|
||||
data: [320, 302, 315, 335, 340, 356, 120, 85],
|
||||
},
|
||||
],
|
||||
})
|
||||
// 功能使用详情表格数据
|
||||
const functionColumns = [
|
||||
{
|
||||
title: '功能模块',
|
||||
dataIndex: 'module',
|
||||
key: 'module',
|
||||
},
|
||||
{
|
||||
title: '子功能',
|
||||
dataIndex: 'function',
|
||||
key: 'function',
|
||||
},
|
||||
{
|
||||
title: '使用次数',
|
||||
dataIndex: 'usageCount',
|
||||
key: 'usageCount',
|
||||
sorter: (a, b) => a.usageCount - b.usageCount,
|
||||
},
|
||||
{
|
||||
title: '使用率',
|
||||
dataIndex: 'usageRate',
|
||||
key: 'usageRate',
|
||||
sorter: (a, b) => a.usageRate - b.usageRate,
|
||||
},
|
||||
{
|
||||
title: '平均使用时长',
|
||||
dataIndex: 'averageTime',
|
||||
key: 'averageTime',
|
||||
sorter: (a, b) => a.averageTime - b.averageTime,
|
||||
},
|
||||
{
|
||||
title: '环比上月',
|
||||
dataIndex: 'trend',
|
||||
key: 'trend',
|
||||
sorter: (a, b) => a.trend - b.trend,
|
||||
}
|
||||
]
|
||||
|
||||
const functionData = [
|
||||
{
|
||||
key: '1',
|
||||
module: '组织架构',
|
||||
function: '人员管理',
|
||||
usageCount: 1245,
|
||||
usageRate: 92,
|
||||
averageTime: '15分钟',
|
||||
trend: 5.2
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
module: '组织架构',
|
||||
function: '角色管理',
|
||||
usageCount: 865,
|
||||
usageRate: 78,
|
||||
averageTime: '12分钟',
|
||||
trend: 3.8
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
module: '资产管理',
|
||||
function: '设备管理',
|
||||
usageCount: 1056,
|
||||
usageRate: 85,
|
||||
averageTime: '18分钟',
|
||||
trend: 7.5
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
module: '资产管理',
|
||||
function: '库存管理',
|
||||
usageCount: 932,
|
||||
usageRate: 80,
|
||||
averageTime: '14分钟',
|
||||
trend: -2.1
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
module: '产品与服务',
|
||||
function: '产品管理',
|
||||
usageCount: 1120,
|
||||
usageRate: 88,
|
||||
averageTime: '20分钟',
|
||||
trend: 4.3
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
module: '产品与服务',
|
||||
function: '服务管理',
|
||||
usageCount: 986,
|
||||
usageRate: 82,
|
||||
averageTime: '16分钟',
|
||||
trend: 0
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
module: '项目管理',
|
||||
function: '项目模板',
|
||||
usageCount: 1320,
|
||||
usageRate: 95,
|
||||
averageTime: '25分钟',
|
||||
trend: 8.7
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
module: '项目管理',
|
||||
function: '合同管理',
|
||||
usageCount: 1150,
|
||||
usageRate: 90,
|
||||
averageTime: '22分钟',
|
||||
trend: 6.2
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
module: '施工操作台',
|
||||
function: '外业施工',
|
||||
usageCount: 1280,
|
||||
usageRate: 93,
|
||||
averageTime: '30分钟',
|
||||
trend: 9.5
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
module: '施工操作台',
|
||||
function: '数据处理',
|
||||
usageCount: 1180,
|
||||
usageRate: 91,
|
||||
averageTime: '28分钟',
|
||||
trend: 5.8
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
module: '聊天平台',
|
||||
function: '消息管理',
|
||||
usageCount: 1420,
|
||||
usageRate: 98,
|
||||
averageTime: '35分钟',
|
||||
trend: 12.3
|
||||
},
|
||||
{
|
||||
key: '12',
|
||||
module: '企业设置',
|
||||
function: '企业信息',
|
||||
usageCount: 720,
|
||||
usageRate: 65,
|
||||
averageTime: '10分钟',
|
||||
trend: -1.5
|
||||
}
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getUsageRateColor = (rate) => {
|
||||
if (rate >= 90) return '#52c41a'
|
||||
if (rate >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getTrendColor = (trend) => {
|
||||
if (trend > 0) return '#52c41a'
|
||||
if (trend < 0) return '#ff4d4f'
|
||||
return '#1890ff'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化功能模块使用频率图表
|
||||
if (moduleUsageChart.value) {
|
||||
const moduleChart = echarts.init(moduleUsageChart.value)
|
||||
moduleChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['使用次数', '使用人数']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['组织架构', '资产管理', '产品与服务', '项目管理', '施工操作台', '聊天平台', '企业设置', '系统资源管理']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用次数',
|
||||
type: 'bar',
|
||||
data: [2110, 1988, 2106, 2470, 2460, 1420, 720, 650]
|
||||
},
|
||||
{
|
||||
name: '使用人数',
|
||||
type: 'bar',
|
||||
data: [320, 302, 315, 335, 340, 356, 120, 85]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化各部门功能使用分布图表
|
||||
if (departmentUsageChart.value) {
|
||||
const departmentChart = echarts.init(departmentUsageChart.value)
|
||||
departmentChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '部门使用分布',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 35, name: '技术部' },
|
||||
{ value: 25, name: '市场部' },
|
||||
{ value: 20, name: '销售部' },
|
||||
{ value: 10, name: '人事部' },
|
||||
{ value: 10, name: '财务部' },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (departmentUsageChart.value) {
|
||||
const departmentChart = echarts.init(departmentUsageChart.value)
|
||||
departmentChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '部门使用分布',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 35, name: '技术部' },
|
||||
{ value: 25, name: '市场部' },
|
||||
{ value: 20, name: '销售部' },
|
||||
{ value: 10, name: '人事部' },
|
||||
{ value: 10, name: '财务部' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化功能使用时长占比图表
|
||||
if (usageTimeChart.value) {
|
||||
const timeChart = echarts.init(usageTimeChart.value)
|
||||
timeChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用时长占比',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '16',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{ value: 20, name: '组织架构' },
|
||||
{ value: 15, name: '资产管理' },
|
||||
{ value: 15, name: '产品与服务' },
|
||||
{ value: 20, name: '项目管理' },
|
||||
{ value: 20, name: '施工操作台' },
|
||||
{ value: 5, name: '聊天平台' },
|
||||
{ value: 3, name: '企业设置' },
|
||||
{ value: 2, name: '系统资源管理' },
|
||||
],
|
||||
if (usageTimeChart.value) {
|
||||
const timeChart = echarts.init(usageTimeChart.value)
|
||||
timeChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '使用时长占比',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '16',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{ value: 20, name: '组织架构' },
|
||||
{ value: 15, name: '资产管理' },
|
||||
{ value: 15, name: '产品与服务' },
|
||||
{ value: 20, name: '项目管理' },
|
||||
{ value: 20, name: '施工操作台' },
|
||||
{ value: 5, name: '聊天平台' },
|
||||
{ value: 3, name: '企业设置' },
|
||||
{ value: 2, name: '系统资源管理' }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', () => {
|
||||
if (moduleUsageChart.value) {
|
||||
const moduleChart = echarts.getInstanceByDom(moduleUsageChart.value)
|
||||
moduleChart?.resize()
|
||||
}
|
||||
if (departmentUsageChart.value) {
|
||||
const departmentChart = echarts.getInstanceByDom(departmentUsageChart.value)
|
||||
departmentChart?.resize()
|
||||
}
|
||||
if (usageTimeChart.value) {
|
||||
const timeChart = echarts.getInstanceByDom(usageTimeChart.value)
|
||||
timeChart?.resize()
|
||||
}
|
||||
if (moduleUsageChart.value) {
|
||||
const moduleChart = echarts.getInstanceByDom(moduleUsageChart.value)
|
||||
moduleChart?.resize()
|
||||
}
|
||||
if (departmentUsageChart.value) {
|
||||
const departmentChart = echarts.getInstanceByDom(departmentUsageChart.value)
|
||||
departmentChart?.resize()
|
||||
}
|
||||
if (usageTimeChart.value) {
|
||||
const timeChart = echarts.getInstanceByDom(usageTimeChart.value)
|
||||
timeChart?.resize()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.general-card {
|
||||
|
|
|
@ -1,491 +1,491 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="成员活跃数据" :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="活跃度分析">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="部门活跃度对比" :bordered="false">
|
||||
<div ref="departmentActivityChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="成员活跃数据" :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="活跃度分析">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="部门活跃度对比" :bordered="false">
|
||||
<div ref="departmentActivityChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="每日活跃用户数" :bordered="false">
|
||||
<div ref="dailyActiveUsersChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="平均在线时长" :bordered="false">
|
||||
<div ref="onlineTimeChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="每日活跃用户数" :bordered="false">
|
||||
<div ref="dailyActiveUsersChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="平均在线时长" :bordered="false">
|
||||
<div ref="onlineTimeChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="2" tab="成员排行榜">
|
||||
<a-card title="本月活跃度排名" :bordered="false">
|
||||
<a-table :columns="rankColumns" :data-source="rankData" :pagination="{ pageSize: 10 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'rank'">
|
||||
<a-tag :color="getRankColor(record.rank)">{{ record.rank }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'activityScore'">
|
||||
<a-progress :percent="record.activityScore" :stroke-color="getScoreColor(record.activityScore)" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="3" tab="考勤数据">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="部门考勤统计" :bordered="false">
|
||||
<a-table :columns="attendanceColumns" :data-source="attendanceData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'attendanceRate'">
|
||||
<a-progress :percent="record.attendanceRate" :stroke-color="getAttendanceColor(record.attendanceRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'lateCount'">
|
||||
<a-tag :color="getLateCountColor(record.lateCount)">{{ record.lateCount }}</a-tag>
|
||||
</template>
|
||||
<a-tab-pane key="2" tab="成员排行榜">
|
||||
<a-card title="本月活跃度排名" :bordered="false">
|
||||
<a-table :columns="rankColumns" :data-source="rankData" :pagination="{ pageSize: 10 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'rank'">
|
||||
<a-tag :color="getRankColor(record.rank)">{{ record.rank }}</a-tag>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<template v-if="column.dataIndex === 'activityScore'">
|
||||
<a-progress :percent="record.activityScore" :stroke-color="getScoreColor(record.activityScore)" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-divider />
|
||||
<a-tab-pane key="3" tab="考勤数据">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="部门考勤统计" :bordered="false">
|
||||
<a-table :columns="attendanceColumns" :data-source="attendanceData" :pagination="{ pageSize: 5 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'attendanceRate'">
|
||||
<a-progress :percent="record.attendanceRate" :stroke-color="getAttendanceColor(record.attendanceRate)" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'lateCount'">
|
||||
<a-tag :color="getLateCountColor(record.lateCount)">{{ record.lateCount }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="考勤趋势" :bordered="false">
|
||||
<div ref="attendanceTrendChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
<a-divider />
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="考勤趋势" :bordered="false">
|
||||
<div ref="attendanceTrendChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
const departmentActivityChart = ref(null)
|
||||
const dailyActiveUsersChart = ref(null)
|
||||
const onlineTimeChart = ref(null)
|
||||
const attendanceTrendChart = ref(null)
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 排行榜数据
|
||||
const rankColumns = [
|
||||
{
|
||||
title: '排名',
|
||||
dataIndex: 'rank',
|
||||
key: 'rank',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
},
|
||||
{
|
||||
title: '活跃度',
|
||||
dataIndex: 'activityScore',
|
||||
key: 'activityScore',
|
||||
},
|
||||
{
|
||||
title: '登录次数',
|
||||
dataIndex: 'loginCount',
|
||||
key: 'loginCount',
|
||||
},
|
||||
{
|
||||
title: '操作次数',
|
||||
dataIndex: 'operationCount',
|
||||
key: 'operationCount',
|
||||
},
|
||||
]
|
||||
const departmentActivityChart = ref(null)
|
||||
const dailyActiveUsersChart = ref(null)
|
||||
const onlineTimeChart = ref(null)
|
||||
const attendanceTrendChart = ref(null)
|
||||
|
||||
const rankData = [
|
||||
{
|
||||
key: '1',
|
||||
rank: 1,
|
||||
name: '张三',
|
||||
department: '技术部',
|
||||
activityScore: 98,
|
||||
loginCount: 45,
|
||||
operationCount: 532,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
rank: 2,
|
||||
name: '李四',
|
||||
department: '市场部',
|
||||
activityScore: 95,
|
||||
loginCount: 42,
|
||||
operationCount: 498,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
rank: 3,
|
||||
name: '王五',
|
||||
department: '销售部',
|
||||
activityScore: 92,
|
||||
loginCount: 40,
|
||||
operationCount: 475,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
rank: 4,
|
||||
name: '赵六',
|
||||
department: '人事部',
|
||||
activityScore: 88,
|
||||
loginCount: 38,
|
||||
operationCount: 450,
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
rank: 5,
|
||||
name: '钱七',
|
||||
department: '财务部',
|
||||
activityScore: 85,
|
||||
loginCount: 36,
|
||||
operationCount: 420,
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
rank: 6,
|
||||
name: '孙八',
|
||||
department: '技术部',
|
||||
activityScore: 82,
|
||||
loginCount: 34,
|
||||
operationCount: 405,
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
rank: 7,
|
||||
name: '周九',
|
||||
department: '市场部',
|
||||
activityScore: 79,
|
||||
loginCount: 32,
|
||||
operationCount: 380,
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
rank: 8,
|
||||
name: '吴十',
|
||||
department: '销售部',
|
||||
activityScore: 76,
|
||||
loginCount: 30,
|
||||
operationCount: 365,
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
rank: 9,
|
||||
name: '郑十一',
|
||||
department: '人事部',
|
||||
activityScore: 73,
|
||||
loginCount: 28,
|
||||
operationCount: 350,
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
rank: 10,
|
||||
name: '王十二',
|
||||
department: '财务部',
|
||||
activityScore: 70,
|
||||
loginCount: 26,
|
||||
operationCount: 335,
|
||||
},
|
||||
]
|
||||
|
||||
// 考勤数据
|
||||
const attendanceColumns = [
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
},
|
||||
{
|
||||
title: '人数',
|
||||
dataIndex: 'memberCount',
|
||||
key: 'memberCount',
|
||||
},
|
||||
{
|
||||
title: '出勤率',
|
||||
dataIndex: 'attendanceRate',
|
||||
key: 'attendanceRate',
|
||||
},
|
||||
{
|
||||
title: '迟到次数',
|
||||
dataIndex: 'lateCount',
|
||||
key: 'lateCount',
|
||||
},
|
||||
{
|
||||
title: '早退次数',
|
||||
dataIndex: 'earlyLeaveCount',
|
||||
key: 'earlyLeaveCount',
|
||||
},
|
||||
{
|
||||
title: '缺勤次数',
|
||||
dataIndex: 'absentCount',
|
||||
key: 'absentCount',
|
||||
},
|
||||
]
|
||||
|
||||
const attendanceData = [
|
||||
{
|
||||
key: '1',
|
||||
department: '技术部',
|
||||
memberCount: 45,
|
||||
attendanceRate: 98,
|
||||
lateCount: 3,
|
||||
earlyLeaveCount: 1,
|
||||
absentCount: 0,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
department: '市场部',
|
||||
memberCount: 32,
|
||||
attendanceRate: 96,
|
||||
lateCount: 5,
|
||||
earlyLeaveCount: 2,
|
||||
absentCount: 1,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
department: '销售部',
|
||||
memberCount: 38,
|
||||
attendanceRate: 95,
|
||||
lateCount: 6,
|
||||
earlyLeaveCount: 3,
|
||||
absentCount: 1,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
department: '人事部',
|
||||
memberCount: 15,
|
||||
attendanceRate: 97,
|
||||
lateCount: 2,
|
||||
earlyLeaveCount: 1,
|
||||
absentCount: 0,
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
department: '财务部',
|
||||
memberCount: 12,
|
||||
attendanceRate: 99,
|
||||
lateCount: 1,
|
||||
earlyLeaveCount: 0,
|
||||
absentCount: 0,
|
||||
},
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getRankColor = (rank) => {
|
||||
if (rank <= 3) return '#f50'
|
||||
if (rank <= 10) return '#2db7f5'
|
||||
return '#87d068'
|
||||
}
|
||||
|
||||
const getScoreColor = (score) => {
|
||||
if (score >= 90) return '#52c41a'
|
||||
if (score >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getAttendanceColor = (rate) => {
|
||||
if (rate >= 95) return '#52c41a'
|
||||
if (rate >= 90) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getLateCountColor = (count) => {
|
||||
if (count <= 2) return 'green'
|
||||
if (count <= 5) return 'orange'
|
||||
return 'red'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化部门活跃度对比图表
|
||||
if (departmentActivityChart.value) {
|
||||
const departmentChart = echarts.init(departmentActivityChart.value)
|
||||
departmentChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['技术部', '市场部', '销售部', '人事部', '财务部'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '活跃度',
|
||||
type: 'bar',
|
||||
data: [92, 85, 88, 79, 82],
|
||||
},
|
||||
{
|
||||
name: '登录次数',
|
||||
type: 'bar',
|
||||
data: [320, 280, 310, 240, 260],
|
||||
},
|
||||
{
|
||||
name: '操作次数',
|
||||
type: 'bar',
|
||||
data: [2800, 2100, 2400, 1800, 2000],
|
||||
},
|
||||
],
|
||||
})
|
||||
// 排行榜数据
|
||||
const rankColumns = [
|
||||
{
|
||||
title: '排名',
|
||||
dataIndex: 'rank',
|
||||
key: 'rank',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
},
|
||||
{
|
||||
title: '活跃度',
|
||||
dataIndex: 'activityScore',
|
||||
key: 'activityScore',
|
||||
},
|
||||
{
|
||||
title: '登录次数',
|
||||
dataIndex: 'loginCount',
|
||||
key: 'loginCount',
|
||||
},
|
||||
{
|
||||
title: '操作次数',
|
||||
dataIndex: 'operationCount',
|
||||
key: 'operationCount',
|
||||
}
|
||||
]
|
||||
|
||||
const rankData = [
|
||||
{
|
||||
key: '1',
|
||||
rank: 1,
|
||||
name: '张三',
|
||||
department: '技术部',
|
||||
activityScore: 98,
|
||||
loginCount: 45,
|
||||
operationCount: 532
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
rank: 2,
|
||||
name: '李四',
|
||||
department: '市场部',
|
||||
activityScore: 95,
|
||||
loginCount: 42,
|
||||
operationCount: 498
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
rank: 3,
|
||||
name: '王五',
|
||||
department: '销售部',
|
||||
activityScore: 92,
|
||||
loginCount: 40,
|
||||
operationCount: 475
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
rank: 4,
|
||||
name: '赵六',
|
||||
department: '人事部',
|
||||
activityScore: 88,
|
||||
loginCount: 38,
|
||||
operationCount: 450
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
rank: 5,
|
||||
name: '钱七',
|
||||
department: '财务部',
|
||||
activityScore: 85,
|
||||
loginCount: 36,
|
||||
operationCount: 420
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
rank: 6,
|
||||
name: '孙八',
|
||||
department: '技术部',
|
||||
activityScore: 82,
|
||||
loginCount: 34,
|
||||
operationCount: 405
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
rank: 7,
|
||||
name: '周九',
|
||||
department: '市场部',
|
||||
activityScore: 79,
|
||||
loginCount: 32,
|
||||
operationCount: 380
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
rank: 8,
|
||||
name: '吴十',
|
||||
department: '销售部',
|
||||
activityScore: 76,
|
||||
loginCount: 30,
|
||||
operationCount: 365
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
rank: 9,
|
||||
name: '郑十一',
|
||||
department: '人事部',
|
||||
activityScore: 73,
|
||||
loginCount: 28,
|
||||
operationCount: 350
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
rank: 10,
|
||||
name: '王十二',
|
||||
department: '财务部',
|
||||
activityScore: 70,
|
||||
loginCount: 26,
|
||||
operationCount: 335
|
||||
}
|
||||
]
|
||||
|
||||
// 考勤数据
|
||||
const attendanceColumns = [
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
},
|
||||
{
|
||||
title: '人数',
|
||||
dataIndex: 'memberCount',
|
||||
key: 'memberCount',
|
||||
},
|
||||
{
|
||||
title: '出勤率',
|
||||
dataIndex: 'attendanceRate',
|
||||
key: 'attendanceRate',
|
||||
},
|
||||
{
|
||||
title: '迟到次数',
|
||||
dataIndex: 'lateCount',
|
||||
key: 'lateCount',
|
||||
},
|
||||
{
|
||||
title: '早退次数',
|
||||
dataIndex: 'earlyLeaveCount',
|
||||
key: 'earlyLeaveCount',
|
||||
},
|
||||
{
|
||||
title: '缺勤次数',
|
||||
dataIndex: 'absentCount',
|
||||
key: 'absentCount',
|
||||
}
|
||||
]
|
||||
|
||||
const attendanceData = [
|
||||
{
|
||||
key: '1',
|
||||
department: '技术部',
|
||||
memberCount: 45,
|
||||
attendanceRate: 98,
|
||||
lateCount: 3,
|
||||
earlyLeaveCount: 1,
|
||||
absentCount: 0
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
department: '市场部',
|
||||
memberCount: 32,
|
||||
attendanceRate: 96,
|
||||
lateCount: 5,
|
||||
earlyLeaveCount: 2,
|
||||
absentCount: 1
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
department: '销售部',
|
||||
memberCount: 38,
|
||||
attendanceRate: 95,
|
||||
lateCount: 6,
|
||||
earlyLeaveCount: 3,
|
||||
absentCount: 1
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
department: '人事部',
|
||||
memberCount: 15,
|
||||
attendanceRate: 97,
|
||||
lateCount: 2,
|
||||
earlyLeaveCount: 1,
|
||||
absentCount: 0
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
department: '财务部',
|
||||
memberCount: 12,
|
||||
attendanceRate: 99,
|
||||
lateCount: 1,
|
||||
earlyLeaveCount: 0,
|
||||
absentCount: 0
|
||||
}
|
||||
]
|
||||
|
||||
// 颜色处理函数
|
||||
const getRankColor = (rank) => {
|
||||
if (rank <= 3) return '#f50'
|
||||
if (rank <= 10) return '#2db7f5'
|
||||
return '#87d068'
|
||||
}
|
||||
|
||||
const getScoreColor = (score) => {
|
||||
if (score >= 90) return '#52c41a'
|
||||
if (score >= 70) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getAttendanceColor = (rate) => {
|
||||
if (rate >= 95) return '#52c41a'
|
||||
if (rate >= 90) return '#1890ff'
|
||||
return '#faad14'
|
||||
}
|
||||
|
||||
const getLateCountColor = (count) => {
|
||||
if (count <= 2) return 'green'
|
||||
if (count <= 5) return 'orange'
|
||||
return 'red'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化部门活跃度对比图表
|
||||
if (departmentActivityChart.value) {
|
||||
const departmentChart = echarts.init(departmentActivityChart.value)
|
||||
departmentChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['技术部', '市场部', '销售部', '人事部', '财务部']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '活跃度',
|
||||
type: 'bar',
|
||||
data: [92, 85, 88, 79, 82]
|
||||
},
|
||||
{
|
||||
name: '登录次数',
|
||||
type: 'bar',
|
||||
data: [320, 280, 310, 240, 260]
|
||||
},
|
||||
{
|
||||
name: '操作次数',
|
||||
type: 'bar',
|
||||
data: [2800, 2100, 2400, 1800, 2000]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化每日活跃用户数图表
|
||||
if (dailyActiveUsersChart.value) {
|
||||
const dailyActiveChart = echarts.init(dailyActiveUsersChart.value)
|
||||
dailyActiveChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 132, 145, 135, 128, 68, 42],
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (dailyActiveUsersChart.value) {
|
||||
const dailyActiveChart = echarts.init(dailyActiveUsersChart.value)
|
||||
dailyActiveChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 132, 145, 135, 128, 68, 42],
|
||||
type: 'line',
|
||||
areaStyle: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化平均在线时长图表
|
||||
if (onlineTimeChart.value) {
|
||||
const onlineChart = echarts.init(onlineTimeChart.value)
|
||||
onlineChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['技术部', '市场部', '销售部', '人事部', '财务部'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} 小时',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '平均在线时长',
|
||||
type: 'bar',
|
||||
data: [7.5, 6.8, 7.2, 6.5, 6.9],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (onlineTimeChart.value) {
|
||||
const onlineChart = echarts.init(onlineTimeChart.value)
|
||||
onlineChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['技术部', '市场部', '销售部', '人事部', '财务部']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} 小时'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '平均在线时长',
|
||||
type: 'bar',
|
||||
data: [7.5, 6.8, 7.2, 6.5, 6.9]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化考勤趋势图表
|
||||
if (attendanceTrendChart.value) {
|
||||
const attendanceChart = echarts.init(attendanceTrendChart.value)
|
||||
attendanceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
if (attendanceTrendChart.value) {
|
||||
const attendanceChart = echarts.init(attendanceTrendChart.value)
|
||||
attendanceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['出勤率', '迟到率', '早退率']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}%'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '出勤率',
|
||||
type: 'line',
|
||||
data: [96.2, 97.1, 96.8, 97.5, 98.2, 97.8]
|
||||
},
|
||||
legend: {
|
||||
data: ['出勤率', '迟到率', '早退率'],
|
||||
{
|
||||
name: '迟到率',
|
||||
type: 'line',
|
||||
data: [2.8, 2.2, 2.5, 1.8, 1.2, 1.5]
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}%',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '出勤率',
|
||||
type: 'line',
|
||||
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],
|
||||
},
|
||||
{
|
||||
name: '早退率',
|
||||
type: 'line',
|
||||
data: [1.0, 0.7, 0.7, 0.7, 0.6, 0.7],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
{
|
||||
name: '早退率',
|
||||
type: 'line',
|
||||
data: [1.0, 0.7, 0.7, 0.7, 0.6, 0.7]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', () => {
|
||||
if (departmentActivityChart.value) {
|
||||
const departmentChart = echarts.getInstanceByDom(departmentActivityChart.value)
|
||||
departmentChart?.resize()
|
||||
}
|
||||
if (dailyActiveUsersChart.value) {
|
||||
const dailyActiveChart = echarts.getInstanceByDom(dailyActiveUsersChart.value)
|
||||
dailyActiveChart?.resize()
|
||||
}
|
||||
if (onlineTimeChart.value) {
|
||||
const onlineChart = echarts.getInstanceByDom(onlineTimeChart.value)
|
||||
onlineChart?.resize()
|
||||
}
|
||||
if (attendanceTrendChart.value) {
|
||||
const attendanceChart = echarts.getInstanceByDom(attendanceTrendChart.value)
|
||||
attendanceChart?.resize()
|
||||
}
|
||||
if (departmentActivityChart.value) {
|
||||
const departmentChart = echarts.getInstanceByDom(departmentActivityChart.value)
|
||||
departmentChart?.resize()
|
||||
}
|
||||
if (dailyActiveUsersChart.value) {
|
||||
const dailyActiveChart = echarts.getInstanceByDom(dailyActiveUsersChart.value)
|
||||
dailyActiveChart?.resize()
|
||||
}
|
||||
if (onlineTimeChart.value) {
|
||||
const onlineChart = echarts.getInstanceByDom(onlineTimeChart.value)
|
||||
onlineChart?.resize()
|
||||
}
|
||||
if (attendanceTrendChart.value) {
|
||||
const attendanceChart = echarts.getInstanceByDom(attendanceTrendChart.value)
|
||||
attendanceChart?.resize()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.general-card {
|
||||
|
|
|
@ -1,258 +1,259 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="企业数据概览" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="项目总数"
|
||||
:value="statistics.projectCount"
|
||||
:precision="0"
|
||||
style="margin-right: 50px"
|
||||
>
|
||||
<template #prefix>
|
||||
<IconFile />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="成员总数"
|
||||
:value="statistics.memberCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<IconUserGroup />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="设备总数"
|
||||
:value="statistics.deviceCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<IconComputer />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="本月完成项目"
|
||||
:value="statistics.completedProjectCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<IconCheckCircle />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="app-container">
|
||||
<a-card class="general-card" title="企业数据概览" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="项目总数"
|
||||
:value="statistics.projectCount"
|
||||
:precision="0"
|
||||
style="margin-right: 50px"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-file />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="成员总数"
|
||||
:value="statistics.memberCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-user-group />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="设备总数"
|
||||
:value="statistics.deviceCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-computer />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card">
|
||||
<a-statistic
|
||||
title="本月完成项目"
|
||||
:value="statistics.completedProjectCount"
|
||||
:precision="0"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-check-circle />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="项目进度统计" :bordered="false">
|
||||
<div ref="projectProgressChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="资源使用情况" :bordered="false">
|
||||
<div ref="resourceUsageChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="项目进度统计" :bordered="false">
|
||||
<div ref="projectProgressChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card title="资源使用情况" :bordered="false">
|
||||
<div ref="resourceUsageChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider />
|
||||
<a-divider />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="近6个月业务趋势" :bordered="false">
|
||||
<div ref="businessTrendChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card title="近6个月业务趋势" :bordered="false">
|
||||
<div ref="businessTrendChart" style="height: 300px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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'
|
||||
<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'
|
||||
|
||||
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({
|
||||
projectCount: 128,
|
||||
memberCount: 356,
|
||||
deviceCount: 243,
|
||||
completedProjectCount: 15,
|
||||
})
|
||||
const statistics = reactive({
|
||||
projectCount: 128,
|
||||
memberCount: 356,
|
||||
deviceCount: 243,
|
||||
completedProjectCount: 15
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
onMounted(() => {
|
||||
// 使用 nextTick 确保 DOM 已完全渲染
|
||||
nextTick(() => {
|
||||
// 初始化项目进度统计图表
|
||||
if (projectProgressChart.value) {
|
||||
const projectChart = echarts.init(projectProgressChart.value)
|
||||
projectChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '项目状态',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 48, name: '进行中' },
|
||||
{ value: 65, name: '已完成' },
|
||||
{ value: 12, name: '已暂停' },
|
||||
{ value: 3, name: '已取消' },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (projectProgressChart.value) {
|
||||
const projectChart = echarts.init(projectProgressChart.value)
|
||||
projectChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '项目状态',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
data: [
|
||||
{ value: 48, name: '进行中' },
|
||||
{ value: 65, name: '已完成' },
|
||||
{ value: 12, name: '已暂停' },
|
||||
{ value: 3, name: '已取消' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化资源使用情况图表
|
||||
if (resourceUsageChart.value) {
|
||||
const resourceChart = echarts.init(resourceUsageChart.value)
|
||||
resourceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
if (resourceUsageChart.value) {
|
||||
const resourceChart = echarts.init(resourceUsageChart.value)
|
||||
resourceChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['服务器', '存储空间', '带宽', '设备使用率', '人力资源']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '已使用',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
},
|
||||
legend: {},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['服务器', '存储空间', '带宽', '设备使用率', '人力资源'],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '已使用',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
data: [65, 72, 58, 80, 75],
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
{
|
||||
name: '剩余',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
data: [35, 28, 42, 20, 25],
|
||||
data: [65, 72, 58, 80, 75]
|
||||
},
|
||||
{
|
||||
name: '剩余',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [35, 28, 42, 20, 25]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化业务趋势图表
|
||||
if (businessTrendChart.value) {
|
||||
const businessChart = echarts.init(businessTrendChart.value)
|
||||
businessChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
if (businessTrendChart.value) {
|
||||
const businessChart = echarts.init(businessTrendChart.value)
|
||||
businessChart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['项目数量', '营业收入', '新增客户']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '项目数量',
|
||||
type: 'line',
|
||||
data: [10, 12, 15, 18, 22, 24]
|
||||
},
|
||||
legend: {
|
||||
data: ['项目数量', '营业收入', '新增客户'],
|
||||
{
|
||||
name: '营业收入',
|
||||
type: 'line',
|
||||
data: [120, 132, 145, 160, 178, 190]
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '项目数量',
|
||||
type: 'line',
|
||||
data: [10, 12, 15, 18, 22, 24],
|
||||
},
|
||||
{
|
||||
name: '营业收入',
|
||||
type: 'line',
|
||||
data: [120, 132, 145, 160, 178, 190],
|
||||
},
|
||||
{
|
||||
name: '新增客户',
|
||||
type: 'line',
|
||||
data: [5, 7, 8, 10, 12, 15],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
{
|
||||
name: '新增客户',
|
||||
type: 'line',
|
||||
data: [5, 7, 8, 10, 12, 15]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', () => {
|
||||
if (projectProgressChart.value) {
|
||||
const projectChart = echarts.getInstanceByDom(projectProgressChart.value)
|
||||
projectChart?.resize()
|
||||
}
|
||||
if (resourceUsageChart.value) {
|
||||
const resourceChart = echarts.getInstanceByDom(resourceUsageChart.value)
|
||||
resourceChart?.resize()
|
||||
}
|
||||
if (businessTrendChart.value) {
|
||||
const businessChart = echarts.getInstanceByDom(businessTrendChart.value)
|
||||
businessChart?.resize()
|
||||
}
|
||||
if (projectProgressChart.value) {
|
||||
const projectChart = echarts.getInstanceByDom(projectProgressChart.value)
|
||||
projectChart?.resize()
|
||||
}
|
||||
if (resourceUsageChart.value) {
|
||||
const resourceChart = echarts.getInstanceByDom(resourceUsageChart.value)
|
||||
resourceChart?.resize()
|
||||
}
|
||||
if (businessTrendChart.value) {
|
||||
const businessChart = echarts.getInstanceByDom(businessTrendChart.value)
|
||||
businessChart?.resize()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.general-card {
|
||||
|
|
|
@ -84,8 +84,8 @@
|
|||
<a-link @click="editRecord(record)">编辑</a-link>
|
||||
<a-link @click="resetPassword(record)">重置密码</a-link>
|
||||
<a-link
|
||||
:class="record.status === 'active' ? 'text-red-500' : 'text-green-500'"
|
||||
@click="toggleStatus(record)"
|
||||
:class="record.status === 'active' ? 'text-red-500' : 'text-green-500'"
|
||||
>
|
||||
{{ record.status === 'active' ? '禁用' : '启用' }}
|
||||
</a-link>
|
||||
|
@ -96,18 +96,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
let 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()
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click="editBankAccount(record)">编辑</a-link>
|
||||
<a-link v-if="!record.isDefault" @click="setDefaultAccount(record)">设为默认</a-link>
|
||||
<a-link @click="setDefaultAccount(record)" v-if="!record.isDefault">设为默认</a-link>
|
||||
<a-link @click="deleteBankAccount(record)">删除</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
|
@ -122,7 +122,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ref, reactive } 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'
|
||||
}
|
||||
|
|
|
@ -67,20 +67,20 @@
|
|||
<a-space>
|
||||
<a-link @click="viewTaskDetail(record)">详情</a-link>
|
||||
<a-link
|
||||
v-if="record.status === 'pending'"
|
||||
@click="startTask(record)"
|
||||
v-if="record.status === 'pending'"
|
||||
>
|
||||
开始
|
||||
</a-link>
|
||||
<a-link
|
||||
v-if="record.status === 'running'"
|
||||
@click="pauseTask(record)"
|
||||
v-if="record.status === 'running'"
|
||||
>
|
||||
暂停
|
||||
</a-link>
|
||||
<a-link
|
||||
v-if="record.status === 'paused'"
|
||||
@click="resumeTask(record)"
|
||||
v-if="record.status === 'paused'"
|
||||
>
|
||||
继续
|
||||
</a-link>
|
||||
|
@ -138,7 +138,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ref, reactive } 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'
|
||||
}
|
||||
|
|
|
@ -85,8 +85,8 @@
|
|||
<a-link @click="viewVersionDetail(record)">详情</a-link>
|
||||
<a-link @click="downloadVersion(record)">下载</a-link>
|
||||
<a-link
|
||||
v-if="record.status === 'installed' && record.id !== currentVersionId"
|
||||
@click="rollbackVersion(record)"
|
||||
v-if="record.status === 'installed' && record.id !== currentVersionId"
|
||||
>
|
||||
回滚
|
||||
</a-link>
|
||||
|
@ -150,7 +150,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
|
@ -162,7 +162,7 @@ const latestVersion = ref({
|
|||
version: 'v2.4.0',
|
||||
description: '新增项目管理模块,优化系统性能,修复已知bug',
|
||||
releaseDate: '2024-03-20',
|
||||
size: '156.8 MB',
|
||||
size: '156.8 MB'
|
||||
})
|
||||
|
||||
// 版本历史记录
|
||||
|
@ -175,7 +175,7 @@ const versionHistory = ref([
|
|||
installDate: '2023-12-20',
|
||||
status: 'archived',
|
||||
description: '初始版本发布,包含基础功能模块',
|
||||
size: '128.5 MB',
|
||||
size: '128.5 MB'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -185,7 +185,7 @@ const versionHistory = ref([
|
|||
installDate: '2024-01-20',
|
||||
status: 'archived',
|
||||
description: '新增用户管理和权限控制功能',
|
||||
size: '142.3 MB',
|
||||
size: '142.3 MB'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
@ -195,7 +195,7 @@ const versionHistory = ref([
|
|||
installDate: '2024-02-20',
|
||||
status: 'current',
|
||||
description: '修复安全漏洞,优化界面交互',
|
||||
size: '145.7 MB',
|
||||
size: '145.7 MB'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
|
@ -205,8 +205,8 @@ const versionHistory = ref([
|
|||
installDate: '',
|
||||
status: 'available',
|
||||
description: '新增项目管理模块,优化系统性能',
|
||||
size: '156.8 MB',
|
||||
},
|
||||
size: '156.8 MB'
|
||||
}
|
||||
])
|
||||
|
||||
// 版本表格列配置
|
||||
|
@ -218,7 +218,7 @@ const versionColumns: TableColumnData[] = [
|
|||
{ title: '状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '文件大小', dataIndex: 'size', width: 100 },
|
||||
{ title: '描述', dataIndex: 'description', width: 300, ellipsis: true, tooltip: true },
|
||||
{ title: '操作', slotName: 'action', width: 200 },
|
||||
{ title: '操作', slotName: 'action', width: 200 }
|
||||
]
|
||||
|
||||
// 升级配置
|
||||
|
@ -226,16 +226,16 @@ const upgradeConfig = reactive({
|
|||
autoCheck: true,
|
||||
checkFrequency: 'weekly',
|
||||
notificationMethods: ['email', 'system'],
|
||||
maintenanceWindow: ['02:00', '06:00'],
|
||||
maintenanceWindow: ['02:00', '06:00']
|
||||
})
|
||||
|
||||
// 获取版本状态颜色
|
||||
const getVersionStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
current: 'green',
|
||||
available: 'blue',
|
||||
archived: 'gray',
|
||||
deprecated: 'red',
|
||||
'current': 'green',
|
||||
'available': 'blue',
|
||||
'archived': 'gray',
|
||||
'deprecated': 'red'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
@ -243,10 +243,10 @@ const getVersionStatusColor = (status: string) => {
|
|||
// 获取版本状态文本
|
||||
const getVersionStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
current: '当前版本',
|
||||
available: '可升级',
|
||||
archived: '已归档',
|
||||
deprecated: '已弃用',
|
||||
'current': '当前版本',
|
||||
'available': '可升级',
|
||||
'archived': '已归档',
|
||||
'deprecated': '已弃用'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
@ -254,10 +254,10 @@ const getVersionStatusText = (status: string) => {
|
|||
// 获取版本类型颜色
|
||||
const getVersionTypeColor = (type: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
主要版本: 'red',
|
||||
功能版本: 'blue',
|
||||
补丁版本: 'green',
|
||||
热修复: 'orange',
|
||||
'主要版本': 'red',
|
||||
'功能版本': 'blue',
|
||||
'补丁版本': 'green',
|
||||
'热修复': 'orange'
|
||||
}
|
||||
return colorMap[type] || 'gray'
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ const resetUpgradeConfig = () => {
|
|||
autoCheck: true,
|
||||
checkFrequency: 'weekly',
|
||||
notificationMethods: ['email', 'system'],
|
||||
maintenanceWindow: ['02:00', '06:00'],
|
||||
maintenanceWindow: ['02:00', '06:00']
|
||||
})
|
||||
Message.success('升级配置已重置')
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<!-- 考勤统计 -->
|
||||
<template>
|
||||
<GiPageLayout>
|
||||
<GiTable
|
||||
|
@ -49,18 +50,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
let searchForm = reactive({
|
||||
userName: '',
|
||||
deptName: '',
|
||||
status: '',
|
||||
attendanceDate: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
|
||||
// 查询条件配置
|
||||
|
@ -70,16 +71,16 @@ const queryFormColumns = [
|
|||
label: '员工姓名',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入员工姓名',
|
||||
},
|
||||
placeholder: '请输入员工姓名'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'deptName',
|
||||
label: '部门',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入部门名称',
|
||||
},
|
||||
placeholder: '请输入部门名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
|
@ -92,10 +93,10 @@ const queryFormColumns = [
|
|||
{ label: '迟到', value: 'late' },
|
||||
{ label: '早退', value: 'early' },
|
||||
{ label: '缺勤', value: 'absent' },
|
||||
{ label: '请假', value: 'leave' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ label: '请假', value: 'leave' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
|
@ -109,7 +110,7 @@ const tableColumns: TableColumnData[] = [
|
|||
{ title: '工作时长', dataIndex: 'workHours', width: 100 },
|
||||
{ title: '考勤状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' },
|
||||
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
|
@ -125,7 +126,7 @@ const dataList = ref([
|
|||
endTime: '18:00',
|
||||
workHours: '8.0小时',
|
||||
status: 'normal',
|
||||
remark: '',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -137,7 +138,7 @@ const dataList = ref([
|
|||
endTime: '18:00',
|
||||
workHours: '7.75小时',
|
||||
status: 'late',
|
||||
remark: '迟到15分钟',
|
||||
remark: '迟到15分钟'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
@ -149,8 +150,8 @@ const dataList = ref([
|
|||
endTime: '17:30',
|
||||
workHours: '7.5小时',
|
||||
status: 'early',
|
||||
remark: '早退30分钟',
|
||||
},
|
||||
remark: '早退30分钟'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
|
@ -158,17 +159,17 @@ const pagination = reactive({
|
|||
pageSize: 10,
|
||||
total: 3,
|
||||
showTotal: true,
|
||||
showPageSize: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
normal: 'green',
|
||||
late: 'orange',
|
||||
early: 'blue',
|
||||
absent: 'red',
|
||||
leave: 'gray',
|
||||
'normal': 'green',
|
||||
'late': 'orange',
|
||||
'early': 'blue',
|
||||
'absent': 'red',
|
||||
'leave': 'gray'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
@ -176,11 +177,11 @@ const getStatusColor = (status: string) => {
|
|||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
normal: '正常',
|
||||
late: '迟到',
|
||||
early: '早退',
|
||||
absent: '缺勤',
|
||||
leave: '请假',
|
||||
'normal': '正常',
|
||||
'late': '迟到',
|
||||
'early': '早退',
|
||||
'absent': '缺勤',
|
||||
'leave': '请假'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
@ -201,7 +202,7 @@ const reset = () => {
|
|||
status: '',
|
||||
attendanceDate: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
|
|
@ -99,17 +99,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
let searchForm = reactive({
|
||||
userName: '',
|
||||
deptName: '',
|
||||
pointLevel: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
|
||||
// 查询条件配置
|
||||
|
@ -119,16 +119,16 @@ const queryFormColumns = [
|
|||
label: '员工姓名',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入员工姓名',
|
||||
},
|
||||
placeholder: '请输入员工姓名'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'deptName',
|
||||
label: '部门',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入部门名称',
|
||||
},
|
||||
placeholder: '请输入部门名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pointLevel',
|
||||
|
@ -141,10 +141,10 @@ const queryFormColumns = [
|
|||
{ label: '熟练', value: '熟练' },
|
||||
{ label: '专家', value: '专家' },
|
||||
{ label: '大师', value: '大师' },
|
||||
{ label: '传奇', value: '传奇' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ label: '传奇', value: '传奇' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
|
@ -160,7 +160,7 @@ const tableColumns: TableColumnData[] = [
|
|||
{ title: '累计获得', dataIndex: 'totalGain', width: 100 },
|
||||
{ title: '累计扣除', dataIndex: 'totalDeduct', width: 100 },
|
||||
{ title: '最后更新', dataIndex: 'lastUpdate', width: 160 },
|
||||
{ title: '操作', slotName: 'action', width: 150, fixed: 'right' },
|
||||
{ title: '操作', slotName: 'action', width: 150, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
|
@ -178,7 +178,7 @@ const dataList = ref([
|
|||
monthDeduct: 2,
|
||||
totalGain: 320,
|
||||
totalDeduct: 35,
|
||||
lastUpdate: '2024-01-15 16:30:00',
|
||||
lastUpdate: '2024-01-15 16:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -192,7 +192,7 @@ const dataList = ref([
|
|||
monthDeduct: 0,
|
||||
totalGain: 156,
|
||||
totalDeduct: 0,
|
||||
lastUpdate: '2024-01-14 14:20:00',
|
||||
lastUpdate: '2024-01-14 14:20:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
@ -206,7 +206,7 @@ const dataList = ref([
|
|||
monthDeduct: 5,
|
||||
totalGain: 95,
|
||||
totalDeduct: 17,
|
||||
lastUpdate: '2024-01-13 10:15:00',
|
||||
lastUpdate: '2024-01-13 10:15:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
|
@ -220,8 +220,8 @@ const dataList = ref([
|
|||
monthDeduct: 0,
|
||||
totalGain: 350,
|
||||
totalDeduct: 0,
|
||||
lastUpdate: '2024-01-15 18:00:00',
|
||||
},
|
||||
lastUpdate: '2024-01-15 18:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
|
@ -229,17 +229,17 @@ const pagination = reactive({
|
|||
pageSize: 10,
|
||||
total: 4,
|
||||
showTotal: true,
|
||||
showPageSize: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 获取等级颜色
|
||||
const getLevelColor = (level: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
新手: 'red',
|
||||
熟练: 'orange',
|
||||
专家: 'blue',
|
||||
大师: 'green',
|
||||
传奇: 'purple',
|
||||
'新手': 'red',
|
||||
'熟练': 'orange',
|
||||
'专家': 'blue',
|
||||
'大师': 'green',
|
||||
'传奇': 'purple'
|
||||
}
|
||||
return colorMap[level] || 'gray'
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ const reset = () => {
|
|||
deptName: '',
|
||||
pointLevel: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
|
|
@ -56,18 +56,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
let searchForm = reactive({
|
||||
userName: '',
|
||||
deptName: '',
|
||||
performanceLevel: '',
|
||||
assessmentPeriod: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
|
||||
// 查询条件配置
|
||||
|
@ -77,16 +77,16 @@ const queryFormColumns = [
|
|||
label: '员工姓名',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入员工姓名',
|
||||
},
|
||||
placeholder: '请输入员工姓名'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'deptName',
|
||||
label: '部门',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入部门名称',
|
||||
},
|
||||
placeholder: '请输入部门名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'performanceLevel',
|
||||
|
@ -98,10 +98,10 @@ const queryFormColumns = [
|
|||
{ label: '优秀', value: '优秀' },
|
||||
{ label: '良好', value: '良好' },
|
||||
{ label: '一般', value: '一般' },
|
||||
{ label: '待改进', value: '待改进' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ label: '待改进', value: '待改进' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
|
@ -118,7 +118,7 @@ const tableColumns: TableColumnData[] = [
|
|||
{ title: '创新能力', dataIndex: 'innovation', width: 100 },
|
||||
{ title: '评估人', dataIndex: 'assessor', width: 120 },
|
||||
{ title: '评估时间', dataIndex: 'assessmentTime', width: 160 },
|
||||
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' },
|
||||
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
|
@ -137,7 +137,7 @@ const dataList = ref([
|
|||
teamwork: '90分',
|
||||
innovation: '88分',
|
||||
assessor: '李经理',
|
||||
assessmentTime: '2024-04-01 14:30:00',
|
||||
assessmentTime: '2024-04-01 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -152,7 +152,7 @@ const dataList = ref([
|
|||
teamwork: '85分',
|
||||
innovation: '82分',
|
||||
assessor: '李经理',
|
||||
assessmentTime: '2024-04-01 15:00:00',
|
||||
assessmentTime: '2024-04-01 15:00:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
@ -167,8 +167,8 @@ const dataList = ref([
|
|||
teamwork: '80分',
|
||||
innovation: '76分',
|
||||
assessor: '张经理',
|
||||
assessmentTime: '2024-04-02 10:00:00',
|
||||
},
|
||||
assessmentTime: '2024-04-02 10:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
|
@ -176,16 +176,16 @@ const pagination = reactive({
|
|||
pageSize: 10,
|
||||
total: 3,
|
||||
showTotal: true,
|
||||
showPageSize: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 获取等级颜色
|
||||
const getLevelColor = (level: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
优秀: 'green',
|
||||
良好: 'blue',
|
||||
一般: 'orange',
|
||||
待改进: 'red',
|
||||
'优秀': 'green',
|
||||
'良好': 'blue',
|
||||
'一般': 'orange',
|
||||
'待改进': 'red'
|
||||
}
|
||||
return colorMap[level] || 'gray'
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ const reset = () => {
|
|||
performanceLevel: '',
|
||||
assessmentPeriod: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<h2 class="page-title">人员资质管理</h2>
|
||||
<a-button type="primary" @click="showAddModal">
|
||||
<template #icon>
|
||||
<IconPlus />
|
||||
<icon-plus />
|
||||
</template>
|
||||
新增资质
|
||||
</a-button>
|
||||
|
@ -30,13 +30,13 @@
|
|||
<a-space>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<IconSearch />
|
||||
<icon-search />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
<icon-refresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
|
@ -47,19 +47,15 @@
|
|||
|
||||
<!-- 人员资质表格 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<a-table
|
||||
:columns="columns" :data="certificationList" :pagination="paginationConfig" :loading="loading"
|
||||
row-key="certificationId" @page-change="handlePageChange"
|
||||
>
|
||||
<a-table :columns="columns" :data="certificationList" :pagination="paginationConfig" :loading="loading"
|
||||
row-key="certificationId" @page-change="handlePageChange">
|
||||
<template #userName="{ record }">
|
||||
<span>{{ getUserName(record.userId) }}</span>
|
||||
</template>
|
||||
|
||||
<template #certificationImage="{ record }">
|
||||
<a-image
|
||||
v-if="record.certificationImage" :src="record.certificationImage" width="60" height="40"
|
||||
fit="cover" show-loader preview
|
||||
/>
|
||||
<a-image v-if="record.certificationImage" :src="record.certificationImage" width="60" height="40"
|
||||
fit="cover" show-loader preview />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
|
@ -81,10 +77,8 @@
|
|||
</a-card>
|
||||
|
||||
<!-- 新增/编辑资质信息模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible" :title="isEdit ? '编辑资质信息' : '新增资质信息'" width="700px" @ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-modal v-model:visible="modalVisible" :title="isEdit ? '编辑资质信息' : '新增资质信息'" width="700px" @ok="handleSubmit"
|
||||
@cancel="handleCancel">
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
|
@ -102,10 +96,8 @@
|
|||
|
||||
<a-form-item label="证书类型" field="certificationType">
|
||||
<a-select v-model="formData.certificationType" placeholder="请选择证书类型">
|
||||
<a-option
|
||||
v-for="item in CERTIFICATION_TYPE_OPTIONS" :key="item.value" :value="item.value"
|
||||
:label="item.label"
|
||||
>
|
||||
<a-option v-for="item in CERTIFICATION_TYPE_OPTIONS" :key="item.value" :value="item.value"
|
||||
:label="item.label">
|
||||
{{ item.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
|
@ -113,10 +105,8 @@
|
|||
|
||||
<a-col :span="12">
|
||||
<a-form-item label="持证人" field="userId">
|
||||
<a-select
|
||||
v-model="formData.userId" placeholder="请选择持证人" :loading="loadingUsers" allow-search
|
||||
:filter-option="filterUserOption" @="console.log('d', formData)"
|
||||
>
|
||||
<a-select v-model="formData.userId" placeholder="请选择持证人" :loading="loadingUsers" allow-search
|
||||
:filter-option="filterUserOption" @="console.log('d', formData)">
|
||||
<a-option v-for="user in userList" :key="user.userId" :value="user.userId" :label="user.name">
|
||||
{{ user.name }}({{ user.account }})
|
||||
</a-option>
|
||||
|
@ -139,18 +129,14 @@
|
|||
</a-row>
|
||||
|
||||
<a-form-item label="证书图片" field="certificationImage">
|
||||
<a-upload
|
||||
:custom-request="handleUpload" :show-file-list="false" accept="image/*"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<a-upload :custom-request="handleUpload" :show-file-list="false" accept="image/*"
|
||||
:before-upload="beforeUpload">
|
||||
<template #upload-button>
|
||||
<div class="upload-wrapper">
|
||||
<a-image
|
||||
v-if="formData.certificationImage" :src="formData.certificationImage" width="200"
|
||||
height="120" fit="cover" show-loader preview
|
||||
/>
|
||||
<a-image v-if="formData.certificationImage" :src="formData.certificationImage" width="200"
|
||||
height="120" fit="cover" show-loader preview />
|
||||
<div v-else class="upload-placeholder">
|
||||
<IconPlus />
|
||||
<icon-plus />
|
||||
<div>点击上传证书图片</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -164,9 +150,9 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import { IconPlus, IconRefresh, IconSearch } from '@arco-design/web-vue/es/icon'
|
||||
import { IconPlus, IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
import * as CertificationAPI from '@/apis/employee'
|
||||
import type { CertificationInfo, CertificationListParams, SimpleUserInfo } from '@/apis/employee'
|
||||
import { uploadFile } from '@/apis/common/common'
|
||||
|
@ -188,7 +174,7 @@ const userList = ref<SimpleUserInfo[]>([])
|
|||
const searchForm = reactive<CertificationListParams>({
|
||||
certificationName: '',
|
||||
certificationType: '',
|
||||
userName: '',
|
||||
userName: ''
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
|
@ -197,7 +183,7 @@ const paginationConfig = reactive({
|
|||
pageSize: 10,
|
||||
total: 0,
|
||||
showTotal: true,
|
||||
showJumper: true,
|
||||
showJumper: true
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
|
@ -215,23 +201,23 @@ const formData = reactive<CertificationInfo>({
|
|||
// 表单验证规则
|
||||
const formRules = {
|
||||
certificationCode: [
|
||||
{ required: true, message: '请输入证书编号' },
|
||||
{ required: true, message: '请输入证书编号' }
|
||||
],
|
||||
certificationName: [
|
||||
{ required: true, message: '请输入证书名称' },
|
||||
{ required: true, message: '请输入证书名称' }
|
||||
],
|
||||
certificationType: [
|
||||
{ required: true, message: '请输入证书类型' },
|
||||
{ required: true, message: '请输入证书类型' }
|
||||
],
|
||||
userId: [
|
||||
{ required: true, message: '请选择持证人' },
|
||||
{ required: true, message: '请选择持证人' }
|
||||
],
|
||||
validityDateBegin: [
|
||||
{ required: true, message: '请选择有效期开始日期' },
|
||||
{ required: true, message: '请选择有效期开始日期' }
|
||||
],
|
||||
validityDateEnd: [
|
||||
{ required: true, message: '请选择有效期结束日期' },
|
||||
],
|
||||
{ required: true, message: '请选择有效期结束日期' }
|
||||
]
|
||||
}
|
||||
const CERTIFICATION_TYPE_OPTIONS = [
|
||||
{ label: '高处作业', value: 'height-operation' },
|
||||
|
@ -240,69 +226,69 @@ const CERTIFICATION_TYPE_OPTIONS = [
|
|||
{ label: '海上交通安全', value: 'maritime-traffic-safety' },
|
||||
{ label: '驾驶证', value: 'driving-license' },
|
||||
{ label: '防雷检测', value: 'lightning-protection-detection' },
|
||||
{ label: '无人机驾驶', value: 'drone-driving' },
|
||||
{ label: '无人机驾驶', value: 'drone-driving' }
|
||||
]
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '证书编号',
|
||||
dataIndex: 'certificationCode',
|
||||
width: 150,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '证书名称',
|
||||
dataIndex: 'certificationName',
|
||||
width: 200,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '证书类型',
|
||||
dataIndex: 'certificationType',
|
||||
width: 150,
|
||||
render: ({ record }) => {
|
||||
const type = CERTIFICATION_TYPE_OPTIONS.find((item) => item.value === record.certificationType)
|
||||
const type = CERTIFICATION_TYPE_OPTIONS.find(item => item.value === record.certificationType)
|
||||
return type ? type.label : '-'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '持证人',
|
||||
dataIndex: 'userName',
|
||||
slotName: 'userName',
|
||||
width: 120,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '证书图片',
|
||||
dataIndex: 'certificationImage',
|
||||
slotName: 'certificationImage',
|
||||
width: 100,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '有效期',
|
||||
dataIndex: 'validityPeriod',
|
||||
slotName: 'validityPeriod',
|
||||
width: 200,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'actions',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
},
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取用户名称
|
||||
const getUserName = (userId: string) => {
|
||||
const user = userList.value.find((u) => u.userId === userId)
|
||||
const user = userList.value.find(u => u.userId === userId)
|
||||
return user ? user.name : '-'
|
||||
}
|
||||
|
||||
// 用户搜索过滤
|
||||
const filterUserOption = (inputValue: string, option: any) => {
|
||||
const user = userList.value.find((u) => u.userId === option.value)
|
||||
const user = userList.value.find(u => u.userId === option.value)
|
||||
if (!user) return false
|
||||
|
||||
const searchText = inputValue.toLowerCase()
|
||||
return user.name.toLowerCase().includes(searchText)
|
||||
|| user.account.toLowerCase().includes(searchText)
|
||||
return user.name.toLowerCase().includes(searchText) ||
|
||||
user.account.toLowerCase().includes(searchText)
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
|
@ -314,7 +300,7 @@ const getUserList = async () => {
|
|||
console.log('用户列表响应:', response)
|
||||
|
||||
if (response.data) {
|
||||
userList.value = response.data.map((item) => ({
|
||||
userList.value = response.data.map(item => ({
|
||||
userId: item.userId,
|
||||
name: item.name,
|
||||
account: item.account,
|
||||
|
@ -401,7 +387,7 @@ const handleReset = () => {
|
|||
Object.assign(searchForm, {
|
||||
certificationName: '',
|
||||
certificationType: '',
|
||||
userName: '',
|
||||
userName: ''
|
||||
})
|
||||
paginationConfig.current = 1
|
||||
getCertificationList()
|
||||
|
@ -443,7 +429,7 @@ const deleteRecord = (record: CertificationInfo) => {
|
|||
console.error('删除失败:', error)
|
||||
Message.error('删除失败')
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -485,7 +471,7 @@ const resetForm = () => {
|
|||
certificationType: '',
|
||||
userId: '',
|
||||
validityDateBegin: '',
|
||||
validityDateEnd: '',
|
||||
validityDateEnd: ''
|
||||
})
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
@ -494,7 +480,7 @@ const resetForm = () => {
|
|||
const init = async () => {
|
||||
await Promise.all([
|
||||
getUserList(),
|
||||
getCertificationList(),
|
||||
getCertificationList()
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<a-space>
|
||||
<a-link @click="viewDetail(record)">详情</a-link>
|
||||
<a-link @click="editRecord(record)">编辑</a-link>
|
||||
<a-link v-if="record.status === 'draft'" @click="confirmSalary(record)">确认</a-link>
|
||||
<a-link @click="confirmSalary(record)" v-if="record.status === 'draft'">确认</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
@ -66,18 +66,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
let searchForm = reactive({
|
||||
userName: '',
|
||||
deptName: '',
|
||||
salaryMonth: '',
|
||||
status: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
|
||||
// 查询条件配置
|
||||
|
@ -87,16 +87,16 @@ const queryFormColumns = [
|
|||
label: '员工姓名',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入员工姓名',
|
||||
},
|
||||
placeholder: '请输入员工姓名'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'deptName',
|
||||
label: '部门',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入部门名称',
|
||||
},
|
||||
placeholder: '请输入部门名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
|
@ -107,10 +107,10 @@ const queryFormColumns = [
|
|||
options: [
|
||||
{ label: '草稿', value: 'draft' },
|
||||
{ label: '已确认', value: 'confirmed' },
|
||||
{ label: '已发放', value: 'paid' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ label: '已发放', value: 'paid' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
|
@ -132,7 +132,7 @@ const tableColumns: TableColumnData[] = [
|
|||
{ title: '其他扣除', dataIndex: 'otherDeduction', width: 100 },
|
||||
{ title: '实发工资', dataIndex: 'netSalary', slotName: 'netSalary', width: 120 },
|
||||
{ title: '状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '操作', slotName: 'action', width: 150, fixed: 'right' },
|
||||
{ title: '操作', slotName: 'action', width: 150, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
|
@ -156,7 +156,7 @@ const dataList = ref([
|
|||
housingFund: 960,
|
||||
otherDeduction: 0,
|
||||
netSalary: 14895,
|
||||
status: 'confirmed',
|
||||
status: 'confirmed'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -176,7 +176,7 @@ const dataList = ref([
|
|||
housingFund: 880,
|
||||
otherDeduction: 0,
|
||||
netSalary: 13245,
|
||||
status: 'paid',
|
||||
status: 'paid'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
@ -196,8 +196,8 @@ const dataList = ref([
|
|||
housingFund: 640,
|
||||
otherDeduction: 0,
|
||||
netSalary: 9215,
|
||||
status: 'draft',
|
||||
},
|
||||
status: 'draft'
|
||||
}
|
||||
])
|
||||
|
||||
const pagination = reactive({
|
||||
|
@ -205,15 +205,15 @@ const pagination = reactive({
|
|||
pageSize: 10,
|
||||
total: 3,
|
||||
showTotal: true,
|
||||
showPageSize: true,
|
||||
showPageSize: true
|
||||
})
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
draft: 'orange',
|
||||
confirmed: 'blue',
|
||||
paid: 'green',
|
||||
'draft': 'orange',
|
||||
'confirmed': 'blue',
|
||||
'paid': 'green'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
@ -221,9 +221,9 @@ const getStatusColor = (status: string) => {
|
|||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
confirmed: '已确认',
|
||||
paid: '已发放',
|
||||
'draft': '草稿',
|
||||
'confirmed': '已确认',
|
||||
'paid': '已发放'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ const reset = () => {
|
|||
salaryMonth: '',
|
||||
status: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 10
|
||||
})
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue