|
|
@@ -0,0 +1,528 @@
|
|
|
+<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="deptName" style="margin-left: 20px">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.deptName"
|
|
|
+ placeholder="请输入部门名称"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
+ class="!w-200px"
|
|
|
+ />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="成套名称" prop="name">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.name"
|
|
|
+ placeholder="请输入成套名称"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
+ class="!w-200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button @click="handleAdd" type="primary"
|
|
|
+ ><Icon icon="ep:plus" class="mr-5px" />新增成套</el-button
|
|
|
+ >
|
|
|
+ <el-button @click="handleQuery"
|
|
|
+ ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
|
|
|
+ >
|
|
|
+ <el-button @click="resetQuery"
|
|
|
+ ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</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="t('monitor.serial')" width="70" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.$index + 1 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="部门名称" align="center" prop="deptName" />
|
|
|
+ <el-table-column label="成套名称" align="center" prop="name" />
|
|
|
+
|
|
|
+ <el-table-column label="描述" align="center" prop="remark" />
|
|
|
+ <el-table-column label="设备数量" align="center" prop="deviceCount">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ (scope.row.details && scope.row.details.length) || 0 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="主设备" align="center" prop="mainDeviceName">
|
|
|
+ <template #default="scope">
|
|
|
+ {{
|
|
|
+ scope.row.details.filter((item) => item.ifMaster)[0]?.deviceName +
|
|
|
+ ' ' +
|
|
|
+ scope.row.details.filter((item) => item.ifMaster)[0]?.deviceCode || '无'
|
|
|
+ }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column :label="t('devicePerson.operation')" align="center" min-width="120px">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
|
|
|
+ <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </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>
|
|
|
+
|
|
|
+ <!-- 新增/编辑成套设备对话框 -->
|
|
|
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" @close="cancel">
|
|
|
+ <template #header>
|
|
|
+ <div class="my-header" style="padding-bottom: 20px">
|
|
|
+ <span>{{ dialogTitle }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-form ref="formRef" :model="formData" :inline="true" :rules="formRules" label-width="80px">
|
|
|
+ <el-form-item label="成套名称" prop="name">
|
|
|
+ <el-input v-model="formData.name" placeholder="请输入成套名称" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item :label="t('iotDevice.dept')" prop="deptId">
|
|
|
+ <el-tree-select
|
|
|
+ v-model="formData.deptId"
|
|
|
+ :data="deptList"
|
|
|
+ :props="defaultProps"
|
|
|
+ check-strictly
|
|
|
+ node-key="id"
|
|
|
+ filterable
|
|
|
+ placeholder="请选择所在部门"
|
|
|
+ @change="handleDeptChange"
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="描述" prop="remark">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.remark"
|
|
|
+ style="width: 470px"
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入描述"
|
|
|
+ :rows="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="选择设备" prop="devices">
|
|
|
+ <div class="transfer-container">
|
|
|
+ <el-transfer
|
|
|
+ v-model="selectedDeviceIds"
|
|
|
+ :data="deviceOptions"
|
|
|
+ :titles="['设备列表', '已选择设备']"
|
|
|
+ :button-texts="['移除', '添加']"
|
|
|
+ filterable
|
|
|
+ :filter-method="filterDeviceMethod"
|
|
|
+ filter-placeholder="请输入设备名称"
|
|
|
+ @change="rightDeviceChange"
|
|
|
+ >
|
|
|
+ <template #left-empty>
|
|
|
+ <el-empty :image-size="60" :description="isEdit ? '加载中...' : '请选择设备'" />
|
|
|
+ </template>
|
|
|
+ <template #right-empty>
|
|
|
+ <el-empty :image-size="60" :description="isEdit ? '加载中...' : '请选择设备'" />
|
|
|
+ </template>
|
|
|
+ </el-transfer>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <div v-if="selectedDevices.length > 0" class="mt-4">
|
|
|
+ <el-form-item label="设置主设备" prop="mainDevice" label-width="100">
|
|
|
+ <el-select
|
|
|
+ v-model="mainDeviceId"
|
|
|
+ placeholder="请选择主设备"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ style="width: 200px"
|
|
|
+ @change="setMainDevice"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="device in selectedDevices"
|
|
|
+ :key="device.id"
|
|
|
+ :label="device.label"
|
|
|
+ :value="device.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="cancel">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="submit">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { IotDeviceApi } from '@/api/pms/device'
|
|
|
+import DeptTree from '@/views/system/user/DeptTree.vue'
|
|
|
+import { defaultProps, handleTree } from '@/utils/tree'
|
|
|
+import * as DeptApi from '@/api/system/dept'
|
|
|
+import { ElMessageBox } from 'element-plus'
|
|
|
+const deptList = ref<Tree[]>([]) // 树形结构
|
|
|
+
|
|
|
+defineOptions({ name: 'IotDeviceComplete' })
|
|
|
+
|
|
|
+const loading = ref(true) // 列表的加载中
|
|
|
+
|
|
|
+const { t } = useI18n()
|
|
|
+
|
|
|
+const list = ref([]) // 列表的数据
|
|
|
+const total = ref(0) // 列表的总页数
|
|
|
+const queryParams = reactive({
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ deptName: undefined,
|
|
|
+ name: undefined,
|
|
|
+ deptId: undefined
|
|
|
+})
|
|
|
+const queryFormRef = ref(null) // 搜索的表单
|
|
|
+
|
|
|
+const contentSpan = ref(20)
|
|
|
+const treeShow = ref(true)
|
|
|
+
|
|
|
+// 对话框相关
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const dialogTitle = ref('')
|
|
|
+const isEdit = ref(false)
|
|
|
+
|
|
|
+// 表单相关
|
|
|
+const formRef = ref()
|
|
|
+const formData = ref({
|
|
|
+ name: '',
|
|
|
+ details: [],
|
|
|
+ deptId: '',
|
|
|
+ remark: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const formRules = {
|
|
|
+ name: [{ required: true, message: '成套名称不能为空', trigger: 'blur' }],
|
|
|
+ code: [{ required: true, message: '成套编码不能为空', trigger: 'blur' }],
|
|
|
+ deptId: [{ required: true, message: '请选择部门', trigger: 'change' }],
|
|
|
+ devices: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (selectedDeviceIds.value.length === 0) {
|
|
|
+ callback(new Error('请至少选择一个设备'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ mainDevice: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (!mainDeviceId.value) {
|
|
|
+ callback(new Error('请选择主设备'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 部门树数据
|
|
|
+const selectedDeptId = ref<number | string>('')
|
|
|
+
|
|
|
+// 新增时部门改变时获取设备列表
|
|
|
+const handleDeptChange = async (deptId) => {
|
|
|
+ if (deptId) {
|
|
|
+ selectedDeptId.value = deptId
|
|
|
+ getDeviceList(deptId)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空主设备she 设备列表
|
|
|
+ selectedDevices.value = []
|
|
|
+ mainDeviceId.value = ''
|
|
|
+ selectedDeviceIds.value = []
|
|
|
+}
|
|
|
+// 获取设备列表
|
|
|
+const getDeviceList = async (deptId) => {
|
|
|
+ try {
|
|
|
+ const res = await IotDeviceApi.getIotDeviceSetOptions(deptId)
|
|
|
+ deviceOptions.value = res.map((item) => ({
|
|
|
+ key: item.id, // 始终使用id作为key
|
|
|
+ label: `${item.deviceName} (${item.deviceCode})`,
|
|
|
+ ...item
|
|
|
+ }))
|
|
|
+ } catch (err) {
|
|
|
+ console.error(err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 设备选择相关
|
|
|
+const deviceOptions = ref<any[]>([])
|
|
|
+const selectedDeviceIds = ref([])
|
|
|
+const selectedDevices = ref([])
|
|
|
+const mainDeviceId = ref('')
|
|
|
+
|
|
|
+/** 查询列表 */
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const data = await IotDeviceApi.getIotDeviceSetList(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 resetQuery = () => {
|
|
|
+ queryFormRef.value.resetFields()
|
|
|
+ handleQuery()
|
|
|
+}
|
|
|
+
|
|
|
+// 设备过滤方法
|
|
|
+const filterDeviceMethod = (query, item) => {
|
|
|
+ return item.label.toLowerCase().includes(query.toLowerCase())
|
|
|
+}
|
|
|
+
|
|
|
+// 更新已选择的设备列表
|
|
|
+const updateSelectedDevices = () => {
|
|
|
+ // 根据 selectedDeviceIds 从 deviceOptions 中找到对应的设备
|
|
|
+ selectedDevices.value = deviceOptions.value.filter((item) =>
|
|
|
+ selectedDeviceIds.value.includes(item.key)
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log('selectedDevices>>>>>>>>>>>>>>>>', selectedDevices.value)
|
|
|
+
|
|
|
+ // 构建 details 数据
|
|
|
+ formData.value.details = selectedDevices.value.map((item) => ({
|
|
|
+ deviceId: item.id,
|
|
|
+ deviceName: item.deviceName,
|
|
|
+ deviceCode: item.deviceCode,
|
|
|
+ deptId: item.deptId,
|
|
|
+ ifMaster: item.id === mainDeviceId.value ? true : false
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 如果主设备不在当前选择中,则清空主设备
|
|
|
+ if (
|
|
|
+ mainDeviceId.value &&
|
|
|
+ !selectedDevices.value.some((device) => device.id === mainDeviceId.value)
|
|
|
+ ) {
|
|
|
+ mainDeviceId.value = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const rightDeviceChange = (val) => {
|
|
|
+ selectedDeviceIds.value = val
|
|
|
+ updateSelectedDevices()
|
|
|
+
|
|
|
+ // 手动触发验证
|
|
|
+ if (formRef.value) {
|
|
|
+ formRef.value.validateField('devices')
|
|
|
+ if (val.length > 0) {
|
|
|
+ formRef.value.validateField('mainDevice')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 设置主设备
|
|
|
+const setMainDevice = (val) => {
|
|
|
+ mainDeviceId.value = val
|
|
|
+ // 更新 details 中的 ifMaster 字段
|
|
|
+ console.log('selectedDevices.value>>>>>>>>>>>>>>>>', selectedDevices.value)
|
|
|
+
|
|
|
+ formData.value.details = selectedDevices.value.map((item) => ({
|
|
|
+ deviceId: item.id,
|
|
|
+ deviceName: item.deviceName,
|
|
|
+ deviceCode: item.deviceCode,
|
|
|
+ deptId: item.deptId,
|
|
|
+ ifMaster: item.id === mainDeviceId.value ? true : false
|
|
|
+ }))
|
|
|
+
|
|
|
+ console.log('formData.value.details>>>>>>>>>>>>>>>>', formData.value.details)
|
|
|
+
|
|
|
+ // 手动触发验证
|
|
|
+ if (formRef.value) {
|
|
|
+ formRef.value.validateField('mainDevice')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 显示新增对话框
|
|
|
+const handleAdd = () => {
|
|
|
+ isEdit.value = false
|
|
|
+ dialogTitle.value = '新增成套设备'
|
|
|
+ resetForm()
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 显示编辑对话框
|
|
|
+const handleEdit = (row) => {
|
|
|
+ isEdit.value = true
|
|
|
+ dialogTitle.value = '编辑成套设备'
|
|
|
+
|
|
|
+ formData.value = {
|
|
|
+ ...row
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先清空之前的选项
|
|
|
+ selectedDeviceIds.value = []
|
|
|
+ selectedDevices.value = []
|
|
|
+ deviceOptions.value = []
|
|
|
+
|
|
|
+ // 获取设备列表后再设置已选择的设备
|
|
|
+ getDeviceList(row.deptId).then(() => {
|
|
|
+ // 设置已选择的设备IDs(使用deviceOptions中的key值)
|
|
|
+ selectedDeviceIds.value = row.details.map((item) => {
|
|
|
+ // 在编辑模式下,使用deviceOptions中对应项的key作为标识
|
|
|
+ const option = deviceOptions.value.find((opt) => opt.deviceId === item.deviceId)
|
|
|
+ return option ? option.key : item.deviceId
|
|
|
+ })
|
|
|
+
|
|
|
+ mainDeviceId.value = row.details.find((item) => item.ifMaster)?.deviceId || ''
|
|
|
+
|
|
|
+ // 更新selectedDevices数组
|
|
|
+ nextTick(() => {
|
|
|
+ updateSelectedDevices()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+//删除成套
|
|
|
+const handleDelete = async (id: number) => {
|
|
|
+ ElMessageBox.confirm('确定要删除该成套设备吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ .then(async () => {
|
|
|
+ try {
|
|
|
+ await IotDeviceApi.deleteIotDeviceSet(id)
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ getList()
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 取消操作
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 重置表单
|
|
|
+const resetForm = () => {
|
|
|
+ formData.value = {
|
|
|
+ name: '',
|
|
|
+ details: [],
|
|
|
+ deptId: '',
|
|
|
+ remark: ''
|
|
|
+ }
|
|
|
+ selectedDeviceIds.value = []
|
|
|
+ selectedDevices.value = []
|
|
|
+ deviceOptions.value = []
|
|
|
+ mainDeviceId.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// 取消操作
|
|
|
+const cancel = () => {
|
|
|
+ dialogVisible.value = false
|
|
|
+ resetForm()
|
|
|
+}
|
|
|
+
|
|
|
+// 提交表单
|
|
|
+const submit = async () => {
|
|
|
+ if (!formRef.value) return
|
|
|
+
|
|
|
+ await formRef.value.validate(async (valid) => {
|
|
|
+ if (!valid) return
|
|
|
+ try {
|
|
|
+ const data = {
|
|
|
+ ...formData.value
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('提交数据:', data)
|
|
|
+
|
|
|
+ if (isEdit.value) {
|
|
|
+ await IotDeviceApi.updateIotDeviceSet(data)
|
|
|
+ ElMessage.success('编辑成功')
|
|
|
+ } else {
|
|
|
+ await IotDeviceApi.createIotDeviceSet(data)
|
|
|
+ ElMessage.success('新增成功')
|
|
|
+ }
|
|
|
+
|
|
|
+ dialogVisible.value = false
|
|
|
+ getList()
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ getList()
|
|
|
+
|
|
|
+ deptList.value = handleTree(await DeptApi.getSimpleDeptList())
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.transfer-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.transfer-footer {
|
|
|
+ margin-top: 8px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+::deep(.el-transfer-panel) {
|
|
|
+ width: 300px;
|
|
|
+}
|
|
|
+
|
|
|
+::deep(.el-tree--highlight-current) {
|
|
|
+ height: 200px !important;
|
|
|
+}
|
|
|
+::deep(.el-transfer-panel__body) {
|
|
|
+ height: 700px !important;
|
|
|
+}
|
|
|
+</style>
|