|
@@ -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>
|