ソースを参照

资料库前端

lipenghui 2 週間 前
コミット
e0ce4cd074

+ 3 - 0
src/api/pms/iotinfo/index.ts

@@ -22,6 +22,9 @@ export const IotInfoApi = {
   getChildContentFile: async (params: any) => {
     return await request.post({ url: `/rq/iot-info/child/content-file`, params })
   },
+  getAllChildContentFile: async (params: any) => {
+    return await request.post({ url: `/rq/iot-info/all/content-file`, params })
+  },
   getIotInfoFilePage: async (params: any) => {
     return await request.post({ url: `/rq/iot-info/file/page`, params })
   },

+ 68 - 32
src/components/UploadFile/src/FileUpload.vue

@@ -1,35 +1,46 @@
 <template>
   <div class="file-upload-container">
-    <!-- 上传按钮区域 -->
+    <!-- 上传按钮区域 - 区分文件和文件夹按钮 -->
     <div class="upload-btn-area">
       <el-button
         type="primary"
         :loading="uploading"
-        @click="handleUploadClick"
+        @click="handleFileUploadClick"
         class="upload-btn"
+        style="margin-right: 10px"
       >
-        <el-icon><Upload /></el-icon>
-        {{ uploading ? '上传中...' : '选择文件或文件夹' }}
+        <el-icon><Document /></el-icon>
+        {{ uploading ? '文件上传中...' : '选择文件' }}
+      </el-button>
+      <el-button
+        type="success"
+        :loading="uploading"
+        @click="handleFolderUploadClick"
+        class="upload-btn"
+      >
+        <el-icon><Folder /></el-icon>
+        {{ uploading ? '文件夹上传中...' : '选择文件夹' }}
       </el-button>
       <input
         ref="fileInput"
         type="file"
         class="file-input"
         :multiple="true"
-        :webkitdirectory="allowFolderUpload"
-        :directory="allowFolderUpload"
+        :webkitdirectory="isFolderMode"
+        :directory="isFolderMode"
         @change="handleFileChange"
       />
       <p class="upload-hint">单个文件夹总大小不能超过300M</p>
     </div>
 
-    <!-- 上传列表与进度 - 使用Element UI组件 -->
+    <!-- 上传列表与进度 - 保持不变 -->
     <el-card v-if="uploadList.length > 0" class="upload-list-card">
       <el-table
         :data="uploadList"
         border
         style="width: 100%; margin-bottom: 0px;"
       >
+        <!-- 表格内容保持不变 -->
         <el-table-column
           prop="name"
           label="文件名/文件夹名"
@@ -99,7 +110,7 @@
       </el-table>
     </el-card>
 
-    <!-- 错误提示 -->
+    <!-- 错误提示 - 保持不变 -->
     <el-dialog
       v-model="showErrorModal"
       title="上传错误"
@@ -121,37 +132,37 @@ import {
   Document,
 } from '@element-plus/icons-vue';
 
-// 组件参数
+// 组件参数 - 保持不变
 const props = defineProps({
-  // 是否允许文件夹上传
   allowFolderUpload: {
     type: Boolean,
     default: true
   },
-  // 最大文件夹大小限制(M)
   maxFolderSize: {
     type: Number,
     default: 300
   },
-  // 上传接口地址,使用代理路径
   uploadUrl: {
     type: String,
     default: 'http://localhost:48080/admin-api/rq/file/upload'
   }
 });
 
-// 组件事件
+// 组件事件 - 保持不变
 const emit = defineEmits(['uploadSuccess', 'uploadError', 'uploadComplete']);
 
-// 状态管理
+// 新增:用于区分当前是文件还是文件夹上传模式
+const isFolderMode = ref();
+
+// 状态管理 - 保持不变
 const fileInput = ref(null);
 const uploading = ref(false);
 const uploadList = ref([]);
 const showErrorModal = ref(false);
 const errorMessage = ref('');
-const uploadTasks = ref(new Map()); // 用于存储上传任务,便于取消
+const uploadTasks = ref(new Map());
 
-// 获取进度条状态
+// 获取进度条状态 - 保持不变
 const getProgressStatus = (status) => {
   switch(status) {
     case 'success':
@@ -165,7 +176,7 @@ const getProgressStatus = (status) => {
   }
 };
 
-// 格式化文件大小
+// 格式化文件大小 - 保持不变
 const formatSize = (row, column, bytes) => {
   if (bytes === 0) return '0 Bytes';
   const k = 1024;
@@ -174,7 +185,7 @@ const formatSize = (row, column, bytes) => {
   return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 };
 
-// 计算文件夹总大小
+// 计算文件夹总大小 - 保持不变
 const calculateFolderSize = (files) => {
   let totalSize = 0;
   files.forEach(file => {
@@ -183,13 +194,38 @@ const calculateFolderSize = (files) => {
   return totalSize;
 };
 
-// 处理上传点击
-const handleUploadClick = () => {
+// 新增:文件上传按钮点击事件
+const handleFileUploadClick = () => {
   if (uploading.value) return;
-  fileInput.value.click();
+  if (fileInput.value) {
+    fileInput.value.value = '';
+  }
+  isFolderMode.value = false; // 切换到文件模式
+  // 强制DOM更新后再触发点击
+  nextTick(() => {
+    fileInput.value.click();
+  });
 };
 
-// 处理文件选择
+// 新增:文件夹上传按钮点击事件
+const handleFolderUploadClick = () => {
+  if (uploading.value) return;
+  if (fileInput.value) {
+    fileInput.value.value = '';
+  }
+  isFolderMode.value = true; // 切换到文件夹模式
+  if (!props.allowFolderUpload) {
+    errorMessage.value = '不允许上传文件夹';
+    showErrorModal.value = true;
+    return;
+  }
+  // 强制DOM更新后再触发点击
+  nextTick(() => {
+    fileInput.value.click();
+  });
+};
+
+// 处理文件选择 - 保持不变
 const handleFileChange = async (e) => {
   const files = Array.from(e.target.files);
   if (files.length === 0) return;
@@ -197,8 +233,8 @@ const handleFileChange = async (e) => {
   // 清空input值,允许重复选择同一文件
   e.target.value = '';
 
-  // 检查是否包含文件夹
-  const hasFolders = files.some(file => file.webkitRelativePath.includes('/'));
+  // 检查是否包含文件夹(根据当前模式判断)
+  const hasFolders = isFolderMode.value && files.some(file => file.webkitRelativePath.includes('/'));
 
   if (hasFolders) {
     // 按文件夹分组
@@ -256,12 +292,12 @@ const handleFileChange = async (e) => {
       });
     });
   }
-debugger
+
   // 开始上传
   await startUpload();
 };
 
-// 开始上传
+// 开始上传 - 保持不变
 const startUpload = async () => {
   uploading.value = true;
 
@@ -341,8 +377,6 @@ const startUpload = async () => {
       // 发送请求
       xhr.open('POST', props.uploadUrl, true);
       xhr.setRequestHeader('tenant-id', 1);
-
-
       xhr.send(formData);
 
     } catch (error) {
@@ -373,7 +407,7 @@ const startUpload = async () => {
   }, 1000);
 };
 
-// 取消上传
+// 取消上传 - 保持不变
 const handleCancelUpload = (fileItem) => {
   const xhr = uploadTasks.value.get(fileItem.uid);
   if (xhr) {
@@ -393,9 +427,8 @@ const handleCancelUpload = (fileItem) => {
   }
 };
 
-// 组件挂载时设置独占窗口样式
+// 组件挂载时设置样式 - 保持不变
 onMounted(() => {
-  // 为组件容器添加独占窗口样式
   const container = document.querySelector('.file-upload-container');
   if (container) {
     container.style.height = '8vh';
@@ -408,6 +441,7 @@ onMounted(() => {
 </script>
 
 <style scoped>
+/* 样式保持不变 */
 .file-upload-container {
   width: 100%;
   max-width: 1200px;
@@ -419,6 +453,7 @@ onMounted(() => {
   flex-direction: row;
   margin-bottom: 20px;
   text-align: left;
+  align-items: center; /* 新增:对齐按钮和提示文字 */
 }
 
 .file-input {
@@ -432,8 +467,9 @@ onMounted(() => {
 .upload-hint {
   color: #666;
   font-size: 10px;
-  margin: 0;
+  margin: 0 0 0 10px; /* 调整左边距 */
   color: orangered;
+  align-self: flex-end; /* 对齐按钮底部 */
 }
 
 .upload-list-card {

+ 1 - 0
src/locales/en.ts

@@ -649,6 +649,7 @@ export default {
     reset:'Reset',
     upload:'Upload',
     fileType:'FileType',
+    fileTypeHolder:'Please select File Type',
     fileSize:'FileSize',
     preview:'Preview',
     operation:'Operation',

+ 1 - 0
src/locales/zh-CN.ts

@@ -646,6 +646,7 @@ export default {
     reset:'重置',
     upload:'文件上传',
     fileType:'文件类型',
+    fileTypeHolder:'请选择文件类型',
     fileSize:'文件大小',
     preview:'查看预览',
     operation:'操作',

+ 3 - 3
src/views/pms/device/index.vue

@@ -175,9 +175,9 @@
               >
                 {{t('iotDevice.delete')}}
               </el-button>
-              <el-button link type="warning" @click="handleUpload(scope.row.id)">
-                {{t('iotDevice.upload')}}
-              </el-button>
+<!--              <el-button link type="warning" @click="handleUpload(scope.row.id)">-->
+<!--                {{t('iotDevice.upload')}}-->
+<!--              </el-button>-->
             </template>
           </el-table-column>
         </el-table>

+ 219 - 108
src/views/system/tree/PmsTree.vue

@@ -13,97 +13,125 @@
       :expand-on-click-node="false"
       :filter-node-method="filterNode"
       :props="defaultProps"
-      :default-expanded-keys="firstLevelKeys"
-      highlight-current
-      node-key="id"
-      @node-click="handleNodeClick"
-      @node-contextmenu="handleRightClick"
-      style="height: 52em"
+      :default-expanded-keys="expandedKeys"
+    highlight-current
+    node-key="id"
+    @node-click="handleNodeClick"
+    @node-contextmenu="handleRightClick"
+    style="height: 52em"
     >
-      <template #default="{ node }">
-        <div
-          style="display: flex; justify-content: space-between; align-items: center; width: 100%"
-        >
-          <div>
-            <Icon
-              style="vertical-align: middle;fill: currentColor;color: orange"
-              v-if="node.data.type === 'dept'"
-              icon="fa:folder-open"
-            />
-            <Icon
-              style="vertical-align: middle;fill: currentColor;color:orange"
-              v-if="node.data.type === 'device'"
-              icon="fa:folder-open"
-            />
-            <Icon icon="fa:folder-open" v-if="node.data.type === 'file'" style="vertical-align: middle;color: orange;fill: currentColor;"/>
-            <!-- 文件夹图标 -->
-            <span style="vertical-align: middle; margin-left: 3px">{{ node.data.name }}</span>
-          </div>
-          <!--          <div>-->
-          <!--            <icon style="vertical-align: middle" @click="handleRightClick" icon="ep:edit" />-->
-          <!--          </div>-->
+    <template #default="{ node }">
+      <div
+        style="display: flex; justify-content: space-between; align-items: center; width: 100%"
+      >
+        <div>
+          <Icon
+            style="vertical-align: middle;fill: currentColor;color: orange"
+            v-if="node.data.type === 'dept'"
+            icon="fa:folder-open"
+          />
+          <Icon
+            style="vertical-align: middle;fill: currentColor;color:orange"
+            v-if="node.data.type === 'device'"
+            icon="fa:folder-open"
+          />
+          <Icon icon="fa:folder-open" v-if="node.data.type === 'file'" style="vertical-align: middle;color: orange;fill: currentColor;"/>
+          <span style="vertical-align: middle; margin-left: 3px">{{ node.data.name }}</span>
         </div>
-      </template>
+      </div>
+    </template>
     </el-tree>
   </div>
   <div v-show="deviceVisible" ref="contextMenuRef" class="custom-menu" :style="{ left: menuX + 'px', top: menuY + 'px' }">
     <ul>
       <li style="border-bottom: 1px solid #ccc;" @click="handleDeviceClick('add')">设备详情</li>
+      <li style="border-bottom: 0px solid #ccc;" @click="handleMenuClick('add')">新建目录</li>
     </ul>
   </div>
-    <div v-show="menuVisible" ref="contextMenuRef" class="custom-menu" :style="{ left: menuX + 'px', top: menuY + 'px' }">
-      <ul>
-        <li style="border-bottom: 1px solid #ccc;" @click="handleMenuClick('add')">新增子节点</li>
-        <li style="border-bottom: 1px solid #ccc;" @click="handleMenuClick('edit')">编辑节点</li>
-        <li @click="handleMenuClick('delete')">删除节点</li>
-      </ul>
-    </div>
-
-    <Dialog v-model="dialogVisible" :title="dialogTitle" style="width: 40em">
-      <el-form
-        ref="formRef"
-        v-loading="formLoading"
-        :model="formData"
-        :rules="formRules"
-        label-width="80px"
-      >
-        <el-form-item label="资料分类名称" prop="name" label-width="110px">
-          <el-input v-model="formData.name" placeholder="请输入资料分类名称" />
-        </el-form-item>
-        <el-form-item label="显示排序" prop="sort" label-width="110px">
-          <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
-        </el-form-item>
-        <el-form-item label="备注" prop="remark" label-width="110px">
-          <el-input
-            v-model="formData.remark"
-            maxlength="11"
-            placeholder="请输入备注"
-            type="textarea"
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="dialogVisible = false">取 消</el-button>
-      </template>
-    </Dialog>
+  <div v-show="menuVisible" ref="contextMenuRef" class="custom-menu" :style="{ left: menuX + 'px', top: menuY + 'px' }">
+    <ul>
+      <li style="border-bottom: 1px solid #ccc;" @click="handleMenuClick('add')">新建目录</li>
+      <li style="border-bottom: 1px solid #ccc;" @click="handleMenuClick('edit')">编辑</li>
+      <li @click="handleMenuClick('delete')">删除目录</li>
+    </ul>
+  </div>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle" style="width: 40em">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="目录名称" prop="name" label-width="110px">
+        <el-input v-model="formData.name" placeholder="请输入目录名称" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort" label-width="110px">
+        <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
+      </el-form-item>
+<!--      <el-form-item label="备注" prop="remark" label-width="110px">-->
+<!--        <el-input-->
+<!--          v-model="formData.remark"-->
+<!--          maxlength="11"-->
+<!--          placeholder="请输入备注"-->
+<!--          type="textarea"-->
+<!--        />-->
+<!--      </el-form-item>-->
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
 </template>
 
 <script lang="ts" setup>
 import { ElTree } from 'element-plus'
 import { defaultProps, handleTree } from '@/utils/tree'
 import { CommonStatusEnum } from '@/utils/constants'
-// import {IotInfoClassifyApi} from "@/api/pms/info";
 import { IotTreeApi } from '@/api/system/tree'
-import { Folder } from '@element-plus/icons-vue'
-import {flatten} from "min-dash";
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useRouter } from 'vue-router'
+import {IotInfoClassifyApi} from "@/api/pms/info";
+
+// 类型定义
+interface Tree {
+  id: number | string
+  name: string
+  type: string
+  children?: Tree[]
+  [key: string]: any
+}
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
 const formRef = ref() // 搜索的表单
-const firstLevelKeys = ref([])
+const firstLevelKeys = ref<(number | string)[]>([])
+const expandedKeys = ref<(number | string)[]>([]) // 用于展开节点的路径
+
+// 表单相关
+const formLoading = ref(false)
+const formType = ref<'create' | 'update'>('create')
+const formData = ref({
+  id: undefined,
+  title: '',
+  parentId: undefined,
+  name: undefined,
+  sort: undefined,
+  leaderUserId: undefined,
+  phone: undefined,
+  email: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = ref({
+  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
+  sort: [{ required: true, message: '请输入排序', trigger: 'blur' }]
+})
+
 const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
@@ -123,10 +151,15 @@ const resetForm = () => {
   }
   formRef.value?.resetFields()
 }
+
 defineOptions({ name: 'IotTree' })
+
+// 接收外部参数
 const props = defineProps({
-  deviceId: { type: Number, required: true }
+  deviceId: { type: Number, required: true },
+  currentId: { type: [Number, String], default: null } // 新增:接收需要定位的节点ID
 })
+
 const deptName = ref('')
 const nodeInfo = ref({})
 const treeList = ref<Tree[]>([]) // 树形结构
@@ -137,9 +170,10 @@ const menuX = ref(0)
 const menuY = ref(0)
 const contextMenuRef = ref(null) // 弹窗DOM引用
 let selectedNode = null
-
+const parentId = ref()
 
 const { push } = useRouter() // 路由跳转
+
 // 动态高度计算
 const treeContainer = ref(null)
 const setHeight = () => {
@@ -149,6 +183,48 @@ const setHeight = () => {
   treeContainer.value.style.height = `${windowHeight * 0.78}px` // 60px 底部预留
 }
 
+// 新增:查找节点路径的递归方法
+const findNodePath = (nodes: Tree[], targetId: number | string, path: (number | string)[] = []): (number | string)[] | null => {
+  for (const node of nodes) {
+    path.push(node.id)
+    if (node.id === targetId) {
+      return [...path]
+    }
+    if (node.children && node.children.length) {
+      const result = findNodePath(node.children, targetId, path)
+      if (result) {
+        return result
+      }
+    }
+    path.pop()
+  }
+  return null
+}
+
+// 新增:定位节点并高亮
+const locateNode = (targetId: number | string) => {
+  if (!targetId || !treeList.value.length) return
+
+  const pathIds = findNodePath(treeList.value, targetId)
+  if (pathIds) {
+    // 展开所有父节点
+    expandedKeys.value = pathIds.slice(0, -1)
+
+    // 等待DOM更新后设置当前节点
+    nextTick(() => {
+      if (treeRef.value) {
+        treeRef.value.setCurrentKey(targetId)
+
+        // 滚动到节点位置
+        const node = treeRef.value.getNode(targetId)
+        if (node && node.$el) {
+          node.$el.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
+        }
+      }
+    })
+  }
+}
+
 onMounted(async () => {
   await getTreeInfo()
   setHeight()
@@ -161,11 +237,12 @@ onUnmounted(() => {
 
 const handleRightClick = (event, node, data) => {
   nodeInfo.value = node
-  console.log(JSON.stringify(nodeInfo.value))
   event.preventDefault()
   menuX.value = event.clientX
   menuY.value = event.clientY
   selectedNode = data
+  debugger
+  parentId.value = data.data.id
   if (nodeInfo.value.type === 'device') {
     deviceVisible.value = true
   } else if (nodeInfo.value.type === 'file') {
@@ -177,44 +254,52 @@ const handleDeviceClick = async () => {
   const id = nodeInfo.value.originId
   push({ name: 'DeviceDetailInfo', params: { id } })
   deviceVisible.value = false
+  menuVisible.value = false
 }
 
-
 const handleMenuClick = async (action) => {
   switch (action) {
     case 'add':
       dialogVisible.value = true
-      dialogTitle.value = '新增资料分类'
+      dialogTitle.value = '新增目录'
       formType.value = 'create'
       resetForm()
       break
     case 'edit':
       resetForm()
       dialogVisible.value = true
-      dialogTitle.value = '编辑资料分类'
+      dialogTitle.value = '编辑目录'
       formType.value = 'update'
-      formData.value = nodeInfo.value
-      console.log(JSON.stringify(formData.value))
+      formData.value = { ...nodeInfo.value }
       break
     case 'delete':
       // 删除的二次确认
       await message.delConfirm()
-      // 发起删除
-      await IotInfoClassifyApi.deleteIotInfoClassify(nodeInfo.value.id)
+      // 假设存在删除接口
+      await IotTreeApi.deleteIotTree(nodeInfo.value.id)
       message.success(t('common.delSuccess'))
       // 刷新列表
-      await getTree()
+      await getTreeInfo()
       break
   }
+  deviceVisible.value = false
   menuVisible.value = false
 }
+
 /** 获得部门树 */
 const getTreeInfo = async () => {
   const res = await IotTreeApi.getSimpleTreeList()
   treeList.value = []
   treeList.value.push(...handleTree(res))
-  firstLevelKeys.value = treeList.value.map(node => node.id);
-  emits('success', treeList.value[0].id)
+
+  // 处理展开逻辑:有currentId则展开对应路径,否则展开一级节点
+  if (props.currentId) {
+    locateNode(props.currentId)
+  } else {
+    firstLevelKeys.value = treeList.value.map(node => node.id)
+    expandedKeys.value = [...firstLevelKeys.value]
+  }
+  emits('success', treeList.value[0]?.id)
 }
 
 /** 基于名字过滤 */
@@ -223,42 +308,61 @@ const filterNode = (name: string, data: Tree) => {
   return data.name.includes(name)
 }
 
-/** 处理部门被点击 */
+/** 处理节点被点击 */
 const handleNodeClick = async (row: { [key: string]: any }) => {
   deviceVisible.value = false
   menuVisible.value = false
   emits('node-click', row)
 }
+
+/** 提交表单 */
+const submitForm = async () => {
+  // 表单验证逻辑
+  formLoading.value = true
+  try {
+    // 假设存在提交接口
+    formData.value.deviceId = props.deviceId
+    debugger
+    formData.value.parentId = parentId.value
+    if (formData.value.parentId===undefined||formData.value.parentId===null) {
+      formData.value.parentId = props.currentId
+    }
+    formData.value.type = 'file'
+    if (formType.value === 'create') {
+      debugger
+      await IotTreeApi.createIotTree(formData.value)
+    } else {
+      await IotTreeApi.updateIotTree(formData.value)
+    }
+    message.success(t(formType.value === 'create' ? 'common.addSuccess' : 'common.updateSuccess'))
+    dialogVisible.value = false
+    await getTreeInfo()
+  } catch (error) {
+    console.error(error)
+  } finally {
+    formLoading.value = false
+  }
+}
+
 const emits = defineEmits(['node-click', 'success'])
 
-/** 监听deptName */
+/** 监听搜索框变化 */
 watch(deptName, (val) => {
   treeRef.value!.filter(val)
 })
-// const handleClickOutside = (event) => {
-//   if (
-//     menuVisible.value &&
-//     contextMenuRef.value &&
-//     !contextMenuRef.value.contains(event.target)
-//   ) {
-//     menuVisible.value = false
-//   }
-// }
-// watch(menuVisible, (visible) => {
-//   if (visible) {
-//     document.addEventListener('click', handleClickOutside)
-//   } else {
-//     document.removeEventListener('click', handleClickOutside)
-//   }
-// })
-// onBeforeUnmount(() => {
-//   document.removeEventListener('click', handleClickOutside)
-// })
-/** 初始化 */
-// onMounted(async () => {
-//   await getTreeInfo()
-// })
+
+/** 监听currentId变化,重新定位 */
+watch(
+  () => props.currentId,
+  (newVal) => {
+    if (newVal && treeList.value.length) {
+      locateNode(newVal)
+    }
+  },
+  { immediate: true }
+)
 </script>
+
 <style lang="scss" scoped>
 .custom-menu {
   position: fixed;
@@ -287,7 +391,7 @@ watch(deptName, (val) => {
   border: 1px solid #e4e7ed;
   border-radius: 4px;
 }
-/* 自定义滚动条(可选) */
+/* 自定义滚动条 */
 .tree-container::-webkit-scrollbar {
   width: 6px;
 }
@@ -295,4 +399,11 @@ watch(deptName, (val) => {
   background: #c0c4cc;
   border-radius: 3px;
 }
+
+/* 自定义高亮样式 */
+::v-deep .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+  background-color: #eaf5ff !important;
+  color: #1890ff !important;
+  font-weight: bold;
+}
 </style>

+ 49 - 8
src/views/system/tree/index.vue

@@ -12,14 +12,14 @@
           {{ item.name }}
         </el-breadcrumb-item>
       </el-breadcrumb>
-      <el-input :placeholder="'在'+breadcrumbs[breadcrumbs.length - 1].name+'下搜索'" style="width: 250px;height: 30px"/>
+      <el-input v-model="queryParams.allName" :placeholder="'在'+breadcrumbs[breadcrumbs.length - 1].name+'下搜索'" style="width: 250px;height: 30px" @input="searchFolderAndFile"/>
     </div>
   </ContentWrap>
   <div class="container-tree" ref="container">
   <el-row >
     <div class="left-tree" :style="{ width: leftWidth + 'px' }">
       <ContentWrapNoBottom >
-        <PmsTree @node-click="handleFileNodeClick" @success="successList" :deviceId="id" />
+        <PmsTree @node-click="handleFileNodeClick" @success="successList" :currentId="queryParams.classId" :deviceId="id" />
       </ContentWrapNoBottom>
     </div>
 <!--    </el-col>-->
@@ -45,11 +45,26 @@
                 class="!w-240px"
               />
             </el-form-item>
+            <el-form-item v-show="false" :label="t('file.fileType') " prop="fileType">
+              <el-select
+                v-model="queryParams.fileType"
+                :placeholder="t('file.fileTypeHolder')"
+                clearable
+                class="!w-200px"
+              >
+                <el-option
+                  v-for="dict in getStrDictOptions(DICT_TYPE.PMS_FILE_TYPE)"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
             <el-form-item>
               <el-button @click="handleQuery"><Icon icon="ep:search" />
                 {{  t('file.search')}}</el-button>
               <el-button @click="resetQuery"><Icon icon="ep:refresh" />{{  t('file.reset')}}</el-button>
-              <el-button type="primary" plain @click="openForm('create')">
+              <el-button type="primary" :loading="uploadLoading"  @click="openForm('create')">
                 <Icon icon="ep:plus" /> {{  t('file.upload')}}
               </el-button>
             </el-form-item>
@@ -85,7 +100,7 @@
 <!--              </template>-->
 <!--            </el-table-column>-->
             <el-table-column :label="t('file.dept') " align="center" prop="deptName" />
-            <el-table-column :label="t('file.device') " align="center" prop="deviceName" min-width="220"/> />
+            <el-table-column :label="t('file.device') " align="center" prop="deviceCode" min-width="220"/> />
             <el-table-column :label="t('file.operation') " align="center" width="160">
               <template #default="scope">
                 <div class="flex items-center justify-center">
@@ -121,7 +136,7 @@ import { ref, onMounted, onUnmounted } from 'vue'
 
 import PmsTree from '@/views/system/tree/PmsTree.vue'
 import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
-import {DICT_TYPE} from "@/utils/dict";
+import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
 import {IotInfoClassifyApi} from "@/api/pms/info";
 import {IotTreeApi} from "@/api/system/tree";
 defineOptions({ name: 'IotTree' })
@@ -130,7 +145,18 @@ const container = ref(null)
 const leftWidth = ref(350) // 初始左侧宽度
 const rightWidth = ref(window.innerWidth * 0.8)
 let isDragging = false
+const uploadLoading = ref(false);
 
+
+const searchFolderAndFile = async () =>{
+  debugger
+  formLoading.value = true
+  const data = await IotInfoApi.IotInfoApi.getAllChildContentFile(queryParams)
+  debugger
+  list.value = data
+  formLoading.value = false
+  queryParams.filename = "";
+}
 const openWeb = (url) => {
   window.open('http://1.94.244.160:8012/onlinePreview?url='+encodeURIComponent(Base64.encode(url)));
 }
@@ -185,15 +211,17 @@ const { params } = useRoute() // 查询参数
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const list = ref<IotDeviceVO[]>([]) // 列表的数据
 // const total = ref(0) // 列表的总页数
-const id = params.id as unknown as number
+const id = ref()
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   filename: null,
+  fileType: null,
   createTime: [],
   deviceId: null,
   classId: null,
-  deptId: undefined
+  deptId: undefined,
+  allName: null,
 })
 // SPU 表单数据
 const formData = ref({
@@ -239,16 +267,23 @@ const handleDelete = async (id: number) => {
   } catch {}
 }
 const formRef = ref()
-const openForm = (type: string, id?: number) => {
+const openForm = async (type: string, id?: number) => {
   // if (classType.value==='dept'){
   //   message.error(t('common.deptChoose'))
   //   return;
   // }
+  uploadLoading.value = true
   if (!queryParams.classId) {
     message.error(t('common.leftNode'))
     return
   }
   formRef.value.open(type, id)
+  await new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(); // 操作完成后resolve
+    }, 2000); // 模拟2秒耗时
+  });
+  uploadLoading.value = false
 }
 const deviceId = ref('')
 const clickNodeId = ref('')
@@ -306,6 +341,11 @@ const handleFileNodeClick = async (row) => {
   })
 
   queryParams.classId = row.id
+  if (row.type=='device') {
+    id.value = row.originId
+  }
+
+  debugger
   classType.value = row.type
   if (row.type==='device') {
     deviceId.value = row.originId
@@ -416,6 +456,7 @@ onMounted(async () => {
   // queryParams.deptId = wsCache.get(CACHE_KEY.USER).user.deptId;
   // await getList()
   // deviceId.value = params.id as unknown as number
+  id.value = params.id as unknown as number
 })
 </script>
 <style scoped>