Parcourir la source

pms 设备调拨

zhangcl il y a 3 mois
Parent
commit
88dbde98ec

+ 5 - 0
src/api/pms/device/index.ts

@@ -87,6 +87,11 @@ export const IotDeviceApi = {
     return await request.post({ url: `/rq/iot-device/saveDeviceStatuses`, data })
   },
 
+  // 保存 设备调拨记录
+  saveDeviceAllot: async (data: any) => {
+    return await request.post({ url: `/rq/iot-device/saveDeviceAllot`, data })
+  },
+
   // 修改设备台账
   updateIotDevice: async (data: IotDeviceVO) => {
     return await request.put({ url: `/rq/iot-device/update`, data })

+ 49 - 0
src/api/pms/iotdeviceallotlog/index.ts

@@ -0,0 +1,49 @@
+import request from '@/config/axios'
+
+// 设备调拨日志 VO
+export interface IotDeviceAllotLogVO {
+  id: number // 主键id
+  deviceId: number // 设备id
+  oldDeptId: number // 设备调拨前部门
+  newDeptId: number // 设备调拨后部门
+  reason: string // 设备调拨原因
+  remark: string // 备注
+  deviceCode: string    // 设备编码
+  deviceName: string    // 设备名称
+  creatorName: string   // 操作人姓名
+  oldDeptName: string   // 设备调拨前部门名称
+  newDeptName: string   // 设备调拨后部门名称
+}
+
+// 设备调拨日志 API
+export const IotDeviceAllotLogApi = {
+  // 查询设备调拨日志分页
+  getIotDeviceAllotLogPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-device-allot-log/page`, params })
+  },
+
+  // 查询设备调拨日志详情
+  getIotDeviceAllotLog: async (id: number) => {
+    return await request.get({ url: `/pms/iot-device-allot-log/get?id=` + id })
+  },
+
+  // 新增设备调拨日志
+  createIotDeviceAllotLog: async (data: IotDeviceAllotLogVO) => {
+    return await request.post({ url: `/pms/iot-device-allot-log/create`, data })
+  },
+
+  // 修改设备调拨日志
+  updateIotDeviceAllotLog: async (data: IotDeviceAllotLogVO) => {
+    return await request.put({ url: `/pms/iot-device-allot-log/update`, data })
+  },
+
+  // 删除设备调拨日志
+  deleteIotDeviceAllotLog: async (id: number) => {
+    return await request.delete({ url: `/pms/iot-device-allot-log/delete?id=` + id })
+  },
+
+  // 导出设备调拨日志 Excel
+  exportIotDeviceAllotLog: async (params) => {
+    return await request.download({ url: `/pms/iot-device-allot-log/export-excel`, params })
+  },
+}

+ 11 - 5
src/views/pms/bom/index.vue

@@ -50,7 +50,7 @@
             <el-button
               type="primary"
               plain
-              @click="openForm('create')"
+              @click="openForm('create', null)"
               v-hasPermi="['rq:iot-bom:create']"
             >
               <Icon icon="ep:plus" class="mr-5px" /> 新增
@@ -92,7 +92,7 @@
               <el-button
                 link
                 type="primary"
-                @click="openForm('update', scope.row.id)"
+                @click="openForm('update', scope.row)"
                 v-hasPermi="['rq:iot-bom:update']"
               >
                 修改
@@ -185,7 +185,7 @@ const CommonBomMaterialData = ref({
 })
 
 // 从 Store 中获取左侧设备分类树选中的 节点ID
-const selectedId = computed(() => treeStore.selectedId);
+let selectedId = computed(() => treeStore.selectedId);
 
 /** 查询 BOM树 列表 */
 const getList = async () => {
@@ -255,8 +255,14 @@ const handleDeviceCategoryTreeNodeClick = async (row) => {
 
 /** 添加/修改操作 */
 const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
+const openForm = (type: string, row) => {
+  // 如果是没有点击左侧设备树 直接在初始化的列表页面点击某个 BOM节点的修改 也要保存当前BOM关联的设备分类ID
+  if(row != null) {
+    treeStore.setSelectedId(row.deviceCategoryId)
+    formRef.value.open(type, row.id)
+    return
+  }
+  formRef.value.open(type, null)
 }
 
 /** 展开/折叠操作 */

+ 100 - 259
src/views/pms/device/ConfigDeviceAllot.vue

@@ -1,14 +1,14 @@
 <template>
-  <div class="container">
-    <el-row :gutter="20">
+  <div class="container" >
+    <el-row :gutter="20" class="equal-height-row">
       <!-- 左侧设备列表 -->
-      <el-col :span="12">
-        <div class="card">
+      <el-col :span="12" class="col-height">
+        <div class="card left-card">
           <h3>设备列表</h3>
           <div class="dept-select">
             <el-tree-select
               clearable
-              v-model="formData.deptId1"
+              v-model="formData.deptId"
               :data="deptList"
               :props="defaultProps"
               check-strictly
@@ -19,14 +19,14 @@
             />
           </div>
           <el-scrollbar height="400px">
-            <el-checkbox-group v-model="selectedDevices"  @change="handleDeviceChange">
+            <el-checkbox-group v-model="selectedDevices">
               <div
                 v-for="device in simpleDevices"
                 :key="device.id"
                 class="checkbox-item"
               >
                 <el-checkbox :label="device.id">
-                  {{ device.deviceCode }} ({{ device.deviceName }})
+                  {{ device.deviceCode }} ({{ device.deviceName }}) - {{ device.deptName }}
                 </el-checkbox>
               </div>
             </el-checkbox-group>
@@ -34,12 +34,12 @@
         </div>
       </el-col>
 
-      <!-- 右侧责任人选择 -->
-      <el-col :span="12" >
-        <div class="card" height="400px">
+      <!-- 右侧部门选择 -->
+      <el-col :span="12" class="col-height">
+        <div class="card right-card">
           <h3>部门列表</h3>
-            <ContentWrap class="h-1/1" v-if="true" height="400px">
-              <DeptTree2 @node-click="handleDeptNodeClick" height="400px" :radio-value="selectedDeptId"/>
+            <ContentWrap class="dept-tree-container" >
+              <DeptTree2 ref="deptTreeRef" v-model="selectedDeptId" height="100%" @update:modelValue="handleDeptChange"/>
             </ContentWrap>
 
           <div class="action-bar">
@@ -54,8 +54,8 @@
       <h3>待提交的关联关系</h3>
       <el-table :data="tempRelations" style="width: 100%">
         <el-table-column prop="deviceNames" label="设备" width="200" />
-        <el-table-column prop="status" label="状态" />
-        <el-table-column prop="reason" label="调整原因" />
+        <el-table-column prop="deptName" label="部门" />
+        <el-table-column prop="deptId" label="部门id" v-if="false"/>
         <el-table-column label="操作" width="120">
           <template #default="{ row }">
             <el-button
@@ -90,24 +90,13 @@ import {defaultProps, handleTree} from "@/utils/tree";
 import * as DeptApi from "@/api/system/dept";
 import {IotDeviceApi, IotDeviceVO} from "@/api/pms/device";
 import {IotDevicePersonApi, IotDevicePersonVO} from "@/api/pms/iotdeviceperson";
-import * as UserApi from "@/api/system/user";
-import {simpleUserList, UserVO} from "@/api/system/user";
-import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
-import {dateFormatter} from "@/utils/formatTime";
 import DeptTree2 from "@/views/pms/device/DeptTree2.vue";
 
 defineOptions({ name: 'ConfigDeviceAllot' })
-
+const selectedDeptId = ref<number | string>('')
 const simpleDevices = ref<IotDeviceVO[]>([])
-const simpleUsers = ref<UserVO[]>([])
-const currentStatus = ref<string>('')
-const adjustReason = ref<string>('')
 const deptList = ref<Tree[]>([]) // 树形结构
 const selectedDevices = ref<number[]>([]) // 改为数组存储多选
-// 获取当前设备对象
-const currentDevice = computed(() => {
-  return simpleDevices.value.find(d => d.id === selectedDevice.value)
-})
 
 const formData = ref({
   id: undefined,
@@ -116,7 +105,6 @@ const formData = ref({
   brand: undefined,
   model: undefined,
   deptId: undefined as string | undefined,
-  deptId1: undefined as string | undefined,
   deviceStatus: undefined,
   reason: '',
   assetProperty: undefined,
@@ -124,189 +112,73 @@ const formData = ref({
 })
 
 const queryParams = reactive({
-  deptId1: formData.value.deptId1,
   deptId: formData.value.deptId,
 })
 
-const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
+const deptTreeRef = ref<InstanceType<typeof DeptTree2>>()
 
-const userList = ref([
-  { id: 'u1', name: '张三', position: '工程师', deptId: 'dept1' },
-  { id: 'u2', name: '李四', position: '技术员', deptId: 'dept1' },
-  { id: 'u3', name: '王五', position: '质检员', deptId: 'dept2' },
-  // 更多用户...
-])
+const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
 
 // 响应式数据
-const selectedDevice = ref<number>(0)
-const selectedDept = ref('')
-const selectedUsers = ref<number[]>([])
 const tempRelations = ref<Array<{
+  deviceIds: []
   deviceId: number
   deviceNames: string
-  deviceStatus: number
+  deptName: string
+  deptId: number
   reason: string
 }>>([])
 
-const canSave = computed(() => {
-  return !!selectedDevice.value && selectedUsers.value.length > 0
-})
-
-// 树形配置
-const treeProps = {
-  label: 'name',
-  children: 'children'
-}
-
-interface Dept {
-  id: number
-  name: string
-  parentId: number
-  children?: Dept[]
-}
-
-// 转换后的树形数据
-const deptTreeData = computed(() => buildDeptTree(deptList.value))
-
-// 构建树形结构的方法
-const buildDeptTree = (list: Dept[]): Dept[] => {
-  const map = new Map<number, Dept>()
-  const roots: Dept[] = []
-
-  // 创建映射表
-  list.forEach(item => {
-    map.set(item.id, { ...item, children: [] })
-  })
-
-  // 构建树结构
-  list.forEach(item => {
-    const node = map.get(item.id)
-    if (item.parentId === 0) {
-      roots.push(node!)
-    } else {
-      const parent = map.get(item.parentId)
-      parent?.children?.push(node!)
-    }
-  })
-
-  return roots
-}
-
-// 方法
-const loadUsers = () => {
-  selectedUsers.value = []
-}
-
-// 设备切换时清空状态
-const handleDeviceChange = () => {
-  currentStatus.value = ''
-  adjustReason.value = ''
-  // 尝试从暂存记录恢复数据
-  const existRecord = tempRelations.value.find(r => r.deviceId === selectedDevice.value)
-  if (existRecord) {
-    currentStatus.value = existRecord.deviceStatus
-    adjustReason.value = existRecord.reason
-  }
-}
-
 // 添加设备选择监听
-watch(selectedDevice, (newVal) => {
-  if (newVal) {
-    // 切换设备时清空人员选择
-    selectedUsers.value = []
-    // 可选:清空部门选择
-    formData.value.deptId = undefined
-    simpleUsers.value = []
-  }
-})
-
-// 处理人员选择变化
-const handleUserSelectionChange = (value: number[]) => {
-  if (!selectedDevice.value) {
-    ElMessage.warning('请先选择设备')
-    selectedUsers.value = []
-    return
-  }
-
-  // 获取当前选择的设备
-  const device = simpleDevices.value.find(d => d.id === selectedDevice.value)
-  if (!device) return
-
-  // 新增或更新关联关系
-  updateDeviceRelation(device, value)
-}
-
-// 保存当前关联关系
-const saveCurrentRelation = () => {
-  if (!selectedDevice.value || !currentDevice.value) return
-  if (!currentStatus.value) {
-    ElMessage.warning('请先选择设备状态')
-    return
-  }
-
-  const statusDict = getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)
-    .find(d => d.value === currentStatus.value)
-
-  const newRelation: DeviceStatusRelation = {
-    deviceId: selectedDevice.value,
-    deviceName: `${currentDevice.value.deviceCode} (${currentDevice.value.deviceName})`,
-    status: currentStatus.value,
-    statusLabel: statusDict?.label || '未知状态',
-    reason: adjustReason.value
-  }
+watch([selectedDevices, selectedDeptId], () => {
+  updateTempRelations();
+}, {deep: true})
+
+const updateTempRelations = () => {
+  if (!selectedDeptId.value || selectedDevices.value.length === 0) return;
+
+  const treeNode = deptTreeRef.value?.treeRef?.getNode(selectedDeptId.value);
+  const deptName = treeNode?.data?.name || '未知部门';
+
+  const newRelations = selectedDevices.value
+    .map(deviceId => {
+      const device = simpleDevices.value.find(d => d.id === deviceId);
+      return device ? {
+        deviceIds: [deviceId],
+        deviceId: deviceId,
+        deviceNames: `${device.deviceCode} (${device.deviceName})`,
+        deptId: selectedDeptId.value,
+        deptName: deptName
+      } : null;
+    })
+    .filter(Boolean);
 
-  const existIndex = tempRelations.value
-    .findIndex(r => r.deviceId === selectedDevice.value)
+  tempRelations.value = [
+    ...tempRelations.value.filter(r =>
+      !selectedDevices.value.includes(r.deviceId)
+    ),
+    ...newRelations
+  ];
+};
 
-  if (existIndex > -1) {
-    tempRelations.value[existIndex] = newRelation
-  } else {
-    tempRelations.value.push(newRelation)
-  }
+// 修改部门变更处理方法
+const handleDeptChange = (deptId) => {
+  selectedDeptId.value = deptId
+  updateTempRelations()
 }
 
-// 修改 暂时 保存关联方法
-const saveTempRelation = () => {
-  if (!selectedDevice.value || selectedUsers.value.length === 0) return
-
-  const device = simpleDevices.value.find(d => d.id === selectedDevice.value)
-  const users = simpleUsers.value.filter(u => selectedUsers.value.includes(u.id))
-
-  if (!device) return
-
-  const newRelation = {
-    deviceIds: [selectedDevice.value], // 保持数组结构但只包含单个设备
-    deviceNames: `${device.deviceCode} (${device.deviceName})`,
-    userIds: users.map(u => u.id),
-    userNames: users.map(u => u.nickname).join(', ')
-  }
-
-  // 覆盖已存在的设备关联
-
-  const existIndex = tempRelations.value.findIndex(
-    r => r.deviceIds[0] === selectedDevice.value
-  )
-
-  if (existIndex > -1) {
-    tempRelations.value[existIndex] = newRelation
-  } else {
-    tempRelations.value.push(newRelation)
-  }
-
-  clearSelection()
-}
 
 /** 处理 部门-设备 树 被点击 */
 const handleDeptDeviceTreeNodeClick = async (row: { [key: string]: any }) => {
   emit('node-click', row)
-  formData.value.deptId1 = row.id
+  formData.value.deptId = row.id
   await getDeviceList()
 }
 
 /** 获得 部门下的设备 列表 */
 const getDeviceList = async () => {
   try {
-    const params = { deptId: formData.value.deptId1 }
+    const params = { deptId: formData.value.deptId }
     const data = await IotDeviceApi.simpleDevices(params)
     simpleDevices.value = data || []
   } catch (error) {
@@ -315,84 +187,27 @@ const getDeviceList = async () => {
   }
 }
 
-/** 处理 部门-人员 树 被点击 */
-const handleDeptUserTreeNodeClick = async (row: { [key: string]: any }) => {
-  emit('node-click', row)
-  formData.value.deptId = row.id
-  await getUserList()
-}
-
-/** 获得 部门下的人员 列表 */
-const getUserList = async () => {
-  try {
-    const params = {
-      deptId: formData.value.deptId,
-      pageNo: 1,
-      pageSize: 10 }
-    const data = await UserApi.simpleUserList(params)
-    simpleUsers.value = data || []
-  } catch (error) {
-    simpleUsers.value = []
-    console.error('获取人员列表失败:', error)
-  }
-}
-
-// 更新设备关联关系
-const updateDeviceRelation = (device: IotDeviceVO, userIds: number[]) => {
-  // 获取当前选择的人员
-  const users = simpleUsers.value.filter(u => userIds.includes(u.id))
-
-  const newRelation = {
-    deviceIds: [device.id],
-    deviceNames: `${device.deviceCode} (${device.deviceName})`,
-    userIds: users.map(u => u.id),
-    userNames: users.map(u => u.nickname).join(', ')
-  }
-
-  // 查找是否已存在该设备的关联
-  const existIndex = tempRelations.value.findIndex(
-    r => r.deviceIds[0] === device.id
-  )
-
-  if (existIndex > -1) {
-    // 如果没有选择人员,移除该关联
-    if (userIds.length === 0) {
-      tempRelations.value.splice(existIndex, 1)
-    } else {
-      // 更新关联
-      tempRelations.value[existIndex] = newRelation
-    }
-  } else if (userIds.length > 0) {
-    // 添加新关联
-    tempRelations.value.push(newRelation)
-  }
-}
-
-const clearSelection = () => {
-  selectedDevice.value = ''
-  selectedUsers.value = []
-  selectedDept.value = ''
-}
-
-const removeTempRelation = (deviceIds: string[]) => {
+const removeTempRelation = (deviceIds: number[]) => {
   tempRelations.value = tempRelations.value.filter(
-    r => r.deviceIds.join() !== deviceIds.join()
-  )
-}
+    r => !deviceIds.includes(r.deviceId)
+  );
+  selectedDevices.value = selectedDevices.value.filter(
+    id => !deviceIds.includes(id)
+  );
+};
 
 const submitRelations = async () => {
   try {
     // 转换为后端需要的格式
-    const submitData = tempRelations.value.flatMap(relation => {
-      return relation.deviceIds.map(deviceId => ({
-        deviceId,
-        userIds: relation.userIds
-      }))
-    })
-    await IotDevicePersonApi.saveDevicePersonRelation(submitData)
+    const submitData = tempRelations.value.map(r => ({
+      deviceId: r.deviceId,
+      deptId: r.deptId,
+      reason: r.reason
+    }))
+    await IotDeviceApi.saveDeviceAllot(submitData)
     // 模拟API调用
     console.log('提交数据:', submitData)
-    ElMessage.success('关联关系提交成功')
+    ElMessage.success('提交成功')
     tempRelations.value = []
   } catch (error) {
     ElMessage.error('提交失败,请重试')
@@ -403,7 +218,10 @@ const submitRelations = async () => {
 onMounted(async () => {
   // 初始化部门树 根据选择的部门 查询设备列表 部门人员列表
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
-  // getList()
+  nextTick(() => {
+    // 强制重新计算布局
+    window.dispatchEvent(new Event('resize'))
+  })
 })
 
 </script>
@@ -502,22 +320,34 @@ h3 {
   flex-shrink: 0; /* 防止搜索框被压缩 */
 }
 
+.el-scrollbar__wrap {
+  overflow-x: hidden;
+}
+
+.tree-container .el-tree {
+  height: 100% !important;
+}
+
 /* 左侧滚动区域 */
-.el-scrollbar {
-  flex: 1;
-  min-height: 0; /* 关键:允许内容区域收缩 */
+.el-scrollbar,
+.tree-container {
+  scrollbar-width: thin;
+  scrollbar-color: #c0c4cc transparent;
 }
 
-/* 右侧树形容器 */
+.left-card,
 .right-card {
+  flex: 1;
   display: flex;
   flex-direction: column;
+  height: 100%; /* 继承父级高度 */
 }
 
 .dept-tree-container {
   flex: 1;
   min-height: 0; /* 关键:允许内容区域收缩 */
   position: relative;
+  overflow: hidden;
 }
 
 /* 确保内容区域填充高度 */
@@ -531,4 +361,15 @@ h3 {
   overflow: auto;
 }
 
+.equal-height-row {
+  display: flex;
+  align-items: stretch; /* 关键:等高布局 */
+}
+
+.col-height {
+  display: flex;
+  flex-direction: column;
+  min-height: 600px; /* 统一最小高度 */
+}
+
 </style>

+ 47 - 6
src/views/pms/device/DeptTree2.vue

@@ -17,9 +17,10 @@
       highlight-current
       node-key="id"
       show-checkbox
+      check-strictly
       @node-click="handleNodeClick"
+      @check="handleCheck"
       @node-contextmenu="handleRightClick"
-      style="height: 52em"
     />
   </div>
   <div
@@ -78,6 +79,29 @@ const handleMenuClick = (action) => {
   }
   menuVisible.value = false;
 };
+
+// 新增props接收单选值
+const props = defineProps({
+  modelValue: [Number, String],
+  height: { type: String, default: '400px' }
+})
+
+// 处理复选框选中事件(实现单选逻辑)
+const handleCheck = (currentNode, { checkedKeys }) => {
+  if (checkedKeys.includes(currentNode.id)) {
+    treeRef.value.setCheckedKeys([currentNode.id])
+    emits('update:modelValue', currentNode.id)
+  } else {
+    treeRef.value.setCheckedKeys([])
+    emits('update:modelValue', null)
+  }
+}
+
+// 监听外部值变化同步选中状态
+watch(() => props.modelValue, (val) => {
+  treeRef.value.setCheckedKeys(val ? [val] : [])
+})
+
 /** 获得部门树 */
 const getTree = async () => {
   const res = await DeptApi.getSimpleDeptList()
@@ -96,7 +120,16 @@ const filterNode = (name: string, data: Tree) => {
 const handleNodeClick = async (row: { [key: string]: any }) => {
   emits('node-click', row)
 }
-const emits = defineEmits(['node-click'])
+// const emits = defineEmits(['node-click'])
+const emits = defineEmits<{
+  (e: 'update:modelValue', value: number | string): void
+}>()
+
+// 新增暴露部门数据的方法
+defineExpose({
+  deptList, // 将部门数据暴露给父组件
+  treeRef
+})
 
 /** 监听deptName */
 watch(deptName, (val) => {
@@ -133,10 +166,18 @@ onUnmounted(() => {
 .custom-menu li:hover {
   background: #f5f5f5;
 }
+
 .tree-container {
-  overflow-y: auto;
-  min-width: 100%;
-  border: 1px solid #e4e7ed;
-  border-radius: 4px;
+  height: v-bind(height);
+  overflow: auto; /* 关键:允许滚动 */
+
+  :deep(.el-tree) {
+    min-height: 100%; /* 确保树撑满容器 */
+    > .el-tree-node {
+      min-width: 100%;
+      display: inline-block; /* 解决长文本换行问题 */
+    }
+  }
 }
+
 </style>

+ 21 - 19
src/views/pms/device/DeviceAllot.vue

@@ -140,16 +140,16 @@
             width="180px"
           />
           <el-table-column label="操作" align="center" min-width="120px">
-            <!-- <template #default="scope">
+            <template #default="scope">
               <el-button
                 link
                 type="primary"
-                @click="openForm('update', scope.row.id)"
+                @click="handleView(scope.row.id)"
                 v-hasPermi="['rq:iot-device:update']"
               >
-                编辑
+                调拨记录
               </el-button>
-            </template> -->
+            </template>
           </el-table-column>
         </el-table>
         <!-- 分页 -->
@@ -162,6 +162,12 @@
       </ContentWrap>
     </el-col>
   </el-row>
+  <DeviceAllotLogDrawer
+    :model-value="drawerVisible"
+    @update:model-value="val => drawerVisible = val"
+    :device-id="currentDeviceId"
+    ref="showDrawer"
+  />
 </template>
 
 <script setup lang="ts">
@@ -171,6 +177,7 @@ import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import DeptTree from '@/views/system/user/DeptTree.vue'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import DeviceAllotLogDrawer from "@/views/pms/device/DeviceAllotLogDrawer.vue";
 
 /** 设备调拨 列表 */
 defineOptions({ name: 'IotDeviceAllot' })
@@ -198,28 +205,17 @@ const queryParams = reactive({
   remark: undefined,
   manufacturerId: undefined,
   supplierId: undefined,
-  manDate: [],
   nameplate: undefined,
   expires: undefined,
-  plPrice: undefined,
-  plDate: [],
-  plYear: undefined,
-  plStartDate: [],
-  plMonthed: undefined,
-  plAmounted: undefined,
-  remainAmount: undefined,
-  infoId: undefined,
-  infoType: undefined,
-  infoName: undefined,
-  infoRemark: undefined,
-  infoUrl: undefined,
-  templateJson: undefined,
   creator: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 const contentSpan = ref(20)
 const treeShow = ref(true)
+const currentDeviceId = ref() // 设备id
+const drawerVisible = ref<boolean>(false)
+const showDrawer = ref()
 const shou = (tree) =>{
   treeShow.value = !tree
   if (tree) {
@@ -260,6 +256,13 @@ const resetQuery = () => {
   handleQuery()
 }
 
+/** 查看设备调拨详情 */
+const handleView = async (deviceId) => {
+  currentDeviceId.value = deviceId
+  drawerVisible.value = true
+  showDrawer.value.openDrawer()
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
@@ -311,7 +314,6 @@ const { wsCache } = useCache()
 /** 初始化 **/
 onMounted(() => {
   const user = wsCache.get(CACHE_KEY.USER)
-  // queryParams.deptId = user.user.deptId
   getList()
 })
 </script>

+ 122 - 0
src/views/pms/device/DeviceAllotLogDrawer.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-drawer
+    title="设备调拨记录"
+    :append-to-body="true"
+    :model-value="modelValue"
+    @update:model-value="$emit('update:modelValue', $event)"
+    :show-close="false"
+    direction="rtl"
+    :size="computedSize"
+    :before-close="handleClose"
+  >
+    <template v-if="deviceId">
+      <div v-loading="loading" style="height: 100%">
+        <el-table :data="deviceAllots" style="width: 100%">
+          <el-table-column prop="deviceName" label="设备名称" width="180" />
+          <el-table-column prop="deviceCode" label="设备编码" width="180" />
+          <el-table-column prop="oldDeptName" label="调整前部门" width="180" />
+          <el-table-column prop="newDeptName" label="调用后部门" width="180" />
+          <el-table-column prop="reason" label="调拨原因" width="180" />
+          <el-table-column prop="creatorName" label="调整人" width="180" />
+          <el-table-column
+            label="调整时间"
+            align="center"
+            prop="createTime"
+            width="180"
+            :formatter="dateFormatter"
+          />
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="loadDeviceAllots(props.deviceId)"
+        />
+      </div>
+    </template>
+
+  </el-drawer>
+</template>
+<script setup lang="ts">
+
+import { ref, watch, defineOptions, defineEmits } from 'vue'
+import { ElMessage } from 'element-plus'
+import {dateFormatter} from "@/utils/formatTime";
+import {IotDeviceAllotLogApi} from "@/api/pms/iotdeviceallotlog";
+const drawerVisible = ref<boolean>(false)
+const emit = defineEmits(['update:modelValue', 'add', 'delete'])
+
+defineOptions({
+  name: 'DeviceAllotLogDrawer'
+})
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  createTime: [],
+  deviceId: '',
+  name: '',
+  code: ''
+})
+
+const windowWidth = ref(window.innerWidth)
+// 动态计算百分比
+const computedSize = computed(() => {
+  return windowWidth.value > 1200 ? '60%' : '80%'
+})
+
+const loading = ref(false)
+const total = ref(0) // 列表的总页数
+const deviceAllots = ref([])
+
+const props = defineProps({
+  modelValue: Boolean,
+  deviceId: Number
+})
+
+// 监听bom树节点ID变化
+watch(() => props.deviceId, async (newVal) => {
+  if (newVal) {
+    await loadDeviceAllots(newVal)
+  }
+})
+
+// 加载设备的状态调整记录
+const loadDeviceAllots = async (deviceId) => {
+  queryParams.deviceId = deviceId
+  queryParams.pageNo = 1
+  try {
+    loading.value = true
+    // API调用
+    const data = await IotDeviceAllotLogApi.getIotDeviceAllotLogPage(queryParams)
+    deviceAllots.value = data.list
+    total.value = data.total
+  } catch (error) {
+    ElMessage.error('数据加载失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 打开抽屉
+const openDrawer = () => {
+  drawerVisible.value = true
+}
+
+// 关闭抽屉
+const closeDrawer = () => {
+  drawerVisible.value = false
+}
+
+// 关闭抽屉
+const handleClose = () => {
+  emit('update:modelValue', false)
+  deviceAllots.value = []
+}
+
+defineExpose({ openDrawer, closeDrawer, loadDeviceAllots }) // 暴露方法给父组件
+
+</script>
+
+<style lang="scss" scoped></style>

+ 1 - 1
src/views/pms/device/DeviceStatus.vue

@@ -250,7 +250,7 @@ const getList = async () => {
 
 const showDrawer = ref()
 
-/** 查看物料详情 */
+/** 查看设备状态调整详情 */
 const handleView = async (deviceId) => {
   currentDeviceId.value = deviceId
   drawerVisible.value = true

+ 111 - 0
src/views/pms/iotdeviceallotlog/IotDeviceAllotLogForm.vue

@@ -0,0 +1,111 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="设备id" prop="deviceId">
+        <el-input v-model="formData.deviceId" placeholder="请输入设备id" />
+      </el-form-item>
+      <el-form-item label="设备调拨前部门" prop="oldDeptId">
+        <el-input v-model="formData.oldDeptId" placeholder="请输入设备调拨前部门" />
+      </el-form-item>
+      <el-form-item label="设备调拨后部门" prop="newDeptId">
+        <el-input v-model="formData.newDeptId" placeholder="请输入设备调拨后部门" />
+      </el-form-item>
+      <el-form-item label="设备调拨原因" prop="reason">
+        <el-input v-model="formData.reason" placeholder="请输入设备调拨原因" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { IotDeviceAllotLogApi, IotDeviceAllotLogVO } from '@/api/pms/iotdeviceallotlog'
+
+/** 设备调拨日志 表单 */
+defineOptions({ name: 'IotDeviceAllotLogForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  deviceId: undefined,
+  oldDeptId: undefined,
+  newDeptId: undefined,
+  reason: undefined,
+  remark: undefined,
+})
+const formRules = reactive({
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await IotDeviceAllotLogApi.getIotDeviceAllotLog(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotDeviceAllotLogVO
+    if (formType.value === 'create') {
+      await IotDeviceAllotLogApi.createIotDeviceAllotLog(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotDeviceAllotLogApi.updateIotDeviceAllotLog(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    deviceId: undefined,
+    oldDeptId: undefined,
+    newDeptId: undefined,
+    reason: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 231 - 0
src/views/pms/iotdeviceallotlog/index.vue

@@ -0,0 +1,231 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="设备id" prop="deviceId">
+        <el-input
+          v-model="queryParams.deviceId"
+          placeholder="请输入设备id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="设备调拨前部门" prop="oldDeptId">
+        <el-input
+          v-model="queryParams.oldDeptId"
+          placeholder="请输入设备调拨前部门"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="设备调拨后部门" prop="newDeptId">
+        <el-input
+          v-model="queryParams.newDeptId"
+          placeholder="请输入设备调拨后部门"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="设备调拨原因" prop="reason">
+        <el-input
+          v-model="queryParams.reason"
+          placeholder="请输入设备调拨原因"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          v-model="queryParams.remark"
+          placeholder="请输入备注"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['pms:iot-device-allot-log:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-device-allot-log:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="主键id" align="center" prop="id" />
+      <el-table-column label="设备id" align="center" prop="deviceId" />
+      <el-table-column label="设备调拨前部门" align="center" prop="oldDeptId" />
+      <el-table-column label="设备调拨后部门" align="center" prop="newDeptId" />
+      <el-table-column label="设备调拨原因" align="center" prop="reason" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['pms:iot-device-allot-log:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['pms:iot-device-allot-log:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotDeviceAllotLogForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotDeviceAllotLogApi, IotDeviceAllotLogVO } from '@/api/pms/iotdeviceallotlog'
+import IotDeviceAllotLogForm from './IotDeviceAllotLogForm.vue'
+
+/** 设备调拨日志 列表 */
+defineOptions({ name: 'IotDeviceAllotLog' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotDeviceAllotLogVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceId: undefined,
+  oldDeptId: undefined,
+  newDeptId: undefined,
+  reason: undefined,
+  remark: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceAllotLogApi.getIotDeviceAllotLogPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotDeviceAllotLogApi.deleteIotDeviceAllotLog(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotDeviceAllotLogApi.exportIotDeviceAllotLog(queryParams)
+    download.excel(data, '设备调拨日志.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>