Просмотр исходного кода

Merge branch 'dailyReport' into test

zhangcl 2 недель назад
Родитель
Сommit
0fdaff354e

+ 53 - 0
src/api/pms/alarm/index.ts

@@ -0,0 +1,53 @@
+import request from '@/config/axios'
+
+// 告警设置 VO
+export interface IotAlarmSettingVO {
+  id: number // 主键id
+  classifyId: number // 分类id
+  classifyName: string // 分类名称
+  deviceId: number // 设备id
+  deviceName: string // 设备名称
+  maxValue: string // 上限
+  minValue: string // 下限
+  alarmProperty: number // 告警属性
+}
+
+// 告警设置 API
+export const IotAlarmSettingApi = {
+  // 查询告警设置分页
+  getDeviceRange: async (devicecode: any, identifier:string) => {
+    return await request.get({ url: `/rq/iot-alarm-setting/get-device-range?deviceName=` + devicecode+`&identifier=` + identifier })
+  },
+  getIotAlarmSettingPage: async (params: any, ifdevice:string) => {
+    return await request.get({ url: `/rq/iot-alarm-setting/page/` + ifdevice, params })
+  },
+  batchUpdateAlarmSettings: async (alarms: any) => {
+    debugger
+    console.log(JSON.stringify(alarms))
+    return await request.post({ url: `/rq/iot-alarm-setting/batch/update`, data:alarms })
+  },
+  // 查询告警设置详情
+  getIotAlarmSetting: async (id: number) => {
+    return await request.get({ url: `/rq/iot-alarm-setting/get?id=` + id })
+  },
+
+  // 新增告警设置
+  createIotAlarmSetting: async (data: IotAlarmSettingVO) => {
+    return await request.post({ url: `/rq/iot-alarm-setting/create`, data })
+  },
+
+  // 修改告警设置
+  updateIotAlarmSetting: async (data: IotAlarmSettingVO) => {
+    return await request.put({ url: `/rq/iot-alarm-setting/update`, data })
+  },
+
+  // 删除告警设置
+  deleteIotAlarmSetting: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-alarm-setting/delete?id=` + id })
+  },
+
+  // 导出告警设置 Excel
+  exportIotAlarmSetting: async (params) => {
+    return await request.download({ url: `/rq/iot-alarm-setting/export-excel`, params })
+  },
+}

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

@@ -46,9 +46,15 @@ export interface IotDeviceVO {
 
 // 设备台账 API
 export const IotDeviceApi = {
+  getMaxCode: async (params:any) => {
+    return await request.get({ url: `/rq/iot-device/max?yfCode=`+params})
+  },
   getMapDevice: async (params:any) => {
     return await request.get({ url: `/rq/iot-device/map`, params})
   },
+  getAllDeviceParams: async (params:any) => {
+    return await request.get({ url: `/rq/iot-device/all/params`, params})
+  },
   // 查询设备台账分页
   getIotDevicePage: async (params: any) => {
     return await request.get({ url: `/rq/iot-device/page`, params })

+ 51 - 0
src/api/pms/yfclass/index.ts

@@ -0,0 +1,51 @@
+import request from '@/config/axios'
+
+// 设备分类 VO
+export interface IotYfClassifyVO {
+  id: number // 主键id
+  parentId: number // 父分类id
+  name: string // 分类名称
+  code: string // 分类编码
+  sort: number // 分类排序
+  status: number // 开启状态
+}
+
+// 设备分类 API
+export const IotYfClassifyApi = {
+  // 查询设备分类分页
+  getChildrenList: async () => {
+    return await request.get({ url: `/rq/iot-yf-classify/children-list`})
+  },
+  // 查询设备分类分页
+  getIotYfClassifyPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-yf-classify/list`, params })
+  },
+
+  // 查询设备分类详情
+  getIotYfClassify: async (id: number) => {
+    return await request.get({ url: `/rq/iot-yf-classify/get?id=` + id })
+  },
+
+  // 新增设备分类
+  createIotYfClassify: async (data: IotYfClassifyVO) => {
+    return await request.post({ url: `/rq/iot-yf-classify/create`, data })
+  },
+
+  // 修改设备分类
+  updateIotYfClassify: async (data: IotYfClassifyVO) => {
+    return await request.put({ url: `/rq/iot-yf-classify/update`, data })
+  },
+
+  // 删除设备分类
+  deleteIotYfClassify: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-yf-classify/delete?id=` + id })
+  },
+
+  // 导出设备分类 Excel
+  exportIotYfClassify: async (params) => {
+    return await request.download({ url: `/rq/iot-yf-classify/export-excel`, params })
+  },
+  getSimpleYfClassifyList: async (): Promise<IotYfClassifyVO[]> => {
+    return await request.get({ url: '/rq/iot-yf-classify/simple-list' })
+  }
+}

+ 1 - 0
src/assets/svgs/iot/arrange.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759112595197" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7721" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M864.064 3.392a152.192 152.192 0 0 0-152 152c0 42.624 17.696 81.184 46.048 108.768l-71.008 146.816a151.072 151.072 0 0 0-19.52-1.376 150.72 150.72 0 0 0-82.624 24.608l-78.72-58.432c1.376-8.224 2.272-16.64 2.272-25.248 0-83.808-68.192-152-152-152s-152 68.192-152 152c0 42.624 17.696 81.152 46.048 108.768l-71.04 146.816a151.072 151.072 0 0 0-19.52-1.408c-83.808 0-152 68.192-152 152s68.192 152 152 152 152-68.192 152-152a151.456 151.456 0 0 0-46.048-108.768l71.008-146.816c6.4 0.832 12.896 1.376 19.52 1.376a151.04 151.04 0 0 0 103.776-41.28l64.736 48.064a150.784 150.784 0 0 0-9.472 52.288c0 83.808 68.192 152 152 152s152-68.192 152-152a151.424 151.424 0 0 0-46.08-108.8l71.008-146.816c6.4 0.832 12.928 1.376 19.552 1.376 83.808 0 152-68.192 152-152S947.808 3.36 864 3.36zM160.032 812.736c-30.88 0-56-25.12-56-56s25.12-56 56-56 56 25.12 56 56-25.12 56-56 56z m196.48-406.208c-30.88 0-56-25.12-56-56s25.12-56 56-56 56 25.12 56 56-25.12 56-56 56z m311.072 211.072c-30.88 0-56-25.12-56-56s25.12-56 56-56 56 25.12 56 56-25.12 56-56 56z m196.48-406.208c-30.88 0-56-25.12-56-56s25.12-56 56-56 56 25.12 56 56-25.12 56-56 56z" p-id="7722" fill="#13227a"></path></svg>

+ 7 - 0
src/locales/en.ts

@@ -268,6 +268,9 @@ export default {
     status: 'status',
     taskAttrDetail: 'Attr Detail',
     nptReason: 'NPT Reason',
+    currentOperation: 'Current Operation',
+    nextPlan: 'Next Plan',
+    transitTime: 'Transit Time',
   },
   form: {
     input: 'Input',
@@ -586,6 +589,10 @@ export default {
     nameHolder:'Please enter DeviceName',
     code:'DeviceCode',
     codeHolder:'Please enter DeviceCode',
+    yfCode:'Yf Code',
+    yfCodeHolder:'Please enter DeviceCode',
+    yfClass:'编码分类',
+    yfClassHolder:'请输入油服编码',
     brand:'Brand',
     brandHolder:'Please enter Brand',
     moreSearch:'MoreSearch',

+ 3 - 0
src/locales/ru.ts

@@ -233,6 +233,9 @@ export default {
     status: '施工状态',
     taskAttrDetail: '任务属性详情',
     nptReason: '非生产时间原因',
+    currentOperation: '目前工序',
+    nextPlan: '下步工序',
+    transitTime: '运行时效',
   },
   form: {
     input: '输入框',

+ 9 - 2
src/locales/zh-CN.ts

@@ -271,6 +271,9 @@ export default {
     status: '施工状态',
     taskAttrDetail: '任务属性详情',
     nptReason: '非生产时间原因',
+    currentOperation: '目前工序',
+    nextPlan: '下步工序',
+    transitTime: '运行时效',
   },
   form: {
     input: '输入框',
@@ -581,8 +584,12 @@ export default {
   iotDevice:{
     name:'设备名称',
     nameHolder:'请输入设备名称',
-    code:'设备编码',
-    codeHolder:'请输入设备编码',
+    code:'历史编码',
+    codeHolder:'请输入历史编码',
+    yfCode:'油服编码',
+    yfCodeHolder:'请输入油服编码',
+    yfClass:'编码分类',
+    yfClassHolder:'请输入油服编码',
     brand:'品牌',
     brandHolder:'请输入品牌',
     moreSearch:'更多查询',

+ 41 - 0
src/store/modules/pms/refreshStore.ts

@@ -0,0 +1,41 @@
+// stores/refreshStore.ts
+import { defineStore } from 'pinia';
+
+// 定义回调函数类型
+type RefreshCallback = () => void;
+
+export const useRefreshStore = defineStore('refreshStore', {
+  state: () => ({
+    // 存储页面标识与对应刷新回调的映射
+    callbacks: new Map<string, RefreshCallback>(),
+  }),
+  actions: {
+    /**
+     * 注册页面刷新回调
+     * @param pageKey 页面唯一标识
+     * @param callback 刷新时执行的回调函数
+     */
+    registerCallback(pageKey: string, callback: RefreshCallback): void {
+      this.callbacks.set(pageKey, callback);
+    },
+
+    /**
+     * 移除页面刷新回调
+     * @param pageKey 页面唯一标识
+     */
+    removeCallback(pageKey: string): void {
+      this.callbacks.delete(pageKey);
+    },
+
+    /**
+     * 触发指定页面的刷新回调
+     * @param pageKey 页面唯一标识
+     */
+    triggerRefresh(pageKey: string): void {
+      const callback = this.callbacks.get(pageKey);
+      if (callback) {
+        callback();
+      }
+    },
+  },
+});

+ 6 - 5
src/views/pms/device/DeviceInfo.vue

@@ -14,7 +14,12 @@
         <el-form ref="formRef" :disabled="false" :model="formData" label-width="120px">
           <el-row>
             <el-col :span="8">
-              <el-form-item :label="t('chooseMaintain.deviceCode')" prop="deviceCode">
+              <el-form-item :label="t('iotDevice.yfCode')" prop="yfDeviceCode">
+                {{ formData.yfDeviceCode }}
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
                 {{ formData.deviceCode }}
               </el-form-item>
             </el-col>
@@ -28,8 +33,6 @@
                 {{ formData.brandName }}
               </el-form-item>
             </el-col>
-          </el-row>
-          <el-row>
             <el-col :span="8">
               <el-form-item :label="t('iotDevice.dept')" prop="deptId">
                 {{ formData.deptName }}
@@ -45,8 +48,6 @@
                 {{ getDictLabel(DICT_TYPE.PMS_DEVICE_STATUS, formData.deviceStatus) }}
               </el-form-item>
             </el-col>
-          </el-row>
-          <el-row>
             <el-col :span="8">
               <el-form-item :label="t('devicePerson.assets')" prop="assetProperty">
                 {{ getDictLabel(DICT_TYPE.PMS_ASSET_PROPERTY, formData.assetProperty) }}

+ 44 - 4
src/views/pms/device/IotDeviceForm.vue

@@ -15,6 +15,21 @@
       </div>
       <div class="base-expandable-content" :class="{ 'is-expanded': baseIsExpanded }">
         <el-row>
+          <el-col :span="8">
+            <el-form-item :label="t('iotDevice.yfClass')" prop="yfClass">
+              <el-cascader
+                style="width: 100%"
+                v-model="formData.yfClass"
+                :options="yfclasses"
+                :props="{ expandTrigger: 'hover' }"
+                @change="handleYfClassChange" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item :label="t('iotDevice.yfCode')" prop="yfDeviceCode">
+              <el-input v-model="formData.yfDeviceCode" placeholder="请输入油服设备编码" />
+            </el-form-item>
+          </el-col>
           <el-col :span="8">
             <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
               <el-input v-model="formData.deviceCode" :disabled="formType==='update'" placeholder="请输入设备编码" />
@@ -22,7 +37,6 @@
           </el-col>
           <el-col :span="8">
             <el-form-item :label="t('iotDevice.name')" prop="deviceName">
-<!--              <el-input v-model="formData.deviceName" placeholder="请输入设备名称" />-->
               <lang-input v-model="formData.deviceName" placeholder="请输入设备名称" />
             </el-form-item>
           </el-col>
@@ -381,6 +395,8 @@ import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import {DeviceAttrModelApi} from "@/api/pms/deviceattrmodel";
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {IotYfClassifyApi} from "@/api/pms/yfclass";
+import { useRefreshStore } from '@/store/modules/pms/refreshStore';
 
 /** 设备台账 表单 */
 defineOptions({ name: 'DeviceDetailAdd' })
@@ -393,7 +409,7 @@ const username = ref('')
 const deptList = ref<Tree[]>([]) // 树形结构
 const productClassifyList = ref<Tree[]>([]) // 树形结构
 const { delView } = useTagsViewStore() // 视图操作
-const { params, name } = useRoute() // 查询参数
+const { params, name,query } = useRoute() // 查询参数
 const { currentRoute, push } = useRouter()
 const { wsCache } = useCache()
 const id = params.id
@@ -409,8 +425,13 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const brandLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const zzLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const supplierLabel = ref('') // 表单的类型:create - 新增;update - 修改
+const yfclasses = ref([])
+const refreshStore = useRefreshStore();
+
 const formData = ref({
   id: undefined,
+  yfClass: undefined,
+  yfDeviceCode: undefined,
   enableDate: undefined,
   deviceCode: undefined,
   deviceName: undefined,
@@ -447,6 +468,8 @@ const formData = ref({
   assetClass: undefined
 })
 const formRules = reactive({
+  yfClass: [{ required: true, message: '编码类别不能为空', trigger: 'blur' }],
+  yfDeviceCode: [{ required: true, message: '油服编码不能为空', trigger: 'blur' }],
   assetClass: [{ required: true, message: '资产类别不能为空', trigger: 'blur' }],
   deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }],
   deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
@@ -459,7 +482,12 @@ const formRules = reactive({
 })
 
 const list = ref([])
-
+const handleYfClassChange = async (value) =>{
+  console.log(value)
+  const prefix = value.join('')
+  const last = await IotDeviceApi.getMaxCode(prefix)
+  formData.value.yfDeviceCode = prefix+last
+}
 const assetclasschange = () => {
   const assetClass = formData.value.assetClass
   DeviceAttrModelApi.getDeviceAttrModelListByDeviceCategoryId(assetClass).then(res => {
@@ -600,6 +628,7 @@ const submitForm = async () => {
       }))
       formData.value.templateJson = JSON.stringify(list.value)
     }
+    formData.value.yfClass = formData.value.yfClass.join(',');
     const data = formData.value as unknown as IotDeviceVO
     if (formType.value === 'create') {
       await IotDeviceApi.createIotDevice(data)
@@ -611,6 +640,12 @@ const submitForm = async () => {
     dialogVisible.value = false
     // 发送操作成功的事件
     //emit('success')
+    const sourcePage = query.source as string;
+
+    // 如果有来源页面标识,触发原页面的刷新
+    if (sourcePage) {
+      refreshStore.triggerRefresh(sourcePage);
+    }
     close()
   } finally {
     formLoading.value = false
@@ -636,11 +671,13 @@ onMounted(async () => {
       formData.value.brandName = iotDevice.brandName;
       formData.value.manufacturerName = iotDevice.zzName;
       formData.value.supplierName = iotDevice.supplierName;
+      if (iotDevice.yfClass) {
+        formData.value.yfClass = iotDevice.yfClass.split(',');
+      }
       list.value = JSON.parse(iotDevice.templateJson);
       list.value.forEach((item) => {
         formData.value[item.code] = item.value;
       })
-      debugger
     } finally {
       formLoading.value = false
     }
@@ -650,6 +687,9 @@ onMounted(async () => {
     }
     formType.value = 'create';
   }
+  await IotYfClassifyApi.getChildrenList().then(res => {
+    yfclasses.value = res
+  });
 })
 /** 重置表单 */
 const resetForm = () => {

+ 42 - 3
src/views/pms/device/IotDeviceFormAdd.vue

@@ -15,6 +15,23 @@
       </div>
       <div class="base-expandable-content" :class="{ 'is-expanded': baseIsExpanded }">
         <el-row>
+          <el-col :span="8">
+            <el-form-item :label="t('iotDevice.yfClass')" prop="yfClass">
+              <el-cascader
+                style="width: 100%"
+                v-model="formData.yfClass"
+                :options="yfclasses"
+                :props="{ expandTrigger: 'hover' }"
+                clearable
+                filterable
+                @change="handleYfClassChange" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item :label="t('iotDevice.yfCode')" prop="yfCode">
+              <el-input v-model="formData.yfCode" :disabled="formType==='update'" placeholder="请输入油服设备编码" />
+            </el-form-item>
+          </el-col>
           <el-col :span="8">
             <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
               <el-input v-model="formData.deviceCode" :disabled="formType==='update'" placeholder="请输入设备编码" />
@@ -370,7 +387,8 @@ import * as ProductClassifyApi from '@/api/pms/productclassify'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import {DeviceAttrModelApi} from "@/api/pms/deviceattrmodel";
-
+import {IotYfClassifyApi} from "@/api/pms/yfclass";
+import { useRefreshStore } from '@/store/modules/pms/refreshStore';
 
 /** 设备台账 表单 */
 defineOptions({ name: 'DeviceDetailAddd' })
@@ -382,7 +400,7 @@ const qtIsExpanded = ref(true) // 控制表单是否展开的变量
 const deptList = ref<Tree[]>([]) // 树形结构
 const productClassifyList = ref<Tree[]>([]) // 树形结构
 const { delView } = useTagsViewStore() // 视图操作
-const { params, name } = useRoute() // 查询参数
+const { params, name, query } = useRoute() // 查询参数
 const { currentRoute, push } = useRouter()
 
 const id = params.id
@@ -398,10 +416,14 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const brandLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const zzLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const supplierLabel = ref('') // 表单的类型:create - 新增;update - 修改
+const yfclasses = ref([])
+const refreshStore = useRefreshStore();
 const formData = ref({
   id: undefined,
   deviceCode: undefined,
   deviceName: undefined,
+  yfClass: undefined,
+  yfCode: undefined,
   brand: undefined,
   brandName: undefined,
   model: undefined,
@@ -433,6 +455,8 @@ const formData = ref({
   assetClass: undefined
 })
 const formRules = reactive({
+  yfClass: [{ required: true, message: '编码类别不能为空', trigger: 'blur' }],
+  yfCode: [{ required: true, message: '油服编码不能为空', trigger: 'blur' }],
   assetClass: [{ required: true, message: '资产类别不能为空', trigger: 'blur' }],
   deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }],
   deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
@@ -463,7 +487,12 @@ const assetclasschange = () => {
     }
   })
 }
-
+const handleYfClassChange = async (value) =>{
+  console.log(value)
+  const prefix = value.join('')
+  const last = await IotDeviceApi.getMaxCode(prefix)
+  formData.value.yfCode = prefix+last
+}
 const brandChoose = (row) => {
   formData.value.brand = row.id
   // brandLabel.value = row.value
@@ -586,6 +615,7 @@ const submitForm = async () => {
       }))
       formData.value.templateJson = JSON.stringify(list.value)
     }
+    formData.value.yfClass = formData.value.yfClass.join(',');
     const data = formData.value as unknown as IotDeviceVO
     if (formType.value === 'create') {
       await IotDeviceApi.createIotDevice(data)
@@ -597,6 +627,12 @@ const submitForm = async () => {
     dialogVisible.value = false
     // 发送操作成功的事件
     //emit('success')
+    const sourcePage = query.source as string;
+
+    // 如果有来源页面标识,触发原页面的刷新
+    if (sourcePage) {
+      refreshStore.triggerRefresh(sourcePage);
+    }
     close()
   } finally {
     formLoading.value = false
@@ -632,6 +668,9 @@ onMounted(async () => {
     }
     formType.value = 'create';
   }
+  await IotYfClassifyApi.getChildrenList().then(res => {
+    yfclasses.value = res
+  });
 })
 /** 重置表单 */
 const resetForm = () => {

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

@@ -17,6 +17,15 @@
           :inline="true"
           label-width="68px"
         >
+          <el-form-item :label="t('iotDevice.yfCode')" prop="yfDeviceCode" style="margin-left: 20px">
+            <el-input
+              v-model="queryParams.yfDeviceCode"
+              :placeholder="t('iotDevice.yfCodeHolder')"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
           <el-form-item :label="t('iotDevice.code')" prop="deviceCode" style="margin-left: 20px">
             <el-input
               v-model="queryParams.deviceCode"
@@ -131,6 +140,7 @@
               {{ scope.$index + 1 }}
             </template>
           </el-table-column>
+          <el-table-column :label="t('iotDevice.yfCode')" sortable align="center" prop="yfDeviceCode" min-width="150"/>
           <el-table-column :label="t('iotDevice.code')" sortable align="center" prop="deviceCode" min-width="150"/>
           <el-table-column :label="t('iotDevice.name')" sortable align="center" prop="deviceName" min-width="250">
             <template #default="scope">
@@ -205,6 +215,7 @@ import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import {buildSortingField} from "@/utils";
 import {defaultProps, handleTree} from "@/utils/tree";
 import * as ProductClassifyApi from "@/api/pms/productclassify";
+import { useRefreshStore } from '@/store/modules/pms/refreshStore';
 
 /** 设备台账 列表 */
 defineOptions({ name: 'IotDevicePms' })
@@ -213,6 +224,7 @@ const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { push } = useRouter() // 路由跳转
 
+const refreshStore = useRefreshStore();
 const loading = ref(true) // 列表的加载中
 const ifShow = ref(false)
 const isDetail = ref(false) // 是否查看详情
@@ -253,6 +265,7 @@ const queryParams = reactive({
   creator: undefined,
   sortingFields: [],
   assetClass: undefined,
+  yfDeviceCode: undefined,
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
@@ -311,14 +324,14 @@ const formRef = ref()
 const openForm = (type: string, id?: number,deptId?:number) => {
   //修改
   if (typeof id === 'number') {
-    push({ name: 'DeviceDetailEdit', params: { type,id } })
+    push({ name: 'DeviceDetailEdit', params: { type,id }, query:{source:'devicerouter'} })
     return
   }
   // 新增
   if (deptId) {
-    push({ name: 'DeviceDetailAdd', params: {type,deptId} })
+    push({ name: 'DeviceDetailAdd', params: {type,deptId}, query:{source:'devicerouter'} })
   }else {
-    push({ name: 'DeviceDetailAddd', params: {} })
+    push({ name: 'DeviceDetailAddd', params: {} ,query:{source:'devicerouter'}})
   }
 }
 
@@ -369,6 +382,8 @@ onMounted(async () => {
   }
   queryParams.sortingFields.push(sort);
   await getList()
+  refreshStore.registerCallback('devicerouter', getList);
+
 })
 </script>
 <style scoped></style>

+ 157 - 0
src/views/pms/device/monitor/IotAlarmSettingForm.vue

@@ -0,0 +1,157 @@
+<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="分类名称" prop="classifyName">
+        <el-input v-model="formData.classifyName" disabled />
+      </el-form-item>
+      <el-form-item label="设备编码" prop="deviceName" v-if="ifDevice">
+        <el-input v-model="formData.deviceName" disabled />
+      </el-form-item>
+      <el-form-item label="告警属性" prop="alarmProperty">
+<!--        <el-input v-model="formData.alarmProperty" placeholder="请输入告警属性" />-->
+        <el-select v-model="formData.alarmProperty" placeholder="请选择告警属性" clearable filterable @change="propertyChange">
+          <el-option
+            v-for="dict in specs"
+            :key="dict.identifier"
+            :label="dict.modelName"
+            :value="dict.modelName"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="属性标识" prop="propertyCode">
+        <el-input v-model="formData.propertyCode" placeholder="请输入属性标识" disabled />
+      </el-form-item>
+      <el-form-item label="上限值" prop="maxValue">
+        <el-input v-model="formData.maxValue" placeholder="请输入上限" />
+      </el-form-item>
+      <el-form-item label="下限值" prop="minValue">
+        <el-input v-model="formData.minValue" 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 { IotAlarmSettingApi, IotAlarmSettingVO } from '@/api/pms/alarm'
+import {IotDeviceApi} from "@/api/pms/device";
+import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
+
+/** 告警设置 表单 */
+defineOptions({ name: 'IotAlarmSettingForm' })
+
+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 specs = ref([])
+const formData = ref({
+  id: undefined,
+  classifyId: undefined,
+  classifyName: undefined,
+  deviceId: undefined,
+  deviceName: undefined,
+  maxValue: undefined,
+  minValue: undefined,
+  alarmProperty: undefined,
+  propertyCode: undefined,
+})
+const formRules = reactive({
+  classifyName: [{ required: true, message: '分类不能为空', trigger: 'blur' }],
+  alarmProperty: [{ required: true, message: '告警属性不能为空', trigger: 'blur' }],
+  propertyCode: [{ required: true, message: '属性标识不能为空', trigger: 'blur' }],
+  // maxValue: [{ required: true, message: '上限不能为空', trigger: 'blur' }],
+  // minValue: [{ required: true, message: '下限不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+const ifDevice = ref(false)
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number,classId:any, className:any,deviceId:any, deviceName:any) => {
+  debugger
+  dialogVisible.value = true
+  dialogTitle.value = '新增设置'
+  formType.value = type
+  resetForm()
+  formData.value.classifyId = classId;
+  formData.value.classifyName = className;
+  formData.value.deviceId = deviceId;
+  formData.value.deviceName = deviceName;
+  ifDevice.value = !(deviceId === '' || deviceId === undefined);
+  // 修改时,设置数据
+  if (id) {
+    dialogTitle.value = '编辑设置'
+    formLoading.value = true
+    try {
+      formData.value = await IotAlarmSettingApi.getIotAlarmSetting(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  if (deviceId) {
+    await IotDeviceApi.getIotDeviceTds(deviceId).then(res => {
+      specs.value = res
+      specs.value = specs.value.sort((a, b) => {
+        return b.modelOrder - a.modelOrder
+      })
+    })
+  }
+}
+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 IotAlarmSettingVO
+    if (formType.value === 'create') {
+      await IotAlarmSettingApi.createIotAlarmSetting(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotAlarmSettingApi.updateIotAlarmSetting(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success',ifDevice.value?'2':'1')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+const propertyChange = (value) =>{
+  debugger
+  const model = specs.value.filter((item) => item.modelName === value)
+  formData.value.propertyCode = model[0].identifier
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    classifyId: undefined,
+    classifyName: undefined,
+    deviceId: undefined,
+    deviceName: undefined,
+    maxValue: undefined,
+    minValue: undefined,
+    alarmProperty: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 539 - 0
src/views/pms/device/monitor/RangeSet.vue

@@ -0,0 +1,539 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧 设备分类 树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeviceCategoryTree @node-click="handleDeviceCategoryTreeNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <ContentWrap>
+          <ContentWrap v-if="selectedNode">
+            <div style="display: flex;flex-direction: row; justify-content: space-between; vertical-align: center">
+              <div style="display: flex;flex-direction: row; ">
+                <el-radio style="background-color: #f4dcaf" v-model="ifDevice" label="1" size="large" @input="handleClassifyRadio" border>分类区间</el-radio>
+                <el-radio style="background-color: #f4f3f3" v-model="ifDevice" label="2" size="large" @input="handleDeviceRadio" border>设备区间</el-radio>
+              </div>
+<!--              <el-select-->
+<!--                style="width: 200px;"-->
+<!--                v-if="selectedNode&&ifDevice==='2'"-->
+<!--                :placeholder="t('faultForm.choose')"-->
+<!--                clearable-->
+<!--                @change="handleDeviceSelected"-->
+<!--                @clear="handleClear"-->
+<!--              />-->
+              <el-select style="width: 250px" v-model="selectedDeviceId" v-if="selectedNode&&ifDevice==='2'" :placeholder="t('iotMaintain.deviceHolder')"
+                         @clear="handleClear" @change="handleDeviceSelected" :class="{ 'shake-highlight': isDeviceSelectShake }" filterable>
+                <el-option
+                  v-for="item in devices"
+                  :key="item.id"
+                  :label="item.deviceCode"
+                  :value="item.id">
+                  <span style="float: left">{{ item.deviceCode }}</span>
+                  <span style="float: right; color: #8492a6; font-size: 13px">{{ item.deviceName }}</span>
+                </el-option>
+              </el-select>
+            </div>
+          </ContentWrap>
+          <!-- 选中项信息与操作 -->
+          <div v-if="selectedNode" class="selected-info-bar">
+            <div class="selected-info">
+              <Icon icon="fa:foursquare" />
+              <h2 class="selected-name">{{ifDevice==='1'?selectedName:selectedDeviceName }}</h2>
+              <el-tag
+                :type="ifDevice === '1' ? 'success' : 'primary'"
+                class="node-type-tag"
+              >
+                {{ ifDevice === '1' ? '分类' : '设备' }}
+              </el-tag>
+            </div>
+
+            <div class="action-buttons">
+              <el-button
+                type="warning"
+                @click="handleQuery"
+                class="add-property-btn"
+                size="default"
+              >
+                <template #icon>
+                  <Icon icon="ep:search" />
+                </template>
+                查询
+              </el-button>
+              <el-button
+                type="primary"
+                @click="openForm('create')"
+                class="add-property-btn"
+                size="default"
+              >
+                <template #icon>
+                  <Plus />
+                </template>
+                添加属性
+              </el-button>
+              <el-button
+                type="success"
+                @click="saveAllProperties"
+                :loading="saveLoading"
+                size="default"
+              >
+                <template #icon>
+                  <Check />
+                </template>
+                保存配置
+              </el-button>
+            </div>
+          </div>
+          <!-- 未选择任何项时的空状态 -->
+          <div v-else class="empty-state">
+            <el-empty description="请从左侧选择一个设备或分类" class="main-empty-state">
+              <template #image>
+                <div class="empty-image-container">
+                  <Icon :size="40" icon="fa-solid:empty-set"/>
+                </div>
+              </template>
+            </el-empty>
+          </div>
+        </ContentWrap>
+
+        <ContentWrap>
+          <el-row v-if="selectedNode&&list.length>0" :gutter="16">
+            <el-col v-for="item in list" :key="item.id" :lg="6" :md="12" :sm="12" :xs="24" class="mb-4">
+              <el-card v-loading="loading" :body-style="{ padding: '0' }" class="h-full transition-colors">
+                <!-- 内容区域 -->
+                <div class="p-4">
+                  <!-- 标题区域 -->
+                  <div class="flex items-center mb-3">
+                    <div class="mr-2.5 flex items-center">
+                      <el-image :src="defaultIconUrl" class="w-[35px] h-[35px]" />
+                    </div>
+                    <div class="text-[16px] font-600">{{ item.alarmProperty }}</div>
+                  </div>
+
+                  <!-- 信息区域 -->
+                  <div class="flex items-center text-[14px]">
+                    <div class="flex-1">
+                      <div class="mb-2.5 last:mb-0">
+                        <span class="text-[#717c8e] mr-2.5">属性标识</span>
+                        <el-tag><span class="text-[#0070ff]" style="font-size: 15px">{{ item.propertyCode }}</span></el-tag>
+                      </div>
+                      <div class="mb-2.5 last:mb-0">
+                        <span class="text-[#717c8e] mr-2.5">上限数值</span>
+                        <el-input
+                          v-model.number="item.maxValue"
+                          type="number"
+                          class="text-[#0070ff] inline-input"
+                          size="small"
+                          step="any"
+                        :min="item.minValue !== null ? item.minValue : -Infinity"
+                        />
+                      </div>
+                      <div class="mb-2.5 last:mb-0">
+                        <span class="text-[#717c8e] mr-2.5">下限数值</span>
+                        <el-input
+                          v-model.number="item.minValue"
+                          type="number"
+                          class="text-[#0070ff] inline-input"
+                          size="small"
+                          step="any"
+                        :max="item.maxValue !== null ? item.maxValue : Infinity"
+                        />
+                      </div>
+                    </div>
+                    <div class="w-[100px] h-[80px]">
+                      <el-image :src="defaultPicUrl" class="w-full h-full" />
+                    </div>
+                  </div>
+
+                  <!-- 分隔线 -->
+                  <el-divider class="!my-3" />
+
+                  <!-- 按钮组 -->
+                  <div class="flex items-center px-0">
+                    <el-button
+                      class="flex-1 !px-2 !h-[32px] text-[13px]"
+                      plain
+                      type="primary"
+                      @click="openForm('update', item.id)"
+                    >
+                      <Icon class="mr-1" icon="ep:edit-pen" />
+                      编辑
+                    </el-button>
+<!--                    <el-button-->
+<!--                      class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"-->
+<!--                      plain-->
+<!--                      type="warning"-->
+<!--                      @click="openDetail(item.id)"-->
+<!--                    >-->
+<!--                      <Icon class="mr-1" icon="ep:view" />-->
+<!--                      详情-->
+<!--                    </el-button>-->
+<!--                    <el-button-->
+<!--                      class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"-->
+<!--                      plain-->
+<!--                      type="success"-->
+<!--                      @click="openObjectModel(item)"-->
+<!--                    >-->
+<!--                      <Icon class="mr-1" icon="ep:scale-to-original" />-->
+<!--                      物模型-->
+<!--                    </el-button>-->
+                    <el-button
+                      class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
+                      plain
+                      type="danger"
+                      @click="handleDelete(item.id)"
+                    >
+                      <Icon icon="ep:delete" />删除
+                    </el-button>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+          <Pagination
+            v-if="selectedNode&&list.length>0"
+            v-model:limit="queryParams.pageSize"
+            v-model:page="queryParams.pageNo"
+            :total="total"
+            @pagination="getList"
+          />
+        </ContentWrap>
+        <!-- 有选中项但无属性时的空状态 -->
+        <div v-if="selectedNode && list.length === 0" class="no-properties-state">
+          <el-empty description="暂无属性,请点击添加属性按钮创建">
+            <template #image>
+              <div class="no-properties-image">
+                <el-icon class="no-properties-icon"><Icon icon="ep:add" /></el-icon>
+              </div>
+            </template>
+            <template #bottom>
+              <el-button type="primary" @click="showAddPropertyDialog" size="default">
+                <template #icon>
+                  <Plus />
+                </template>
+                添加第一个属性
+              </el-button>
+            </template>
+          </el-empty>
+        </div>
+      </ContentWrap>
+    </el-col>
+  </el-row>
+  <IotAlarmSettingForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { CommonStatusEnum } from '@/utils/constants'
+import * as DeviceTemplateApi from '@/api/pms/devicetemplate'
+import DeviceCategoryTree from '../../bom/DeviceCategoryTree.vue'
+import { useTreeStore } from '@/store/modules/attrTemplateTreeStore'
+import { Check, Plus } from '@element-plus/icons-vue'
+import { nextTick, reactive, ref } from 'vue'
+import { IotAlarmSettingApi } from '@/api/pms/alarm'
+import IotAlarmSettingForm from "@/views/pms/device/monitor/IotAlarmSettingForm.vue";
+import defaultIconUrl from '@/assets/svgs/iot/cube.svg'
+import defaultPicUrl from '@/assets/svgs/iot/arrange.svg'
+import {IotDeviceApi} from "@/api/pms/device";
+
+defineOptions({ name: 'IotAlarmSet' })
+
+const treeStore = useTreeStore()
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const { push } = useRouter()
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const saveLoading = ref(false) // 保存的加载状态
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  code: undefined,
+  status: undefined,
+  classifyId: undefined,
+  deviceId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+// 从 Store 中获取左侧 设备分类树 选中的 节点ID
+const selectedId = ref<string>()
+const selectedDeviceId = ref<string>()
+const selectedName = ref<string>()
+const selectedDeviceName = ref<string>()
+const selectedNode = ref(null)
+const ifDevice = ref('')
+const devices = ref([])
+const isDeviceSelectShake = ref(false)
+
+const handleDeviceSelected = async (row) =>{
+  debugger
+  // selectedDeviceId.value = row.id
+  const device = devices.value.find(item => item.id === row)
+  if (device) {
+    selectedDeviceName.value = device.deviceCode+device.deviceName
+  }
+  queryParams.deviceId = row
+  await getList("2");
+}
+const handleClear = () =>{
+  selectedDeviceId.value = ''
+  selectedDeviceName.value = ''
+}
+const handleDeviceRadio = async () =>{
+  //置空分类名称
+  // selectedName.value = ''
+  const deviceParams = {
+    assetClass: selectedId.value,
+    pageNo:1,
+  }
+  // queryParams.deviceId  = selectedDeviceId.value
+  const data = await IotDeviceApi.getAllDeviceParams(deviceParams)
+  debugger
+  devices.value = data;
+  // if (selectedDeviceId.value===''||selectedDeviceId.value===undefined) {
+  //   list.value = []
+  // } else {
+    await getList("2")
+  // }
+};
+const handleClassifyRadio = async () =>{
+  queryParams.classifyId = selectedId.value
+  queryParams.deviceId = null
+  selectedDeviceId.value = ''
+  await getList("1")
+};
+/** 查询 设备属性模板 列表 */
+const getList = async (ifdevice:string) => {
+  loading.value = true
+  try {
+    debugger
+    const data = await IotAlarmSettingApi.getIotAlarmSettingPage(queryParams, ifdevice)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList(ifDevice.value)
+}
+
+/** 搜索按钮操作 */
+const handleAllQuery = () => {
+  queryParams.pageNo = 1
+  queryParams.deviceCategoryId = ''
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理 设备分类树 被点击 */
+const handleDeviceCategoryTreeNodeClick = async (row) => {
+  selectedNode.value = row
+  selectedId.value = row.id
+  selectedName.value = row.name
+  queryParams.classifyId = row.id
+  ifDevice.value = '1'
+  await getList("1")
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  debugger
+  if (ifDevice.value === '2'&&(selectedDeviceId.value === ''||selectedDeviceId.value === null||selectedDeviceId.value === undefined)) {
+    message.error('请先选择设备');
+    // 触发抖动效果
+    isDeviceSelectShake.value = true
+    // 1秒后移除抖动类名,以便下次可以重新触发
+    setTimeout(() => {
+      isDeviceSelectShake.value = false
+    }, 1000)
+    return
+  }
+  formRef.value.open(type, id, selectedId.value, selectedName.value, selectedDeviceId.value, selectedDeviceName.value)
+}
+
+/** 打开详情 */
+const openDetail = (row) => {
+  push({
+    name: 'DeviceAttrTemplateModel',
+    params: {
+      id: row.deviceCategoryId,
+      // 添加额外参数
+      templateName: row.name,
+      categoryName: row.deviceCategoryName
+    }
+  })
+}
+
+/** 修改 设备属性模板 状态 */
+const handleStatusChange = async (row: DeviceTemplateApi.DeviceAttrTemplateVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.name + '"属性模板吗?')
+    // 发起修改状态
+    await DeviceTemplateApi.updateDeviceTemplateStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: DeviceTemplateApi.DeviceAttrTemplateVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotAlarmSettingApi.deleteIotAlarmSetting(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList(ifDevice.value)
+  } catch {}
+}
+
+/** 保存所有属性配置 */
+const saveAllProperties = async () => {
+  if (!selectedNode.value) {
+    message.warning('请先选择设备或分类')
+    return
+  }
+
+  // 筛选出有修改的属性
+  const modifiedItems = list.value.filter(item =>
+    item.maxValue !== undefined || item.minValue !== undefined
+  )
+
+  if (modifiedItems.length === 0) {
+    message.info('没有需要保存的修改')
+    return
+  }
+
+  saveLoading.value = true
+  try {
+    debugger
+    // 调用保存接口
+    await IotAlarmSettingApi.batchUpdateAlarmSettings(modifiedItems)
+    message.success('保存成功')
+    // 重新获取列表刷新数据
+    await getList(ifDevice.value)
+  } catch (error) {
+    message.error('保存失败')
+  } finally {
+    saveLoading.value = false
+  }
+}
+
+/** 初始化 */
+onMounted(() => {
+  //一进来先查询分类的属性
+  getList("1")
+})
+</script>
+<style scoped>
+.property-config-area {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+.selected-info-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  margin-bottom: 20px;
+}
+
+.selected-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+.selected-name {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 500;
+  color: #1d2129;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 12px;
+}
+
+.empty-state {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+.empty-image-container {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background-color: #f0f7ff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16px;
+}
+
+/* 新增样式:调整输入框显示效果 */
+.inline-input {
+  width: 100px;
+  display: inline-block;
+  vertical-align: middle;
+}
+.shake-highlight {
+  animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
+  background-color: red !important;
+  box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.2) !important;
+}
+
+@keyframes shake {
+  10%, 90% { transform: translateX(-1px); }
+  20%, 80% { transform: translateX(2px); }
+  30%, 50%, 70% { transform: translateX(-3px); }
+  40%, 60% { transform: translateX(3px); }
+}
+:deep(.el-select__selection) {
+  height: 32px; /* 自定义高度 */
+}
+:deep(.el-select__placeholder.is-transparent){
+  color: #409EFF;
+}
+</style>

+ 1388 - 0
src/views/pms/device/monitor/RangeSetting.vue

@@ -0,0 +1,1388 @@
+<template>
+  <div class="property-manager-container">
+    <!-- 页面标题与导航 -->
+    <el-page-header
+      @back="handleBack"
+      content="设备属性管理"
+      class="page-header"
+    >
+      <template #extra>
+        <el-breadcrumb separator="/" class="breadcrumb">
+          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
+          <el-breadcrumb-item :to="{ path: '/devices' }">设备管理</el-breadcrumb-item>
+          <el-breadcrumb-item>属性配置</el-breadcrumb-item>
+        </el-breadcrumb>
+      </template>
+    </el-page-header>
+
+    <div class="main-content">
+      <!-- 左侧设备/分类选择面板 -->
+      <div class="sidebar">
+        <el-card class="sidebar-card">
+          <template #header>
+            <div class="sidebar-header">
+              <h3 class="sidebar-title">设备与分类</h3>
+              <el-input
+                v-model="searchQuery"
+                placeholder="搜索设备或分类..."
+                size="small"
+                class="search-input"
+              >
+                <template #prefix>
+                  <el-icon class="search-icon"><Search /></el-icon>
+                </template>
+              </el-input>
+            </div>
+          </template>
+
+          <el-tree
+            ref="deviceTree"
+            :data="deviceTreeData"
+            :props="treeProps"
+            :filter-node-method="filterNode"
+            node-key="id"
+            default-expand-all
+            @node-click="handleNodeSelect"
+            class="device-tree"
+            :highlight-current="true"
+          >
+            <!-- 树形节点自定义插槽 -->
+            <template #default="{ node, data }">
+              <div class="tree-node">
+                <el-icon :class="data.type === 'category' ? 'category-icon' : 'device-icon'">
+                  <template v-if="data.type === 'category'">
+                    <FolderOpened />
+                  </template>
+                  <template v-else>
+                    <Monitor />
+                  </template>
+                </el-icon>
+                <span class="node-label">{{ node.label }}</span>
+                <el-badge
+                  v-if="data.propertyCount"
+                  :value="data.propertyCount"
+                  class="property-count-badge"
+                  size="small"
+                />
+              </div>
+            </template>
+          </el-tree>
+        </el-card>
+      </div>
+
+      <!-- 右侧属性配置区域 -->
+      <div class="property-config-area">
+        <!-- 选中项信息与操作 -->
+        <div v-if="selectedNode" class="selected-info-bar">
+          <div class="selected-info">
+            <el-icon :class="selectedNode.type === 'category' ? 'category-icon' : 'device-icon'">
+              <template v-if="selectedNode.type === 'category'">
+                <FolderOpened />
+              </template>
+              <template v-else>
+                <Monitor />
+              </template>
+            </el-icon>
+            <h2 class="selected-name">{{ selectedNode.label }}</h2>
+            <el-tag :type="selectedNode.type === 'category' ? 'info' : 'primary'" class="node-type-tag">
+              {{ selectedNode.type === 'category' ? '分类' : '设备' }}
+            </el-tag>
+          </div>
+
+          <div class="action-buttons">
+            <el-button
+              type="primary"
+              @click="showAddPropertyDialog"
+              class="add-property-btn"
+              size="default"
+            >
+              <template #icon>
+                <Plus />
+              </template>
+              添加属性
+            </el-button>
+            <el-button
+              type="success"
+              @click="saveAllProperties"
+              :loading="saveLoading"
+              size="default"
+            >
+              <template #icon>
+                <Check />
+              </template>
+              保存配置
+            </el-button>
+          </div>
+        </div>
+
+        <!-- 未选择任何项时的空状态 -->
+        <div v-else class="empty-state">
+          <el-empty
+            description="请从左侧选择一个设备或分类"
+            class="main-empty-state"
+          >
+            <template #image>
+              <div class="empty-image-container">
+                <el-icon class="empty-icon"><Icon icon="ep:add" /></el-icon>
+              </div>
+            </template>
+          </el-empty>
+        </div>
+
+        <!-- 属性卡片列表 -->
+        <div v-if="selectedNode && properties.length > 0" class="properties-grid">
+          <!-- 属性卡片组件,使用v-for渲染 -->
+          <property-card
+            v-for="(property, index) in properties"
+            :key="property.id"
+            :property="property"
+            @update-property="handlePropertyUpdate(index, $event)"
+            @delete-property="handlePropertyDelete(index)"
+            class="property-card-item"
+          >
+            <!-- 卡片底部插槽示例 - 可以根据需要自定义内容 -->
+            <template #footer-actions>
+              <el-tooltip content="查看历史记录">
+                <el-button icon="Clock" size="mini" class="history-btn" />
+              </el-tooltip>
+            </template>
+          </property-card>
+        </div>
+
+        <!-- 有选中项但无属性时的空状态 -->
+        <div v-if="selectedNode && properties.length === 0" class="no-properties-state">
+          <el-empty
+            description="暂无属性,请点击添加属性按钮创建"
+          >
+            <template #image>
+              <div class="no-properties-image">
+                <el-icon class="no-properties-icon"><Icon icon="ep:add" /></el-icon>
+              </div>
+            </template>
+            <template #bottom>
+              <el-button
+                type="primary"
+                @click="showAddPropertyDialog"
+                size="default"
+              >
+                <template #icon>
+                  <Plus />
+                </template>
+                添加第一个属性
+              </el-button>
+            </template>
+          </el-empty>
+        </div>
+      </div>
+    </div>
+
+    <!-- 添加/编辑属性对话框 -->
+    <el-dialog
+      v-model="propertyDialogVisible"
+      :title="isEditing ? '编辑属性' : '添加新属性'"
+      :width="500"
+      @close="resetPropertyForm"
+      class="property-dialog"
+    >
+      <el-form
+        ref="propertyForm"
+        :model="currentProperty"
+        :rules="propertyRules"
+        label-width="120px"
+        class="property-form"
+      >
+        <el-form-item label="属性名称" prop="name">
+          <el-input
+            v-model="currentProperty.name"
+            placeholder="请输入属性名称"
+            maxlength="50"
+          />
+        </el-form-item>
+
+        <el-form-item label="属性标识" prop="code">
+          <el-input
+            v-model="currentProperty.code"
+            placeholder="请输入属性唯一标识"
+            maxlength="30"
+            :disabled="isEditing"
+          />
+          <div class="form-hint">标识用于系统内部识别,添加后不可修改</div>
+        </el-form-item>
+
+        <el-form-item label="数据类型" prop="dataType">
+          <el-select
+            v-model="currentProperty.dataType"
+            placeholder="请选择数据类型"
+            @change="handleDataTypeChange"
+          >
+            <el-option label="整数" value="integer" />
+            <el-option label="浮点数" value="float" />
+            <el-option label="布尔值" value="boolean" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="单位" prop="unit">
+          <el-input
+            v-model="currentProperty.unit"
+            placeholder="例如:℃、%、m"
+            maxlength="10"
+          />
+        </el-form-item>
+
+        <el-row :gutter="16" class="limit-inputs-row">
+          <el-col :span="12">
+            <el-form-item label="最小值" prop="minValue">
+              <el-input-number
+                v-model.number="currentProperty.minValue"
+                :precision="currentProperty.dataType === 'float' ? 2 : 0"
+                :step="currentProperty.dataType === 'float' ? 0.1 : 1"
+                placeholder="请输入最小值"
+                :disabled="currentProperty.dataType === 'boolean'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="最大值" prop="maxValue">
+              <el-input-number
+                v-model.number="currentProperty.maxValue"
+                :precision="currentProperty.dataType === 'float' ? 2 : 0"
+                :step="currentProperty.dataType === 'float' ? 0.1 : 1"
+                placeholder="请输入最大值"
+                :disabled="currentProperty.dataType === 'boolean'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="描述信息" prop="description">
+          <el-input
+            v-model="currentProperty.description"
+            placeholder="请输入属性描述(可选)"
+            type="textarea"
+            :rows="3"
+            maxlength="200"
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <el-checkbox v-model="currentProperty.required">设为必填属性</el-checkbox>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="propertyDialogVisible = false">取消</el-button>
+        <el-button
+          type="primary"
+          @click="confirmPropertyAction"
+          :loading="dialogLoading"
+        >
+          {{ isEditing ? '更新属性' : '创建属性' }}
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, nextTick, computed } from 'vue';
+import {
+  Search, FolderOpened, Monitor, Plus, Check,
+  Edit, Delete, InfoFilled, Clock
+} from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
+
+// 属性卡片组件(使用slot)
+const PropertyCard = {
+  props: {
+    property: {
+      type: Object,
+      required: true,
+      default: () => ({})
+    }
+  },
+  emits: ['update-property', 'delete-property'],
+  template: `
+    <el-card class="property-card">
+      <!-- 卡片头部 -->
+      <template #header>
+        <div class="property-card-header">
+          <div class="property-name">
+            <span>{{ property.name }}</span>
+            <el-tag
+              v-if="property.required"
+              size="mini"
+              type="danger"
+              class="required-tag"
+            >
+              必填
+            </el-tag>
+          </div>
+          <div class="property-actions">
+            <el-tooltip content="编辑属性" placement="top">
+              <el-button
+                icon="Edit"
+                size="mini"
+                @click="$emit('update-property', { ...property })"
+                class="action-btn edit-btn"
+              />
+            </el-tooltip>
+            <el-tooltip content="删除属性" placement="top">
+              <el-button
+                icon="Delete"
+                size="mini"
+                @click="$emit('delete-property')"
+                class="action-btn delete-btn"
+              />
+            </el-tooltip>
+          </div>
+        </div>
+      </template>
+
+      <!-- 卡片内容区 -->
+      <div class="property-content">
+        <div class="property-info-item">
+          <span class="info-label">标识:</span>
+          <span class="info-value">{{ property.code }}</span>
+        </div>
+
+        <div class="property-info-item">
+          <span class="info-label">类型:</span>
+          <span class="info-value">
+            <el-tag size="small" :type="getTypeTagType(property.dataType)">
+              {{ getTypeName(property.dataType) }}
+            </el-tag>
+          </span>
+        </div>
+
+        <!-- 上下限配置区域 -->
+        <div v-if="property.dataType !== 'boolean'" class="property-limits">
+          <div class="limits-label">数值范围:</div>
+          <div class="limits-inputs">
+            <el-input-number
+              v-model.number="property.minValue"
+              :precision="property.dataType === 'float' ? 2 : 0"
+              :step="property.dataType === 'float' ? 0.1 : 1"
+              :placeholder="'最小'"
+              size="small"
+              class="limit-input min-input"
+              @change="handleLimitChange"
+            />
+            <span class="limit-separator">-</span>
+            <el-input-number
+              v-model.number="property.maxValue"
+              :precision="property.dataType === 'float' ? 2 : 0"
+              :step="property.dataType === 'float' ? 0.1 : 1"
+              :placeholder="'最大'"
+              size="small"
+              class="limit-input max-input"
+              :min="property.minValue"
+              @change="handleLimitChange"
+            />
+            <span v-if="property.unit" class="property-unit">{{ property.unit }}</span>
+          </div>
+        </div>
+
+        <!-- 布尔值特殊处理 -->
+        <div v-else class="boolean-value">
+          <span class="info-label">值:</span>
+          <el-switch
+            v-model="property.booleanValue"
+            active-text="真"
+            inactive-text="假"
+            @change="handleBooleanChange"
+          />
+        </div>
+
+        <!-- 描述信息 -->
+        <div v-if="property.description" class="property-description">
+          <el-icon class="description-icon"><InfoFilled /></el-icon>
+          <span>{{ property.description }}</span>
+        </div>
+      </div>
+
+      <!-- 卡片底部,包含默认内容和插槽 -->
+      <template #footer>
+        <div class="property-card-footer">
+          <span class="last-updated">
+            最后更新:{{ formatDate(property.updatedAt) }}
+          </span>
+          <div class="footer-actions">
+            <!-- 插槽:允许父组件添加额外的操作按钮 -->
+            <slot name="footer-actions"></slot>
+          </div>
+        </div>
+      </template>
+    </el-card>
+  `,
+  methods: {
+    // 获取数据类型显示名称
+    getTypeName(type) {
+      const types = {
+        'integer': '整数',
+        'float': '浮点数',
+        'boolean': '布尔值'
+      };
+      return types[type] || type;
+    },
+    // 获取数据类型标签样式
+    getTypeTagType(type) {
+      const types = {
+        'integer': 'primary',
+        'float': 'success',
+        'boolean': 'warning'
+      };
+      return types[type] || 'info';
+    },
+    // 处理上下限变化
+    handleLimitChange() {
+      this.$emit('update-property', { ...this.property });
+    },
+    // 处理布尔值变化
+    handleBooleanChange() {
+      this.$emit('update-property', { ...this.property });
+    },
+    // 格式化日期
+    formatDate(timestamp) {
+      if (!timestamp) return '未更新';
+      const date = new Date(timestamp);
+      return date.toLocaleString();
+    }
+  }
+};
+
+// 生成唯一ID
+const generateId = () => {
+  return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
+};
+
+// 设备树形结构数据
+const deviceTreeData = ref([
+  {
+    id: 'cat1',
+    label: '温度设备',
+    type: 'category',
+    propertyCount: 3,
+    children: [
+      {
+        id: 'dev11',
+        label: '室内温度计',
+        type: 'device',
+        propertyCount: 5
+      },
+      {
+        id: 'dev12',
+        label: '室外温度计',
+        type: 'device',
+        propertyCount: 4
+      }
+    ]
+  },
+  {
+    id: 'cat2',
+    label: '湿度设备',
+    type: 'category',
+    propertyCount: 2,
+    children: [
+      {
+        id: 'dev21',
+        label: '车间湿度计',
+        type: 'device',
+        propertyCount: 3
+      },
+      {
+        id: 'dev22',
+        label: '仓库湿度计',
+        type: 'device',
+        propertyCount: 3
+      }
+    ]
+  }
+]);
+
+// 树形结构配置
+const treeProps = {
+  children: 'children',
+  label: 'label'
+};
+
+// 状态管理
+const searchQuery = ref('');
+const deviceTree = ref(null);
+const selectedNode = ref(null);
+const properties = ref([]);
+const saveLoading = ref(false);
+const dialogLoading = ref(false);
+const propertyDialogVisible = ref(false);
+const isEditing = ref(false);
+const currentEditIndex = ref(-1);
+
+// 属性表单数据
+const currentProperty = reactive({
+  id: '',
+  name: '',
+  code: '',
+  dataType: 'float',
+  unit: '',
+  minValue: null,
+  maxValue: null,
+  booleanValue: false,
+  description: '',
+  required: false,
+  updatedAt: null
+});
+
+// 属性表单验证规则
+const propertyRules = {
+  name: [
+    { required: true, message: '请输入属性名称', trigger: 'blur' },
+    { max: 50, message: '属性名称不能超过50个字符', trigger: 'blur' }
+  ],
+  code: [
+    { required: true, message: '请输入属性标识', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9_]+$/, message: '标识只能包含字母、数字和下划线', trigger: 'blur' },
+    { max: 30, message: '属性标识不能超过30个字符', trigger: 'blur' }
+  ],
+  dataType: [
+    { required: true, message: '请选择数据类型', trigger: 'change' }
+  ],
+  minValue: [
+    {
+      required: () => currentProperty.dataType !== 'boolean',
+      message: '请输入最小值',
+      trigger: 'blur'
+    },
+    {
+      type: 'number',
+      message: '请输入有效的数字',
+      trigger: 'blur',
+      validator: (rule, value, callback) => {
+        if (currentProperty.dataType === 'boolean') {
+          return callback();
+        }
+        if (value === null || value === undefined) {
+          return callback(new Error('请输入最小值'));
+        }
+        if (currentProperty.maxValue !== null && value > currentProperty.maxValue) {
+          return callback(new Error('最小值不能大于最大值'));
+        }
+        callback();
+      }
+    }
+  ],
+  maxValue: [
+    {
+      required: () => currentProperty.dataType !== 'boolean',
+      message: '请输入最大值',
+      trigger: 'blur'
+    },
+    {
+      type: 'number',
+      message: '请输入有效的数字',
+      trigger: 'blur',
+      validator: (rule, value, callback) => {
+        if (currentProperty.dataType === 'boolean') {
+          return callback();
+        }
+        if (value === null || value === undefined) {
+          return callback(new Error('请输入最大值'));
+        }
+        if (currentProperty.minValue !== null && value < currentProperty.minValue) {
+          return callback(new Error('最大值不能小于最小值'));
+        }
+        callback();
+      }
+    }
+  ]
+};
+
+// 过滤节点方法
+const filterNode = (value, data) => {
+  if (!value) return true;
+  return data.label.toLowerCase().includes(value.toLowerCase());
+};
+
+// 监听搜索关键词变化
+watch(searchQuery, (value) => {
+  deviceTree.value?.filter(value);
+});
+
+// 处理节点选择
+const handleNodeSelect = (data) => {
+  // 检查是否有未保存的修改
+  if (hasUnsavedChanges.value) {
+    ElMessageBox.confirm(
+      '当前有未保存的修改,切换设备/分类将丢失这些更改,是否继续?',
+      '确认切换',
+      {
+        confirmButtonText: '继续',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    ).then(() => {
+      loadNodeProperties(data);
+    }).catch(() => {
+      // 取消切换,恢复之前的选择
+      nextTick(() => {
+        deviceTree.value.setCurrentKey(selectedNode.value?.id);
+      });
+    });
+  } else {
+    loadNodeProperties(data);
+  }
+};
+
+// 加载节点属性
+const loadNodeProperties = (data) => {
+  selectedNode.value = data;
+  // 模拟API加载属性数据
+  saveLoading.value = true;
+
+  setTimeout(() => {
+    // 根据不同节点加载不同的属性示例数据
+    if (data.id === 'dev11') { // 室内温度计
+      properties.value = [
+        {
+          id: generateId(),
+          name: '测量温度',
+          code: 'measure_temp',
+          dataType: 'float',
+          unit: '℃',
+          minValue: -10,
+          maxValue: 50,
+          required: true,
+          description: '设备测量的环境温度',
+          updatedAt: Date.now() - 86400000
+        },
+        {
+          id: generateId(),
+          name: '测量精度',
+          code: 'measure_precision',
+          dataType: 'float',
+          unit: '℃',
+          minValue: 0,
+          maxValue: 0.5,
+          required: false,
+          description: '温度测量的允许误差范围',
+          updatedAt: Date.now() - 3600000
+        },
+        {
+          id: generateId(),
+          name: '采样频率',
+          code: 'sample_rate',
+          dataType: 'integer',
+          unit: '次/分钟',
+          minValue: 1,
+          maxValue: 60,
+          required: true,
+          updatedAt: Date.now()
+        }
+      ];
+    } else if (data.id === 'cat1') { // 温度设备分类
+      properties.value = [
+        {
+          id: generateId(),
+          name: '工作温度',
+          code: 'working_temp',
+          dataType: 'float',
+          unit: '℃',
+          minValue: -20,
+          maxValue: 70,
+          required: true,
+          description: '设备正常工作的环境温度范围',
+          updatedAt: Date.now() - 86400000 * 2
+        },
+        {
+          id: generateId(),
+          name: '存储温度',
+          code: 'storage_temp',
+          dataType: 'float',
+          unit: '℃',
+          minValue: -40,
+          maxValue: 85,
+          required: true,
+          description: '设备存储的环境温度范围',
+          updatedAt: Date.now() - 86400000
+        }
+      ];
+    } else {
+      // 其他节点的默认属性
+      properties.value = [];
+    }
+
+    saveLoading.value = false;
+    ElNotification({
+      title: '已加载',
+      message: `已加载 ${data.label} 的 ${properties.value.length} 个属性`,
+      duration: 1500,
+      position: 'bottom-right'
+    });
+  }, 600);
+};
+
+// 检查是否有未保存的更改
+const hasUnsavedChanges = ref(false);
+
+// 监听属性变化
+watch(properties, (newVal) => {
+  hasUnsavedChanges.value = true;
+}, { deep: true });
+
+// 显示添加属性对话框
+const showAddPropertyDialog = () => {
+  resetPropertyForm();
+  isEditing.value = false;
+  currentProperty.id = generateId();
+  propertyDialogVisible.value = true;
+
+  // 自动聚焦到第一个输入框
+  nextTick(() => {
+    const firstInput = document.querySelector('.property-dialog .el-input__inner');
+    if (firstInput) firstInput.focus();
+  });
+};
+
+// 显示编辑属性对话框
+const showEditPropertyDialog = (index, property) => {
+  resetPropertyForm();
+  isEditing.value = true;
+  currentEditIndex.value = index;
+
+  // 复制属性数据到表单
+  Object.assign(currentProperty, { ...property });
+
+  propertyDialogVisible.value = true;
+  nextTick(() => {
+    const firstInput = document.querySelector('.property-dialog .el-input__inner');
+    if (firstInput) firstInput.focus();
+  });
+};
+
+// 重置属性表单
+const resetPropertyForm = () => {
+  currentProperty.name = '';
+  currentProperty.code = '';
+  currentProperty.dataType = 'float';
+  currentProperty.unit = '';
+  currentProperty.minValue = null;
+  currentProperty.maxValue = null;
+  currentProperty.booleanValue = false;
+  currentProperty.description = '';
+  currentProperty.required = false;
+
+  // 重置表单验证
+  nextTick(() => {
+    propertyForm.value?.clearValidate();
+  });
+};
+
+// 处理数据类型变化
+const handleDataTypeChange = (type) => {
+  // 根据数据类型重置相关值
+  if (type === 'boolean') {
+    currentProperty.minValue = null;
+    currentProperty.maxValue = null;
+  } else if (type === 'integer') {
+    currentProperty.minValue = currentProperty.minValue !== null ? Math.round(currentProperty.minValue) : 0;
+    currentProperty.maxValue = currentProperty.maxValue !== null ? Math.round(currentProperty.maxValue) : 100;
+  }
+};
+
+// 确认属性操作(添加或编辑)
+const propertyForm = ref(null);
+const confirmPropertyAction = () => {
+  propertyForm.value.validate((valid) => {
+    if (valid) {
+      dialogLoading.value = true;
+
+      // 模拟API请求延迟
+      setTimeout(() => {
+        const newProperty = { ...currentProperty };
+        newProperty.updatedAt = Date.now();
+
+        if (isEditing.value) {
+          // 编辑现有属性
+          properties.value.splice(currentEditIndex.value, 1, newProperty);
+          ElMessage.success('属性已更新');
+        } else {
+          // 添加新属性
+          properties.value.push(newProperty);
+          // 更新节点的属性计数
+          if (selectedNode.value) {
+            selectedNode.value.propertyCount = (selectedNode.value.propertyCount || 0) + 1;
+          }
+          ElMessage.success('新属性已添加');
+        }
+
+        dialogLoading.value = false;
+        propertyDialogVisible.value = false;
+      }, 500);
+    }
+  });
+};
+
+// 处理属性更新
+const handlePropertyUpdate = (index, updatedProperty) => {
+  updatedProperty.updatedAt = Date.now();
+  properties.value.splice(index, 1, updatedProperty);
+};
+
+// 处理属性删除
+const handlePropertyDelete = (index) => {
+  const property = properties.value[index];
+  ElMessageBox.confirm(
+    `确定要删除属性"${property.name}"吗?此操作不可撤销。`,
+    '确认删除',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'danger'
+    }
+  ).then(() => {
+    properties.value.splice(index, 1);
+    // 更新节点的属性计数
+    if (selectedNode.value) {
+      selectedNode.value.propertyCount = Math.max(0, (selectedNode.value.propertyCount || 0) - 1);
+    }
+    ElMessage.success('属性已删除');
+  }).catch(() => {
+    // 取消删除
+  });
+};
+
+// 保存所有属性配置
+const saveAllProperties = () => {
+  if (properties.value.length === 0) {
+    ElMessage.warning('没有可保存的属性');
+    return;
+  }
+
+  saveLoading.value = true;
+
+  // 模拟API保存
+  setTimeout(() => {
+    saveLoading.value = false;
+    hasUnsavedChanges.value = false;
+
+    ElNotification({
+      title: '保存成功',
+      message: `已成功保存 ${properties.value.length} 个属性配置`,
+      type: 'success',
+      duration: 2000,
+      position: 'bottom-right'
+    });
+  }, 800);
+};
+
+// 返回操作
+const handleBack = () => {
+  if (hasUnsavedChanges.value) {
+    ElMessageBox.confirm(
+      '当前有未保存的修改,离开页面将丢失这些更改,是否继续?',
+      '确认离开',
+      {
+        confirmButtonText: '离开',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    ).then(() => {
+      // 实际应用中这里会执行路由跳转
+      ElMessage.info('已返回上一页');
+    });
+  } else {
+    // 实际应用中这里会执行路由跳转
+    ElMessage.info('已返回上一页');
+  }
+};
+
+// 页面加载时默认选中第一个设备
+watch(() => deviceTreeData.value.length, (length) => {
+  if (length > 0 && !selectedNode.value) {
+    // 尝试选择第一个设备节点
+    const firstDevice = deviceTreeData.value[0]?.children?.[0];
+    if (firstDevice) {
+      nextTick(() => {
+        handleNodeSelect(firstDevice);
+        deviceTree.value.setCurrentKey(firstDevice.id);
+      });
+    }
+  }
+}, { immediate: true });
+</script>
+
+<style scoped>
+/* 全局样式 */
+.property-manager-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+/* 页面头部 */
+.page-header {
+  margin-bottom: 24px;
+  --el-page-header-text-color: #1d2129;
+  --el-page-header-font-size: 20px;
+}
+
+.breadcrumb {
+  font-size: 13px;
+  color: #86909c;
+}
+
+/* 主内容区 */
+.main-content {
+  display: flex;
+  gap: 24px;
+  height: calc(100vh - 120px);
+}
+
+/* 左侧边栏 */
+.sidebar {
+  width: 320px;
+  flex-shrink: 0;
+}
+
+.sidebar-card {
+  height: 100%;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border-radius: 8px;
+  overflow: hidden;
+  border: none;
+  display: flex;
+  flex-direction: column;
+}
+
+.sidebar-header {
+  padding: 16px;
+  background-color: #f7f8fa;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.sidebar-title {
+  margin: 0 0 12px 0;
+  font-size: 16px;
+  font-weight: 500;
+  color: #1d2129;
+}
+
+.search-input {
+  width: 100%;
+  --el-input-bg-color: #fff;
+}
+
+.search-icon {
+  color: #86909c;
+}
+
+.device-tree {
+  flex: 1;
+  overflow-y: auto;
+  padding: 8px 0;
+  --el-tree-node-content-hover-bg-color: #f5f7fa;
+}
+
+.tree-node {
+  display: flex;
+  align-items: center;
+  padding: 4px 8px;
+}
+
+.category-icon {
+  color: #409eff;
+  margin-right: 8px;
+  font-size: 16px;
+}
+
+.device-icon {
+  color: #00b42a;
+  margin-right: 8px;
+  font-size: 16px;
+}
+
+.node-label {
+  font-size: 14px;
+  flex: 1;
+  transition: color 0.2s;
+}
+
+.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content .node-label {
+  color: #409eff;
+  font-weight: 500;
+}
+
+.property-count-badge {
+  background-color: #f2f3f5;
+  color: #86909c;
+  --el-badge-font-size: 12px;
+}
+
+/* 右侧属性配置区 */
+.property-config-area {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+/* 选中项信息栏 */
+.selected-info-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  margin-bottom: 20px;
+}
+
+.selected-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.selected-name {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 500;
+  color: #1d2129;
+}
+
+.node-type-tag {
+  margin-left: 8px;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 12px;
+}
+
+.add-property-btn {
+  transition: all 0.2s;
+}
+
+.add-property-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+}
+
+/* 属性卡片网格 */
+.properties-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
+  gap: 20px;
+  padding: 4px;
+  overflow-y: auto;
+  flex: 1;
+}
+
+.property-card-item {
+  transition: all 0.3s ease;
+}
+
+.property-card-item:hover {
+  transform: translateY(-4px);
+}
+
+/* 属性卡片样式 */
+.property-card {
+  height: 100%;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+  transition: all 0.2s;
+  border: none;
+  overflow: hidden;
+}
+
+.property-card:hover {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+.property-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 16px;
+  background-color: #f7f8fa;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.property-name {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 500;
+  color: #1d2129;
+}
+
+.required-tag {
+  font-size: 12px;
+  padding: 0 4px;
+  height: 18px;
+  line-height: 18px;
+}
+
+.property-actions {
+  display: flex;
+  gap: 4px;
+}
+
+.action-btn {
+  padding: 0 4px;
+  height: 24px;
+  border-radius: 4px;
+  transition: all 0.2s;
+}
+
+.edit-btn {
+  color: #409eff;
+  background-color: #ecf5ff;
+}
+
+.edit-btn:hover {
+  background-color: #d9ecff;
+}
+
+.delete-btn {
+  color: #f56c6c;
+  background-color: #fef0f0;
+}
+
+.delete-btn:hover {
+  background-color: #fee4e4;
+}
+
+.property-content {
+  padding: 16px;
+}
+
+.property-info-item {
+  margin-bottom: 12px;
+  font-size: 14px;
+}
+
+.info-label {
+  color: #86909c;
+  display: inline-block;
+  width: 60px;
+}
+
+.info-value {
+  color: #1d2129;
+}
+
+.property-limits {
+  margin: 16px 0;
+  padding: 12px;
+  background-color: #f7f8fa;
+  border-radius: 6px;
+}
+
+.limits-label {
+  color: #86909c;
+  margin-bottom: 8px;
+  font-size: 14px;
+}
+
+.limits-inputs {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.limit-input {
+  flex: 1;
+  --el-input-number-input-height: 32px;
+}
+
+.min-input {
+  --el-input-number-bg-color: #f0f7ff;
+}
+
+.max-input {
+  --el-input-number-bg-color: #f0fff4;
+}
+
+.limit-separator {
+  color: #86909c;
+  font-weight: 500;
+}
+
+.property-unit {
+  color: #86909c;
+  white-space: nowrap;
+  padding-left: 4px;
+}
+
+.boolean-value {
+  margin: 16px 0;
+  padding: 12px;
+  background-color: #f7f8fa;
+  border-radius: 6px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.property-description {
+  margin-top: 12px;
+  padding: 8px 12px;
+  background-color: #f0f7ff;
+  border-radius: 4px;
+  font-size: 13px;
+  color: #4e5969;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.description-icon {
+  color: #409eff;
+  font-size: 14px;
+}
+
+.property-card-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 16px;
+  background-color: #f7f8fa;
+  border-top: 1px solid #f0f0f0;
+  font-size: 12px;
+  color: #86909c;
+}
+
+.last-updated {
+  flex: 1;
+}
+
+.footer-actions {
+  display: flex;
+  gap: 4px;
+}
+
+.history-btn {
+  color: #86909c;
+  background-color: #f2f3f5;
+  padding: 0 4px;
+  height: 24px;
+}
+
+/* 空状态样式 */
+.empty-state {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.empty-image-container {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background-color: #f0f7ff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16px;
+}
+
+.empty-icon {
+  font-size: 60px;
+  color: #409eff;
+}
+
+.no-properties-state {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  padding: 40px;
+}
+
+.no-properties-image {
+  width: 100px;
+  height: 100px;
+  margin-bottom: 16px;
+}
+
+.no-properties-icon {
+  font-size: 100px;
+  color: #c9cdD4;
+}
+
+/* 对话框样式 */
+.property-dialog {
+  --el-dialog-border-radius: 8px;
+}
+
+.property-form {
+  margin-top: 12px;
+}
+
+.form-hint {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #86909c;
+  line-height: 1.4;
+}
+
+.limit-inputs-row {
+  margin-bottom: 8px;
+}
+
+/* 滚动条美化 */
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 动画效果 */
+.el-collapse-transition {
+  transition: height 0.3s ease, opacity 0.3s ease;
+}
+
+/* 响应式调整 */
+@media (max-width: 1200px) {
+  .properties-grid {
+    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+  }
+}
+
+@media (max-width: 992px) {
+  .main-content {
+    flex-direction: column;
+    height: auto;
+  }
+
+  .sidebar {
+    width: 100%;
+    height: 300px;
+  }
+
+  .properties-grid {
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  }
+}
+</style>

+ 69 - 21
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -92,6 +92,7 @@ import {IotDeviceApi} from "@/api/pms/device";
 import * as echarts from 'echarts'
 import dayjs from 'dayjs'
 import {IotStatApi} from "@/api/pms/stat";
+import {IotAlarmSettingApi} from "@/api/pms/alarm";
 
 const { params, name } = useRoute() // 查询参数
 const info = ref({})
@@ -114,14 +115,16 @@ const endTime = ref('')
 const topicName = ref([])
 const loading = ref(false)
 const topic = ref('')
+// 设置固定阈值
 
 const handleDateChange = async (val) => {
   if (val && val.length === 2) {
     await getChart(val)
     await renderChart()
-
   }
 }
+
+
 const defaultEnd = dayjs()
 const defaultStart = defaultEnd.subtract(1, 'day')
 const dateRange = ref([
@@ -155,41 +158,86 @@ const getChart = async (range) =>{
 // 初始化图表
 const renderChart = async () => {
   if (!chartContainer.value) return
-
+  let upperLimit;
+  let lowerLimit;
+  await IotAlarmSettingApi.getDeviceRange(params.code, topic.value).then(res=>{
+    if (res){
+      if (res.maxValue){
+        upperLimit = res.maxValue
+      }
+      if (res.minValue){
+        lowerLimit = res.minValue
+      }
+    }
+  })
   // 销毁旧实例
   if (chartInstance) chartInstance.dispose()
 
   chartInstance = markRaw(echarts.init(chartContainer.value))
+
   const option = {
-    title:{
-      text: topicName.value+'数据趋势',
-      left:'center',
+    title: {
+      text: topicName.value + '数据趋势',
+      left: 'center',
     },
-    tooltip: { trigger: 'axis', },
+    tooltip: { trigger: 'axis' },
     xAxis: {
       type: 'category',
       data: result.value.map(d => dayjs(d.timestamp).format('YYYY-MM-DD HH:mm:ss')),
-      // data: result.value.map(item => Object.keys(item)[0]),
-      axisLabel: { rotate: 45 }, // X轴标签旋转防止重叠
+      axisLabel: { rotate: 45 },
       inverse: true,
     },
-    yAxis: { type: 'value' },
+    yAxis: {
+      type: 'value',
+      // 根据固定阈值和实际数据调整Y轴范围,使阈值线更清晰
+      // min: Math.min(lowerLimit * 0.9, ...result.value.map(d => d.value || 0)),
+      // max: Math.max(upperLimit * 1.03, ...result.value.map(d => d.value || 0))
+    },
     dataZoom: [{
       type: 'slider',
       xAxisIndex: 0,
-      start: 0,  // 初始显示范围开始位置
-      end: 100   // 初始显示范围结束位置:ml-citation{ref="7" data="citationList"}
+      start: 0,
+      end: 100
     }],
-    series: [{
-      data: result.value.map(d => d.value),
-      // data: result.value.map(item => {
-      //   const key = Object.keys(item)[0]; // 获取当前元素的key
-      //   return item[key][0][topic.value]; // 提取value数组中第一个对象的属性
-      // }),
-      type: 'line',
-      smooth: true,
-      areaStyle: {} // 显示区域填充
-    }]
+    series: [
+      // 原始数据曲线
+      {
+        data: result.value.map(d => d.value),
+        type: 'line',
+        smooth: true,
+        name: '实时数据',
+        lineStyle: { color: '#409eff' }
+      },
+      // 上限阈值线(固定100)
+      {
+        data: result.value.map(() => upperLimit),
+        type: 'line',
+        name: '上限阈值',
+        lineStyle: {
+          color: '#f56c6c',  // 红色虚线
+          type: 'dashed'
+        },
+        symbol: 'none',  // 不显示数据点
+        emphasis: { disabled: true }  // 禁用悬停高亮
+      },
+      // 下限阈值线(固定95)
+      {
+        data: result.value.map(() => lowerLimit),
+        type: 'line',
+        name: '下限阈值',
+        lineStyle: {
+          color: '#e6a23c',  // 橙色虚线
+          type: 'dashed'
+        },
+        symbol: 'none',
+        emphasis: { disabled: true }
+      }
+    ],
+    // 添加图例显示各线条含义
+    legend: {
+      data: ['实时数据', '上限阈值', '下限阈值'],
+      top: 30
+    }
   }
 
   chartInstance.setOption(option)

+ 18 - 3
src/views/pms/iotmainworkorder/DeviceAlarmBomList.vue

@@ -16,6 +16,10 @@
           <span class="info-label">{{ t('iotDevice.name') }}:</span>
           <span class="info-value">{{ deviceInfo.deviceName }}</span>
         </div>
+        <div class="info-item" v-if="deviceInfo.model">
+          <span class="info-label">{{ t('deviceForm.model') }}:</span>
+          <span class="info-value">{{ deviceInfo.model }}</span>
+        </div>
       </div>
 
       <div class="table-container">
@@ -126,6 +130,9 @@ const total = ref(0) // 列表的总页数
 // 分页重置标志
 const shouldResetPagination = ref(false)
 
+// 添加外部传入的设备信息
+const externalDeviceInfo = ref(null)
+
 const dialogWidth = '1500px';
 
 const tableRef = ref(null)    // 表格实例引用
@@ -283,7 +290,7 @@ const paginatedList = computed(() => {
   return list.value.slice(start, end);
 });
 
-const open = async (id?: number, flag?: string, deviceId?: number) => {
+const open = async (id?: number, flag?: string, deviceInfo?: any) => {
   // 重置分页参数
   queryParams.pageNo = 1
   queryParams.pageSize = 10
@@ -291,7 +298,10 @@ const open = async (id?: number, flag?: string, deviceId?: number) => {
   total.value = 0
 
   await nextTick() // 确保DOM更新完成
-  queryParams.deviceId = deviceId
+  if (deviceInfo) {
+    externalDeviceInfo.value = deviceInfo
+    queryParams.deviceId = deviceInfo.id // 如果需要的话
+  }
   if('workOrder' === flag) {
     // 加载保养工单 BOM
     queryParams.workOrderId = id
@@ -365,11 +375,16 @@ const getPlanList = async () => {
 
 // 添加设备信息计算属性
 const deviceInfo = computed(() => {
+  // 优先使用外部传入的设备信息
+  if (externalDeviceInfo.value) {
+    return externalDeviceInfo.value;
+  }
   if (list.value.length > 0) {
     const firstRecord = list.value[0];
     return {
       deviceCode: firstRecord.deviceCode,
-      deviceName: firstRecord.deviceName
+      deviceName: firstRecord.deviceName,
+      model: firstRecord.model // 确保列表数据中也有 model
     };
   }
   return null;

+ 8 - 1
src/views/pms/iotmainworkorder/IotDeviceMainAlarm.vue

@@ -257,12 +257,19 @@ const drawerVisible = ref<boolean>(false)
 const showDrawer = ref()
 
 const openBomForm = async (row) => {
+  // 构建设备信息对象,包含所有需要的属性
+  const deviceInfo = {
+    deviceId: row.id,
+    deviceCode: row.deviceCode,
+    deviceName: row.deviceName,
+    model: row.model // 新增 model 属性
+  }
   if (row.workOrderId) {
     flag.value = 'workOrder';
     modelFormRef.value.open(row.workOrderId, flag.value, row.id)
   } else if (row.planId) {
     flag.value = 'plan';
-    modelFormRef.value.open(row.planId, flag.value, row.id)
+    modelFormRef.value.open(row.planId, flag.value, deviceInfo)
   }
 }
 

+ 47 - 37
src/views/pms/iotmainworkorder/IotMainWorkOrderOptimize.vue

@@ -89,8 +89,17 @@
   <ContentWrap>
     <!-- 列表 -->
     <ContentWrap>
-      <el-table v-loading="loading" :data="paginatedList" :stripe="true" @row-click="handleRowClick" :row-class-name="tableRowClassName"
-                :show-overflow-tooltip="true" :header-cell-style="tableHeaderStyle" :span-method="handleSpanMethod">
+      <el-table ref="mainTableRef"
+                v-loading="loading"
+                :data="paginatedList"
+                :stripe="true"
+                @row-click="handleRowClick"
+                :row-class-name="tableRowClassName"
+                :show-overflow-tooltip="true"
+                :header-cell-style="tableHeaderStyle"
+                :span-method="handleSpanMethod"
+                highlight-current-row
+                @current-change="handleCurrentChangeTable">
         <!-- 序号列 -->
         <el-table-column
           type="index"
@@ -879,6 +888,7 @@ const currentPage = ref(1)
 const pageSize = ref(10)
 
 const tableRef = ref();
+const mainTableRef = ref(); // 添加表格ref
 
 // 新增响应式变量
 const maintItemsWidth = ref('auto')
@@ -973,9 +983,9 @@ const toggleShowAllMaterials = () => {
 };
 
 // 为表格行添加类名,实现高亮效果
-const tableRowClassName = ({ row }) => {
+/* const tableRowClassName = ({ row }) => {
   return row.isSelected ? 'highlight-row' : '';
-};
+}; */
 
 // 分组合并计算逻辑
 const groupSpans = ref<Record<string, { span: number, index: number }>>({})
@@ -2199,6 +2209,13 @@ const handleSelectMaterial = () => {
   openMaterialForm(currentBomItem.value);
 };
 
+// Element UI表格当前行变化事件
+const handleCurrentChangeTable = (currentRow) => {
+  if (currentRow) {
+    handleBomItemClick(currentRow);
+  }
+};
+
 // 监听分页数据和规则变化 - 重新布局表格
 watch([paginatedList, hasMileageRuleInCurrentPage, hasTimeRuleInCurrentPage, hasDateRuleInCurrentPage], () => {
   nextTick(() => {
@@ -2217,10 +2234,12 @@ watch([paginatedList, currentPage], () => {
 // 监听分页数据变化,自动选中第一条
 watch(() => paginatedList.value, (newList) => {
   if (newList && newList.length > 0) {
-    // 取消之前选中的保养项(如果存在)
-    if (currentBomItem.value) {
-      currentBomItem.value.isSelected = false;
-    }
+    // 使用Element UI的方法设置当前行
+    nextTick(() => {
+      if (mainTableRef.value) {
+        mainTableRef.value.setCurrentRow(newList[0]);
+      }
+    });
 
     // 选中新分页的第一条
     handleBomItemClick(newList[0]);
@@ -2404,24 +2423,6 @@ onMounted(async () => {
             ? item.name.split('->')[0].trim()
             : '';
 
-          // 处理物料数据映射
-          /* if (item.deviceBomMaterials && item.deviceBomMaterials.length > 0) {
-            // 生成唯一键
-            const uniqueKey = `${item.bomNodeId}`;
-
-            // 转换物料字段映射
-            const mappedMaterials = item.deviceBomMaterials.map(material => ({
-              ...material,
-              materialName: material.name, // name -> materialName
-              materialCode: material.code, // code -> materialCode
-              projectDepartment: material.storageLocation, // storageLocation -> projectDepartment
-              totalInventoryQuantity: material.stockQuantity, // stockQuantity -> totalInventoryQuantity
-              deviceId: item.deviceId // 添加 deviceId,从保养项中获取
-            }));
-            // 存储到映射表中
-            bomMaterialsMap.value[uniqueKey] = mappedMaterials;
-          } */
-
           if (item.mileageRule === 0) {
             item.nextMaintenanceKm = calculateNextMaintenanceKm(item);
             item.remainKm = calculateRemainKm(item);
@@ -2523,6 +2524,13 @@ onMounted(async () => {
       const uniqueKey = `${firstItem.bomNodeId}`;
       materialList.value = bomMaterialsMap.value[uniqueKey] || [];
 
+      // 使用Element UI的方法设置当前行
+      nextTick(() => {
+        if (mainTableRef.value) {
+          mainTableRef.value.setCurrentRow(firstItem);
+        }
+      });
+
       handleBomItemClick(firstItem);
     }
     // 页面初始化完成后立即计算费用
@@ -2549,12 +2557,12 @@ onUnmounted(async () => {
 // 处理保养项点击事件
 const handleBomItemClick = (item) => {
   // 移除之前的高亮
-  if (currentBomItem.value) {
+  /* if (currentBomItem.value) {
     currentBomItem.value.isSelected = false;
-  }
+  } */
 
   // 设置当前选中项
-  item.isSelected = true;
+  // item.isSelected = true;
   currentBomItem.value = item;
   // 切换到当前保养项物料视图
   showAllMaterials.value = false;
@@ -2685,7 +2693,7 @@ const handleRowClick = (row) => {
   border-bottom: none; /* 移除默认边框 */
 }
 
-/* 添加选中行高亮样式 - 增强版 */
+/* 添加选中行高亮样式 - 增强版
 :deep(.el-table .highlight-row) {
   background-color: #d1eaff !important;
 }
@@ -2697,13 +2705,14 @@ const handleRowClick = (row) => {
 :deep(.el-table .highlight-row.current-row>td) {
   background-color: #99ccff !important;
   border-bottom: 2px solid #409eff !important;
-}
+} */
 
 /* 增强高亮行样式的特异性,确保覆盖Element UI的默认样式 */
-:deep(.el-table__body tr.highlight-row td) {
+:deep(.el-table__body tr.current-row>td) {
   background-color: #d1eaff !important;
 }
 
+/*
 :deep(.el-table__body tr.highlight-row:hover td) {
   background-color: #d9ecff !important;
 }
@@ -2711,20 +2720,21 @@ const handleRowClick = (row) => {
 :deep(.el-table__body tr.highlight-row.current-row td) {
   background-color: #c6e2ff !important;
   border-bottom: 2px solid #409eff !important;
-}
+} */
 
-/* 针对斑马纹表格的特殊处理 */
+/* 针对斑马纹表格的特殊处理
 :deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped td) {
   background-color: #d1eaff !important;
-}
+} */
 
-:deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped:hover td) {
+:deep(.el-table__body tr.current-row:hover>td) {
   background-color: #b8dfff !important;
 }
 
+/*
 :deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped.current-row td) {
   background-color: #99ccff !important;
-}
+} */
 
 /* 物料列表操作区域样式 */
 .material-list-header {

+ 17 - 1
src/views/pms/iotrhdailyreport/DeptTree2.vue

@@ -52,6 +52,15 @@ const firstLevelKeys = ref([])
 let selectedNode = null;
 const treeStore = useTreeStore();
 
+// props 定义,接收 deptId 参数
+interface Props {
+  deptId?: number
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  deptId: 157 // 可以设置默认值,但建议父组件必须传递
+})
+
 const handleRightClick = (event, { node, data }) => {
   event.preventDefault();
   menuX.value = event.clientX;
@@ -82,7 +91,7 @@ const handleMenuClick = (action) => {
 };
 /** 获得部门树 */
 const getTree = async () => {
-  const res = await DeptApi.specifiedSimpleDepts(157)
+  const res = await DeptApi.specifiedSimpleDepts(props.deptId)
   deptList.value = []
   deptList.value.push(...handleTree(res))
   firstLevelKeys.value = deptList.value.map(node => node.id);
@@ -106,6 +115,13 @@ watch(deptName, (val) => {
   treeRef.value!.filter(val)
 })
 
+// 监听 deptId 变化,当父组件改变 deptId 时重新加载树
+watch(() => props.deptId, (newVal, oldVal) => {
+  if (newVal !== oldVal) {
+    getTree()
+  }
+})
+
 /** 初始化 */
 onMounted(async () => {
   await getTree()

+ 2 - 1
src/views/pms/iotrhdailyreport/index.vue

@@ -2,7 +2,7 @@
   <el-row :gutter="20">
     <el-col :span="4" :xs="24">
       <ContentWrap class="h-1/1">
-        <DeptTree2 @node-click="handleDeptNodeClick" />
+        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
       </ContentWrap>
     </el-col>
     <el-col :span="20" :xs="24">
@@ -252,6 +252,7 @@ const queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
+const rootDeptId = ref(157)
 
 // 表格引用
 const tableRef = ref()

+ 61 - 23
src/views/pms/iotrydailyreport/IotRyXjDailyReportForm.vue

@@ -4,7 +4,7 @@
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="120px"
+      label-width="130px"
       v-loading="formLoading"
     >
       <el-form-item label="施工队伍" prop="deptName">
@@ -26,18 +26,29 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="上井次完井时间" prop="latestWellDoneTime">
-        <el-date-picker
-          v-model="formData.latestWellDoneTime"
-          type="date"
-          value-format="x"
-          placeholder=""
-          disabled
-        />
+      <el-form-item :label="t('project.technology')" prop="technique">
+        <el-select v-model="displayData.technique" placeholder="请选择" disabled>
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY)"
+            :key="dict.id"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="井别" prop="wellCategory">
+        <el-input v-model="displayData.wellCategory" placeholder="" disabled/>
       </el-form-item>
       <el-form-item label="设计井深(m)" prop="designWellDepth">
         <el-input v-model="displayData.designWellDepth" placeholder="" disabled/>
       </el-form-item>
+      <el-form-item label="井控级别" prop="wellControlLevel">
+        <el-input v-model="displayData.wellControlLevel" placeholder="" disabled/>
+      </el-form-item>
+      <el-form-item label="套生段产管尺寸(mm)" prop="casingPipeSize">
+        <el-input v-model="displayData.casingPipeSize" placeholder="" disabled/>
+      </el-form-item>
+      <!--
       <el-form-item label="当前井深(m)" prop="currentDepth">
         <el-input v-model="formData.currentDepth" placeholder="请输入当前井深(m)" />
       </el-form-item>
@@ -55,13 +66,14 @@
       </el-form-item>
       <el-form-item label="当日油耗(吨)" prop="dailyFuel">
         <el-input v-model="formData.dailyFuel" placeholder="请输入当日油耗(吨)" />
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="总施工井数" prop="monthlyFuel">
         <el-input v-model="displayData.totalConstructionWells" placeholder="" disabled/>
       </el-form-item>
       <el-form-item label="完工井数" prop="completedWells">
         <el-input v-model="displayData.completedWells" placeholder="" disabled/>
       </el-form-item>
+      <!--
       <el-form-item label="泥浆密度(g/cm³)" prop="mudDensity">
         <el-input v-model="formData.mudDensity" placeholder="请输入泥浆性能-密度(g/cm³)" />
       </el-form-item>
@@ -79,6 +91,27 @@
       </el-form-item>
       <el-form-item label="设计井身结构" prop="designWellStruct">
         <el-input v-model="displayData.designWellStruct" placeholder="" type="textarea" disabled/>
+      </el-form-item> -->
+      <el-form-item :label="t('project.currentOperation')" prop="currentOperation">
+        <el-input v-model="formData.currentOperation" placeholder="请输入目前工序" type="textarea"/>
+      </el-form-item>
+      <el-form-item :label="t('project.nextPlan')" prop="nextPlan">
+        <el-input v-model="formData.nextPlan" placeholder="请输入下步工序" type="textarea"/>
+      </el-form-item>
+      <el-form-item :label="t('project.transitTime')" prop="transitTime">
+        <el-input v-model="displayTransitTime" placeholder="" disabled
+                  :class="{'red-text': isTransitTimeOver}"
+                  id="transitTimeInput" />
+      </el-form-item>
+      <el-form-item :label="t('project.nptReason')" prop="ryNptReason">
+        <el-select v-model="formData.ryNptReason" placeholder="请选择" disabled>
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_NPT_REASON)"
+            :key="dict.id"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item label="生产动态" prop="productionStatus">
         <el-input v-model="formData.productionStatus" placeholder="请输入生产动态" type="textarea"/>
@@ -122,9 +155,10 @@ watch(() => props.rowData, (newVal) => {
     displayData.value.contractName = newVal.contractName || ''
     displayData.value.taskName = newVal.taskName || ''
     displayData.value.designWellDepth = newVal.designWellDepth || ''
-    displayData.value.designWellStruct = newVal.designWellStruct || ''
-    displayData.value.totalConstructionWells = newVal.totalConstructionWells || ''
-    displayData.value.completedWells = newVal.completedWells || ''
+    displayData.value.technique = newVal.technique || ''
+    displayData.value.wellCategory = newVal.wellCategory || ''
+    displayData.value.wellControlLevel = newVal.wellControlLevel || ''
+    displayData.value.casingPipeSize = newVal.casingPipeSize || ''
   }
 }, { immediate: true })
 
@@ -139,9 +173,10 @@ const displayData = ref({
   contractName: '',
   taskName: '',
   designWellDepth: '',
-  designWellStruct: '',
-  totalConstructionWells: '',
-  completedWells: ''
+  technique: '',
+  wellCategory: '',
+  wellControlLevel: '',
+  casingPipeSize: ''
 })
 
 const formData = ref({
@@ -152,6 +187,7 @@ const formData = ref({
   projectClassification: undefined,
   relocationDays: undefined,
   latestWellDoneTime: undefined,
+  technique: undefined,
   designWellDepth: undefined,
   currentDepth: undefined,
   dailyFootage: undefined,
@@ -164,7 +200,7 @@ const formData = ref({
   dailyFuel: undefined,
   monthlyFuel: undefined,
   nonProductionTime: undefined,
-  nptReason: undefined,
+  ryNptReason: undefined,
   constructionStartDate: undefined,
   constructionEndDate: undefined,
   productionStatus: undefined,
@@ -201,9 +237,10 @@ const open = async (type: string, id?: number) => {
     displayData.value.contractName = props.rowData.contractName || ''
     displayData.value.taskName = props.rowData.taskName || ''
     displayData.value.designWellDepth = props.rowData.designWellDepth || ''
-    displayData.value.designWellStruct = props.rowData.designWellStruct || ''
-    displayData.value.totalConstructionWells = props.rowData.totalConstructionWells || ''
-    displayData.value.completedWells = props.rowData.completedWells || ''
+    displayData.value.technique = props.rowData.technique || ''
+    displayData.value.wellCategory = props.rowData.wellCategory || ''
+    displayData.value.wellControlLevel = props.rowData.wellControlLevel || ''
+    displayData.value.casingPipeSize = props.rowData.casingPipeSize || ''
   }
 
   // 修改时,设置数据
@@ -291,9 +328,10 @@ const resetForm = () => {
     contractName: '',
     taskName: '',
     designWellDepth: '',
-    designWellStruct: '',
-    totalConstructionWells: '',
-    completedWells: ''
+    technique: '',
+    wellCategory: '',
+    wellControlLevel: '',
+    casingPipeSize: ''
   }
 
   formRef.value?.resetFields()

+ 210 - 187
src/views/pms/iotrydailyreport/index.vue

@@ -1,199 +1,209 @@
 <template>
-  <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="项目" prop="contractName">
-        <el-input
-          v-model="queryParams.contractName"
-          placeholder="请输入项目"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="任务" prop="taskName">
-        <el-input
-          v-model="queryParams.taskName"
-          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-ry-daily-report:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['pms:iot-ry-daily-report:export']"
+  <el-row :gutter="20">
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <ContentWrap class="mb-15px">
-    <div class="color-legend">
-      <div class="legend-item">
-        <span class="color-indicator orange"></span>
-        <span>进尺工作时间+其它生产时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span>
-      </div>
-    </div>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap ref="tableContainerRef">
-    <div class="table-container">
-      <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true"
-                style="width: 100%" :cell-style="cellStyle">
-        <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
-          <template #default="scope">
-            {{ scope.$index + 1 }}
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="日期"
-          align="center"
-          prop="createTime"
-          :formatter="dateFormatter2"
-          :width="columnWidths.createTime"
-        />
-        <el-table-column label="施工队伍" align="center" prop="deptName" :width="columnWidths.deptName"/>
-        <el-table-column label="项目" align="center" prop="contractName" :width="columnWidths.contractName"/>
-        <el-table-column label="任务" align="center" prop="taskName" :width="columnWidths.taskName"/>
-        <el-table-column label="设备型号" align="center" prop="equipmentType" :width="columnWidths.equipmentType"/>
-        <el-table-column :label="t('project.status')" align="center" prop="rigStatus" :width="columnWidths.rigStatus">
-          <template #default="scope">
-            <dict-tag :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE" :value="scope.row.rigStatus" />
-          </template>
-        </el-table-column>
-        <el-table-column label="上井次完井时间" align="center" prop="latestWellDoneTime" :width="columnWidths.latestWellDoneTime" :formatter="dateFormatter2"/>
-        <el-table-column label="设计井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
-        <el-table-column label="当前井深(m)" align="center" prop="currentDepth" :width="columnWidths.currentDepth" />
-        <el-table-column label="日进尺(m)" align="center" prop="dailyFootage" :width="columnWidths.dailyFootage" />
-        <el-table-column label="月进尺(m)" align="center" prop="monthlyFootage" :width="columnWidths.monthlyFootage"/>
-        <el-table-column label="年累计进尺(m)" align="center" prop="annualFootage" :width="columnWidths.annualFootage"/>
-        <el-table-column label="总施工井数" align="center" prop="totalConstructionWells" :width="columnWidths.totalConstructionWells"/>
-        <el-table-column label="完工井数" align="center" prop="completedWells" :width="columnWidths.completedWells"/>
-        <el-table-column label="泥浆性能-密度(g/cm³)" align="center" prop="mudDensity" :width="columnWidths.mudDensity"/>
-        <el-table-column label="泥浆性能-粘度(S)" align="center" prop="mudViscosity" :width="columnWidths.mudViscosity"/>
-        <el-table-column
-          label="施工开始日期"
-          align="center"
-          prop="constructionStartDate"
-          :formatter="dateFormatter"
-          :width="columnWidths.constructionStartDate"
-        />
-        <el-table-column
-          label="施工结束日期"
-          align="center"
-          prop="constructionEndDate"
-          :formatter="dateFormatter"
-          :width="columnWidths.constructionEndDate"
-        />
-        <el-table-column label="水平段长度(m)" align="center" prop="lateralLength" :width="columnWidths.lateralLength" />
-        <el-table-column label="井斜(°)" align="center" prop="wellInclination" :width="columnWidths.wellInclination"/>
-        <el-table-column label="方位(°)" align="center" prop="azimuth" :width="columnWidths.azimuth"/>
-        <el-table-column label="设计井身结构" align="center" :width="columnWidths.designWellStruct" fixed-width>
-          <template #default="scope">
-            <el-tooltip
-              effect="light"
-              :content="scope.row.designWellStruct"
-              placement="top"
-              popper-class="design-well-struct-tooltip"
-              :disabled="!scope.row.designWellStruct || scope.row.designWellStruct.length <= 30"
-            >
-              <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.designWellStruct) }}</span>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-        <el-table-column label="生产动态" align="center" :width="columnWidths.productionStatus" fixed-width>
-          <template #default="scope">
-            <el-tooltip
-              effect="light"
-              :content="scope.row.productionStatus"
-              placement="top"
-              popper-class="design-well-struct-tooltip"
-              :disabled="!scope.row.productionStatus || scope.row.productionStatus.length <= 30"
-            >
-              <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.productionStatus) }}</span>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-        <el-table-column label="进尺工作时间(H)" align="center" prop="drillingWorkingTime" :width="columnWidths.drillingWorkingTime"/>
-        <el-table-column label="其它生产时间(H)" align="center" prop="otherProductionTime" :width="columnWidths.otherProductionTime"/>
-        <el-table-column label="非生产时间" align="center">
-          <el-table-column label="事故(H)" align="center" prop="accidentTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="修理(H)" align="center" prop="repairTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="自停(H)" align="center" prop="selfStopTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="复杂(H)" align="center" prop="complexityTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="搬迁(H)" align="center" prop="relocationTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="整改(H)" align="center" prop="rectificationTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="等停(H)" align="center" prop="waitingStopTime" :width="columnWidths.nonProductionTime"/>
-          <el-table-column label="冬休(H)" align="center" prop="winterBreakTime" :width="columnWidths.nonProductionTime"/>
-        </el-table-column>
-        <el-table-column label="操作" align="center" :width="columnWidths.operation" fixed="right">
-          <template #default="scope">
+          <el-form-item label="项目" prop="contractName">
+            <el-input
+              v-model="queryParams.contractName"
+              placeholder="请输入项目"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input
+              v-model="queryParams.taskName"
+              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
-              link
               type="primary"
-              @click="openForm('update', scope.row.id, scope.row)"
-              v-hasPermi="['pms:iot-ry-daily-report:update']"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['pms:iot-ry-daily-report:create']"
             >
-              编辑
+              <Icon icon="ep:plus" class="mr-5px" /> 新增
             </el-button>
             <el-button
-              link
-              type="danger"
-              @click="handleDelete(scope.row.id)"
-              v-hasPermi="['pms:iot-ry-daily-report:delete']"
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['pms:iot-ry-daily-report:export']"
             >
-              删除
+              <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </ContentWrap>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <IotRyDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData"/>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <ContentWrap class="mb-15px">
+        <div class="color-legend">
+          <div class="legend-item">
+            <span class="color-indicator orange"></span>
+            <span>进尺工作时间+其它生产时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span>
+          </div>
+        </div>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap ref="tableContainerRef">
+        <div class="table-container">
+          <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true"
+                    style="width: 100%" :cell-style="cellStyle">
+            <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
+              <template #default="scope">
+                {{ scope.$index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="日期"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter2"
+              :width="columnWidths.createTime"
+            />
+            <el-table-column label="施工队伍" align="center" prop="deptName" :width="columnWidths.deptName"/>
+            <el-table-column label="项目" align="center" prop="contractName" :width="columnWidths.contractName"/>
+            <el-table-column label="任务" align="center" prop="taskName" :width="columnWidths.taskName"/>
+            <el-table-column label="设备型号" align="center" prop="equipmentType" :width="columnWidths.equipmentType"/>
+            <el-table-column :label="t('project.status')" align="center" prop="rigStatus" :width="columnWidths.rigStatus">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE" :value="scope.row.rigStatus" />
+              </template>
+            </el-table-column>
+            <el-table-column label="上井次完井时间" align="center" prop="latestWellDoneTime" :width="columnWidths.latestWellDoneTime" :formatter="dateFormatter2"/>
+            <el-table-column label="设计井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
+            <el-table-column label="当前井深(m)" align="center" prop="currentDepth" :width="columnWidths.currentDepth" />
+            <el-table-column label="日进尺(m)" align="center" prop="dailyFootage" :width="columnWidths.dailyFootage" />
+            <el-table-column label="月进尺(m)" align="center" prop="monthlyFootage" :width="columnWidths.monthlyFootage"/>
+            <el-table-column label="年累计进尺(m)" align="center" prop="annualFootage" :width="columnWidths.annualFootage"/>
+            <el-table-column label="总施工井数" align="center" prop="totalConstructionWells" :width="columnWidths.totalConstructionWells"/>
+            <el-table-column label="完工井数" align="center" prop="completedWells" :width="columnWidths.completedWells"/>
+            <el-table-column label="泥浆性能-密度(g/cm³)" align="center" prop="mudDensity" :width="columnWidths.mudDensity"/>
+            <el-table-column label="泥浆性能-粘度(S)" align="center" prop="mudViscosity" :width="columnWidths.mudViscosity"/>
+            <el-table-column
+              label="施工开始日期"
+              align="center"
+              prop="constructionStartDate"
+              :formatter="dateFormatter"
+              :width="columnWidths.constructionStartDate"
+            />
+            <el-table-column
+              label="施工结束日期"
+              align="center"
+              prop="constructionEndDate"
+              :formatter="dateFormatter"
+              :width="columnWidths.constructionEndDate"
+            />
+            <el-table-column label="水平段长度(m)" align="center" prop="lateralLength" :width="columnWidths.lateralLength" />
+            <el-table-column label="井斜(°)" align="center" prop="wellInclination" :width="columnWidths.wellInclination"/>
+            <el-table-column label="方位(°)" align="center" prop="azimuth" :width="columnWidths.azimuth"/>
+            <el-table-column label="设计井身结构" align="center" :width="columnWidths.designWellStruct" fixed-width>
+              <template #default="scope">
+                <el-tooltip
+                  effect="light"
+                  :content="scope.row.designWellStruct"
+                  placement="top"
+                  popper-class="design-well-struct-tooltip"
+                  :disabled="!scope.row.designWellStruct || scope.row.designWellStruct.length <= 30"
+                >
+                  <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.designWellStruct) }}</span>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+            <el-table-column label="生产动态" align="center" :width="columnWidths.productionStatus" fixed-width>
+              <template #default="scope">
+                <el-tooltip
+                  effect="light"
+                  :content="scope.row.productionStatus"
+                  placement="top"
+                  popper-class="design-well-struct-tooltip"
+                  :disabled="!scope.row.productionStatus || scope.row.productionStatus.length <= 30"
+                >
+                  <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.productionStatus) }}</span>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+            <el-table-column label="进尺工作时间(H)" align="center" prop="drillingWorkingTime" :width="columnWidths.drillingWorkingTime"/>
+            <el-table-column label="其它生产时间(H)" align="center" prop="otherProductionTime" :width="columnWidths.otherProductionTime"/>
+            <el-table-column label="非生产时间" align="center">
+              <el-table-column label="事故(H)" align="center" prop="accidentTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="修理(H)" align="center" prop="repairTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="自停(H)" align="center" prop="selfStopTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="复杂(H)" align="center" prop="complexityTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="搬迁(H)" align="center" prop="relocationTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="整改(H)" align="center" prop="rectificationTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="等停(H)" align="center" prop="waitingStopTime" :width="columnWidths.nonProductionTime"/>
+              <el-table-column label="冬休(H)" align="center" prop="winterBreakTime" :width="columnWidths.nonProductionTime"/>
+            </el-table-column>
+            <el-table-column label="操作" align="center" :width="columnWidths.operation" fixed="right">
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  @click="openForm('update', scope.row.id, scope.row)"
+                  v-hasPermi="['pms:iot-ry-daily-report:update']"
+                >
+                  编辑
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  @click="handleDelete(scope.row.id)"
+                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+
+      <!-- 表单弹窗:添加/修改 -->
+      <IotRyDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData"/>
+    </el-col>
+  </el-row>
+
 </template>
 
 <script setup lang="ts">
@@ -203,6 +213,7 @@ import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyrep
 import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
 import {DICT_TYPE, getDictLabel} from "@/utils/dict";
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
+import DeptTree2 from "@/views/pms/iotrhdailyreport/DeptTree2.vue";
 
 /** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyDailyReport' })
@@ -213,6 +224,8 @@ const { t } = useI18n() // 国际化
 // 添加 selectedRowData 响应式变量
 const selectedRowData = ref<Record<string, any> | null>(null)
 
+const rootDeptId = ref(158)
+
 const loading = ref(true) // 列表的加载中
 const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
@@ -544,6 +557,16 @@ const handleDelete = async (id: number) => {
   } catch {}
 }
 
+// 响应式变量存储选中的部门
+const selectedDept = ref<{ id: number; name: string }>()
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  // 记录选中的部门信息
+  selectedDept.value = { id: row.id, name: row.name }
+  queryParams.deptId = row.id
+  await getList()
+}
+
 /** 导出按钮操作 */
 const handleExport = async () => {
   try {

+ 230 - 206
src/views/pms/iotrydailyreport/xjindex.vue

@@ -1,223 +1,234 @@
 <template>
-  <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="项目" prop="contractName">
-        <el-input
-          v-model="queryParams.contractName"
-          placeholder="请输入项目"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="任务" prop="taskName">
-        <el-input
-          v-model="queryParams.taskName"
-          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-rh-daily-report:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['pms:iot-rh-daily-report:export']"
+  <el-row :gutter="20">
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <ContentWrap class="mb-15px">
-    <div class="color-legend">
-      <div class="legend-item">
-        <span class="color-indicator red"></span>
-        <span>运行时效=生产时间/额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过100%红色预警</span>
-      </div>
-      <div class="legend-item">
-        <span class="color-indicator orange"></span>
-        <span>生产时间+非生产时间=额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span>
-      </div>
-    </div>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap ref="tableContainerRef">
-    <div class="table-container">
-      <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true"
-                style="width: 100%" :cell-style="cellStyle">
-        <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
-          <template #default="scope">
-            {{ scope.$index + 1 }}
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="日期"
-          align="center"
-          prop="createTime"
-          :formatter="dateFormatter2"
-          :width="columnWidths.createTime"
-        />
-        <el-table-column label="施工队伍" align="center" prop="deptName" :width="columnWidths.deptName"/>
-        <el-table-column label="项目" align="center" prop="contractName" :width="columnWidths.contractName"/>
-        <el-table-column label="任务" align="center" prop="taskName" :width="columnWidths.taskName"/>
-        <el-table-column :label="t('project.status')" align="center" prop="rigStatus" :width="columnWidths.rigStatus">
-          <template #default="scope">
-            <dict-tag :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE" :value="scope.row.rigStatus" />
-          </template>
-        </el-table-column>
-
-        <!--
-        <el-table-column label="上井次完井时间" align="center" prop="latestWellDoneTime" :width="columnWidths.latestWellDoneTime" :formatter="dateFormatter2"/>
-        <el-table-column label="设计井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
-        <el-table-column label="当前井深(m)" align="center" prop="currentDepth" :width="columnWidths.currentDepth" />
-        <el-table-column label="日进尺(m)" align="center" prop="dailyFootage" :width="columnWidths.dailyFootage" />
-        <el-table-column label="月进尺(m)" align="center" prop="monthlyFootage" :width="columnWidths.monthlyFootage"/>
-        <el-table-column label="年累计进尺(m)" align="center" prop="annualFootage" :width="columnWidths.annualFootage"/> -->
-        <el-table-column label="总施工井数" align="center" prop="totalConstructionWells" :width="columnWidths.totalConstructionWells"/>
-        <el-table-column label="完工井数" align="center" prop="completedWells" :width="columnWidths.completedWells"/>
-        <el-table-column :label="t('project.technology')" align="center" prop="technique" :width="columnWidths.technique">
-          <template #default="scope">
-            <dict-tag :type="DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY" :value="scope.row.technique" />
-          </template>
-        </el-table-column>
-        <el-table-column label="井别" align="center" prop="wellCategory" :width="columnWidths.wellCategory"/>
-        <el-table-column label="井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
-        <el-table-column label="套生段产管尺寸(mm)" align="center" prop="casingPipeSize" :width="columnWidths.casingPipeSize"/>
-        <el-table-column label="井控级别" align="center" prop="wellControlLevel" :width="columnWidths.wellControlLevel"/>
-        <!--
-        <el-table-column label="泥浆性能-密度(g/cm³)" align="center" prop="mudDensity" :width="columnWidths.mudDensity"/>
-        <el-table-column label="泥浆性能-粘度(S)" align="center" prop="mudViscosity" :width="columnWidths.mudViscosity"/> -->
-        <el-table-column
-          label="施工开始日期"
-          align="center"
-          prop="constructionStartDate"
-          :formatter="dateFormatter"
-          :width="columnWidths.constructionStartDate"
-        />
-        <el-table-column
-          label="施工结束日期"
-          align="center"
-          prop="constructionEndDate"
-          :formatter="dateFormatter"
-          :width="columnWidths.constructionEndDate"
-        />
-        <!--
-        <el-table-column label="水平段长度(m)" align="center" prop="lateralLength" :width="columnWidths.lateralLength" />
-        <el-table-column label="井斜(°)" align="center" prop="wellInclination" :width="columnWidths.wellInclination"/>
-        <el-table-column label="方位(°)" align="center" prop="azimuth" :width="columnWidths.azimuth"/>
-        <el-table-column label="设计井身结构" align="center" :width="columnWidths.designWellStruct" fixed-width>
-          <template #default="scope">
-            <el-tooltip
-              effect="light"
-              :content="scope.row.designWellStruct"
-              placement="top"
-              popper-class="design-well-struct-tooltip"
-              :disabled="!scope.row.designWellStruct || scope.row.designWellStruct.length <= 30"
-            >
-              <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.designWellStruct) }}</span>
-            </el-tooltip>
-          </template>
-        </el-table-column> -->
-        <el-table-column label="目前工序" align="center" prop="currentOperation" :width="columnWidths.currentOperation"/>
-        <el-table-column label="下部工序" align="center" prop="nextPlan" :width="columnWidths.nextPlan"/>
-        <el-table-column label="运行时效" align="center" prop="transitTime" :width="columnWidths.transitTime" :formatter="percentageFormatter"/>
-        <el-table-column label="额定生产时间(H)" align="center" prop="ratedProductionTime" :width="columnWidths.ratedProductionTime"/>
-        <el-table-column label="生产时间(H)" align="center" prop="productionTime" :width="columnWidths.productionTime"/>
-        <el-table-column label="非生产时间(H)" align="center" prop="nonProductionTime" :width="columnWidths.nonProductionTime"/>
-        <el-table-column :label="t('project.nptReason')" align="center" prop="ryNptReason" :width="columnWidths.ryNptReason">
-          <template #default="scope">
-            <dict-tag :type="DICT_TYPE.PMS_PROJECT_RY_NPT_REASON" :value="scope.row.ryNptReason" />
-          </template>
-        </el-table-column>
-        <el-table-column label="生产动态" align="center" :width="columnWidths.productionStatus" fixed-width>
-          <template #default="scope">
-            <el-tooltip
-              effect="light"
-              :content="scope.row.productionStatus"
-              placement="top"
-              popper-class="design-well-struct-tooltip"
-              :disabled="!scope.row.productionStatus || scope.row.productionStatus.length <= 30"
-            >
-              <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.productionStatus) }}</span>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" align="center" :width="columnWidths.operation" fixed="right">
-          <template #default="scope">
+          <el-form-item label="项目" prop="contractName">
+            <el-input
+              v-model="queryParams.contractName"
+              placeholder="请输入项目"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input
+              v-model="queryParams.taskName"
+              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
-              link
               type="primary"
-              @click="openForm('update', scope.row.id, scope.row)"
-              v-hasPermi="['pms:iot-rh-daily-report:update']"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['pms:iot-rh-daily-report:create']"
             >
-              编辑
+              <Icon icon="ep:plus" class="mr-5px" /> 新增
             </el-button>
             <el-button
-              link
-              type="danger"
-              @click="handleDelete(scope.row.id)"
-              v-hasPermi="['pms:iot-rh-daily-report:delete']"
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['pms:iot-rh-daily-report:export']"
             >
-              删除
+              <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </ContentWrap>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <IotRyDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData"/>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <ContentWrap class="mb-15px">
+        <div class="color-legend">
+          <div class="legend-item">
+            <span class="color-indicator red"></span>
+            <span>运行时效=生产时间/额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过100%红色预警</span>
+          </div>
+          <div class="legend-item">
+            <span class="color-indicator orange"></span>
+            <span>生产时间+非生产时间=额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span>
+          </div>
+        </div>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap ref="tableContainerRef">
+        <div class="table-container">
+          <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true"
+                    style="width: 100%" :cell-style="cellStyle">
+            <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
+              <template #default="scope">
+                {{ scope.$index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="日期"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter2"
+              :width="columnWidths.createTime"
+            />
+            <el-table-column label="施工队伍" align="center" prop="deptName" :width="columnWidths.deptName"/>
+            <el-table-column label="项目" align="center" prop="contractName" :width="columnWidths.contractName"/>
+            <el-table-column label="任务" align="center" prop="taskName" :width="columnWidths.taskName"/>
+            <el-table-column :label="t('project.status')" align="center" prop="rigStatus" :width="columnWidths.rigStatus">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE" :value="scope.row.rigStatus" />
+              </template>
+            </el-table-column>
+
+            <!--
+            <el-table-column label="上井次完井时间" align="center" prop="latestWellDoneTime" :width="columnWidths.latestWellDoneTime" :formatter="dateFormatter2"/>
+            <el-table-column label="设计井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
+            <el-table-column label="当前井深(m)" align="center" prop="currentDepth" :width="columnWidths.currentDepth" />
+            <el-table-column label="日进尺(m)" align="center" prop="dailyFootage" :width="columnWidths.dailyFootage" />
+            <el-table-column label="月进尺(m)" align="center" prop="monthlyFootage" :width="columnWidths.monthlyFootage"/>
+            <el-table-column label="年累计进尺(m)" align="center" prop="annualFootage" :width="columnWidths.annualFootage"/> -->
+            <el-table-column label="总施工井数" align="center" prop="totalConstructionWells" :width="columnWidths.totalConstructionWells"/>
+            <el-table-column label="完工井数" align="center" prop="completedWells" :width="columnWidths.completedWells"/>
+            <el-table-column :label="t('project.technology')" align="center" prop="technique" :width="columnWidths.technique">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY" :value="scope.row.technique" />
+              </template>
+            </el-table-column>
+            <el-table-column label="井别" align="center" prop="wellCategory" :width="columnWidths.wellCategory"/>
+            <el-table-column label="井深(m)" align="center" prop="designWellDepth" :width="columnWidths.designWellDepth"/>
+            <el-table-column label="套生段产管尺寸(mm)" align="center" prop="casingPipeSize" :width="columnWidths.casingPipeSize"/>
+            <el-table-column label="井控级别" align="center" prop="wellControlLevel" :width="columnWidths.wellControlLevel"/>
+            <!--
+            <el-table-column label="泥浆性能-密度(g/cm³)" align="center" prop="mudDensity" :width="columnWidths.mudDensity"/>
+            <el-table-column label="泥浆性能-粘度(S)" align="center" prop="mudViscosity" :width="columnWidths.mudViscosity"/> -->
+            <el-table-column
+              label="施工开始日期"
+              align="center"
+              prop="constructionStartDate"
+              :formatter="dateFormatter"
+              :width="columnWidths.constructionStartDate"
+            />
+            <el-table-column
+              label="施工结束日期"
+              align="center"
+              prop="constructionEndDate"
+              :formatter="dateFormatter"
+              :width="columnWidths.constructionEndDate"
+            />
+            <!--
+            <el-table-column label="水平段长度(m)" align="center" prop="lateralLength" :width="columnWidths.lateralLength" />
+            <el-table-column label="井斜(°)" align="center" prop="wellInclination" :width="columnWidths.wellInclination"/>
+            <el-table-column label="方位(°)" align="center" prop="azimuth" :width="columnWidths.azimuth"/>
+            <el-table-column label="设计井身结构" align="center" :width="columnWidths.designWellStruct" fixed-width>
+              <template #default="scope">
+                <el-tooltip
+                  effect="light"
+                  :content="scope.row.designWellStruct"
+                  placement="top"
+                  popper-class="design-well-struct-tooltip"
+                  :disabled="!scope.row.designWellStruct || scope.row.designWellStruct.length <= 30"
+                >
+                  <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.designWellStruct) }}</span>
+                </el-tooltip>
+              </template>
+            </el-table-column> -->
+            <el-table-column :label="t('project.currentOperation')" align="center" prop="currentOperation" :width="columnWidths.currentOperation"/>
+            <el-table-column :label="t('project.nextPlan')" align="center" prop="nextPlan" :width="columnWidths.nextPlan"/>
+            <el-table-column :label="t('project.transitTime')" align="center" prop="transitTime" :width="columnWidths.transitTime" :formatter="percentageFormatter"/>
+            <el-table-column label="额定生产时间(H)" align="center" prop="ratedProductionTime" :width="columnWidths.ratedProductionTime"/>
+            <el-table-column label="生产时间(H)" align="center" prop="productionTime" :width="columnWidths.productionTime"/>
+            <el-table-column label="非生产时间(H)" align="center" prop="nonProductionTime" :width="columnWidths.nonProductionTime"/>
+            <el-table-column :label="t('project.nptReason')" align="center" prop="ryNptReason" :width="columnWidths.ryNptReason">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.PMS_PROJECT_RY_NPT_REASON" :value="scope.row.ryNptReason" />
+              </template>
+            </el-table-column>
+            <el-table-column label="生产动态" align="center" :width="columnWidths.productionStatus" fixed-width>
+              <template #default="scope">
+                <el-tooltip
+                  effect="light"
+                  :content="scope.row.productionStatus"
+                  placement="top"
+                  popper-class="design-well-struct-tooltip"
+                  :disabled="!scope.row.productionStatus || scope.row.productionStatus.length <= 30"
+                >
+                  <span class="design-well-struct-text">{{ formatDesignWellStruct(scope.row.productionStatus) }}</span>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" :width="columnWidths.operation" fixed="right">
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  @click="openForm('update', scope.row.id, scope.row)"
+                  v-hasPermi="['pms:iot-rh-daily-report:update']"
+                >
+                  编辑
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  @click="handleDelete(scope.row.id)"
+                  v-hasPermi="['pms:iot-rh-daily-report:delete']"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+
+      <!-- 表单弹窗:添加/修改 -->
+      <IotRyXjDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData"/>
+    </el-col>
+  </el-row>
+
 </template>
 
 <script setup lang="ts">
 import {dateFormatter, dateFormatter2} from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
-import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
+import IotRyXjDailyReportForm from './IotRyXjDailyReportForm.vue'
 import {DICT_TYPE, getDictLabel} from "@/utils/dict";
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
+import DeptTree2 from "@/views/pms/iotrhdailyreport/DeptTree2.vue";
 
 /** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyXjDailyReport' })
@@ -274,6 +285,8 @@ const queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
+const rootDeptId = ref(158)
+
 // 表格引用
 const tableRef = ref()
 // 表格容器引用
@@ -607,9 +620,10 @@ const openForm = (type: string, id?: number, row?: any) => {
       contractName: row.contractName,
       taskName: row.taskName,
       designWellDepth: row.designWellDepth,
-      designWellStruct: row.designWellStruct,
-      totalConstructionWells: row.totalConstructionWells,
-      completedWells: row.completedWells
+      technique: row.technique,
+      wellCategory: row.wellCategory,
+      wellControlLevel: row.wellControlLevel,
+      casingPipeSize: row.casingPipeSize
     }
   } else {
     selectedRowData.value = null
@@ -631,6 +645,16 @@ const handleDelete = async (id: number) => {
   } catch {}
 }
 
+// 响应式变量存储选中的部门
+const selectedDept = ref<{ id: number; name: string }>()
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  // 记录选中的部门信息
+  selectedDept.value = { id: row.id, name: row.name }
+  queryParams.deptId = row.id
+  await getList()
+}
+
 /** 导出按钮操作 */
 const handleExport = async () => {
   try {

+ 161 - 0
src/views/pms/yfclass/YfClassifyForm.vue

@@ -0,0 +1,161 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="上级编码分类" prop="parentId" label-width="100px">
+        <el-tree-select
+          v-model="formData.parentId"
+          :data="deptTree"
+          :props="defaultProps"
+          check-strictly
+          placeholder="请选择上级编码分类"
+          value-key="id"
+          :default-expanded-keys="firstLevelKeys"
+        />
+      </el-form-item>
+      <el-form-item label="分类名称" prop="name" label-width="100px">
+<!--        <el-input v-model="formData.name" placeholder="请输入编码分类名称" />-->
+        <lang-input v-model="formData.name" placeholder="请输入分类名称" />
+      </el-form-item>
+      <el-form-item label="编码" prop="name" label-width="100px">
+        <!--        <el-input v-model="formData.name" placeholder="请输入编码分类名称" />-->
+        <lang-input v-model="formData.code" placeholder="请输入编码" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status" v-if="formType==='update'">
+        <el-select v-model="formData.status" clearable placeholder="请选择状态">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" maxlength="11" placeholder="请输入备注" type="textarea" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as YfClassifyApi from '@/api/pms/yfclass'
+// import * as UserApi from '@/api/system/user'
+import { CommonStatusEnum } from '@/utils/constants'
+import { FormRules } from 'element-plus'
+import {IotYfClassifyApi, IotYfClassifyVO} from "@/api/pms/yfclass";
+
+defineOptions({ name: 'IotYfClassifyForm' })
+
+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,
+  code: undefined,
+  parentId: undefined,
+  name: undefined,
+  sort: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive<FormRules>({
+  parentId: [{ required: true, message: '上级编码分类不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '编码分类名称不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const deptTree = ref() // 树形结构
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number, parentId: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  formData.value.sort = 0;
+  formData.value.parentId = parentId
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await YfClassifyApi.IotYfClassifyApi.getIotYfClassify(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得编码分类树
+  await getTree()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotYfClassifyVO
+    if (formType.value === 'create') {
+      await IotYfClassifyApi.createIotYfClassify(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotYfClassifyApi.updateIotYfClassify(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    title: '',
+    parentId: undefined,
+    name: undefined,
+    sort: undefined,
+    leaderUserId: undefined,
+    phone: undefined,
+    email: undefined,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+
+const firstLevelKeys = ref([])
+/** 获得编码分类树 */
+const getTree = async () => {
+  deptTree.value = []
+  const data = await IotYfClassifyApi.getSimpleYfClassifyList()
+  let dept: Tree = { id: 0, name: '顶级编码分类', children: [] }
+  dept.children = handleTree(data)
+  deptTree.value.push(dept)
+  firstLevelKeys.value = deptTree.value.map(node => node.id);
+}
+</script>

+ 212 - 0
src/views/pms/yfclass/index.vue

@@ -0,0 +1,212 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="编码分类名称" prop="name" label-width="100px">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入编码分类名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="编码分类状态" prop="status" label-width="100px">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择编码分类状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" 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="['iot:product-classify:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="danger" plain @click="toggleExpandAll">
+          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      v-if="refreshTable"
+      @row-click="handleClick"
+    >
+      <el-table-column prop="name" label="编码分类名称" />
+      <el-table-column prop="code" label="编码分类编码" />
+      <el-table-column prop="sort" label="排序" />
+      <el-table-column prop="company" label="所属公司" >
+        <template #default="scope">
+          <span v-if="scope.row.company==='RH'" style="font-weight: bold">瑞恒兴域</span>
+          <span v-if="scope.row.company==='RD'" style="font-weight: bold">四川瑞都</span>
+          <span v-if="scope.row.company==='RY'" style="font-weight: bold">瑞鹰国际</span>
+          <span v-if="scope.row.company==='DF'" style="font-weight: bold">东方石油</span>
+          <span v-if="scope.row.company==='RL'" style="font-weight: bold">瑞霖技术</span>
+          <span v-if="scope.row.company==='RQ'" style="font-weight: bold">瑞气能源</span>
+          <span v-if="scope.row.company==='DM'" style="font-weight: bold">DMCC 中东</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="状态">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column prop="remark" label="备注" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['iot:product-classify:update']"
+          >
+            修改
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['iot:product-classify:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <YfClassifyForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import * as YfClassifyApi from '@/api/pms/yfclass'
+import YfClassifyForm from './YfClassifyForm.vue'
+import {IotYfClassifyApi} from "@/api/pms/yfclass";
+
+defineOptions({ name: 'IotProductClassify' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref() // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 100,
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const isExpandAll = ref(true) // 是否展开,默认全部展开
+const refreshTable = ref(true) // 重新渲染表格状态
+const parentId = ref('')
+const handleClick = (node: {}) => {
+  parentId.value = node.id
+}
+/** 查询编码分类列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await YfClassifyApi.IotYfClassifyApi.getIotYfClassifyPage(queryParams)
+    list.value = handleTree(data)
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  nextTick(() => {
+    refreshTable.value = true
+  })
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.pageNo = 1
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number, parent: number) => {
+  parent = parentId.value
+  formRef.value.open(type, id, parent)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotYfClassifyApi.deleteIotYfClassify(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 获取用户列表
+  //userList.value = await UserApi.getSimpleUserList()
+})
+</script>
+<style scoped>
+/* 全局样式或 scoped 穿透 */
+:deep(.el-table__body tr) {
+  cursor: pointer; /* 手型光标 */
+}
+
+:deep(.el-table__body tr:hover) {
+  cursor: pointer;
+}
+</style>