Przeglądaj źródła

Merge branch 'feature/inspection'

Zimo 1 tydzień temu
rodzic
commit
5b287f7566

+ 206 - 69
src/views/pms/inspect/item/IotInspectItemForm.vue

@@ -1,78 +1,98 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible" style="width: 50vw;min-height: 50vh" >
+  <Dialog
+    v-model="dialogVisible"
+    :title="dialogTitle"
+    width="min(920px, 92vw)"
+    class="inspect-item-form-dialog">
+    <template #title>
+      <div class="dialog-title mb-4">
+        <div class="dialog-title__icon"><Icon icon="ep:list" /></div>
+        <div class="dialog-title__content">
+          <div class="dialog-title__text">{{ dialogTitle }}</div>
+          <div class="dialog-title__subtitle">维护巡检设备、巡检项目与执行标准</div>
+        </div>
+      </div>
+    </template>
+
     <el-form
       ref="formRef"
+      v-loading="formLoading"
       :model="formData"
       :rules="formRules"
+      size="default"
       label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-row>
-        <el-col :span="12">
-        <el-form-item :label="t('monitor.category')"  prop="deviceClassify">
-          <el-tree-select
-            filterable
-            v-model="formData.deviceClassify"
-            :data="productClassifyList"
-            :props="defaultProps"
-            check-strictly
-            node-key="id"
-            :placeholder="t('deviceForm.categoryHolder')"
-            clearable
-          />
-        </el-form-item>
-          </el-col>
-        <el-col :span="12">
+      class="inspect-item-form">
+      <section class="form-section">
+        <header class="section-heading"><span></span>基础信息</header>
+        <div class="form-grid">
+          <el-form-item :label="t('monitor.category')" prop="deviceClassify">
+            <el-tree-select
+              v-model="formData.deviceClassify"
+              :data="productClassifyList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              :placeholder="t('deviceForm.categoryHolder')"
+              filterable
+              clearable />
+          </el-form-item>
           <el-form-item :label="t('iotDevice.brand')" prop="brandName">
             <el-select
-              clearable
               v-model="formData.brandName"
-              @clear="brandClear"
               :placeholder="t('iotDevice.brandHolder')"
-              @click="openBrand"
-            />
+              clearable
+              @clear="brandClear"
+              @click="openBrand" />
+          </el-form-item>
+          <el-form-item :label="t('monitor.deviceName')" prop="deviceId">
+            <el-select
+              v-model="formData.deviceId"
+              :model-value="deviceLabel"
+              :placeholder="t('monitor.nameHolder')"
+              @click="openForm" />
           </el-form-item>
-        </el-col>
-        <el-col :span="12">
-        <el-form-item :label="t('monitor.deviceName')" prop="deviceId">
-          <el-select
-            v-model="formData.deviceId"
-            :model-value="deviceLabel"
-            :placeholder="t('monitor.nameHolder')"
-            @click="openForm"
-          />
-        </el-form-item>
-          </el-col>
-        <el-col :span="12">
-        <el-form-item :label="t('inspect.InspectionItems')" prop="item">
-          <el-input v-model="formData.item" :placeholder="t('inspect.itemHolder')" />
-        </el-form-item>
-          </el-col>
-        <el-col :span="12">
-        <el-form-item :label="t('common.sort')" prop="sort">
-          <el-input v-model="formData.sort" type="number" :placeholder="t('common.sortHolder')" />
-        </el-form-item>
-          </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('fileInfo.appendix')" prop="urls">
-            <UploadFile v-model="formData.urls" :limit="1" :is-show-tip="false" class="min-w-80px" />
+          <el-form-item :label="t('common.sort')" prop="sort">
+            <el-input v-model="formData.sort" type="number" :placeholder="t('common.sortHolder')" />
+          </el-form-item>
+          <el-form-item :label="t('fileInfo.appendix')" prop="urls" class="form-grid__full">
+            <UploadFile
+              v-model="formData.urls"
+              :limit="1"
+              :is-show-tip="false"
+              class="min-w-80px" />
+          </el-form-item>
+        </div>
+      </section>
+
+      <section class="form-section">
+        <header class="section-heading"><span></span>巡检内容</header>
+        <div class="form-content">
+          <el-form-item :label="t('inspect.InspectionItems')" prop="item">
+            <el-input v-model="formData.item" :placeholder="t('inspect.itemHolder')" />
           </el-form-item>
-        </el-col>
-        <el-col :span="24">
           <el-form-item :label="t('inspect.InspectionStandards')" prop="standard">
-            <el-input v-model="formData.standard" type="textarea" :placeholder="t('inspect.stanHolder')" />
+            <el-input
+              v-model="formData.standard"
+              type="textarea"
+              :rows="3"
+              :placeholder="t('inspect.stanHolder')" />
           </el-form-item>
-        </el-col>
-        <el-col :span="24">
-        <el-form-item :label="t('form.remark')" prop="remark">
-          <el-input v-model="formData.remark" type="textarea" :placeholder="t('deviceForm.remarkHolder')" />
-        </el-form-item>
-          </el-col>
-      </el-row>
+          <el-form-item :label="t('form.remark')" prop="remark">
+            <el-input
+              v-model="formData.remark"
+              type="textarea"
+              :rows="3"
+              :placeholder="t('deviceForm.remarkHolder')" />
+          </el-form-item>
+        </div>
+      </section>
     </el-form>
+
     <template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('faultForm.ok') }}</el-button>
       <el-button @click="dialogVisible = false">{{ t('faultForm.cancel') }}</el-button>
+      <el-button type="primary" :loading="formLoading" @click="submitForm">
+        {{ t('faultForm.ok') }}
+      </el-button>
     </template>
   </Dialog>
   <DeviceList ref="deviceFormRef" @choose="deviceChoose" />
@@ -80,11 +100,11 @@
 </template>
 <script setup lang="ts">
 import { IotInspectItemApi, IotInspectItemVO } from '@/api/pms/inspect/item'
-import {defaultProps, handleTree} from "@/utils/tree";
-import * as ProductClassifyApi from "@/api/pms/productclassify";
-import DeviceList from "@/views/pms/failure/DeviceList.vue";
-import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
-import BrandList from "@/views/pms/device/BrandList.vue";
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as ProductClassifyApi from '@/api/pms/productclassify'
+import DeviceList from '@/views/pms/failure/DeviceList.vue'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import BrandList from '@/views/pms/device/BrandList.vue'
 /** 巡检项 表单 */
 defineOptions({ name: 'IotInspectItemForm' })
 
@@ -111,13 +131,13 @@ const formData = ref({
   deviceCode: undefined,
   urls: undefined,
   brand: undefined,
-  brandName: undefined,
+  brandName: undefined
 })
 const formRules = reactive({
   deviceClassify: [{ required: true, message: '设备类别不能为空', trigger: 'blur' }],
   item: [{ required: true, message: '巡检项不能为空', trigger: 'blur' }],
   standard: [{ required: true, message: '巡检标准不能为空', trigger: 'blur' }],
-  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
 
@@ -125,7 +145,7 @@ const deviceChoose = (row) => {
   formData.value.deviceId = row.id
   formData.value.deviceName = row.deviceName
   formData.value.deviceCode = row.deviceCode
-  formData.value.deptId = row.deptId;
+  formData.value.deptId = row.deptId
   deviceLabel.value = row.deviceName
 }
 
@@ -138,7 +158,7 @@ const brandChoose = (row) => {
   // brandLabel.value = row.value
   formData.value.brandName = row.label
 }
-const brandClear = () =>{
+const brandClear = () => {
   formData.value.brand = undefined
   formData.value.brandName = undefined
 }
@@ -157,7 +177,7 @@ const open = async (type: string, id?: number) => {
     } finally {
       formLoading.value = false
     }
-  }else {
+  } else {
     formData.value.sort = 0
     deviceLabel.value = ''
   }
@@ -208,8 +228,125 @@ const resetForm = () => {
     standard: undefined,
     sort: undefined,
     remark: undefined,
-    deptId: undefined,
+    deptId: undefined
   }
   formRef.value?.resetFields()
 }
 </script>
+
+<style scoped>
+.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__content {
+  min-width: 0;
+}
+
+.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);
+}
+
+.inspect-item-form {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding-top: 8px;
+}
+
+.form-section {
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+}
+
+.section-heading {
+  display: flex;
+  align-items: center;
+  height: 42px;
+  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 {
+  width: 4px;
+  height: 17px;
+  margin-right: 9px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  column-gap: 24px;
+  padding: 18px 18px 2px;
+}
+
+.form-grid__full {
+  grid-column: 1 / -1;
+}
+
+.form-content {
+  padding: 18px 18px 2px;
+}
+
+.inspect-item-form :deep(.el-form-item) {
+  margin-bottom: 16px;
+}
+
+.inspect-item-form :deep(.el-select),
+.inspect-item-form :deep(.el-tree-select),
+.inspect-item-form :deep(.el-input) {
+  width: 100%;
+}
+
+@media (width <= 768px) {
+  .dialog-title__subtitle {
+    display: none;
+  }
+
+  .form-grid {
+    grid-template-columns: minmax(0, 1fr);
+    padding: 14px 14px 0;
+  }
+
+  .form-grid__full {
+    grid-column: auto;
+  }
+
+  .form-content {
+    padding: 14px 14px 0;
+  }
+}
+</style>

+ 281 - 148
src/views/pms/inspect/item/index.vue

@@ -1,150 +1,152 @@
 <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('deviceForm.category')" prop="deviceClassify" style="width: 15vw">
-            <el-tree-select
-              v-model="queryParams.deviceClassify"
-              :data="productClassifyList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              :placeholder="t('deviceForm.categoryHolder')"
-              filterable
-            />
-          </el-form-item>
-          <el-form-item :label="t('inspect.InspectionItems')" prop="item" style="margin-left: 30px">
-            <el-input
-              v-model="queryParams.item"
-              :placeholder="t('inspect.itemHolder')"
-              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('operationFill.start')"
-              :end-placeholder="t('operationFill.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-item:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
-            </el-button>
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-inspect-item:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="inspect-item-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-item-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="88px"
+      class="inspect-item-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('deviceForm.category')" prop="deviceClassify">
+          <el-tree-select
+            v-model="queryParams.deviceClassify"
+            :data="productClassifyList"
+            :props="defaultProps"
+            check-strictly
+            node-key="id"
+            :placeholder="t('deviceForm.categoryHolder')"
+            filterable
+            clearable
+            class="query-control" />
+        </el-form-item>
+        <el-form-item :label="t('inspect.InspectionItems')" prop="item">
+          <el-input
+            v-model="queryParams.item"
+            :placeholder="t('inspect.itemHolder')"
+            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('operationFill.start')"
+            :end-placeholder="t('operationFill.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-item:create']">
+          <Icon icon="ep:plus" class="mr-5px" />{{ t('operationFill.add') }}
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['rq:iot-inspect-item: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-item-list">
+              <ZmTableColumn
+                type="index"
+                :label="t('iotDevice.serial')"
+                :width="70"
+                fixed="left"
+                hide-in-column-settings />
+              <ZmTableColumn :label="t('monitor.category')" prop="deviceClassifyName" />
+              <ZmTableColumn :label="t('iotDevice.brand')" prop="brandName" />
+              <ZmTableColumn :label="t('iotDevice.code')" prop="deviceCode" />
+              <ZmTableColumn :label="t('monitor.deviceName')" prop="deviceName" />
+              <ZmTableColumn :label="t('inspect.InspectionItems')" prop="item" />
+              <ZmTableColumn :label="t('inspect.InspectionStandards')" prop="standard" />
+              <ZmTableColumn :label="t('inspect.createName')" prop="createName" />
+              <ZmTableColumn
+                :label="t('common.createTime')"
+                prop="createTime"
+                :formatter="dateFormatter" />
+              <ZmTableColumn
+                :label="t('operationFill.operation')"
+                :width="120"
+                action
+                fixed="right">
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="openForm('update', scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-item:update']">
+                    {{ t('fault.edit') }}
+                  </el-button>
+                  <el-button
+                    link
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-item: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" show-border height="calc(85vh - 180px)">
-          <!--      <el-table-column label="主键id" align="center" prop="id" />-->
-          <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('monitor.category')"
-            align="center"
-            prop="deviceClassifyName"
-          />
-          <zm-table-column :label="t('iotDevice.brand')" align="center" prop="brandName" />
-          <zm-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" />
-          <zm-table-column :label="t('monitor.deviceName')" align="center" prop="deviceName" />
-          <zm-table-column :label="t('inspect.InspectionItems')" align="center" prop="item" />
-          <zm-table-column
-            :label="t('inspect.InspectionStandards')"
-            align="center"
-            prop="standard"
-          />
-          <zm-table-column :label="t('inspect.createName')" align="center" prop="createName" />
-          <!--      <el-table-column :label="t('common.sort')" align="center" prop="sort" />-->
-          <!--      <el-table-column :label="t('form.remark')" align="center" prop="remark" />-->
-
-          <zm-table-column
-            :label="t('common.createTime')"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            width="180px"
-          />
-          <!--      <el-table-column label="部门id" align="center" prop="deptId" />-->
-          <zm-table-column
-            :label="t('operationFill.operation')"
-            align="center"
-            min-width="120px"
-            action
-            fixed="right"
-          >
-            <template #default="scope">
-              <el-button
-                link
-                type="primary"
-                @click="openForm('update', scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-item:update']"
-              >
-                {{ t('fault.edit') }}
-              </el-button>
-              <el-button
-                link
-                type="danger"
-                @click="handleDelete(scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-item: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>
 
   <!-- 表单弹窗:添加/修改 -->
   <IotInspectItemForm ref="formRef" @success="getList" />
@@ -157,17 +159,18 @@ import { IotInspectItemApi, IotInspectItemVO } from '@/api/pms/inspect/item'
 import IotInspectItemForm from './IotInspectItemForm.vue'
 import { defaultProps, handleTree } from '@/utils/tree'
 import * as ProductClassifyApi from '@/api/pms/productclassify'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
+import { useUserStore } from '@/store/modules/user'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInspectItemVO>()
 
 /** 巡检项 列表 */
 defineOptions({ name: 'IotInspectItem' })
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
-let isLeftContentCollapsed = ref(false)
+const rootDeptId = 156
+const deptId = useUserStore().getUser.deptId || rootDeptId
 const loading = ref(true) // 列表的加载中
 const list = ref<IotInspectItemVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
@@ -185,8 +188,9 @@ const queryParams = reactive({
 const productClassifyList = ref<Tree[]>([]) // 树形结构
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
-const handleDeptNodeClick = async (row) => {
+const handleDeptNodeClick = async (row: Tree) => {
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 /** 查询列表 */
@@ -209,10 +213,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+const handleSizeChange = (pageSize: number) => {
+  queryParams.pageSize = pageSize
   handleQuery()
 }
 
+const handleCurrentChange = (pageNo: number) => {
+  queryParams.pageNo = pageNo
+  getList()
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
@@ -255,3 +269,122 @@ onMounted(async () => {
   await getList()
 })
 </script>
+
+<style scoped>
+.inspect-item-query {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px 24px;
+}
+
+.query-row {
+  display: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  min-width: 0;
+  gap: 12px 24px;
+}
+
+.query-actions {
+  flex: 0 0 auto;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px 10px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 190px;
+}
+
+.query-control--date {
+  width: 230px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+@media (width >= 1800px) {
+  .inspect-item-query,
+  .query-row {
+    flex-wrap: nowrap;
+  }
+}
+
+@media (width <= 1500px) {
+  .inspect-item-query,
+  .query-row {
+    gap: 12px 18px;
+  }
+
+  .query-control {
+    width: 175px;
+  }
+
+  .query-control--date {
+    width: 220px;
+  }
+}
+
+@media (width <= 1200px) {
+  .inspect-item-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-item-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+
+  .query-actions {
+    width: 100%;
+  }
+}
+
+@media (width <= 768px) {
+  .inspect-item-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>

+ 348 - 176
src/views/pms/inspect/order/InspectOrderDetail.vue

@@ -1,204 +1,376 @@
 <template>
-  <ContentWrap v-loading="formLoading">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      v-loading="formLoading"
-      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.planCode')" prop="planCode">
-              <el-input v-model="formData.planCode" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('bomList.name')" prop="inspectOrderTitle">
-              <el-input v-model="formData.inspectOrderTitle" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('bomList.code')" prop="inspectOrderCode">
-              <el-input v-model="formData.inspectOrderCode" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('iotMaintain.PersonInCharge')" prop="chargeName">
-              <el-select v-model="formData.chargeName" style="width: 100%" disabled />
-              <el-option
-                v-for="item in deptUsers"
-                :key="item.id"
-                :label="item.nickname"
-                :value="item.id"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('inspect.reason')" prop="reason">
-              <el-input type="textarea" v-model="formData.reason" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('iotMaintain.remark')" prop="remark">
-              <el-input type="textarea" v-model="formData.remark" disabled />
-            </el-form-item>
-          </el-col>
-        </el-row>
+  <div class="inspect-order-detail-page" v-loading="formLoading">
+    <header class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon"><Icon icon="ep:document" /></div>
+        <div class="page-header__content">
+          <h1>{{ formData.inspectOrderTitle || '巡检工单详情' }}</h1>
+          <p>查看工单基础信息及设备巡检结果统计</p>
+        </div>
       </div>
-    </el-form>
-  </ContentWrap>
-  <ContentWrap>
-    <!-- 列表 -->
-      <ContentWrap>
-        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-          <el-table-column :label="t('iotMaintain.deviceCode')" align="center" prop="deviceCode" />
-          <el-table-column :label="t('iotMaintain.deviceName')" align="center" prop="deviceName" />
-          <el-table-column :label="t('inspect.itemNeed')" align="center" prop="itemNeed" width="180" >
-            <template #default="scope">
-              <el-tag  type="info"> {{scope.row.itemNeed}}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column :label="t('inspect.itemNormal')" align="center" prop="itemNormal" width="180" >
-            <template #default="scope">
-              <el-tag  type="success"> {{scope.row.itemNormal}}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column :label="t('inspect.itemException')" align="center" prop="itemException" width="180" >
-            <template #default="scope">
-              <el-tag  type="danger"> {{scope.row.itemException}}</el-tag>
-            </template>
-          </el-table-column>
-<!--          <el-table-column label="路线名称" align="center" prop="routeName" />-->
-          <el-table-column :label="t('iotMaintain.operation')" align="center" width="200px">
-            <template #default="scope">
-              <div style="display: flex; justify-content: center; align-items: center; width: 100%">
-                <div style="margin-right: 10px">
-                  <Icon style="vertical-align: middle; color: #c36e5b" icon="ep:view" />
-                  <el-button
-                    style="vertical-align: middle"
-                    link
-                    type="warning"
-                    @click="viewRoute(scope.row.orderDetails)"
-                  >
-                    {{ t('route.InspectionRoute') }}
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </header>
+
+    <section class="detail-section">
+      <header class="section-heading"><span></span>工单信息</header>
+      <div class="detail-grid">
+        <div class="detail-item">
+          <span class="detail-item__label">{{ t('main.planCode') }}</span>
+          <span class="detail-item__value">{{ formData.planCode || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-item__label">{{ t('bomList.name') }}</span>
+          <span class="detail-item__value">{{ formData.inspectOrderTitle || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-item__label">{{ t('bomList.code') }}</span>
+          <span class="detail-item__value">{{ formData.inspectOrderCode || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="detail-item__label">{{ t('iotMaintain.PersonInCharge') }}</span>
+          <span class="detail-item__value">{{ formData.chargeName || '-' }}</span>
+        </div>
+        <div class="detail-item detail-item--multiline">
+          <span class="detail-item__label">{{ t('inspect.reason') }}</span>
+          <span class="detail-item__value">{{ formData.reason || '-' }}</span>
+        </div>
+        <div class="detail-item detail-item--multiline">
+          <span class="detail-item__label">{{ t('iotMaintain.remark') }}</span>
+          <span class="detail-item__value">{{ formData.remark || '-' }}</span>
+        </div>
+      </div>
+    </section>
+
+    <section class="detail-section device-section">
+      <header class="section-heading section-heading--between">
+        <div class="section-heading__title"><span></span>巡检设备</div>
+        <div class="device-count">共 {{ list.length }} 台</div>
+      </header>
+      <div class="table-body">
+        <el-auto-resizer class="table-resizer">
+          <template #default="{ width, height }">
+            <ZmTable
+              :loading="loading"
+              :data="list"
+              :width="width"
+              :height="height"
+              :max-height="height"
+              show-border
+              settings-cache-key="pms-inspect-order-detail-device-list">
+              <ZmTableColumn :label="t('iotMaintain.deviceCode')" prop="deviceCode" />
+              <ZmTableColumn :label="t('iotMaintain.deviceName')" prop="deviceName" />
+              <ZmTableColumn :label="t('inspect.itemNeed')" prop="itemNeed">
+                <template #default="scope">
+                  <el-tag type="info">{{ scope.row.itemNeed }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('inspect.itemNormal')" prop="itemNormal">
+                <template #default="scope">
+                  <el-tag type="success">{{ scope.row.itemNormal }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('inspect.itemException')" prop="itemException">
+                <template #default="scope">
+                  <el-tag type="danger">{{ scope.row.itemException }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('iotMaintain.operation')"
+                :width="160"
+                fixed="right"
+                hide-in-column-settings
+                action>
+                <template #default="scope">
+                  <el-button link type="primary" @click="viewRoute(scope.row.orderDetails)">
+                    <Icon icon="ep:view" class="mr-5px" />{{ t('route.InspectionRoute') }}
                   </el-button>
-                </div>
-              </div>
-            </template>
-          </el-table-column>
-        </el-table>
-      </ContentWrap>
-  </ContentWrap>
-  <ContentWrap>
-    <el-form>
-      <el-form-item style="float: right">
-        <el-button @click="close">{{t('common.cancel')}}</el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-  <RouteInspectItemDrawer
-    ref="showDrawer"
-    :model-value="drawerVisible"
-    @update:model-value="(val) => (drawerVisible = val)"
-  />
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </el-auto-resizer>
+      </div>
+    </section>
+
+    <footer class="action-bar">
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </footer>
+
+    <RouteInspectItemDrawer
+      ref="showDrawer"
+      :model-value="drawerVisible"
+      @update:model-value="(val) => (drawerVisible = val)" />
+  </div>
 </template>
+
 <script setup lang="ts">
-import * as UserApi from '@/api/system/user'
-import { ref } from 'vue'
 import { useTagsViewStore } from '@/store/modules/tagsView'
-import { useCache } from '@/hooks/web/useCache'
 import RouteInspectItemDrawer from '@/views/pms/inspect/plan/RouteInspectItemDrawer.vue'
 import { IotInspectOrderApi, IotInspectOrderVO } from '@/api/pms/inspect/order'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+
+type InspectOrderDeviceRow = {
+  deviceCode: string
+  deviceName: string
+  itemNeed: number
+  itemNormal: number
+  itemException: number
+  orderDetails: unknown[]
+}
+
+type InspectOrderDetail = IotInspectOrderVO & {
+  planCode?: string
+  chargeName?: string
+  reason?: string
+}
+
+const { ZmTable, ZmTableColumn } = useTableComponents<InspectOrderDeviceRow>()
 
-/** 维修工单 表单 */
-defineOptions({ name: 'IotMaintainAe' })
+defineOptions({ name: 'InspectOrderDetail' })
 
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-const { delView } = useTagsViewStore() // 视图操作
+const { t } = useI18n()
+const { delView } = useTagsViewStore()
 const { currentRoute, push } = useRouter()
-const dialogTitle = ref('') // 弹窗的标题
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const drawerVisible = ref<boolean>(false)
-const showDrawer = ref()
-const list = ref<IotInspectOrderVO[]>([]) // 列表的数据
-const { params, name } = useRoute() // 查询参数
+const { params } = useRoute()
 const id = params.id
-const deptUsers = ref<UserApi.UserVO[]>([]) // 用户列表
-const formData = ref({
-  planCode: undefined,
-  id: undefined,
-  inspectOrderTitle: undefined,
-  inspectOrderCode: undefined,
-  status: undefined,
-  remark: undefined,
-  deptId: undefined,
-  reason:undefined,
-  deviceIds: undefined
-})
-const formRules = reactive({
-  planTitle: [{ required: true, message: '巡检计划标题不能为空', trigger: 'blur' }],
-  planCode: [{ required: true, message: '巡检计划编号不能为空', trigger: 'blur' }],
-  planUnit: [{ required: true, message: '单位不能为空', trigger: 'blur' }],
-  charges: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
-})
-// 拖动状态管理
-const items = ref([])
-const deviceChoose = (rows) => {
-  list.value = rows
-}
 
-const viewRoute = (itemJson) => {
+const formLoading = ref(false)
+const loading = ref(false)
+const drawerVisible = ref(false)
+const showDrawer = ref()
+const list = ref<InspectOrderDeviceRow[]>([])
+const formData = ref<Partial<InspectOrderDetail>>({})
+
+const viewRoute = (orderDetails: unknown[]) => {
   drawerVisible.value = true
-  showDrawer.value.openDrawer(itemJson)
-}
-const formRef = ref() // 表单 Ref
-const inspectItemChoose = (rows) => {
-  items.value = []
-  items.value = rows
-}
-const deviceFormRef = ref()
-const openForm = () => {
-  deviceFormRef.value.open()
+  showDrawer.value.openDrawer(orderDetails)
 }
 
 const close = () => {
   delView(unref(currentRoute))
   push({ name: 'IotInspectOrder', params: {} })
 }
-const { wsCache } = useCache()
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 
-/** 重置表单 */
 onMounted(async () => {
-  if (id) {
-    formType.value = 'update'
-    const iotInspectOrder = await IotInspectOrderApi.getIotInspectOrder(id)
-    formData.value = iotInspectOrder
-    list.value = iotInspectOrder.details
-  } else {
-    formType.value = 'create'
+  if (!id) return
+
+  formLoading.value = true
+  loading.value = true
+  try {
+    const inspectOrder = (await IotInspectOrderApi.getIotInspectOrder(id)) as InspectOrderDetail
+    formData.value = inspectOrder
+    list.value = (inspectOrder.details || []) as InspectOrderDeviceRow[]
+  } finally {
+    formLoading.value = false
+    loading.value = false
   }
 })
-const handleDelete = async (id: number) => {
-  try {
-    const index = list.value.findIndex((item) => item.code === id)
-    if (index !== -1) {
-      // 通过 splice 删除元素
-      list.value.splice(index, 1)
-    }
-  } catch {}
-}
 </script>
+
 <style scoped>
-.base-expandable-content {
-  overflow: hidden; /* 隐藏溢出的内容 */
-  transition: max-height 0.3s ease; /* 平滑过渡效果 */
+@media (width <= 1400px) {
+  .inspect-order-detail-page .detail-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (width <= 768px) {
+  .inspect-order-detail-page .page-header {
+    align-items: flex-start;
+  }
+
+  .inspect-order-detail-page .page-header p {
+    display: none;
+  }
+
+  .inspect-order-detail-page .detail-grid {
+    grid-template-columns: minmax(0, 1fr);
+    padding: 12px;
+  }
+
+  .inspect-order-detail-page .detail-item {
+    grid-template-columns: 96px minmax(0, 1fr);
+  }
+}
+
+.inspect-order-detail-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;
+}
+
+.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;
+}
+
+.page-header__content {
+  min-width: 0;
+}
+
+.page-header h1 {
+  margin: 0;
+  overflow: hidden;
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.page-header p {
+  margin: 1px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.detail-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--between {
+  justify-content: space-between;
+}
+
+.section-heading__title {
+  display: flex;
+  align-items: center;
+}
+
+.device-count {
+  font-size: 13px;
+  font-weight: 400;
+  color: var(--el-text-color-secondary);
+}
+
+.detail-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  padding: 16px;
+  gap: 12px 24px;
+}
+
+.detail-item {
+  display: grid;
+  grid-template-columns: 120px minmax(0, 1fr);
+  min-height: 38px;
+  padding: 8px 10px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  align-items: start;
+}
+
+.detail-item__label {
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.detail-item__value {
+  min-width: 0;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  overflow-wrap: anywhere;
+}
+
+.detail-item--multiline .detail-item__value {
+  white-space: pre-wrap;
+}
+
+.device-section {
+  display: flex;
+  min-height: 320px;
+  flex: 1;
+  flex-direction: column;
+}
+
+.table-body {
+  position: relative;
+  min-width: 0;
+  min-height: 260px;
+  overflow: hidden;
+  box-sizing: border-box;
+  flex: 1;
+}
+
+.table-resizer {
+  position: absolute;
+  inset: 12px 12px 18px;
+  width: auto !important;
+  height: auto !important;
+  min-width: 0;
+}
+
+.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>

+ 938 - 153
src/views/pms/inspect/order/WriteOrder.vue

@@ -1,134 +1,183 @@
 <template>
-  <ContentWrap>
-    <el-tabs
-      v-model="activeTab"
-      type="border-card"
-      tab-position="left"
-      v-loading="loading"
-      style="height: 84vh"
-    >
-      <el-tab-pane
-        style="height: 100%"
-        v-for="(tab, tabIndex) in tabs"
-        :key="tab.deviceName"
-        :name="String(tabIndex)"
-      >
-        <template #label>
-          <span class="custom-label">
-            {{ tab.deviceName }} ({{ completedSteps(tabIndex) }}/{{ tab.orderDetails.length }})
-                  <el-tooltip :content="tab.routeDesc" placement="top" effect="dark">
-                    <Icon icon="ep:info-filled" />
-                  </el-tooltip>
-          </span>
-        </template>
-
-        <div class="step-container">
-          <el-steps direction="vertical" :active="currentStep[tabIndex]" class="steps-nav">
-            <el-step
+  <div class="write-order-page" v-loading="loading">
+    <header class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon"><Icon icon="ep:edit-pen" /></div>
+        <div>
+          <h1>填写巡检工单</h1>
+          <p>按设备和巡检项顺序填写检查结果与异常信息</p>
+        </div>
+      </div>
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </header>
+
+    <el-empty v-if="!loading && tabs.length === 0" class="empty-panel" description="暂无巡检设备" />
+    <div v-else class="inspection-workbench">
+      <aside class="device-panel">
+        <header class="device-panel__header">
+          <div>
+            <strong>巡检设备</strong>
+            <span>共 {{ tabs.length }} 台</span>
+          </div>
+        </header>
+        <div class="device-list">
+          <button
+            v-for="(tab, tabIndex) in tabs"
+            :key="`${tab.deviceId}-${tabIndex}`"
+            type="button"
+            class="device-item"
+            :class="{ 'is-active': activeTab === String(tabIndex) }"
+            @click="activeTab = String(tabIndex)">
+            <span class="device-item__icon"><Icon icon="ep:monitor" /></span>
+            <span class="device-item__content">
+              <span class="device-item__name">{{ tab.deviceName }}</span>
+              <span class="device-item__progress">
+                已完成 {{ completedSteps(tabIndex) }} / {{ tab.orderDetails.length }} 项
+              </span>
+            </span>
+            <Icon icon="ep:arrow-right" class="device-item__arrow" />
+          </button>
+        </div>
+      </aside>
+
+      <div class="device-content">
+        <section
+          v-for="(tab, tabIndex) in tabs"
+          v-show="activeTab === String(tabIndex)"
+          :key="`${tab.deviceId}-content`"
+          class="device-inspection-panel">
+          <header class="device-summary">
+            <div class="device-summary__main">
+              <div class="device-summary__icon"><Icon icon="ep:monitor" /></div>
+              <div>
+                <h2>{{ tab.deviceName }}</h2>
+                <p>{{ tab.routeDesc || '按巡检项顺序完成当前设备检查' }}</p>
+              </div>
+            </div>
+            <span class="device-summary__progress">
+              {{ completedSteps(tabIndex) }}/{{ tab.orderDetails.length }} 已完成
+            </span>
+          </header>
+
+          <div class="item-progress-strip">
+            <div
               v-for="(step, stepIndex) in tab.orderDetails"
               :key="stepIndex"
-              :title="`${step.item}`"
-              :status="getStepStatus(tabIndex, stepIndex)"
-            >
-              <template #title>
-                <div class="step-title-wrapper">
-                  <span class="title-text">{{ step.item }}</span>
-                  <el-tooltip :content="step.standard" placement="top" effect="dark">
-                    <Icon icon="ep:info-filled" />
-                  </el-tooltip>
+              class="progress-item"
+              :class="`is-${getStepStatus(tabIndex, stepIndex)}`">
+              <span class="progress-item__index">
+                <Icon v-if="getStepStatus(tabIndex, stepIndex) === 'finish'" icon="ep:check" />
+                <template v-else>{{ stepIndex + 1 }}</template>
+              </span>
+              <span class="progress-item__name">{{ step.item }}</span>
+            </div>
+          </div>
+
+          <div class="current-item-layout">
+            <section class="entry-section">
+              <header class="section-heading section-heading--between">
+                <div class="section-heading__title">
+                  <span></span>
+                  {{ tab.orderDetails[currentStep[tabIndex]].item }}
+                </div>
+                <div class="step-counter">
+                  第 {{ currentStep[tabIndex] + 1 }} / {{ tab.orderDetails.length }} 项
                 </div>
-              </template>
-            </el-step>
-          </el-steps>
+              </header>
 
-          <div class="form-wrapper">
-            <ContentWrap>
               <el-form
                 :model="formData[tabIndex][currentStep[tabIndex]]"
                 :rules="formRules"
                 ref="formRefs"
-                label-width="120px"
-              >
+                size="default"
+                label-width="96px"
+                class="inspection-form">
                 <el-form-item label="设备id" v-if="false" prop="deviceId">
                   <el-input
                     v-model="formData[tabIndex][currentStep[tabIndex]].deviceId"
                     :model-value="tab.deviceId"
-                    clearable
-                  />
-                </el-form-item>
-                <el-form-item :label="t('inspect.isException')" prop="ifNormal">
-                  <el-select
-                    v-model="formData[tabIndex][currentStep[tabIndex]].ifNormal"
-                    :placeholder="t('inspect.isException')"
-                    clearable
-                  >
-                    <el-option
-                      v-for="dict in getBoolDictOptions(DICT_TYPE.PMS_INSPECT_WRITE)"
-                      :key="dict.value"
-                      :label="dict.label"
-                      :value="dict.value"
-                    />
-                  </el-select>
-                </el-form-item>
-
-                <el-form-item
-                  :label="t('inspect.exceptionDes')"
-                  prop="description"
-                  :rules="formData[tabIndex][currentStep[tabIndex]].ifNormal ?  []: descriptionRule"
-                >
-                  <el-input
-                    v-model="formData[tabIndex][currentStep[tabIndex]].description"
-                    type="textarea"
-                    :rows="5"
-                    :placeholder="t('inspect.exceptionDes')"
-                  />
-                </el-form-item>
-
-                <el-form-item :label="t('deviceForm.picture')" prop="picUrl">
-                  <UploadImg
-                    v-model="formData[tabIndex][currentStep[tabIndex]].picUrl"
-                    height="55px"
-                    width="30em"
-                  />
+                    clearable />
                 </el-form-item>
+                <div class="form-grid">
+                  <el-form-item :label="t('inspect.isException')" prop="ifNormal">
+                    <el-select
+                      v-model="formData[tabIndex][currentStep[tabIndex]].ifNormal"
+                      :placeholder="t('inspect.isException')"
+                      clearable>
+                      <el-option
+                        v-for="dict in getBoolDictOptions(DICT_TYPE.PMS_INSPECT_WRITE)"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value" />
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item :label="t('deviceForm.picture')" prop="picUrl">
+                    <UploadImg
+                      v-model="formData[tabIndex][currentStep[tabIndex]].picUrl"
+                      height="55px"
+                      width="120px" />
+                  </el-form-item>
+                  <el-form-item
+                    :label="t('inspect.exceptionDes')"
+                    prop="description"
+                    class="form-grid__wide"
+                    :rules="
+                      formData[tabIndex][currentStep[tabIndex]].ifNormal ? [] : descriptionRule
+                    ">
+                    <el-input
+                      v-model="formData[tabIndex][currentStep[tabIndex]].description"
+                      type="textarea"
+                      :rows="5"
+                      :placeholder="t('inspect.exceptionDes')" />
+                  </el-form-item>
+                </div>
               </el-form>
+            </section>
 
-              <div class="navigation-controls">
-                <el-button :disabled="currentStep[tabIndex] === 0" @click="handlePrev(tabIndex)">
-                  上一步
-                </el-button>
-
-                <el-button
-                  type="primary"
-                  :disabled="!isStepValid(tabIndex)"
-                  @click="handleNext(tabIndex, tab.deviceId)"
-                >
-                  {{ isLastStep(tabIndex) ? '完成提交' : '下一步' }}
-                </el-button>
+            <aside class="standard-section">
+              <header class="standard-section__header">
+                <Icon icon="ep:notebook-2" />
+                <span>{{ t('inspect.InspectionStandards') }}</span>
+              </header>
+              <div class="standard-section__content">
+                {{ tab.orderDetails[currentStep[tabIndex]].standard || '-' }}
               </div>
-            </ContentWrap>
-            <ContentWrap style="margin-top: 50px">
-              <el-card>
-                <span style="font-weight: bold">{{ t('inspect.InspectionStandards') }}:</span
-                >{{ tab.orderDetails[currentStep[tabIndex]].standard }}<br />
-                <el-divider />
-                <span style="font-weight: bold">{{ t('fileInfo.appendix') }}:</span>
+              <footer class="standard-section__footer">
+                <span>{{ t('fileInfo.appendix') }}</span>
                 <el-button
                   v-if="tab.orderDetails[currentStep[tabIndex]].urls"
                   link
                   type="primary"
-                  @click="openWeb(tab.orderDetails[currentStep[tabIndex]].urls)"
-                >
-                  <Icon size="19" icon="ep:view" />
-                  {{ t('action.preview') }}
+                  @click="openWeb(tab.orderDetails[currentStep[tabIndex]].urls)">
+                  <Icon icon="ep:view" class="mr-5px" />{{ t('action.preview') }}
                 </el-button>
-              </el-card>
-            </ContentWrap>
+                <span v-else>-</span>
+              </footer>
+            </aside>
           </div>
-        </div>
-      </el-tab-pane>
-    </el-tabs>
-  </ContentWrap>
+
+          <div class="navigation-controls">
+            <el-button
+              size="default"
+              :disabled="currentStep[tabIndex] === 0"
+              @click="handlePrev(tabIndex)">
+              <Icon icon="ep:arrow-left" class="mr-5px" />上一步
+            </el-button>
+            <el-button
+              size="default"
+              type="primary"
+              :disabled="!isStepValid(tabIndex)"
+              @click="handleNext(tabIndex, tab.deviceId)">
+              {{ isLastStep(tabIndex) ? '完成提交' : '下一步' }}
+              <Icon :icon="isLastStep(tabIndex) ? 'ep:check' : 'ep:arrow-right'" class="ml-5px" />
+            </el-button>
+          </div>
+        </section>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup>
@@ -160,6 +209,11 @@ const openWeb = (url) => {
     'http://1.94.244.160:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(url))
   )
 }
+
+const close = () => {
+  delView(unref(currentRoute))
+  push({ name: 'IotInspectOrder', params: {} })
+}
 // 响应式状态
 const loading = ref(true)
 const activeTab = ref('0')
@@ -265,78 +319,809 @@ const submitForm = (tabIndex, current, type) => {
 </script>
 
 <style scoped>
-.step-container {
+@media (width <= 1400px) {
+  .write-order-page .device-tabs :deep(.el-tabs__header.is-left) {
+    width: 210px;
+  }
+
+  .write-order-page .inspection-workspace {
+    grid-template-columns: 220px minmax(0, 1fr);
+  }
+}
+
+@media (width <= 1100px) {
+  .write-order-page .inspection-workspace {
+    grid-template-columns: minmax(0, 1fr);
+    grid-template-rows: 220px minmax(0, 1fr);
+  }
+
+  .write-order-page .steps-panel {
+    border-right: 0;
+    border-bottom: 1px solid var(--el-border-color-lighter);
+  }
+}
+
+@media (width <= 768px) {
+  .write-order-page .page-header {
+    align-items: flex-start;
+  }
+
+  .write-order-page .page-header p {
+    display: none;
+  }
+
+  .write-order-page .device-tabs :deep(.el-tabs__header.is-left) {
+    width: 170px;
+  }
+
+  .write-order-page .custom-label__progress {
+    display: none;
+  }
+
+  .write-order-page .step-panel__content {
+    padding: 16px 12px 0;
+  }
+
+  .write-order-page .inspection-form {
+    max-width: none;
+  }
+
+  .write-order-page .standard-panel {
+    padding: 12px;
+  }
+}
+
+.write-order-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;
+}
+
+.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;
+}
+
+.page-header h1 {
+  margin: 0;
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.page-header p {
+  margin: 1px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.empty-panel {
+  min-height: 480px;
+  background: var(--el-bg-color);
+  border-radius: 8px;
+  box-shadow: var(--el-box-shadow-light);
+  flex: 1;
+}
+
+.device-tabs {
+  display: flex;
+  min-height: 580px;
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border-radius: 8px;
+  box-shadow: var(--el-box-shadow-light);
+  flex: 1;
+}
+
+.device-tabs :deep(.el-tabs__header.is-left) {
+  width: 240px;
+  margin-right: 0;
+  overflow: hidden;
+  background: var(--el-fill-color-extra-light);
+  flex: 0 0 auto;
+}
+
+.device-tabs :deep(.el-tabs__nav-wrap.is-left) {
+  height: 100%;
+}
+
+.device-tabs :deep(.el-tabs__item.is-left) {
+  justify-content: flex-start;
+  height: 48px;
+  padding: 0 14px;
+}
+
+.device-tabs :deep(.el-tabs__content) {
+  height: 100%;
+  min-width: 0;
+  padding: 0;
+  overflow: hidden;
+  flex: 1;
+}
+
+.device-tabs :deep(.el-tab-pane) {
+  height: 100%;
+}
+
+.custom-label {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  min-width: 0;
+  gap: 8px;
+}
+
+.custom-label__name {
+  min-width: 0;
+  overflow: hidden;
+  font-weight: 500;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  flex: 1;
+}
+
+.custom-label__progress {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  flex: 0 0 auto;
+}
+
+.custom-label__info,
+.step-info {
+  color: var(--el-color-primary);
+  flex: 0 0 auto;
+}
+
+.inspection-workspace {
   display: grid;
-  grid-template-columns: 220px 1fr;
-  gap: 10px;
+  grid-template-columns: 260px minmax(0, 1fr);
   height: 100%;
-  min-height: 600px;
+  min-height: 0;
 }
 
-.steps-nav {
+.steps-panel {
+  display: flex;
+  min-width: 0;
+  overflow: hidden;
+  background: var(--el-fill-color-blank);
+  border-right: 1px solid var(--el-border-color-lighter);
+  flex-direction: column;
+}
+
+.steps-panel__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 44px;
+  padding: 0 16px;
+  font-size: 14px;
+  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);
+}
+
+.steps-panel__header span:last-child {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--el-text-color-secondary);
+}
+
+.steps-panel__body {
+  min-height: 0;
+  padding: 18px 12px 10px 18px;
   overflow-y: auto;
-  padding-right: 15px;
+  flex: 1;
 }
 
-.form-wrapper {
-  background: #fff;
-  padding: 30px;
-  border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+.steps-nav {
+  min-height: 100%;
 }
 
-.navigation-controls {
-  margin-top: 40px;
-  text-align: center;
+.steps-nav :deep(.el-step__title) {
+  min-width: 0;
+  font-size: 14px;
+  line-height: 22px;
 }
 
-.custom-label {
-  font-weight: 1000;
-  font-size: 17px;
-  padding: 0 10px;
+.steps-nav :deep(.el-step__head.is-process .el-step__icon) {
+  color: #fff;
+  background: var(--el-color-primary);
+  border-color: var(--el-color-primary);
 }
-::v-deep .el-step__icon {
-  background-color: #409eff;
+
+.steps-nav :deep(.el-step__head.is-finish .el-step__icon) {
   color: #fff;
-  border: 0px;
+  background: var(--el-color-success);
+  border-color: var(--el-color-success);
 }
+
 .step-title-wrapper {
-  display: inline-flex;
+  display: flex;
+  align-items: flex-start;
+  min-width: 0;
+  padding-right: 8px;
+  gap: 6px;
+}
+
+.title-text {
+  min-width: 0;
+  overflow-wrap: anywhere;
+}
+
+.step-panel {
+  display: flex;
+  min-width: 0;
+  min-height: 0;
+  flex-direction: column;
+}
+
+.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--between {
+  justify-content: space-between;
+}
+
+.section-heading__title {
+  display: flex;
   align-items: center;
+  min-width: 0;
+}
+
+.section-heading__title > span {
+  width: 4px;
+  height: 17px;
+  margin-right: 9px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+  flex: 0 0 auto;
+}
+
+.step-counter {
+  margin-left: 16px;
+  font-size: 13px;
+  font-weight: 400;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.step-panel__content {
+  display: flex;
+  min-height: 0;
+  padding: 24px 24px 0;
+  overflow-y: auto;
+  flex: 1;
+  flex-direction: column;
+}
+
+.inspection-form {
+  width: 100%;
+  max-width: 920px;
+}
+
+.inspection-form :deep(.el-form-item) {
+  margin-bottom: 20px;
+}
+
+.inspection-form :deep(.el-select),
+.inspection-form :deep(.el-input) {
+  width: 100%;
+}
+
+.standard-panel {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr);
+  padding: 14px 16px;
+  margin-top: 4px;
+  background: var(--el-fill-color-extra-light);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  gap: 10px;
+}
+
+.standard-item {
+  display: flex;
+  min-width: 0;
+  align-items: flex-start;
+}
+
+.standard-item__label {
+  width: 110px;
+  margin-right: 10px;
+  font-weight: 600;
+  color: var(--el-text-color-regular);
+  flex: 0 0 auto;
+}
+
+.standard-item__value {
+  min-width: 0;
+  color: var(--el-text-color-secondary);
+  overflow-wrap: anywhere;
+}
+
+.navigation-controls {
+  position: sticky;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  min-height: 60px;
+  padding: 10px 0;
+  margin-top: auto;
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-lighter);
   gap: 8px;
-  position: relative;
-  padding-right: 25px;
 }
 
-/* 覆盖步骤条默认样式 */
-:deep(.custom-steps) {
-  /* 调整头部位置 */
-  .el-step__head {
-    top: 3px;
+.navigation-controls :deep(.el-button) {
+  margin-left: 0;
+}
+
+@media (width <= 1400px) {
+  .write-order-page .inspection-workbench {
+    grid-template-columns: 250px minmax(0, 1fr);
+  }
+
+  .write-order-page .current-item-layout {
+    grid-template-columns: minmax(0, 1fr) 380px;
   }
+}
+
+@media (width <= 1200px) {
+  .write-order-page .current-item-layout {
+    grid-template-columns: minmax(0, 1fr);
+  }
+
+  .write-order-page .standard-section__content {
+    max-height: 220px;
+  }
+}
 
-  /* 标题容器定位 */
-  .el-step__title {
-    display: inline-block;
-    margin-left: 10px;
-    padding-right: 0;
+@media (width <= 900px) {
+  .write-order-page .inspection-workbench {
+    grid-template-columns: minmax(0, 1fr);
+    grid-template-rows: auto minmax(520px, 1fr);
   }
 
-  /* 步骤连接线 */
-  .el-step__line {
-    left: 11px;
-    background-color: #ebeef5;
+  .write-order-page .device-panel {
+    border-right: 0;
+    border-bottom: 1px solid var(--el-border-color-lighter);
   }
 
-  /* 当前步骤样式 */
-  .is-process .title-text {
-    font-weight: 600;
-    color: #409eff;
+  .write-order-page .device-list {
+    flex-direction: row;
+    padding: 10px;
+    overflow-x: auto;
   }
 
-  /* 完成状态图标 */
-  .is-finish .tip-icon {
-    color: #67c23a;
+  .write-order-page .device-item {
+    width: 240px;
+    flex: 0 0 auto;
   }
 }
+
+@media (width <= 600px) {
+  .write-order-page .device-summary,
+  .write-order-page .section-heading--between {
+    align-items: flex-start;
+  }
+
+  .write-order-page .device-summary__progress,
+  .write-order-page .step-counter {
+    margin-top: 3px;
+  }
+
+  .write-order-page .current-item-layout {
+    padding: 12px;
+  }
+
+  .write-order-page .form-grid {
+    grid-template-columns: minmax(0, 1fr);
+  }
+
+  .write-order-page .form-grid__wide {
+    grid-column: auto;
+  }
+}
+
+.inspection-workbench {
+  display: grid;
+  grid-template-columns: 280px minmax(0, 1fr);
+  min-height: 580px;
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: var(--el-box-shadow-light);
+  flex: 1;
+}
+
+.device-panel {
+  display: flex;
+  min-width: 0;
+  overflow: hidden;
+  background: var(--el-fill-color-extra-light);
+  border-right: 1px solid var(--el-border-color-lighter);
+  flex-direction: column;
+}
+
+.device-panel__header {
+  display: flex;
+  align-items: center;
+  min-height: 54px;
+  padding: 10px 16px;
+  background: var(--el-bg-color);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.device-panel__header > div {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  width: 100%;
+  gap: 12px;
+}
+
+.device-panel__header strong {
+  font-size: 15px;
+  color: var(--el-text-color-primary);
+}
+
+.device-panel__header span {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.device-list {
+  display: flex;
+  min-height: 0;
+  padding: 10px;
+  overflow-y: auto;
+  flex: 1;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.device-item {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  min-height: 58px;
+  padding: 9px 10px;
+  color: var(--el-text-color-regular);
+  text-align: left;
+  cursor: pointer;
+  background: transparent;
+  border: 1px solid transparent;
+  border-radius: 6px;
+  transition:
+    color 0.2s ease,
+    background-color 0.2s ease,
+    border-color 0.2s ease;
+  gap: 10px;
+}
+
+.device-item:hover {
+  background: var(--el-fill-color-light);
+}
+
+.device-item.is-active {
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border-color: var(--el-color-primary-light-7);
+}
+
+.device-item__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 30px;
+  height: 30px;
+  font-size: 15px;
+  color: var(--el-text-color-secondary);
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  flex: 0 0 auto;
+}
+
+.device-item.is-active .device-item__icon {
+  color: var(--el-color-primary);
+  border-color: var(--el-color-primary-light-7);
+}
+
+.device-item__content {
+  display: flex;
+  min-width: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.device-item__name {
+  overflow: hidden;
+  font-size: 14px;
+  font-weight: 500;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.device-item__progress {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.device-item__arrow {
+  color: var(--el-text-color-placeholder);
+  flex: 0 0 auto;
+}
+
+.device-content {
+  min-width: 0;
+  min-height: 0;
+  overflow: auto;
+}
+
+.device-inspection-panel {
+  display: flex;
+  min-height: 100%;
+  flex-direction: column;
+}
+
+.device-summary {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 70px;
+  padding: 12px 18px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  gap: 16px;
+}
+
+.device-summary__main {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.device-summary__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 36px;
+  height: 36px;
+  margin-right: 12px;
+  font-size: 17px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border-radius: 7px;
+  flex: 0 0 auto;
+}
+
+.device-summary h2 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.device-summary p {
+  margin: 1px 0 0;
+  overflow: hidden;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.device-summary__progress {
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.item-progress-strip {
+  display: flex;
+  padding: 12px 18px;
+  overflow-x: auto;
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  gap: 8px;
+}
+
+.progress-item {
+  display: flex;
+  max-width: 240px;
+  min-width: 150px;
+  padding: 7px 10px;
+  color: var(--el-text-color-secondary);
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  align-items: center;
+  gap: 8px;
+}
+
+.progress-item.is-process {
+  color: var(--el-color-primary);
+  border-color: var(--el-color-primary-light-5);
+}
+
+.progress-item.is-finish {
+  color: var(--el-color-success);
+  border-color: var(--el-color-success-light-5);
+}
+
+.progress-item__index {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 22px;
+  height: 22px;
+  font-size: 12px;
+  color: #fff;
+  background: var(--el-text-color-placeholder);
+  border-radius: 50%;
+  flex: 0 0 auto;
+}
+
+.progress-item.is-process .progress-item__index {
+  background: var(--el-color-primary);
+}
+
+.progress-item.is-finish .progress-item__index {
+  background: var(--el-color-success);
+}
+
+.progress-item__name {
+  min-width: 0;
+  overflow: hidden;
+  font-size: 13px;
+  font-weight: 500;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.current-item-layout {
+  display: grid;
+  grid-template-columns: minmax(0, 1.2fr) minmax(360px, 0.8fr);
+  padding: 18px;
+  align-items: start;
+  gap: 16px;
+}
+
+.entry-section,
+.standard-section {
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 7px;
+}
+
+.entry-section .inspection-form {
+  max-width: none;
+  padding: 18px 18px 0;
+}
+
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  column-gap: 20px;
+}
+
+.form-grid :deep(.el-form-item) {
+  min-width: 0;
+  align-items: flex-start;
+}
+
+.form-grid :deep(.el-form-item__label) {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  height: 32px;
+  padding-right: 12px;
+  line-height: 32px;
+  white-space: nowrap;
+}
+
+.form-grid :deep(.el-form-item__content) {
+  min-width: 0;
+}
+
+.form-grid__wide {
+  grid-column: 1 / -1;
+}
+
+.standard-section {
+  background: var(--el-fill-color-extra-light);
+}
+
+.standard-section__header {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  padding: 0 14px;
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+  background: var(--el-bg-color);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  gap: 8px;
+}
+
+.standard-section__header > :first-child {
+  color: var(--el-color-primary);
+}
+
+.standard-section__content {
+  max-height: 300px;
+  padding: 14px;
+  overflow-y: auto;
+  font-size: 14px;
+  line-height: 1.8;
+  color: var(--el-text-color-regular);
+  white-space: pre-wrap;
+}
+
+.standard-section__footer {
+  display: flex;
+  align-items: center;
+  min-height: 42px;
+  padding: 6px 14px;
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-lighter);
+  gap: 12px;
+}
+
+.device-inspection-panel > .navigation-controls {
+  position: static;
+  min-height: 58px;
+  padding: 10px 18px;
+  margin-top: 0;
+  border-top: 1px solid var(--el-border-color-lighter);
+}
 </style>

+ 312 - 250
src/views/pms/inspect/order/index.vue

@@ -1,236 +1,191 @@
 <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('bomList.name')" prop="inspectOrderTitle">
-            <el-input
-              v-model="queryParams.inspectOrderTitle"
-              :placeholder="t('bomList.nHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-180px"
-            />
-          </el-form-item>
-          <!--          <el-form-item label="工单编码" prop="inspectOrderCode">-->
-          <!--            <el-input-->
-          <!--              v-model="queryParams.inspectOrderCode"-->
-          <!--              placeholder="请输入工单编码"-->
-          <!--              clearable-->
-          <!--              @keyup.enter="handleQuery"-->
-          <!--              class="!w-240px"-->
-          <!--            />-->
-          <!--          </el-form-item>-->
-          <el-form-item :label="t('operationFill.status')" prop="status">
-            <el-select
-              v-model="queryParams.status"
-              :placeholder="t('operationFill.status')"
-              clearable
-              class="!w-180px"
-            >
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_INSPECT_ORDER_STATUS)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </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-180px"
-            />
-          </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-order:create']"-->
-            <!--          >-->
-            <!--            <Icon icon="ep:plus" class="mr-5px" /> 新增-->
-            <!--          </el-button>-->
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-inspect-order:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="inspect-order-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="currentDeptId"
+      v-model="queryParams.deptId"
+      :init-select="false"
+      :show-title="false"
+      request-api="getSimpleDeptList"
+      class="inspect-order-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="88px"
+      class="inspect-order-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('bomList.name')" prop="inspectOrderTitle">
+          <el-input
+            v-model="queryParams.inspectOrderTitle"
+            :placeholder="t('bomList.nHolder')"
+            clearable
+            class="query-control"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item :label="t('operationFill.status')" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            :placeholder="t('operationFill.status')"
+            clearable
+            class="query-control">
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.PMS_INSPECT_ORDER_STATUS)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </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>
 
-      <!-- 列表 -->
-      <ContentWrap style="border: none; margin-top: 20px">
-        <zm-table :loading="loading" :data="list" height="calc(85vh - 138px)">
-          <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('bomList.name')" align="center" prop="inspectOrderTitle" />
-          <!--          <el-table-column label="工单编码" align="center" prop="inspectOrderCode" />-->
-          <zm-table-column
-            :label="t('route.orderType')"
-            align="center"
-            prop="type"
-            min-width="90"
-          />
-          <zm-table-column
-            :label="t('operationFill.status')"
-            align="center"
-            prop="status"
-            min-width="95"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.PMS_INSPECT_ORDER_STATUS" :value="scope.row.status" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('iotMaintain.PersonInCharge')"
-            align="center"
-            prop="chargeName"
-            min-width="110"
-          />
-          <zm-table-column
-            :label="t('inspect.deviceCount')"
-            align="center"
-            prop="deviceCount"
-            min-width="100"
-          >
-            <template #default="scope">
-              <el-tag type="info"> {{ scope.row.deviceCount }}</el-tag>
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('inspect.needDevice')"
-            align="center"
-            prop="needDevice"
-            min-width="100"
-          >
-            <template #default="scope">
-              <el-tag type="success"> {{ scope.row.needDevice }}</el-tag>
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('inspect.exceptionCount')"
-            align="center"
-            prop="exceptionCount"
-            min-width="100"
-          >
-            <template #default="scope">
-              <el-tag type="danger"> {{ scope.row.exceptionCount }}</el-tag>
-            </template>
-          </zm-table-column>
-          <!--          <el-table-column label="备注" align="center" prop="remark" />-->
-          <zm-table-column
-            :label="t('inspect.generateTime')"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column
-            :label="t('inspect.executeTime')"
-            align="center"
-            prop="executeDate"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column
-            :label="t('iotMaintain.operation')"
-            align="center"
-            min-width="160px"
-            fixed="right"
-            action
-          >
-            <template #default="scope">
-              <el-button link type="primary" @click="openForm(scope.row.id)">
-                {{ t('operationFill.view') }}
-              </el-button>
-              <el-button
-                v-if="scope.row.status === 'todo'"
-                link
-                type="primary"
-                @click="openWrite(scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-order:update']"
-              >
-                {{ t('operationFill.fill') }}
-              </el-button>
-              <el-button
-                link
-                type="warning"
-                v-if="scope.row.status === 'todo'"
-                @click="openDialog(scope.row.id)"
-              >
-                {{ t('inspect.ignore') }}
-              </el-button>
-              <!--              <el-button link type="primary" @click="openForm(scope.row.id)"> {{ t('inspect.ignore') }} </el-button>-->
-            </template>
-          </zm-table-column>
-        </zm-table>
-        <el-dialog
-          v-model="dialogVisible"
-          title="忽略理由"
-          :width="600"
-          :before-close="handleClose"
-          append-to-body
-          :close-on-click-modal="false"
-        >
-          <el-form ref="reasonFormRef" :model="form" :rules="rules" label-width="60px">
-            <el-form-item label="理由" prop="reason">
-              <el-input
-                type="textarea"
-                v-model="form.reason"
-                placeholder="请输入忽略理由"
-                :rows="4"
-                resize="none"
-              />
-            </el-form-item>
-          </el-form>
-
-          <template #footer>
-            <el-button @click="handleCancel">取消</el-button>
-            <el-button type="primary" @click="handleConfirm">确定</el-button>
+      <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="success"
+          plain
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['rq:iot-inspect-order: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-order-list">
+              <ZmTableColumn
+                type="index"
+                :label="t('iotDevice.serial')"
+                :width="70"
+                fixed="left"
+                hide-in-column-settings />
+              <ZmTableColumn :label="t('bomList.name')" prop="inspectOrderTitle" fixed="left" />
+              <ZmTableColumn :label="t('route.orderType')" prop="type" />
+              <ZmTableColumn :label="t('operationFill.status')" prop="status">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.PMS_INSPECT_ORDER_STATUS" :value="scope.row.status" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('iotMaintain.PersonInCharge')" prop="chargeName" />
+              <ZmTableColumn :label="t('inspect.deviceCount')" prop="deviceCount">
+                <template #default="scope">
+                  <el-tag type="info">{{ scope.row.deviceCount }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('inspect.needDevice')" prop="needDevice">
+                <template #default="scope">
+                  <el-tag type="success">{{ scope.row.needDevice }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('inspect.exceptionCount')" prop="exceptionCount">
+                <template #default="scope">
+                  <el-tag type="danger">{{ scope.row.exceptionCount }}</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('inspect.generateTime')"
+                prop="createTime"
+                :formatter="dateFormatter" />
+              <ZmTableColumn
+                :label="t('inspect.executeTime')"
+                prop="executeDate"
+                :formatter="dateFormatter" />
+              <ZmTableColumn :label="t('iotMaintain.operation')" :width="180" fixed="right" action>
+                <template #default="scope">
+                  <el-button link type="primary" @click="openForm(scope.row.id)">
+                    {{ t('operationFill.view') }}
+                  </el-button>
+                  <el-button
+                    v-if="scope.row.status === 'todo'"
+                    link
+                    type="primary"
+                    @click="openWrite(scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-order:update']">
+                    {{ t('operationFill.fill') }}
+                  </el-button>
+                  <el-button
+                    v-if="scope.row.status === 'todo'"
+                    link
+                    type="warning"
+                    @click="openDialog(scope.row.id)">
+                    {{ t('inspect.ignore') }}
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
           </template>
-        </el-dialog>
-        <!-- 分页 -->
-        <Pagination
+        </el-auto-resizer>
+      </div>
+
+      <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>
+
+  <el-dialog
+    v-model="dialogVisible"
+    title="忽略理由"
+    width="min(520px, 92vw)"
+    :before-close="handleClose"
+    append-to-body
+    :close-on-click-modal="false">
+    <el-form ref="reasonFormRef" :model="form" :rules="rules" size="default" label-width="60px">
+      <el-form-item label="理由" prop="reason">
+        <el-input
+          v-model="form.reason"
+          type="textarea"
+          placeholder="请输入忽略理由"
+          :rows="4"
+          resize="none" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="default" @click="handleCancel">取消</el-button>
+      <el-button size="default" type="primary" @click="handleConfirm">确定</el-button>
+    </template>
+  </el-dialog>
+
   <IotInspectOrderForm ref="formRef" @success="getList" />
 </template>
 
@@ -240,10 +195,10 @@ import download from '@/utils/download'
 import { IotInspectOrderApi, IotInspectOrderVO } from '@/api/pms/inspect/order'
 import IotInspectOrderForm from './IotInspectOrderForm.vue'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
+import { useUserStore } from '@/store/modules/user'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInspectOrderVO>()
 
 const { push } = useRouter()
 const { params } = useRoute()
@@ -254,11 +209,11 @@ const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const list = ref<IotInspectOrderVO[]>([]) // 列表的数据
-const status = params.status
-const deptId = params.deptId
+const rootDeptId = 156
+const currentDeptId = useUserStore().getUser.deptId || rootDeptId
+const routeDeptId = params.deptId
 const createTime = params.createTime
 const total = ref(0) // 列表的总页数
-let isLeftContentCollapsed = ref(false)
 const dialogVisible = ref(false)
 const queryParams = reactive({
   pageNo: 1,
@@ -275,6 +230,7 @@ const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 /** 查询列表 */
@@ -297,10 +253,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   handleQuery()
 }
 
+const handleSizeChange = (pageSize: number) => {
+  queryParams.pageSize = pageSize
+  handleQuery()
+}
+
+const handleCurrentChange = (pageNo: number) => {
+  queryParams.pageNo = pageNo
+  getList()
+}
+
 const reasonFormRef = ref(null)
 const form = reactive({
   id: undefined,
@@ -396,12 +362,11 @@ const handleExport = async () => {
 
 /** 初始化 **/
 onMounted(() => {
-  debugger
   if (params.status) {
     queryParams.status = params.status
   }
-  if (deptId != null) {
-    queryParams.deptId = deptId
+  if (routeDeptId != null) {
+    queryParams.deptId = routeDeptId
   }
 
   if (createTime) {
@@ -410,24 +375,121 @@ onMounted(() => {
   getList()
 })
 </script>
-<style scoped scss>
-.reason-confirm-container {
-  margin: 20px;
+<style scoped>
+@media (width >= 1800px) {
+  .inspect-order-page .inspect-order-query,
+  .inspect-order-page .query-row {
+    flex-wrap: nowrap;
+  }
 }
 
-/* 调整文本框样式 */
-:deep(.el-textarea__inner) {
-  font-size: 14px;
+@media (width <= 1500px) {
+  .inspect-order-page .inspect-order-query,
+  .inspect-order-page .query-row {
+    gap: 12px 18px;
+  }
+
+  .inspect-order-page .query-control {
+    width: 175px;
+  }
+
+  .inspect-order-page .query-control--date {
+    width: 220px;
+  }
 }
 
-/* 调整表单项间距 */
-:deep(.el-form-item) {
-  margin-bottom ::v-deep .el-table__header-wrapper {
-    position: sticky !important;
+@media (width <= 1200px) {
+  .inspect-order-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)
+    );
+  }
+
+  .inspect-order-page :deep(.inspect-order-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+
+  .inspect-order-page .query-actions {
     width: 100%;
-    top: 0px;
-    z-index: 2000;
   }
-  :15px;
+}
+
+@media (width <= 768px) {
+  .inspect-order-page .inspect-order-query,
+  .inspect-order-page .table-panel {
+    padding: 12px;
+  }
+
+  .inspect-order-page .query-row,
+  .inspect-order-page .query-row :deep(.el-form-item),
+  .inspect-order-page .query-actions,
+  .inspect-order-page .query-control,
+  .inspect-order-page .query-control--date {
+    width: 100%;
+  }
+
+  .inspect-order-page .query-actions :deep(.el-form-item__content) {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    width: 100%;
+    gap: 8px;
+  }
+
+  .inspect-order-page .query-actions :deep(.el-button) {
+    width: 100%;
+  }
+}
+
+.inspect-order-query {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px 24px;
+}
+
+.query-row {
+  display: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  min-width: 0;
+  gap: 12px 24px;
+}
+
+.query-actions {
+  flex: 0 0 auto;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px 10px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 190px;
+}
+
+.query-control--date {
+  width: 240px;
+}
+
+.inspect-order-query :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.inspect-order-query :deep(.el-form-item__label) {
+  white-space: nowrap;
 }
 </style>

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

+ 306 - 169
src/views/pms/inspect/route/InspectItemList.vue

@@ -1,141 +1,141 @@
 <template>
-  <Dialog v-model="dialogVisible" :title="t('route.selectInspectionItems')" style="width: 1300px; min-height: 50%">
-    <ContentWrap>
+  <Dialog
+    v-model="dialogVisible"
+    :title="t('route.selectInspectionItems')"
+    width="min(1200px, 92vw)"
+    class="inspect-item-select-dialog"
+    @close="handleClose">
+    <template #title>
+      <div class="dialog-title mb-4">
+        <div class="dialog-title__icon"><Icon icon="ep:finished" /></div>
+        <div>
+          <div class="dialog-title__text">{{ t('route.selectInspectionItems') }}</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('inspect.InspectionItems')" prop="item" style="margin-left: 45px">
-          <el-input
-            v-model="queryParams.item"
-            :placeholder="t('inspect.itemHolder')"
-            clearable
-            @keyup.enter="handleQuery"
-            class="!w-200px"
-          />
-        </el-form-item>
-<!--        <el-form-item :label="t('iotDevice.brand')" prop="brandName">-->
-<!--          <el-input-->
-<!--            v-model="queryParams.brandName"-->
-<!--            :placeholder="t('iotDevice.brandHolder')"-->
-<!--            clearable-->
-<!--            @keyup.enter="handleQuery"-->
-<!--            class="!w-200px"-->
-<!--          />-->
-<!--        </el-form-item>-->
-        <el-form-item :label="t('common.createTime')" prop="createTime">
-          <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-240px"
-          />
-        </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 @click="handleConfirm" type="danger"
-            ><Icon icon="ep:check" class="mr-5px" /> {{ t('workOrderMaterial.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="88px"
+        class="query-panel">
+        <div class="query-row">
+          <el-form-item :label="t('inspect.InspectionItems')" prop="item">
+            <el-input
+              v-model="queryParams.item"
+              :placeholder="t('inspect.itemHolder')"
+              clearable
+              class="query-control"
+              @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item :label="t('common.createTime')" prop="createTime">
+            <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-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.item }}
-          <!-- 自定义关闭按钮 -->
-          <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.item }}
+          </el-tag>
+        </div>
+      </div>
+
+      <section class="table-section">
+        <ZmTable
+          :loading="loading"
+          :data="list"
+          :max-height="360"
+          show-border
+          settings-cache-key="pms-inspect-route-item-select"
+          @row-click="handleRowClick">
+          <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="toggleRow(row)" />
+            </template>
+          </ZmTableColumn>
+          <ZmTableColumn :label="t('inspect.InspectionItems')" prop="item" />
+          <ZmTableColumn :label="t('inspect.InspectionStandards')" prop="standard" />
+          <ZmTableColumn :label="t('iotDevice.brand')" prop="brandName" />
+          <ZmTableColumn :label="t('fileInfo.appendix')" prop="urls">
+            <template #default="scope">
+              <el-button
+                v-if="scope.row.urls"
+                link
+                type="primary"
+                @click.stop="openWeb(scope.row.urls)">
+                <Icon size="18" icon="ep:view" />
+              </el-button>
+              <span v-else>-</span>
+            </template>
+          </ZmTableColumn>
+          <ZmTableColumn :label="t('common.sort')" prop="sort" />
+          <ZmTableColumn
+            :label="t('common.createTime')"
+            prop="createTime"
+            :formatter="dateFormatter" />
+        </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('workOrderMaterial.confirm') }}
+          </el-button>
+        </div>
       </div>
-    </ContentWrap>
-    <ContentWrap>
-      <el-table
-        ref="tableRef"
-        v-loading="loading"
-        :data="list"
-        :stripe="true"
-        :show-overflow-tooltip="true"
-        @row-click="handleRowClick"
-        row-key="id"
-        @selection-change="handleSelectionChange"
-      >
-<!--        <el-table-column-->
-<!--          type="selection"-->
-<!--          :reserve-selection="true"-->
-<!--          :selectable="(row) => !row.disabled"-->
-<!--        />-->
-        <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('inspect.InspectionItems')" align="center" prop="item" />
-        <el-table-column :label="t('inspect.InspectionStandards')" align="center" prop="standard" />
-        <el-table-column :label="t('iotDevice.brand')" align="center" prop="brandName" />
-        <el-table-column :label="t('fileInfo.appendix')" align="center" prop="urls">
-          <template #default="scope">
-            <el-button v-if="scope.row.urls" link type="primary" @click="openWeb(scope.row.urls)">
-              <Icon size="19" icon="ep:view"
-            /></el-button>
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('common.sort')" align="center" prop="sort" />
-        <el-table-column
-          :label="t('common.createTime')"
-          align="center"
-          prop="createTime"
-          :formatter="dateFormatter"
-        />
-      </el-table>
-      <!-- 分页 -->
-      <Pagination
-        :total="total"
-        v-model:page="queryParams.pageNo"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
-    </ContentWrap>
+    </template>
   </Dialog>
 </template>
 
 <script setup lang="ts">
-import { IotDeviceVO } from '@/api/pms/device'
 import { dateFormatter } from '@/utils/formatTime'
 import { useCache } from '@/hooks/web/useCache'
 import { IotInspectItemApi, IotInspectItemVO } from '@/api/pms/inspect/item'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInspectItemVO>()
 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<IotInspectItemVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
@@ -143,7 +143,7 @@ const queryParams = reactive({
   item: undefined,
   deptId: undefined,
   deviceClassify: undefined,
-  brandName:undefined,
+  brandName: undefined,
   deviceId: undefined,
   createTime: undefined
 })
@@ -153,51 +153,28 @@ const openWeb = (url) => {
   )
 }
 const removeTag = (route) => {
-  // 从已选列表中移除
-  const index = selectedRows.value.findIndex(item => item.id === route.id);
-  debugger
+  const index = selectedRows.value.findIndex(
+    (item) => item.id === route.id && item.deviceCode === route.deviceCode
+  )
   if (index !== -1) {
-    selectedRows.value.splice(index, 1);
-  }
-
-  // 如果当前行在当前页,取消其选中状态
-  const rowInTable = list.value.find(item => item.id === route.id);
-  if (rowInTable) {
-    const rowIndex = list.value.indexOf(rowInTable);
-    // 这里可以添加逻辑强制更新行状态(如果需要)
+    selectedRows.value.splice(index, 1)
   }
-};
-const selectRow = (row) => {
-  selectedRow.value = selectedRow.value?.id === row.id ? null : row
-  emit('choose', row)
-  dialogVisible.value = false
-}
-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 isSelected = (row: IotInspectItemVO) =>
+  selectedRows.value.some((item) => item.id === row.id && item.deviceCode === row.deviceCode)
 
 // 多选 切换行选中状态
-const toggleRow = (row) => {
-  const includes = selectedRows.value.includes(row)
-  if (includes) {
-    selectedRows.value.splice(row)
+const toggleRow = (row: IotInspectItemVO) => {
+  const index = selectedRows.value.findIndex(
+    (item) => item.id === row.id && item.deviceCode === row.deviceCode
+  )
+  if (index !== -1) {
+    selectedRows.value.splice(index, 1)
   } else {
-    selectedRows.value.push(row) // 选中
+    selectedRows.value.push(row)
   }
-  tableRef.value.toggleRowSelection(row, !includes)
 }
 
 // 点击整行选中
@@ -261,12 +238,10 @@ const handleConfirm = () => {
     }))
   )
   dialogVisible.value = false
-  handleClose()
 }
 
 // 关闭时清空选择
 const handleClose = () => {
-  tableRef.value?.clearSelection()
   selectedRows.value = []
   emit('close')
 }
@@ -275,21 +250,183 @@ 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>
+.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: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  min-width: 0;
+  gap: 12px 20px;
+}
+
+.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: 190px;
+}
+
+.query-control--date {
+  width: 240px;
+}
+
+.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 {
+  flex: 0 0 auto;
+  font-weight: 600;
+  color: var(--el-color-primary);
+  white-space: nowrap;
+}
+
+.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);
+}
+
+@media (width <= 768px) {
+  .dialog-title__subtitle {
+    display: none;
+  }
+
+  .query-panel,
+  .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));
+    width: 100%;
+  }
+
+  .query-actions :deep(.el-button) {
+    width: 100%;
+  }
+
+  .selected-panel {
+    flex-direction: column;
+    gap: 8px;
+  }
 }
 </style>

+ 410 - 139
src/views/pms/inspect/route/IotInspectRoute.vue

@@ -1,90 +1,78 @@
 <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="8">
-              <el-form-item :label="t('route.RouteName')" prop="routeName">
-                <el-input v-model="formData.routeName" :placeholder="t('route.RouteName')" />
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item :label="t('monitor.category')" prop="deviceClassify">
-                <el-tree-select
-                  filterable
-                  clearable
-                  v-model="formData.deviceClassify"
-                  :data="productClassifyList"
-                  :props="defaultProps"
-                  check-strictly
-                  node-key="id"
-                  :placeholder="t('deviceForm.categoryHolder')"
-                />
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item :label="t('iotDevice.brand')" prop="brandName">
-                <el-select
-                  clearable
-                  v-model="formData.brandName"
-                  @clear="brandClear"
-                  :placeholder="t('iotDevice.brandHolder')"
-                  @click="openBrand"
-                />
-              </el-form-item>
-            </el-col>
-            <el-col :span="8">
-              <el-form-item :label="t('faultForm.device')" prop="deviceName">
-                <el-input
-                  v-model="formData.deviceName"
-                  @clear="handleClear"
-                  :placeholder="t('iotMaintain.deviceHolder')"
-                  @click="openDevice"
-                  clearable
-                />
-              </el-form-item>
-            </el-col>
-            <el-col :span="16">
-              <el-form-item :label="t('inspect.routeDesc')" prop="routeDesc">
-                <el-input
-                  type="textarea"
-                  v-model="formData.routeDesc"
-                  :placeholder="t('inspect.routeDescHolder')"
-                  clearable
-                />
-              </el-form-item>
-            </el-col>
-          </el-row>
+  <div class="route-form-page" v-loading="formLoading">
+    <header class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon"><Icon icon="ep:guide" /></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.selectInspectionItems') }}</el-button
-              >
-            </el-form-item>
-          </el-form>
-        </ContentWrap>
+      </div>
+      <el-button @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="100px"
+      class="route-form">
+      <section class="form-section">
+        <header class="section-heading"><span></span>路线信息</header>
+        <div class="form-grid">
+          <el-form-item :label="t('route.RouteName')" prop="routeName">
+            <el-input v-model="formData.routeName" :placeholder="t('route.RouteName')" />
+          </el-form-item>
+          <el-form-item :label="t('monitor.category')" prop="deviceClassify">
+            <el-tree-select
+              v-model="formData.deviceClassify"
+              :data="productClassifyList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              :placeholder="t('deviceForm.categoryHolder')"
+              filterable
+              clearable />
+          </el-form-item>
+          <el-form-item :label="t('iotDevice.brand')" prop="brandName">
+            <el-select
+              v-model="formData.brandName"
+              :placeholder="t('iotDevice.brandHolder')"
+              clearable
+              @clear="brandClear"
+              @click="openBrand" />
+          </el-form-item>
+          <el-form-item :label="t('faultForm.device')" prop="deviceName">
+            <el-input
+              v-model="formData.deviceName"
+              :placeholder="t('iotMaintain.deviceHolder')"
+              clearable
+              @clear="handleClear"
+              @click="openDevice" />
+          </el-form-item>
+          <el-form-item :label="t('inspect.routeDesc')" prop="routeDesc" class="form-grid__wide">
+            <el-input
+              v-model="formData.routeDesc"
+              type="textarea"
+              :rows="3"
+              :placeholder="t('inspect.routeDescHolder')"
+              clearable />
+          </el-form-item>
+        </div>
+      </section>
+
+      <section class="form-section inspection-section">
+        <header class="section-heading section-heading--actions">
+          <div class="section-heading__title"><span></span>巡检项顺序</div>
+          <el-button type="primary" @click="openForm">
+            <Icon icon="ep:plus" class="mr-5px" />{{ t('route.selectInspectionItems') }}
+          </el-button>
+        </header>
+
+        <el-empty v-if="items.length === 0" description="暂未选择巡检项" :image-size="72" />
         <draggable
+          v-else
           v-model="items"
           item-key="id"
           tag="div"
@@ -92,47 +80,41 @@
           handle=".sortable-item"
           :animation="150"
           @start="dragStart"
-          @end="dragEnd"
-        >
+          @end="dragEnd">
           <template #item="{ element, index }">
             <div class="sortable-item">
-              <!-- 序号显示 -->
               <div class="order-number">{{ index + 1 }}</div>
-
-              <!-- 拖动手柄 -->
-              <span class="drag-handle">≡</span>
-
-              <!-- 组件内容 -->
+              <span class="drag-handle"><Icon icon="ep:rank" /></span>
               <div class="component-content">
-                <span style="font-weight: bold">{{ t('inspect.InspectionItems') }}:</span
-                ><span style="font-size: 14px">{{ element.item }}</span
-                >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-                <span style="font-weight: bold">{{ t('inspect.InspectionStandards') }}:</span
-                ><span style="font-size: 14px">{{ element.standard }}</span>
-              </div>
-              <div>
-                <el-button type="warning" @click="deleteDraggable(index)">{{ t('form.delete') }}</el-button>
+                <div class="item-field">
+                  <span class="item-field__label">{{ t('inspect.InspectionItems') }}</span>
+                  <span class="item-field__value">{{ element.item || '-' }}</span>
+                </div>
+                <div class="item-field item-field--standard">
+                  <span class="item-field__label">{{ t('inspect.InspectionStandards') }}</span>
+                  <span class="item-field__value">{{ element.standard || '-' }}</span>
+                </div>
               </div>
+              <el-button link type="danger" @click="deleteDraggable(index)">
+                <Icon icon="ep:delete" class="mr-5px" />{{ t('form.delete') }}
+              </el-button>
             </div>
           </template>
         </draggable>
-      </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>
-    <InspectItemList
-      ref="inspectItemFormRef"
-      @choose="inspectItemChoose"
-    />
+      </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" @choose="inspectItemChoose" />
     <DeviceList ref="deviceFormRef" @choose="deviceChoose" />
     <BrandList ref="brandFormRef" @choose="brandChoose" />
-  </ContentWrap>
+  </div>
 </template>
 <script setup lang="ts">
 import * as UserApi from '@/api/system/user'
@@ -146,7 +128,7 @@ import draggable from 'vuedraggable'
 import InspectItemList from '@/views/pms/inspect/route/InspectItemList.vue'
 import DeviceList from '@/views/pms/failure/DeviceList.vue'
 import { IotInspectRouteApi, IotInspectRouteVO } from '@/api/pms/inspect/route'
-import BrandList from "@/views/pms/device/BrandList.vue";
+import BrandList from '@/views/pms/device/BrandList.vue'
 
 /** 维修工单 表单 */
 defineOptions({ name: 'RouteAdd' })
@@ -159,6 +141,7 @@ const deptUsers = ref<UserApi.UserVO[]>([]) // 用户列表
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const pageTitle = computed(() => (formType.value === 'update' ? '编辑巡检路线' : '新增巡检路线'))
 const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const drawerVisible = ref<boolean>(false)
 const showDrawer = ref()
@@ -188,7 +171,7 @@ const brandChoose = (row) => {
   // brandLabel.value = row.value
   formData.value.brandName = row.label
 }
-const brandClear = () =>{
+const brandClear = () => {
   formData.value.brand = undefined
   formData.value.brandName = undefined
 }
@@ -246,7 +229,11 @@ const openForm = () => {
     message.error('请选择设备类别')
     return
   }
-  inspectItemFormRef.value.open(formData.value.deviceClassify, formData.value.deviceId,formData.value.brandName)
+  inspectItemFormRef.value.open(
+    formData.value.deviceClassify,
+    formData.value.deviceId,
+    formData.value.brandName
+  )
 }
 const deviceFormRef = ref()
 const openDevice = () => {
@@ -263,7 +250,7 @@ const close = () => {
   // })
 
   delView(unref(currentRoute))
-  push({ name: 'IotInspectRoute', params:{}})
+  push({ name: 'IotInspectRoute', params: {} })
 }
 const itemsWithIndex = computed(() => {
   return items.value.map((item, index) => ({
@@ -275,7 +262,7 @@ const itemsWithIndex = computed(() => {
 })
 const itemsWithIndexUpdate = computed(() => {
   return items.value.map((item, index) => ({
-    itemId: item.itemId||item.id,
+    itemId: item.itemId || item.id,
     item: item.item,
     standard: item.standard,
     index: index + 1 // 序号从1开始
@@ -345,10 +332,57 @@ const handleDelete = async (id: number) => {
 }
 </script>
 <style scoped>
+@media (width <= 1400px) {
+  .route-form-page .form-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (width <= 768px) {
+  .route-form-page .page-header {
+    align-items: flex-start;
+  }
+
+  .route-form-page .page-header p {
+    display: none;
+  }
+
+  .route-form-page .form-grid {
+    grid-template-columns: minmax(0, 1fr);
+    padding: 14px 14px 0;
+  }
+
+  .route-form-page .form-grid__wide {
+    grid-column: auto;
+  }
+
+  .route-form-page .section-heading--actions {
+    height: auto;
+    min-height: 44px;
+    padding-top: 6px;
+    padding-bottom: 6px;
+  }
+
+  .route-form-page .component-content {
+    grid-template-columns: minmax(0, 1fr);
+    gap: 6px;
+  }
+
+  .route-form-page .sortable-item {
+    align-items: flex-start;
+  }
+
+  .route-form-page .drag-handle {
+    padding-right: 8px;
+    padding-left: 8px;
+  }
+}
+
 .base-expandable-content {
   overflow: hidden; /* 隐藏溢出的内容 */
   transition: max-height 0.3s ease; /* 平滑过渡效果 */
 }
+
 /* 横向布局容器 */
 .horizontal-list {
   display: flex;
@@ -360,28 +394,29 @@ const handleDelete = async (id: number) => {
 
 /* 拖拽项样式 */
 .horizontal-item {
-  flex: 0 0 auto;
+  display: flex;
   min-width: 150px;
   padding: 20px;
+  cursor: move;
   background: #d3a137;
   border: 2px solid #e0e0e0;
   border-radius: 8px;
-  cursor: move;
-  user-select: none; /* 防止文字选中:ml-citation{ref="7" data="citationList"} */
   transition:
     transform 0.3s,
     box-shadow 0.3s;
-  display: flex;
+  user-select: none; /* 防止文字选中:ml-citation{ref="7" data="citationList"} */
+  flex: 0 0 auto;
   align-items: center;
   gap: 10px;
 }
 
 /* 拖拽手柄样式 */
 .drag-handle {
-  opacity: 0.5;
   cursor: move;
+  opacity: 0.5;
   transition: opacity 0.3s;
 }
+
 .drag-handle:hover {
   opacity: 1;
 }
@@ -389,31 +424,31 @@ const handleDelete = async (id: number) => {
 /* 拖拽时的悬停效果 */
 .horizontal-item:hover {
   transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
 }
 
 /* 滚动条样式 */
 .horizontal-list::-webkit-scrollbar {
   height: 8px;
 }
+
 .horizontal-list::-webkit-scrollbar-thumb {
   background: #888;
   border-radius: 4px;
 }
 
 .sortable-container {
-  cursor: move;
   display: flex;
+  padding: 7px;
+  overflow-y: auto;
+  cursor: move;
   flex-direction: column;
   gap: 9px;
-  //max-height: 80vh;
-  overflow-y: auto;
-  padding: 7px;
 }
 
 .sortable-item {
   display: flex;
-  align-items: center;
+  height: 50%;
   padding: 8px;
   background: #fff;
   border: 1px solid #ebeef5;
@@ -422,24 +457,24 @@ const handleDelete = async (id: number) => {
     transform 0.3s,
     box-shadow 0.3s;
   user-select: none;
-  height: 50%;
+  align-items: center;
 }
 
 .sortable-item:hover {
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
 }
 
 .order-number {
+  display: flex;
   width: 24px;
   height: 24px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: #409eff;
-  color: white;
-  border-radius: 50%;
   margin-right: 7px;
   font-weight: bold;
+  color: white;
+  background: #409eff;
+  border-radius: 50%;
+  align-items: center;
+  justify-content: center;
 }
 
 .drag-handle {
@@ -461,8 +496,244 @@ const handleDelete = async (id: number) => {
 .sortable-container::-webkit-scrollbar {
   width: 8px;
 }
+
 .sortable-container::-webkit-scrollbar-thumb {
   background: #c0c4cc;
   border-radius: 4px;
 }
+
+.route-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;
+}
+
+.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;
+}
+
+.page-header h1 {
+  margin: 0;
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.page-header p {
+  margin: 1px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.route-form {
+  display: flex;
+  flex: 1;
+  min-height: 0;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.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;
+}
+
+.route-form :deep(.el-form-item) {
+  margin-bottom: 16px;
+}
+
+.route-form :deep(.el-input),
+.route-form :deep(.el-select),
+.route-form :deep(.el-tree-select) {
+  width: 100%;
+}
+
+.inspection-section {
+  display: flex;
+  min-height: 280px;
+  flex: 1;
+  flex-direction: column;
+}
+
+.inspection-section :deep(.el-empty) {
+  flex: 1;
+}
+
+.sortable-container {
+  display: flex;
+  min-height: 0;
+  padding: 14px;
+  overflow-y: auto;
+  cursor: default;
+  flex: 1;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.sortable-item {
+  display: flex;
+  height: auto;
+  min-height: 58px;
+  padding: 10px 12px;
+  cursor: grab;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+  align-items: center;
+  transition:
+    border-color 0.2s ease,
+    box-shadow 0.2s ease;
+  user-select: none;
+}
+
+.sortable-item:hover {
+  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 {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 26px;
+  height: 26px;
+  margin-right: 2px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #fff;
+  background: var(--el-color-primary);
+  border-radius: 50%;
+  flex: 0 0 auto;
+}
+
+.drag-handle {
+  display: flex;
+  padding: 8px 12px;
+  font-size: 18px;
+  color: var(--el-text-color-placeholder);
+  cursor: grab;
+  opacity: 1;
+}
+
+.component-content {
+  display: grid;
+  grid-template-columns: minmax(180px, 1fr) minmax(280px, 2fr);
+  min-width: 0;
+  flex: 1;
+  gap: 16px;
+}
+
+.item-field {
+  display: flex;
+  min-width: 0;
+  align-items: flex-start;
+}
+
+.item-field__label {
+  margin-right: 8px;
+  font-weight: 600;
+  color: var(--el-text-color-regular);
+  white-space: nowrap;
+}
+
+.item-field__value {
+  min-width: 0;
+  color: var(--el-text-color-secondary);
+  overflow-wrap: anywhere;
+}
+
+.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>

+ 267 - 144
src/views/pms/inspect/route/index.vue

@@ -1,146 +1,147 @@
 <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 mb-2"
-      >
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="68px"
-        >
-          <el-form-item :label="t('route.RouteName')" prop="routeName" style="margin-left: 25px">
-            <el-input
-              v-model="queryParams.routeName"
-              :placeholder="t('route.nHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <!--      <el-form-item :label="t('monitor.category')" prop="deviceClassify">-->
-          <!--        <el-input-->
-          <!--          v-model="queryParams.deviceClassify"-->
-          <!--          :placeholder="t('deviceForm.categoryHolder')"-->
-          <!--          clearable-->
-          <!--          @keyup.enter="handleQuery"-->
-          <!--          class="!w-240px"-->
-          <!--        />-->
-          <!--      </el-form-item>-->
-          <el-form-item :label="t('monitor.category')" prop="deviceClassify" style="width: 15vw">
-            <el-tree-select
-              v-model="queryParams.deviceClassify"
-              :data="productClassifyList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              :placeholder="t('deviceForm.categoryHolder')"
-              filterable
-            />
-          </el-form-item>
-          <el-form-item :label="t('monitor.deviceName')" prop="deviceName">
-            <el-input
-              v-model="queryParams.deviceName"
-              :placeholder="t('iotDevice.nameHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button @click="handleQuery"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('file.search') }}</el-button
-            >
-            <el-button @click="resetQuery"
-              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('file.reset') }}</el-button
-            >
-            <el-button
-              type="primary"
-              plain
-              @click="openForm('create')"
-              v-hasPermi="['rq:iot-inspect-route:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> {{ t('fault.added') }}
-            </el-button>
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-inspect-route:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="inspect-route-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-route-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="88px"
+      class="inspect-route-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('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('monitor.category')" prop="deviceClassify">
+          <el-tree-select
+            v-model="queryParams.deviceClassify"
+            :data="productClassifyList"
+            :props="defaultProps"
+            check-strictly
+            node-key="id"
+            :placeholder="t('deviceForm.categoryHolder')"
+            filterable
+            clearable
+            class="query-control" />
+        </el-form-item>
+        <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>
+      </div>
+
+      <el-form-item class="query-actions">
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />{{ t('file.search') }}
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />{{ t('file.reset') }}
+        </el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-inspect-route:create']">
+          <Icon icon="ep:plus" class="mr-5px" />{{ t('fault.added') }}
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['rq:iot-inspect-route: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-route-list">
+              <ZmTableColumn
+                type="index"
+                :label="t('iotDevice.serial')"
+                :width="70"
+                fixed="left"
+                hide-in-column-settings />
+              <ZmTableColumn :label="t('route.RouteName')" prop="routeName" />
+              <ZmTableColumn :label="t('monitor.category')" prop="deviceClassifyName" />
+              <ZmTableColumn :label="t('monitor.deviceName')" prop="deviceName" />
+              <ZmTableColumn :label="t('iotDevice.brand')" prop="brandName" />
+              <ZmTableColumn
+                :label="t('common.createTime')"
+                prop="createTime"
+                :formatter="dateFormatter" />
+              <ZmTableColumn
+                :label="t('operationFill.operation')"
+                :width="120"
+                action
+                fixed="right">
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="openForm('update', scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-route:update']">
+                    {{ t('fault.edit') }}
+                  </el-button>
+                  <el-button
+                    link
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                    v-hasPermi="['rq:iot-inspect-route:delete']">
+                    {{ t('fault.del') }}
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </el-auto-resizer>
       </div>
 
-      <!-- 列表 -->
-      <ContentWrap style="border: none; margin-top: 10px">
-        <zm-table :loading="loading" :data="list" height="64vh">
-          <zm-table-column :label="t('iotDevice.serial')" width="70" align="center">
-            <template #default="scope">
-              {{ scope.$index + 1 }}
-            </template>
-          </zm-table-column>
-          <el-table-column :label="t('route.RouteName')" align="center" prop="routeName" />
-          <zm-table-column
-            :label="t('monitor.category')"
-            align="center"
-            prop="deviceClassifyName"
-          />
-          <zm-table-column :label="t('monitor.deviceName')" align="center" prop="deviceName" />
-          <!--      <el-table-column label="巡检项" align="center" prop="itemJson" />-->
-          <zm-table-column :label="t('iotDevice.brand')" align="center" prop="brandName" />
-          <zm-table-column
-            :label="t('common.createTime')"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            width="180px"
-          />
-          <!--      <el-table-column label="部门id" align="center" prop="deptId" />-->
-          <zm-table-column
-            :label="t('operationFill.operation')"
-            align="center"
-            min-width="120px"
-            action
-          >
-            <template #default="scope">
-              <el-button
-                link
-                type="primary"
-                @click="openForm('update', scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-route:update']"
-              >
-                {{ t('fault.edit') }}
-              </el-button>
-              <el-button
-                link
-                type="danger"
-                @click="handleDelete(scope.row.id)"
-                v-hasPermi="['rq:iot-inspect-route: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>
 
   <!-- 表单弹窗:添加/修改 -->
   <IotInspectRouteForm ref="formRef" @success="getList" />
@@ -151,18 +152,19 @@ import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotInspectRouteApi, IotInspectRouteVO } from '@/api/pms/inspect/route'
 import IotInspectRouteForm from './IotInspectRouteForm.vue'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
 import { defaultProps } from '@/utils/tree'
+import { useUserStore } from '@/store/modules/user'
 const { push } = useRouter() // 路由跳转
 /** 巡检路线 列表 */
 defineOptions({ name: 'IotInspectRoute' })
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInspectRouteVO>()
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
-let isLeftContentCollapsed = ref(false)
+const rootDeptId = 156
+const deptId = useUserStore().getUser.deptId || rootDeptId
 const loading = ref(true) // 列表的加载中
 const list = ref<IotInspectRouteVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
@@ -183,8 +185,9 @@ const queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
-const handleDeptNodeClick = async (row) => {
+const handleDeptNodeClick = async (row: Tree) => {
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 /** 查询列表 */
@@ -207,10 +210,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   handleQuery()
 }
 
+const handleSizeChange = (pageSize: number) => {
+  queryParams.pageSize = pageSize
+  handleQuery()
+}
+
+const handleCurrentChange = (pageNo: number) => {
+  queryParams.pageNo = pageNo
+  getList()
+}
+
 /** 添加/修改操作 */
 const openForm = (type: string, id?: number) => {
   //修改
@@ -255,3 +268,113 @@ onMounted(() => {
   getList()
 })
 </script>
+
+<style scoped>
+.inspect-route-query {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px 24px;
+}
+
+.query-row {
+  display: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  min-width: 0;
+  gap: 12px 24px;
+}
+
+.query-actions {
+  flex: 0 0 auto;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px 10px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 190px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+@media (width >= 1800px) {
+  .inspect-route-query,
+  .query-row {
+    flex-wrap: nowrap;
+  }
+}
+
+@media (width <= 1500px) {
+  .inspect-route-query,
+  .query-row {
+    gap: 12px 18px;
+  }
+
+  .query-control {
+    width: 175px;
+  }
+}
+
+@media (width <= 1200px) {
+  .inspect-route-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-route-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+
+  .query-actions {
+    width: 100%;
+  }
+}
+
+@media (width <= 768px) {
+  .inspect-route-query,
+  .table-panel {
+    padding: 12px;
+  }
+
+  .query-row,
+  .query-row :deep(.el-form-item),
+  .query-actions,
+  .query-control {
+    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>

+ 661 - 313
src/views/pms/stat/inspect.vue

@@ -1,254 +1,260 @@
 <template>
-  <!-- 第一行:筛选条件 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="24">
-      <el-card class="filter-card" shadow="hover" style="border: none">
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
-        >
-          <el-form-item label="所属部门" prop="project_name">
-            <el-tree-select
-              v-model="queryParams.deptId"
-              :data="deptList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              filterable
-              placeholder="请选择所在部门"
-              clearable
-              style="width: 200px"
-            />
-          </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-240px"
-              @change="handleDateChange"
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" @click="handleQuery">
-              <Icon icon="ep:search" class="mr-5px" /> 搜索
-            </el-button>
-            <el-button @click="resetQuery" class="btn-default">
-              <Icon icon="ep:refresh" class="mr-5px" /> 重置
-            </el-button>
-          </el-form-item>
-        </el-form>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第二行:统计卡片行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-1" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:calendar" class="stat-icon" />
-            </div>
-            <div class="stat-title">昨日工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ day.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ day.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-2" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:star" class="stat-icon" />
-            </div>
-            <div class="stat-title">近一周工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ week.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ week.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-3" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:trend-charts" class="stat-icon" />
-            </div>
-            <div class="stat-title">近一月工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ month.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ month.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-4" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:data-analysis" class="stat-icon" />
-            </div>
-            <div class="stat-title">工单总数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ total.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ total.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第三行:图表行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="12">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-blue"></div>
-              <span class="chart-title">工单状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="8" class="chart-item">
-            <div ref="finishedChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单已执行')">
-              <span class="label-dot label-dot-blue"></span>
-              <span class="label-text">已执行</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="writeChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单待执行')">
-              <span class="label-dot label-dot-amber"></span>
-              <span class="label-text">待执行</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="ignoreChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单忽略')">
-              <span class="label-dot label-dot-gray"></span>
-              <span class="label-text">忽略</span>
-            </div>
-          </el-col>
-        </el-row>
-      </el-card>
-    </el-col>
-    <el-col :span="12">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-green"></div>
-              <span class="chart-title">设备状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="8" class="chart-item">
-            <div ref="finishedTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备已执行')">
-              <span class="label-dot label-dot-blue"></span>
-              <span class="label-text">已填写</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="writeTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备待执行')">
-              <span class="label-dot label-dot-amber"></span>
-              <span class="label-text">未填写</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="ignoreTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备忽略')">
-              <span class="label-dot label-dot-gray"></span>
-              <span class="label-text">忽略</span>
-            </div>
-          </el-col>
-        </el-row>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第四行:消息统计行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="8">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-purple"></div>
-              <span class="chart-title">巡检项状态统计</span>
-            </div>
-          </div>
-        </template>
-        <div ref="statusChartRef" class="pie-chart-container"></div>
-      </el-card>
-    </el-col>
-    <el-col :span="16">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-indigo"></div>
-              <span class="chart-title">近一年数量统计</span>
-            </div>
-          </div>
-        </template>
-        <div ref="chartContainer" class="bar-chart-container"></div>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- TODO 第五行:地图 -->
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="bg kb-screen" id="inspect-kb" :style="targetAreaStyle">
+        <header class="header">巡检统计</header>
+        <main class="inspect-kb-content">
+          <!-- 第一行:筛选条件 -->
+          <el-row :gutter="16" class="filter-row">
+            <el-col :span="24">
+              <el-card class="filter-card" shadow="hover" style="border: none">
+                <el-form
+                  class="-mb-15px"
+                  :model="queryParams"
+                  ref="queryFormRef"
+                  :inline="true"
+                  label-width="80px">
+                  <el-form-item label="所属部门" prop="project_name">
+                    <el-tree-select
+                      v-model="queryParams.deptId"
+                      :data="deptList"
+                      :props="defaultProps"
+                      check-strictly
+                      node-key="id"
+                      filterable
+                      placeholder="请选择所在部门"
+                      clearable
+                      style="width: 200px" />
+                  </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-240px"
+                      @change="handleDateChange" />
+                  </el-form-item>
+                  <el-form-item>
+                    <el-button type="primary" @click="handleQuery">
+                      <Icon icon="ep:search" class="mr-5px" /> 搜索
+                    </el-button>
+                    <el-button @click="resetQuery" class="btn-default">
+                      <Icon icon="ep:refresh" class="mr-5px" /> 重置
+                    </el-button>
+                  </el-form-item>
+                </el-form>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第二行:统计卡片行 -->
+          <el-row :gutter="16" class="summary-row">
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-1" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:calendar" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">昨日工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ day.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ day.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-2" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:star" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">近一周工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ week.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ week.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-3" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:trend-charts" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">近一月工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ month.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ month.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-4" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:data-analysis" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">工单总数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ total.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ total.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第三行:图表行 -->
+          <el-row :gutter="16" class="status-row">
+            <el-col :span="12">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-blue"></div>
+                      <span class="chart-title">工单状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <el-row class="chart-grid">
+                  <el-col :span="8" class="chart-item">
+                    <div ref="finishedChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单已执行')">
+                      <span class="label-dot label-dot-blue"></span>
+                      <span class="label-text">已执行</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="writeChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单待执行')">
+                      <span class="label-dot label-dot-amber"></span>
+                      <span class="label-text">待执行</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="ignoreChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单忽略')">
+                      <span class="label-dot label-dot-gray"></span>
+                      <span class="label-text">忽略</span>
+                    </div>
+                  </el-col>
+                </el-row>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-green"></div>
+                      <span class="chart-title">设备状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <el-row class="chart-grid">
+                  <el-col :span="8" class="chart-item">
+                    <div ref="finishedTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备已执行')">
+                      <span class="label-dot label-dot-blue"></span>
+                      <span class="label-text">已填写</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="writeTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备待执行')">
+                      <span class="label-dot label-dot-amber"></span>
+                      <span class="label-text">未填写</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="ignoreTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备忽略')">
+                      <span class="label-dot label-dot-gray"></span>
+                      <span class="label-text">忽略</span>
+                    </div>
+                  </el-col>
+                </el-row>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第四行:消息统计行 -->
+          <el-row :gutter="16" class="trend-row">
+            <el-col :span="8">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-purple"></div>
+                      <span class="chart-title">巡检项状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <div ref="statusChartRef" class="pie-chart-container"></div>
+              </el-card>
+            </el-col>
+            <el-col :span="16">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-indigo"></div>
+                      <span class="chart-title">近一年数量统计</span>
+                    </div>
+                  </div>
+                </template>
+                <div ref="chartContainer" class="bar-chart-container"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- TODO 第五行:地图 -->
+        </main>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="Index">
@@ -277,12 +283,56 @@ import { ref } from 'vue'
 import { DeptTreeItem } from '@/api/system/dept'
 import * as DeptApi from '@/api/system/dept'
 import { useUserStore } from '@/store/modules/user'
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
 
 // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
 
 /** IoT 首页 */
 defineOptions({ name: 'IotInspectStat' })
 
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
+const chartInstances = new Set<ReturnType<typeof echarts.init>>()
+
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
+
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const targetAreaStyle = computed(() => ({
+  '--kb-scale': scale.value,
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const getChartInstance = (el: HTMLElement | null) => {
+  if (!el) return null
+  const chart = echarts.getInstanceByDom(el) || echarts.init(el)
+  chartInstances.add(chart)
+  return chart
+}
+
+const resizeCharts = () => {
+  chartInstances.forEach((chart) => chart.resize())
+}
+
+const updateScale = () => {
+  cancelAnimationFrame(resizeRaf)
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
+
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
+
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
+    nextTick(resizeCharts)
+  })
+}
+
 // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
 echarts.use([
   TooltipComponent,
@@ -505,7 +555,8 @@ const getStats = async () => {
 
 /** 初始化图表 */
 const initCharts = () => {
-  const chart = echarts.init(statusChartRef.value)
+  const chart = getChartInstance(statusChartRef.value)
+  if (!chart) return
   chart.setOption({
     tooltip: {
       trigger: 'item'
@@ -543,6 +594,7 @@ const initCharts = () => {
       }
     ]
   })
+  chart.off('click')
   chart.on('click', (params) => {
     console.log('点击的数据值为:', params.value)
     console.log('点击的数据类型为:', params.data.type)
@@ -601,7 +653,8 @@ const initCharts = () => {
 
 /** 初始化仪表盘图表 */
 const initGaugeChart = (el: any, value: number, color: string, type: string) => {
-  const chart = echarts.init(el)
+  const chart = getChartInstance(el)
+  if (!chart) return
   chart.setOption({
     series: [
       {
@@ -646,6 +699,7 @@ const initGaugeChart = (el: any, value: number, color: string, type: string) =>
   })
 
   // 添加点击事件监听器
+  chart.off('click')
   chart.on('click', (params) => {
     console.log('点击的数据值为:', params.value)
     console.log('点击的数据类型为:', params.data.type)
@@ -701,7 +755,7 @@ const initMessageChart = () => {
   })
 
   // 配置图表
-  echarts.init(deviceMessageCountChartRef.value).setOption({
+  getChartInstance(deviceMessageCountChartRef.value)?.setOption({
     tooltip: {
       trigger: 'axis',
       backgroundColor: 'rgba(255, 255, 255, 0.9)',
@@ -794,7 +848,7 @@ const initMessageChart = () => {
 }
 
 const chartContainer = ref(null)
-let chartInstance = null
+let chartInstance: ReturnType<typeof echarts.init> | null = null
 
 // 生成过去12个月份的标签 (格式: YYYY-MM)
 const generateMonthLabels = () => {
@@ -885,11 +939,9 @@ const initChart = async () => {
   }
 
   // 初始化图表
-  chartInstance = echarts.init(chartContainer.value)
+  chartInstance = getChartInstance(chartContainer.value)
+  if (!chartInstance) return
   chartInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleResize)
   handleResize()
 }
 
@@ -899,28 +951,62 @@ const handleResize = () => {
 }
 /** 初始化 */
 onMounted(async () => {
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
+  }
+  window.addEventListener('resize', updateScale)
+
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
   queryParams.deptId = useUserStore().getUser.deptId
   await getStats()
+  nextTick(resizeCharts)
   // await initChart()
   // await initCharts()
 })
 onUnmounted(() => {
-  chartInstance?.dispose()
-  window.removeEventListener('resize', handleResize)
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
+  chartInstances.forEach((chart) => chart.dispose())
+  chartInstances.clear()
 })
 </script>
 
 <style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+// 响应式优化
+@media (width <= 1200px) {
+  .stat-sub-value {
+    font-size: 24px;
+  }
+}
+
+@media (width <= 768px) {
+  .stat-card {
+    margin-bottom: 16px;
+  }
+
+  .chart-item {
+    padding: 8px;
+  }
+
+  .gauge-chart {
+    height: 140px;
+  }
+}
+
 // 筛选卡片样式
 .filter-card {
-  border-radius: 12px;
+  background: linear-gradient(to bottom, #fafafa, #fff);
   border: 1px solid #e5e7eb;
-  background: linear-gradient(to bottom, #fafafa, #ffffff);
+  border-radius: 12px;
   transition: all 0.3s ease;
 
   &:hover {
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+    box-shadow: 0 4px 12px rgb(0 0 0 / 5%);
   }
 
   :deep(.el-card__body) {
@@ -928,8 +1014,8 @@ onUnmounted(() => {
   }
 
   :deep(.el-form-item__label) {
-    color: #6b7280;
     font-weight: 500;
+    color: #6b7280;
   }
 }
 
@@ -941,35 +1027,35 @@ onUnmounted(() => {
   &:hover {
     background: linear-gradient(135deg, #475569 0%, #334155 100%);
     transform: translateY(-1px);
-    box-shadow: 0 4px 8px rgba(100, 116, 139, 0.3);
+    box-shadow: 0 4px 8px rgb(100 116 139 / 30%);
   }
 }
 
 .btn-default {
-  border-color: #d1d5db;
   color: #6b7280;
+  border-color: #d1d5db;
   transition: all 0.3s ease;
 
   &:hover {
-    border-color: #9ca3af;
     color: #4b5563;
     background: #f9fafb;
+    border-color: #9ca3af;
   }
 }
 
 // 统计卡片基础样式
 .stat-card {
-  border-radius: 12px;
+  position: relative;
+  overflow: hidden;
+  background: #fff;
   border: 1px solid #e5e7eb;
+  border-radius: 12px;
   transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-  overflow: hidden;
-  position: relative;
-  background: #ffffff;
 
   &:hover {
-    transform: translateY(-4px);
-    box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.1);
     border-color: #d1d5db;
+    transform: translateY(-4px);
+    box-shadow: 0 12px 24px -8px rgb(0 0 0 / 10%);
   }
 
   :deep(.el-card__body) {
@@ -1007,14 +1093,14 @@ onUnmounted(() => {
 }
 
 .stat-icon-wrapper {
+  display: flex;
   width: 48px;
   height: 48px;
   background: #0090ff;
   border-radius: 12px;
-  display: flex;
+  box-shadow: 0 4px 8px rgb(100 116 139 / 20%);
   align-items: center;
   justify-content: center;
-  box-shadow: 0 4px 8px rgba(100, 116, 139, 0.2);
 }
 
 .stat-icon {
@@ -1029,8 +1115,8 @@ onUnmounted(() => {
 }
 
 .stat-divider {
-  border-color: #e2e8f0;
   margin: 12px 0;
+  border-color: #e2e8f0;
 }
 
 .stat-body {
@@ -1044,19 +1130,19 @@ onUnmounted(() => {
   justify-content: space-between;
   align-items: center;
   padding: 8px 12px;
-  background: rgba(255, 255, 255, 0.6);
+  background: rgb(255 255 255 / 60%);
   border-radius: 8px;
   transition: all 0.2s ease;
 
   &:hover {
-    background: rgba(255, 255, 255, 0.9);
+    background: rgb(255 255 255 / 90%);
   }
 }
 
 .stat-sub-label {
   font-size: 13px;
-  color: #64748b;
   font-weight: 500;
+  color: #64748b;
 }
 
 .stat-sub-value {
@@ -1067,20 +1153,20 @@ onUnmounted(() => {
 
 // 图表卡片增强样式
 .chart-card-enhanced {
-  border-radius: 12px;
+  background: #fff;
   border: 1px solid #e5e7eb;
+  border-radius: 12px;
   transition: all 0.3s ease;
-  background: #ffffff;
 
   &:hover {
-    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
     border-color: #d1d5db;
+    box-shadow: 0 8px 24px rgb(0 0 0 / 8%);
   }
 
   :deep(.el-card__header) {
     padding: 16px 20px;
+    background: linear-gradient(to right, #fafafa, #fff);
     border-bottom: 1px solid #f1f5f9;
-    background: linear-gradient(to right, #fafafa, #ffffff);
   }
 
   :deep(.el-card__body) {
@@ -1104,30 +1190,30 @@ onUnmounted(() => {
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
 }
 
 .chart-title-dot-blue {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-green {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-purple {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-indigo {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title {
   font-size: 16px;
   font-weight: 600;
-  color: #334155;
   letter-spacing: 0.3px;
+  color: #334155;
 }
 
 .chart-grid {
@@ -1154,13 +1240,13 @@ onUnmounted(() => {
 
 .chart-label {
   display: flex;
-  align-items: center;
-  gap: 6px;
-  margin-top: 8px;
   padding: 6px 12px;
+  margin-top: 8px;
   background: #f1f5f9;
   border-radius: 20px;
   transition: all 0.2s ease;
+  align-items: center;
+  gap: 6px;
 
   .chart-item:hover & {
     background: #e2e8f0;
@@ -1177,61 +1263,323 @@ onUnmounted(() => {
 }
 
 .label-dot {
+  display: inline-block;
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  display: inline-block;
 }
 
 .label-dot-blue {
   background: #64748b;
-  box-shadow: 0 0 0 2px rgba(100, 116, 139, 0.2);
+  box-shadow: 0 0 0 2px rgb(100 116 139 / 20%);
 }
 
 .label-dot-amber {
   background: #d97706;
-  box-shadow: 0 0 0 2px rgba(217, 119, 6, 0.2);
+  box-shadow: 0 0 0 2px rgb(217 119 6 / 20%);
 }
 
 .label-dot-gray {
   background: #78716c;
-  box-shadow: 0 0 0 2px rgba(120, 113, 108, 0.2);
+  box-shadow: 0 0 0 2px rgb(120 113 108 / 20%);
 }
 
 .label-text {
   font-size: 13px;
-  color: #64748b;
   font-weight: 500;
+  color: #64748b;
 }
 
 .pie-chart-container {
-  height: 300px;
   width: 100%;
+  height: 300px;
 }
 
 .bar-chart-container {
+  width: 100%;
   height: 300px;
+}
+
+.kb-screen {
+  overflow: hidden;
+}
+
+.inspect-kb-content {
+  display: grid;
+  height: calc(100% - 52px * var(--kb-scale));
+  padding: calc(16px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
+  gap: calc(12px * var(--kb-scale));
+  grid-template-rows:
+    calc(74px * var(--kb-scale)) calc(174px * var(--kb-scale))
+    calc(300px * var(--kb-scale)) minmax(0, 1fr);
+}
+
+.inspect-kb-content > .el-row {
+  height: 100%;
+  margin-bottom: 0 !important;
+}
+
+.inspect-kb-content > .el-row > .el-col {
+  height: 100%;
+}
+
+.filter-card,
+.stat-card,
+.chart-card-enhanced {
+  height: 100%;
+  background: linear-gradient(180deg, rgb(235 243 255 / 76%) 0%, rgb(221 233 251 / 58%) 100%);
+  border: 1px solid rgb(255 255 255 / 68%) !important;
+  border-radius: calc(8px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 calc(10px * var(--kb-scale)) calc(24px * var(--kb-scale)) rgb(46 90 164 / 10%);
+  backdrop-filter: blur(12px);
+}
+
+.filter-card:hover,
+.stat-card:hover,
+.chart-card-enhanced:hover {
+  border-color: rgb(255 255 255 / 84%) !important;
+  transform: none;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 86%),
+    0 calc(12px * var(--kb-scale)) calc(26px * var(--kb-scale)) rgb(46 90 164 / 14%);
+}
+
+.filter-card :deep(.el-card__body) {
+  display: flex;
+  height: 100%;
+  padding: calc(12px * var(--kb-scale)) calc(18px * var(--kb-scale));
+  align-items: center;
+}
+
+.filter-card :deep(.el-form) {
+  display: flex;
   width: 100%;
+  margin-bottom: 0 !important;
+  align-items: center;
+  gap: calc(16px * var(--kb-scale));
 }
 
-// 响应式优化
-@media (max-width: 1200px) {
-  .stat-sub-value {
-    font-size: 24px;
-  }
+.filter-card :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
 }
 
-@media (max-width: 768px) {
-  .stat-card {
-    margin-bottom: 16px;
-  }
+.filter-card :deep(.el-form-item__label) {
+  height: calc(32px * var(--kb-scale));
+  padding-right: calc(10px * var(--kb-scale));
+  font-size: calc(14px * var(--kb-scale));
+  line-height: calc(32px * var(--kb-scale));
+  color: #31527e;
+}
 
-  .chart-item {
-    padding: 8px;
-  }
+.filter-card :deep(.el-tree-select) {
+  width: calc(220px * var(--kb-scale)) !important;
+}
 
-  .gauge-chart {
-    height: 140px;
-  }
+.filter-card :deep(.el-date-editor) {
+  width: calc(320px * var(--kb-scale)) !important;
+}
+
+.filter-card :deep(.el-input__wrapper),
+.filter-card :deep(.el-select__wrapper) {
+  min-height: calc(32px * var(--kb-scale));
+  padding: 0 calc(10px * var(--kb-scale));
+  background: rgb(255 255 255 / 72%);
+}
+
+.filter-card :deep(.el-range-input),
+.filter-card :deep(.el-range-separator),
+.filter-card :deep(.el-input__inner) {
+  font-size: calc(13px * var(--kb-scale));
+}
+
+.filter-card :deep(.el-button) {
+  height: calc(32px * var(--kb-scale));
+  padding: 0 calc(14px * var(--kb-scale));
+  margin-left: calc(8px * var(--kb-scale));
+  font-size: calc(13px * var(--kb-scale));
+}
+
+.stat-card {
+  overflow: hidden;
+}
+
+.stat-card :deep(.el-card__body) {
+  height: 100%;
+  padding: 0;
+}
+
+.stat-card-neutral-1,
+.stat-card-neutral-2,
+.stat-card-neutral-3,
+.stat-card-neutral-4 {
+  background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 42%) 100%);
+}
+
+.stat-content {
+  display: flex;
+  height: 100%;
+  padding: calc(16px * var(--kb-scale));
+  flex-direction: column;
+}
+
+.stat-header {
+  margin-bottom: calc(8px * var(--kb-scale));
+  gap: calc(10px * var(--kb-scale));
+}
+
+.stat-icon-wrapper {
+  width: calc(38px * var(--kb-scale));
+  height: calc(38px * var(--kb-scale));
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(235 244 255 / 92%) 100%);
+  border: 1px solid rgb(255 255 255 / 88%);
+  border-radius: calc(8px * var(--kb-scale));
+  box-shadow: 0 calc(5px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(57 111 194 / 12%);
+}
+
+.stat-icon {
+  font-size: calc(19px * var(--kb-scale));
+  color: #2474dd;
+}
+
+.stat-title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(20px * var(--kb-scale));
+  font-weight: normal;
+  letter-spacing: 0;
+  color: #03409b;
+}
+
+.stat-divider {
+  margin: 0 0 calc(8px * var(--kb-scale));
+  border-color: rgb(3 64 155 / 12%);
+}
+
+.stat-body {
+  display: grid;
+  min-height: 0;
+  flex: 1;
+  gap: calc(10px * var(--kb-scale));
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.stat-item {
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
+  background: rgb(255 255 255 / 38%);
+  border: 1px solid rgb(255 255 255 / 46%);
+  border-radius: calc(6px * var(--kb-scale));
+}
+
+.stat-sub-label {
+  font-size: calc(13px * var(--kb-scale));
+  color: #55708f;
+}
+
+.stat-sub-value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(28px * var(--kb-scale));
+  font-weight: normal;
+  color: #1762c8;
+}
+
+.stat-sub-value.text-amber-600 {
+  color: #e87b1e;
+}
+
+.chart-card-enhanced {
+  display: flex;
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.chart-card-enhanced :deep(.el-card__header) {
+  height: calc(42px * var(--kb-scale));
+  padding: 0 calc(16px * var(--kb-scale)) 0 calc(36px * var(--kb-scale));
+  background: transparent;
+  border-bottom: 1px solid rgb(3 64 155 / 12%);
+}
+
+.chart-card-enhanced :deep(.el-card__body) {
+  min-height: 0;
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
+  flex: 1;
+}
+
+.chart-header,
+.chart-title-wrapper {
+  height: 100%;
+}
+
+.chart-title-wrapper {
+  position: relative;
+}
+
+.chart-title-dot {
+  position: absolute;
+  left: calc(-22px * var(--kb-scale));
+  width: calc(4px * var(--kb-scale));
+  height: calc(18px * var(--kb-scale));
+  background: #03409b;
+  border-radius: calc(2px * var(--kb-scale));
+  box-shadow: none;
+}
+
+.chart-title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(22px * var(--kb-scale));
+  font-weight: normal;
+  letter-spacing: 0;
+  color: #03409b;
+}
+
+.chart-grid {
+  height: 100%;
+  min-height: 0;
+}
+
+.chart-item {
+  height: 100%;
+  padding: calc(4px * var(--kb-scale));
+  justify-content: center;
+}
+
+.chart-item:hover {
+  background: transparent;
+}
+
+.gauge-chart {
+  height: calc(190px * var(--kb-scale));
+}
+
+.chart-label {
+  min-height: calc(28px * var(--kb-scale));
+  padding: calc(4px * var(--kb-scale)) calc(10px * var(--kb-scale));
+  margin-top: 0;
+  background: rgb(255 255 255 / 52%);
+  border-radius: calc(5px * var(--kb-scale));
+  gap: calc(6px * var(--kb-scale));
+}
+
+.chart-label.clickable:hover {
+  background: rgb(255 255 255 / 78%);
+  transform: none;
+}
+
+.label-dot {
+  width: calc(7px * var(--kb-scale));
+  height: calc(7px * var(--kb-scale));
+}
+
+.label-text {
+  font-size: calc(13px * var(--kb-scale));
+  color: #466484;
+}
+
+.pie-chart-container,
+.bar-chart-container {
+  width: 100%;
+  height: 100%;
 }
 </style>