Kaynağa Gözat

feat(inspect): 优化巡检计划列表及配置页面样式

- 统一巡检计划列表页的查询区域、表格及分页布局
- 优化巡检计划新增编辑页标题区和响应式表单排布
- 重构巡检设备路线拖拽列表,增加空状态和字段化展示
- 增加吸底操作栏并统一按钮尺寸
- 优化选择巡检设备弹窗的查询、已选标签及底部操作区
- 将巡检设备路线表格替换为 ZmTable
- 修复点击表格行取消选择时的选中状态处理
- 保持计划保存、路线排序及数据回传逻辑不变
Zimo 1 hafta önce
ebeveyn
işleme
bf211c7158

+ 349 - 217
src/views/pms/inspect/plan/InspectRouteList.vue

@@ -1,165 +1,172 @@
 <template>
-  <Dialog v-model="dialogVisible" :title="t('route.InspectionRoute')" style="width: 1300px; min-height: 600px">
-    <ContentWrap>
+  <Dialog
+    v-model="dialogVisible"
+    :title="t('route.SelectInspectionDevice')"
+    width="min(1280px, 94vw)"
+    class="inspect-route-select-dialog"
+    @close="handleClose">
+    <template #title>
+      <div class="dialog-title mb-4">
+        <div class="dialog-title__icon"><Icon icon="ep:guide" /></div>
+        <div>
+          <div class="dialog-title__text">{{ t('route.SelectInspectionDevice') }}</div>
+          <div class="dialog-title__subtitle">筛选并选择需要加入计划的巡检设备路线</div>
+        </div>
+      </div>
+    </template>
+
+    <div class="dialog-content">
       <el-form
-        class="-mb-15px"
-        :model="queryParams"
         ref="queryFormRef"
-        :inline="true"
-        label-width="68px"
-      >
-        <el-form-item :label="t('monitor.deviceName')" prop="deviceName" style="margin-left: 25px">
-          <el-input
-            v-model="queryParams.deviceName"
-            :placeholder="t('iotDevice.nameHolder')"
-            clearable
-            @keyup.enter="handleQuery"
-            class="!w-220px"
-          />
-        </el-form-item>
-        <el-form-item  :label="t('iotDevice.code')" prop="deviceCode">
-          <el-input
-            v-model="queryParams.deviceCode"
-            :placeholder="t('iotDevice.codeHolder')"
-            clearable
-            @keyup.enter="handleQuery"
-            class="!w-220px"
-          />
-        </el-form-item>
-        <el-form-item  :label="t('route.RouteName')" prop="routeName">
-          <el-input
-            v-model="queryParams.routeName"
-            :placeholder="t('route.nHolder')"
-            clearable
-            @keyup.enter="handleQuery"
-            class="!w-220px"
-          />
-        </el-form-item>
-        <el-form-item :label="t('iotDevice.dept')" prop="deptId">
-          <el-tree-select
-            v-model="queryParams.deptId"
-            :data="deptList"
-            :props="defaultProps"
-            check-strictly
-            node-key="id"
-            filterable
-            placeholder="请选择部门"
-            class="!w-220px"
-          />
-          <!--              <el-tree-select-->
-          <!--                v-model="formData.deptId"-->
-          <!--                :data="deptList"-->
-          <!--                :props="defaultProps"-->
-          <!--                check-strictly-->
-          <!--                node-key="id"-->
-          <!--                placeholder="请选择归属部门"-->
-          <!--              />-->
-        </el-form-item>
-        <el-form-item>
-          <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('chooseMaintain.search') }}</el-button>
-          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('chooseMaintain.reset')  }}</el-button>
-          <el-button @click="handleConfirm" type="danger"
-            ><Icon icon="ep:check" class="mr-5px" />  {{ t('chooseMaintain.confirm')  }}</el-button
-          >
-<!--          <el-button @click="toggleAll" type="primary"-->
-<!--            ><Icon icon="ep:refresh" class="mr-5px" />{{ t('chooseMaintain.selectAll') }}</el-button-->
-<!--          >-->
-<!--          <el-button @click="invertSelection" type="info"-->
-<!--            ><Icon icon="ep:refresh" class="mr-5px" />{{ t('chooseMaintain.cancelAllSelection') }}</el-button-->
-<!--          >-->
+        :model="queryParams"
+        size="default"
+        label-width="84px"
+        class="query-panel">
+        <div class="query-row">
+          <el-form-item :label="t('monitor.deviceName')" prop="deviceName">
+            <el-input
+              v-model="queryParams.deviceName"
+              :placeholder="t('iotDevice.nameHolder')"
+              clearable
+              class="query-control"
+              @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
+            <el-input
+              v-model="queryParams.deviceCode"
+              :placeholder="t('iotDevice.codeHolder')"
+              clearable
+              class="query-control"
+              @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item :label="t('route.RouteName')" prop="routeName">
+            <el-input
+              v-model="queryParams.routeName"
+              :placeholder="t('route.nHolder')"
+              clearable
+              class="query-control"
+              @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item :label="t('iotDevice.dept')" prop="deptId">
+            <el-tree-select
+              v-model="queryParams.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              clearable
+              placeholder="请选择部门"
+              class="query-control" />
+          </el-form-item>
+        </div>
+        <el-form-item class="query-actions">
+          <el-button type="primary" @click="handleQuery">
+            <Icon icon="ep:search" class="mr-5px" />{{ t('chooseMaintain.search') }}
+          </el-button>
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" class="mr-5px" />{{ t('chooseMaintain.reset') }}
+          </el-button>
         </el-form-item>
       </el-form>
-    </ContentWrap>
-    <!-- 新增:已选物料标签区域 -->
-    <ContentWrap v-if="selectedRows.length > 0"
-                 style="margin: -10px 0px 10px; padding: 10px 15px; background: #f8fafc; border-radius: 4px; border: 1px solid #ebeef5;">
-      <div style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px;">
-        <div style="font-weight: bold; color: #606266; margin-right: 10px;">已选路线设备:</div>
-        <el-tag
-          v-for="item in selectedRows"
-          :key="item.id"
-          @close="removeTag(item)"
-          style="margin-bottom: 5px; position: relative; padding-right: 25px;"
-        >
-          {{ item.deviceCode }}-{{ item.routeName }}
-          <!-- 自定义关闭按钮 -->
-          <span class="close-icon" @click.stop="removeTag(item)">
-            <Icon icon="ep:close" style="font-size: 12px; position: absolute; right: 8px; top: 50%; transform: translateY(-50%);"/>
-          </span>
-        </el-tag>
+
+      <div v-if="selectedRows.length > 0" class="selected-panel">
+        <div class="selected-panel__label">已选 {{ selectedRows.length }} 条路线</div>
+        <div class="selected-panel__tags">
+          <el-tag
+            v-for="item in selectedRows"
+            :key="`${item.id}-${item.deviceCode || ''}`"
+            closable
+            @close="removeTag(item)">
+            {{ item.deviceCode }} - {{ item.routeName }}
+          </el-tag>
+        </div>
+      </div>
+
+      <section class="table-section">
+        <ZmTable
+          :loading="loading"
+          :data="list"
+          :max-height="380"
+          show-border
+          settings-cache-key="pms-inspect-plan-route-select"
+          @row-click="selectRow">
+          <ZmTableColumn :label="t('workOrderMaterial.select')" :width="64" hide-in-column-settings>
+            <template #default="{ row }">
+              <el-checkbox
+                :model-value="isSelected(row)"
+                class="no-label-checkbox"
+                @click.stop="selectRow(row)" />
+            </template>
+          </ZmTableColumn>
+          <ZmTableColumn :label="t('iotDevice.code')" prop="deviceCode" />
+          <ZmTableColumn :label="t('iotDevice.name')" prop="deviceName" />
+          <ZmTableColumn :label="t('route.RouteName')" prop="routeName" />
+          <ZmTableColumn :label="t('iotDevice.dept')" prop="deptName" />
+          <ZmTableColumn
+            :label="t('common.createTime')"
+            prop="createTime"
+            :formatter="dateFormatter" />
+          <ZmTableColumn :label="t('operationFill.operation')" hide-in-column-settings>
+            <template #default="scope">
+              <el-button link type="primary" @click.stop="viewRoute(scope.row.itemJson)">
+                <Icon icon="ep:view" class="mr-5px" />{{ t('operationFill.view') }}
+              </el-button>
+            </template>
+          </ZmTableColumn>
+        </ZmTable>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList" />
+      </section>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <span class="selection-summary">已选择 {{ selectedRows.length }} 条路线</span>
+        <div>
+          <el-button size="default" @click="dialogVisible = false">取消</el-button>
+          <el-button size="default" type="primary" @click="handleConfirm">
+            <Icon icon="ep:check" class="mr-5px" />{{ t('chooseMaintain.confirm') }}
+          </el-button>
+        </div>
       </div>
-    </ContentWrap>
-    <ContentWrap>
-      <el-table
-        ref="tableRef"
-        v-loading="loading"
-        :data="list"
-        :stripe="true"
-        :row-key="rowKey"
-        :show-overflow-tooltip="true"
-        table-layout="auto"
-        @row-click="handleRowClick"
-        class="full-width-table" >
-        <el-table-column width="80" :label="t('workOrderMaterial.select')">
-          <template #default="{ row }">
-            <el-checkbox
-              :model-value="selectedRows.some(item => item.id === row.id&&item.deviceCode===row.deviceCode)"
-              @click.stop="selectRow(row)"
-              class="no-label-radio"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" />
-        <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName" />
-        <el-table-column :label="t('route.RouteName')" align="center" prop="routeName" />
-        <el-table-column :label="t('iotDevice.dept')" align="center" prop="deptName" />
-        <el-table-column
-          :label="t('common.createTime')"
-          align="center"
-          prop="createTime"
-          width="180"
-          :formatter="dateFormatter"
-        />
-        <el-table-column :label="t('operationFill.operation')" align="center">
-          <template #default="scope">
-            <el-button link type="primary" @click="viewRoute(scope.row.itemJson)">
-              {{ t('operationFill.view') }}
-            </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <!-- 分页 -->
-      <Pagination
-        :total="total"
-        v-model:page="queryParams.pageNo"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
-      <RouteInspectItemDrawer
-        ref="showDrawer"
-        :model-value="drawerVisible"
-        @update:model-value="(val) => (drawerVisible = val)"
-      />
-    </ContentWrap>
+    </template>
+
+    <RouteInspectItemDrawer
+      ref="showDrawer"
+      :model-value="drawerVisible"
+      @update:model-value="(val) => (drawerVisible = val)" />
   </Dialog>
 </template>
 
 <script setup lang="ts">
 import { IotDeviceVO } from '@/api/pms/device'
 import { dateFormatter } from '@/utils/formatTime'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { IotInspectRouteApi } from '@/api/pms/inspect/route'
-import { IotInspectItemVO } from '@/api/pms/inspect/item'
-import RouteInspectItemDrawer from "@/views/pms/inspect/plan/RouteInspectItemDrawer.vue";
-import {ref} from "vue";
-import {defaultProps, handleTree} from "@/utils/tree";
-import * as DeptApi from "@/api/system/dept";
+import RouteInspectItemDrawer from '@/views/pms/inspect/plan/RouteInspectItemDrawer.vue'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+
+type InspectRouteDeviceRow = IotDeviceVO & {
+  routeName: string
+  itemJson: string
+  createTime: string
+  deviceId: number
+  item?: string
+  standard?: string
+}
+
+const { ZmTable, ZmTableColumn } = useTableComponents<InspectRouteDeviceRow>()
 const { t } = useI18n() // 国际化
 const emit = defineEmits(['choose']) // 定义 success 事件,用于操作成功后的回调
 const dialogVisible = ref(false) // 弹窗的是否展示
 const loading = ref(true) // 列表的加载中
 const queryFormRef = ref() // 搜索的表单
-const list = ref<IotDeviceVO[]>([]) // 列表的数据
+const list = ref<InspectRouteDeviceRow[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const deptList = ref<Tree[]>([]) // 树形结构
 const drawerVisible = ref<boolean>(false)
@@ -173,38 +180,34 @@ const queryParams = reactive({
   assetClass: undefined,
   deviceName: undefined,
   deviceCode: undefined,
-  routeName: undefined,
+  routeName: undefined
 })
-// 生成行唯一标识
-const rowKey = (row: any) => {
-  return `${row.id}`; // 确保行更新时重新渲染
-};
-// 处理单选逻辑
-const selectRow = (row) => {
-  const index = selectedRows.value.findIndex(item => item.id === row.id&&item.deviceCode===row.deviceCode);
+
+const selectedRows = ref<InspectRouteDeviceRow[]>([])
+
+const isSelected = (row: InspectRouteDeviceRow) =>
+  selectedRows.value.some((item) => item.id === row.id && item.deviceCode === row.deviceCode)
+
+const selectRow = (row: InspectRouteDeviceRow) => {
+  const index = selectedRows.value.findIndex(
+    (item) => item.id === row.id && item.deviceCode === row.deviceCode
+  )
   if (index > -1) {
-    selectedRows.value.splice(index, 1); // 取消选中
-    // row.quantity = undefined;
+    selectedRows.value.splice(index, 1)
   } else {
-    selectedRows.value.push({ ...row }); // 选中
+    selectedRows.value.push({ ...row })
   }
-};
-// 新增:移除标签方法
-const removeTag = (route) => {
-  // 从已选列表中移除
-  const index = selectedRows.value.findIndex(item => item.id === route.id&&item.deviceCode === route.deviceCode);
-  debugger
+}
+
+const removeTag = (route: InspectRouteDeviceRow) => {
+  const index = selectedRows.value.findIndex(
+    (item) => item.id === route.id && item.deviceCode === route.deviceCode
+  )
   if (index !== -1) {
-    selectedRows.value.splice(index, 1);
+    selectedRows.value.splice(index, 1)
   }
+}
 
-  // 如果当前行在当前页,取消其选中状态
-  const rowInTable = list.value.find(item => item.id === route.id&&item.deviceCode === route.deviceCode);
-  if (rowInTable) {
-    const rowIndex = list.value.indexOf(rowInTable);
-    // 这里可以添加逻辑强制更新行状态(如果需要)
-  }
-};
 const handleConfirm = () => {
   if (selectedRows.value.length === 0) {
     ElMessage.warning('请至少选择一个')
@@ -215,60 +218,22 @@ const handleConfirm = () => {
     'choose',
     selectedRows.value.map((row) => ({
       ...row,
-      // 确保返回必要字段
       code: row.item,
       name: row.standard
     }))
   )
   dialogVisible.value = false
-  handleClose()
 }
 
-// 关闭时清空选择
 const handleClose = () => {
-  tableRef.value?.clearSelection()
   selectedRows.value = []
   emit('close')
 }
 
-
 const viewRoute = (itemJson) => {
   drawerVisible.value = true
   showDrawer.value.openDrawer(JSON.parse(itemJson))
 }
-const toggleAll = () => {
-  list.value.forEach((row) => {
-    tableRef.value.toggleRowSelection(row, true)
-  })
-}
-
-const handleSelectionChange = (val) => {
-  selectedRows.value = val
-}
-const invertSelection = () => {
-  selectedRows.value.forEach((row) => {
-    tableRef.value.toggleRowSelection(row, false)
-    selectedRows.value.splice(row)
-  })
-}
-const selectedRows = ref<IotInspectItemVO[]>([]) // 多选数据(存储所有选中行的数组)
-const tableRef = ref()
-
-// 多选 切换行选中状态
-const toggleRow = (row) => {
-  const includes = selectedRows.value.includes(row)
-  if (includes) {
-    selectedRows.value.splice(row)
-  } else {
-    selectedRows.value.push(row) // 选中
-  }
-  tableRef.value.toggleRowSelection(row, !includes)
-}
-
-// 点击整行选中
-const handleRowClick = (row) => {
-  toggleRow(row)
-}
 
 const open = async (classify) => {
   dialogVisible.value = true
@@ -280,7 +245,7 @@ const open = async (classify) => {
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
 }
 defineExpose({ open })
-const { wsCache } = useCache()
+
 const getList = async () => {
   loading.value = true
   list.value = []
@@ -298,21 +263,188 @@ const handleQuery = () => {
   queryParams.pageNo = 1
   getList()
 }
-const choose = (row: DictDataVO) => {
-  emit('choose', row)
-  dialogVisible.value = false
-}
+
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   handleQuery()
 }
 </script>
-<style lang="scss">
-.no-label-radio .el-radio__label {
+<style lang="scss" scoped>
+@media (width <= 1400px) {
+  .inspect-route-select-dialog .query-row {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (width <= 768px) {
+  .inspect-route-select-dialog .dialog-title__subtitle {
+    display: none;
+  }
+
+  .inspect-route-select-dialog .query-panel,
+  .inspect-route-select-dialog .query-row,
+  .inspect-route-select-dialog .query-actions,
+  .inspect-route-select-dialog .query-control {
+    width: 100%;
+  }
+
+  .inspect-route-select-dialog .query-row {
+    grid-template-columns: minmax(0, 1fr);
+  }
+
+  .inspect-route-select-dialog .query-actions :deep(.el-form-item__content) {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    width: 100%;
+  }
+
+  .inspect-route-select-dialog .query-actions :deep(.el-button) {
+    width: 100%;
+  }
+
+  .inspect-route-select-dialog .selected-panel {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+}
+
+.dialog-title {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.dialog-title__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 38px;
+  height: 38px;
+  margin-right: 12px;
+  font-size: 18px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 8px;
+}
+
+.dialog-title__text {
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.dialog-title__subtitle {
+  margin-top: 1px;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.dialog-content {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding-top: 8px;
+}
+
+.query-panel {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: var(--el-fill-color-extra-light);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  gap: 12px 20px;
+}
+
+.query-row {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  min-width: 0;
+  flex: 1 1 880px;
+  gap: 12px 16px;
+}
+
+.query-actions {
+  flex: 0 0 auto;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  gap: 8px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 100%;
+}
+
+.query-panel :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.query-panel :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+.selected-panel {
+  display: flex;
+  align-items: center;
+  padding: 10px 14px;
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-8);
+  border-radius: 8px;
+  gap: 12px;
+}
+
+.selected-panel__label {
+  font-weight: 600;
+  color: var(--el-color-primary);
+  white-space: nowrap;
+  flex: 0 0 auto;
+}
+
+.selected-panel__tags {
+  display: flex;
+  flex-wrap: wrap;
+  min-width: 0;
+  gap: 6px;
+  transform: translateY(1.5px);
+}
+
+.table-section {
+  padding: 12px 12px 0;
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+}
+
+.table-section :deep(.pagination-container) {
+  padding: 12px 8px;
+}
+
+.no-label-checkbox :deep(.el-checkbox__label) {
   display: none;
 }
-.no-label-radio .el-radio__inner {
-  margin-right: 0;
+
+.dialog-footer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.selection-summary {
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
 }
 </style>

+ 388 - 243
src/views/pms/inspect/plan/IotInspectPlan.vue

@@ -1,199 +1,139 @@
 <template>
-  <ContentWrap v-loading="formLoading">
-    <ContentWrap>
-      <el-form
-        ref="formRef"
-        :model="formData"
-        :rules="formRules"
-        style="margin-right: 4em; margin-left: 0.5em; margin-top: 1em"
-        label-width="130px"
-      >
-        <div class="base-expandable-content">
-          <el-row>
-            <el-col :span="12">
-              <el-form-item :label="t('main.planName')" prop="planTitle">
-                <el-input v-model="formData.planTitle" :placeholder="t('main.nameHolder')" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item :label="t('workOrderMaterial.unit')" prop="planUnit">
-                <el-select
-                  v-model="formData.planUnit"
-                  :placeholder="t('route.unitHolder')"
-                  :disabled="formType === 'update'"
-                  clearable
-                >
-                  <el-option
-                    v-for="dict in getStrDictOptions(DICT_TYPE.PMS_INSPECT_UNIT)"
-                    :key="dict.label"
-                    :label="dict.label"
-                    :value="dict.value"
-                  />
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item :label="t('route.cycle')" prop="planCycle">
-                <el-input-number
-                  style="width: 100%"
-                  v-model="formData.planCycle"
-                  :precision="0"
-                  :min="1"
-                  :max="99999"
-                />
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item :label="t('route.beginCreateTime')" prop="beginCreateTime">
-                <el-date-picker
-                  style="width: 150%"
-                  v-model="formData.beginCreateTime"
-                  type="datetime"
-                  value-format="x"
-                />
-              </el-form-item>
-            </el-col>
-            <el-col :span="24">
-              <el-form-item :label="t('form.remark')" prop="remark">
-                <el-input type="textarea" v-model="formData.remark" />
-              </el-form-item>
-            </el-col>
-          </el-row>
+  <div class="plan-form-page" v-loading="formLoading">
+    <header class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon"><Icon icon="ep:calendar" /></div>
+        <div>
+          <h1>{{ pageTitle }}</h1>
+          <p>配置巡检周期、首次执行时间及设备路线顺序</p>
         </div>
-      </el-form>
-    </ContentWrap>
-    <ContentWrap>
-      <!-- 列表 -->
-<!--      <ContentWrap>-->
-        <ContentWrap>
-          <el-form
-            class="-mb-15px"
-            :model="queryParams"
-            ref="queryFormRef"
-            :inline="true"
-            label-width="68px"
-          >
-            <el-form-item>
-              <el-button @click="openForm" type="primary"
-                ><Icon icon="ep:plus" class="mr-5px" />
-                {{ t('route.SelectInspectionDevice') }}</el-button
-              >
-            </el-form-item>
-          </el-form>
-        </ContentWrap>
-      <draggable
-        v-model="list"
-        item-key="id"
-        tag="div"
-        class="sortable-container"
-        handle=".sortable-item"
-        :animation="150"
-        @start="dragStart"
-        @end="dragEnd"
-      >
-        <template #item="{ element, index }">
-          <div class="sortable-item">
-            <!-- 序号显示 -->
-            <div class="order-number">{{ index + 1 }}</div>
-
-            <!-- 拖动手柄 -->
-            <span class="drag-handle">≡</span>
-
-            <!-- 组件内容 -->
-            <div class="component-content">
-                <span style="font-weight: bold">{{ t('iotDevice.code') }}:</span
-                ><span style="font-size: 14px">{{ element.deviceCode }}</span
-            >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-              <span style="font-weight: bold">{{ t('iotDevice.name') }}:</span
-              ><span style="font-size: 14px">{{ element.deviceName }}</span>
-              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-              <span style="font-weight: bold">{{ t('route.RouteName') }}:</span
-              ><span style="font-size: 14px">{{ element.routeName }}</span>
-            </div>
-            <div>
-<!--              <el-button type="warning" @click="deleteDraggable(index)">{{ t('form.delete') }}</el-button>-->
-              <el-button
-                style="vertical-align: middle"
-                link
-                type="warning"
-                @click="viewRoute(element.itemJson)"
-              >
-                <Icon style="vertical-align: middle; color: #c36e5b" icon="ep:view" />
-                {{ t('route.InspectionRoute') }}
-              </el-button>
-              <el-button
-                style="vertical-align: middle"
-                link
-                type="danger"
-                @click="handleDelete(element.id,element.deviceId)"
-              >
-                <Icon style="vertical-align: middle; color: #ea3434" icon="ep:zoom-out" />
-                {{ t('form.delete') }}
-              </el-button>
+      </div>
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </header>
+
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      size="default"
+      label-width="110px"
+      class="plan-form">
+      <section class="form-section">
+        <header class="section-heading"><span></span>计划信息</header>
+        <div class="form-grid">
+          <el-form-item :label="t('main.planName')" prop="planTitle">
+            <el-input v-model="formData.planTitle" :placeholder="t('main.nameHolder')" />
+          </el-form-item>
+          <el-form-item :label="t('workOrderMaterial.unit')" prop="planUnit">
+            <el-select
+              v-model="formData.planUnit"
+              :placeholder="t('route.unitHolder')"
+              :disabled="formType === 'update'"
+              clearable>
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_INSPECT_UNIT)"
+                :key="dict.label"
+                :label="dict.label"
+                :value="dict.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item :label="t('route.cycle')" prop="planCycle">
+            <el-input-number
+              v-model="formData.planCycle"
+              :precision="0"
+              :min="1"
+              :max="99999"
+              controls-position="right" />
+          </el-form-item>
+          <el-form-item :label="t('route.beginCreateTime')" prop="beginCreateTime">
+            <el-date-picker
+              v-model="formData.beginCreateTime"
+              type="datetime"
+              value-format="x"
+              placeholder="请选择首次执行时间" />
+          </el-form-item>
+          <el-form-item :label="t('form.remark')" prop="remark" class="form-grid__wide">
+            <el-input
+              v-model="formData.remark"
+              type="textarea"
+              :rows="3"
+              placeholder="请输入备注" />
+          </el-form-item>
+        </div>
+      </section>
+
+      <section class="form-section route-section">
+        <header class="section-heading section-heading--actions">
+          <div class="section-heading__title"><span></span>巡检设备路线</div>
+          <el-button size="default" type="primary" @click="openForm">
+            <Icon icon="ep:plus" class="mr-5px" />
+            {{ t('route.SelectInspectionDevice') }}
+          </el-button>
+        </header>
+
+        <el-empty v-if="list.length === 0" description="暂未选择巡检设备路线" :image-size="72" />
+        <draggable
+          v-else
+          v-model="list"
+          item-key="id"
+          tag="div"
+          class="sortable-container"
+          handle=".sortable-item"
+          :animation="150"
+          @start="dragStart"
+          @end="dragEnd">
+          <template #item="{ element, index }">
+            <div class="sortable-item">
+              <div class="order-number">{{ index + 1 }}</div>
+              <span class="drag-handle"><Icon icon="ep:rank" /></span>
+              <div class="component-content">
+                <div class="item-field">
+                  <span class="item-field__label">{{ t('iotDevice.code') }}</span>
+                  <span class="item-field__value">{{ element.deviceCode || '-' }}</span>
+                </div>
+                <div class="item-field">
+                  <span class="item-field__label">{{ t('iotDevice.name') }}</span>
+                  <span class="item-field__value">{{ element.deviceName || '-' }}</span>
+                </div>
+                <div class="item-field">
+                  <span class="item-field__label">{{ t('route.RouteName') }}</span>
+                  <span class="item-field__value">{{ element.routeName || '-' }}</span>
+                </div>
+              </div>
+              <div class="item-actions">
+                <el-button link type="primary" @click="viewRoute(element.itemJson)">
+                  <Icon icon="ep:view" class="mr-5px" />{{ t('route.InspectionRoute') }}
+                </el-button>
+                <el-button link type="danger" @click="handleDelete(element.id, element.deviceId)">
+                  <Icon icon="ep:delete" class="mr-5px" />{{ t('form.delete') }}
+                </el-button>
+              </div>
             </div>
-          </div>
-        </template>
-      </draggable>
-<!--        <ContentWrap>-->
-<!--          <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">-->
-<!--            <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" />-->
-<!--            <el-table-column :label="t('iotDevice.name')"  align="center" prop="deviceName" />-->
-<!--            <el-table-column :label="t('route.RouteName')" align="center" prop="routeName" />-->
-<!--            <el-table-column :label="t('operationFill.operation')" align="center" min-width="120px">-->
-<!--              <template #default="scope">-->
-<!--                <div-->
-<!--                  style="display: flex; justify-content: center; align-items: center; width: 100%"-->
-<!--                >-->
-<!--                  <div style="margin-right: 10px">-->
-<!--                    <el-button-->
-<!--                      style="vertical-align: middle"-->
-<!--                      link-->
-<!--                      type="warning"-->
-<!--                      @click="viewRoute(scope.row.itemJson)"-->
-<!--                    >-->
-<!--                      <Icon style="vertical-align: middle; color: #c36e5b" icon="ep:view" />-->
-<!--                      {{ t('route.InspectionRoute') }}-->
-<!--                    </el-button>-->
-<!--                  </div>-->
-<!--                  <div>-->
-<!--                    <el-button-->
-<!--                      style="vertical-align: middle"-->
-<!--                      link-->
-<!--                      type="danger"-->
-<!--                      @click="handleDelete(scope.row.id,scope.row.deviceId)"-->
-<!--                    >-->
-<!--                      <Icon style="vertical-align: middle; color: #ea3434" icon="ep:zoom-out" />-->
-<!--                      {{ t('form.delete') }}-->
-<!--                    </el-button>-->
-<!--                  </div>-->
-<!--                </div>-->
-<!--              </template>-->
-<!--            </el-table-column>-->
-<!--          </el-table>-->
-<!--        </ContentWrap>-->
-<!--      </ContentWrap>-->
-    </ContentWrap>
-    <ContentWrap>
-      <el-form>
-        <el-form-item style="float: right">
-          <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('iotMaintain.save') }}</el-button>
-          <el-button @click="close">{{ t('iotMaintain.cancel') }}</el-button>
-        </el-form-item>
-      </el-form>
-    </ContentWrap>
+          </template>
+        </draggable>
+      </section>
+    </el-form>
+
+    <footer class="action-bar">
+      <el-button size="default" @click="close">{{ t('iotMaintain.cancel') }}</el-button>
+      <el-button size="default" type="primary" :loading="formLoading" @click="submitForm">
+        <Icon icon="ep:check" class="mr-5px" />{{ t('iotMaintain.save') }}
+      </el-button>
+    </footer>
+
     <InspectItemList
       ref="inspectItemFormRef"
       :classify="formData.deviceClassify"
       :deviceId="formData.deviceId"
-      @choose="inspectItemChoose"
-    />
+      @choose="inspectItemChoose" />
     <InspectRouteList ref="deviceFormRef" @choose="deviceChoose" />
     <RouteInspectItemDrawer
       ref="showDrawer"
       :model-value="drawerVisible"
-      @update:model-value="(val) => (drawerVisible = val)"
-    />
-  </ContentWrap>
+      @update:model-value="(val) => (drawerVisible = val)" />
+  </div>
 </template>
 <script setup lang="ts">
 import * as UserApi from '@/api/system/user'
@@ -219,6 +159,7 @@ const { currentRoute, push } = useRouter()
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const pageTitle = computed(() => (formType.value === 'update' ? '编辑巡检计划' : '新增巡检计划'))
 const drawerVisible = ref<boolean>(false)
 const showDrawer = ref()
 const list = ref<IotInspectRouteVO[]>([]) // 列表的数据
@@ -252,7 +193,7 @@ const items = ref([])
 const deviceChoose = (rows) => {
   debugger
   rows.forEach((it) => {
-    const ifExist = list.value.find((item) => item.id === it.id&&item.deviceId===it.deviceId)
+    const ifExist = list.value.find((item) => item.id === it.id && item.deviceId === it.deviceId)
     if (!ifExist) {
       list.value.push(it)
       console.log(JSON.stringify(it))
@@ -317,9 +258,10 @@ const submitForm = async () => {
       //校验是否是已有计划
       const newitems = itemsWithIndex
       formData.value.deviceIds = JSON.stringify(newitems.value)
-      if (!formData.value.deptId) {formData.value.deptId = user.user.deptId}
-      await IotInspectPlanApi.createIotInspectPlan(data).then((res) => {
-      })
+      if (!formData.value.deptId) {
+        formData.value.deptId = user.user.deptId
+      }
+      await IotInspectPlanApi.createIotInspectPlan(data).then((res) => {})
       message.success(t('common.createSuccess'))
       close()
     } else {
@@ -358,7 +300,7 @@ onMounted(async () => {
 const handleDelete = async (id: number, deviceId: number) => {
   try {
     debugger
-    const index = list.value.findIndex((item) => item.id === id&&item.deviceId===deviceId)
+    const index = list.value.findIndex((item) => item.id === id && item.deviceId === deviceId)
     if (index !== -1) {
       // 通过 splice 删除元素
       list.value.splice(index, 1)
@@ -367,100 +309,303 @@ const handleDelete = async (id: number, deviceId: number) => {
 }
 </script>
 <style scoped>
-.base-expandable-content {
-  overflow: hidden; /* 隐藏溢出的内容 */
-  transition: max-height 0.3s ease; /* 平滑过渡效果 */
+@media (width <= 1400px) {
+  .plan-form-page .form-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .plan-form-page .component-content {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
 }
-/* 横向布局容器 */
 
+@media (width <= 768px) {
+  .plan-form-page .page-header {
+    align-items: flex-start;
+  }
 
-/* 拖拽手柄样式 */
-.drag-handle {
-  opacity: 0.5;
-  cursor: move;
-  transition: opacity 0.3s;
+  .plan-form-page .page-header p {
+    display: none;
+  }
+
+  .plan-form-page .form-grid {
+    grid-template-columns: minmax(0, 1fr);
+    padding: 14px 14px 0;
+  }
+
+  .plan-form-page .form-grid__wide {
+    grid-column: auto;
+  }
+
+  .plan-form-page .section-heading--actions {
+    height: auto;
+    min-height: 44px;
+    padding-top: 6px;
+    padding-bottom: 6px;
+  }
+
+  .plan-form-page .sortable-item {
+    align-items: flex-start;
+  }
+
+  .plan-form-page .drag-handle {
+    padding-right: 8px;
+    padding-left: 8px;
+  }
+
+  .plan-form-page .component-content {
+    grid-template-columns: minmax(0, 1fr);
+    gap: 6px;
+  }
+
+  .plan-form-page .item-actions {
+    flex-direction: column;
+    align-items: flex-end;
+  }
+
+  .plan-form-page .item-actions :deep(.el-button + .el-button) {
+    margin-left: 0;
+  }
+}
+
+.plan-form-page {
+  display: flex;
+  min-height: calc(
+    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+  );
+  flex-direction: column;
+  gap: 12px;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 66px;
+  padding: 12px 16px;
+  background: var(--el-bg-color);
+  border-radius: 8px;
+  box-shadow: var(--el-box-shadow-light);
+}
+
+.page-header__main {
+  display: flex;
+  align-items: center;
+  min-width: 0;
 }
-.drag-handle:hover {
-  opacity: 1;
+
+.page-header__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 38px;
+  height: 38px;
+  margin-right: 12px;
+  font-size: 18px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 8px;
 }
 
-/* 拖拽时的悬停效果 */
-.horizontal-item:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+.page-header h1 {
+  margin: 0;
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
 }
 
-/* 滚动条样式 */
-.horizontal-list::-webkit-scrollbar {
-  height: 8px;
+.page-header p {
+  margin: 1px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
 }
-.horizontal-list::-webkit-scrollbar-thumb {
-  background: #888;
-  border-radius: 4px;
+
+.plan-form {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: 12px;
 }
 
-.sortable-container {
-  cursor: move;
+.form-section {
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: var(--el-box-shadow-lighter);
+}
+
+.section-heading {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  padding: 0 16px;
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.section-heading > span,
+.section-heading__title > span {
+  width: 4px;
+  height: 17px;
+  margin-right: 9px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.section-heading--actions {
+  justify-content: space-between;
+}
+
+.section-heading__title {
   display: flex;
+  align-items: center;
+}
+
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  column-gap: 24px;
+  padding: 18px 18px 2px;
+}
+
+.form-grid__wide {
+  grid-column: 1 / -1;
+}
+
+.plan-form :deep(.el-form-item) {
+  margin-bottom: 16px;
+}
+
+.plan-form :deep(.el-input),
+.plan-form :deep(.el-input-number),
+.plan-form :deep(.el-select),
+.plan-form :deep(.el-date-editor) {
+  width: 100%;
+}
+
+.route-section {
+  display: flex;
+  min-height: 280px;
+  flex: 1;
   flex-direction: column;
-  gap: 9px;
-  //max-height: 80vh;
+}
+
+.route-section :deep(.el-empty) {
+  flex: 1;
+}
+
+.sortable-container {
+  display: flex;
+  min-height: 0;
+  padding: 14px;
   overflow-y: auto;
-  padding: 7px;
+  cursor: default;
+  flex: 1;
+  flex-direction: column;
+  gap: 10px;
 }
 
 .sortable-item {
   display: flex;
+  min-height: 60px;
+  padding: 10px 12px;
+  cursor: grab;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
   align-items: center;
-  padding: 8px;
-  background: #fff;
-  border: 1px solid #ebeef5;
-  border-radius: 8px;
   transition:
-    transform 0.3s,
-    box-shadow 0.3s;
+    border-color 0.2s ease,
+    box-shadow 0.2s ease;
   user-select: none;
-  height: 50%;
 }
 
 .sortable-item:hover {
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  border-color: var(--el-color-primary-light-7);
+  box-shadow: 0 3px 10px rgb(0 0 0 / 6%);
+}
+
+.sortable-item:active {
+  cursor: grabbing;
 }
 
 .order-number {
-  width: 24px;
-  height: 24px;
   display: flex;
   align-items: center;
   justify-content: center;
-  background: #409eff;
-  color: white;
+  width: 26px;
+  height: 26px;
+  margin-right: 2px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #fff;
+  background: var(--el-color-primary);
   border-radius: 50%;
-  margin-right: 7px;
-  font-weight: bold;
+  flex: 0 0 auto;
 }
 
 .drag-handle {
-  padding: 0 12px;
-  opacity: 0.4;
-  transition: opacity 0.3s;
-}
-
-.drag-handle:hover {
-  opacity: 1;
+  display: flex;
+  padding: 8px 12px;
+  font-size: 18px;
+  color: var(--el-text-color-placeholder);
+  cursor: grab;
 }
 
 .component-content {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  min-width: 0;
   flex: 1;
+  gap: 16px;
+}
+
+.item-field {
+  display: flex;
   min-width: 0;
+  align-items: flex-start;
 }
 
-/* 优化滚动条 */
-.sortable-container::-webkit-scrollbar {
-  width: 8px;
+.item-field__label {
+  margin-right: 8px;
+  font-weight: 600;
+  color: var(--el-text-color-regular);
+  white-space: nowrap;
 }
-.sortable-container::-webkit-scrollbar-thumb {
-  background: #c0c4cc;
-  border-radius: 4px;
+
+.item-field__value {
+  min-width: 0;
+  color: var(--el-text-color-secondary);
+  overflow-wrap: anywhere;
+}
+
+.item-actions {
+  display: flex;
+  align-items: center;
+  margin-left: 16px;
+  flex: 0 0 auto;
+}
+
+.action-bar {
+  position: sticky;
+  bottom: 0;
+  z-index: 5;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  min-height: 56px;
+  padding: 10px 16px;
+  margin-top: auto;
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-lighter);
+  box-shadow: 0 -4px 12px rgb(0 0 0 / 5%);
 }
 </style>

+ 276 - 226
src/views/pms/inspect/plan/index.vue

@@ -1,181 +1,164 @@
 <template>
-  <el-row :gutter="20">
-    <DeptTree @node-click="handleDeptNodeClick" v-model:collapsed="isLeftContentCollapsed" />
-
-    <el-col :xs="24" :span="isLeftContentCollapsed ? 24 : 20">
-      <div
-        style="border: none; background: #fff; display: flex; align-items: center"
-        class="py-2 rounded-sm px-2"
-      >
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="68px"
-        >
-          <el-form-item :label="t('main.planName')" prop="planTitle" style="margin-left: 25px">
-            <el-input
-              v-model="queryParams.planTitle"
-              :placeholder="t('main.nameHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('main.planCode')" prop="planCode">
-            <el-input
-              v-model="queryParams.planCode"
-              :placeholder="t('main.codeHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('common.createTime')" prop="createTime" label-width="100px">
-            <el-date-picker
-              v-model="queryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              :start-placeholder="t('info.start')"
-              :end-placeholder="t('info.end')"
-              :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" /> {{ t('operationFill.search') }}</el-button
-            >
-            <el-button @click="resetQuery"
-              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('operationFill.reset') }}</el-button
-            >
-            <el-button
-              type="primary"
-              plain
-              @click="openForm('create')"
-              v-hasPermi="['rq:iot-inspect-plan:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
-            </el-button>
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-inspect-plan:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="inspect-plan-page grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
+    <DeptTreeSelect
+      :top-id="rootDeptId"
+      :deptId="deptId"
+      v-model="queryParams.deptId"
+      :init-select="false"
+      :show-title="false"
+      request-api="getSimpleDeptList"
+      class="inspect-plan-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="88px"
+      class="inspect-plan-query bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 py-3 min-w-0">
+      <div class="query-row">
+        <el-form-item :label="t('main.planName')" prop="planTitle">
+          <el-input
+            v-model="queryParams.planTitle"
+            :placeholder="t('main.nameHolder')"
+            clearable
+            class="query-control"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item :label="t('main.planCode')" prop="planCode">
+          <el-input
+            v-model="queryParams.planCode"
+            :placeholder="t('main.codeHolder')"
+            clearable
+            class="query-control"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item :label="t('common.createTime')" prop="createTime" label-width="100px">
+          <el-date-picker
+            v-model="queryParams.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            :start-placeholder="t('info.start')"
+            :end-placeholder="t('info.end')"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="query-control query-control--date" />
+        </el-form-item>
+      </div>
+
+      <el-form-item class="query-actions">
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />{{ t('operationFill.search') }}
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />{{ t('operationFill.reset') }}
+        </el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-inspect-plan:create']">
+          <Icon icon="ep:plus" class="mr-5px" />{{ t('action.add') }}
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['rq:iot-inspect-plan:export']">
+          <Icon icon="ep:download" class="mr-5px" />导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div
+      class="table-panel bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 min-w-0 min-h-0">
+      <div class="flex-1 relative min-h-0">
+        <el-auto-resizer class="absolute">
+          <template #default="{ width, height }">
+            <ZmTable
+              :loading="loading"
+              :data="list"
+              :width="width"
+              :height="height"
+              :max-height="height"
+              show-border
+              settings-cache-key="pms-inspect-plan-list">
+              <ZmTableColumn
+                type="index"
+                :label="t('iotDevice.serial')"
+                :width="70"
+                fixed="left"
+                hide-in-column-settings />
+              <ZmTableColumn :label="t('route.planTitle')" prop="planTitle" fixed="left" />
+              <ZmTableColumn :label="t('route.planNumber')" prop="planCode" />
+              <ZmTableColumn :label="t('route.cycle')" prop="planCycle" />
+              <ZmTableColumn :label="t('workOrderMaterial.unit')" prop="planUnit">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.PMS_INSPECT_UNIT" :value="scope.row.planUnit" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('bomList.status')" prop="status">
+                <template #default="scope">
+                  <el-switch
+                    v-model="scope.row.status"
+                    :active-value="0"
+                    :inactive-value="1"
+                    @change="handleStatusChange(scope.row)" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('route.beginCreateTime')"
+                prop="beginCreateTime"
+                :formatter="dateFormatter" />
+              <ZmTableColumn
+                :label="t('route.lastCreateTime')"
+                prop="lastCreateTime"
+                :formatter="dateFormatter" />
+              <ZmTableColumn :label="t('iotDevice.dept')" prop="deptName" />
+              <ZmTableColumn :label="t('inspect.createName')" prop="createName" />
+              <ZmTableColumn
+                :label="t('operationFill.operation')"
+                :width="120"
+                fixed="right"
+                action>
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="openForm('update', scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-plan:update']">
+                    {{ t('fault.edit') }}
+                  </el-button>
+                  <el-button
+                    link
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-plan:delete']">
+                    {{ t('fault.del') }}
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </el-auto-resizer>
       </div>
 
-      <!-- 列表 -->
-      <ContentWrap style="border: none; margin-top: 20px">
-        <zm-table :loading="loading" :data="list" height="63vh">
-          <zm-table-column :label="t('iotDevice.serial')" width="70" align="center">
-            <template #default="scope">
-              {{ scope.$index + 1 }}
-            </template>
-          </zm-table-column>
-          <zm-table-column :label="t('route.planTitle')" align="center" prop="planTitle" />
-          <zm-table-column
-            :label="t('route.planNumber')"
-            align="center"
-            prop="planCode"
-            min-width="200"
-          />
-          <zm-table-column
-            :label="t('route.cycle')"
-            align="center"
-            prop="planCycle"
-            min-width="80"
-          />
-          <zm-table-column
-            :label="t('workOrderMaterial.unit')"
-            align="center"
-            prop="planUnit"
-            min-width="80"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.PMS_INSPECT_UNIT" :value="scope.row.planUnit" />
-            </template>
-          </zm-table-column>
-          <zm-table-column :label="t('bomList.status')" key="status" min-width="80">
-            <template #default="scope">
-              <el-switch
-                v-model="scope.row.status"
-                :active-value="0"
-                :inactive-value="1"
-                @change="handleStatusChange(scope.row)"
-              />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('route.beginCreateTime')"
-            align="center"
-            prop="beginCreateTime"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column
-            :label="t('route.lastCreateTime')"
-            align="center"
-            prop="lastCreateTime"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column
-            :label="t('iotDevice.dept')"
-            align="center"
-            prop="deptName"
-            min-width="130"
-          />
-          <zm-table-column
-            :label="t('inspect.createName')"
-            align="center"
-            prop="createName"
-            min-width="130"
-          />
-          <zm-table-column
-            :label="t('operationFill.operation')"
-            align="center"
-            min-width="120"
-            fixed="right"
-            action
-          >
-            <template #default="scope">
-              <el-button
-                link
-                type="primary"
-                @click="openForm('update', scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-plan:update']"
-              >
-                {{ t('fault.edit') }}
-              </el-button>
-              <el-button
-                link
-                type="danger"
-                @click="handleDelete(scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-plan:delete']"
-              >
-                {{ t('fault.del') }}
-              </el-button>
-            </template>
-          </zm-table-column>
-        </zm-table>
-        <!-- 分页 -->
-        <Pagination
+      <div class="h-8 mt-2 flex items-center justify-end">
+        <el-pagination
+          v-show="total > 0"
+          size="default"
+          :current-page="queryParams.pageNo"
+          :page-size="queryParams.pageSize"
+          :background="true"
+          :page-sizes="[10, 20, 30, 50, 100]"
           :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </ContentWrap>
-    </el-col>
-  </el-row>
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange" />
+      </div>
+    </div>
+  </div>
   <!-- 表单弹窗:添加/修改 -->
   <IotInspectPlanForm ref="formRef" @success="getList" />
 </template>
@@ -188,18 +171,20 @@ import { IotInspectPlanApi, IotInspectPlanVO } from '@/api/pms/inspect/plan'
 import IotInspectPlanForm from './IotInspectPlanForm.vue'
 import { CommonStatusEnum } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/dict'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
 import { useRouter } from 'vue-router'
 import { useI18n } from 'vue-i18n'
+import { useUserStore } from '@/store/modules/user'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInspectPlanVO>()
 
 defineOptions({ name: 'IotInspectPlan' })
 const message = useMessage() // 消息弹窗
 
 const { push } = useRouter()
 const { t } = useI18n()
+const rootDeptId = 156
+const deptId = useUserStore().getUser.deptId || rootDeptId
 
 const loading = ref(true)
 const list = ref<IotInspectPlanVO[]>([])
@@ -220,13 +205,12 @@ const queryParams = reactive({
 const queryFormRef = ref()
 const exportLoading = ref(false)
 
-const isLeftContentCollapsed = ref(false)
-const hoverText = ref('')
 const selectedDept = ref<{ id: number; name: string }>()
 
-const handleDeptNodeClick = async (row) => {
+const handleDeptNodeClick = async (row: Tree) => {
   selectedDept.value = { id: row.id, name: row.name }
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 
@@ -265,11 +249,21 @@ const handleQuery = () => {
 }
 
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   selectedDept.value = undefined
   handleQuery()
 }
 
+const handleSizeChange = (pageSize: number) => {
+  queryParams.pageSize = pageSize
+  handleQuery()
+}
+
+const handleCurrentChange = (pageNo: number) => {
+  queryParams.pageNo = pageNo
+  getList()
+}
+
 const openForm = (type: string, id?: number) => {
   if (typeof id === 'number') {
     push({ name: 'InspectPlanEdit', params: { id } })
@@ -305,70 +299,126 @@ const handleExport = async () => {
   }
 }
 
-const toggleLeftContent = () => {
-  isLeftContentCollapsed.value = !isLeftContentCollapsed.value
-}
-
-const handleMouseOver = () => {
-  hoverText.value = isLeftContentCollapsed.value ? '展开' : '收起'
-}
-
-const handleMouseOut = () => {
-  hoverText.value = ''
-}
-
 onMounted(() => {
   getList()
 })
 </script>
 
 <style scoped>
-.leftcontent {
-  transition: width 0.3s ease;
-  position: relative;
+.inspect-plan-query {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px 24px;
 }
 
-.leftcontent.collapsed {
-  width: 0;
-  overflow: hidden;
+.query-row {
+  display: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  min-width: 0;
+  gap: 12px 24px;
 }
 
-.rightcontent {
-  position: relative;
+.query-actions {
+  flex: 0 0 auto;
 }
 
-.toggle-button {
-  position: absolute;
-  top: 44%;
-  transform: translate(-65%, -50%);
-  width: 12px;
-  height: 40px;
-  background-color: #f0f0f0;
-  cursor: pointer;
+.query-actions :deep(.el-form-item__content) {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 1;
-  clip-path: polygon(0 0, 100% 18%, 100% 85%, 0 100%);
-  border-radius: 8px;
+  flex-wrap: wrap;
+  gap: 8px 10px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 190px;
 }
 
-/* 添加鼠标悬停样式 */
-.toggle-button:hover {
-  background-color: #afafaf;
+.query-control--date {
+  width: 230px;
 }
 
-.triangle {
-  width: 0;
-  height: 0;
-  border-top: 4px solid transparent;
-  border-bottom: 4px solid transparent;
-  transition: transform 0.4s ease;
-  border-right: 5px solid gray; /* 修改为右边框显示颜色 */
-  border-left: none; /* 移除左边框 */
+:deep(.el-form-item) {
+  margin-bottom: 0;
 }
 
-.triangle.rotated {
-  transform: rotate(180deg);
+:deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+@media (width >= 1800px) {
+  .inspect-plan-query,
+  .query-row {
+    flex-wrap: nowrap;
+  }
+}
+
+@media (width <= 1500px) {
+  .inspect-plan-query,
+  .query-row {
+    gap: 12px 18px;
+  }
+
+  .query-control {
+    width: 175px;
+  }
+
+  .query-control--date {
+    width: 220px;
+  }
+}
+
+@media (width <= 1200px) {
+  .inspect-plan-page {
+    grid-template-columns: minmax(0, 1fr);
+    grid-template-rows: auto auto minmax(480px, 1fr);
+    height: auto;
+    min-height: calc(
+      100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+    );
+  }
+
+  :deep(.inspect-plan-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+
+  .query-actions {
+    width: 100%;
+  }
+}
+
+@media (width <= 768px) {
+  .inspect-plan-query,
+  .table-panel {
+    padding: 12px;
+  }
+
+  .query-row,
+  .query-row :deep(.el-form-item),
+  .query-actions,
+  .query-control,
+  .query-control--date {
+    width: 100%;
+  }
+
+  .query-actions :deep(.el-form-item__content) {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    gap: 8px;
+    width: 100%;
+  }
+
+  .query-actions :deep(.el-button) {
+    width: 100%;
+  }
 }
 </style>