diff --git a/src/apis/data/index.ts b/src/apis/data/index.ts new file mode 100644 index 0000000..cec076f --- /dev/null +++ b/src/apis/data/index.ts @@ -0,0 +1,188 @@ +// @/apis/data/index.ts - 数据管理API +import http from '@/utils/http' +import type { + FolderInfo, + FileInfo, + FolderListParams, + FileListParams, + FolderListResponse, + FileListResponse, + CreateFolderParams, + RenameFolderParams, + DeleteFolderParams, + UploadFileParams, + DownloadFileParams, + DeleteFileParams, + PreviewFileParams, + RenameFileParams +} from './type' + +const { request, requestRaw } = http + +// 导出类型定义 +export type { + FolderInfo, + FileInfo, + FolderListParams, + FileListParams, + FolderListResponse, + FileListResponse, + CreateFolderParams, + RenameFolderParams, + DeleteFolderParams, + UploadFileParams, + DownloadFileParams, + DeleteFileParams, + PreviewFileParams, + RenameFileParams +} + +// 获取文件夹列表(分页) +export function getFolderListApi(params?: FolderListParams) { + return request({ + url: '/data/folder/list', + method: 'get', + params: { + page: params?.page || 1, + pageSize: params?.pageSize || 10, + folderName: params?.folderName + } + }) +} + +// 获取文件列表(分页) +export function getFilesApi(params?: FileListParams) { + return request({ + url: '/data/file/list', + method: 'get', + params: { + page: params?.page || 1, + pageSize: params?.pageSize || 10, + folderId: params?.folderId || '0', + fileName: params?.fileName, + sortField: params?.sortField, + sortOrder: params?.sortOrder + } + }) +} + +// 创建文件夹 +export function createFolderApi(data: CreateFolderParams) { + return request({ + url: '/data/folder/creatFolder', + method: 'post', + data: { + name: data.name, + parentId: data.parentId || '0' + } + }) +} + +// 重命名文件夹 +export function updateFolderApi(folderId: string, newName: string) { + return request({ + url: '/data/folder/rename', + method: 'put', + params: { + folderId: folderId, + newName: newName + } + }) +} + +// 删除文件夹 +export function deleteFolderApi(folderId: string) { + return request({ + url: '/data/folder/delete', + method: 'delete', + params: { + folderId: folderId + } + }) +} + +// 上传文件 +export function uploadFileApi( + file: File, + folderId: string, + onUploadProgress?: (progressEvent: any) => void, + cancelToken?: any +) { + const formData = new FormData() + formData.append('file', file) + + return requestRaw({ + url: '/data/file/add', + method: 'post', + params: { + folderId: folderId + }, + data: formData, + onUploadProgress, + cancelToken, + headers: { + 'Content-Type': 'multipart/form-data' + } + }).then(response => response.data) + .catch(error => { + // 确保错误不会抛出,而是返回一个错误对象 + console.error('上传文件API错误:', error) + return { + code: 500, + msg: error.message || '上传失败', + success: false + } + }) +} + +// 下载文件 +export function downloadFileApi(fileId: string) { + return request({ + url: '/data/file/download', + method: 'get', + params: { + fileId: fileId + }, + responseType: 'blob' + }) +} + +// 删除文件 +export function deleteFileApi(fileId: string) { + return request({ + url: '/data/file/delete', + method: 'delete', + params: { + fileId: fileId + } + }) +} + +// 预览文件 +export function previewFileApi(fileId: string) { + return request({ + url: '/data/file/preview', + method: 'get', + params: { + fileId: fileId + }, + responseType: 'blob' + }) +} + +// 重命名文件 +export function renameFileApi(fileId: string, newFileName: string) { + return request({ + url: '/data/file/rename', + method: 'put', + params: { + fileId: fileId, + newFileName: newFileName + } + }) +} + +// 重命名文件(兼容旧接口) +export function updateFileNameApi(fileId: string, data: RenameFileParams) { + return renameFileApi(fileId, data.newFileName) +} \ No newline at end of file diff --git a/src/apis/data/type.ts b/src/apis/data/type.ts new file mode 100644 index 0000000..b5aa836 --- /dev/null +++ b/src/apis/data/type.ts @@ -0,0 +1,98 @@ +/** 文件夹信息接口 */ +export interface FolderInfo { + folderId: string + name: string + parentId: string + createTime?: string + updateTime?: string + } + + /** 文件信息接口 */ + export interface FileInfo { + fileId: string + fileName: string + fileSize: number + fileType: string + folderId: string + createTime?: string + updateTime?: string + } + + /** 文件夹列表查询参数 */ + export interface FolderListParams { + page?: number + pageSize?: number + folderName?: string + } + + /** 文件列表查询参数 */ + export interface FileListParams { + page?: number + pageSize?: number + folderId?: string + fileName?: string + sortField?: string + sortOrder?: string + } + + /** 文件夹列表响应 */ + export interface FolderListResponse { + data: FolderInfo[] + total: number + current: number + size: number + } + + /** 文件列表响应 */ + export interface FileListResponse { + data: FileInfo[] + total: number + current: number + size: number + } + + /** 创建文件夹请求参数 */ + export interface CreateFolderParams { + name: string + parentId?: string + } + + /** 重命名文件夹请求参数 */ + export interface RenameFolderParams { + folderId: string + newName: string + } + + /** 删除文件夹请求参数 */ + export interface DeleteFolderParams { + folderId: string + } + + /** 上传文件请求参数 */ + export interface UploadFileParams { + file: File + folderId: string + onUploadProgress?: (progressEvent: any) => void + cancelToken?: any + } + + /** 下载文件请求参数 */ + export interface DownloadFileParams { + fileId: string + } + + /** 删除文件请求参数 */ + export interface DeleteFileParams { + fileId: string + } + + /** 预览文件请求参数 */ + export interface PreviewFileParams { + fileId: string + } + + /** 重命名文件请求参数 */ + export interface RenameFileParams { + fileId: string + newFileName: string + } \ No newline at end of file diff --git a/src/apis/equipment/type.ts b/src/apis/equipment/type.ts index f9bf6d9..1e2403f 100644 --- a/src/apis/equipment/type.ts +++ b/src/apis/equipment/type.ts @@ -457,6 +457,7 @@ export interface ReceiptRequest { useStatus?: string healthStatus?: string receiptStatus?: string + paymentStatus?: string // 其他管理信息 depreciationMethod?: string diff --git a/src/apis/index.ts b/src/apis/index.ts index 8218898..c39615e 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -20,6 +20,7 @@ export * as RegulationAPI from './regulation' export * as TrainingAPI from './training' export * as EquipmentAPI from './equipment' export * as BussinessAPI from './bussiness/bussiness' +export * as DataAPI from './data' export * from './area/type' export * from './auth/type' diff --git a/src/apis/project/index.ts b/src/apis/project/index.ts index bde6b8a..3471f0c 100644 --- a/src/apis/project/index.ts +++ b/src/apis/project/index.ts @@ -15,6 +15,12 @@ export function getProject(id: string | number) { return http.get(`${BASE_URL}/${id}`) } +/** @desc 获取项目详情(标准详情接口) */ +export function getProjectDetail(id: string | number) { + return http.get(`${BASE_URL}/detail/${id}`) +} + + /** @desc 新增项目 */ export function addProject(data: any) { return http.post(`${BASE_URL}`, data) @@ -49,4 +55,4 @@ export function importProject(file: File) { /** @desc 导出项目 */ export function exportProject(query: T.ProjectQuery) { return http.download(`${BASE_URL}/export`, query) -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/components/NotificationCenter/index.vue b/src/components/NotificationCenter/index.vue index 7fb4ccf..7230f61 100644 --- a/src/components/NotificationCenter/index.vue +++ b/src/components/NotificationCenter/index.vue @@ -19,7 +19,7 @@ :mask-closable="true" :closable="true" :destroy-on-close="false" - :z-index="999999" + :z-index="1000" class="notification-modal" > @@ -673,7 +673,7 @@ defineExpose({ diff --git a/src/router/route.ts b/src/router/route.ts index c7279f4..c632539 100644 --- a/src/router/route.ts +++ b/src/router/route.ts @@ -906,12 +906,61 @@ export const systemRoutes: RouteRecordRaw[] = [ }, ], }, - // start + + { + path: '/construction-operation-platform', + name: 'ConstructionOperationPlatform', + component: Layout, + redirect: '/construction-operation-platform/implementation-workflow/field-construction', + meta: { title: '我的工作台', icon: 'tool', hidden: false, sort: 5 }, + children: [ + // { + // path: '/construction-operation-platform/implementation-workflow', + // name: 'ImplementationWorkflow', + // component: () => import('@/components/ParentView/index.vue'), + // redirect: '/construction-operation-platform/implementation-workflow/field-construction', + // meta: { title: '项目实施工作流程', icon: 'fork', hidden: false }, + // children: [ + { + path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment', + name: 'AttachmentManagement', + 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/model-config', + name: 'ModelConfig', + component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'), + meta: { title: '模型配置', icon: 'robot', hidden: false }, + }, + { + path: '/construction-operation-platform/implementation-workflow/field-construction', + name: 'FieldConstruction', + component: () => import('@/components/ParentView/index.vue'), + redirect: '/construction-operation-platform/implementation-workflow/field-construction/project-list', + meta: { title: '我的项目', icon: 'construction', hidden: false }, + children: [ + { + path: '/project-management/projects/list', + name: 'ProjectList', + component: () => import('@/views/project-management/projects/list/index.vue'), + meta: { + title: '项目列表', + icon: 'unordered-list', + hidden: false, + }, + }, + { + path: '/construction-operation-platform/implementation-workflow/field-construction/technology', + name: 'FieldConstructionTechnology', + component: () => import('@/views/project-operation-platform/implementation-workflow/field-construction/project-list/index.vue'), + meta: { title: '我的施工', icon: 'tool', hidden: false }, + }, + // start // 数据管理 { path: '/data-management', name: 'DataManagement', - component: Layout, redirect: '/data-management/project-management/project-template', meta: { title: '数据管理', icon: 'database', hidden: false, sort: 4 }, children: [ @@ -1027,55 +1076,6 @@ export const systemRoutes: RouteRecordRaw[] = [ ], }, // end - { - path: '/construction-operation-platform', - name: 'ConstructionOperationPlatform', - component: Layout, - redirect: '/construction-operation-platform/implementation-workflow/field-construction', - meta: { title: '我的工作台', icon: 'tool', hidden: false, sort: 5 }, - children: [ - // { - // path: '/construction-operation-platform/implementation-workflow', - // name: 'ImplementationWorkflow', - // component: () => import('@/components/ParentView/index.vue'), - // redirect: '/construction-operation-platform/implementation-workflow/field-construction', - // meta: { title: '项目实施工作流程', icon: 'fork', hidden: false }, - // children: [ - { - path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment', - name: 'AttachmentManagement', - 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/model-config', - name: 'ModelConfig', - component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'), - meta: { title: '模型配置', icon: 'robot', hidden: false }, - }, - { - path: '/construction-operation-platform/implementation-workflow/field-construction', - name: 'FieldConstruction', - component: () => import('@/components/ParentView/index.vue'), - redirect: '/construction-operation-platform/implementation-workflow/field-construction/project-list', - meta: { title: '我的项目', icon: 'construction', hidden: false }, - children: [ - { - path: '/project-management/projects/list', - name: 'ProjectList', - component: () => import('@/views/project-management/projects/list/index.vue'), - meta: { - title: '项目列表', - icon: 'unordered-list', - hidden: false, - }, - }, - { - path: '/construction-operation-platform/implementation-workflow/field-construction/technology', - name: 'FieldConstructionTechnology', - component: () => import('@/views/project-operation-platform/implementation-workflow/field-construction/project-list/index.vue'), - meta: { title: '我的施工', icon: 'tool', hidden: false }, - }, { path: '/construction-operation-platform/implementation-workflow/project-delivery', name: 'ProjectDelivery', @@ -1184,7 +1184,7 @@ export const systemRoutes: RouteRecordRaw[] = [ name: 'bussinesskonwledge', component: Layout, redirect: '/bussiness-knowledge/data', - meta: { title: '商务资料知识库', icon: 'database', hidden: false, sort: 5.5 }, + meta: { title: '智能商务', icon: 'database', hidden: false, sort: 5.5 }, children: [ { path: '/bussiness-konwledge/data', @@ -1198,47 +1198,162 @@ export const systemRoutes: RouteRecordRaw[] = [ }, ], }, + // 数据管理模块 { - path: '/chat-platform', - name: 'ChatPlatform', + path: '/data-management', + name: 'dataManagement', component: Layout, - redirect: '/chat-platform/options', - meta: { title: '聊天平台', icon: 'message', hidden: false, sort: 6 }, + redirect: '/data-management/data', + meta: { title: '数据管理', icon: 'database', hidden: false, sort: 5.6 }, children: [ - // { - // path: '/chat-platform/options', - // name: 'ChatOptions', - // component: () => import('@/views/default/redirect/index.vue'), // 临时使用一个组件,实际开发中需要替换 - // meta: { - // title: '二级选项1', - // icon: 'setting', - // hidden: false - // } - // } + { + path: '/data-management/data', + name: 'data-management', + component: () => import('@/views/data/data.vue'), + meta: { + title: '数据管理', + icon: 'database', + hidden: false, + }, + }, ], }, + { + path: '/image-detection', + name: 'ImageDetection', + component: Layout, + redirect: '/Image-detection/tower-monitoring/clearance-monitoring', + meta: { + title: '图像检测', + icon: 'monitor', + hidden: false, + sort: 6.5, + }, + + children: [ + { + path: '/image-detection/image-analysis', + name: 'ImageAnalysis', + component: () => import('@/components/ParentView/index.vue'), + meta: { + title: '检查图像分析', + icon: 'line-chart', + hidden: false, + }, + redirect: '/image-detection/image-analysis/defect-detection', + children: [ + {path: '/image-detection/image-analysis/defect-detection', + name: 'DefectDetection', + component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection/defect-algorithm/index.vue'), + meta: { + title: '缺陷检测', + icon: 'line-chart', + hidden: false, + } + }, + {path: '/image-detection/image-analysis/defect-edit', + name: 'DefectEdit', + component: () => import('@/components/ParentView/index.vue'), + meta: { + title: '缺陷编辑', + icon: 'line-chart', + hidden: false, + } + }, + {path: '/image-detection/image-analysis/generate-reports', + name: 'GenerateReports', + component: () => import('@/views/project-operation-platform/data-processing/report-generation/index.vue'), + meta: { + title: '生成报告', + icon: 'line-chart', + hidden: false, + } + }, + {path: '/image-detection/image-analysis/defect-base', + name: 'DefectBase', + component: () => import('@/views/project-operation-platform/data-processing/standard-info/index.vue'), + meta: { + title: '缺陷标准数据信息库', + icon: 'line-chart', + hidden: false, + } + }, + ] + }, + { + path: '/tower-monitoring', + name: 'TowerMonitoring', + component: () => import('@/components/ParentView/index.vue'), + redirect: '/tower-monitoring/clearance-monitoring', + meta: { + title: '音视频检测', + icon: 'monitor', + hidden: false, + sort: 6.5, + }, + children: [ + { + path: '/tower-monitoring/clearance-monitoring', + name: 'ClearanceMonitoring', + component: () => import('@/views/tower-monitoring/deformation.vue'), + meta: { + title: '净空监测', + icon: 'fullscreen', + hidden: false, + }, + }, + { + path: '/tower-monitoring/deformation-monitoring', + name: 'DeformationMonitoring', + component: () => import('@/views/tower-monitoring/clearance.vue'), + meta: { + title: '形变监测', + icon: 'line-chart', + hidden: false, + }, + }, + { + path: '/tower-monitoring/whistle-monitoring', + name: 'WhistleMonitoring', + component: () => import('@/views/tower-monitoring/whistle.vue'), + meta: { + title: '哨声监测', + icon: 'sound', + hidden: false, + }, + }, + { + path: '/tower-monitoring/vibration-monitoring', + name: 'VibrationMonitoring', + component: () => import('@/views/tower-monitoring/vibration.vue'), + meta: { + title: '振动监测', + icon: 'shake', + hidden: false, + }, + }, + ], + }, + { + path: '/image-detection/reporting-center', + name: 'ReportingCenter', + component: () => import('@/views/tower-monitoring/vibration.vue'), + meta: { + title: '报告中心', + icon: 'shake', + hidden: false, + }, + } + ] +}, + // { - // path: '/user/profile', - // name: 'UserProfile', + // path: '/chat-platform', + // name: 'ChatPlatform', // component: Layout, - // redirect: '/user/profile', - // meta: { - // title: '个人中心', - // icon: 'user', - // hidden: false, - // sort: 100, - // }, + // redirect: '/chat-platform/options', + // meta: { title: '聊天平台', icon: 'message', hidden: false, sort: 6 }, // children: [ - // { - // path: '/user/profile', - // name: 'UsersProfile', - // component: () => import('@/views/user/profile/index.vue'), - // meta: { - // title: '个人中心', - // icon: 'user', - // hidden: false, - // }, - // }, // ], // }, { diff --git a/src/views/bussiness-data/bussiness.vue b/src/views/bussiness-data/bussiness.vue index 550c414..9cebdba 100644 --- a/src/views/bussiness-data/bussiness.vue +++ b/src/views/bussiness-data/bussiness.vue @@ -2,7 +2,7 @@ - - + - - + + + + + + 上传文件 + + + + 新建文件夹 + + - + +
+
+ 文件列表 ({{ fileList.length }}) +
+
+ +
+
+ + + + + + + + + + + +
+ + + +
+ 文件名 +
+
+
+
+
+
+ +
+ 类型 +
+
+
+
+
+
+ +
+ 大小 +
+
+
+
+
+
+ +
+ 修改时间 +
+
+
+
+
+
+ 操作 +
+ + + + + +
+ +
+ {{ file.fileName || file.name }} +
{{ file.fileName || file.name }}
+
+
+
+ + + +
{{ fileTypeText(getFileExtension(file.fileName || file.name)) }}
+
+ + + +
{{ formatFileListSize(file.fileSize || file.size) }}
+
+ + + +
{{ formatUploadTime(file.uploadTime || file.uploadTime) }}
+
+ + + +
+ + + + + + + + + + + + +
+
+
+
- +
+ +
+ + + + + +
@@ -192,13 +408,128 @@ - - + + title="上传文件" + width="620px" + :mask-closable="false" + @ok="handleUploadSubmit" + @cancel="resetUpload" + :confirm-loading="uploading" + :ok-disabled="!canUpload" + > + + + +
+ + + + + 点击选择文件 + + + + +
+ 支持 {{ allowedFileTypesText }} 等格式,单个文件不超过 {{ maxFileSizeText }} +
+
+ + +
+
+
+ +
+
{{ file.name }}
+
+ {{ formatFileSize(file.size) }} + {{ file.error }} +
+
+
+ + +
+ +
+ + +
+ + + + + + +
+
+
+
+ + + + + 根目录 + + {{ folder.name }} + + + +
+
// 导入核心依赖 import { ref, reactive, onMounted, computed, watch, nextTick, h } from 'vue'; -// 导入子组件 -import FilePagination from './components/FilePagination.vue'; -import FileHeader from './components/FileHeader.vue'; -import FileList from './components/FileList.vue'; -import FileUpload from './components/FileUpload.vue'; import { IconFolder, IconFile, IconPlus, - + IconUpload, IconMenuFold, IconMenuUnfold, IconEye, @@ -263,10 +589,10 @@ import { IconRefresh, IconEdit, IconFolderAdd, - + IconStop } from '@arco-design/web-vue/es/icon'; import { Message, Modal } from '@arco-design/web-vue'; - +import axios from 'axios'; // 导入API import { @@ -277,7 +603,7 @@ import { deleteFolderApi, deleteFileApi, downloadFileApi, - + uploadFileApi, updateFileNameApi, renameFileApi, previewFileApi @@ -330,10 +656,47 @@ const folderRules = { ] }; +// 上传相关状态 +const uploadForm = reactive({ + folderId: '' +}); +const fileListTemp = ref([]); const folderFormRef = ref(null); +const uploadFormRef = ref(null); +const uploadRef = ref(null); const folderColor = 'var(--color-primary)'; const refreshing = ref(false); const folderSubmitting = ref(false); +const uploading = ref(false); +const allowedFileTypes = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip,.txt,.jpg,.jpeg,.png,.gif,.bmp,.webp'; +const allowedFileTypesText = 'PDF, Word, Excel, PPT, 压缩文件, 文本文件, 图片文件'; +const maxFileSize = 1000 * 1024 * 1024; // 1000MB +const maxFileSizeText = '1000MB'; +const cancelTokens = ref({}); + +// 计算属性:是否有文件可上传 +const hasFiles = computed(() => { + console.log('=== hasFiles计算属性执行 ==='); + console.log('原始fileListTemp:', fileListTemp.value); + console.log('fileListTemp长度:', fileListTemp.value.length); + + const validFiles = fileListTemp.value.filter(file => { + const isValid = !file.error && file.status !== 'removed' && file.status !== 'canceled'; + console.log(`文件 ${file.name}: error=${file.error}, status=${file.status}, isValid=${isValid}`); + return isValid; + }); + + console.log('过滤后的有效文件:', validFiles); + console.log('有效文件数量:', validFiles.length); + console.log('hasFiles结果:', validFiles.length > 0); + + return validFiles.length > 0; +}); + +// 计算属性:是否可以上传 +const canUpload = computed(() => { + return hasFiles.value && !uploading.value && uploadForm.folderId; +}); // 计算属性:将平铺的文件夹数据转换为树形结构 const folderTreeData = computed(() => { @@ -668,12 +1031,8 @@ const loadFiles = async (folderId) => { if (sortField.value && sortOrder.value) { apiParams.sortField = sortField.value; apiParams.sortOrder = sortOrder.value; - console.log('📤 发送排序参数:', apiParams.sortField, apiParams.sortOrder); - } else { - console.log('📤 未发送排序参数'); } - console.log('📤 发送API参数:', apiParams); const res = await getFilesApi(apiParams); // 根据后端返回的数据结构处理 @@ -698,34 +1057,21 @@ const loadFiles = async (folderId) => { // 排序处理函数 const handleSortChange = (field) => { - console.log('=== 排序处理函数被调用 ==='); - console.log('传入的字段:', field); - const backendField = sortFieldMap[field]; - console.log('映射后的后端字段:', backendField); - if (!backendField) { - console.log('❌ 找不到对应的后端字段,退出'); - return; - } - - console.log('当前排序字段:', sortField.value); - console.log('当前排序方向:', sortOrder.value); + if (!backendField) return; // 切换排序方向 if (sortField.value === backendField) { sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'; - console.log('✅ 切换排序方向为:', sortOrder.value); } else { // 新字段,默认降序 sortField.value = backendField; sortOrder.value = 'desc'; - console.log('✅ 设置新排序字段:', sortField.value, '排序方向:', sortOrder.value); } // 重新加载文件列表 if (currentFolderId.value) { - console.log('🔄 重新加载文件列表,文件夹ID:', currentFolderId.value); loadFiles(currentFolderId.value); } }; @@ -903,7 +1249,7 @@ const handleRenameFolder = (folder) => { } const folderId = folder.key || folder.id; - let currentName = folder.title || folder.name; + const currentName = folder.title || folder.name; if (!folderId) { Message.error('文件夹ID不能为空'); @@ -1080,9 +1426,159 @@ const submitFolderForm = async () => { } }; +// 格式化上传时间 +const formatUploadTime = (timeStr) => { + if (!timeStr) return '未知时间'; + const date = new Date(timeStr); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); +}; +// 文件变化处理 +const handleFileChange = (info) => { + console.log('=== 文件变化事件 ==='); + console.log('完整info对象:', info); + + // 安全检查:确保 info 存在且是数组 + if (!info || !Array.isArray(info)) { + console.log('❌ info 不存在或不是数组,跳过处理'); + return; + } + + const fileList = info; + console.log('文件列表:', fileList); + console.log('文件列表长度:', fileList.length); + + // 检查是否是组件内部状态触发的(可能是之前的状态) + if (fileList.length === 0) { + console.log('⚠️ 文件列表为空,可能是组件内部状态,跳过处理'); + return; + } + + // 检查是否是对话框刚打开时的触发(可能是之前的状态) + if (!uploadDialogVisible.value) { + console.log('⚠️ 对话框未显示,可能是之前的状态触发,跳过处理'); + return; + } + + // 获取当前已存在的文件UID列表,用于去重 + const existingUids = fileListTemp.value.map(f => f.uid); + console.log('已存在的文件UID:', existingUids); + + // 获取当前文件夹中已存在的文件名列表,用于检查重复 + const currentFolderFiles = fileList.value || []; + const existingFileNames = currentFolderFiles.map(f => f.fileName || f.name); + console.log('当前文件夹中的文件:', existingFileNames); + + // 强制重置上传组件状态 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + console.log('已强制重置上传组件'); + } catch (error) { + console.log('重置上传组件时出错:', error); + } + } + + // 处理新选择的文件 - 支持多文件选择,同时避免重复 + fileList.forEach((file, index) => { + console.log(`处理第${index + 1}个文件:`, file); + console.log('文件名称:', file.name); + console.log('文件大小:', file.size); + console.log('文件UID:', file.uid); + console.log('文件对象结构:', Object.keys(file)); + + // 检查文件是否已存在(去重) + if (existingUids.includes(file.uid)) { + console.log('⚠️ 文件已存在,跳过:', file.name); + return; + } + + // 确保文件对象有正确的属性 + const fileObj = { + uid: file.uid, + name: file.name, + size: file.size || file.file?.size || 0, + type: file.type || file.file?.type || '', + status: 'ready', + error: '', + originFileObj: file.file || file // 保存原始File对象 + }; + + console.log('开始验证新文件...'); + + // 检查文件是否已存在于当前文件夹中 + if (existingFileNames.includes(fileObj.name)) { + fileObj.error = '文件已存在于当前文件夹中'; + console.log('⚠️ 文件已存在于文件夹中:', fileObj.name); + // 显示友好的提示信息 + Message.warning(`文件 "${fileObj.name}" 已存在于当前文件夹中,已跳过`); + return; + } + + // 验证文件 + const isValid = validateFile(fileObj); + console.log('文件验证结果:', isValid); + + if (isValid) { + // 支持多文件:添加到列表 + fileListTemp.value.push(fileObj); + console.log('✅ 成功添加文件到列表:', fileObj.name); + } else { + console.log('❌ 文件验证失败:', fileObj.name, '错误:', fileObj.error); + } + }); + + console.log('=== 当前文件列表状态 ==='); + console.log('fileListTemp长度:', fileListTemp.value.length); + console.log('fileListTemp内容:', fileListTemp.value); + console.log('hasFiles计算结果:', hasFiles.value); +}; - +// 文件验证 +const validateFile = (file) => { + console.log('=== 开始验证文件 ==='); + console.log('验证文件:', file.name); + console.log('文件大小:', file.size); + + // 清除之前的错误 + file.error = ''; + + // 验证文件类型 + const ext = getFileExtension(file.name).toLowerCase(); + console.log('文件扩展名:', ext); + + const allowedExts = allowedFileTypes + .split(',') + .map(type => type.toLowerCase().replace(/^\./, '')); + + console.log('允许的扩展名:', allowedExts); + console.log('扩展名是否匹配:', allowedExts.includes(ext)); + + if (!allowedExts.includes(ext)) { + file.error = `不支持的文件类型,支持: ${allowedFileTypesText}`; + console.log('❌ 文件类型验证失败:', file.error); + return false; + } + + // 验证文件大小 + console.log('文件大小验证:', file.size, '<=', maxFileSize); + if (file.size > maxFileSize) { + file.error = `文件过大,最大支持 ${maxFileSizeText}`; + console.log('❌ 文件大小验证失败:', file.error); + return false; + } + + console.log('✅ 文件验证通过'); + return true; +}; // 获取文件扩展名 const getFileExtension = (fileName) => { @@ -1090,6 +1586,157 @@ const getFileExtension = (fileName) => { return lastDotIndex > 0 ? fileName.slice(lastDotIndex + 1) : ''; }; +// 获取文件图标颜色 +const fileColor = (extension) => { + const colorMap = { + pdf: '#ff4d4f', + doc: '#1890ff', + docx: '#1890ff', + xls: '#52c41a', + xlsx: '#52c41a', + ppt: '#faad14', + pptx: '#faad14', + zip: '#722ed1', + txt: '#8c8c8c', + // 图片格式颜色 + jpg: '#52c41a', + jpeg: '#52c41a', + png: '#1890ff', + gif: '#faad14', + bmp: '#722ed1', + webp: '#13c2c2' + }; + return colorMap[extension.toLowerCase()] || 'var(--color-text-3)'; +}; + + + +// 移除文件 +const removeFile = (file) => { + fileListTemp.value = fileListTemp.value.filter(f => f.uid !== file.uid); + + // 如果是正在上传的文件,取消请求 + if (file.status === 'uploading' && cancelTokens.value[file.uid]) { + cancelTokens.value[file.uid].cancel('上传已取消'); + delete cancelTokens.value[file.uid]; + } +}; + +// 取消上传 +const cancelUpload = (file) => { + if (cancelTokens.value[file.uid]) { + cancelTokens.value[file.uid].cancel('上传已取消'); + file.status = 'canceled'; + } +}; + +// 提交上传 +const handleUploadSubmit = async () => { + // 过滤有效文件 + const validFiles = fileListTemp.value.filter(file => + !file.error && file.status !== 'removed' && file.status !== 'canceled' + ); + + if (validFiles.length === 0) { + Message.warning('请选择有效的文件'); + return; + } + + // 验证文件夹ID + if (!uploadForm.folderId) { + Message.warning('请选择目标文件夹'); + return; + } + + uploading.value = true; + let hasError = false; + let hasFileExists = false; + + for (const fileItem of validFiles) { + // 获取原始File对象 + const realFile = fileItem.originFileObj || fileItem; + + if (!realFile) { + hasError = true; + continue; + } + + fileItem.status = 'uploading'; + fileItem.percent = 0; + + // 创建取消令牌 + const source = axios.CancelToken.source(); + cancelTokens.value[fileItem.uid] = source; + + // 调用API + const result = await uploadFileApi( + realFile, + Number(uploadForm.folderId), + (progressEvent) => { + if (progressEvent.lengthComputable) { + fileItem.percent = Math.round((progressEvent.loaded / progressEvent.total) * 100); + } + }, + source.token + ); + + // 检查上传结果 + if (result.code === 200) { + fileItem.status = 'success'; + fileItem.percent = 100; + } else if (result.code === 400 && result.msg && result.msg.includes('已存在')) { + // 文件已存在的情况 + fileItem.status = 'error'; + fileItem.error = '文件已存在'; + hasFileExists = true; + } else { + fileItem.status = 'error'; + fileItem.error = result.msg || '上传失败'; + hasError = true; + } + } + + // 根据结果显示相应的消息 + if (hasFileExists && !hasError) { + Message.warning('文件已存在'); + } else if (hasError) { + Message.error('上传失败'); + } else { + Message.success('上传成功'); + // 刷新当前文件夹文件列表 + if (currentFolderId.value === uploadForm.folderId) { + loadFiles(currentFolderId.value); + } + } + + resetUpload(); +}; + +// 重置上传表单 +const resetUpload = () => { + console.log('=== 重置上传表单 ==='); + + // 取消所有正在进行的上传 + Object.values(cancelTokens.value).forEach(source => { + source.cancel('上传已取消'); + }); + + // 重置所有状态 + uploadDialogVisible.value = false; + uploadForm.folderId = currentFolderId.value || ''; + fileListTemp.value = []; + cancelTokens.value = {}; + uploading.value = false; + + // 清空上传组件 + if (uploadRef.value) { + uploadRef.value.reset(); + console.log('已重置上传组件'); + } + + console.log('上传表单重置完成'); +}; + // 预览文件 const handlePreview = async (file) => { try { @@ -1660,97 +2307,61 @@ const handleDelete = (file) => { }); }; +// 格式化文件大小 +const formatFileSize = (fileSize) => { + const size = Number(fileSize); + if (isNaN(size) || size < 0) return '未知'; + + if (size < 1024) return `${size} B`; + if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; + if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`; + return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`; +}; + +// 专门用于文件列表的格式化函数(假设后端返回的是KB单位) +const formatFileListSize = (fileSize) => { + const size = Number(fileSize); + if (isNaN(size) || size < 0) return '未知'; + + // 假设后端返回的是KB单位 + if (size < 1024) { + return `${size} KB`; + } else if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(1)} MB`; + } else { + return `${(size / (1024 * 1024)).toFixed(1)} GB`; + } +}; - - - - +const fileTypeText = (type) => { + const types = { + pdf: 'PDF文档', + doc: 'Word文档', + docx: 'Word文档', + xls: 'Excel表格', + xlsx: 'Excel表格', + ppt: 'PPT演示', + pptx: 'PPT演示', + zip: '压缩文件', + txt: '文本文件', + // 图片格式 + jpg: 'JPG图片', + jpeg: 'JPEG图片', + png: 'PNG图片', + gif: 'GIF图片', + bmp: 'BMP图片', + webp: 'WebP图片', + unknown: '未知类型' + }; + + return types[type] || type; +}; // 侧边栏控制 const sidebarCollapsed = ref(false); -// 侧边栏宽度控制 -const sidebarWidth = ref(260); // 默认宽度 -const isResizing = ref(false); -const startX = ref(0); -const startWidth = ref(0); - -// 从localStorage加载保存的宽度 -const loadSavedWidth = () => { - try { - const savedWidth = localStorage.getItem('bussiness-sidebar-width'); - if (savedWidth) { - const width = parseInt(savedWidth); - if (width >= 200 && width <= 500) { - sidebarWidth.value = width; - } - } - } catch (error) { - console.warn('加载侧边栏宽度失败:', error); - } -}; - -// 保存宽度到localStorage -const saveWidth = (width) => { - try { - localStorage.setItem('bussiness-sidebar-width', width.toString()); - } catch (error) { - console.warn('保存侧边栏宽度失败:', error); - } -}; - - - -// 开始拖拽调整大小 -const startResize = (event) => { - event.preventDefault(); - isResizing.value = true; - startX.value = event.type === 'mousedown' ? event.clientX : event.touches[0].clientX; - startWidth.value = sidebarWidth.value; - - // 添加事件监听器 - if (event.type === 'mousedown') { - document.addEventListener('mousemove', handleResize); - document.addEventListener('mouseup', stopResize); - } else { - document.addEventListener('touchmove', handleResize); - document.addEventListener('touchend', stopResize); - } - - // 添加样式 - document.body.classList.add('resizing'); -}; - -// 处理拖拽调整 -const handleResize = (event) => { - if (!isResizing.value) return; - - const currentX = event.type === 'mousemove' ? event.clientX : event.touches[0].clientX; - const deltaX = currentX - startX.value; - const newWidth = Math.max(200, Math.min(500, startWidth.value + deltaX)); - - sidebarWidth.value = newWidth; -}; - -// 停止拖拽调整 -const stopResize = () => { - isResizing.value = false; - - // 移除事件监听器 - document.removeEventListener('mousemove', handleResize); - document.removeEventListener('mouseup', stopResize); - document.removeEventListener('touchmove', handleResize); - document.removeEventListener('touchend', stopResize); - - // 移除样式 - document.body.classList.remove('resizing'); - - // 保存宽度 - saveWidth(sidebarWidth.value); -}; - // 打开新建文件夹对话框 const handleCreateFolder = () => { folderForm.id = ''; @@ -1761,15 +2372,27 @@ const handleCreateFolder = () => { // 打开上传文件对话框 const handleUploadFile = () => { - uploadDialogVisible.value = true; -}; - -// 上传成功回调 -const handleUploadSuccess = () => { - // 刷新当前文件夹文件列表 - if (currentFolderId.value) { - loadFiles(currentFolderId.value); + // 清空文件列表,避免显示之前上传的文件 + fileListTemp.value = []; + + // 重置上传组件状态 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + // 强制清空组件的内部文件列表 + if (uploadRef.value.fileList) { + uploadRef.value.fileList = []; + } + if (uploadRef.value.fileListTemp) { + uploadRef.value.fileListTemp = []; + } + } catch (error) { + console.log('重置上传组件时出错:', error); + } } + + uploadForm.folderId = currentFolderId.value || ''; + uploadDialogVisible.value = true; }; // 侧边栏控制函数 @@ -1788,13 +2411,43 @@ watch(currentFolderId, (newId) => { } }); - +// 监听上传对话框显示状态,确保文件列表清空 +watch(uploadDialogVisible, (visible) => { + if (visible) { + console.log('=== 上传对话框已显示,确保文件列表清空 ==='); + // 立即清空文件列表 + fileListTemp.value = []; + console.log('✅ 已清空文件列表'); + + // 强制重置上传组件 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + // 强制清空组件的内部文件列表 + if (uploadRef.value.fileList) { + uploadRef.value.fileList = []; + } + if (uploadRef.value.fileListTemp) { + uploadRef.value.fileListTemp = []; + } + console.log('✅ 已重置上传组件'); + } catch (error) { + console.log('❌ 重置上传组件时出错:', error); + } + } + + // 延迟再次清空,确保处理完所有可能的触发 + setTimeout(() => { + fileListTemp.value = []; + console.log('✅ 延迟清空文件列表'); + }, 100); + } +}); // 初始化加载 onMounted(() => { - loadSavedWidth(); // 加载保存的侧边栏宽度 initData(); }); @@ -1805,8 +2458,6 @@ onMounted(() => { background-color: var(--color-bg-1); } - - /* 侧边栏样式 */ .folder-sidebar { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); @@ -1952,7 +2603,22 @@ onMounted(() => { /* 删除旧的span样式,因为现在使用.folder-name */ +/* 顶部导航样式 */ +.file-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 24px; + background: var(--color-bg-1); + border-bottom: 1px solid var(--color-border); + height: 64px; +} +.breadcrumbs { + display: flex; + align-items: center; + gap: 8px; +} /* 文件内容区域样式 */ .file-content { @@ -1981,13 +2647,360 @@ onMounted(() => { padding-bottom: 80px; /* 为分页器留出空间 */ } +/* 表格容器 */ +.file-grid-container { + flex: 1; + width: 100%; + margin-top: 16px; + border-radius: 8px; + border: 1px solid var(--color-border); + overflow-y: auto; + background-color: var(--color-bg-1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + margin-bottom: 0; + min-height: 300px; + max-height: calc(100vh - 380px); /* 调整高度为分页器留出空间 */ +} +/* 表头行样式 */ +.table-header-row { + padding: 0 16px; + height: 48px; + line-height: 48px; + background-color: var(--color-fill-1); + border-bottom: 1px solid var(--color-border); + font-size: 13px; + color: var(--color-text-3); + font-weight: 500; +} +/* 数据行样式 */ +.table-data-row { + display: flex; + padding: 0 16px; + height: 64px; + align-items: center; + border-bottom: 1px solid var(--color-border); + transition: all 0.25s ease; + cursor: pointer; + background-color: var(--color-bg-1); + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: rgba(22, 93, 255, 0.1); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); + } +} +/* 通用列样式 */ +.table-column { + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + white-space: nowrap; + padding: 0 8px; +} +.cell-content { + display: inline-block; + width: 100%; + text-align: center; + justify-content: center; + align-items: center; +} +.name-column { + padding: 0 14px; + justify-content: flex-start !important; +} +.file-info { + display: flex; + align-items: center; + width: 100%; +} +.file-icon { + font-size: 20px; + margin-right: 12px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; +} + +.folder-icon { + color: var(--color-primary); + background-color: var(--color-primary-light-1); +} + +.file-name { + font-size: 14px; + font-weight: 500; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: color 0.2s ease; +} + +.table-data-row:hover .file-name { + color: var(--color-primary); +} + +.type-column, .size-column, .time-column { + color: var(--color-text-3); + font-size: 14px; + justify-content: center; + align-items: center; + padding: 4px; +} + +.action-column { + justify-content: center; +} + +.file-main { + display: flex; + align-items: center; + width: 100%; +} + +.file-icon-large { + font-size: 24px; + margin-right: 12px; + flex-shrink: 0; +} + +.file-name-wrap { + flex: 1; + overflow: hidden; + min-width: 0; + display: flex; + justify-content: center; +} + +.file-name { + margin: 0; + font-size: 14px; + color: var(--color-text-1); + transition: color 0.2s; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + .table-data-row:hover & { + color: var(--color-primary); + } +} + +.file-name-small { + font-size: 14px; + color: var(--color-text-4); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +/* 操作按钮区域 */ +.file-actions { + display: flex; + gap: 4px; + justify-content: center; +} + +.action-btn { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: transparent; + border: none; + cursor: pointer; + transition: all 0.2s ease; + color: var(--color-text-3); + + &:hover { + background: var(--color-fill-3); + color: var(--color-primary); + } +} + +.delete-btn { + &:hover { + color: var(--color-danger); + background-color: rgba(255, 77, 77, 0.05); + } +} + +/* 响应式调整 */ +@media (max-width: 1200px) { + .name-column { + flex: 0 0 35% !important; + max-width: 35% !important; + } + .time-column { + flex: 0 0 20% !important; + max-width: 20% !important; + } +} + +@media (max-width: 992px) { + .name-column { + flex: 0 0 45% !important; + max-width: 45% !important; + } + .type-column { + flex: 0 0 20% !important; + max-width: 20% !important; + } + .time-column { + display: none; + } + .action-column { + flex: 0 0 35% !important; + max-width: 35% !important; + } + .file-actions { + justify-content: flex-end; + } +} + +@media (max-width: 768px) { + .size-column, .time-column { + display: none; + } + .type-column { + flex: 0 0 25% !important; + max-width: 25% !important; + } + .name-column { + flex: 0 0 45% !important; + max-width: 45% !important; + } + .action-column { + flex: 0 0 30% !important; + max-width: 30% !important; + } + .file-content { + padding: 12px; + } + .file-grid-container { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} + +@media (max-width: 576px) { + .type-column { + display: none; + } + .name-column { + flex: 0 0 60% !important; + max-width: 60% !important; + } + .action-column { + flex: 0 0 40% !important; + max-width: 40% !important; + } + .file-header { + padding: 0 12px; + flex-wrap: wrap; + } + .breadcrumbs { + margin-bottom: 8px; + width: 100%; + } + .file-card { + min-height: auto; + } +} + +/* 浏览器缩放调整 */ +@media (max-height: 800px) { + .folder-content { + max-height: calc(100vh - 180px); + } + + .file-content { + max-height: calc(100vh - 100px); + } + + .file-grid-container { + max-height: calc(100vh - 280px); + } +} + +@media (max-height: 600px) { + .folder-content { + max-height: calc(100vh - 160px); + } + + .file-content { + max-height: calc(100vh - 80px); + } + + .file-grid-container { + max-height: calc(100vh - 260px); + } +} + +/* 空状态样式 */ +.initial-state, .empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 64px 0; + color: var(--color-text-3); + background-color: var(--color-fill-1); + border-radius: 8px; + text-align: center; +} + +.initial-icon { + font-size: 48px; + margin-bottom: 16px; + color: var(--color-text-4); +} + +:deep(.empty-state .arco-btn) { + margin-top: 16px; + padding: 8px 16px; + background-color: var(--color-primary); + color: white; + border-radius: 4px; + border: none; + cursor: pointer; + transition: all 0.2s ease; + font-weight: 500; +} + +:deep(.empty-state .arco-btn:hover) { + background-color: var(--color-primary-dark-1); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +:deep(.empty-state .arco-btn:active) { + transform: translateY(0); +} /* 上传区域样式 */ .upload-area { @@ -2017,9 +3030,141 @@ onMounted(() => { color: var(--color-text-3); } +.upload-hint { + margin-top: 8px; + font-size: 12px; + color: var(--color-text-4); +} +/* 上传相关样式 */ +.upload-file-list { + margin-top: 16px; + border-radius: 4px; + border: 1px solid var(--color-border); + overflow: hidden; +} +.upload-file-item { + display: flex; + align-items: center; + padding: 12px; + border-bottom: 1px solid var(--color-border); + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: var(--color-fill-1); + } +} +.file-info { + display: flex; + align-items: center; + flex: 1; + min-width: 0; +} + +.file-icon { + font-size: 20px; + margin-right: 12px; + flex-shrink: 0; +} + +.file-name { + flex: 1; + min-width: 0; +} + +.name-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; +} + +.file-meta { + font-size: 12px; + color: var(--color-text-4); + margin-top: 4px; +} + +.file-error { + color: var(--color-danger); + margin-left: 8px; +} + +.file-progress { + flex: 1; + margin: 0 16px; +} + +.file-actions { + flex-shrink: 0; +} + +/* 分页样式 */ +.pagination-container { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: var(--color-bg-1); + padding: 16px 24px; + border-top: 1px solid var(--color-border); + display: flex; + justify-content: flex-end; + align-items: center; + z-index: 10; + box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); + margin-top: 0; + + .arco-pagination { + margin: 0; + + .arco-pagination-item { + border-radius: 6px; + margin: 0 4px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--color-primary); + color: var(--color-primary); + } + + &.arco-pagination-item-active { + background: var(--color-primary); + border-color: var(--color-primary); + color: white; + } + } + + .arco-pagination-prev, + .arco-pagination-next { + border-radius: 6px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--color-primary); + color: var(--color-primary); + } + } + + .arco-pagination-size-changer { + margin-left: 16px; + } + + .arco-pagination-jumper { + margin-left: 16px; + } + + .arco-pagination-total { + color: var(--color-text-2); + font-size: 14px; + } + } +} @@ -2309,14 +3454,228 @@ onMounted(() => { } } +/* 上传文件相关样式 */ +.upload-container { + display: flex; + flex-direction: column; + gap: 12px; +} +.upload-btn { + align-self: flex-start; + border-radius: 8px; + font-weight: 500; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3); + } +} + +.upload-hint { + color: var(--color-text-3); + font-size: 12px; + line-height: 1.4; + padding: 8px 12px; + background: var(--color-fill-2); + border-radius: 6px; + border-left: 3px solid var(--color-primary-light-3); +} + +.upload-file-list { + margin-top: 16px; + border: 1px solid var(--color-border); + border-radius: 8px; + background: var(--color-bg-1); + max-height: 300px; + overflow-y: auto; +} + +.upload-file-item { + display: flex; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--color-border); + transition: all 0.2s ease; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: var(--color-fill-1); + } + + &.file-error { + background: rgba(255, 77, 79, 0.05); + border-left: 3px solid #ff4d4f; + } +} + +.file-info { + display: flex; + align-items: center; + flex: 1; + gap: 12px; + min-width: 0; +} + +.file-icon { + font-size: 20px; + flex-shrink: 0; +} + +.file-details { + flex: 1; + min-width: 0; +} + +.file-name { + font-weight: 500; + color: var(--color-text-1); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-meta { + font-size: 12px; + color: var(--color-text-3); + display: flex; + align-items: center; + gap: 8px; +} + +.error-text { + color: #ff4d4f; + font-weight: 500; +} + +.file-progress { + margin: 0 16px; + min-width: 120px; +} + +.file-actions { + display: flex; + gap: 4px; +} + +.remove-btn { + color: var(--color-text-3); + + &:hover { + color: #ff4d4f; + background: rgba(255, 77, 79, 0.1); + } +} + +.cancel-btn { + color: var(--color-text-3); + + &:hover { + color: #faad14; + background: rgba(250, 173, 20, 0.1); + } +} @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } +/* 文件头部容器样式 */ +.file-header-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding: 16px 0; +} +.file-title { + display: flex; + align-items: center; +} + +.file-list-title { + font-size: 16px; + font-weight: 600; + color: var(--color-text-1); + margin: 0; +} + +/* 文件搜索样式 */ +.file-search-container { + display: flex; + align-items: center; + gap: 12px; +} + +.file-search-input { + max-width: 300px; + transition: all 0.3s ease; + + &:focus-within { + box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1); + } +} + +/* 可排序表头样式 */ +.sortable-header { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: all 0.2s ease; + user-select: none; +} + +.sortable-header:hover { + background: var(--color-fill-2); + color: var(--color-primary); +} + +.sort-indicator { + display: flex; + flex-direction: column; + gap: 1px; + margin-left: 4px; +} + +.sort-arrow { + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + transition: all 0.2s ease; +} + +.sort-arrow.up { + border-bottom: 3px solid var(--color-text-4); +} + +.sort-arrow.down { + border-top: 3px solid var(--color-text-4); +} + +.sort-arrow.active { + border-bottom-color: var(--color-primary); + border-top-color: var(--color-primary); +} + +.sortable-header:hover .sort-arrow.up { + border-bottom-color: var(--color-primary); +} + +.sortable-header:hover .sort-arrow.down { + border-top-color: var(--color-primary); +} @@ -2425,52 +3784,4 @@ onMounted(() => { } } } - -/* 拖拽分隔线样式 */ -.sidebar-resizer { - position: absolute; - top: 0; - right: 0; - width: 6px; - height: 100%; - cursor: col-resize; - background: transparent; - transition: background-color 0.2s ease; - z-index: 10; - - &:hover { - background: rgba(var(--color-primary-6), 0.1); - } - - &:active { - background: rgba(var(--color-primary-6), 0.2); - } -} - -.resizer-handle { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 2px; - height: 40px; - background: var(--color-primary); - border-radius: 1px; - opacity: 0.6; - transition: opacity 0.2s ease; -} - -.sidebar-resizer:hover .resizer-handle { - opacity: 1; -} - -/* 拖拽时的全局样式 */ -body.resizing { - cursor: col-resize !important; - user-select: none !important; -} - -body.resizing * { - cursor: col-resize !important; -} diff --git a/src/views/bussiness-data/components/FileHeader.vue b/src/views/bussiness-data/components/FileHeader.vue deleted file mode 100644 index 6d56a3f..0000000 --- a/src/views/bussiness-data/components/FileHeader.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - - - diff --git a/src/views/bussiness-data/components/FileList.vue b/src/views/bussiness-data/components/FileList.vue deleted file mode 100644 index 4901a88..0000000 --- a/src/views/bussiness-data/components/FileList.vue +++ /dev/null @@ -1,669 +0,0 @@ - - - - - diff --git a/src/views/bussiness-data/components/FilePagination.vue b/src/views/bussiness-data/components/FilePagination.vue deleted file mode 100644 index 915a632..0000000 --- a/src/views/bussiness-data/components/FilePagination.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/src/views/bussiness-data/components/FileUpload.vue b/src/views/bussiness-data/components/FileUpload.vue deleted file mode 100644 index 4802274..0000000 --- a/src/views/bussiness-data/components/FileUpload.vue +++ /dev/null @@ -1,557 +0,0 @@ - - - - - diff --git a/src/views/data/data.vue b/src/views/data/data.vue new file mode 100644 index 0000000..d4d6c3a --- /dev/null +++ b/src/views/data/data.vue @@ -0,0 +1,3787 @@ + + + + + diff --git a/src/views/operation-platform/data-processing/data-storage/index.vue b/src/views/operation-platform/data-processing/data-storage/index.vue index 23de01b..a15755d 100644 --- a/src/views/operation-platform/data-processing/data-storage/index.vue +++ b/src/views/operation-platform/data-processing/data-storage/index.vue @@ -73,18 +73,8 @@ - -
- - - -
-
- - - @@ -107,8 +97,6 @@ import { } from '@/apis/industrial-image' import DeformationTap from './components/DeformationTap.vue' -// 预览弹窗引用(待重新设计) -// const previewModal = ref() // 活动选项卡 const activeTab = ref('image') diff --git a/src/views/project/TurbineCard.vue b/src/views/project/TurbineCard.vue index 70764b9..bf02fef 100644 --- a/src/views/project/TurbineCard.vue +++ b/src/views/project/TurbineCard.vue @@ -3,22 +3,22 @@ import { computed } from 'vue' import WindTurbine from './icons/WindTurbine.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 } const props = defineProps<{ modelValue: Turbine }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: Turbine): void - (e: 'map'): void + (e: 'update:modelValue', v: Turbine): void + (e: 'map'): void }>() const turbine = computed({ - get: () => props.modelValue, - set: v => emit('update:modelValue', v) + get: () => props.modelValue, + set: (v) => emit('update:modelValue', v), }) /* 状态文字 & 颜色 */ @@ -27,34 +27,36 @@ const statusColorMap = { 0: '#FF7D00', 1: '#165DFF', 2: '#00B42A' } /* 点击循环切换 */ function toggleStatus() { - const next = ((turbine.value.status + 1) % 3) as 0 | 1 | 2 - turbine.value = { ...turbine.value, status: next } + const next = ((turbine.value.status + 1) % 3) as 0 | 1 | 2 + turbine.value = { ...turbine.value, status: next } } function updateNo(val: string) { - turbine.value = { ...turbine.value, turbineNo: val } + turbine.value = { ...turbine.value, turbineNo: val } } \ No newline at end of file + diff --git a/src/views/project/detail/index.vue b/src/views/project/detail/index.vue index ed7aa2d..486f420 100644 --- a/src/views/project/detail/index.vue +++ b/src/views/project/detail/index.vue @@ -7,9 +7,9 @@

{{ projectTitle }}

- {{ - projectData.status - }} + + {{ projectData.statusLabel ?? projectData.status }} +
@@ -198,8 +198,8 @@ import { ref, reactive, computed, onMounted } from 'vue' import { useRoute, useRouter } from 'vue-router' import { Message, Modal } from '@arco-design/web-vue' -import { getProject, deleteProject } from '@/apis/project' -import { addTask, addTaskGroup, listTask, updateTaskProgress } from '@/apis/project/task' +import { getProjectDetail, deleteProject } from '@/apis/project' +import { addTask, addTaskGroup, updateTaskProgress } from '@/apis/project/task' import dayjs from 'dayjs' defineOptions({ name: 'ProjectDetail' }) @@ -257,10 +257,15 @@ const projectTitle = computed(() => { const projectInfos = computed(() => [ { label: '项目编号', value: projectData.value?.projectCode }, - { label: '项目负责人', value: projectData.value?.projectManager }, - { label: '参与人', value: projectData.value?.projectStaff?.join(', ') }, - { label: '项目周期', value: projectData.value?.projectPeriod ? `${projectData.value.projectPeriod[0]} 至 ${projectData.value.projectPeriod[1]}` : '' }, - { label: '客户', value: projectData.value?.commissionUnit }, + { label: '项目负责人', value: projectData.value?.projectManagerName || projectData.value?.projectManager }, + { label: '项目来源', value: projectData.value?.projectOrigin }, + { label: '风场名称', value: projectData.value?.farmName }, + { label: '风场地址', value: projectData.value?.farmAddress }, + { label: '开始时间', value: projectData.value?.startDate }, + { label: '结束时间', value: projectData.value?.endDate }, + { label: '项目规模', value: projectData.value?.scale }, + { label: '状态', value: (statusMap as any)[Number(projectData.value?.status)]?.label || projectData.value?.statusLabel }, + { label: '客户', value: projectData.value?.client }, { label: '备注', value: projectData.value?.projectIntro || '无' } ]) @@ -284,6 +289,15 @@ const taskDetailInfos = computed(() => { { label: '状态', value: currentTask.value.status }, { label: '描述', value: currentTask.value.description || '无' } ] + +const statusMap: Record = { + 0: { label: '待施工', color: 'gray' }, + 1: { label: '施工中', color: 'blue' }, + 2: { label: '已完工', color: 'green' }, + 3: { label: '已审核', color: 'orange' }, + 4: { label: '已验收', color: 'arcoblue' }, +} + }) const getStatusColor = (status: string) => { @@ -322,8 +336,14 @@ const formatDate = (date: string) => { const fetchProjectData = async () => { loading.value = true try { - const res = await getProject(projectId.value) - projectData.value = res.data + const res = await getProjectDetail(projectId.value) + const detail = (res as any).data || res + // 如果status是数字,补充statusLabel用于页面展示 + if (typeof detail.status === 'number' && !detail.statusLabel) { + const mapper = (statusMap as any)[detail.status] + if (mapper) detail.statusLabel = mapper.label + } + projectData.value = detail } catch (error) { console.error(error) Message.error('获取项目详情失败') @@ -332,34 +352,39 @@ const fetchProjectData = async () => { } } -const fetchTaskData = async () => { - try { - const res = await listTask({ - projectId: projectId.value, - page: 1, - size: 100 - }) +// 若任务未返回状态,依据计划时间简单推断状态 +const inferTaskStatus = (task: any): string => { + if (task.status) return task.status + const now = dayjs() + const start = task.planStartDate ? dayjs(task.planStartDate) : null + const end = task.planEndDate ? dayjs(task.planEndDate) : null + if (end && end.isBefore(now)) return '已完成' + if (start && start.isAfter(now)) return '计划中' + if (start && (!end || end.isAfter(now))) return '正在做' + return '其他' +} - // 重置任务列表 - taskColumns.value.forEach(column => { - column.tasks = [] - }) - const tasks = res.data?.list || [] +const fetchTaskData = () => { + // 使用详情接口返回的任务列表 + const detail = projectData.value || {} + const tasks = (detail.tasks || []) as any[] - // 分配任务到对应的列 - tasks.forEach((task: any) => { - const column = taskColumns.value.find(col => col.status === task.status) - if (column) { - column.tasks.push(task) - } else { - taskColumns.value.find(col => col.status === '其他')?.tasks.push(task) - } - }) - } catch (error) { - console.error(error) - Message.error('获取任务数据失败') - } + // 重置任务列表 + taskColumns.value.forEach(column => { + column.tasks = [] + }) + + // 分配任务到对应的列(按状态或推断状态) + tasks.forEach((task: any) => { + const st = inferTaskStatus(task) + const column = taskColumns.value.find(col => col.status === st) + if (column) { + column.tasks.push(task) + } else { + taskColumns.value.find(col => col.status === '其他')?.tasks.push(task) + } + }) } const goBack = () => { @@ -491,8 +516,7 @@ const submitProgressUpdate = async () => { } onMounted(() => { - fetchProjectData() - fetchTaskData() + fetchProjectData().then(() => fetchTaskData()) }) diff --git a/src/views/project/index.vue b/src/views/project/index.vue index e9a8e4e..5200ecc 100644 --- a/src/views/project/index.vue +++ b/src/views/project/index.vue @@ -332,6 +332,249 @@ + + + + 基本信息 + + + + + + + + + + + + + + + + + + + {{ user.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 任务设置 +
+ + + 新增任务 + +
+
暂无任务。
+ + + + + + + + + + + + + + + + + + {{ u.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 子任务 + + + + + + + + + + + + + + + + + {{ u.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + {{ option.label }} + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
@@ -347,20 +590,136 @@
+ + + + + + {{ detailData.projectName || '-' }} + {{ detailData.projectCode || '-' }} + + + {{ PROJECT_STATUS_MAP[detailData.status] || detailData.statusLabel || '-' }} + + + {{ detailData.projectCategory || '-' }} + + {{ detailData.inspectionUnit || '-' }} + {{ detailData.inspectionContact || '-' }} + {{ detailData.inspectionPhone || '-' }} + + {{ detailData.client || '-' }} + {{ detailData.clientContact || '-' }} + {{ detailData.clientPhone || '-' }} + + {{ detailData.farmName || '-' }} + {{ detailData.farmAddress || '-' }} + + {{ detailData.projectManagerName || '-' }} + {{ detailData.scale || detailData.projectScale || '-' }} + {{ detailData.turbineModel || '-' }} + + {{ detailData.startDate || '-' }} + {{ detailData.endDate || '-' }} + + + + {{ detailData.projectIntro || '-' }} + + + + + 任务列表 + + + + + + + diff --git a/src/views/system-resource/device-management/procurement/components/PaymentModal.vue b/src/views/system-resource/device-management/procurement/components/PaymentModal.vue index 81b7d52..8535ad2 100644 --- a/src/views/system-resource/device-management/procurement/components/PaymentModal.vue +++ b/src/views/system-resource/device-management/procurement/components/PaymentModal.vue @@ -1,474 +1,380 @@ - diff --git a/src/views/system-resource/device-management/procurement/components/ProcurementModal.vue b/src/views/system-resource/device-management/procurement/components/ProcurementModal.vue index 260162c..a84a405 100644 --- a/src/views/system-resource/device-management/procurement/components/ProcurementModal.vue +++ b/src/views/system-resource/device-management/procurement/components/ProcurementModal.vue @@ -87,11 +87,15 @@ +
+ + 选择设备类型后自动生成,格式:设备类型+顺序号+日期 +
@@ -528,6 +532,7 @@ import { Message } from '@arco-design/web-vue' import type { FormInstance } from '@arco-design/web-vue' import { equipmentProcurementApi } from '@/apis/equipment/procurement' import type { EquipmentResp, EquipmentReq } from '@/apis/equipment/type' +import { IconInfoCircle } from '@arco-design/web-vue/es/icon' interface Props { visible: boolean @@ -692,6 +697,40 @@ watch([() => formData.unitPrice, () => formData.quantity], ([newUnitPrice, newQu } }) +// 生成设备序列号 +const generateEquipmentSn = (equipmentType: string) => { + // 获取当前时间戳作为顺序号 + const timestamp = Date.now() + const orderNumber = timestamp.toString().slice(-6) // 取后6位作为顺序号 + + // 获取设备类型简称 + const typeMap: Record = { + 'detection': 'DET', + 'security': 'SEC', + 'office': 'OFF', + 'car': 'CAR', + 'other': 'OTH' + } + const typeCode = typeMap[equipmentType] || 'OTH' + + // 格式化当前时间(年月日) + const now = new Date() + const dateStr = now.getFullYear().toString().slice(-2) + + (now.getMonth() + 1).toString().padStart(2, '0') + + now.getDate().toString().padStart(2, '0') + + // 生成序列号:设备类型+顺序号+当前时间 + return `${typeCode}${orderNumber}${dateStr}` +} + +// 监听设备类型变化,自动生成序列号 +watch(() => formData.equipmentType, (newType) => { + if (newType && !formData.equipmentSn) { + // 只有在序列号为空时才自动生成 + formData.equipmentSn = generateEquipmentSn(newType) + } +}) + // 初始化表单数据 const initFormData = () => { if (props.procurementData) { @@ -841,7 +880,7 @@ const fillTestData = () => { const nextMaintenanceTime = nextMaintenanceDate.toISOString().slice(0, 19).replace('T', ' ') // 生成随机序列号 - const randomSn = 'SN' + Math.random().toString(36).substr(2, 8).toUpperCase() + const randomSn = generateEquipmentSn('detection') // 生成随机资产编号 const randomAssetCode = 'ZC' + Math.random().toString(36).substr(2, 6).toUpperCase() @@ -949,6 +988,15 @@ const handleCancel = () => { color: var(--color-text-1); margin-bottom: 8px; } + + .field-tip { + margin-top: 4px; + font-size: 12px; + color: var(--color-text-3); + display: flex; + align-items: center; + line-height: 1.4; + } } .arco-input, diff --git a/src/views/system-resource/device-management/procurement/components/ReceiptModal.vue b/src/views/system-resource/device-management/procurement/components/ReceiptModal.vue index 17ebc74..11264ca 100644 --- a/src/views/system-resource/device-management/procurement/components/ReceiptModal.vue +++ b/src/views/system-resource/device-management/procurement/components/ReceiptModal.vue @@ -27,9 +27,15 @@ {{ equipmentData?.equipmentModel || '-' }} + + {{ equipmentData?.equipmentSn || '-' }} + {{ equipmentData?.brand || '-' }} + + {{ equipmentData?.specification || '-' }} + {{ equipmentData?.supplierName || '-' }} @@ -370,6 +376,32 @@ watch(() => props.equipmentData, () => { } }, { deep: true }) +// 生成设备序列号 +const generateEquipmentSn = (equipmentType: string, inStockTime: string) => { + // 获取当前时间戳作为顺序号 + const timestamp = Date.now() + const orderNumber = timestamp.toString().slice(-6) // 取后6位作为顺序号 + + // 获取设备类型简称 + const typeMap: Record = { + 'detection': 'DET', + 'security': 'SEC', + 'office': 'OFF', + 'car': 'CAR', + 'other': 'OTH' + } + const typeCode = typeMap[equipmentType] || 'OTH' + + // 格式化入库时间(年月日) + const date = new Date(inStockTime) + const dateStr = date.getFullYear().toString().slice(-2) + + (date.getMonth() + 1).toString().padStart(2, '0') + + date.getDate().toString().padStart(2, '0') + + // 生成序列号:设备类型+顺序号+入库时间 + return `${typeCode}${orderNumber}${dateStr}` +} + // 提交表单 const handleSubmit = async () => { try { @@ -380,13 +412,37 @@ const handleSubmit = async () => { throw new Error('设备ID不能为空') } - console.log('📦 开始提交收货数据...') - console.log('📦 设备数据:', props.equipmentData) - console.log('📦 表单数据:', formData) - - // 构建收货请求数据 - const receiptData: ReceiptRequest = { - // 收货特有信息 + // 1. 获取采购阶段的设备信息 + const procurementData = props.equipmentData + + // 2. 生成设备序列号 + const equipmentSn = generateEquipmentSn( + procurementData.equipmentType || 'other', + formData.receiptTime || new Date().toISOString() + ) + + // 3. 合并收货信息和设备信息 + const equipmentData = { + // 采购阶段的数据(已有) + equipmentId: procurementData.equipmentId, + equipmentName: procurementData.equipmentName, + equipmentModel: procurementData.equipmentModel, + equipmentType: procurementData.equipmentType, + equipmentSn: equipmentSn, // 使用生成的序列号 + brand: procurementData.brand, + specification: procurementData.specification, + assetCode: procurementData.assetCode, + + // 采购信息(已有) + purchaseOrder: procurementData.purchaseOrder, + supplierName: procurementData.supplierName, + purchasePrice: procurementData.purchasePrice, + purchaseTime: procurementData.purchaseTime, + quantity: procurementData.quantity, + unitPrice: procurementData.unitPrice, + totalPrice: procurementData.totalPrice, + + // 收货相关信息 receiptTime: formData.receiptTime ? formatDateTime(formData.receiptTime) : formatDateTime(new Date()), receiptPerson: formData.receiptPerson, receiptQuantity: formData.receiptQuantity, @@ -400,62 +456,31 @@ const handleSubmit = async () => { storageLocation: formData.storageLocation, storageManager: formData.storageManager, - // 设备基本信息(从采购数据继承) - equipmentName: props.equipmentData.equipmentName, - equipmentModel: props.equipmentData.equipmentModel, - equipmentType: props.equipmentData.equipmentType, - equipmentSn: props.equipmentData.equipmentSn, - brand: props.equipmentData.brand, - specification: props.equipmentData.specification, - assetCode: props.equipmentData.assetCode, - - // 采购信息(从采购数据继承) - purchaseOrder: props.equipmentData.purchaseOrder, - supplierName: props.equipmentData.supplierName, - purchasePrice: props.equipmentData.purchasePrice, - purchaseTime: props.equipmentData.purchaseTime, - quantity: props.equipmentData.quantity, - unitPrice: props.equipmentData.unitPrice, - totalPrice: props.equipmentData.totalPrice, - - // 入库信息 - inStockTime: formData.receiptTime ? formatDateTime(formData.receiptTime) : formatDateTime(new Date()), - physicalLocation: formData.storageLocation, - locationStatus: 'in_stock', - responsiblePerson: formData.storageManager, - inventoryBarcode: props.equipmentData.inventoryBarcode || generateInventoryBarcode(), - - // 状态信息 + // 系统默认数据 equipmentStatus: 'normal', - useStatus: '0', - healthStatus: 'good', - receiptStatus: 'RECEIVED', - - // 其他管理信息 - depreciationMethod: props.equipmentData.depreciationMethod || 'straight_line', - depreciationYears: props.equipmentData.depreciationYears || 5, - salvageValue: props.equipmentData.salvageValue || 0, - currentNetValue: props.equipmentData.purchasePrice || 0, - - // 系统字段 - createTime: formatDateTime(new Date()), - updateTime: formatDateTime(new Date()) + useStatus: '0', // 空闲中 + locationStatus: 'in_stock', // 库存中 + healthStatus: 'excellent', + responsiblePerson: formData.storageManager, + physicalLocation: formData.storageLocation, + inStockTime: formData.receiptTime ? formatDateTime(formData.receiptTime) : formatDateTime(new Date()), + inventoryBarcode: `BC${Date.now()}${Math.random().toString(36).substr(2, 4).toUpperCase()}`, + depreciationMethod: 'straight_line', + warrantyExpireDate: procurementData.warrantyExpireDate, + assetRemark: `设备已收货入库,收货人:${formData.receiptPerson},入库时间:${formData.receiptTime}` } - - console.log('📦 构建的收货数据:', receiptData) - - // 调用收货API - await equipmentProcurementApi.receiveGoods( - props.equipmentData.equipmentId, - receiptData - ) - Message.success('收货成功,设备已自动入库') + console.log('📦 准备提交收货数据:', equipmentData) + + // 4. 调用收货API + await equipmentProcurementApi.receiveGoods(procurementData.equipmentId, equipmentData) + + Message.success('收货成功!设备已自动入库') emit('success') - emit('update:visible', false) + } catch (error: any) { console.error('收货失败:', error) - Message.error(error?.message || '收货失败,请检查表单信息') + Message.error(error?.message || '收货失败,请重试') } finally { loading.value = false } diff --git a/src/views/system-resource/device-management/procurement/index.vue b/src/views/system-resource/device-management/procurement/index.vue index 91f57f1..2ed8c52 100644 --- a/src/views/system-resource/device-management/procurement/index.vue +++ b/src/views/system-resource/device-management/procurement/index.vue @@ -201,6 +201,8 @@ 编辑 + + 申请采购 + + + + + 管理合同发票 + + + + + + 申请付款 + + + 查看支付详情 + + + 查看收货 - - - 付款 - - - 查看支付详情 - - + + + + + {{ getProcurementStatusText(record.procurementStatus) }} - - - {{ getApprovalStatusText(record.approvalStatus) }} + + + + 已完善 + + 待完善 + + + 未开始 + + + + + {{ getPaymentStatusText(record.paymentStatus) }} + + 未支付 + + + + {{ getReceiptStatusText(record.receiptStatus) }} + + 未收货 + + + +
@@ -349,6 +412,7 @@ import ReceiptDetailModal from './components/ReceiptDetailModal.vue' import PaymentDetailModal from './components/PaymentDetailModal.vue' import ReceiptModal from './components/ReceiptModal.vue' import PaymentModal from './components/PaymentModal.vue' +import ContractInvoiceModal from './components/ContractInvoiceModal.vue' import { equipmentProcurementApi } from '@/apis/equipment/procurement' import { equipmentApprovalApi } from '@/apis/equipment/approval' import type { EquipmentListReq, EquipmentResp } from '@/apis/equipment/type' @@ -397,6 +461,10 @@ const currentPaymentData = ref(null) const receiptModalVisible = ref(false) const paymentModalVisible = ref(false) +// 合同发票管理弹窗控制 +const contractInvoiceModalVisible = ref(false) +const currentContractInvoiceData = ref(null) + // 表格选择 const selectedRowKeys = ref([]) const rowSelection = reactive({ @@ -936,13 +1004,42 @@ const handleModalSuccess = () => { // 采购申请成功回调 const handleApplicationSuccess = async () => { applicationModalVisible.value = false - console.log('采购申请成功,准备刷新数据...') - // 延迟刷新数据,确保后端状态更新完成 + console.log('✅ 采购申请成功,准备更新本地数据...') + + // 立即更新本地数据,让申请采购按钮消失 + if (currentApplicationData.value) { + const equipmentId = currentApplicationData.value.equipmentId + + // 找到对应的记录并更新采购状态 + const recordIndex = tableData.value.findIndex(item => item.equipmentId === equipmentId) + if (recordIndex !== -1) { + // 立即更新本地状态为待审批 + tableData.value[recordIndex] = { + ...tableData.value[recordIndex], + procurementStatus: 'PENDING_APPROVAL' + } + + console.log('✅ 本地数据已更新,申请采购按钮应该消失') + console.log('🔍 更新后的记录:', tableData.value[recordIndex]) + + message.success('采购申请已提交,请等待审批') + } else { + console.warn('⚠️ 未找到对应的记录,无法更新本地状态') + message.warning('状态更新失败,请手动刷新页面') + } + } + + // 可选:延迟刷新数据以确保后端状态同步 setTimeout(async () => { - console.log('开始刷新数据...') - await loadData(currentSearchParams.value) - message.success('采购申请已提交,请等待审批') - }, 1000) + try { + console.log('🔄 延迟刷新数据,确保后端状态同步...') + await loadData(currentSearchParams.value) + console.log('✅ 后端数据同步完成') + } catch (error) { + console.error('❌ 后端数据同步失败:', error) + // 不显示错误提示,因为本地状态已经更新 + } + }, 1000) // 1秒后刷新 } // 刷新数据 @@ -993,17 +1090,45 @@ const getTotalAmount = () => { // 检查是否可以申请采购 const canApplyProcurement = (record: EquipmentResp) => { // 根据采购状态判断是否可以申请采购 - // 只有未开始、已拒绝、已完成的设备可以重新申请采购 - const allowedStatuses = ['NOT_STARTED', 'REJECTED', 'COMPLETED', null, undefined] + // 只有未开始、已拒绝的设备可以申请采购 + // 待审批、已通过、已完成等状态不能重复申请 + const allowedStatuses = ['NOT_STARTED', 'REJECTED', null, undefined] const canApply = allowedStatuses.includes(record.procurementStatus) - console.log(`设备 ${record.equipmentName} 采购状态: ${record.procurementStatus}, 可申请: ${canApply}`) + + // 添加详细的调试日志 + console.log(`🔍 申请采购按钮显示检查 - 设备: ${record.equipmentName}`) + console.log(`🔍 当前采购状态: ${record.procurementStatus}`) + console.log(`🔍 允许申请的状态: ${allowedStatuses.join(', ')}`) + console.log(`🔍 是否显示按钮: ${canApply}`) + console.log(`🔍 完整记录:`, record) + return canApply } // 检查是否可以收货 const canReceiveGoods = (record: EquipmentResp) => { - const receiptStatus = (record as any).receiptStatus - return receiptStatus === 'NOT_RECEIVED' || receiptStatus === 'PARTIALLY_RECEIVED' + // 只有在采购状态为已通过且收货状态为未收货时才显示确认收货按钮 + // 同时需要确保合同发票信息已经完善 + const procurementStatus = record.procurementStatus + const receiptStatus = record.receiptStatus + + // 检查是否有合同发票信息(这里可以根据实际业务逻辑调整) + // 暂时使用一个简单的判断,后续可以根据实际数据结构调整 + const hasContractInvoice = true // 暂时设为true,后续根据实际业务逻辑调整 + + console.log('🔍 canReceiveGoods 检查:', { + equipmentName: record.equipmentName, + procurementStatus, + receiptStatus, + hasContractInvoice, + canReceive: procurementStatus === 'APPROVED' && + (receiptStatus === 'NOT_RECEIVED' || receiptStatus === 'PARTIALLY_RECEIVED') && + hasContractInvoice + }) + + return procurementStatus === 'APPROVED' && + (receiptStatus === 'NOT_RECEIVED' || receiptStatus === 'PARTIALLY_RECEIVED') && + hasContractInvoice } // 收货操作 @@ -1021,7 +1146,10 @@ const handleViewReceipt = (record: EquipmentResp) => { // 收货成功回调 const handleReceiptSuccess = () => { receiptModalVisible.value = false + // 收货成功后,采购状态应该更新为已收货 + // 重新加载数据以显示最新状态 loadData(currentSearchParams.value) + message.success('收货成功!设备已自动入库') } // 检查是否可以付款 @@ -1072,6 +1200,57 @@ const getApprovalStatusText = (status: string) => { return textMap[status] || '未知' } +// 手动刷新单条记录 +const handleRefreshRecord = async (record: EquipmentResp) => { + console.log('🔄 手动刷新记录:', record.equipmentId) + try { + // 显示加载提示 + const loadingMessage = message.loading('正在刷新数据...') + + // 重新加载整个表格数据,确保获取最新状态 + await loadData(currentSearchParams.value) + + // 清除加载提示 + if (loadingMessage && typeof loadingMessage.close === 'function') { + loadingMessage.close() + } + message.success('记录状态已刷新') + + console.log('✅ 表格数据刷新完成') + + } catch (error: any) { + console.error('❌ 刷新失败:', error) + message.error(error?.message || '刷新失败') + } +} + +// 检查是否可以管理合同发票 +const canManageContractInvoice = (record: EquipmentResp) => { + // 只有在采购状态为已通过时才显示管理合同发票按钮 + return record.procurementStatus === 'APPROVED' +} + +// 管理合同发票操作 +const handleManageContractInvoice = (record: EquipmentResp) => { + currentContractInvoiceData.value = { ...record } + contractInvoiceModalVisible.value = true +} + +// 合同发票管理成功回调 +const handleContractInvoiceSuccess = () => { + contractInvoiceModalVisible.value = false + loadData(currentSearchParams.value) +} + +// 检查是否有合同发票信息 +const hasContractInvoiceInfo = (record: EquipmentResp) => { + // 这里可以根据实际业务逻辑调整判断条件 + // 暂时使用一个简单的判断,后续可以根据实际数据结构调整 + // 例如:检查是否有合同编号、发票号码等字段 + return record.procurementStatus === 'APPROVED' && + (record.receiptStatus === 'RECEIVED' || record.paymentStatus === 'PAID') +} + onMounted(() => { loadData() }) @@ -1258,6 +1437,8 @@ onMounted(() => { } } } + + } // 响应式设计 diff --git a/src/views/tower-monitoring/clearance.vue b/src/views/tower-monitoring/clearance.vue new file mode 100644 index 0000000..b3ff709 --- /dev/null +++ b/src/views/tower-monitoring/clearance.vue @@ -0,0 +1,498 @@ + + + + + + diff --git a/src/views/tower-monitoring/deformation.vue b/src/views/tower-monitoring/deformation.vue new file mode 100644 index 0000000..1152c74 --- /dev/null +++ b/src/views/tower-monitoring/deformation.vue @@ -0,0 +1,665 @@ + + + + + + \ No newline at end of file diff --git a/src/views/tower-monitoring/vibration.vue b/src/views/tower-monitoring/vibration.vue new file mode 100644 index 0000000..07d9e49 --- /dev/null +++ b/src/views/tower-monitoring/vibration.vue @@ -0,0 +1,497 @@ + + + + + + diff --git a/src/views/tower-monitoring/whistle.vue b/src/views/tower-monitoring/whistle.vue new file mode 100644 index 0000000..07d9e49 --- /dev/null +++ b/src/views/tower-monitoring/whistle.vue @@ -0,0 +1,497 @@ + + + + + +