快乐的昕的电脑
2025-11-14 68b182f81924b9ea2f11b9d5638ffb9e8df1a6c2
components/EquipmentInspection.vue
@@ -1,58 +1,78 @@
<template>
   <view class="inspection-page">
      <!-- 顶部显示机台信息与年份选择 -->
      <!-- 顶部标题和机台信息 -->
      <view class="header">
         <view class="machine-info">
            <text>当前机台:</text>
            <text class="machine-text">{{ machineNo || '未绑定' }}</text>
         <view class="title-row">
            <text class="page-title">设备点检表</text>
         </view>
         <view class="year-picker">
            <text class="year-label">点检年份</text>
            <picker mode="selector"
                  :range="yearOptions"
                  :value="yearPickerIndex"
                  @change="handleYearChange">
               <view class="picker-trigger">{{ currentYear }} 年</view>
            </picker>
         </view>
      </view>
      <view class="section">
         <view class="section-title">日点检(31 天)</view>
         <view class="grid days-grid">
            <view v-for="(day, index) in days"
                 :key="`day-${day}`"
                 class="grid-cell"
                 :class="{ checked: dailyChecks[index] }"
                 @click="toggleDay(index)">
               <text class="grid-text">{{ day }}日</text>
         <view class="info-row">
            <view class="info-item">
               <text class="info-label">设备名称:</text>
               <text class="info-value">{{ machineNo || '未绑定' }}号齿轮机</text>
            </view>
            <view class="info-item">
               <text class="info-label">日期:</text>
               <picker mode="date" fields="month" :value="currentDate" @change="handleDateChange">
                  <view class="date-picker">{{ displayDate }}</view>
               </picker>
            </view>
         </view>
         <view class="summary">已点检:{{ checkedDaysCount }}/31</view>
      </view>
      <view class="section">
         <view class="section-title">月点检(12 月)</view>
         <view class="grid months-grid">
            <view v-for="(month, index) in months"
                 :key="`month-${month}`"
                 class="grid-cell"
                 :class="{ checked: monthlyChecks[index] }"
                 @click="toggleMonth(index)">
               <text class="grid-text">{{ month }}月</text>
            </view>
         </view>
         <view class="summary">已点检:{{ checkedMonthsCount }}/12</view>
      <!-- 点检表格 -->
      <view class="table-container">
         <table class="inspection-table">
            <thead>
               <tr>
                  <th class="col-category" rowspan="2">类别</th>
                  <th class="col-item" rowspan="2">点检、保养项目内容</th>
                  <th class="col-cycle" rowspan="2">周期</th>
                  <th class="col-days" colspan="31">日期</th>
               </tr>
               <tr>
                  <th v-for="day in 31" :key="`day-${day}`" class="day-header">{{ day }}</th>
               </tr>
            </thead>
            <tbody>
               <!-- 日常点检项目 -->
               <tr v-for="(item, idx) in dailyItems" :key="`daily-${idx}`">
                  <td v-if="idx === 0" :rowspan="dailyItems.length" class="category-cell">日常点检</td>
                  <td class="item-cell">{{ item.name }}</td>
                  <td class="cycle-cell">{{ item.cycle }}</td>
                  <td v-for="day in 31"
                     :key="`daily-${idx}-${day}`"
                     class="check-cell"
                     :class="{ checked: dailyChecks[idx][day - 1] }"
                     @click="toggleCheck('daily', idx, day - 1)">
                     <text v-if="dailyChecks[idx][day - 1]">●</text>
                  </td>
               </tr>
               <!-- 月度点检项目 -->
               <tr v-for="(item, idx) in monthlyItems" :key="`monthly-${idx}`">
                  <td v-if="idx === 0" :rowspan="monthlyItems.length" class="category-cell">月度点检</td>
                  <td class="item-cell">{{ item.name }}</td>
                  <td class="cycle-cell">{{ item.cycle }}</td>
                  <td v-for="day in 31"
                     :key="`monthly-${idx}-${day}`"
                     class="check-cell"
                     :class="{ checked: monthlyChecks[idx][day - 1] }"
                     @click="toggleCheck('monthly', idx, day - 1)">
                     <text v-if="monthlyChecks[idx][day - 1]">●</text>
                  </td>
               </tr>
            </tbody>
         </table>
      </view>
      <!-- 底部按钮 -->
      <view class="actions">
         <button class="btn-primary"
         <button class="btn-save"
               :loading="saving"
               :disabled="saving || !machineNo"
               @click="handleSave">
            保存
         </button>
         <button class="btn-secondary"
         <button class="btn-clear"
               :disabled="saving"
               @click="resetChecks">
            清空
@@ -73,35 +93,40 @@
      }
   },
   data() {
      const currentYear = new Date().getFullYear()
      const now = new Date()
      const year = now.getFullYear()
      const month = String(now.getMonth() + 1).padStart(2, '0')
      return {
         // 当前年份用于区分不同年度的点检表
         currentYear,
         yearOptions: this.buildYearOptions(currentYear),
         dailyChecks: Array(31).fill(false),
         monthlyChecks: Array(12).fill(false),
         currentDate: `${year}-${month}`,
         dailyItems: [
            { name: '机芯是否清洁', cycle: '●' },
            { name: '设备开关', cycle: '●' },
            { name: '改善运行', cycle: '●' },
            { name: '清理清洁或调试是否有异常', cycle: '●' },
            { name: '工艺参数', cycle: '●' },
            { name: '机油运行是否有异常', cycle: '●' }
         ],
         monthlyItems: [
            { name: '电表油面是否正常是否有渗漏', cycle: '●' },
            { name: '万向接头复查并加油', cycle: '●' }
         ],
         dailyChecks: [],
         monthlyChecks: [],
         saving: false,
         loading: false,
         dirty: false
      }
   },
   computed: {
      yearPickerIndex() {
         const index = this.yearOptions.indexOf(this.currentYear)
         return index >= 0 ? index : 0
      },
      days() {
         return Array.from({ length: 31 }, (_, idx) => idx + 1)
      },
      months() {
         return Array.from({ length: 12 }, (_, idx) => idx + 1)
      },
      checkedDaysCount() {
         return this.dailyChecks.filter(Boolean).length
      },
      checkedMonthsCount() {
         return this.monthlyChecks.filter(Boolean).length
      displayDate() {
         // 格式化显示为 "2025年11月"
         if (!this.currentDate) return ''
         const [year, month] = this.currentDate.split('-')
         return `${year}年${month}月`
      }
   },
   created() {
      this.initChecks()
   },
   watch: {
      machineNo: {
@@ -116,77 +141,101 @@
      }
   },
   methods: {
      buildYearOptions(baseYear) {
         // 生成前后各两年的年份列表,方便切换历史记录
         const range = []
         for (let offset = -2; offset <= 2; offset += 1) {
            range.push(baseYear + offset)
         }
         return range
      initChecks() {
         // 初始化点检数组:每个项目对应31天的勾选状态
         this.dailyChecks = this.dailyItems.map(() => Array(31).fill(false))
         this.monthlyChecks = this.monthlyItems.map(() => Array(31).fill(false))
      },
      handleYearChange(event) {
         // 切换年份后读取对应的点检数据
         const index = Number(event.detail.value || 0)
         const selected = this.yearOptions[index]
         if (selected === this.currentYear) {
            return
         }
         this.currentYear = selected
      handleDateChange(event) {
         this.currentDate = event.detail.value
         this.loadInspectionData()
      },
      async loadInspectionData() {
         // 从后台或本地缓存加载点检记录
         if (!this.machineNo) {
            return
         }
         if (!this.machineNo) return
         this.loading = true
         try {
            const record = await queryEquipmentInspection(this, {
               machineNo: this.machineNo,
               year: this.currentYear
               date: this.currentDate
            }, { mock: true, showLoading: true })
            this.dailyChecks = record.dailyChecks || Array(31).fill(false)
            this.monthlyChecks = record.monthlyChecks || Array(12).fill(false)
            // 验证并设置日常点检数据
            if (record && Array.isArray(record.dailyChecks) && record.dailyChecks.length === 6) {
               // 确保每个子数组都是有效的数组
               const isValid = record.dailyChecks.every(arr => Array.isArray(arr) && arr.length === 31)
               if (isValid) {
                  // 使用 $set 确保 Vue 能够检测到嵌套数组的变化
                  this.$set(this, 'dailyChecks', record.dailyChecks.map(arr => [...arr]))
               } else {
                  console.warn('日常点检数据格式不正确,使用默认值')
                  this.$set(this, 'dailyChecks', this.dailyItems.map(() => Array(31).fill(false)))
               }
            } else {
               this.$set(this, 'dailyChecks', this.dailyItems.map(() => Array(31).fill(false)))
            }
            // 验证并设置月度点检数据
            if (record && Array.isArray(record.monthlyChecks) && record.monthlyChecks.length === 2) {
               // 确保每个子数组都是有效的数组
               const isValid = record.monthlyChecks.every(arr => Array.isArray(arr) && arr.length === 31)
               if (isValid) {
                  // 使用 $set 确保 Vue 能够检测到嵌套数组的变化
                  this.$set(this, 'monthlyChecks', record.monthlyChecks.map(arr => [...arr]))
               } else {
                  console.warn('月度点检数据格式不正确,使用默认值')
                  this.$set(this, 'monthlyChecks', this.monthlyItems.map(() => Array(31).fill(false)))
               }
            } else {
               this.$set(this, 'monthlyChecks', this.monthlyItems.map(() => Array(31).fill(false)))
            }
            this.dirty = false
         } catch (error) {
            console.error('加载设备点检信息失败', error)
            this.$showMessage('点检记录加载失败')
            this.initChecks()
         } finally {
            this.loading = false
         }
      },
      toggleDay(index) {
         // 切换日点检的勾选状态
      toggleCheck(type, itemIdx, dayIdx) {
         if (!this.machineNo) {
            this.$showMessage('请先绑定机台')
            return
         }
         this.$set(this.dailyChecks, index, !this.dailyChecks[index])
         this.dirty = true
      },
      toggleMonth(index) {
         // 切换月点检的勾选状态
         if (!this.machineNo) {
            this.$showMessage('请先绑定机台')
            return
         if (type === 'daily') {
            // 安全检查:确保数组和索引有效
            if (!Array.isArray(this.dailyChecks) || !Array.isArray(this.dailyChecks[itemIdx])) {
               console.error('日常点检数据结构异常,重新初始化')
               this.initChecks()
               return
            }
            this.$set(this.dailyChecks[itemIdx], dayIdx, !this.dailyChecks[itemIdx][dayIdx])
         } else {
            // 安全检查:确保数组和索引有效
            if (!Array.isArray(this.monthlyChecks) || !Array.isArray(this.monthlyChecks[itemIdx])) {
               console.error('月度点检数据结构异常,重新初始化')
               this.initChecks()
               return
            }
            this.$set(this.monthlyChecks[itemIdx], dayIdx, !this.monthlyChecks[itemIdx][dayIdx])
         }
         this.$set(this.monthlyChecks, index, !this.monthlyChecks[index])
         this.dirty = true
      },
      async handleSave() {
         // 保存当前点检表,预留后台 POST 接口
         if (!this.machineNo) {
            this.$showMessage('请先绑定机台')
            return
         }
         if (this.saving) {
            return
         }
         if (this.saving) return
         this.saving = true
         try {
            const response = await saveEquipmentInspection(this, {
               machineNo: this.machineNo,
               year: this.currentYear,
               date: this.currentDate,
               dailyChecks: this.dailyChecks,
               monthlyChecks: this.monthlyChecks
            }, { mock: true, showLoading: true })
@@ -205,10 +254,9 @@
         }
      },
      resetChecks() {
         // 一键清空勾选状态,便于重新录入
         this.dailyChecks = Array(31).fill(false)
         this.monthlyChecks = Array(12).fill(false)
         this.initChecks()
         this.dirty = true
         this.$showMessage('已清空所有点检记录')
      }
   }
}
@@ -218,138 +266,260 @@
.inspection-page {
   display: flex;
   flex-direction: column;
   padding: 2vh 2vw;
   font-size: 1.4vw;
   color: #333333;
   padding: 10px;
   font-size: 14px;
   color: #333;
   background: #f5f5f5;
   height: 100%;
   overflow: hidden;
}
.header {
   display: flex;
   flex-direction: row;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 2vh;
   background: #fff;
   border-radius: 8px;
   padding: 12px 16px;
   margin-bottom: 10px;
   box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.machine-info {
   display: flex;
   flex-direction: row;
   align-items: center;
   font-size: 1.6vw;
.title-row {
   text-align: center;
   margin-bottom: 8px;
}
.machine-text {
.page-title {
   font-size: 20px;
   font-weight: bold;
   margin-left: 0.5vw;
   color: #333;
}
.year-picker {
.info-row {
   display: flex;
   flex-direction: row;
   justify-content: flex-start;
   align-items: center;
   gap: 60px;
}
.year-label {
   margin-right: 1vw;
.info-item {
   display: flex;
   align-items: center;
   gap: 8px;
}
.picker-trigger {
   border: 1px solid #d8d8d8;
   border-radius: 0.8vw;
   padding: 0.6vh 1.4vw;
   background-color: #ffffff;
   font-size: 1.4vw;
.info-label {
   font-size: 14px;
   color: #666;
}
.section {
   margin-bottom: 3vh;
   background-color: #ffffff;
   border-radius: 1vw;
   padding: 2vh 1.5vw;
   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
.info-value {
   font-size: 14px;
   font-weight: 600;
   color: #333;
}
.section-title {
   font-size: 1.8vw;
.date-picker {
   padding: 4px 12px;
   background: #f0f0f0;
   border-radius: 4px;
   font-size: 14px;
   cursor: pointer;
   min-width: 100px;
   text-align: center;
}
.table-container {
   flex: 1;
   overflow: auto;
   background: #fff;
   border-radius: 8px;
   padding: 8px;
   box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.inspection-table {
   width: 100%;
   border-collapse: collapse;
   font-size: 12px;
   min-width: 1400px;
}
.inspection-table th,
.inspection-table td {
   border: 1px solid #666;
   padding: 4px 6px;
   text-align: center;
   white-space: nowrap;
}
.inspection-table thead {
   background: #e8e8e8;
   position: sticky;
   top: 0;
   z-index: 10;
}
.inspection-table th {
   font-weight: bold;
   margin-bottom: 1.5vh;
   font-size: 13px;
   background: #e8e8e8;
}
.grid {
   display: grid;
   grid-gap: 1vh;
.col-category {
   min-width: 80px;
   width: 80px;
}
.days-grid {
   grid-template-columns: repeat(7, 1fr);
.col-item {
   min-width: 180px;
   width: 180px;
   text-align: left;
}
.months-grid {
   grid-template-columns: repeat(4, 1fr);
.col-cycle {
   min-width: 50px;
   width: 50px;
}
.grid-cell {
   display: flex;
   align-items: center;
   justify-content: center;
   height: 6vh;
   background-color: #f5f7fa;
   border-radius: 0.8vw;
   border: 1px solid #dcdfe6;
   color: #606266;
   transition: all 0.2s ease-in-out;
.col-days {
   background: #d0d0d0;
}
.grid-cell.checked {
   background-color: #0faeff;
   color: #ffffff;
   border-color: transparent;
.day-header {
   width: 30px;
   min-width: 30px;
   font-size: 11px;
   padding: 2px;
}
.grid-cell:active {
   transform: scale(0.98);
.category-cell {
   background: #f5f5f5;
   font-weight: bold;
   vertical-align: middle;
}
.grid-text {
   font-size: 1.4vw;
.item-cell {
   text-align: left;
   padding-left: 8px;
   font-size: 12px;
}
.summary {
   margin-top: 1.5vh;
   font-size: 1.3vw;
   color: #909399;
.cycle-cell {
   font-size: 16px;
   color: #333;
}
.check-cell {
   width: 30px;
   min-width: 30px;
   height: 28px;
   cursor: pointer;
   background: #fff;
   transition: background 0.2s;
   font-size: 18px;
   color: #0faeff;
}
.check-cell:hover {
   background: #f0f8ff;
}
.check-cell.checked {
   background: #e6f7ff;
}
.actions {
   display: flex;
   flex-direction: row;
   justify-content: flex-end;
   gap: 1vw;
   justify-content: center;
   gap: 16px;
   margin-top: 10px;
   padding: 10px 0;
}
.btn-primary,
.btn-secondary {
   min-width: 12vw;
   padding: 1.2vh 1vw;
   font-size: 1.4vw;
   border-radius: 0.8vw;
.btn-save,
.btn-clear {
   min-width: 120px;
   padding: 10px 24px;
   font-size: 16px;
   border-radius: 6px;
   border: none;
   text-align: center;
   cursor: pointer;
   transition: all 0.2s;
}
.btn-primary {
   background-color: #0faeff;
   color: #ffffff;
.btn-save {
   background: #00a2e9;
   color: #fff;
}
.btn-secondary {
   background-color: #ffffff;
   color: #0faeff;
   border: 1px solid #0faeff;
.btn-save:hover {
   background: #0086c0;
}
.btn-secondary:active,
.btn-primary:active {
   opacity: 0.8;
.btn-save:disabled {
   background: #ccc;
   cursor: not-allowed;
}
.btn-clear {
   background: #fff;
   color: #333;
   border: 1px solid #d0d0d0;
}
.btn-clear:hover {
   background: #f5f5f5;
}
/* 针对1280*717屏幕优化 */
@media screen and (max-width: 1280px) and (max-height: 800px) {
   .inspection-page {
      padding: 6px;
   }
   .header {
      padding: 8px 12px;
      margin-bottom: 6px;
   }
   .page-title {
      font-size: 18px;
   }
   .inspection-table {
      font-size: 11px;
   }
   .inspection-table th {
      font-size: 12px;
      padding: 3px 4px;
   }
   .inspection-table td {
      padding: 3px 4px;
   }
   .day-header {
      width: 26px;
      min-width: 26px;
      font-size: 10px;
   }
   .check-cell {
      width: 26px;
      min-width: 26px;
      height: 24px;
      font-size: 16px;
   }
   .item-cell {
      font-size: 11px;
   }
   .btn-save,
   .btn-clear {
      min-width: 100px;
      padding: 8px 20px;
      font-size: 14px;
   }
}
</style>