Răsfoiți Sursa

pms 功能优化

zhangcl 3 luni în urmă
părinte
comite
020c226b4a

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

@@ -43,6 +43,16 @@ export const IotDeviceApi = {
     return await request.get({ url: `/rq/iot-device/page`, params })
   },
 
+  // 获得设备关联责任人 分页
+  responsiblePage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-device/responsiblePage`, params })
+  },
+
+  // 获得设备关联责任人 分页
+  simpleDevices: async (params: any) => {
+    return await request.get({ url: `/rq/iot-device/simple-list`, params })
+  },
+
   // 查询 设备bom关联 列表分页
   deviceAssociateBomPage: async (params: any) => {
     return await request.get({ url: `/rq/iot-device/deviceAssociateBomPage`, params })

+ 48 - 0
src/api/pms/iotdeviceperson/index.ts

@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+
+// 设备负责人分配 VO
+export interface IotDevicePersonVO {
+  id: number // 主键id
+  deviceId: number // 设备id
+  personId: number // 负责人id
+  status: number // 开启状态 0启用  1停用
+  remark: string // 备注
+}
+
+// 设备负责人分配 API
+export const IotDevicePersonApi = {
+  // 查询设备负责人分配分页
+  getIotDevicePersonPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-device-person/page`, params })
+  },
+
+  // 查询设备负责人分配详情
+  getIotDevicePerson: async (id: number) => {
+    return await request.get({ url: `/pms/iot-device-person/get?id=` + id })
+  },
+
+  // 新增设备负责人分配
+  createIotDevicePerson: async (data: IotDevicePersonVO) => {
+    return await request.post({ url: `/pms/iot-device-person/create`, data })
+  },
+
+  // 保存 设备与人员 的关联关系
+  saveDevicePersonRelation: async (data: any) => {
+    return await request.post({ url: `/pms/iot-device-person/saveDevicePersons`, data })
+  },
+
+  // 修改设备负责人分配
+  updateIotDevicePerson: async (data: IotDevicePersonVO) => {
+    return await request.put({ url: `/pms/iot-device-person/update`, data })
+  },
+
+  // 删除设备负责人分配
+  deleteIotDevicePerson: async (id: number) => {
+    return await request.delete({ url: `/pms/iot-device-person/delete?id=` + id })
+  },
+
+  // 导出设备负责人分配 Excel
+  exportIotDevicePerson: async (params) => {
+    return await request.download({ url: `/pms/iot-device-person/export-excel`, params })
+  },
+}

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

@@ -31,6 +31,11 @@ export const IotMainWorkOrderApi = {
     return await request.get({ url: `/pms/iot-main-work-order/page`, params })
   },
 
+  // 根据保养结果统计工单数量
+  allWorkOrderCountByResult: async () => {
+    return await request.get({ url: `/pms/iot-main-work-order/allWorkOrderCountByResult` })
+  },
+
   // 查询保养工单详情
   getIotMainWorkOrder: async (id: number) => {
     return await request.get({ url: `/pms/iot-main-work-order/get?id=` + id })

+ 6 - 0
src/api/system/user/index.ts

@@ -5,6 +5,7 @@ export interface UserVO {
   username: string
   nickname: string
   deptId: number
+  deptName: string
   postIds: string[]
   email: string
   mobile: string
@@ -22,6 +23,11 @@ export const getUserPage = (params: PageParam) => {
   return request.get({ url: '/system/user/page', params })
 }
 
+// 查询精简用户信息列表
+export const simpleUserList = (params: PageParam) => {
+  return request.get({ url: '/system/user/simpleUserList', params })
+}
+
 // 查询用户详情
 export const getUser = (id: number) => {
   return request.get({ url: '/system/user/get?id=' + id })

+ 39 - 0
src/router/modules/remaining.ts

@@ -218,6 +218,45 @@ const remainingRouter: AppRouteRecordRaw[] = [
           activeMenu: '/device/bom'
         }
       },
+      {
+        path: 'device/person',
+        component: () => import('@/views/pms/device/ConfigDevicePerson.vue'),
+        name: 'ConfigDevicePerson',
+        meta: {
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:add',
+          title: '设备责任人',
+          activeMenu: '/device/person'
+        }
+      },
+      {
+        path: 'device/status',
+        component: () => import('@/views/pms/device/ConfigDeviceStatus.vue'),
+        name: 'ConfigDeviceStatus',
+        meta: {
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:add',
+          title: '调整设备状态',
+          activeMenu: '/device/status'
+        }
+      },
+      {
+        path: 'device/allot',
+        component: () => import('@/views/pms/device/ConfigDeviceAllot.vue'),
+        name: 'ConfigDeviceAllot',
+        meta: {
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:add',
+          title: '设备调拨',
+          activeMenu: '/device/allot'
+        }
+      }
     ]
   },
 

+ 23 - 1
src/views/pms/bom/index.vue

@@ -71,8 +71,8 @@
           v-if="refreshTable"
           style="width: 100%"
         >
-          <el-table-column prop="name" label="BOM节点" />
           <el-table-column prop="deviceCategoryName" label="设备分类" />
+          <el-table-column prop="name" label="BOM节点" />
           <el-table-column prop="sort" label="排序" />
           <el-table-column prop="status" label="状态" >
             <template #default="scope">
@@ -285,6 +285,20 @@ const handleDelete = async (id: number) => {
 onMounted(() => {
   getList()
 })
+
+// 自定义箭头展开位置
+const toggleRowExpansion = (row) => {
+  const $table = document.querySelector('.el-table__body-wrapper') as any
+  if ($table) {
+    $table.__vue__.toggleRowExpansion(row)
+  }
+}
+
+const isExpanded = (row) => {
+  const $table = document.querySelector('.el-table__body-wrapper') as any
+  return $table?.__vue__.store.states.expandRows.value.includes(row)
+}
+
 </script>
 
 <style scoped>
@@ -300,4 +314,12 @@ onMounted(() => {
 .gap-4px {
   gap: 4px;
 }
+
+/* BOM节点名称样式 */
+.bom-node-name {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
 </style>

+ 495 - 0
src/views/pms/device/ConfigDeviceAllot.vue

@@ -0,0 +1,495 @@
+<template>
+  <div class="container">
+    <el-row :gutter="20">
+      <!-- 左侧设备列表 -->
+      <el-col :span="12">
+        <div class="card">
+          <h3>设备列表</h3>
+          <div class="dept-select">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId1"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择设备所属部门"
+              @node-click="handleDeptDeviceTreeNodeClick"
+            />
+          </div>
+          <el-scrollbar height="400px">
+            <el-checkbox-group v-model="selectedDevices"  @change="handleDeviceChange">
+              <div
+                v-for="device in simpleDevices"
+                :key="device.id"
+                class="checkbox-item"
+              >
+                <el-checkbox :label="device.id">
+                  {{ device.deviceCode }} ({{ device.deviceName }})
+                </el-checkbox>
+              </div>
+            </el-checkbox-group>
+          </el-scrollbar>
+        </div>
+      </el-col>
+
+      <!-- 右侧责任人选择 -->
+      <el-col :span="12" >
+        <div class="card" height="400px">
+          <h3>部门列表</h3>
+            <ContentWrap class="h-1/1" v-if="true" height="400px">
+              <DeptTree @node-click="handleDeptNodeClick" height="400px" :radio-value="selectedDeptId"/>
+            </ContentWrap>
+
+          <div class="action-bar">
+
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 暂存关联列表 -->
+    <div class="temp-list card">
+      <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 label="操作" width="120">
+          <template #default="{ row }">
+            <el-button
+              type="danger"
+              size="small"
+              @click="removeTempRelation(row.deviceIds)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="submit-area">
+        <el-button
+          type="primary"
+          size="large"
+          @click="submitRelations"
+          :disabled="tempRelations.length === 0"
+        >
+          提交全部关联关系
+        </el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+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 DeptTree from "@/views/system/user/DeptTree.vue";
+
+defineOptions({ name: 'ConfigDeviceAllot' })
+
+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,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined as string | undefined,
+  deptId1: undefined as string | undefined,
+  deviceStatus: undefined,
+  reason: '',
+  assetProperty: undefined,
+  picUrl: undefined,
+})
+
+const queryParams = reactive({
+  deptId1: formData.value.deptId1,
+  deptId: formData.value.deptId,
+})
+
+const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
+
+const userList = ref([
+  { id: 'u1', name: '张三', position: '工程师', deptId: 'dept1' },
+  { id: 'u2', name: '李四', position: '技术员', deptId: 'dept1' },
+  { id: 'u3', name: '王五', position: '质检员', deptId: 'dept2' },
+  // 更多用户...
+])
+
+// 响应式数据
+const selectedDevice = ref<number>(0)
+const selectedDept = ref('')
+const selectedUsers = ref<number[]>([])
+const tempRelations = ref<Array<{
+  deviceId: number
+  deviceNames: string
+  deviceStatus: 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
+  }
+
+  const existIndex = tempRelations.value
+    .findIndex(r => r.deviceId === selectedDevice.value)
+
+  if (existIndex > -1) {
+    tempRelations.value[existIndex] = newRelation
+  } else {
+    tempRelations.value.push(newRelation)
+  }
+}
+
+// 修改 暂时 保存关联方法
+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
+  await getDeviceList()
+}
+
+/** 获得 部门下的设备 列表 */
+const getDeviceList = async () => {
+  try {
+    const params = { deptId: formData.value.deptId1 }
+    const data = await IotDeviceApi.simpleDevices(params)
+    simpleDevices.value = data || []
+  } catch (error) {
+    simpleDevices.value = []
+    console.error('获取设备列表失败:', error)
+  }
+}
+
+/** 处理 部门-人员 树 被点击 */
+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[]) => {
+  tempRelations.value = tempRelations.value.filter(
+    r => r.deviceIds.join() !== deviceIds.join()
+  )
+}
+
+const submitRelations = async () => {
+  try {
+    // 转换为后端需要的格式
+    const submitData = tempRelations.value.flatMap(relation => {
+      return relation.deviceIds.map(deviceId => ({
+        deviceId,
+        userIds: relation.userIds
+      }))
+    })
+    await IotDevicePersonApi.saveDevicePersonRelation(submitData)
+    // 模拟API调用
+    console.log('提交数据:', submitData)
+    ElMessage.success('关联关系提交成功')
+    tempRelations.value = []
+  } catch (error) {
+    ElMessage.error('提交失败,请重试')
+  }
+}
+
+/** 初始化 */
+onMounted(async () => {
+  // 初始化部门树 根据选择的部门 查询设备列表 部门人员列表
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // getList()
+})
+
+</script>
+
+<style scoped>
+.container {
+  padding: 20px;
+}
+
+.card {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 20px;
+  margin-bottom: 20px;
+  background: white;
+}
+
+.list-item {
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.dept-select {
+  margin-bottom: 20px;
+}
+
+.user-list {
+  margin-bottom: 20px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 10px;
+}
+
+.action-bar {
+  text-align: right;
+}
+
+.temp-list {
+  margin-top: 20px;
+}
+
+.submit-area {
+  margin-top: 20px;
+  text-align: center;
+}
+
+h3 {
+  margin: 0 0 15px 0;
+  color: #303133;
+}
+
+.radio-item {
+  width: 100%;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.radio-item .el-radio {
+  width: 100%;
+  height: 100%;
+}
+
+.radio-item .el-radio__label {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.dept-node {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  width: 100%;
+  padding: 5px 0;
+
+  :deep(.el-radio) {
+    flex: 1;
+    .el-radio__label {
+      font-size: 14px;
+    }
+  }
+
+  .el-tag {
+    margin-left: auto;
+  }
+}
+
+</style>

+ 446 - 0
src/views/pms/device/ConfigDevicePerson.vue

@@ -0,0 +1,446 @@
+<template>
+  <div class="container">
+    <el-row :gutter="20">
+      <!-- 左侧设备列表 -->
+      <el-col :span="12">
+        <div class="card">
+          <h3>设备列表</h3>
+          <div class="dept-select">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId1"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择设备所属部门"
+              @node-click="handleDeptDeviceTreeNodeClick"
+            />
+          </div>
+          <el-scrollbar height="400px">
+            <el-radio-group v-model="selectedDevice">
+              <div
+                v-for="device in simpleDevices"
+                :key="device.id"
+                class="radio-item"
+              >
+                <el-radio :label="device.id">
+                  {{ device.deviceCode }} ({{ device.deviceName }})
+                </el-radio>
+              </div>
+            </el-radio-group>
+          </el-scrollbar>
+        </div>
+      </el-col>
+
+      <!-- 右侧责任人选择 -->
+      <el-col :span="12">
+        <div class="card">
+          <h3>责任人关联设置</h3>
+          <div class="dept-select">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择人员所属部门"
+              @node-click="handleDeptUserTreeNodeClick"
+            />
+            <!--
+            <el-select
+              v-model="selectedDept"
+              placeholder="请选择部门"
+              @change="loadUsers"
+            >
+              <el-option
+                v-for="dept in departmentList"
+                :key="dept.id"
+                :label="dept.name"
+                :value="dept.id"
+              />
+            </el-select> -->
+          </div>
+
+          <el-scrollbar height="300px" class="user-list">
+            <el-checkbox-group v-model="selectedUsers" @change="handleUserSelectionChange">
+              <div
+                v-for="user in simpleUsers"
+                :key="user.id"
+                class="list-item"
+              >
+                <el-checkbox :label="user.id">
+                  {{ user.nickname }} ({{ user.deptName }})
+                </el-checkbox>
+              </div>
+            </el-checkbox-group>
+          </el-scrollbar>
+
+          <div class="action-bar">
+            <!--
+            <el-button
+              type="primary"
+              @click="saveTempRelation"
+              :disabled="!canSave"
+            >
+              确定关联
+            </el-button>
+            <el-button @click="clearSelection">清空选择</el-button> -->
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 暂存关联列表 -->
+    <div class="temp-list card">
+      <h3>待提交的关联关系</h3>
+      <el-table :data="tempRelations" style="width: 100%">
+        <el-table-column prop="deviceNames" label="设备" width="200" />
+        <el-table-column prop="userNames" label="关联责任人" />
+        <el-table-column label="操作" width="120">
+          <template #default="{ row }">
+            <el-button
+              type="danger"
+              size="small"
+              @click="removeTempRelation(row.deviceIds)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="submit-area">
+        <el-button
+          type="primary"
+          size="large"
+          @click="submitRelations"
+          :disabled="tempRelations.length === 0"
+        >
+          提交全部关联关系
+        </el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+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";
+
+defineOptions({ name: 'ConfigDevicePerson' })
+
+// 模拟数据
+const deviceList = ref([
+  { id: 'd1', name: '数控机床', type: '生产设备' },
+  { id: 'd2', name: '检测仪', type: '检测设备' },
+  { id: 'd3', name: '包装机', type: '包装设备' },
+  // 更多设备...
+])
+
+const simpleDevices = ref<IotDeviceVO[]>([])
+const simpleUsers = ref<UserVO[]>([])
+
+const deptList = ref<Tree[]>([]) // 树形结构
+
+const formData = ref({
+  id: undefined,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined as string | undefined,
+  deptId1: undefined as string | undefined,
+  deviceStatus: undefined,
+  assetProperty: undefined,
+  picUrl: undefined,
+})
+
+const queryParams = reactive({
+  deptId1: formData.value.deptId1,
+  deptId: formData.value.deptId,
+})
+
+const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
+
+const departmentList = ref([
+  { id: 'dept1', name: '生产部' },
+  { id: 'dept2', name: '质检部' },
+  { id: 'dept3', name: '设备部' },
+])
+
+const userList = ref([
+  { id: 'u1', name: '张三', position: '工程师', deptId: 'dept1' },
+  { id: 'u2', name: '李四', position: '技术员', deptId: 'dept1' },
+  { id: 'u3', name: '王五', position: '质检员', deptId: 'dept2' },
+  // 更多用户...
+])
+
+// 响应式数据
+const selectedDevice = ref<number>(0)
+const selectedDept = ref('')
+const selectedUsers = ref<number[]>([])
+const tempRelations = ref<Array<{
+  deviceIds: number[]
+  deviceNames: string
+  userIds: number[]
+  userNames: string
+}>>([])
+
+// 计算属性
+const filteredUsers = computed(() => {
+  return userList.value.filter(user => user.deptId === selectedDept.value)
+})
+
+const canSave = computed(() => {
+  return !!selectedDevice.value && selectedUsers.value.length > 0
+})
+
+// 方法
+const loadUsers = () => {
+  selectedUsers.value = []
+}
+
+// 添加设备选择监听
+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 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
+  await getDeviceList()
+}
+
+/** 获得 部门下的设备 列表 */
+const getDeviceList = async () => {
+  try {
+    const params = { deptId: formData.value.deptId1 }
+    const data = await IotDeviceApi.simpleDevices(params)
+    simpleDevices.value = data || []
+  } catch (error) {
+    simpleDevices.value = []
+    console.error('获取设备列表失败:', error)
+  }
+}
+
+/** 处理 部门-人员 树 被点击 */
+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[]) => {
+  tempRelations.value = tempRelations.value.filter(
+    r => r.deviceIds.join() !== deviceIds.join()
+  )
+}
+
+const submitRelations = async () => {
+  try {
+    // 转换为后端需要的格式
+    const submitData = tempRelations.value.flatMap(relation => {
+      return relation.deviceIds.map(deviceId => ({
+        deviceId,
+        userIds: relation.userIds
+      }))
+    })
+    await IotDevicePersonApi.saveDevicePersonRelation(submitData)
+    // 模拟API调用
+    console.log('提交数据:', submitData)
+    ElMessage.success('关联关系提交成功')
+    tempRelations.value = []
+  } catch (error) {
+    ElMessage.error('提交失败,请重试')
+  }
+}
+
+/** 初始化 */
+onMounted(async () => {
+  // 初始化部门树 根据选择的部门 查询设备列表 部门人员列表
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // getList()
+})
+
+</script>
+
+<style scoped>
+.container {
+  padding: 20px;
+}
+
+.card {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 20px;
+  margin-bottom: 20px;
+  background: white;
+}
+
+.list-item {
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.dept-select {
+  margin-bottom: 20px;
+}
+
+.user-list {
+  margin-bottom: 20px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 10px;
+}
+
+.action-bar {
+  text-align: right;
+}
+
+.temp-list {
+  margin-top: 20px;
+}
+
+.submit-area {
+  margin-top: 20px;
+  text-align: center;
+}
+
+h3 {
+  margin: 0 0 15px 0;
+  color: #303133;
+}
+
+.radio-item {
+  width: 100%;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.radio-item .el-radio {
+  width: 100%;
+  height: 100%;
+}
+
+.radio-item .el-radio__label {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>

+ 450 - 0
src/views/pms/device/ConfigDeviceStatus.vue

@@ -0,0 +1,450 @@
+<template>
+  <div class="container">
+    <el-row :gutter="20">
+      <!-- 左侧设备列表 -->
+      <el-col :span="12">
+        <div class="card">
+          <h3>设备列表</h3>
+          <div class="dept-select">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId1"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择设备所属部门"
+              @node-click="handleDeptDeviceTreeNodeClick"
+            />
+          </div>
+          <el-scrollbar height="400px">
+            <el-checkbox-group v-model="selectedDevices"  @change="handleDeviceChange">
+              <div
+                v-for="device in simpleDevices"
+                :key="device.id"
+                class="checkbox-item"
+              >
+                <el-checkbox :label="device.id">
+                  {{ device.deviceCode }} ({{ device.deviceName }})
+                </el-checkbox>
+              </div>
+            </el-checkbox-group>
+          </el-scrollbar>
+        </div>
+      </el-col>
+
+      <!-- 右侧责任人选择 -->
+      <el-col :span="12">
+        <div class="card">
+          <h3>设备状态</h3>
+          <div class="dept-select">
+            <el-select v-model="formData.deviceStatus" placeholder="请选择" clearable>
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
+                :key="dict.label"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </div>
+
+          <el-input
+            v-model="formData.reason"
+            placeholder="请输入调整原因"
+            :disabled="!selectedDevice"
+            class="reason-input"
+            type="textarea"
+            :rows="3"
+            @blur="saveCurrentRelation"
+          />
+
+          <div class="action-bar">
+
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 暂存关联列表 -->
+    <div class="temp-list card">
+      <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 label="操作" width="120">
+          <template #default="{ row }">
+            <el-button
+              type="danger"
+              size="small"
+              @click="removeTempRelation(row.deviceIds)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="submit-area">
+        <el-button
+          type="primary"
+          size="large"
+          @click="submitRelations"
+          :disabled="tempRelations.length === 0"
+        >
+          提交全部关联关系
+        </el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+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";
+
+defineOptions({ name: 'ConfigDeviceStatus' })
+
+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,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined as string | undefined,
+  deptId1: undefined as string | undefined,
+  deviceStatus: undefined,
+  reason: '',
+  assetProperty: undefined,
+  picUrl: undefined,
+})
+
+const queryParams = reactive({
+  deptId1: formData.value.deptId1,
+  deptId: formData.value.deptId,
+})
+
+const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
+
+const userList = ref([
+  { id: 'u1', name: '张三', position: '工程师', deptId: 'dept1' },
+  { id: 'u2', name: '李四', position: '技术员', deptId: 'dept1' },
+  { id: 'u3', name: '王五', position: '质检员', deptId: 'dept2' },
+  // 更多用户...
+])
+
+// 响应式数据
+const selectedDevice = ref<number>(0)
+const selectedDept = ref('')
+const selectedUsers = ref<number[]>([])
+const tempRelations = ref<Array<{
+  deviceId: number
+  deviceNames: string
+  deviceStatus: number
+  reason: string
+}>>([])
+
+const canSave = computed(() => {
+  return !!selectedDevice.value && selectedUsers.value.length > 0
+})
+
+// 方法
+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
+  }
+
+  const existIndex = tempRelations.value
+    .findIndex(r => r.deviceId === selectedDevice.value)
+
+  if (existIndex > -1) {
+    tempRelations.value[existIndex] = newRelation
+  } else {
+    tempRelations.value.push(newRelation)
+  }
+}
+
+// 修改 暂时 保存关联方法
+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
+  await getDeviceList()
+}
+
+/** 获得 部门下的设备 列表 */
+const getDeviceList = async () => {
+  try {
+    const params = { deptId: formData.value.deptId1 }
+    const data = await IotDeviceApi.simpleDevices(params)
+    simpleDevices.value = data || []
+  } catch (error) {
+    simpleDevices.value = []
+    console.error('获取设备列表失败:', error)
+  }
+}
+
+/** 处理 部门-人员 树 被点击 */
+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[]) => {
+  tempRelations.value = tempRelations.value.filter(
+    r => r.deviceIds.join() !== deviceIds.join()
+  )
+}
+
+const submitRelations = async () => {
+  try {
+    // 转换为后端需要的格式
+    const submitData = tempRelations.value.flatMap(relation => {
+      return relation.deviceIds.map(deviceId => ({
+        deviceId,
+        userIds: relation.userIds
+      }))
+    })
+    await IotDevicePersonApi.saveDevicePersonRelation(submitData)
+    // 模拟API调用
+    console.log('提交数据:', submitData)
+    ElMessage.success('关联关系提交成功')
+    tempRelations.value = []
+  } catch (error) {
+    ElMessage.error('提交失败,请重试')
+  }
+}
+
+/** 初始化 */
+onMounted(async () => {
+  // 初始化部门树 根据选择的部门 查询设备列表 部门人员列表
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // getList()
+})
+
+</script>
+
+<style scoped>
+.container {
+  padding: 20px;
+}
+
+.card {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 20px;
+  margin-bottom: 20px;
+  background: white;
+}
+
+.list-item {
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.dept-select {
+  margin-bottom: 20px;
+}
+
+.user-list {
+  margin-bottom: 20px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 10px;
+}
+
+.action-bar {
+  text-align: right;
+}
+
+.temp-list {
+  margin-top: 20px;
+}
+
+.submit-area {
+  margin-top: 20px;
+  text-align: center;
+}
+
+h3 {
+  margin: 0 0 15px 0;
+  color: #303133;
+}
+
+.radio-item {
+  width: 100%;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.radio-item .el-radio {
+  width: 100%;
+  height: 100%;
+}
+
+.radio-item .el-radio__label {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+</style>

+ 318 - 0
src/views/pms/device/DeviceAllot.vue

@@ -0,0 +1,318 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="资产编码" prop="deviceCode">
+            <el-input
+              v-model="queryParams.deviceCode"
+              placeholder="请输入资产编码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input
+              v-model="queryParams.deviceName"
+              placeholder="请输入设备名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="品牌" prop="brand">
+            <el-input
+              v-model="queryParams.brand"
+              placeholder="请输入品牌"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="设备状态" label-width="85px" prop="deviceStatus">
+            <el-select
+              v-model="queryParams.deviceStatus"
+              placeholder="设备状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="资产性质" prop="assetProperty">
+            <el-select
+              v-model="queryParams.assetProperty"
+              placeholder="资产性质"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
+              ><Icon icon="ep:search" class="mr-5px" /> 更多查询</el-button
+            >
+            <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
+              ><Icon icon="ep:search" class="mr-5px" /> 收起查询</el-button
+            >
+            <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', undefined, queryParams.deptId)"
+              v-hasPermi="['rq:iot-device:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 设备调拨
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['rq:iot-device: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="序号" width="60" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="资产编码" align="center" prop="deviceCode" />
+          <el-table-column label="设备名称" align="center" prop="deviceName">
+            <template #default="scope">
+              <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
+                {{ scope.row.deviceName }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column label="所在部门" align="center" prop="deptName" />
+          <el-table-column label="设备状态" align="center" prop="deviceStatus">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
+            </template>
+          </el-table-column>
+          <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="['rq:iot-device:update']"
+              >
+                编辑
+              </el-button>
+            </template> -->
+          </el-table-column>
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
+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'
+
+/** 设备调拨 列表 */
+defineOptions({ name: 'IotDeviceAllot' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+
+const loading = ref(true) // 列表的加载中
+const ifShow = ref(false)
+const isDetail = ref(false) // 是否查看详情
+const list = ref<IotDeviceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined,
+  deviceStatus: undefined,
+  assetProperty: undefined,
+  picUrl: undefined,
+  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 shou = (tree) =>{
+  treeShow.value = !tree
+  if (tree) {
+    contentSpan.value = 20
+  } else {
+    contentSpan.value = 24
+  }
+}
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceApi.responsiblePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+const moreQuery = (show) => {
+  ifShow.value = show
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  //修改
+  if (typeof id === 'number') {
+    push({ name: 'DeviceDetailEdit', params: { id } })
+    return
+  }
+  // 新增
+  push({ name: 'ConfigDeviceAllot', params: {} })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotDeviceApi.deleteIotDevice(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const handleDetail = (id: number) => {
+  push({ name: 'DeviceDetailInfo', params: { id } })
+}
+
+const handleUpload = (id: number) => {
+  push({ name: 'DeviceUpload', params: { id } })
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotDeviceApi.exportIotDevice(queryParams)
+    download.excel(data, '设备台账.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+const { wsCache } = useCache()
+/** 初始化 **/
+onMounted(() => {
+  const user = wsCache.get(CACHE_KEY.USER)
+  queryParams.deptId = user.user.deptId
+  getList()
+})
+</script>
+<style scoped></style>

+ 315 - 0
src/views/pms/device/DevicePerson.vue

@@ -0,0 +1,315 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="资产编码" prop="deviceCode">
+            <el-input
+              v-model="queryParams.deviceCode"
+              placeholder="请输入资产编码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input
+              v-model="queryParams.deviceName"
+              placeholder="请输入设备名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="品牌" prop="brand">
+            <el-input
+              v-model="queryParams.brand"
+              placeholder="请输入品牌"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="设备状态" label-width="85px" prop="deviceStatus">
+            <el-select
+              v-model="queryParams.deviceStatus"
+              placeholder="设备状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="资产性质" prop="assetProperty">
+            <el-select
+              v-model="queryParams.assetProperty"
+              placeholder="资产性质"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
+              ><Icon icon="ep:search" class="mr-5px" /> 更多查询</el-button
+            >
+            <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
+              ><Icon icon="ep:search" class="mr-5px" /> 收起查询</el-button
+            >
+            <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', undefined, queryParams.deptId)"
+              v-hasPermi="['rq:iot-device:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 设置责任人
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['rq:iot-device: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="序号" width="60" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="资产编码" align="center" prop="deviceCode" />
+          <el-table-column label="设备名称" align="center" prop="deviceName">
+            <template #default="scope">
+              <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
+                {{ scope.row.deviceName }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column label="所在部门" align="center" prop="deptName" />
+          <el-table-column label="责任人" align="center" prop="responsibleNames" />
+          <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="['rq:iot-device:update']"
+              >
+                编辑
+              </el-button>
+            </template> -->
+          </el-table-column>
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
+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'
+
+/** 设备台账 列表 */
+defineOptions({ name: 'IotDevicePerson' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+
+const loading = ref(true) // 列表的加载中
+const ifShow = ref(false)
+const isDetail = ref(false) // 是否查看详情
+const list = ref<IotDeviceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined,
+  deviceStatus: undefined,
+  assetProperty: undefined,
+  picUrl: undefined,
+  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 shou = (tree) =>{
+  treeShow.value = !tree
+  if (tree) {
+    contentSpan.value = 20
+  } else {
+    contentSpan.value = 24
+  }
+}
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceApi.responsiblePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+const moreQuery = (show) => {
+  ifShow.value = show
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  //修改
+  if (typeof id === 'number') {
+    push({ name: 'DeviceDetailEdit', params: { id } })
+    return
+  }
+  // 新增
+  push({ name: 'ConfigDevicePerson', params: {} })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotDeviceApi.deleteIotDevice(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const handleDetail = (id: number) => {
+  push({ name: 'DeviceDetailInfo', params: { id } })
+}
+
+const handleUpload = (id: number) => {
+  push({ name: 'DeviceUpload', params: { id } })
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotDeviceApi.exportIotDevice(queryParams)
+    download.excel(data, '设备台账.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+const { wsCache } = useCache()
+/** 初始化 **/
+onMounted(() => {
+  const user = wsCache.get(CACHE_KEY.USER)
+  queryParams.deptId = user.user.deptId
+  getList()
+})
+</script>
+<style scoped></style>

+ 318 - 0
src/views/pms/device/DeviceStatus.vue

@@ -0,0 +1,318 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="资产编码" prop="deviceCode">
+            <el-input
+              v-model="queryParams.deviceCode"
+              placeholder="请输入资产编码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input
+              v-model="queryParams.deviceName"
+              placeholder="请输入设备名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item label="品牌" prop="brand">
+            <el-input
+              v-model="queryParams.brand"
+              placeholder="请输入品牌"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="设备状态" label-width="85px" prop="deviceStatus">
+            <el-select
+              v-model="queryParams.deviceStatus"
+              placeholder="设备状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item v-show="ifShow" label="资产性质" prop="assetProperty">
+            <el-select
+              v-model="queryParams.assetProperty"
+              placeholder="资产性质"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
+              ><Icon icon="ep:search" class="mr-5px" /> 更多查询</el-button
+            >
+            <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
+              ><Icon icon="ep:search" class="mr-5px" /> 收起查询</el-button
+            >
+            <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', undefined, queryParams.deptId)"
+              v-hasPermi="['rq:iot-device:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 调整状态
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['rq:iot-device: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="序号" width="60" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="资产编码" align="center" prop="deviceCode" />
+          <el-table-column label="设备名称" align="center" prop="deviceName">
+            <template #default="scope">
+              <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
+                {{ scope.row.deviceName }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column label="所在部门" align="center" prop="deptName" />
+          <el-table-column label="设备状态" align="center" prop="deviceStatus">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
+            </template>
+          </el-table-column>
+          <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="['rq:iot-device:update']"
+              >
+                编辑
+              </el-button>
+            </template> -->
+          </el-table-column>
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
+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'
+
+/** 设备台账 列表 */
+defineOptions({ name: 'IotDeviceStatus' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+
+const loading = ref(true) // 列表的加载中
+const ifShow = ref(false)
+const isDetail = ref(false) // 是否查看详情
+const list = ref<IotDeviceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceCode: undefined,
+  deviceName: undefined,
+  brand: undefined,
+  model: undefined,
+  deptId: undefined,
+  deviceStatus: undefined,
+  assetProperty: undefined,
+  picUrl: undefined,
+  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 shou = (tree) =>{
+  treeShow.value = !tree
+  if (tree) {
+    contentSpan.value = 20
+  } else {
+    contentSpan.value = 24
+  }
+}
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceApi.responsiblePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+const moreQuery = (show) => {
+  ifShow.value = show
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  //修改
+  if (typeof id === 'number') {
+    push({ name: 'DeviceDetailEdit', params: { id } })
+    return
+  }
+  // 新增
+  push({ name: 'ConfigDeviceStatus', params: {} })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotDeviceApi.deleteIotDevice(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const handleDetail = (id: number) => {
+  push({ name: 'DeviceDetailInfo', params: { id } })
+}
+
+const handleUpload = (id: number) => {
+  push({ name: 'DeviceUpload', params: { id } })
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotDeviceApi.exportIotDevice(queryParams)
+    download.excel(data, '设备台账.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+const { wsCache } = useCache()
+/** 初始化 **/
+onMounted(() => {
+  const user = wsCache.get(CACHE_KEY.USER)
+  queryParams.deptId = user.user.deptId
+  getList()
+})
+</script>
+<style scoped></style>

+ 109 - 0
src/views/pms/iotdeviceperson/IotDevicePersonForm.vue

@@ -0,0 +1,109 @@
+<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="负责人id" prop="personId">
+        <el-input v-model="formData.personId" placeholder="请输入负责人id" />
+      </el-form-item>
+      <el-form-item label="开启状态 0启用  1停用" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </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 { IotDevicePersonApi, IotDevicePersonVO } from '@/api/pms/iotdeviceperson'
+
+/** 设备负责人分配 表单 */
+defineOptions({ name: 'IotDevicePersonForm' })
+
+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,
+  personId: undefined,
+  status: undefined,
+  remark: undefined,
+})
+const formRules = reactive({
+  deviceId: [{ required: true, message: '设备id不能为空', trigger: 'blur' }],
+})
+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 IotDevicePersonApi.getIotDevicePerson(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 IotDevicePersonVO
+    if (formType.value === 'create') {
+      await IotDevicePersonApi.createIotDevicePerson(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotDevicePersonApi.updateIotDevicePerson(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    deviceId: undefined,
+    personId: undefined,
+    status: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 221 - 0
src/views/pms/iotdeviceperson/index.vue

@@ -0,0 +1,221 @@
+<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="负责人id" prop="personId">
+        <el-input
+          v-model="queryParams.personId"
+          placeholder="请输入负责人id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="开启状态 0启用  1停用" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择开启状态 0启用  1停用"
+          clearable
+          class="!w-240px"
+        >
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </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-person:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-device-person: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="负责人id" align="center" prop="personId" />
+      <el-table-column label="开启状态 0启用  1停用" align="center" prop="status" />
+      <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-person:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['pms:iot-device-person: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>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotDevicePersonForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotDevicePersonApi, IotDevicePersonVO } from '@/api/pms/iotdeviceperson'
+import IotDevicePersonForm from './IotDevicePersonForm.vue'
+
+/** 设备负责人分配 列表 */
+defineOptions({ name: 'IotDevicePerson' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotDevicePersonVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceId: undefined,
+  personId: undefined,
+  status: undefined,
+  remark: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDevicePersonApi.getIotDevicePersonPage(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 IotDevicePersonApi.deleteIotDevicePerson(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotDevicePersonApi.exportIotDevicePerson(queryParams)
+    download.excel(data, '设备负责人分配.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 24 - 8
src/views/pms/iotmaincalendar/index.vue

@@ -18,6 +18,7 @@ import { useRouter } from 'vue-router'
 import FullCalendar from '@fullcalendar/vue3'
 import dayGridPlugin from '@fullcalendar/daygrid'
 import interactionPlugin from '@fullcalendar/interaction'
+import { IotMainWorkOrderApi, IotMainWorkOrderVO } from '@/api/pms/iotmainworkorder'
 import '@fullcalendar/core/locales-all'
 import 'bootstrap-icons/font/bootstrap-icons.css'
 const { push } = useRouter() // 路由跳转
@@ -130,11 +131,13 @@ function handleDayClick(event) {
 }
 
 // 初始化
-onMounted(() => {
+onMounted(async () => {
   const calendarApi = calendar.value.getApi()
   calendarApi.setOption('eventDidMount', (info) => {
     info.el.addEventListener('click', handleDayClick)
   })
+  const data = await IotMainWorkOrderApi.allWorkOrderCountByResult();
+
 })
 </script>
 
@@ -173,15 +176,15 @@ onMounted(() => {
 }
 
 :deep(.calendar-day-content) {
-  padding: 5px;
-  font-size: 0.9em;
+  padding: 4px;
+  font-size: 0.85em;
 }
 
 :deep(.count-item) {
-  margin: 2px 0;
-  padding: 2px;
+  margin: 3px 0;
+  padding: 3px;
   border-radius: 3px;
-  transition: background 0.3s;
+  transition: background 0.2s;
 }
 
 :deep(.plan-count) {
@@ -197,11 +200,13 @@ onMounted(() => {
 }
 
 :deep(.count-item:hover) {
-  background: #f5f5f5;
+  background: rgba(240, 240, 240, 0.8);
+  transform: translateX(2px);
 }
 
 :deep(.bi) {
-  margin-right: 3px;
+  margin-right: 5px;
+  font-size: 0.9em;
 }
 
 /* 精确控制prev/next/today按钮 */
@@ -254,4 +259,15 @@ onMounted(() => {
   gap: 4px;
 }
 
+/* 新增样式 */
+:deep(.all-count) {
+  color: #1890ff;
+}
+:deep(.todo-count) {
+  color: #faad14;
+}
+:deep(.done-count) {
+  color: #52c41a;
+}
+
 </style>

+ 13 - 67
src/views/pms/iotmainworkorder/IotMainWorkOrder.vue

@@ -161,7 +161,7 @@
                   type="primary"
                   @click="openConfigDialog(scope.row)"
                 >
-                  配置
+                  推迟保养
                 </el-button>
               </div>
               <div style="margin-left: 12px">
@@ -442,6 +442,10 @@ const formData = ref({
   name: '',
   orderNumber: undefined,
   responsiblePerson: undefined,
+  actualStartTime: undefined,
+  actualEndTime: undefined,
+  cost: undefined,
+  otherCost: undefined,
   remark: undefined,
   status: undefined,
 })
@@ -671,8 +675,8 @@ const submitForm = async () => {
   // 校验表单
   await formRef.value.validate()
   // 校验表格数据
-  // const isValid = validateTableData()
-  // if (!isValid) return
+  const isValid = validateTableData()
+  if (!isValid) return
   // 提交请求
   formLoading.value = true
   try {
@@ -681,20 +685,9 @@ const submitForm = async () => {
       mainWorkOrderBom: list.value,
       mainWorkOrderMaterials: materialList.value
     }
-    debugger
     await IotMainWorkOrderApi.fillWorkOrder(data)
     message.success(t('common.createSuccess'))
     close()
-
-    /* if (formType.value === 'create') {
-      await IotMaintenancePlanApi.createIotMaintenancePlan(data)
-      message.success(t('common.createSuccess'))
-      close()
-    } else {
-      await IotMaintainApi.updateIotMaintain(data)
-      message.success(t('common.updateSuccess'))
-      close()
-    } */
     // 发送操作成功的事件
     emit('success')
   } finally {
@@ -746,71 +739,24 @@ const validateTableData = (): boolean => {
   let shouldBreak = false;
 
   if (list.value.length === 0) {
-    errorMessages.push('请至少添加一条设备保养明细')
+    errorMessages.push('工单明细不存在')
     isValid = false
     // 直接返回无需后续校验
-    message.error('请至少添加一条设备保养明细')
+    message.error('工单明细不存在')
     return isValid
   }
 
   list.value.forEach((row, index) => {
     if (shouldBreak) return;
     const rowNumber = index + 1 // 用户可见的行号从1开始
-    const deviceIdentifier = `${row.deviceCode}-${row.name}` // 设备标识
-    // 校验逻辑
-    const checkConfig = (ruleName: string, ruleValue: number, configField: keyof typeof row) => {
-      if (ruleValue === 0) { // 规则开启
-        if (!row[configField] || row[configField] <= 0) {
-          configErrors.push(`第 ${rowNumber} 行(${deviceIdentifier}):请点击【配置】维护${ruleName}上次保养值`)
-          isValid = false
-        }
-      }
-    }
-    // 里程校验逻辑
-    if (row.mileageRule === 0) { // 假设 0 表示开启状态
-      if (!row.nextRunningKilometers || row.nextRunningKilometers <= 0) {
-        errorMessages.push(`第 ${rowNumber} 行:开启里程规则必须填写有效的里程周期`)
-        isValid = false
-      }
-      // 再校验配置值
-      checkConfig('里程', row.mileageRule, 'lastRunningKilometers')
-    } else {
-      noRules.push(`第 ${rowNumber} 行:未设置里程规则`)
-    }
-    // 运行时间校验逻辑
-    if (row.runningTimeRule === 0) {
-      if (!row.nextRunningTime || row.nextRunningTime <= 0) {
-        errorMessages.push(`第 ${rowNumber} 行:开启运行时间规则必须填写有效的时间周期`)
-        isValid = false
-      }
-      checkConfig('运行时间', row.runningTimeRule, 'lastRunningTime')
-    } else {
-      noRules.push(`第 ${rowNumber} 行:未设置运行时间规则`)
-    }
-    // 自然日期校验逻辑
-    if (row.naturalDateRule === 0) {
-      if (!row.nextNaturalDate) {
-        errorMessages.push(`第 ${rowNumber} 行:开启自然日期规则必须填写有效的自然日期周期`)
-        isValid = false
-      }
-      checkConfig('自然日期', row.naturalDateRule, 'lastNaturalDate')
-    } else {
-      noRules.push(`第 ${rowNumber} 行:未设置自然日期规则`)
-    }
-    // 如果选中的一行记录未设置任何保养规则 提示 ‘保养项未设置任何保养规则’
-    if (noRules.length === 3) {
+    // const deviceIdentifier = `${row.deviceCode}-${row.name}` // 设备标识
+    if(row.deviceCode === null || row.deviceName === null){
+      errorMessages.push('设备状态错误')
       isValid = false
-      shouldBreak = true; // 设置标志变量为true,退出循环
-      noRulesErrorMessages.push('保养项至少设置1个保养规则')
     }
-    noRules.length = 0;
   })
   if (errorMessages.length > 0) {
-    message.error('设置保养规则后,请维护对应的周期值')
-  } else if (noRulesErrorMessages.length > 0) {
-    message.error(noRulesErrorMessages.pop())
-  } else if (configErrors.length > 0) {
-    message.error(configErrors.pop())
+    message.error('设备状态错误')
   }
   return isValid
 }

+ 2 - 3
src/views/pms/iotmainworkorder/WorkOrderMaterial.vue

@@ -76,7 +76,6 @@
           <template #default="scope">
             <el-input
               v-model="scope.row.quantity"
-              @input="handleInput(scope.row.quantity, 'quantity')"
               @click.stop=""
               @focus="scope.$el.querySelector('input').focus()"
             />
@@ -220,13 +219,13 @@ const handleClose = () => {
 const rowClassName = ({ row }: { row: any }) => {
   let className = '';
   if(row.bomMaterialFlag === 'Y'){
-    className = 'row-green-bg'
+    className = ''
   }
   if (row.materialSource === '本地库存' && row.bomMaterialFlag != 'Y') {
     className = ''
   }
   if (row.materialSource === 'sap库存') {
-    className = 'row-red-bg'
+    className = ''
   }
   return className
 }

+ 4 - 4
src/views/pms/iotmainworkorder/index.vue

@@ -17,15 +17,15 @@
           class="!w-240px"
         />
       </el-form-item>
-      <el-form-item label="审批状态" prop="auditStatus">
+      <el-form-item label="保养状态" prop="result">
         <el-select
-          v-model="queryParams.auditStatus"
-          placeholder="请选择审批状态"
+          v-model="queryParams.result"
+          placeholder="请选择保养状态"
           clearable
           class="!w-240px"
         >
           <el-option
-            v-for="dict in getStrDictOptions(DICT_TYPE.CRM_AUDIT_STATUS)"
+            v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_RESULT)"
             :key="dict.value"
             :label="dict.label"
             :value="dict.value"

+ 3 - 1
src/views/pms/iotsapstock/IotSapStockConfig.vue

@@ -91,7 +91,7 @@
       <el-table-column label="数量" align="center" prop="quantity" />
       <el-table-column label="单价" align="center" prop="unitPrice" />
       <el-table-column label="单位" align="center" prop="unit" />
-      <el-table-column label="安全库存" align="center" prop="safetyStock" />
+      <el-table-column label="安全库存" align="center" prop="safetyStock" :formatter="erpPriceTableColumnFormatter"/>
       <el-table-column
         label="创建时间"
         align="center"
@@ -142,6 +142,7 @@ import download from '@/utils/download'
 import { IotSapStockApi, IotSapStockVO } from '@/api/pms/iotsapstock'
 import IotSapStockForm from './IotSapStockForm.vue'
 import * as SapOrgApi from "@/api/system/saporg";
+import {erpPriceTableColumnFormatter} from "@/utils";
 
 /** PMS SAP 库存(通用库存/项目部库存) 列表 */
 defineOptions({ name: 'IotSapStockConfig' })
@@ -180,6 +181,7 @@ const queryParams = reactive({
   sort: undefined,
   status: undefined,
   remark: undefined,
+  configFlag: 'Y',
   createTime: [],
 })
 const queryFormRef = ref() // 搜索的表单

+ 2 - 2
src/views/pms/maintenance/index.vue

@@ -33,7 +33,7 @@
               class="!w-240px"
             />
           </el-form-item>
-
+          <!--
           <el-form-item label="状态" prop="status">
             <el-select
               v-model="queryParams.status"
@@ -43,7 +43,7 @@
             >
               <el-option label="请选择字典生成" value="" />
             </el-select>
-          </el-form-item>
+          </el-form-item> -->
           <el-form-item label="创建时间" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"