Преглед изворни кода

Merge branch 'feature/maintenance'

Zimo пре 1 недеља
родитељ
комит
26e07b847b

+ 2 - 0
src/components/ZmTable/index.vue

@@ -725,6 +725,8 @@ defineExpose({
   }
 
   .el-table__empty-block {
+    width: 100% !important;
+    min-width: 100%;
     min-height: var(--zm-table-empty-min-height);
     background: var(--zm-table-empty-bg);
   }

+ 409 - 173
src/views/pms/failure/IotFailureReportForm.vue

@@ -1,162 +1,268 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible" style="width: 68em">
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="1080px" class="failure-report-dialog">
+    <template #title>
+      <div class="dialog-header mb-6">
+        <div class="dialog-header__icon">
+          <Icon icon="ep:warning" :size="22" />
+        </div>
+        <div>
+          <div class="dialog-title">{{ dialogTitle }}</div>
+          <div class="dialog-subtitle">维护故障基础信息、处理状态与现场描述</div>
+        </div>
+      </div>
+    </template>
+
     <el-form
       ref="formRef"
+      v-loading="formLoading"
       :model="formData"
       :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-row >
-        <el-col :span="12">
-          <el-form-item :label="t('faultForm.failureType')" prop="failureType">
-            <el-select v-model="formData.failureType" :placeholder="t('faultForm.failureTypeHolder')" :disabled="disabled" clearable>
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.FAILURE_TYPE)"
-                :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('faultForm.device')" prop="deviceName">
-        <el-select
-          v-if="disabled"
-          :disabled="disabled"
-          v-model="formData.deviceName"
-          :placeholder="t('faultForm.choose')"
-          clearable
-          @clear="handleClear"
-        />
-        <el-select
-          v-if="!disabled"
-          v-model="formData.deviceName"
-          :placeholder="t('faultForm.choose')"
-          @click="openForm"
-          clearable
-          @clear="handleClear"
-        />
-      </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('faultForm.solve')" prop="ifDeal">
-            <el-select v-model="formData.ifDeal" :placeholder="t('faultForm.choose')" @change="dealChange" clearable :disabled="disabled">
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :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('faultForm.assist')" prop="needHelp" >
-            <el-select v-model="formData.needHelp" :placeholder="t('faultForm.choose')" @change="helpChange" clearable :disabled="disabled">
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :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('faultForm.faultySystem')" prop="failureSystem" :disabled="disabled">
-            <el-input v-model="formData.failureSystem" :placeholder="t('faultForm.faultySystemHolder')" :disabled="disabled" />
-          </el-form-item>
-        </el-col>
-      <el-col :span="12">
-      <el-form-item :label="t('faultForm.failureTime')" prop="failureTime" :disabled="disabled">
-        <el-date-picker
-          :disabled="disabled"
-          style="width: 150%"
-          v-model="formData.failureTime"
-          type="datetime"
-          value-format="x"
-          :placeholder="t('faultForm.failureTimeHolder')"
-        />
-      </el-form-item>
-        </el-col>
-        <el-col :span="12" v-if="formData.ifDeal">
-          <el-form-item :label="t('faultForm.dealTime')" prop="dealTime">
-            <el-date-picker
-              @change="endTimeBlur"
-              :disabled="disabled"
-              style="width: 150%"
-              v-model="formData.dealTime"
-              type="datetime"
-              value-format="x"
-              placeholder="选择故障解决时间"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('faultForm.shutDown')" prop="ifStop">
-            <el-select v-model="formData.ifStop" :placeholder="t('faultForm.choose')" clearable :disabled="disabled">
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :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('faultForm.picture')" prop="pic">
-            <UploadImg v-model="formData.pic" height="60px" :disabled="disabled" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12" v-if="!formData.ifDeal&&approvalShow">
-          <el-form-item :label="t('faultForm.approvalId')" prop="approvalId" :rules="!formData.ifDeal?approvalRule:[]">
-            <el-select multiple v-model="formData.approvalId" :placeholder="t('faultForm.approvalHolder')" filterable clearable :disabled="disabled">
-              <el-option
-                v-for="ap in approver"
-                :key="ap.id"
-                :label="ap.nickname"
-                :value="ap.id"
-              />
-            </el-select>
-          </el-form-item>
-        </el-col>
-        <el-col :span="24">
-          <el-form-item :label="t('faultForm.faultImpact')" prop="failureInfluence">
-            <el-input type="textarea" v-model="formData.failureInfluence" :placeholder="t('faultForm.faultImpactHolder')" :disabled="disabled" />
-          </el-form-item>
-        </el-col>
-      <el-col :span="24">
-      <el-form-item label="解决办法" v-if="formData.ifDeal" prop="solution" :rules="formData.ifDeal?solutionRule:[]">
-        <el-input v-model="formData.solution" type="textarea" placeholder="请输入解决办法" :disabled="disabled" />
-      </el-form-item>
-        </el-col>
-        <el-col :span="24">
-          <el-form-item :label="t('faultForm.faultDescription')" prop="description">
-            <el-input type="textarea" v-model="formData.description" :disabled="disabled" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="24">
-      <el-form-item :label="t('faultForm.remark')" prop="remark">
-        <el-input v-model="formData.remark" type="textarea" :placeholder="t('faultForm.rHolder')" :disabled="disabled" />
-      </el-form-item>
-          </el-col>
-      </el-row>
+      size="default"
+      label-width="110px"
+      class="failure-report-form"
+      :class="{ 'is-readonly': disabled }">
+      <section class="info-card">
+        <div class="info-card__header">
+          <div class="info-card__title">
+            <span class="info-card__marker"></span>
+            <span>基础信息</span>
+          </div>
+        </div>
+        <div class="info-card__body">
+          <el-row :gutter="16">
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.failureType')" prop="failureType">
+                <el-select
+                  v-model="formData.failureType"
+                  :placeholder="t('faultForm.failureTypeHolder')"
+                  :disabled="disabled"
+                  clearable
+                  class="form-control">
+                  <el-option
+                    v-for="dict in getStrDictOptions(DICT_TYPE.FAILURE_TYPE)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.device')" prop="deviceName">
+                <el-select
+                  v-if="disabled"
+                  v-model="formData.deviceName"
+                  :disabled="disabled"
+                  :placeholder="t('faultForm.choose')"
+                  clearable
+                  class="form-control"
+                  @clear="handleClear" />
+                <el-select
+                  v-else
+                  v-model="formData.deviceName"
+                  :placeholder="t('faultForm.choose')"
+                  clearable
+                  class="form-control"
+                  @click="openForm"
+                  @clear="handleClear" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.faultySystem')" prop="failureSystem">
+                <el-input
+                  v-model="formData.failureSystem"
+                  :placeholder="t('faultForm.faultySystemHolder')"
+                  :disabled="disabled"
+                  class="form-control" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.failureTime')" prop="failureTime">
+                <el-date-picker
+                  v-model="formData.failureTime"
+                  :disabled="disabled"
+                  type="datetime"
+                  value-format="x"
+                  :placeholder="t('faultForm.failureTimeHolder')"
+                  class="form-control" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.shutDown')" prop="ifStop">
+                <el-select
+                  v-model="formData.ifStop"
+                  :placeholder="t('faultForm.choose')"
+                  clearable
+                  :disabled="disabled"
+                  class="form-control">
+                  <el-option
+                    v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.picture')" prop="pic">
+                <UploadImg v-model="formData.pic" height="60px" :disabled="disabled" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </section>
+
+      <section class="info-card">
+        <div class="info-card__header">
+          <div class="info-card__title">
+            <span class="info-card__marker"></span>
+            <span>处理信息</span>
+          </div>
+        </div>
+        <div class="info-card__body">
+          <el-row :gutter="16">
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.solve')" prop="ifDeal">
+                <el-select
+                  v-model="formData.ifDeal"
+                  :placeholder="t('faultForm.choose')"
+                  clearable
+                  :disabled="disabled"
+                  class="form-control"
+                  @change="dealChange">
+                  <el-option
+                    v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.assist')" prop="needHelp">
+                <el-select
+                  v-model="formData.needHelp"
+                  :placeholder="t('faultForm.choose')"
+                  clearable
+                  :disabled="disabled"
+                  class="form-control"
+                  @change="helpChange">
+                  <el-option
+                    v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                    :key="dict.label"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col v-if="formData.ifDeal" :xs="24" :sm="12">
+              <el-form-item :label="t('faultForm.dealTime')" prop="dealTime">
+                <el-date-picker
+                  v-model="formData.dealTime"
+                  :disabled="disabled"
+                  type="datetime"
+                  value-format="x"
+                  placeholder="选择故障解决时间"
+                  class="form-control"
+                  @change="endTimeBlur" />
+              </el-form-item>
+            </el-col>
+            <el-col v-if="!formData.ifDeal && approvalShow" :xs="24" :sm="12">
+              <el-form-item
+                :label="t('faultForm.approvalId')"
+                prop="approvalId"
+                :rules="!formData.ifDeal ? approvalRule : []">
+                <el-select
+                  v-model="formData.approvalId"
+                  multiple
+                  :placeholder="t('faultForm.approvalHolder')"
+                  filterable
+                  clearable
+                  :disabled="disabled"
+                  class="form-control">
+                  <el-option
+                    v-for="ap in approver"
+                    :key="ap.id"
+                    :label="ap.nickname"
+                    :value="ap.id" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </section>
+
+      <section class="info-card">
+        <div class="info-card__header">
+          <div class="info-card__title">
+            <span class="info-card__marker"></span>
+            <span>故障描述</span>
+          </div>
+        </div>
+        <div class="info-card__body">
+          <el-row :gutter="16">
+            <el-col :span="24">
+              <el-form-item :label="t('faultForm.faultImpact')" prop="failureInfluence">
+                <el-input
+                  v-model="formData.failureInfluence"
+                  type="textarea"
+                  :rows="2"
+                  :placeholder="t('faultForm.faultImpactHolder')"
+                  :disabled="disabled" />
+              </el-form-item>
+            </el-col>
+            <el-col v-if="formData.ifDeal" :span="24">
+              <el-form-item
+                label="解决办法"
+                prop="solution"
+                :rules="formData.ifDeal ? solutionRule : []">
+                <el-input
+                  v-model="formData.solution"
+                  type="textarea"
+                  :rows="2"
+                  placeholder="请输入解决办法"
+                  :disabled="disabled" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item :label="t('faultForm.faultDescription')" prop="description">
+                <el-input
+                  v-model="formData.description"
+                  type="textarea"
+                  :rows="3"
+                  :disabled="disabled" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item :label="t('faultForm.remark')" prop="remark">
+                <el-input
+                  v-model="formData.remark"
+                  type="textarea"
+                  :rows="2"
+                  :placeholder="t('faultForm.rHolder')"
+                  :disabled="disabled" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </section>
     </el-form>
+
     <template #footer>
-      <el-button @click="submitForm" type="primary" v-if="!disabled" :disabled="formLoading">{{t('faultForm.ok')}}</el-button>
-      <el-button @click="dialogVisible = false">{{t('faultForm.cancel')}}</el-button>
+      <div class="dialog-footer">
+        <el-button @click="dialogVisible = false">{{ t('faultForm.cancel') }}</el-button>
+        <el-button v-if="!disabled" type="primary" :disabled="formLoading" @click="submitForm">
+          {{ t('faultForm.ok') }}
+        </el-button>
+      </div>
     </template>
     <DeviceList ref="deviceFormRef" @choose="deviceChoose" />
   </Dialog>
 </template>
 <script setup lang="ts">
 import { IotFailureReportApi, IotFailureReportVO } from '@/api/pms/failure'
-import {DICT_TYPE, getBoolDictOptions, getStrDictOptions} from "@/utils/dict";
-import DeviceList from "@/views/pms/failure/DeviceList.vue";
-import dayjs from "dayjs";
+import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
+import DeviceList from '@/views/pms/failure/DeviceList.vue'
+import dayjs from 'dayjs'
 import { IotDeviceApi } from '@/api/pms/device'
 
 /** 故障上报 表单 */
@@ -170,12 +276,11 @@ const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const approvalShow = ref(true)
-const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const approver = ref([])
 let company = ''
 const formData = ref({
   failureType: undefined,
-  approvalId:undefined,
+  approvalId: undefined,
   id: undefined,
   failureCode: undefined,
   failureName: undefined,
@@ -194,13 +299,12 @@ const formData = ref({
   needHelp: undefined,
   solution: undefined,
   remark: undefined,
-  deptId: undefined,
+  deptId: undefined
 })
 const dealChange = () => {
-  console.log(formData.value.ifDeal)
   formData.value.needHelp = !formData.value.ifDeal
-  if (!formData.value.ifDeal){
-    IotFailureReportApi.getApproval().then(res=>{
+  if (!formData.value.ifDeal) {
+    IotFailureReportApi.getApproval().then((res) => {
       approver.value = res
     })
   }
@@ -212,10 +316,10 @@ const deviceChoose = async (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
 
   company = await IotDeviceApi.getCompanyByDevice(row.id)
-  if ('rh'===company) {
+  if ('rh' === company) {
     approvalShow.value = false
   } else {
     approvalShow.value = true
@@ -227,14 +331,14 @@ const endTimeBlur = () => {
     message.error('故障解决时间不得早于故障时间')
     formData.value.dealTime = undefined
   }
-  const now = dayjs();
-  const target = dayjs(formData.value.dealTime);
+  const now = dayjs()
+  const target = dayjs(formData.value.dealTime)
   if (target.isAfter(now)) {
     message.error('故障解决时间不得晚于当前时间')
     formData.value.dealTime = undefined
   }
 }
-const handleClear = () =>{
+const handleClear = () => {
   formData.value.deviceId = undefined
   formData.value.deviceName = undefined
   formData.value.deptId = undefined
@@ -249,28 +353,24 @@ const formRules = ref({
   deviceName: [{ required: true, message: '设备不能为空', trigger: 'blur' }],
   ifDeal: [{ required: true, message: '是否解决不能为空', trigger: 'blur' }],
   needHelp: [{ required: true, message: '是否协助不能为空', trigger: 'blur' }],
-  failureTime:[{ required: true, message: '故障时间不能为空', trigger: 'blur' }],
-  failureSystem:[{ required: true, message: '故障系统不能为空', trigger: 'blur' }],
-  dealTime:[{ required: true, message: '故障解决时间不能为空', trigger: 'blur' }],
+  failureTime: [{ required: true, message: '故障时间不能为空', trigger: 'blur' }],
+  failureSystem: [{ required: true, message: '故障系统不能为空', trigger: 'blur' }],
+  dealTime: [{ required: true, message: '故障解决时间不能为空', trigger: 'blur' }]
   // solution:[{required: ()=> formData.value.ifDeal==='true', message: '解决办法不能为空', trigger: 'blur'}],
 })
-const solutionRule = [
-  { required: true, message: '解决办法不能为空', trigger: 'blur' }
-];
-const approvalRule = [
-  { required: true, message: '审批人不能为空', trigger: 'blur' }
-];
+const solutionRule = [{ required: true, message: '解决办法不能为空', trigger: 'blur' }]
+const approvalRule = [{ required: true, message: '审批人不能为空', trigger: 'blur' }]
 const formRef = ref() // 表单 Ref
 const disabled = ref(false)
 /** 打开弹窗 */
-const open = async (type: string, id?: number, disable?:boolean) => {
+const open = async (type: string, id?: number, disable?: boolean) => {
   disabled.value = disable
   dialogVisible.value = true
   dialogTitle.value = t('action.' + type)
   formType.value = type
   resetForm()
-  formData.value.deviceId = null;
-  formData.value.deviceName = null;
+  formData.value.deviceId = null
+  formData.value.deviceName = null
   // 修改时,设置数据
   if (id) {
     formLoading.value = true
@@ -293,9 +393,8 @@ const submitForm = async () => {
   try {
     // formData.value.pic = formData.value.pic.path;
     const data = formData.value as unknown as IotFailureReportVO
-    if (!formData.value.ifDeal){
-      if (company!=='rh')
-      formData.value.approvalId = formData.value.approvalId.join(',')
+    if (!formData.value.ifDeal) {
+      if (company !== 'rh') formData.value.approvalId = formData.value.approvalId.join(',')
     }
     if (formType.value === 'create') {
       await IotFailureReportApi.createIotFailureReport(data)
@@ -329,8 +428,145 @@ const resetForm = () => {
     ifDeal: undefined,
     needHelp: undefined,
     solution: undefined,
-    remark: undefined,
+    remark: undefined
   }
   formRef.value?.resetFields()
 }
 </script>
+
+<style lang="scss">
+.failure-report-dialog {
+  .el-dialog {
+    overflow: hidden;
+    border-radius: 8px;
+  }
+
+  .el-dialog__header {
+    min-height: 56px;
+    padding: 8px 18px;
+    margin-right: 0 !important;
+    background: var(--el-bg-color);
+    border-bottom: 1px solid var(--el-border-color-lighter);
+  }
+
+  .el-dialog__body {
+    padding: 14px 16px 0 !important;
+    background: var(--el-bg-color);
+  }
+
+  .el-dialog__footer {
+    padding: 10px 18px;
+    background: var(--el-bg-color);
+    border-top: 1px solid var(--el-border-color-lighter);
+  }
+}
+</style>
+
+<style scoped lang="scss">
+.dialog-header {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.dialog-header__icon {
+  display: flex;
+  width: 36px;
+  height: 36px;
+  color: var(--el-color-primary);
+  background: rgb(64 158 255 / 12%);
+  border: 1px solid rgb(64 158 255 / 18%);
+  border-radius: 8px;
+  align-items: center;
+  justify-content: center;
+}
+
+.dialog-title {
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 22px;
+  color: var(--el-text-color-primary);
+}
+
+.dialog-subtitle {
+  margin-top: 2px;
+  font-size: 12px;
+  line-height: 16px;
+  color: var(--el-text-color-secondary);
+}
+
+.failure-report-form {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-bottom: 16px;
+}
+
+.info-card {
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: 0 6px 18px rgb(31 45 61 / 4%);
+}
+
+.info-card__header {
+  display: flex;
+  min-height: 40px;
+  padding: 0 16px;
+  background: linear-gradient(90deg, rgb(64 158 255 / 8%) 0%, rgb(255 255 255 / 0%) 72%);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  align-items: center;
+}
+
+.info-card__title {
+  display: flex;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 600;
+  line-height: 22px;
+  color: var(--el-text-color-primary);
+  align-items: center;
+}
+
+.info-card__marker {
+  width: 4px;
+  height: 16px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.info-card__body {
+  padding: 12px 16px 0;
+}
+
+.form-control {
+  width: 100%;
+}
+
+.failure-report-form :deep(.el-form-item) {
+  margin-bottom: 18px;
+}
+
+.failure-report-form :deep(.el-form-item__label) {
+  font-weight: 600;
+  color: var(--el-text-color-regular);
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+@media (width <= 768px) {
+  .info-card__header {
+    min-height: auto;
+    padding: 10px 14px;
+  }
+
+  .info-card__body {
+    padding: 12px 14px 0;
+  }
+}
+</style>

+ 398 - 289
src/views/pms/failure/index.vue

@@ -1,256 +1,256 @@
 <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"
-      >
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="68px"
-        >
-          <!--          <el-form-item label="故障编码" prop="failureCode">-->
-          <!--            <el-input-->
-          <!--              v-model="queryParams.failureCode"-->
-          <!--              placeholder="请输入故障编码"-->
-          <!--              clearable-->
-          <!--              @keyup.enter="handleQuery"-->
-          <!--              class="!w-200px"-->
-          <!--            />-->
-          <!--          </el-form-item>-->
-          <el-form-item :label="t('fault.faultTitle')" label-width="70px" prop="failureName">
-            <el-input
-              v-model="queryParams.failureName"
-              :placeholder="t('fault.titleHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-200px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('fault.status')" label-width="40px" prop="status">
-            <el-select
-              v-model="queryParams.status"
-              :placeholder="t('fault.status')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_FAILURE_STATUS)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item :label="t('fault.shutDown')" v-show="ifShow" prop="ifStop">
-            <el-select
-              v-model="queryParams.ifStop"
-              :placeholder="t('fault.shutDown')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item :label="t('fault.failureTime')" v-show="ifShow" prop="failureTime">
-            <el-date-picker
-              v-model="queryParams.failureTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-220px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('fault.solve')" v-show="ifShow" prop="ifDeal">
-            <el-select
-              v-model="queryParams.ifDeal"
-              :placeholder="t('fault.solve')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item :label="t('fault.assist')" v-show="ifShow" prop="needHelp">
-            <el-select
-              v-model="queryParams.needHelp"
-              :placeholder="t('fault.assist')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="创建时间" v-show="ifShow" prop="createTime">
-            <el-date-picker
-              v-model="queryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-220px"
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('fault.moreSearch') }}</el-button
-            >
-            <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('fault.closeSearch') }}</el-button
-            >
-            <el-button @click="handleQuery"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('fault.search') }}</el-button
-            >
-            <el-button @click="resetQuery"
-              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('fault.reset') }}</el-button
-            >
-            <el-button type="primary" plain @click="openForm('create', undefined, false)">
-              <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-failure-report:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="failure-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"
+      :dept-id="deptId"
+      v-model="queryParams.deptId"
+      :init-select="false"
+      :show-title="false"
+      request-api="getSimpleDeptList"
+      class="failure-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="68px"
+      class="failure-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('fault.faultTitle')" label-width="70px" prop="failureName">
+          <el-input
+            v-model="queryParams.failureName"
+            :placeholder="t('fault.titleHolder')"
+            clearable
+            class="query-control"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item :label="t('fault.status')" label-width="40px" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            :placeholder="t('fault.status')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.PMS_FAILURE_STATUS)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-show="ifShow" :label="t('fault.shutDown')" prop="ifStop">
+          <el-select
+            v-model="queryParams.ifStop"
+            :placeholder="t('fault.shutDown')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-show="ifShow" :label="t('fault.failureTime')" prop="failureTime">
+          <el-date-picker
+            v-model="queryParams.failureTime"
+            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="query-control query-control--date" />
+        </el-form-item>
+        <el-form-item v-show="ifShow" :label="t('fault.solve')" prop="ifDeal">
+          <el-select
+            v-model="queryParams.ifDeal"
+            :placeholder="t('fault.solve')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-show="ifShow" :label="t('fault.assist')" prop="needHelp">
+          <el-select
+            v-model="queryParams.needHelp"
+            :placeholder="t('fault.assist')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-show="ifShow" 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="query-control query-control--date" />
+        </el-form-item>
       </div>
 
-      <!-- 列表 -->
-      <ContentWrap style="border: none; margin-top: 20px">
-        <zm-table height="calc(85vh - 140px)" :loading="loading" :data="list" show-border>
-          <zm-table-column :label="t('fault.serial')" width="70" align="center">
-            <template #default="scope">
-              {{ scope.$index + 1 }}
-            </template>
-          </zm-table-column>
-          <!--        <el-table-column label="故障编码" align="center" prop="failureCode" />-->
-          <zm-table-column :label="t('fault.faultTitle')" align="center" prop="failureName" />
-          <zm-table-column :label="t('iotMaintain.deviceCode')" align="center" prop="deviceCode" />
-          <zm-table-column :label="t('fault.deviceName')" align="center" prop="deviceName" />
-          <zm-table-column :label="t('fault.status')" align="center" prop="status">
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.PMS_FAILURE_STATUS" :value="scope.row.status" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('faultForm.failureType')"
-            align="center"
-            prop="failureType"
-            min-width="90px"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.FAILURE_TYPE" :value="scope.row.failureType" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('fault.approvalStatus')"
-            align="center"
-            prop="auditStatus"
-            min-width="90px"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
-            </template>
-          </zm-table-column>
-          <zm-table-column :label="t('fault.solve')" align="center" prop="ifDeal">
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.ifDeal" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('fault.failureTime')"
-            align="center"
-            prop="failureTime"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column :label="t('fault.solveTime')" align="center" prop="dealHour">
-            <template #default="scope">
-              {{ scope.row.dealHour && scope.row.dealHour > 0 ? scope.row.dealHour + 'H' : '' }}
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('table.createTime')"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column
-            :label="t('fault.operation')"
-            align="center"
-            min-width="120px"
-            fixed="right"
-            action
-          >
-            <template #default="scope">
-              <el-button
-                link
-                type="primary"
-                @click="openForm('detail', scope.row.id, true)"
-                v-hasPermi="['rq:iot-failure-report:query']"
-              >
-                {{ t('fault.view') }}
-              </el-button>
-              <el-button
-                link
-                v-if="scope.row.auditStatus === '30'"
-                type="primary"
-                @click="openForm('update', scope.row.id, false)"
-                v-hasPermi="['rq:iot-failure-report:update']"
-              >
-                {{ t('fault.edit') }}
-              </el-button>
-              <el-button
-                link
-                v-if="scope.row.auditStatus === '30'"
-                type="danger"
-                @click="handleDelete(scope.row.id)"
-                v-hasPermi="['rq:iot-failure-report:delete']"
-              >
-                {{ t('fault.del') }}
-              </el-button>
-            </template>
-          </zm-table-column>
-        </zm-table>
-        <!-- 分页 -->
-        <Pagination
+      <el-form-item class="query-actions">
+        <el-button v-if="!ifShow" type="warning" @click="moreQuery(true)">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('fault.moreSearch') }}
+        </el-button>
+        <el-button v-else type="danger" @click="moreQuery(false)">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('fault.closeSearch') }}
+        </el-button>
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('fault.search') }}
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" /> {{ t('fault.reset') }}
+        </el-button>
+        <el-button type="primary" plain @click="openForm('create', undefined, false)">
+          <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-failure-report:export']">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="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>
+              <ZmTableColumn :label="t('fault.serial')" width="70" align="center" fixed="left">
+                <template #default="scope">
+                  {{ scope.$index + 1 }}
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('fault.faultTitle')"
+                align="center"
+                prop="failureName"
+                fixed="left" />
+              <ZmTableColumn
+                :label="t('iotMaintain.deviceCode')"
+                align="center"
+                prop="deviceCode" />
+              <ZmTableColumn :label="t('fault.deviceName')" align="center" prop="deviceName" />
+              <ZmTableColumn :label="t('fault.status')" align="center" prop="status">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.PMS_FAILURE_STATUS" :value="scope.row.status" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('faultForm.failureType')"
+                align="center"
+                prop="failureType"
+                min-width="90px">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.FAILURE_TYPE" :value="scope.row.failureType" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('fault.approvalStatus')"
+                align="center"
+                prop="auditStatus"
+                min-width="90px">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('fault.solve')" align="center" prop="ifDeal">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.ifDeal" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('fault.failureTime')"
+                align="center"
+                prop="failureTime"
+                :formatter="dateFormatter"
+                min-width="180px" />
+              <ZmTableColumn :label="t('fault.solveTime')" align="center" prop="dealHour">
+                <template #default="scope">
+                  {{ scope.row.dealHour && scope.row.dealHour > 0 ? scope.row.dealHour + 'H' : '' }}
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('table.createTime')"
+                align="center"
+                prop="createTime"
+                :formatter="dateFormatter"
+                min-width="180px" />
+              <ZmTableColumn
+                :label="t('fault.operation')"
+                align="center"
+                min-width="120px"
+                fixed="right"
+                action>
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="openForm('detail', scope.row.id, true)"
+                    v-hasPermi="['rq:iot-failure-report:query']">
+                    {{ t('fault.view') }}
+                  </el-button>
+                  <el-button
+                    link
+                    v-if="scope.row.auditStatus === '30'"
+                    type="primary"
+                    @click="openForm('update', scope.row.id, false)"
+                    v-hasPermi="['rq:iot-failure-report:update']">
+                    {{ t('fault.edit') }}
+                  </el-button>
+                  <el-button
+                    link
+                    v-if="scope.row.auditStatus === '30'"
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                    v-hasPermi="['rq:iot-failure-report:delete']">
+                    {{ t('fault.del') }}
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </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>
+
   <!-- 表单弹窗:添加/修改 -->
   <IotFailureReportForm ref="formRef" @success="getList" />
 </template>
@@ -261,10 +261,11 @@ import download from '@/utils/download'
 import { IotFailureReportApi, IotFailureReportVO } from '@/api/pms/failure'
 import IotFailureReportForm from './IotFailureReportForm.vue'
 import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
+import DeptTreeSelect from '@/components/DeptTreeSelect/index.vue'
+import { useUserStore } from '@/store/modules/user'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotFailureReportVO>()
 
 /** 故障上报 列表 */
 defineOptions({ name: 'IotFailureReport' })
@@ -275,7 +276,8 @@ const ifShow = ref(false)
 const loading = ref(true) // 列表的加载中
 const list = ref<IotFailureReportVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
-let isLeftContentCollapsed = ref(false)
+const rootDeptId = 156
+const deptId = useUserStore().getUser.deptId || rootDeptId
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -298,29 +300,9 @@ const queryParams = reactive({
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
-const moreQuery = (show) => {
+const moreQuery = (show: boolean) => {
   ifShow.value = show
 }
-const openWeb = (url) => {
-  window.open(
-    'http://1.94.244.160:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(url))
-  )
-}
-const handleCommand = (command: string, row: IotFailureReportVO) => {
-  switch (command) {
-    case 'handleDelete':
-      handleDelete(row.id)
-      break
-    case 'handleUpdate':
-      openForm('update', row.id)
-      break
-    case 'handleSubmit':
-      submitProcess(row.id)
-      break
-    default:
-      break
-  }
-}
 
 /** 查询列表 */
 const getList = async () => {
@@ -334,8 +316,9 @@ const getList = async () => {
   }
 }
 /** 处理部门被点击 */
-const handleDeptNodeClick = async (row) => {
+const handleDeptNodeClick = async (row: Tree) => {
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 /** 搜索按钮操作 */
@@ -346,10 +329,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   handleQuery()
 }
 
+const handleSizeChange = (val: number) => {
+  queryParams.pageSize = val
+  handleQuery()
+}
+
+const handleCurrentChange = (val: number) => {
+  queryParams.pageNo = val
+  getList()
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number, disable?: boolean) => {
@@ -388,18 +381,134 @@ const handleExport = async () => {
 onMounted(() => {
   getList()
 })
-const submitProcess = async (row: IotFailureReportVO) => {
-  try {
-    // 提交审核的二次确认
-    await message.confirm(`您确定提交审核吗?`)
-    // 提交审核
-    loading.value = true
-    await IotFailureReportApi.submitForApproval(row).then((res) => {
-      loading.value = false
-    })
-    message.success('提交审核成功!')
-    // 刷新列表
-    await getList()
-  } catch {}
-}
 </script>
+
+<style scoped>
+.failure-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;
+  gap: 12px 24px;
+  min-width: 0;
+}
+
+.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: 180px;
+}
+
+.query-control--small {
+  width: 140px;
+}
+
+.query-control--date {
+  width: 220px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+@media (width >= 2200px) {
+  .failure-query,
+  .query-row {
+    flex-wrap: nowrap;
+  }
+}
+
+@media (width <= 1500px) {
+  .failure-query {
+    gap: 12px 18px;
+  }
+
+  .query-row {
+    gap: 12px 18px;
+  }
+
+  .query-control {
+    width: 168px;
+  }
+
+  .query-control--small {
+    width: 132px;
+  }
+
+  .query-control--date {
+    width: 210px;
+  }
+}
+
+@media (width <= 1200px) {
+  .failure-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(.failure-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+
+  .query-actions {
+    width: 100%;
+  }
+}
+
+@media (width <= 768px) {
+  .failure-query {
+    padding: 12px;
+  }
+
+  .query-row,
+  .query-row :deep(.el-form-item),
+  .query-actions {
+    width: 100%;
+  }
+
+  .query-control,
+  .query-control--small,
+  .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%;
+    margin-left: 0;
+  }
+}
+</style>

+ 189 - 45
src/views/pms/information/IotInformationDbForm.vue

@@ -1,63 +1,100 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
+  <Dialog
+    v-model="dialogVisible"
+    :title="dialogTitle"
+    width="760px"
+    :fullscreen="false"
+    class="information-form-dialog">
+    <template #title>
+      <div class="dialog-title mb-4">
+        <div class="dialog-title__icon">
+          <Icon icon="ep:notebook" />
+        </div>
+        <div>
+          <div class="dialog-title__text">{{ dialogTitle }}</div>
+          <div class="dialog-title__subtitle">维护设备故障现象、影响及解决方案</div>
+        </div>
+      </div>
+    </template>
+
     <el-form
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-row>
-        <el-col :span="12">
-          <el-form-item :label="t('info.deviceClass')" prop="deviceType" >
-            <el-tree-select
-              v-model="formData.deviceType"
-              :data="productClassifyList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              :placeholder="t('deviceForm.category')"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-        <el-form-item :label="t('faultForm.faultImpact')" prop="failureInfluence">
-          <el-input v-model="formData.failureInfluence" :placeholder="t('faultForm.faultImpactHolder')" />
-        </el-form-item>
+      size="default"
+      label-width="92px"
+      class="information-form"
+      v-loading="formLoading">
+      <section class="form-section">
+        <div class="section-heading"><span></span>基础信息</div>
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="12">
+            <el-form-item :label="t('info.deviceClass')" prop="deviceType">
+              <el-tree-select
+                v-model="formData.deviceType"
+                :data="productClassifyList"
+                :props="defaultProps"
+                check-strictly
+                node-key="id"
+                :placeholder="t('deviceForm.category')"
+                filterable
+                clearable />
+            </el-form-item>
           </el-col>
-        <el-col :span="12">
-        <el-form-item :label="t('faultForm.faultySystem')" prop="failureSystem">
-          <el-input v-model="formData.failureSystem" :placeholder="t('faultForm.faultySystemHolder')" />
-        </el-form-item>
+          <el-col :xs="24" :sm="12">
+            <el-form-item :label="t('faultForm.faultImpact')" prop="failureInfluence">
+              <el-input
+                v-model="formData.failureInfluence"
+                :placeholder="t('faultForm.faultImpactHolder')" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12">
+            <el-form-item :label="t('faultForm.faultySystem')" prop="failureSystem">
+              <el-input
+                v-model="formData.failureSystem"
+                :placeholder="t('faultForm.faultySystemHolder')" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12">
+            <el-form-item :label="t('iotMaintain.solution')" prop="solutions">
+              <el-input v-model="formData.solutions" :placeholder="t('iotMaintain.soHolder')" />
+            </el-form-item>
           </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('iotMaintain.solution')" prop="solutions">
-            <el-input v-model="formData.solutions" :placeholder="t('iotMaintain.soHolder')" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="24">
+        </el-row>
+      </section>
+
+      <section class="form-section">
+        <div class="section-heading"><span></span>补充说明</div>
         <el-form-item :label="t('info.description')" prop="description">
-          <el-input v-model="formData.description" type="textarea" height="150px" />
+          <el-input v-model="formData.description" type="textarea" :rows="3" resize="none" />
         </el-form-item>
-          </el-col>
-        <el-col :span="24">
         <el-form-item :label="t('iotMaintain.remark')" prop="remark">
-          <el-input v-model="formData.remark" type="textarea" :placeholder="t('iotMaintain.remarkHolder')" />
+          <el-input
+            v-model="formData.remark"
+            type="textarea"
+            :rows="2"
+            resize="none"
+            :placeholder="t('iotMaintain.remarkHolder')" />
         </el-form-item>
-          </el-col>
-      </el-row>
+      </section>
     </el-form>
+
     <template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('iotMaintain.ok') }}</el-button>
-      <el-button @click="dialogVisible = false">{{ t('iotMaintain.cancel') }}</el-button>
+      <div class="dialog-footer">
+        <el-button size="default" @click="dialogVisible = false">
+          {{ t('iotMaintain.cancel') }}
+        </el-button>
+        <el-button size="default" type="primary" :loading="formLoading" @click="submitForm">
+          {{ t('iotMaintain.ok') }}
+        </el-button>
+      </div>
     </template>
   </Dialog>
 </template>
 <script setup lang="ts">
 import { IotInformationDbApi, IotInformationDbVO } from '@/api/pms/information'
-import {defaultProps, handleTree} from "@/utils/tree";
-import * as ProductClassifyApi from "@/api/pms/productclassify";
-import {DeviceAttrModelApi} from "@/api/pms/deviceattrmodel";
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as ProductClassifyApi from '@/api/pms/productclassify'
 
 /** 故障知识库 表单 */
 defineOptions({ name: 'IotInformationDbForm' })
@@ -79,12 +116,12 @@ const formData = ref({
   solutions: undefined,
   remark: undefined,
   processInstanceId: undefined,
-  auditStatus: undefined,
+  auditStatus: undefined
 })
 const formRules = reactive({
   deviceType: [{ required: true, message: t('info.classHolder1'), trigger: 'change' }],
   failureInfluence: [{ required: true, message: t('info.faultyImpact'), trigger: 'change' }],
-  solutions: [{ required: true, message: t('info.solution'), trigger: 'change' }],
+  solutions: [{ required: true, message: t('info.solution'), trigger: 'change' }]
 })
 const formRef = ref() // 表单 Ref
 
@@ -144,8 +181,115 @@ const resetForm = () => {
     solutions: undefined,
     remark: undefined,
     processInstanceId: undefined,
-    auditStatus: undefined,
+    auditStatus: 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__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);
+}
+
+.information-form {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+}
+
+.form-section {
+  padding: 0 16px 2px;
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+}
+
+.section-heading {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  padding: 0 16px;
+  margin: 0 -16px 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;
+}
+
+.information-form :deep(.el-select),
+.information-form :deep(.el-tree-select) {
+  width: 100%;
+}
+
+.information-form :deep(.el-form-item) {
+  margin-bottom: 18px;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+}
+
+.dialog-footer :deep(.el-button) {
+  margin-left: 0;
+}
+
+@media (width <= 768px) {
+  .dialog-title__subtitle {
+    display: none;
+  }
+
+  .form-section {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+
+  .section-heading {
+    padding-right: 12px;
+    padding-left: 12px;
+    margin-right: -12px;
+    margin-left: -12px;
+  }
+}
+</style>

+ 220 - 113
src/views/pms/information/index.vue

@@ -1,133 +1,135 @@
 <template>
   <div
-    style="border: none; background: #fff; display: flex; align-items: center"
-    class="py-2 rounded-sm px-2"
-  >
-    <!-- 搜索工作栏 -->
+    class="information-page grid grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
     <el-form
-      class="-mb-15px"
-      :model="queryParams"
       ref="queryFormRef"
-      :inline="true"
+      :model="queryParams"
+      size="default"
       label-width="68px"
-    >
-      <el-form-item :label="t('deviceForm.category')" prop="deviceType" style="width: 15vw">
-        <el-tree-select
-          v-model="queryParams.deviceType"
-          :data="productClassifyList"
-          :props="defaultProps"
-          check-strictly
-          node-key="id"
-          :placeholder="t('deviceForm.categoryHolder')"
-          filterable
-        />
-      </el-form-item>
-      <el-form-item :label="t('info.faultySystem')" prop="failureSystem">
-        <el-input
-          v-model="queryParams.failureSystem"
-          :placeholder="t('info.faultySystemHolder')"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item :label="t('info.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-220px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery"
-          ><Icon icon="ep:search" class="mr-5px" /> {{ t('info.search') }}</el-button
-        >
-        <el-button @click="resetQuery"
-          ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('info.reset') }}</el-button
-        >
+      class="information-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="deviceType">
+          <el-tree-select
+            v-model="queryParams.deviceType"
+            :data="productClassifyList"
+            :props="defaultProps"
+            check-strictly
+            node-key="id"
+            :placeholder="t('deviceForm.categoryHolder')"
+            filterable
+            clearable
+            class="query-control query-control--category" />
+        </el-form-item>
+        <el-form-item :label="t('info.faultySystem')" prop="failureSystem">
+          <el-input
+            v-model="queryParams.failureSystem"
+            :placeholder="t('info.faultySystemHolder')"
+            clearable
+            class="query-control"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item :label="t('info.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 size="default" type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />{{ t('info.search') }}
+        </el-button>
+        <el-button size="default" @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />{{ t('info.reset') }}
+        </el-button>
         <el-button
+          size="default"
           type="primary"
           plain
           @click="openForm('create')"
-          v-hasPermi="['rq:iot-information-db:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" />
-          {{ t('info.add') }}
+          v-hasPermi="['rq:iot-information-db:create']">
+          <Icon icon="ep:plus" class="mr-5px" />{{ t('info.add') }}
         </el-button>
         <el-button
+          size="default"
           type="success"
           plain
           @click="handleExport"
           :loading="exportLoading"
-          v-hasPermi="['rq:iot-information-db:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          v-hasPermi="['rq:iot-information-db:export']">
+          <Icon icon="ep:download" class="mr-5px" />导出
         </el-button>
       </el-form-item>
     </el-form>
-  </div>
 
-  <!-- 列表 -->
-  <ContentWrap style="border: none; margin-top: 20px">
-    <zm-table :loading="loading" :data="list" height="70vh">
-      <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('info.deviceClass')" align="center" prop="className" />
-      <zm-table-column :label="t('faultForm.faultImpact')" align="center" prop="failureInfluence" />
-      <zm-table-column :label="t('iotMaintain.faultySystem')" align="center" prop="failureSystem" />
-      <zm-table-column :label="t('info.description')" align="center" prop="description" />
-      <zm-table-column :label="t('iotMaintain.solution')" align="center" prop="solutions" />
-      <zm-table-column :label="t('iotMaintain.remark')" align="center" prop="remark" />
-      <zm-table-column
-        :label="t('info.createTime')"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <!--      <zm-table-column label="审核状态" align="center" prop="auditStatus" />-->
-      <zm-table-column
-        :label="t('iotMaintain.operation')"
-        align="center"
-        min-width="120px"
-        fixed="right"
-        action
-      >
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['rq:iot-information-db:update']"
-          >
-            {{ t('info.edit') }}
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['rq:iot-information-db:delete']"
-          >
-            {{ t('info.delete') }}
-          </el-button>
-        </template>
-      </zm-table-column>
-    </zm-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </ContentWrap>
+    <div class="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>
+              <ZmTableColumn type="index" :label="t('iotDevice.serial')" width="70" fixed="left" />
+              <ZmTableColumn :label="t('info.deviceClass')" prop="className" fixed="left" />
+              <ZmTableColumn :label="t('faultForm.faultImpact')" prop="failureInfluence" />
+              <ZmTableColumn :label="t('iotMaintain.faultySystem')" prop="failureSystem" />
+              <ZmTableColumn :label="t('info.description')" prop="description" />
+              <ZmTableColumn :label="t('iotMaintain.solution')" prop="solutions" />
+              <ZmTableColumn :label="t('iotMaintain.remark')" prop="remark" />
+              <ZmTableColumn
+                :label="t('info.createTime')"
+                prop="createTime"
+                :formatter="dateFormatter"
+                width="180" />
+              <ZmTableColumn :label="t('iotMaintain.operation')" width="120" fixed="right" action>
+                <template #default="scope">
+                  <el-button
+                    size="default"
+                    link
+                    type="primary"
+                    @click="openForm('update', scope.row.id)"
+                    v-hasPermi="['rq:iot-information-db:update']">
+                    {{ t('info.edit') }}
+                  </el-button>
+                  <el-button
+                    size="default"
+                    link
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                    v-hasPermi="['rq:iot-information-db:delete']">
+                    {{ t('info.delete') }}
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </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"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange" />
+      </div>
+    </div>
+  </div>
 
   <!-- 表单弹窗:添加/修改 -->
   <IotInformationDbForm ref="formRef" style="width: 60vw" @success="getList" />
@@ -142,7 +144,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
 import * as ProductClassifyApi from '@/api/pms/productclassify'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotInformationDbVO>()
 
 /** 故障知识库 列表 */
 defineOptions({ name: 'IotInformationDb' })
@@ -190,10 +192,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+const handleSizeChange = (val: number) => {
+  queryParams.pageSize = val
   handleQuery()
 }
 
+const handleCurrentChange = (val: number) => {
+  queryParams.pageNo = val
+  getList()
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
@@ -236,3 +248,98 @@ onMounted(async () => {
   await getList()
 })
 </script>
+
+<style scoped>
+.information-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;
+  gap: 12px 24px;
+  min-width: 0;
+}
+
+.query-row :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+.query-actions {
+  flex: 0 0 auto;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.query-control {
+  width: 200px;
+}
+
+.query-control--category {
+  width: 220px;
+}
+
+.query-control--date {
+  width: 240px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+@media (width <= 1200px) {
+  .information-page {
+    grid-template-rows: auto minmax(480px, 1fr);
+    height: auto;
+    min-height: calc(
+      100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+    );
+  }
+
+  .query-actions {
+    width: 100%;
+  }
+}
+
+@media (width <= 768px) {
+  .information-query {
+    padding: 12px;
+  }
+
+  .query-row,
+  .query-row :deep(.el-form-item),
+  .query-actions {
+    width: 100%;
+  }
+
+  .query-control,
+  .query-control--category,
+  .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%;
+  }
+}
+</style>

+ 250 - 115
src/views/pms/iotmainworkorder/SelectedMaterialDrawer.vue

@@ -1,156 +1,291 @@
 <template>
   <el-drawer
-    :title="t('bomList.materialDetail')"
     :append-to-body="true"
     :model-value="modelValue"
-    @update:model-value="$emit('update:modelValue', $event)"
     :show-close="false"
     direction="rtl"
     :size="computedSize"
-    :before-close="handleClose"
-  >
-    <template v-if="nodeId">
-      <div v-loading="loading" style="height: 100%">
-        <el-table :data="filteredMaterials" style="width: 100%">
-          <el-table-column prop="bomNodeId" :label="t('bomList.bomNode')" width="180" v-if="false"/>
-          <el-table-column prop="factory" :label="t('workOrderMaterial.factory')" width="180" v-if="!hideExtraColumns"/>
-          <el-table-column prop="costCenter" :label="t('workOrderMaterial.costCenter')" width="180" v-if="!hideExtraColumns"/>
-          <el-table-column prop="projectDepartment" :label="t('workOrderMaterial.storageLocation')"  width="180" v-if="!hideExtraColumns"/>
-          <el-table-column prop="materialName" :label="t('workOrderMaterial.materialName')" width="180" />
-          <el-table-column prop="materialCode" :label="t('workOrderMaterial.materialCode')" width="180" />
-          <el-table-column prop="unit" :label="t('workOrderMaterial.unit')" width="180" />
-          <el-table-column prop="unitPrice" :label="t('workOrderMaterial.unitPrice')" width="180" />
-          <el-table-column prop="quantity" :label="t('workOrderMaterial.ConsumptionQuantity')" width="180" />
-          <el-table-column prop="materialSource" :label="t('bomList.type')" width="180" />
-          <el-table-column :label="t('workplace.operation')" align="right" v-if="!props.detail">
-            <template #default="scope">
-              <el-button
-                size="small"
-                type="danger"
-                @click="handleDelete(scope.row)"
-              >{{ t('form.delete') }}</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-        <!-- 分页
-        <Pagination
-          :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="loadMaterials(props.nodeId)"
-        />
-        -->
+    class="selected-material-drawer"
+    @update:model-value="emit('update:modelValue', $event)">
+    <template #header>
+      <div class="drawer-header">
+        <div class="drawer-header__main">
+          <div class="drawer-header__icon">
+            <Icon icon="ep:goods" :size="22" />
+          </div>
+          <div>
+            <div class="drawer-title">{{ t('bomList.materialDetail') }}</div>
+            <div class="drawer-subtitle">查看当前维修项目关联的物料明细</div>
+          </div>
+        </div>
+        <el-button circle @click="handleClose">
+          <Icon icon="ep:close" />
+        </el-button>
       </div>
     </template>
 
+    <div class="drawer-body">
+      <section class="material-section">
+        <div class="section-header">
+          <div class="section-title">
+            <span class="section-marker"></span>
+            <span>关联物料</span>
+          </div>
+          <span class="material-total">共 {{ filteredMaterials.length }} 条</span>
+        </div>
+
+        <div class="table-container">
+          <ZmTable
+            :data="filteredMaterials"
+            :loading="loading"
+            :show-border="true"
+            settings-cache-key="pms-maintain-selected-material">
+            <ZmTableColumn
+              v-if="!hideExtraColumns"
+              prop="factory"
+              :label="t('workOrderMaterial.factory')"
+              min-width="140" />
+            <ZmTableColumn
+              v-if="!hideExtraColumns"
+              prop="costCenter"
+              :label="t('workOrderMaterial.costCenter')"
+              min-width="150" />
+            <ZmTableColumn
+              v-if="!hideExtraColumns"
+              prop="projectDepartment"
+              :label="t('workOrderMaterial.storageLocation')"
+              min-width="150" />
+            <ZmTableColumn
+              prop="materialName"
+              :label="t('workOrderMaterial.materialName')"
+              min-width="180" />
+            <ZmTableColumn
+              prop="materialCode"
+              :label="t('workOrderMaterial.materialCode')"
+              min-width="150" />
+            <ZmTableColumn prop="unit" :label="t('workOrderMaterial.unit')" width="100" />
+            <ZmTableColumn
+              prop="unitPrice"
+              :label="t('workOrderMaterial.unitPrice')"
+              min-width="120" />
+            <ZmTableColumn
+              prop="quantity"
+              :label="t('workOrderMaterial.ConsumptionQuantity')"
+              min-width="130" />
+            <ZmTableColumn prop="materialSource" :label="t('bomList.type')" min-width="120" />
+            <ZmTableColumn
+              v-if="!detail"
+              :label="t('workplace.operation')"
+              width="100"
+              fixed="right"
+              action>
+              <template #default="{ row }">
+                <el-button link type="danger" @click="handleDelete(row)">
+                  <Icon icon="ep:delete" class="mr-4px" />{{ t('form.delete') }}
+                </el-button>
+              </template>
+            </ZmTableColumn>
+          </ZmTable>
+        </div>
+      </section>
+    </div>
+
+    <template #footer>
+      <div class="drawer-footer">
+        <el-button @click="handleClose">关闭</el-button>
+      </div>
+    </template>
   </el-drawer>
 </template>
+
 <script setup lang="ts">
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 
-import { ref, watch, defineOptions, defineEmits } from 'vue'
-import { ElMessage } from 'element-plus'
-import * as PmsMaterialApi from '@/api/pms/material'
-const drawerVisible = ref<boolean>(false)
-const emit = defineEmits(['update:modelValue', 'add', 'delete'])
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
+interface MaterialRow {
+  id?: number
+  bomNodeId?: number | string
+  factory?: string
+  costCenter?: string
+  projectDepartment?: string
+  materialName?: string
+  materialCode?: string
+  unit?: string
+  unitPrice?: number
+  quantity?: number
+  materialSource?: string
+}
 
-defineOptions({
-  name: 'SelectedMaterialDrawer'
-})
+defineOptions({ name: 'SelectedMaterialDrawer' })
 
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  createTime: [],
-  bomId: '',
-  name: '',
-  code: ''
-})
+const props = withDefaults(
+  defineProps<{
+    modelValue: boolean
+    nodeId?: number | string
+    detail?: boolean
+    materials?: MaterialRow[]
+    hideExtraColumns?: boolean
+  }>(),
+  {
+    detail: false,
+    materials: () => [],
+    hideExtraColumns: false
+  }
+)
 
-const windowWidth = ref(window.innerWidth)
-// 动态计算百分比
-const computedSize = computed(() => {
-  return windowWidth.value > 1200 ? '60%' : '80%'
-})
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: boolean): void
+  (e: 'add', row: MaterialRow): void
+  (e: 'delete', row: MaterialRow): void
+}>()
 
+const { t } = useI18n()
+const message = useMessage()
+const { ZmTable, ZmTableColumn } = useTableComponents<MaterialRow>()
 const loading = ref(false)
-const total = ref(0) // 列表的总页数
-// const materials = ref([])
-
-const props = defineProps({
-  modelValue: Boolean,
-  nodeId: Number,
-  detail: Boolean,
-  materials: Array, // 接收父组件传递的完整物料列表
-  hideExtraColumns: {
-    type: Boolean,
-    default: false
-  }
-})
+const windowWidth = ref(window.innerWidth)
 
-// 监听bom树节点ID变化
-watch(() => props.nodeId, (newVal) => {
-  console.log('传递过来的nodeId', newVal)
-  if (newVal) {
-    // await loadMaterials(newVal)
-  }
+const computedSize = computed(() => {
+  if (windowWidth.value <= 768) return '100%'
+  return windowWidth.value > 1440 ? '68%' : '82%'
 })
 
-// 计算属性过滤当前节点物料
 const filteredMaterials = computed(() => {
-  return props.materials?.filter(item =>
-    item.bomNodeId === props.nodeId
-  ) || []
+  if (!props.nodeId) return []
+  return props.materials.filter((item) => item.bomNodeId === props.nodeId)
 })
 
-// 加载指定bom节点下的物料数据
-const loadMaterials = async (nodeId) => {
-  queryParams.bomId = nodeId
-  try {
-    loading.value = true
-    // API调用
-    const data = await PmsMaterialApi.listByBomId(queryParams)
-    materials.value = data.list
-    total.value = data.total
-  } catch (error) {
-    ElMessage.error('数据加载失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 删除物料前的确认 */
-const handleDelete = async (row: any) => {
+const handleDelete = async (row: MaterialRow) => {
   try {
     await message.delConfirm()
-    // 用户确认后触发删除事件
     emit('delete', row)
-    // message.success(t('common.delSuccess'))
-  } catch (error) {
-    // 用户取消操作
-    console.log('删除操作已取消')
-  }
+  } catch {}
+}
+
+const handleClose = () => {
+  emit('update:modelValue', false)
 }
 
-// 打开抽屉
 const openDrawer = () => {
-  drawerVisible.value = true
+  emit('update:modelValue', true)
 }
 
-// 关闭抽屉
 const closeDrawer = () => {
-  drawerVisible.value = false
+  handleClose()
 }
 
-// 关闭抽屉
-const handleClose = () => {
-  emit('update:modelValue', false)
-  // materials.value = []
+const updateWindowWidth = () => {
+  windowWidth.value = window.innerWidth
 }
 
-defineExpose({ openDrawer, closeDrawer }) // 暴露方法给父组件
+onMounted(() => window.addEventListener('resize', updateWindowWidth))
+onUnmounted(() => window.removeEventListener('resize', updateWindowWidth))
 
+defineExpose({ openDrawer, closeDrawer })
 </script>
 
-<style lang="scss" scoped></style>
+<style scoped>
+.drawer-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  width: 100%;
+}
+
+.drawer-header__main {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.drawer-header__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 44px;
+  height: 44px;
+  margin-right: 12px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 8px;
+}
+
+.drawer-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.drawer-subtitle {
+  margin-top: 4px;
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+}
+
+.drawer-body {
+  height: 100%;
+  padding: 4px;
+}
+
+.material-section {
+  height: 100%;
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 54px;
+  padding: 0 18px;
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.section-marker {
+  width: 4px;
+  height: 18px;
+  margin-right: 10px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.material-total {
+  padding: 4px 10px;
+  font-size: 12px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border-radius: 4px;
+}
+
+.table-container {
+  padding: 16px;
+}
+
+.drawer-footer {
+  display: flex;
+  justify-content: flex-end;
+}
+
+@media (width <= 768px) {
+  .drawer-subtitle {
+    display: none;
+  }
+
+  .table-container {
+    padding: 12px;
+  }
+}
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 512 - 440
src/views/pms/maintain/IotMaintainAddEdit.vue


+ 417 - 222
src/views/pms/maintain/IotMaintainDetail.vue

@@ -1,308 +1,347 @@
 <template>
-  <ContentWrap v-loading="formLoading">
+  <div v-loading="formLoading" class="maintain-detail-page">
+    <div class="detail-header">
+      <div class="detail-header__main">
+        <div class="detail-header__icon">
+          <Icon icon="ep:tools" />
+        </div>
+        <div>
+          <h2 class="detail-title">维修工单详情</h2>
+          <p class="detail-subtitle">查看设备故障、维修处理与关联物料信息</p>
+        </div>
+      </div>
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </div>
+
     <el-form
       ref="formRef"
       :model="formData"
       :rules="formData.type === 'in' ? formRules : outFormRules"
-      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="8">
+      size="default"
+      label-width="112px"
+      class="detail-form">
+      <section class="detail-section">
+        <div class="section-heading"><span></span>基础信息</div>
+        <el-row :gutter="24">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('maintain.deviceName')" prop="deviceName">
-              <el-select disabled v-model="formData.deviceName" />
+              <el-input v-model="formData.deviceName" disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('maintain.status')" prop="status">
-              <el-select v-model="formData.status" clearable disabled>
+              <el-select v-model="formData.status" disabled>
                 <el-option
                   v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_STATUS)"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="dict.value"
-                />
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('iotMaintain.repairType')" prop="type">
-              <el-select disabled v-model="formData.type" clearable>
+              <el-select v-model="formData.type" disabled>
                 <el-option
                   v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_TYPE)"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="dict.value"
-                />
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('iotMaintain.shutDown')" prop="ifStop">
-              <el-select v-model="formData.ifStop" clearable disabled>
+              <el-select v-model="formData.ifStop" disabled>
                 <el-option
                   v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="dict.value"
-                />
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.failureTime')" prop="failureTime">
+              <el-date-picker
+                v-model="formData.failureTime"
+                type="datetime"
+                value-format="x"
+                disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.faultySystem')" prop="failureSystem">
+              <el-input v-model="formData.failureSystem" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.failureImpact')" prop="failureInfluence">
+              <el-input v-model="formData.failureInfluence" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </section>
+
+      <section class="detail-section">
+        <div class="section-heading"><span></span>维修处理</div>
+        <el-row :gutter="24">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item
               :label="t('iotMaintain.startTime')"
               prop="maintainStartTime"
-              :rules="formData.type === 'in' ? startRule : []"
-            >
+              :rules="formData.type === 'in' ? startRule : []">
               <el-date-picker
-                disabled
-                style="width: 150%"
                 v-model="formData.maintainStartTime"
                 type="datetime"
                 value-format="x"
-              />
+                disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item
               :label="t('iotMaintain.endTime')"
               prop="maintainEndTime"
-              :rules="formData.type === 'in' ? endRule : []"
-            >
+              :rules="formData.type === 'in' ? endRule : []">
               <el-date-picker
-                disabled
-                style="width: 150%"
                 v-model="formData.maintainEndTime"
                 type="datetime"
                 value-format="x"
-              />
+                disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('iotMaintain.PersonInCharge')" prop="maintainPerson">
-              <el-select
-                v-model="formData.maintainPerson"
-                filterable
-                clearable
-                style="width: 100%"
-                disabled
-              >
-                <el-option
-                  v-for="item in deptUsers"
-                  :key="item.id"
-                  :label="item.nickname"
-                  :value="item.id"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.failureTime')" prop="failureTime">
-              <el-date-picker
-                disabled
-                style="width: 150%"
-                v-model="formData.failureTime"
-                type="datetime"
-                value-format="x"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.failureImpact')" prop="failureInfluence">
-              <el-input v-model="formData.failureInfluence" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.faultySystem')" prop="failureSystem">
-              <el-input v-model="formData.failureSystem" disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.picture')" prop="pics">
-              <UploadImgs v-model="formData.pics" :disabled="true" height="55px" />
+              <el-input v-model="formData.maintainPerson" disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
             <el-form-item :label="t('maintain.maintainWay')" prop="maintainMethod">
-              <el-select v-model="formData.maintainMethod" clearable disabled>
+              <el-select v-model="formData.maintainMethod" disabled>
                 <el-option
                   v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAINTAIN_METHOD)"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="dict.value"
-                />
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8">
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item
+              :label="t('iotMaintain.repairCosts')"
+              prop="maintainFee"
+              :rules="formData.type === 'out' ? feeRules : []">
+              <el-input-number
+                v-model="formData.maintainFee"
+                :min="0"
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
             <el-form-item :label="t('maintain.methodDescription')" prop="methodDescription">
-              <el-input v-model="formData.methodDescription" type="textarea"  disabled />
+              <el-input
+                v-model="formData.methodDescription"
+                type="textarea"
+                :rows="2"
+                resize="none"
+                disabled />
             </el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.applyPerson')" prop="applyPersonId" :rules="formData.type==='out'?classifyRules:[]">
-              <el-select v-model="formData.applyPersonId" :placeholder="t('iotMaintain.applyPersonHolder')" disabled clearable>
+        </el-row>
+      </section>
+
+      <section v-if="formData.type === 'out'" class="detail-section">
+        <div class="section-heading"><span></span>外委信息</div>
+        <el-row :gutter="24">
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item
+              :label="t('iotMaintain.applyPerson')"
+              prop="applyPersonId"
+              :rules="classifyRules">
+              <el-select v-model="formData.applyPersonId" disabled>
                 <el-option
                   v-for="person in applyPeoPle"
                   :key="person.oaId"
                   :label="person.lastname"
-                  :value="person.oaId"
-                />
+                  :value="person.oaId" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.maintainClassify')" prop="maintainClassify" :rules="formData.type==='out'?classifyRules:[]">
-              <el-select v-model="formData.maintainClassify" :placeholder="t('iotMaintain.maintainClassifyHolder')" disabled clearable>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item
+              :label="t('iotMaintain.maintainClassify')"
+              prop="maintainClassify"
+              :rules="classifyRules">
+              <el-select v-model="formData.maintainClassify" disabled>
                 <el-option
                   v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_CLASSIFY)"
                   :key="dict.value"
                   :label="dict.label"
-                  :value="dict.value"
-                />
+                  :value="dict.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.kmHour')" prop="kmHour">
-              <el-input v-model="formData.kmHour" :placeholder="t('iotMaintain.kmHourHolder')" disabled/>
-            </el-form-item>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.kmHour')" prop="kmHour"
+              ><el-input v-model="formData.kmHour" disabled
+            /></el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('deviceForm.model')" prop="model" :rules="formData.type==='out'?modelRules:[]">
-              <el-input v-model="formData.model" :placeholder="t('deviceForm.modelHolder')" disabled/>
-            </el-form-item>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('deviceForm.model')" prop="model" :rules="modelRules"
+              ><el-input v-model="formData.model" disabled
+            /></el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('deviceForm.enable')" prop="enableDate" :rules="formData.type==='out'?modelRules:[]">
-              <el-date-picker
-                disabled
-                style="width: 150%"
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('deviceForm.enable')" prop="enableDate" :rules="modelRules"
+              ><el-date-picker
                 v-model="formData.enableDate"
                 type="date"
                 value-format="YYYY-MM-DD"
-                :placeholder="t('deviceForm.enableHolder')"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <div style="display: flex;flex-direction: row">
-              <el-form-item :label="t('iotMaintain.supplier')" prop="supplier" style="width: 86%" :rules="formData.type==='out'?supplierRules:[]">
-                <el-input
-                  disabled
-                  clearable
-                  v-model="formData.supplier"
-                  :placeholder="t('iotMaintain.suppHolder')"
-                />
-              </el-form-item>
-            </div>
-          </el-col>
-          <el-col :span="8" >
-            <el-form-item :label="t('iotMaintain.repairCosts')" prop="maintainFee" :rules="formData.type==='out'?feeRules:[]">
-              <el-input-number
                 disabled
-                v-model="formData.maintainFee"
-                :min="0"
-                :precision="2"
-                :step="0.1"
-                style="width: 100%"
-              />
-            </el-form-item>
+            /></el-form-item>
           </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.projectManager')" prop="projectManager" :rules="formData.type==='out'?projectRules:[]">
-              <el-select v-model="formData.projectManager" :placeholder="t('iotMaintain.projectManagerHolder')" disabled clearable filterable>
-                <el-option
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.supplier')" prop="supplier" :rules="supplierRules"
+              ><el-input v-model="formData.supplier" disabled
+            /></el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item
+              :label="t('iotMaintain.projectManager')"
+              prop="projectManager"
+              :rules="projectRules">
+              <el-select v-model="formData.projectManager" disabled
+                ><el-option
                   v-for="person in projectManager"
                   :key="person.oaId"
                   :label="person.lastname"
                   :value="person.oaId"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8"  v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.address')" prop="address">
-              <el-input  v-model="formData.address" disabled/>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8" v-if="formData.type === 'out'">
-            <el-form-item :label="t('iotMaintain.attachment')" prop="outFiles">
-              <UploadFile v-model="formData.outFiles" :is-show-tip="false" class="min-w-80px" multiple :limit="2" disabled />
+              /></el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="16"  v-if="formData.type === 'out'" >
-            <el-form-item :label="t('iotMaintain.maintainItem')" prop="maintainItem" :rules="formData.type==='out'?itemRules:[]">
-              <el-input type="textarea" v-model="formData.maintainItem" disabled/>
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.faultDescription')" prop="description">
-              <el-input type="textarea" v-model="formData.description" disabled />
-            </el-form-item>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.address')" prop="address"
+              ><el-input v-model="formData.address" disabled
+            /></el-form-item>
           </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.repairDescription')" prop="maintainDescription">
-              <el-input type="textarea" v-model="formData.maintainDescription" disabled />
-            </el-form-item>
+          <el-col :xs="24" :sm="12" :xl="8">
+            <el-form-item :label="t('iotMaintain.attachment')" prop="outFiles"
+              ><UploadFile
+                v-model="formData.outFiles"
+                :is-show-tip="false"
+                multiple
+                :limit="2"
+                disabled
+            /></el-form-item>
           </el-col>
-          <el-col :span="8">
-            <el-form-item :label="t('iotMaintain.solution')" prop="solution">
-              <el-input type="textarea" v-model="formData.solution" disabled />
-            </el-form-item>
+          <el-col :span="24">
+            <el-form-item
+              :label="t('iotMaintain.maintainItem')"
+              prop="maintainItem"
+              :rules="itemRules"
+              ><el-input
+                v-model="formData.maintainItem"
+                type="textarea"
+                :rows="2"
+                resize="none"
+                disabled
+            /></el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item :label="t('iotMaintain.remark')" prop="remark">
-              <el-input v-model="formData.remark" type="textarea" disabled />
+        </el-row>
+      </section>
+
+      <section class="detail-section">
+        <div class="section-heading"><span></span>故障描述</div>
+        <el-row :gutter="24">
+          <el-col :xs="24" :xl="8">
+            <el-form-item :label="t('iotMaintain.picture')" prop="pics">
+              <UploadImgs v-model="formData.pics" :disabled="true" height="72px" />
             </el-form-item>
           </el-col>
+          <el-col :xs="24" :xl="16"
+            ><el-form-item :label="t('iotMaintain.faultDescription')" prop="description"
+              ><el-input
+                v-model="formData.description"
+                type="textarea"
+                :rows="3"
+                resize="none"
+                disabled /></el-form-item
+          ></el-col>
+          <el-col :xs="24" :xl="12"
+            ><el-form-item :label="t('iotMaintain.repairDescription')" prop="maintainDescription"
+              ><el-input
+                v-model="formData.maintainDescription"
+                type="textarea"
+                :rows="3"
+                resize="none"
+                disabled /></el-form-item
+          ></el-col>
+          <el-col :xs="24" :xl="12"
+            ><el-form-item :label="t('iotMaintain.solution')" prop="solution"
+              ><el-input
+                v-model="formData.solution"
+                type="textarea"
+                :rows="3"
+                resize="none"
+                disabled /></el-form-item
+          ></el-col>
+          <el-col :span="24"
+            ><el-form-item :label="t('iotMaintain.remark')" prop="remark"
+              ><el-input
+                v-model="formData.remark"
+                type="textarea"
+                :rows="2"
+                resize="none"
+                disabled /></el-form-item
+          ></el-col>
         </el-row>
-      </div>
+      </section>
     </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('iotMaintain.repairItems')" align="center" prop="name" />
-
-        <el-table-column :label="t('iotMaintain.operation')" align="center" min-width="120px">
+
+    <section class="detail-section material-section">
+      <div class="section-heading"><span></span>关联物料</div>
+      <ZmTable
+        :loading="formLoading"
+        :data="list"
+        :show-border="true"
+        settings-cache-key="pms-maintain-detail-material">
+        <ZmTableColumn
+          :label="t('iotMaintain.deviceCode')"
+          align="center"
+          prop="deviceCode"
+          fixed="left" />
+        <ZmTableColumn :label="t('iotMaintain.deviceName')" align="center" prop="deviceName" />
+        <ZmTableColumn :label="t('iotMaintain.repairItems')" align="center" prop="name" />
+        <ZmTableColumn :label="t('iotMaintain.operation')" align="center" width="120" fixed="right">
           <template #default="scope">
-            <div style="display: flex; justify-content: center; align-items: center; width: 100%">
-              <div style="margin-left: 12px">
-                <el-button
-                  link
-                  type="primary"
-                  @click="handleViewNew(scope.row.id, scope.row.bomNodeId)"
-                >
-                  物料详情
-                </el-button>
-              </div>
-            </div>
+            <el-button
+              size="default"
+              link
+              type="primary"
+              @click="handleViewNew(scope.row.id, scope.row.bomNodeId)"
+              >物料详情</el-button
+            >
           </template>
-        </el-table-column>
-      </el-table>
-    </ContentWrap>
-  </ContentWrap>
-  <ContentWrap>
-    <el-form>
-      <el-form-item style="float: right">
-        <el-button type="info" @click="close">取 消</el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
+        </ZmTableColumn>
+      </ZmTable>
+    </section>
+
+    <div class="sticky-action-bar">
+      <el-button size="default" @click="close">返回</el-button>
+    </div>
+  </div>
+
   <MaterialListDrawer
     :model-value="drawerVisible"
     @update:model-value="(val) => (drawerVisible = val)"
     :node-id="currentBomNodeId"
     :materials="materialList"
-    :detail="true"
-  />
+    :detail="true" />
 </template>
 <script setup lang="ts">
 import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
@@ -313,12 +352,13 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
 import { IotMaintainApi } from '@/api/pms/maintain'
 import MaterialListDrawer from '@/views/pms/iotmainworkorder/SelectedMaterialDrawer.vue'
 import { IotMaintainMaterialsApi } from '@/api/pms/maintain/materials'
-import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 
 /** 维修工单 表单 */
 defineOptions({ name: 'IotMaintainDetail' })
 
 const { t } = useI18n() // 国际化
+const { ZmTable, ZmTableColumn } = useTableComponents<IotMaintainMaterialVO>()
 
 const projectRules = [{ required: true, message: '项目经理不能为空', trigger: 'blur' }]
 const itemRules = [{ required: true, message: '维修项目不能为空', trigger: 'blur' }]
@@ -331,32 +371,26 @@ const applyPeoPle = ref([])
 const projectManager = ref([])
 
 const props = defineProps<{ id?: number }>()
-const message = useMessage() // 消息弹窗
 const { delView } = useTagsViewStore() // 视图操作
 const { currentRoute, push } = useRouter()
-const deptUsers = ref<UserApi.UserVO[]>([]) // 用户列表
-const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const drawerVisible = ref<boolean>(false)
-const showDrawer = ref()
 const list = ref<IotMaintainMaterialVO[]>([]) // 列表的数据
-const { params, name } = useRoute() // 查询参数
+const { params } = useRoute() // 查询参数
 const id = params.id || props.id
 const materialList = ref<any[]>([]) // 保养工单bom关联物料列表
-const { wsCache } = useCache()
 const currentBomNodeId = ref<string>()
 const formData = ref({
   methodDescription: undefined,
   maintainMethod: undefined,
-  kmHour:undefined,
-  maintainItem:undefined,
+  kmHour: undefined,
+  maintainItem: undefined,
   enableDate: undefined,
-  projectManager:undefined,
-  applyPersonId:undefined,
+  projectManager: undefined,
+  applyPersonId: undefined,
   maintainClassify: undefined,
-  address:undefined,
-  model:undefined,
+  address: undefined,
+  model: undefined,
   id: undefined,
   failureCode: undefined,
   failureName: undefined,
@@ -369,8 +403,8 @@ const formData = ref({
   failureSystem: undefined,
   description: undefined,
   pic: undefined,
-  pics:undefined,
-  outFiles:undefined,
+  pics: undefined,
+  outFiles: undefined,
   solution: undefined,
   maintainStartTime: undefined,
   maintainEndTime: undefined,
@@ -384,7 +418,7 @@ const formData = ref({
   supplier: undefined,
   maintainFee: undefined,
   outFile: undefined,
-  supplierName: undefined,
+  supplierName: undefined
 })
 const formRules = reactive({
   // deviceName: [{ required: true, message: '设备不能为空', trigger: 'blur' }],
@@ -437,13 +471,10 @@ const handleViewNew = (nodeId, bomId) => {
     materialList.value = res.list
   })
 }
-const userId = ref('')
 /** 提交表单 */
 onMounted(async () => {
-  const userInfo = wsCache.get(CACHE_KEY.USER)
-  userId.value = userInfo.user.nickname;
+  formLoading.value = true
   await IotMaintainApi.getIotMaintain(id).then((res) => {
-    debugger
     formData.value = res
     if (res.outFile) {
       formData.value.outFile = res.outFile.split(',')
@@ -462,10 +493,174 @@ onMounted(async () => {
         applyPeoPle.value = resss
       })
       IotMaintainApi.getProjectUsers(res.creator).then((ressss) => {
-        debugger
         projectManager.value = ressss
       })
     }
   })
+  formLoading.value = false
 })
 </script>
+
+<style scoped>
+.maintain-detail-page {
+  display: flex;
+  flex-direction: column;
+  min-height: calc(
+    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+  );
+  padding: 16px;
+  background: #f5f7fa;
+}
+
+.detail-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  background: var(--el-bg-color);
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgb(0 0 0 / 8%);
+}
+
+.detail-header__main {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.detail-header__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 42px;
+  height: 42px;
+  margin-right: 12px;
+  font-size: 20px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 8px;
+}
+
+.detail-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  line-height: 26px;
+  color: var(--el-text-color-primary);
+}
+
+.detail-subtitle {
+  margin: 2px 0 0;
+  font-size: 13px;
+  line-height: 20px;
+  color: var(--el-text-color-secondary);
+}
+
+.detail-form,
+.detail-section {
+  min-width: 0;
+}
+
+.detail-section {
+  padding: 0 20px 4px;
+  margin-bottom: 16px;
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
+}
+
+.section-heading {
+  display: flex;
+  height: 46px;
+  padding: 0 20px;
+  margin: 0 -20px 16px;
+  font-size: 16px;
+  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);
+  align-items: center;
+}
+
+.section-heading span {
+  width: 4px;
+  height: 18px;
+  margin-right: 10px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.detail-form :deep(.el-select),
+.detail-form :deep(.el-date-editor),
+.detail-form :deep(.el-input-number) {
+  width: 100%;
+}
+
+.detail-form :deep(.el-form-item) {
+  margin-bottom: 16px;
+}
+
+.material-section {
+  padding-bottom: 20px;
+}
+
+.sticky-action-bar {
+  position: sticky;
+  bottom: 0;
+  z-index: 20;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 10px;
+  min-height: 58px;
+  padding: 10px 20px;
+  margin-top: auto;
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: 0 -4px 12px rgb(0 0 0 / 8%);
+}
+
+.sticky-action-bar :deep(.el-button) {
+  margin-left: 0;
+}
+
+@media (width <= 768px) {
+  .maintain-detail-page {
+    padding: 12px;
+  }
+
+  .sticky-action-bar {
+    padding-right: 12px;
+    padding-left: 12px;
+    margin-right: -12px;
+    margin-bottom: -12px;
+    margin-left: -12px;
+  }
+
+  .detail-header,
+  .detail-section {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+
+  .detail-subtitle {
+    display: none;
+  }
+
+  .section-heading {
+    padding-right: 12px;
+    padding-left: 12px;
+    margin-right: -12px;
+    margin-left: -12px;
+  }
+
+  .detail-form {
+    --el-form-label-width: 96px;
+  }
+}
+</style>

+ 417 - 188
src/views/pms/maintain/IotMaintainRecord.vue

@@ -1,158 +1,150 @@
 <template>
-  <ContentWrap v-loading="formLoading">
-    <!-- 第一部分:日报标题 -->
-    <div class="daily-report-title">
-      <h2>{{ pageTitle }}</h2>
-    </div>
-
-    <!-- 第二部分:项目/任务信息 -->
-    <ContentWrap>
-      <div class="info-table" style="margin-top: 0.5em">
-        <!-- 表格行 -->
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">油服编码:</span>
-              <!-- 甲方字段:添加 single-line-ellipsis 类 + title 绑定完整内容 -->
-              <span
-                class="cell-value single-line-ellipsis"
-                :title="repairInfo.yfDeviceCode || repairInfo.deviceCode || '-'"
-              >
-                {{ repairInfo.yfDeviceCode || repairInfo.deviceCode || '-' }}
-              </span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">设备名称:</span>
-              <span class="cell-value">{{ repairInfo.deviceName || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">维修类型:</span>
-              <span class="cell-value">{{
-                getDictLabel(DICT_TYPE.PMS_MAIN_TYPE, repairInfo.type)
-              }}</span>
-            </div>
-          </div>
+  <div v-loading="formLoading" class="maintain-record-page">
+    <div class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon"><Icon icon="ep:document" /></div>
+        <div>
+          <h2 class="page-title">{{ pageTitle }}</h2>
+          <p class="page-subtitle">查看设备维修信息并维护维修过程记录</p>
         </div>
+      </div>
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </div>
 
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">维修开始时间:</span>
-              <span class="cell-value">{{ formatDate(repairInfo.maintainStartTime) || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">维修结束日期:</span>
-              <span class="cell-value">{{ formatDate(repairInfo.maintainEndTime) || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">故障时间:</span>
-              <span class="cell-value">{{ formatDate(repairInfo.failureTime) || '-' }}</span>
-            </div>
-          </div>
-        </div>
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">维修人员:</span>
-              <span class="cell-value">{{ repairInfo.maintainPersonName || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">故障系统:</span>
-              <span class="cell-value">{{ repairInfo.failureSystem || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <!--              <span class="cell-label">故障时间:</span>-->
-              <!--              <span class="cell-value">{{ formatDate(repairInfo.failureTime) || '-' }}</span>-->
-            </div>
-          </div>
+    <section class="page-section info-section">
+      <div class="section-header">
+        <div class="section-title"><span></span>工单概览</div>
+      </div>
+      <div class="info-grid">
+        <div class="info-item">
+          <span class="info-label">油服编码</span>
+          <span class="info-value" :title="repairInfo.yfDeviceCode || repairInfo.deviceCode || '-'">
+            {{ repairInfo.yfDeviceCode || repairInfo.deviceCode || '-' }}
+          </span>
         </div>
+        <div class="info-item"
+          ><span class="info-label">设备名称</span
+          ><span class="info-value">{{ repairInfo.deviceName || '-' }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">维修类型</span
+          ><span class="info-value">{{
+            getDictLabel(DICT_TYPE.PMS_MAIN_TYPE, repairInfo.type) || '-'
+          }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">维修开始时间</span
+          ><span class="info-value">{{
+            formatDate(repairInfo.maintainStartTime) || '-'
+          }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">维修结束日期</span
+          ><span class="info-value">{{ formatDate(repairInfo.maintainEndTime) || '-' }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">故障时间</span
+          ><span class="info-value">{{ formatDate(repairInfo.failureTime) || '-' }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">维修人员</span
+          ><span class="info-value">{{ repairInfo.maintainPersonName || '-' }}</span></div
+        >
+        <div class="info-item"
+          ><span class="info-label">故障系统</span
+          ><span class="info-value">{{ repairInfo.failureSystem || '-' }}</span></div
+        >
+        <div class="info-item info-item--empty" aria-hidden="true"></div>
       </div>
-    </ContentWrap>
-    <ContentWrap>
-      <el-form :inline="true" ref="queryFormRef" :model="queryParams">
-        <el-form-item>
-          <el-form-item label="维修时间" prop="createTime">
-            <el-date-picker
-              v-model="queryParams.repairTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-220px"
-            />
-          </el-form-item>
-        </el-form-item>
-        <el-form-item>
-          <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-          <el-button type="primary" @click="openForm('create')">
-            <Icon icon="ep:plus" /> 新增
+    </section>
+
+    <section class="page-section records-section">
+      <div class="section-header">
+        <div class="section-title"><span></span>维修记录</div>
+        <div class="section-actions">
+          <el-button size="default" type="primary" @click="openForm('create')">
+            <Icon icon="ep:plus" class="mr-5px" />新增
           </el-button>
-          <el-button type="warning" @click="viewTimeline">
-            <Icon icon="ep:timer" /> 时间线
+          <el-button size="default" type="warning" @click="viewTimeline">
+            <Icon icon="ep:timer" class="mr-5px" />时间线
           </el-button>
+        </div>
+      </div>
+
+      <el-form ref="queryFormRef" :model="queryParams" size="default" class="query-form">
+        <el-form-item label="维修时间" prop="repairTime">
+          <el-date-picker
+            v-model="queryParams.repairTime"
+            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="query-date" />
+        </el-form-item>
+        <el-form-item class="query-actions">
+          <el-button size="default" type="primary" @click="handleQuery"
+            ><Icon icon="ep:search" class="mr-5px" />搜索</el-button
+          >
+          <el-button size="default" @click="resetQuery"
+            ><Icon icon="ep:refresh" class="mr-5px" />重置</el-button
+          >
         </el-form-item>
       </el-form>
-    </ContentWrap>
-    <ContentWrap>
-      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-        <el-table-column
-          label="维修时间"
-          align="center"
-          prop="repairTime"
-          :formatter="dateFormatter"
-          width="150px"
-        />
-        <el-table-column label="维修项" align="center" min-width="90" prop="repairItem" />
-        <el-table-column label="维修内容" align="center" min-width="150" prop="repairContent" />
-        <el-table-column label="解决办法" align="center" prop="solution" min-width="150" />
-        <el-table-column label="图片" align="center"  prop="pic" />
-        <el-table-column label="创建人" align="center" prop="createName" />
-        <el-table-column label="配合维修人员" align="center" prop="repairPersonName" />
-        <el-table-column label="备注" align="center" prop="remark" />
-        <el-table-column
-          label="创建时间"
-          align="center"
-          prop="createTime"
-          :formatter="dateFormatter"
-          width="180px"
-        />
-        <el-table-column label="操作" align="center" min-width="90px">
-          <template #default="scope">
-            <el-button link type="primary" v-if="userid.toString()===scope.row.creator" @click="openForm('update', scope.row.id)">
-              编辑
-            </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <!-- 分页 -->
+
+      <div class="table-wrap">
+        <ZmTable
+          :loading="loading"
+          :data="list"
+          :show-border="true"
+          settings-cache-key="pms-maintain-record-list">
+          <ZmTableColumn
+            label="维修时间"
+            prop="repairTime"
+            :formatter="dateFormatter"
+            min-width="150" />
+          <ZmTableColumn label="维修项" prop="repairItem" />
+          <ZmTableColumn label="维修内容" prop="repairContent" min-width="150" />
+          <ZmTableColumn label="解决办法" prop="solution" min-width="150" />
+          <ZmTableColumn label="图片" prop="pic" />
+          <ZmTableColumn label="创建人" prop="createName" />
+          <ZmTableColumn label="配合维修人员" prop="repairPersonName" />
+          <ZmTableColumn label="备注" prop="remark" />
+          <ZmTableColumn
+            label="创建时间"
+            prop="createTime"
+            :formatter="dateFormatter"
+            min-width="180" />
+          <ZmTableColumn label="操作" width="90" fixed="right">
+            <template #default="scope">
+              <el-button
+                v-if="userid.toString() === scope.row.creator"
+                size="default"
+                link
+                type="primary"
+                @click="openForm('update', scope.row.id)">
+                编辑
+              </el-button>
+            </template>
+          </ZmTableColumn>
+        </ZmTable>
+      </div>
+
       <Pagination
         :total="total"
         v-model:page="queryParams.pageNo"
         v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
-    </ContentWrap>
+        @pagination="getList" />
+    </section>
+
     <IotJdRecordForm ref="formRef" @success="getList" />
     <MaintainRecordDrawer
       ref="showDrawer"
       :model-value="drawerVisible"
-      @update:model-value="(val) => (drawerVisible = val)"
-    />
-  </ContentWrap>
+      @update:model-value="(val) => (drawerVisible = val)" />
+  </div>
 </template>
 
 <script setup lang="ts">
@@ -169,6 +161,7 @@ import { dateFormatter } from '@/utils/formatTime'
 import { IotJdRecordApi, IotJdRecordVO } from '@/api/pms/maintain/record'
 import IotJdRecordForm from '@/views/pms/maintain/record/IotJdRecordForm.vue'
 import MaintainRecordDrawer from '@/views/pms/maintain/MaintainRecordDrawer.vue'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 
 const { t } = useI18n()
 const message = useMessage()
@@ -176,8 +169,9 @@ const { delView } = useTagsViewStore()
 const { push, currentRoute } = useRouter()
 const { params } = useRoute()
 const userStore = useUserStore()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotJdRecordVO>()
 const total = ref(0) // 列表的总页数
-const list = ref<IotJdRecordVO>([])
+const list = ref<IotJdRecordVO[]>([])
 const loading = ref(true) // 列表的加载中
 const drawerVisible = ref<boolean>(false)
 const showDrawer = ref()
@@ -243,7 +237,7 @@ const pageTitle = computed(() => {
 
 const close = () => {
   delView(unref(currentRoute))
-  push({ name: 'FillDailyReport', params: {} })
+  push({ name: 'IotMaintain', params: {} })
 }
 
 const formatDate = (timestamp: number) => {
@@ -271,10 +265,81 @@ onMounted(async () => {
 </script>
 
 <style scoped>
+/* 响应式设计 */
+@media (width <= 768px) {
+  .table-row {
+    flex-direction: column;
+  }
+
+  .table-cell {
+    border-right: none;
+    border-bottom: 1px solid #e0e0e0;
+  }
+
+  .table-cell:last-child {
+    border-bottom: none;
+  }
+}
+
+@media (width <= 1200px) {
+  .info-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .info-item--empty {
+    display: none;
+  }
+}
+
+@media (width <= 768px) {
+  .maintain-record-page {
+    padding: 12px;
+  }
+
+  .page-header,
+  .section-header {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+
+  .page-subtitle {
+    display: none;
+  }
+
+  .section-header {
+    align-items: flex-start;
+    flex-direction: column;
+    padding-top: 12px;
+    padding-bottom: 12px;
+  }
+
+  .section-actions {
+    justify-content: flex-start;
+    width: 100%;
+  }
+
+  .info-grid {
+    grid-template-columns: minmax(0, 1fr);
+    margin: 12px;
+  }
+
+  .query-form,
+  .table-wrap {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+
+  .query-form,
+  .query-form :deep(.el-form-item),
+  .query-date {
+    width: 100%;
+  }
+}
+
 .info-table {
+  overflow: hidden;
   border: 1px solid #e0e0e0;
   border-radius: 4px;
-  overflow: hidden;
 }
 
 .table-row {
@@ -287,11 +352,11 @@ onMounted(async () => {
 }
 
 .table-cell {
-  flex: 1;
-  border-right: 1px solid #e0e0e0;
-  padding: 12px 8px;
-  min-height: 44px;
   display: flex;
+  min-height: 44px;
+  padding: 12px 8px;
+  border-right: 1px solid #e0e0e0;
+  flex: 1;
   align-items: center;
 }
 
@@ -311,89 +376,81 @@ onMounted(async () => {
 }
 
 .cell-label {
-  font-weight: 500;
+  min-width: 80px;
+  margin-right: 8px;
+
   /* 统一字体大小为 14px(Element 表单默认) */
   font-size: 14px;
+  font-weight: 500;
   color: #606266;
-  min-width: 80px;
-  margin-right: 8px;
   flex-shrink: 0;
 }
 
 .cell-value {
   /* 统一字体大小为 14px(Element 输入框默认) */
   font-size: 14px;
-  color: #303133;
+
   /* 统一行高为 1.5(Element 组件默认行高) */
   line-height: 1.5;
-  flex: 1;
+  color: #303133;
   word-break: break-all;
-}
-
-/* 响应式设计 */
-@media (max-width: 768px) {
-  .table-row {
-    flex-direction: column;
-  }
-
-  .table-cell {
-    border-right: none;
-    border-bottom: 1px solid #e0e0e0;
-  }
-
-  .table-cell:last-child {
-    border-bottom: none;
-  }
+  flex: 1;
 }
 
 .daily-report-title {
-  text-align: center;
-  margin: 5px 0;
   padding: 5px;
+  margin: 5px 0;
+  text-align: center;
   border-bottom: 2px solid #409eff;
 }
 
 .daily-report-title h2 {
   margin: 0;
-  color: #303133;
   font-size: 16px;
   font-weight: bold;
+  color: #303133;
 }
 
 /* 为第二、三部分增加左右留白 */
 .section-padding {
-  padding-left: 0px;
   padding-right: 40px;
+  padding-left: 0;
 }
+
 :deep(.el-textarea .el-textarea__inner) {
   min-height: 80px;
 }
 
 /* 确保表单label不换行 */
 :deep(.el-form-item__label) {
-  white-space: nowrap;
-  text-overflow: ellipsis;
   overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 /* 甲方字段:单行显示+超出省略 */
 .single-line-ellipsis {
-  /* 强制文本单行显示 */
-  white-space: nowrap;
   /* 超出容器部分隐藏 */
   overflow: hidden;
+
   /* 超出部分显示省略号 */
   text-overflow: ellipsis;
+
   /* 避免文本被截断(可选,根据需求调整) */
   word-break: normal;
+
+  /* 强制文本单行显示 */
+  white-space: nowrap;
 }
 
 /* 设备配置字段:换行缩进(与首行对齐) */
 .indent-multiline {
   /* 首行及换行后缩进 2em(与 label 宽度匹配,可根据需求调整) */
-  text-indent: 0em;
+  text-indent: 0;
+
   /* 允许长文本换行(覆盖原有 cell-value 的 break-all,确保中文换行正常) */
   word-break: break-word;
+
   /* 保证换行后文本正常显示(可选,清除可能的 nowrap 影响) */
   white-space: normal;
 }
@@ -401,24 +458,24 @@ onMounted(async () => {
 /* 审批模式下表单字段的只读样式 */
 :deep(.el-form-item.is-disabled .el-input__inner),
 :deep(.el-form-item.is-disabled .el-textarea__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
   color: #c0c4cc;
   cursor: not-allowed;
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
 }
 
 :deep(.el-form-item.is-disabled .el-select .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
   color: #c0c4cc;
   cursor: not-allowed;
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
 }
 
 :deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
   color: #c0c4cc;
   cursor: not-allowed;
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
 }
 
 /* 只读模式下表单字段的样式 */
@@ -426,16 +483,16 @@ onMounted(async () => {
 :deep(.el-form-item.is-disabled .el-textarea__inner),
 :deep(.el-form-item.is-disabled .el-select .el-input__inner),
 :deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
   color: #606266; /* 保持文字可读性 */
   cursor: not-allowed;
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
 }
 
 /* 详情模式下的特殊样式 */
 .detail-mode .cell-value {
-  color: #303133;
   font-weight: normal;
+  color: #303133;
 }
 
 /* 审批意见文本域样式 */
@@ -456,14 +513,14 @@ onMounted(async () => {
 
 :deep(.el-transfer-panel__item) {
   display: flex !important;
-  align-items: center !important;
   height: 32px !important;
-  line-height: 32px !important;
   padding: 0 8px !important;
   margin: 0 !important;
-  white-space: nowrap;
   overflow: hidden;
+  line-height: 32px !important;
   text-overflow: ellipsis;
+  white-space: nowrap;
+  align-items: center !important;
 }
 
 :deep(.el-transfer-panel__list) {
@@ -472,8 +529,180 @@ onMounted(async () => {
 
 /* 只读模式下的设备显示样式 */
 :deep(.is-disabled) .device-display-container {
-  background-color: #f5f7fa;
   color: #606266;
   cursor: not-allowed;
+  background-color: #f5f7fa;
+}
+
+.maintain-record-page {
+  min-height: calc(
+    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+  );
+  padding: 16px;
+  background: #f5f7fa;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  background: var(--el-bg-color);
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgb(0 0 0 / 8%);
+}
+
+.page-header__main {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.page-header__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 42px;
+  height: 42px;
+  margin-right: 12px;
+  font-size: 20px;
+  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-title {
+  max-width: min(720px, 65vw);
+  margin: 0;
+  overflow: hidden;
+  font-size: 18px;
+  font-weight: 600;
+  line-height: 26px;
+  color: var(--el-text-color-primary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.page-subtitle {
+  margin: 2px 0 0;
+  font-size: 13px;
+  line-height: 20px;
+  color: var(--el-text-color-secondary);
+}
+
+.page-section {
+  margin-bottom: 16px;
+  overflow: hidden;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  min-height: 54px;
+  padding: 0 20px;
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  flex: 0 0 auto;
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.section-title span {
+  width: 4px;
+  height: 18px;
+  margin-right: 10px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
+
+.section-actions {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  justify-content: flex-end;
+}
+
+.section-actions :deep(.el-button),
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 1px;
+  margin: 16px 20px;
+  overflow: hidden;
+  background: var(--el-border-color-lighter);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 4px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+  min-height: 48px;
+  padding: 10px 14px;
+  background: var(--el-bg-color);
+}
+
+.info-label {
+  flex: 0 0 104px;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.info-value {
+  min-width: 0;
+  overflow: hidden;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.query-form {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12px 20px;
+  padding: 16px 20px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.query-form :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.query-date {
+  width: 260px;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  display: flex;
+  gap: 8px;
+}
+
+.table-wrap {
+  padding: 16px 20px;
+}
+
+.records-section :deep(.pagination-container) {
+  padding: 12px 20px;
 }
 </style>

+ 430 - 328
src/views/pms/maintain/index.vue

@@ -1,339 +1,322 @@
 <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('maintain.faultCode')"
-            prop="failureCode"
-            style="margin-left: 25px"
-          >
-            <el-input
-              v-model="queryParams.failureCode"
-              :placeholder="t('maintain.codeHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-200px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('maintain.faultName')" label-width="70px" prop="failureName">
-            <el-input
-              v-model="queryParams.failureName"
-              :placeholder="t('maintain.nameHolder')"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-200px"
-            />
-          </el-form-item>
-          <el-form-item label="执行时间" label-width="70px" prop="executeDate">
-            <el-date-picker
-              size="small"
-              v-model="queryParams.executeDate"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              class="!w-220px"
-              :clearable="true"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-            />
-          </el-form-item>
-          <el-form-item label="创建时间" label-width="70px" prop="createTime">
-            <el-date-picker
-              size="small"
-              v-model="queryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              class="!w-220px"
-              :clearable="true"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-            />
-          </el-form-item>
-          <el-form-item :label="t('maintain.status')" label-width="40px" prop="status">
-            <el-select
-              v-model="queryParams.status"
-              :placeholder="t('maintain.status')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_STATUS)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item :label="t('maintain.shutDown')" v-show="ifShow" prop="ifStop">
-            <el-select
-              v-model="queryParams.ifStop"
-              :placeholder="t('maintain.shutDown')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item
-            :label="t('maintain.failureTime')"
-            v-show="ifShow"
-            prop="failureTime"
-            style="margin-left: 25px"
-          >
-            <el-date-picker
-              v-model="queryParams.failureTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-220px"
-            />
-          </el-form-item>
-          <el-form-item :label="t('maintain.solve')" v-show="ifShow" prop="ifDeal">
-            <el-select
-              v-model="queryParams.ifDeal"
-              :placeholder="t('maintain.solve')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item :label="t('maintain.assist')" v-show="ifShow" prop="needHelp">
-            <el-select
-              v-model="queryParams.needHelp"
-              :placeholder="t('maintain.assist')"
-              clearable
-              class="!w-200px"
-            >
-              <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item>
-            <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.moreSearch') }}</el-button
-            >
-            <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.closeSearch') }}</el-button
-            >
-            <el-button @click="handleQuery"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.search') }}</el-button
-            >
-            <el-button @click="resetQuery"
-              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('maintain.reset') }}</el-button
-            >
-            <el-button
-              type="primary"
-              plain
-              @click="openForm('create')"
-              v-hasPermi="['rq:iot-maintain:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> {{ t('maintain.added') }}
-            </el-button>
-            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
+  <div
+    class="maintain-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
+      v-model="queryParams.deptId"
+      :top-id="rootDeptId"
+      :dept-id="deptId"
+      :init-select="false"
+      :show-title="false"
+      request-api="getSimpleDeptList"
+      class="maintain-tree row-span-2"
+      @node-click="handleDeptNodeClick" />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="68px"
+      class="maintain-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('maintain.faultCode')" prop="failureCode">
+          <el-input
+            v-model="queryParams.failureCode"
+            :placeholder="t('maintain.codeHolder')"
+            clearable
+            @keyup.enter="handleQuery"
+            class="query-control" />
+        </el-form-item>
+        <el-form-item :label="t('maintain.faultName')" label-width="70px" prop="failureName">
+          <el-input
+            v-model="queryParams.failureName"
+            :placeholder="t('maintain.nameHolder')"
+            clearable
+            @keyup.enter="handleQuery"
+            class="query-control" />
+        </el-form-item>
+        <el-form-item label="执行时间" label-width="70px" prop="executeDate">
+          <el-date-picker
+            v-model="queryParams.executeDate"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            class="query-control query-control--date"
+            :clearable="true"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" />
+        </el-form-item>
+        <el-form-item label="创建时间" label-width="70px" prop="createTime">
+          <el-date-picker
+            v-model="queryParams.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            class="query-control query-control--date"
+            :clearable="true"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" />
+        </el-form-item>
+        <el-form-item :label="t('maintain.status')" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            :placeholder="t('maintain.status')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAIN_STATUS)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item :label="t('maintain.shutDown')" v-show="ifShow" prop="ifStop">
+          <el-select
+            v-model="queryParams.ifStop"
+            :placeholder="t('maintain.shutDown')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item :label="t('maintain.failureTime')" v-show="ifShow" prop="failureTime">
+          <el-date-picker
+            v-model="queryParams.failureTime"
+            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="query-control query-control--date" />
+        </el-form-item>
+        <el-form-item :label="t('maintain.solve')" v-show="ifShow" prop="ifDeal">
+          <el-select
+            v-model="queryParams.ifDeal"
+            :placeholder="t('maintain.solve')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item :label="t('maintain.assist')" v-show="ifShow" prop="needHelp">
+          <el-select
+            v-model="queryParams.needHelp"
+            :placeholder="t('maintain.assist')"
+            clearable
+            class="query-control query-control--small">
+            <el-option
+              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value" />
+          </el-select>
+        </el-form-item>
       </div>
 
-      <!-- 列表 -->
-      <ContentWrap style="border: none; margin-top: 20px">
-        <zm-table :loading="loading" :data="list" height="calc(85vh - 180px)">
-          <zm-table-column :label="t('maintain.serial')" min-width="70" align="center">
-            <template #default="scope">
-              {{ scope.$index + 1 }}
-            </template>
-          </zm-table-column>
-          <zm-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" />
-          <zm-table-column :label="t('maintain.deviceName')" align="center" prop="deviceName" />
-          <zm-table-column
-            :label="t('maintain.status')"
-            align="center"
-            prop="status"
-            min-width="95"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.PMS_MAIN_STATUS" :value="scope.row.status" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            :label="t('maintain.source')"
-            align="center"
-            prop="maintainType"
-            min-width="135"
-          />
-          <zm-table-column
-            :label="t('iotMaintain.repairType')"
-            align="center"
-            prop="type"
-            min-width="135"
-          >
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.PMS_MAIN_TYPE" :value="scope.row.type" />
-            </template>
-          </zm-table-column>
-          <zm-table-column :label="t('maintain.shutDown')" align="center" prop="ifStop">
-            <template #default="scope">
-              <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.ifStop" />
-            </template>
-          </zm-table-column>
-          <zm-table-column
-            label="执行时间"
-            align="center"
-            prop="executeDate"
-            :formatter="dateFormatter"
-            min-width="180px"
-          />
-          <zm-table-column :label="t('iotMaintain.oaFlowNo')" align="center" prop="oaFlowNo" />
-          <zm-table-column
-            :label="t('maintain.dept')"
-            align="center"
-            prop="deptName"
-            min-width="135"
-          />
-          <zm-table-column
-            :label="t('inspect.createName')"
-            align="center"
-            prop="createName"
-            min-width="130"
-          />
-          <zm-table-column
-            label="创建时间"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            width="180px"
-          />
-          <zm-table-column
-            :label="t('maintain.operation')"
-            align="center"
-            min-width="170"
-            fixed="right"
-            action
-          >
-            <template #default="scope">
-              <el-button link type="primary" @click="detail(scope.row.id)">
-                {{ t('maintain.view') }}
-              </el-button>
-              <el-button
-                link
-                v-if="
-                  scope.row.status === 'tx' &&
-                  (scope.row.auditStatus === 20 || scope.row.auditStatus === null)
-                "
-                type="danger"
-                @click="
-                  openForm('update', scope.row.id, scope.row.maintainPerson, scope.row.company)
-                "
-              >
-                {{ t('maintain.fill') }}
-              </el-button>
-              <el-button
-                link
-                v-if="scope.row.status === 'personnel' && userid === scope.row.maintainPerson"
-                type="danger"
-                @click="openDialog(scope.row.id)"
-              >
-                {{ t('maintain.maintainWay') }}
-              </el-button>
-              <el-button
-                v-if="scope.row.status !== 'personnel'"
-                link
-                type="warning"
-                @click="openRecord(scope.row.id)"
-              >
-                {{ t('maintain.record') }}
-              </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="methodFormRef" :model="form" :rules="rules" label-width="70px">
-            <el-form-item label="维修方式" prop="maintainMethod">
-              <el-select v-model="form.maintainMethod" placeholder="请选择维修方式">
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAINTAIN_METHOD)"
-                  :key="dict.label"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </el-select>
-            </el-form-item>
-            <el-form-item label="备注说明" prop="methodDescription">
-              <el-input
-                type="textarea"
-                v-model="form.methodDescription"
-                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 v-if="!ifShow" type="warning" @click="moreQuery(true)">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.moreSearch') }}
+        </el-button>
+        <el-button v-else type="danger" @click="moreQuery(false)">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.closeSearch') }}
+        </el-button>
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" /> {{ t('maintain.search') }}
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" /> {{ t('maintain.reset') }}
+        </el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-maintain:create']">
+          <Icon icon="ep:plus" class="mr-5px" /> {{ t('maintain.added') }}
+        </el-button>
+        <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="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>
+              <ZmTableColumn :label="t('maintain.serial')" width="70" align="center" fixed="left">
+                <template #default="scope">
+                  {{ scope.$index + 1 }}
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('iotDevice.code')" align="center" prop="deviceCode" />
+              <ZmTableColumn :label="t('maintain.deviceName')" align="center" prop="deviceName" />
+              <ZmTableColumn
+                :label="t('maintain.status')"
+                align="center"
+                prop="status"
+                min-width="95">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.PMS_MAIN_STATUS" :value="scope.row.status" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('maintain.source')"
+                align="center"
+                prop="maintainType"
+                min-width="135" />
+              <ZmTableColumn
+                :label="t('iotMaintain.repairType')"
+                align="center"
+                prop="type"
+                min-width="135">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.PMS_MAIN_TYPE" :value="scope.row.type" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :label="t('maintain.shutDown')" align="center" prop="ifStop">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.ifStop" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                label="执行时间"
+                align="center"
+                prop="executeDate"
+                :formatter="dateFormatter"
+                min-width="180px" />
+              <ZmTableColumn :label="t('iotMaintain.oaFlowNo')" align="center" prop="oaFlowNo" />
+              <ZmTableColumn
+                :label="t('maintain.dept')"
+                align="center"
+                prop="deptName"
+                min-width="135" />
+              <ZmTableColumn
+                :label="t('inspect.createName')"
+                align="center"
+                prop="createName"
+                min-width="130" />
+              <ZmTableColumn
+                label="创建时间"
+                align="center"
+                prop="createTime"
+                :formatter="dateFormatter"
+                width="180px" />
+              <ZmTableColumn
+                :label="t('maintain.operation')"
+                align="center"
+                min-width="170"
+                fixed="right"
+                action>
+                <template #default="scope">
+                  <el-button link type="primary" @click="detail(scope.row.id)">
+                    {{ t('maintain.view') }}
+                  </el-button>
+                  <el-button
+                    link
+                    v-if="
+                      scope.row.status === 'tx' &&
+                      (scope.row.auditStatus === 20 || scope.row.auditStatus === null)
+                    "
+                    type="danger"
+                    @click="
+                      openForm('update', scope.row.id, scope.row.maintainPerson, scope.row.company)
+                    ">
+                    {{ t('maintain.fill') }}
+                  </el-button>
+                  <el-button
+                    link
+                    v-if="scope.row.status === 'personnel' && userid === scope.row.maintainPerson"
+                    type="danger"
+                    @click="openDialog(scope.row.id)">
+                    {{ t('maintain.maintainWay') }}
+                  </el-button>
+                  <el-button
+                    v-if="scope.row.status !== 'personnel'"
+                    link
+                    type="warning"
+                    @click="openRecord(scope.row.id)">
+                    {{ t('maintain.record') }}
+                  </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="600"
+    :before-close="handleClose"
+    append-to-body
+    :close-on-click-modal="false">
+    <el-form ref="methodFormRef" :model="form" :rules="rules" label-width="70px">
+      <el-form-item label="维修方式" prop="maintainMethod">
+        <el-select v-model="form.maintainMethod" placeholder="请选择维修方式">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.PMS_MAINTAIN_METHOD)"
+            :key="dict.label"
+            :label="dict.label"
+            :value="dict.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备注说明" prop="methodDescription">
+        <el-input
+          type="textarea"
+          v-model="form.methodDescription"
+          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>
+    </template>
+  </el-dialog>
 </template>
 
 <script setup lang="ts">
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotMaintainApi, IotMaintainVO } from '@/api/pms/maintain'
-import DeptTree from '@/views/system/user/DeptTree2.vue'
+import DeptTreeSelect from '@/components/DeptTreeSelect/index.vue'
 import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
 import { useUserStore } from '@/store/modules/user'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-const { ZmTable, ZmTableColumn } = useTableComponents()
+const { ZmTable, ZmTableColumn } = useTableComponents<IotMaintainVO>()
 
 /** 维修工单 列表 */
 defineOptions({ name: 'IotMaintain' })
@@ -346,7 +329,8 @@ const loading = ref(true) // 列表的加载中
 const list = ref<IotMaintainVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const dialogVisible = ref(false)
-let isLeftContentCollapsed = ref(false)
+const rootDeptId = 156
+const deptId = useUserStore().getUser.deptId || rootDeptId
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -460,6 +444,7 @@ const getList = async () => {
 /** 处理部门被点击 */
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 /** 搜索按钮操作 */
@@ -470,10 +455,20 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields()
   handleQuery()
 }
 
+const handleSizeChange = (val: number) => {
+  queryParams.pageSize = val
+  handleQuery()
+}
+
+const handleCurrentChange = (val: number) => {
+  queryParams.pageNo = val
+  getList()
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {
@@ -510,11 +505,118 @@ onMounted(() => {
 })
 </script>
 
-<style lang="scss" scoped>
-::v-deep .el-table__header-wrapper {
-  position: sticky !important;
+<style scoped>
+.maintain-query {
+  display: block;
+}
+
+.query-row {
+  display: flex;
+  flex: 1 1 auto;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12px 24px;
+  min-width: 0;
+}
+
+.query-row :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+.query-actions {
   width: 100%;
-  top: 0px;
-  z-index: 2000;
+  margin-top: 12px;
+}
+
+.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: 180px;
+}
+
+.query-control--small {
+  width: 140px;
+}
+
+.query-control--date {
+  width: 220px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+@media (width <= 1500px) {
+  .query-row {
+    gap: 12px 18px;
+  }
+
+  .query-control {
+    width: 168px;
+  }
+
+  .query-control--small {
+    width: 132px;
+  }
+
+  .query-control--date {
+    width: 210px;
+  }
+}
+
+@media (width <= 1200px) {
+  .maintain-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(.maintain-tree) {
+    grid-row: auto !important;
+    width: 100% !important;
+    height: 320px !important;
+    min-width: 0 !important;
+  }
+}
+
+@media (width <= 768px) {
+  .maintain-query {
+    padding: 12px;
+  }
+
+  .query-row,
+  .query-row :deep(.el-form-item),
+  .query-actions {
+    width: 100%;
+  }
+
+  .query-control,
+  .query-control--small,
+  .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%;
+    margin-left: 0;
+  }
 }
 </style>

+ 43 - 22
src/views/pms/maintain/material/MaintainMaterialDrawer.vue

@@ -7,33 +7,42 @@
     :show-close="false"
     direction="rtl"
     :size="computedSize"
-    :before-close="handleClose"
-  >
+    :before-close="handleClose">
     <el-form
       ref="drawerRef"
       :model="drawerFormData"
       :rules="formRules"
       label-width="100px"
-      v-loading="formLoading"
-    >
+      v-loading="formLoading">
       <el-form-item :label="t('stock.MaterialMaster')" prop="code1" style="margin-bottom: 30px">
         <el-select
           v-model="drawerFormData.code1"
           :model-value="materialLabel"
           :placeholder="t('stock.MaterialMaster')"
-          @click="openForm"
-        />
+          @click="openForm" />
       </el-form-item>
-      <el-form-item :label="t('workOrderMaterial.materialCode')" prop="code1" style="margin-bottom: 30px">
+      <el-form-item
+        :label="t('workOrderMaterial.materialCode')"
+        prop="code1"
+        style="margin-bottom: 30px">
         <el-input v-model="drawerFormData.code1" disabled />
       </el-form-item>
-      <el-form-item :label="t('workOrderMaterial.materialName')" prop="name1" style="margin-bottom: 30px">
+      <el-form-item
+        :label="t('workOrderMaterial.materialName')"
+        prop="name1"
+        style="margin-bottom: 30px">
         <el-input v-model="drawerFormData.name1" disabled />
       </el-form-item>
       <el-form-item :label="t('workOrderMaterial.unit')" prop="unit1" style="margin-bottom: 30px">
-        <el-input v-model="drawerFormData.unit1" disabled :placeholder="t('workOrderMaterial.unit')" />
+        <el-input
+          v-model="drawerFormData.unit1"
+          disabled
+          :placeholder="t('workOrderMaterial.unit')" />
       </el-form-item>
-      <el-form-item :label="t('workOrderMaterial.ConsumptionQuantity')" prop="depleteCount1" style="margin-bottom: 30px">
+      <el-form-item
+        :label="t('workOrderMaterial.ConsumptionQuantity')"
+        prop="depleteCount1"
+        style="margin-bottom: 30px">
         <el-input-number
           v-model="drawerFormData.depleteCount1"
           :min="0"
@@ -41,25 +50,31 @@
           :step="0.1"
           style="width: 50%"
           class="w-100%"
-          controls-position="right"
-        />
+          controls-position="right" />
       </el-form-item>
-      <el-form-item :label="t('workOrderMaterial.unitPrice')" prop="price1" style="margin-bottom: 30px">
+      <el-form-item
+        :label="t('workOrderMaterial.unitPrice')"
+        prop="price1"
+        style="margin-bottom: 30px">
         <el-input-number
           v-model="drawerFormData.price1"
           :min="0"
           :precision="2"
           :step="0.1"
           style="width: 50%"
-          controls-position="right"
-        />
+          controls-position="right" />
       </el-form-item>
       <el-form-item :label="t('deviceForm.remark')" prop="remark1" style="margin-bottom: 30px">
-        <el-input v-model="drawerFormData.remark1" type="textarea" :placeholder="t('deviceForm.remarkHolder')" />
+        <el-input
+          v-model="drawerFormData.remark1"
+          type="textarea"
+          :placeholder="t('deviceForm.remarkHolder')" />
       </el-form-item>
     </el-form>
     <template #footer>
-      <el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('common.ok') }}</el-button>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">{{
+        t('common.ok')
+      }}</el-button>
       <el-button @click="handleClose">{{ t('common.cancel') }}</el-button>
     </template>
   </el-drawer>
@@ -73,6 +88,7 @@ import { CommonStatusEnum } from '@/utils/constants'
 import MaterialSelect from './MaterialSelect.vue'
 const { t } = useI18n() // 国际化
 const drawerVisible = ref<boolean>(false)
+const formLoading = ref(false)
 const emit = defineEmits(['update:modelValue', 'add'])
 const materialLabel = ref('') // 表单的类型:create - 新增;update - 修改
 defineOptions({
@@ -125,7 +141,7 @@ const formRules = reactive({
   code1: [{ required: true, message: '物料编码不能为空', trigger: 'blur' }],
   name1: [{ required: true, message: '物料名称不能为空', trigger: 'blur' }],
   depleteCount1: [{ required: true, message: '消耗数量不能为空', trigger: 'blur' }],
-  price1:[{ required: true, message: '单价不能为空', trigger: 'blur' }],
+  price1: [{ required: true, message: '单价不能为空', trigger: 'blur' }]
 })
 const materialFormRef = ref()
 const openForm = () => {
@@ -163,10 +179,15 @@ const handleClose = () => {
 }
 const drawerRef = ref()
 const submitForm = async () => {
-  await drawerRef.value.validate()
-  drawerFormData.value.sourceType = '手动添加'
-  emit('add', drawerFormData.value)
-  emit('update:modelValue', false)
+  formLoading.value = true
+  try {
+    await drawerRef.value.validate()
+    drawerFormData.value.sourceType = '手动添加'
+    emit('add', drawerFormData.value)
+    emit('update:modelValue', false)
+  } finally {
+    formLoading.value = false
+  }
 }
 
 defineExpose({ openDrawer, closeDrawer }) // 暴露方法给父组件

+ 540 - 287
src/views/pms/stat/maintain.vue

@@ -1,186 +1,156 @@
 <template>
-  <!-- 第一行:统计卡片行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-gradient-1" shadow="hover">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:data-line" class="stat-icon" />
-            </div>
-            <div class="stat-title">MTTR</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-label">平均解决时间</div>
-            <div class="stat-value">{{ statsData.productCategoryCount }}</div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-gradient-2" shadow="hover">
-        <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-blue-500">{{ week.failureWeek }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">维修工单</div>
-              <div class="stat-sub-value text-white">{{ week.maintainWeek }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-gradient-3" shadow="hover">
-        <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-blue-500">{{ month.failureMonth }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">维修工单</div>
-              <div class="stat-sub-value text-white">{{ month.maintainMonth }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-gradient-4" shadow="hover">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:pie-chart" 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-blue-500">{{ total.failureTotal }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">维修工单</div>
-              <div class="stat-sub-value text-white">{{ total.maintainTotal }}</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"></div>
-              <span class="chart-title">故障上报状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="6" class="chart-item">
-            <div ref="reportingChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-blue"></span>
-              <span class="label-text">上报中</span>
-            </div>
-          </el-col>
-          <el-col :span="6" class="chart-item">
-            <div ref="dealFinishedChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-orange"></span>
-              <span class="label-text">处理完成</span>
-            </div>
-          </el-col>
-          <el-col :span="6" class="chart-item">
-            <div ref="transOrderChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-purple"></span>
-              <span class="label-text">转工单</span>
-            </div>
-          </el-col>
-          <el-col :span="6" class="chart-item">
-            <div ref="orderFinishChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-cyan"></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"></div>
-              <span class="chart-title">维修工单状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="12" class="chart-item">
-            <div ref="writeChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-indigo"></span>
-              <span class="label-text">待填写</span>
-            </div>
-          </el-col>
-          <el-col :span="12" class="chart-item">
-            <div ref="finishedChartRef" class="gauge-chart"></div>
-            <div class="chart-label">
-              <span class="label-dot label-dot-emerald"></span>
-              <span class="label-text">已完成</span>
-            </div>
-          </el-col>
-        </el-row>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第三行:消息统计行 -->
-  <el-row>
-    <el-col :span="24">
-      <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"></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="maintain-kb-wrapper bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="maintain-kb-screen bg" :style="targetAreaStyle">
+        <header class="header">维修统计</header>
+
+        <main class="maintain-kb-content">
+          <section class="summary-grid">
+            <button class="summary-card stat-card" type="button">
+              <span class="stat-icon-wrapper">
+                <Icon icon="ep:data-line" class="stat-icon" />
+              </span>
+              <span class="stat-title">MTTR</span>
+              <strong class="stat-value">{{ statsData.productCategoryCount }}</strong>
+              <span class="stat-label">平均解决时间</span>
+            </button>
+
+            <button class="summary-card stat-card" type="button">
+              <span class="stat-icon-wrapper">
+                <Icon icon="ep:trend-charts" class="stat-icon" />
+              </span>
+              <span class="stat-title">近一周工单数量</span>
+              <span class="stat-pair">
+                <span>
+                  <em>故障上报</em>
+                  <strong>{{ week.failureWeek }}</strong>
+                </span>
+                <span>
+                  <em>维修工单</em>
+                  <strong>{{ week.maintainWeek }}</strong>
+                </span>
+              </span>
+            </button>
+
+            <button class="summary-card stat-card" type="button">
+              <span class="stat-icon-wrapper">
+                <Icon icon="ep:calendar" class="stat-icon" />
+              </span>
+              <span class="stat-title">近一月工单数量</span>
+              <span class="stat-pair">
+                <span>
+                  <em>故障上报</em>
+                  <strong>{{ month.failureMonth }}</strong>
+                </span>
+                <span>
+                  <em>维修工单</em>
+                  <strong>{{ month.maintainMonth }}</strong>
+                </span>
+              </span>
+            </button>
+
+            <button class="summary-card stat-card" type="button">
+              <span class="stat-icon-wrapper">
+                <Icon icon="ep:pie-chart" class="stat-icon" />
+              </span>
+              <span class="stat-title">工单总数量</span>
+              <span class="stat-pair">
+                <span>
+                  <em>故障上报</em>
+                  <strong>{{ total.failureTotal }}</strong>
+                </span>
+                <span>
+                  <em>维修工单</em>
+                  <strong>{{ total.maintainTotal }}</strong>
+                </span>
+              </span>
+            </button>
+          </section>
+
+          <section class="status-grid">
+            <el-card class="panel chart-panel" shadow="never">
+              <div class="panel-title">
+                <div class="icon-decorator">
+                  <span></span>
+                  <span></span>
+                </div>
+                故障上报状态统计
+              </div>
+              <div class="gauge-grid gauge-grid--failure">
+                <div class="chart-item">
+                  <div ref="reportingChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-blue"></span>
+                    <span class="label-text">上报中</span>
+                  </div>
+                </div>
+                <div class="chart-item">
+                  <div ref="dealFinishedChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-orange"></span>
+                    <span class="label-text">处理完成</span>
+                  </div>
+                </div>
+                <div class="chart-item">
+                  <div ref="transOrderChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-purple"></span>
+                    <span class="label-text">转工单</span>
+                  </div>
+                </div>
+                <div class="chart-item">
+                  <div ref="orderFinishChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-cyan"></span>
+                    <span class="label-text">工单处理完成</span>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+
+            <el-card class="panel chart-panel" shadow="never">
+              <div class="panel-title">
+                <div class="icon-decorator">
+                  <span></span>
+                  <span></span>
+                </div>
+                维修工单状态统计
+              </div>
+              <div class="gauge-grid gauge-grid--maintain">
+                <div class="chart-item">
+                  <div ref="writeChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-indigo"></span>
+                    <span class="label-text">待填写</span>
+                  </div>
+                </div>
+                <div class="chart-item">
+                  <div ref="finishedChartRef" class="gauge-chart"></div>
+                  <div class="chart-label">
+                    <span class="label-dot label-dot-emerald"></span>
+                    <span class="label-text">已完成</span>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </section>
+
+          <section class="trend-section">
+            <el-card class="panel trend-panel" shadow="never">
+              <div class="panel-title">
+                <div class="icon-decorator">
+                  <span></span>
+                  <span></span>
+                </div>
+                近一年数量统计
+              </div>
+              <div ref="chartContainer" class="bar-chart-container"></div>
+            </el-card>
+          </section>
+        </main>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="Index">
@@ -203,6 +173,7 @@ import {
 } from '@/api/iot/statistics'
 import { formatDate } from '@/utils/formatTime'
 import { IotStatApi } from '@/api/pms/stat'
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
 
 // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
 
@@ -232,6 +203,37 @@ const queryParams = reactive({
   startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
   endTime: Date.now() // 设置默认结束时间为当前时间
 })
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
+
+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`
+}))
+
+function 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(handleResize)
+  })
+}
 
 // const deviceCountChartRef = ref() // 设备数量统计的图表
 const reportingChartRef = ref() // 在线设备统计的图表
@@ -241,6 +243,7 @@ const orderFinishChartRef = ref()
 const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
 const writeChartRef = ref() // 上下行消息量统计的图表
 const finishedChartRef = ref() // 上下行消息量统计的图表
+const chartInstances: echarts.ECharts[] = []
 
 // 基础统计数据
 // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
@@ -357,7 +360,6 @@ const getStats = async () => {
   })
   IotStatApi.getMainStatus().then((res) => {
     status.value = res
-    console.log(JSON.stringify(status.value))
     initCharts()
   })
   // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
@@ -371,6 +373,7 @@ const getStats = async () => {
 
 /** 初始化图表 */
 const initCharts = () => {
+  disposeChartInstances()
   // 设备数量统计
   // echarts.init(deviceCountChartRef.value).setOption({
   //   tooltip: {
@@ -456,7 +459,11 @@ const initCharts = () => {
 
 /** 初始化仪表盘图表 */
 const initGaugeChart = (el: any, value: number, color: string) => {
-  echarts.init(el).setOption({
+  if (!el) return
+
+  const chart = echarts.init(el)
+  chartInstances.push(chart)
+  chart.setOption({
     series: [
       {
         type: 'gauge',
@@ -620,7 +627,11 @@ const initMessageChart = () => {
 }
 
 const chartContainer = ref(null)
-let chartInstance = null
+let chartInstance: echarts.ECharts | null = null
+
+const disposeChartInstances = () => {
+  chartInstances.splice(0).forEach((instance) => instance.dispose())
+}
 
 // 生成过去12个月份的标签 (格式: YYYY-MM)
 const generateMonthLabels = () => {
@@ -731,49 +742,87 @@ const initChart = async () => {
   }
 
   // 初始化图表
+  chartInstance?.dispose()
   chartInstance = echarts.init(chartContainer.value)
   chartInstance.setOption(option)
 
-  // 窗口缩放监听
-  window.addEventListener('resize', handleResize)
   handleResize()
 }
 
 // 自适应调整
 const handleResize = () => {
   chartInstance?.resize()
+  chartInstances.forEach((instance) => instance.resize())
 }
 /** 初始化 */
 onMounted(() => {
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
+  }
+  window.addEventListener('resize', updateScale)
+
   getStats()
   initChart()
 })
 onUnmounted(() => {
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
+  disposeChartInstances()
   chartInstance?.dispose()
-  window.removeEventListener('resize', handleResize)
+  chartInstance = null
 })
 </script>
 
 <style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+// 响应式优化
+@media (width <= 1200px) {
+  .stat-value {
+    font-size: 28px;
+  }
+
+  .stat-sub-value {
+    font-size: 24px;
+  }
+}
+
+@media (width <= 768px) {
+  .stat-card {
+    margin-bottom: 16px;
+  }
+
+  .chart-item {
+    padding: 8px;
+  }
+
+  .gauge-chart {
+    height: 140px;
+  }
+}
+
 // 统计卡片基础样式
 .stat-card {
-  border-radius: 12px;
-  border: 1px solid rgba(207, 220, 237, 0.9);
-  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-  overflow: hidden;
   position: relative;
-  background: linear-gradient(180deg, #ffffff 0%, #f4f8ff 100%);
+  overflow: hidden;
+  background: linear-gradient(180deg, #fff 0%, #f4f8ff 100%);
+  border: 1px solid rgb(207 220 237 / 90%);
+  border-radius: 12px;
   box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.95),
-    inset 0 -10px 24px rgba(210, 225, 244, 0.26),
-    0 10px 24px rgba(32, 66, 120, 0.08);
+    inset 0 1px 0 rgb(255 255 255 / 95%),
+    inset 0 -10px 24px rgb(210 225 244 / 26%),
+    0 10px 24px rgb(32 66 120 / 8%);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
   &:hover {
     transform: translateY(-4px);
     box-shadow:
-      inset 0 1px 0 rgba(255, 255, 255, 0.98),
-      inset 0 -12px 26px rgba(204, 220, 243, 0.32),
-      0 16px 32px rgba(32, 66, 120, 0.12);
+      inset 0 1px 0 rgb(255 255 255 / 98%),
+      inset 0 -12px 26px rgb(204 220 243 / 32%),
+      0 16px 32px rgb(32 66 120 / 12%);
   }
 
   :deep(.el-card__body) {
@@ -783,23 +832,23 @@ onUnmounted(() => {
 
 // 渐变色背景
 .stat-card-gradient-1 {
-  background: radial-gradient(circle at top left, rgba(121, 164, 255, 0.16), transparent 42%),
-    linear-gradient(180deg, #ffffff 0%, #f4f8ff 100%);
+  background: radial-gradient(circle at top left, rgb(121 164 255 / 16%), transparent 42%),
+    linear-gradient(180deg, #fff 0%, #f4f8ff 100%);
 }
 
 .stat-card-gradient-2 {
-  background: radial-gradient(circle at top left, rgba(118, 186, 255, 0.14), transparent 42%),
-    linear-gradient(180deg, #ffffff 0%, #f3f8ff 100%);
+  background: radial-gradient(circle at top left, rgb(118 186 255 / 14%), transparent 42%),
+    linear-gradient(180deg, #fff 0%, #f3f8ff 100%);
 }
 
 .stat-card-gradient-3 {
-  background: radial-gradient(circle at top left, rgba(96, 154, 241, 0.14), transparent 42%),
-    linear-gradient(180deg, #ffffff 0%, #f5f9ff 100%);
+  background: radial-gradient(circle at top left, rgb(96 154 241 / 14%), transparent 42%),
+    linear-gradient(180deg, #fff 0%, #f5f9ff 100%);
 }
 
 .stat-card-gradient-4 {
-  background: radial-gradient(circle at top left, rgba(137, 176, 242, 0.14), transparent 42%),
-    linear-gradient(180deg, #ffffff 0%, #f4f8fe 100%);
+  background: radial-gradient(circle at top left, rgb(137 176 242 / 14%), transparent 42%),
+    linear-gradient(180deg, #fff 0%, #f4f8fe 100%);
 }
 
 // 统计内容区域
@@ -816,18 +865,18 @@ onUnmounted(() => {
 }
 
 .stat-icon-wrapper {
+  display: flex;
   width: 48px;
   height: 48px;
-  background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(232, 240, 252, 0.92) 100%);
-  border: 1px solid rgba(205, 219, 239, 0.9);
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(232 240 252 / 92%) 100%);
+  border: 1px solid rgb(205 219 239 / 90%);
   border-radius: 12px;
-  display: flex;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 95%),
+    inset 0 -6px 12px rgb(205 220 242 / 34%),
+    0 6px 14px rgb(66 104 168 / 8%);
   align-items: center;
   justify-content: center;
-  box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.95),
-    inset 0 -6px 12px rgba(205, 220, 242, 0.34),
-    0 6px 14px rgba(66, 104, 168, 0.08);
 }
 
 .stat-icon {
@@ -842,8 +891,8 @@ onUnmounted(() => {
 }
 
 .stat-divider {
-  border-color: rgba(201, 214, 234, 0.85);
   margin: 12px 0;
+  border-color: rgb(201 214 234 / 85%);
 }
 
 .stat-body {
@@ -853,16 +902,16 @@ onUnmounted(() => {
 }
 
 .stat-label {
+  margin-bottom: 4px;
   font-size: 13px;
   color: #7182a1;
-  margin-bottom: 4px;
 }
 
 .stat-value {
   font-size: 36px;
   font-weight: 700;
-  color: #1f2f54;
   line-height: 1;
+  color: #1f2f54;
 }
 
 .stat-item {
@@ -885,32 +934,28 @@ onUnmounted(() => {
 
 // 图表卡片增强样式
 .chart-card-enhanced {
+  background: linear-gradient(180deg, #fff 0%, #f5f9ff 100%);
+  border: 1px solid rgb(207 220 237 / 90%);
   border-radius: 12px;
-  border: 1px solid rgba(207, 220, 237, 0.9);
-  transition: all 0.3s ease;
-  background: linear-gradient(180deg, #ffffff 0%, #f5f9ff 100%);
   box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.96),
-    inset 0 -12px 26px rgba(212, 226, 244, 0.2),
-    0 10px 24px rgba(32, 66, 120, 0.07);
+    inset 0 1px 0 rgb(255 255 255 / 96%),
+    inset 0 -12px 26px rgb(212 226 244 / 20%),
+    0 10px 24px rgb(32 66 120 / 7%);
+  transition: all 0.3s ease;
 
   &:hover {
+    border-color: rgb(193 209 231 / 95%);
     box-shadow:
-      inset 0 1px 0 rgba(255, 255, 255, 0.98),
-      inset 0 -14px 30px rgba(208, 223, 244, 0.24),
-      0 16px 30px rgba(32, 66, 120, 0.1);
-    border-color: rgba(193, 209, 231, 0.95);
+      inset 0 1px 0 rgb(255 255 255 / 98%),
+      inset 0 -14px 30px rgb(208 223 244 / 24%),
+      0 16px 30px rgb(32 66 120 / 10%);
   }
 
   :deep(.el-card__header) {
     padding: 16px 20px;
-    border-bottom: 1px solid rgba(223, 232, 245, 0.95);
-    background: linear-gradient(
-      90deg,
-      rgba(244, 248, 255, 0.96) 0%,
-      rgba(255, 255, 255, 0.98) 100%
-    );
-    box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.78);
+    background: linear-gradient(90deg, rgb(244 248 255 / 96%) 0%, rgb(255 255 255 / 98%) 100%);
+    border-bottom: 1px solid rgb(223 232 245 / 95%);
+    box-shadow: inset 0 -1px 0 rgb(255 255 255 / 78%);
   }
 
   :deep(.el-card__body) {
@@ -933,18 +978,18 @@ onUnmounted(() => {
 .chart-title-dot {
   width: 8px;
   height: 8px;
-  border-radius: 50%;
   background: linear-gradient(135deg, #84aef7 0%, #5e87d8 100%);
+  border-radius: 50%;
   box-shadow:
-    0 0 0 4px rgba(122, 162, 235, 0.12),
-    0 2px 6px rgba(86, 124, 189, 0.24);
+    0 0 0 4px rgb(122 162 235 / 12%),
+    0 2px 6px rgb(86 124 189 / 24%);
 }
 
 .chart-title {
   font-size: 16px;
   font-weight: 600;
-  color: #233554;
   letter-spacing: 0.3px;
+  color: #233554;
 }
 
 .chart-grid {
@@ -953,21 +998,17 @@ onUnmounted(() => {
 
 .chart-item {
   display: flex;
-  flex-direction: column;
-  align-items: center;
   padding: 12px;
-  transition: all 0.2s ease;
   border-radius: 12px;
+  transition: all 0.2s ease;
+  flex-direction: column;
+  align-items: center;
 
   &:hover {
-    background: linear-gradient(
-      180deg,
-      rgba(244, 248, 255, 0.9) 0%,
-      rgba(237, 244, 255, 0.95) 100%
-    );
+    background: linear-gradient(180deg, rgb(244 248 255 / 90%) 0%, rgb(237 244 255 / 95%) 100%);
     box-shadow:
-      inset 0 1px 0 rgba(255, 255, 255, 0.86),
-      inset 0 -8px 16px rgba(212, 225, 243, 0.22);
+      inset 0 1px 0 rgb(255 255 255 / 86%),
+      inset 0 -8px 16px rgb(212 225 243 / 22%);
   }
 }
 
@@ -978,103 +1019,315 @@ onUnmounted(() => {
 
 .chart-label {
   display: flex;
-  align-items: center;
-  gap: 6px;
-  margin-top: 8px;
   padding: 6px 12px;
-  background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(242, 247, 255, 0.96) 100%);
-  border: 1px solid rgba(216, 228, 244, 0.95);
+  margin-top: 8px;
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(242 247 255 / 96%) 100%);
+  border: 1px solid rgb(216 228 244 / 95%);
   border-radius: 20px;
-  transition: all 0.2s ease;
   box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.9),
-    0 4px 10px rgba(58, 90, 145, 0.06);
+    inset 0 1px 0 rgb(255 255 255 / 90%),
+    0 4px 10px rgb(58 90 145 / 6%);
+  transition: all 0.2s ease;
+  align-items: center;
+  gap: 6px;
 
   .chart-item:hover & {
-    background: linear-gradient(
-      180deg,
-      rgba(255, 255, 255, 0.98) 0%,
-      rgba(238, 244, 255, 0.98) 100%
-    );
-    border-color: rgba(201, 217, 239, 0.98);
+    background: linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(238 244 255 / 98%) 100%);
+    border-color: rgb(201 217 239 / 98%);
   }
 }
 
 .label-dot {
+  display: inline-block;
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  display: inline-block;
 }
 
 .label-dot-blue {
   background: #3b82f6;
-  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
+  box-shadow: 0 0 0 2px rgb(59 130 246 / 20%);
 }
 
 .label-dot-orange {
   background: #f97316;
-  box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.2);
+  box-shadow: 0 0 0 2px rgb(249 115 22 / 20%);
 }
 
 .label-dot-purple {
   background: #8b5cf6;
-  box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
+  box-shadow: 0 0 0 2px rgb(139 92 246 / 20%);
 }
 
 .label-dot-cyan {
   background: #06b6d4;
-  box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.2);
+  box-shadow: 0 0 0 2px rgb(6 182 212 / 20%);
 }
 
 .label-dot-indigo {
   background: #6366f1;
-  box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
+  box-shadow: 0 0 0 2px rgb(99 102 241 / 20%);
 }
 
 .label-dot-emerald {
   background: #10b981;
-  box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
+  box-shadow: 0 0 0 2px rgb(16 185 129 / 20%);
 }
 
 .label-text {
   font-size: 13px;
-  color: #64748f;
   font-weight: 500;
+  color: #64748f;
 }
 
 .bar-chart-container {
-  height: 350px;
   width: 100%;
+  height: 350px;
+  background: linear-gradient(180deg, rgb(252 254 255 / 90%) 0%, rgb(244 249 255 / 92%) 100%);
   border-radius: 12px;
-  background: linear-gradient(180deg, rgba(252, 254, 255, 0.9) 0%, rgba(244, 249, 255, 0.92) 100%);
   box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.92),
-    inset 0 -10px 20px rgba(217, 230, 245, 0.16);
+    inset 0 1px 0 rgb(255 255 255 / 92%),
+    inset 0 -10px 20px rgb(217 230 245 / 16%);
 }
 
-// 响应式优化
-@media (max-width: 1200px) {
-  .stat-value {
-    font-size: 28px;
+.maintain-kb-wrapper {
+  overflow: hidden;
+}
+
+.maintain-kb-screen {
+  display: flex;
+  overflow: hidden;
+  color: #24364f;
+  flex-direction: column;
+}
+
+.maintain-kb-content {
+  display: flex;
+  height: calc(100% - 52px * var(--kb-scale));
+  min-height: 0;
+  padding: calc(44px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
+  flex-direction: column;
+  gap: calc(12px * var(--kb-scale));
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: calc(12px * var(--kb-scale));
+  flex: 0 0 calc(154px * var(--kb-scale));
+}
+
+.status-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: calc(12px * var(--kb-scale));
+  flex: 0 0 calc(300px * var(--kb-scale));
+  min-height: 0;
+}
+
+.trend-section {
+  min-height: 0;
+  flex: 1;
+}
+
+.chart-panel,
+.trend-panel {
+  height: 100%;
+  overflow: hidden;
+
+  :deep(.el-card__body) {
+    display: flex;
+    height: 100%;
+    min-height: 0;
+    padding: 0;
+    flex-direction: column;
   }
+}
 
-  .stat-sub-value {
-    font-size: 24px;
+.stat-card {
+  display: grid;
+  height: 100%;
+  min-width: 0;
+  padding: calc(14px * var(--kb-scale));
+  text-align: left;
+  cursor: default;
+  background: linear-gradient(180deg, rgb(255 255 255 / 42%) 0%, rgb(213 227 249 / 28%) 100%);
+  border: 1px solid rgb(255 255 255 / 74%);
+  border-radius: calc(16px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 calc(6px * var(--kb-scale)) calc(14px * var(--kb-scale)) rgb(63 103 171 / 7%);
+  grid-template-rows: auto auto 1fr;
+  grid-template-columns: auto 1fr;
+  gap: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
+
+  &:hover {
+    border-color: rgb(76 132 214 / 26%);
+    transform: translateY(calc(-2px * var(--kb-scale)));
+    box-shadow:
+      inset 0 1px 0 rgb(255 255 255 / 84%),
+      0 calc(10px * var(--kb-scale)) calc(20px * var(--kb-scale)) rgb(63 103 171 / 11%);
   }
 }
 
-@media (max-width: 768px) {
-  .stat-card {
-    margin-bottom: 16px;
+.stat-icon-wrapper {
+  display: flex;
+  width: calc(44px * var(--kb-scale));
+  height: calc(44px * var(--kb-scale));
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(232 240 252 / 92%) 100%);
+  border: 1px solid rgb(205 219 239 / 90%);
+  border-radius: calc(12px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 95%),
+    inset 0 calc(-6px * var(--kb-scale)) calc(12px * var(--kb-scale)) rgb(205 220 242 / 34%),
+    0 calc(6px * var(--kb-scale)) calc(14px * var(--kb-scale)) rgb(66 104 168 / 8%);
+  align-items: center;
+  justify-content: center;
+  grid-row: 1 / span 2;
+}
+
+.stat-icon {
+  font-size: calc(24px * var(--kb-scale));
+  color: #1f5bb8;
+}
+
+.stat-title {
+  min-width: 0;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(22px * var(--kb-scale));
+  font-weight: normal;
+  line-height: 1.1;
+  color: #03409b;
+}
+
+.stat-label {
+  font-size: calc(13px * var(--kb-scale));
+  font-weight: 700;
+  color: #536986;
+}
+
+.stat-value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(42px * var(--kb-scale));
+  font-weight: normal;
+  line-height: 1;
+  color: #1f5bb8;
+  grid-column: 1 / -1;
+}
+
+.stat-pair {
+  display: grid;
+  min-width: 0;
+  grid-column: 1 / -1;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: calc(10px * var(--kb-scale));
+
+  span {
+    display: flex;
+    min-width: 0;
+    padding: calc(8px * var(--kb-scale)) calc(10px * var(--kb-scale));
+    overflow: hidden;
+    background: linear-gradient(180deg, rgb(255 255 255 / 30%) 0%, rgb(213 227 249 / 18%) 100%);
+    border: 1px solid rgb(255 255 255 / 48%);
+    border-radius: calc(10px * var(--kb-scale));
+    flex-direction: column;
+    gap: calc(6px * var(--kb-scale));
   }
 
-  .chart-item {
-    padding: 8px;
+  em {
+    overflow: hidden;
+    font-size: calc(12px * var(--kb-scale));
+    font-style: normal;
+    font-weight: 700;
+    color: #536986;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
 
-  .gauge-chart {
-    height: 140px;
+  strong {
+    font-family: YouSheBiaoTiHei, sans-serif;
+    font-size: calc(28px * var(--kb-scale));
+    font-weight: normal;
+    line-height: 1;
+    color: #1f5bb8;
   }
 }
+
+.gauge-grid {
+  display: grid;
+  min-height: 0;
+  padding: calc(12px * var(--kb-scale)) calc(14px * var(--kb-scale)) calc(16px * var(--kb-scale));
+  gap: calc(12px * var(--kb-scale));
+  flex: 1;
+}
+
+.gauge-grid--failure {
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.gauge-grid--maintain {
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.chart-item {
+  display: flex;
+  min-width: 0;
+  min-height: 0;
+  padding: calc(8px * var(--kb-scale));
+  background: linear-gradient(180deg, rgb(255 255 255 / 42%) 0%, rgb(213 227 249 / 28%) 100%);
+  border: 1px solid rgb(255 255 255 / 54%);
+  border-radius: calc(14px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 64%),
+    0 calc(5px * var(--kb-scale)) calc(12px * var(--kb-scale)) rgb(63 103 171 / 6%);
+  align-items: center;
+  flex-direction: column;
+}
+
+.gauge-chart {
+  width: 100%;
+  min-height: 0;
+  flex: 1;
+}
+
+.chart-label {
+  display: flex;
+  height: calc(28px * var(--kb-scale));
+  padding: 0 calc(10px * var(--kb-scale));
+  background: linear-gradient(180deg, rgb(255 255 255 / 88%) 0%, rgb(242 247 255 / 76%) 100%);
+  border: 1px solid rgb(216 228 244 / 78%);
+  border-radius: calc(999px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 90%),
+    0 calc(4px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(58 90 145 / 6%);
+  align-items: center;
+  gap: calc(6px * var(--kb-scale));
+}
+
+.label-dot {
+  width: calc(8px * var(--kb-scale));
+  height: calc(8px * var(--kb-scale));
+}
+
+.label-text {
+  overflow: hidden;
+  font-size: calc(13px * var(--kb-scale));
+  font-weight: 700;
+  color: #536986;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.bar-chart-container {
+  width: 100%;
+  min-height: 0;
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale)) calc(14px * var(--kb-scale));
+  background: transparent;
+  box-shadow: none;
+  flex: 1;
+}
+
+:deep(.el-card) {
+  --el-card-bg-color: transparent;
+}
 </style>

Неке датотеке нису приказане због велике количине промена