快乐的昕的电脑
2025-11-24 5103d7a6f424d1675d2a731196b39ec30ea08ade
components/EquipmentInspection.vue
@@ -1,58 +1,79 @@
<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 +94,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 +142,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,151 +255,279 @@
         }
      },
      resetChecks() {
         // 一键清空勾选状态,便于重新录入
         this.dailyChecks = Array(31).fill(false)
         this.monthlyChecks = Array(12).fill(false)
         this.initChecks()
         this.dirty = true
         this.$showMessage('已清空所有点检记录')
      }
   }
}
</script>
<style scoped>
.inspection-page {
   display: flex;
   flex-direction: column;
   padding: 2vh 2vw;
   font-size: 1.4vw;
   color: #333333;
}
    .inspection-page {
        display: flex;
        flex-direction: column;
        padding: 10px;
        font-size: 18px; /* 原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;
}
    .header {
        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 {
   font-weight: bold;
   margin-left: 0.5vw;
}
    .page-title {
        font-size: 28px; /* 原20px */
        font-weight: bold;
        color: #333;
    }
.year-picker {
   display: flex;
   flex-direction: row;
   align-items: center;
}
    .info-row {
        display: flex;
        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: 18px; /* 原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: 18px; /* 原14px */
        font-weight: 600;
        color: #333;
    }
.section-title {
   font-size: 1.8vw;
   font-weight: bold;
   margin-bottom: 1.5vh;
}
    .date-picker {
        padding: 4px 12px;
        background: #f0f0f0;
        border-radius: 4px;
        font-size: 18px; /* 原14px */
        cursor: pointer;
        min-width: 100px;
        text-align: center;
    }
.grid {
   display: grid;
   grid-gap: 1vh;
}
    .table-container {
        flex: 1;
        overflow: auto;
        background: #fff;
        border-radius: 8px;
        padding: 8px;
        box-shadow: 0 1px 4px rgba(0,0,0,0.1);
    }
.days-grid {
   grid-template-columns: repeat(7, 1fr);
}
    .inspection-table {
        width: 100%;
        border-collapse: collapse;
        font-size: 16px; /* 原12px */
        min-width: 1400px;
    }
.months-grid {
   grid-template-columns: repeat(4, 1fr);
}
        .inspection-table th,
        .inspection-table td {
            border: 1px solid #666;
            padding: 6px 10px; /* 原4px 6px */
            text-align: center;
            white-space: nowrap;
        }
.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;
}
        .inspection-table thead {
            background: #e8e8e8;
            position: sticky;
            top: 0;
            z-index: 10;
        }
.grid-cell.checked {
   background-color: #0faeff;
   color: #ffffff;
   border-color: transparent;
}
        .inspection-table th {
            font-weight: bold;
            font-size: 18px; /* 原13px */
            background: #e8e8e8;
        }
.grid-cell:active {
   transform: scale(0.98);
}
    .col-category {
        min-width: 80px;
        width: 80px;
    }
.grid-text {
   font-size: 1.4vw;
}
    .col-item {
        min-width: 180px;
        width: 180px;
        text-align: left;
    }
.summary {
   margin-top: 1.5vh;
   font-size: 1.3vw;
   color: #909399;
}
    .col-cycle {
        min-width: 50px;
        width: 50px;
    }
.actions {
   display: flex;
   flex-direction: row;
   justify-content: flex-end;
   gap: 1vw;
}
    .col-days {
        background: #d0d0d0;
    }
.btn-primary,
.btn-secondary {
   min-width: 12vw;
   padding: 1.2vh 1vw;
   font-size: 1.4vw;
   border-radius: 0.8vw;
   border: none;
   text-align: center;
}
    .day-header {
        width: 36px; /* 原30px */
        min-width: 36px;
        font-size: 15px; /* 原11px */
        padding: 4px;
    }
.btn-primary {
   background-color: #0faeff;
   color: #ffffff;
}
    .category-cell {
        background: #f5f5f5;
        font-weight: bold;
        vertical-align: middle;
    }
.btn-secondary {
   background-color: #ffffff;
   color: #0faeff;
   border: 1px solid #0faeff;
}
    .item-cell {
        text-align: left;
        padding-left: 12px; /* 原8px */
        font-size: 16px; /* 原12px */
    }
.btn-secondary:active,
.btn-primary:active {
   opacity: 0.8;
}
    .cycle-cell {
        font-size: 20px; /* 原16px */
        color: #333;
    }
    .check-cell {
        width: 36px; /* 原30px */
        min-width: 36px;
        height: 32px; /* 原28px */
        cursor: pointer;
        background: #fff;
        transition: background 0.2s;
        font-size: 22px; /* 原18px */
        color: #0faeff;
    }
        .check-cell:hover {
            background: #f0f8ff;
        }
        .check-cell.checked {
            background: #e6f7ff;
        }
    .actions {
        display: flex;
        justify-content: center;
        gap: 24px; /* 原16px */
        margin-top: 10px;
        padding: 10px 0;
    }
    .btn-save,
    .btn-clear {
        min-width: 140px; /* 原120px */
        padding: 14px 32px; /* 原10px 24px */
        font-size: 20px; /* 原16px */
        border-radius: 6px;
        border: none;
        cursor: pointer;
        transition: all 0.2s;
    }
    .btn-save {
        background: #00a2e9;
        color: #fff;
    }
        .btn-save:hover {
            background: #0086c0;
        }
        .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;
            font-size: 15px;
        }
        .header {
            padding: 8px 12px;
            margin-bottom: 6px;
        }
        .page-title {
            font-size: 22px;
        }
        .info-label,
        .info-value,
        .date-picker {
            font-size: 15px;
        }
        .inspection-table {
            font-size: 13px;
        }
            .inspection-table th {
                font-size: 15px;
                padding: 4px 6px;
            }
            .inspection-table td {
                padding: 4px 6px;
            }
        .day-header {
            width: 28px;
            min-width: 28px;
            font-size: 12px;
        }
        .check-cell {
            width: 28px;
            min-width: 28px;
            height: 24px;
            font-size: 16px;
        }
        .item-cell {
            font-size: 13px;
        }
        .btn-save,
        .btn-clear {
            min-width: 110px;
            padding: 10px 20px;
            font-size: 16px;
        }
    }
</style>