|
@@ -0,0 +1,1605 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="maintenance-plan-page" v-loading="formLoading">
|
|
|
|
|
+ <section class="plan-section plan-form-section">
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ :model="formData"
|
|
|
|
|
+ :rules="formRules"
|
|
|
|
|
+ label-width="88px"
|
|
|
|
|
+ class="plan-form">
|
|
|
|
|
+ <el-row :gutter="48">
|
|
|
|
|
+ <el-col :span="13">
|
|
|
|
|
+ <el-form-item :label="t('main.planName')" prop="name">
|
|
|
|
|
+ <el-input v-model="formData.name" :disabled="isReadonly" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="11">
|
|
|
|
|
+ <el-form-item :label="t('main.planCode')" prop="serialNumber">
|
|
|
|
|
+ <el-input v-model="formData.serialNumber" 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"
|
|
|
|
|
+ :placeholder="t('iotMaintain.remarkHolder')"
|
|
|
|
|
+ :disabled="isReadonly" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="plan-section plan-table-section">
|
|
|
|
|
+ <div v-if="!isReadonly" class="table-toolbar">
|
|
|
|
|
+ <el-button @click="openForm" type="warning">
|
|
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="table-resizer">
|
|
|
|
|
+ <el-auto-resizer class="absolute">
|
|
|
|
|
+ <template #default="{ width, height }">
|
|
|
|
|
+ <ZmTable
|
|
|
|
|
+ ref="tableRef"
|
|
|
|
|
+ class="maintenance-plan-table"
|
|
|
|
|
+ :data="list"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ :width="width"
|
|
|
|
|
+ :height="height"
|
|
|
|
|
+ :max-height="height"
|
|
|
|
|
+ :show-border="true"
|
|
|
|
|
+ :cell-class-name="cellClassName"
|
|
|
|
|
+ :cell-style="cellStyle"
|
|
|
|
|
+ :column-max-width="420"
|
|
|
|
|
+ :highlightCurrentRow="false"
|
|
|
|
|
+ :hover-highlight="false"
|
|
|
|
|
+ @header-dragend="handleHeaderDragEnd">
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ type="index"
|
|
|
|
|
+ :label="t('iotDevice.serial')"
|
|
|
|
|
+ :width="62"
|
|
|
|
|
+ fixed="left"
|
|
|
|
|
+ hide-in-column-settings />
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="deviceCode"
|
|
|
|
|
+ :label="t('iotMaintain.deviceCode')"
|
|
|
|
|
+ :min-width="100"
|
|
|
|
|
+ fixed="left" />
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="deviceName"
|
|
|
|
|
+ :label="t('iotMaintain.deviceName')"
|
|
|
|
|
+ :min-width="110"
|
|
|
|
|
+ fixed="left" />
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="name"
|
|
|
|
|
+ :label="t('bomList.bomNode')"
|
|
|
|
|
+ :min-width="190"
|
|
|
|
|
+ fixed="left"
|
|
|
|
|
+ :show-overflow-tooltip="false">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="full-content-cell">{{ row.name }}</div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="runningTimeRule" :label="t('main.runTime')" :width="92">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ v-model="row.runningTimeRule"
|
|
|
|
|
+ :active-value="0"
|
|
|
|
|
+ :inactive-value="1"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ @change="handleRuleChange(row, 'runningTime')" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="mileageRule" :label="t('main.mileage')" :width="92">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ v-model="row.mileageRule"
|
|
|
|
|
+ :active-value="0"
|
|
|
|
|
+ :inactive-value="1"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ @change="handleRuleChange(row, 'mileage')" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="naturalDateRule" :label="t('main.date')" :width="92">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ v-model="row.naturalDateRule"
|
|
|
|
|
+ :active-value="0"
|
|
|
|
|
+ :inactive-value="1"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ @change="handleRuleChange(row, 'date')" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="totalRunTime"
|
|
|
|
|
+ :label="t('operationFillForm.sumTime')"
|
|
|
|
|
+ :min-width="130"
|
|
|
|
|
+ :real-value="(row) => row.totalRunTime ?? row.tempTotalRunTime">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.totalRunTime ?? row.tempTotalRunTime ?? '-' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="totalMileage"
|
|
|
|
|
+ :label="t('operationFillForm.sumKil')"
|
|
|
|
|
+ :min-width="150"
|
|
|
|
|
+ :real-value="(row) => row.totalMileage ?? row.tempTotalMileage">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.totalMileage ?? row.tempTotalMileage ?? '-' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="lastMaintenanceDate"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceDate')"
|
|
|
|
|
+ :min-width="126"
|
|
|
|
|
+ :real-value="(row) => formatDate(row.lastMaintenanceDate)">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatDate(row.lastMaintenanceDate) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ZmTableColumn v-if="hasTimeRule" column-key="time-group" label="保养时长" is-parent>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="lastRunningTime"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceOperationTime')"
|
|
|
|
|
+ :min-width="150" />
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="nextMaintenanceH"
|
|
|
|
|
+ :label="t('mainPlan.nextMaintenanceH')"
|
|
|
|
|
+ :min-width="150">
|
|
|
|
|
+ <template #default="{ row }">{{ row.nextMaintenanceH ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="remainH" :label="t('mainPlan.remainH')" :min-width="120">
|
|
|
|
|
+ <template #default="{ row }">{{ row.remainH ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ v-if="hasMileageRule"
|
|
|
|
|
+ column-key="mileage-group"
|
|
|
|
|
+ label="保养里程"
|
|
|
|
|
+ is-parent>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="lastRunningKilometers"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceMileage')"
|
|
|
|
|
+ :min-width="150" />
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="nextMaintenanceKm"
|
|
|
|
|
+ :label="t('mainPlan.nextMaintenanceKm')"
|
|
|
|
|
+ :min-width="150">
|
|
|
|
|
+ <template #default="{ row }">{{ row.nextMaintenanceKm ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="remainKm" :label="t('mainPlan.remainKm')" :min-width="120">
|
|
|
|
|
+ <template #default="{ row }">{{ row.remainKm ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ZmTableColumn v-if="hasDateRule" column-key="date-group" label="保养日期" is-parent>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="lastNaturalDate"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceNaturalDate')"
|
|
|
|
|
+ :min-width="150"
|
|
|
|
|
+ :real-value="(row) => formatDate(row.lastNaturalDate)">
|
|
|
|
|
+ <template #default="{ row }">{{ formatDate(row.lastNaturalDate) }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ prop="nextMaintenanceDate"
|
|
|
|
|
+ :label="t('mainPlan.nextMaintDate')"
|
|
|
|
|
+ :min-width="140">
|
|
|
|
|
+ <template #default="{ row }">{{ row.nextMaintenanceDate ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ <ZmTableColumn prop="remainDay" :label="t('mainPlan.remainDay')" :min-width="120">
|
|
|
|
|
+ <template #default="{ row }">{{ row.remainDay ?? '-' }}</template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ZmTableColumn
|
|
|
|
|
+ column-key="operation"
|
|
|
|
|
+ :label="t('operationFill.operation')"
|
|
|
|
|
+ :width="150"
|
|
|
|
|
+ fixed="right"
|
|
|
|
|
+ action>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="table-actions">
|
|
|
|
|
+ <el-button v-if="!isReadonly" link type="danger" @click="handleDelete(row)">
|
|
|
|
|
+ <Icon icon="ep:zoom-out" class="mr-3px" />
|
|
|
|
|
+ {{ t('modelTemplate.delete') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button link type="primary" @click="openConfigDialog(row)">
|
|
|
|
|
+ {{ isReadonly ? t('form.set') : t('modelTemplate.update') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ </ZmTable>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-auto-resizer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="plan-footer">
|
|
|
|
|
+ <el-button v-if="!isReadonly" type="primary" @click="submitForm" :disabled="formLoading">
|
|
|
|
|
+ {{ t('iotMaintain.save') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="close">
|
|
|
|
|
+ {{ isReadonly ? t('operationFillForm.cancel') : t('iotMaintain.cancel') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <MainPlanDeviceList v-if="!isReadonly" ref="deviceFormRef" @choose="deviceChoose" />
|
|
|
|
|
+ <!-- 新增配置对话框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="configDialog.visible"
|
|
|
|
|
+ :title="`设备 ${configDialog.current?.deviceCode + '-' + configDialog.current?.name} 保养配置`"
|
|
|
|
|
+ width="600px"
|
|
|
|
|
+ :close-on-click-modal="false">
|
|
|
|
|
+ <!-- 使用header插槽自定义标题 -->
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <span
|
|
|
|
|
+ >设备
|
|
|
|
|
+ <strong>{{ configDialog.current?.deviceCode }}-{{ configDialog.current?.name }}</strong>
|
|
|
|
|
+ 保养项配置</span
|
|
|
|
|
+ >
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ :model="configDialog.form"
|
|
|
|
|
+ label-width="200px"
|
|
|
|
|
+ :rules="configFormRules"
|
|
|
|
|
+ ref="configFormRef">
|
|
|
|
|
+ <div class="form-group">
|
|
|
|
|
+ <div class="group-title">{{ t('mainPlan.basicMaintenanceRecords') }}</div>
|
|
|
|
|
+ <!-- 里程配置 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.mileageRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceMileage')"
|
|
|
|
|
+ prop="lastRunningKilometers">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.lastRunningKilometers"
|
|
|
|
|
+ :precision="2"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <!-- 运行时间配置 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.runningTimeRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceOperationTime')"
|
|
|
|
|
+ prop="lastRunningTime">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.lastRunningTime"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <!-- 自然日期配置 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.naturalDateRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.lastMaintenanceNaturalDate')"
|
|
|
|
|
+ prop="lastNaturalDate">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.lastNaturalDate"
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ placeholder="选择日期"
|
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
|
+ value-format="x"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-group" v-if="configDialog.current?.mileageRule === 0">
|
|
|
|
|
+ <div class="group-title">{{ t('mainPlan.operatingMileageRuleConfiguration') }}</div>
|
|
|
|
|
+ <!-- 保养规则周期值 + 提前量 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.mileageRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.operatingMileageCycle')"
|
|
|
|
|
+ prop="nextRunningKilometers">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.nextRunningKilometers"
|
|
|
|
|
+ :precision="2"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.mileageRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.OperatingMileageCycle_lead')"
|
|
|
|
|
+ prop="kiloCycleLead">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.kiloCycleLead"
|
|
|
|
|
+ :precision="2"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-group" v-if="configDialog.current?.runningTimeRule === 0">
|
|
|
|
|
+ <div class="group-title">{{ t('mainPlan.RunTimeRuleConfiguration') }}</div>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.runningTimeRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.RunTimeCycle')"
|
|
|
|
|
+ prop="nextRunningTime">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.nextRunningTime"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.runningTimeRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.RunTimeCycle_Lead')"
|
|
|
|
|
+ prop="timePeriodLead">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.timePeriodLead"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-group" v-if="configDialog.current?.naturalDateRule === 0">
|
|
|
|
|
+ <div class="group-title">{{ t('mainPlan.NaturalDayRuleConfig') }}</div>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.naturalDateRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.NaturalDailyCycle')"
|
|
|
|
|
+ prop="nextNaturalDate">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.nextNaturalDate"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="configDialog.current?.naturalDateRule === 0"
|
|
|
|
|
+ :label="t('mainPlan.NaturalDailyCycle_Lead')"
|
|
|
|
|
+ prop="naturalDatePeriodLead">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.naturalDatePeriodLead"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 60%" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 运行记录模板中 多个 累计运行时长 累计运行里程 属性匹配-->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="form-group"
|
|
|
|
|
+ v-if="
|
|
|
|
|
+ (configDialog.current?.runningTimeRule === 0 ||
|
|
|
|
|
+ configDialog.current?.mileageRule === 0) &&
|
|
|
|
|
+ (configDialog.current?.timeAccumulatedAttrs?.length ||
|
|
|
|
|
+ configDialog.current?.mileageAccumulatedAttrs?.length) &&
|
|
|
|
|
+ (configDialog.current.totalRunTime == null || isNaN(configDialog.current.totalRunTime)) &&
|
|
|
|
|
+ (configDialog.current.totalMileage == null || isNaN(configDialog.current.totalMileage))
|
|
|
|
|
+ ">
|
|
|
|
|
+ <div class="group-title">{{ t('mainPlan.accumulatedParams') }}</div>
|
|
|
|
|
+ <!-- 累计运行时长 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="
|
|
|
|
|
+ configDialog.current?.runningTimeRule === 0 &&
|
|
|
|
|
+ configDialog.current?.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ (configDialog.current.totalRunTime == null || isNaN(configDialog.current.totalRunTime))
|
|
|
|
|
+ "
|
|
|
|
|
+ :label="t('mainPlan.accumulatedRunTime')"
|
|
|
|
|
+ prop="accumulatedTimeOption"
|
|
|
|
|
+ :rules="[
|
|
|
|
|
+ {
|
|
|
|
|
+ required: Boolean(
|
|
|
|
|
+ configDialog.current?.runningTimeRule === 0 &&
|
|
|
|
|
+ configDialog.current?.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ (configDialog.current.totalRunTime === null ||
|
|
|
|
|
+ isNaN(configDialog.current.totalRunTime))
|
|
|
|
|
+ ),
|
|
|
|
|
+ message: '请选择累计运行时长',
|
|
|
|
|
+ trigger: 'change'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.accumulatedTimeOption"
|
|
|
|
|
+ placeholder="请选择累计运行时长"
|
|
|
|
|
+ style="width: 80%"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @change="handleAccumulatedTimeChange">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="(item, index) in configDialog.current.timeAccumulatedAttrs"
|
|
|
|
|
+ :key="`time-${item.pointName}-${index}`"
|
|
|
|
|
+ :label="item.pointName"
|
|
|
|
|
+ :value="item.pointName" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <!-- 累计运行公里数 -->
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ v-if="
|
|
|
|
|
+ configDialog.current?.mileageRule === 0 &&
|
|
|
|
|
+ configDialog.current?.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ (configDialog.current.totalMileage == null || isNaN(configDialog.current.totalMileage))
|
|
|
|
|
+ "
|
|
|
|
|
+ :label="t('mainPlan.accumulatedMileage')"
|
|
|
|
|
+ prop="accumulatedMileageOption"
|
|
|
|
|
+ :rules="[
|
|
|
|
|
+ {
|
|
|
|
|
+ required: Boolean(
|
|
|
|
|
+ configDialog.current?.mileageRule === 0 &&
|
|
|
|
|
+ configDialog.current?.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ (configDialog.current.totalMileage == null ||
|
|
|
|
|
+ isNaN(configDialog.current.totalMileage))
|
|
|
|
|
+ ),
|
|
|
|
|
+ message: '请选择累计运行公里数',
|
|
|
|
|
+ trigger: 'change'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ v-model="configDialog.form.accumulatedMileageOption"
|
|
|
|
|
+ placeholder="请选择累计运行公里数"
|
|
|
|
|
+ style="width: 80%"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @change="handleAccumulatedMileageChange">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="(item, index) in configDialog.current.mileageAccumulatedAttrs"
|
|
|
|
|
+ :key="`mileage-${item.pointName}-${index}`"
|
|
|
|
|
+ :label="item.pointName"
|
|
|
|
|
+ :value="item.pointName" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="configDialog.visible = false">{{ t('common.cancel') }}</el-button>
|
|
|
|
|
+ <el-button v-if="!isReadonly" type="primary" @click="saveConfig">{{
|
|
|
|
|
+ t('common.save')
|
|
|
|
|
+ }}</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { IotDeviceApi } from '@/api/pms/device'
|
|
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
+import { ref, computed, watch } from 'vue'
|
|
|
|
|
+import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
|
|
|
|
|
+import { IotMaintenancePlanApi } from '@/api/pms/maintenance'
|
|
|
|
|
+import { useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
|
|
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
|
|
|
|
+import MainPlanDeviceList from '@/views/pms/maintenance/maintenance-device-list.vue'
|
|
|
|
|
+import * as DeptApi from '@/api/system/dept'
|
|
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
+
|
|
|
|
|
+const { t } = useI18n() // 国际化
|
|
|
|
|
+const message = useMessage() // 消息弹窗
|
|
|
|
|
+const { delView } = useTagsViewStore() // 视图操作
|
|
|
|
|
+const { currentRoute, push } = useRouter()
|
|
|
|
|
+const route = useRoute()
|
|
|
|
|
+const mode = computed(() => {
|
|
|
|
|
+ if (route.name === 'IotMaintenancePlanDetail') return 'detail'
|
|
|
|
|
+ if (route.name === 'IotMainPlanEdit') return 'edit'
|
|
|
|
|
+ return 'create'
|
|
|
|
|
+})
|
|
|
|
|
+const isReadonly = computed(() => mode.value === 'detail')
|
|
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<IotMaintenanceBomVO>()
|
|
|
|
|
+const dept = ref() // 当前登录人所属部门对象
|
|
|
|
|
+const configFormRef = ref() // 配置弹出框对象
|
|
|
|
|
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
|
|
|
|
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
|
|
|
|
+const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
|
|
|
|
|
+const list = ref<IotMaintenanceBomVO[]>([]) // 设备bom关联列表的数据
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const lastNaturalDateWatchers = ref(new Map())
|
|
|
|
|
+
|
|
|
|
|
+const deviceIds = ref<number[]>([]) // 已经选择的设备id数组
|
|
|
|
|
+
|
|
|
|
|
+const tableRef = ref()
|
|
|
|
|
+
|
|
|
|
|
+const cellStyle: any = ({ row, column }: any) => {
|
|
|
|
|
+ if (['remainH', 'remainKm', 'remainDay'].includes(column.property)) {
|
|
|
|
|
+ if (row[column.property] < 0) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ color: 'var(--el-color-danger)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const id = computed(() => route.params.id as string | undefined)
|
|
|
|
|
+const formData = ref<any>({
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ deptId: undefined,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ serialNumber: undefined,
|
|
|
|
|
+ responsiblePerson: undefined,
|
|
|
|
|
+ remark: undefined,
|
|
|
|
|
+ failureName: undefined,
|
|
|
|
|
+ status: undefined,
|
|
|
|
|
+ devicePersons: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const formRules = reactive({
|
|
|
|
|
+ name: [{ required: true, message: '计划名称不能为空', trigger: 'blur' }],
|
|
|
|
|
+ responsiblePerson: [{ required: true, message: '责任人不能为空', trigger: 'blur' }]
|
|
|
|
|
+})
|
|
|
|
|
+const formRef = ref() // 表单 Ref
|
|
|
|
|
+
|
|
|
|
|
+// 新增配置相关状态
|
|
|
|
|
+const configDialog = reactive({
|
|
|
|
|
+ visible: false,
|
|
|
|
|
+ current: null as IotMaintenanceBomVO | null,
|
|
|
|
|
+ form: {
|
|
|
|
|
+ lastRunningKilometers: 0,
|
|
|
|
|
+ lastRunningTime: 0,
|
|
|
|
|
+ lastNaturalDate: null,
|
|
|
|
|
+ // 保养规则 周期
|
|
|
|
|
+ nextRunningKilometers: 0,
|
|
|
|
|
+ nextRunningTime: 0,
|
|
|
|
|
+ nextNaturalDate: 0,
|
|
|
|
|
+ // 提前量
|
|
|
|
|
+ kiloCycleLead: 0,
|
|
|
|
|
+ timePeriodLead: 0,
|
|
|
|
|
+ naturalDatePeriodLead: 0,
|
|
|
|
|
+ // 多个累计时长 累计里程 匹配
|
|
|
|
|
+ accumulatedTimeOption: null, // 累计运行时长选项
|
|
|
|
|
+ accumulatedMileageOption: null // 累计运行公里数选项
|
|
|
|
|
+ } as any
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 打开配置对话框
|
|
|
|
|
+const openConfigDialog = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 新增规则校验:至少一个规则开启
|
|
|
|
|
+ if (row.mileageRule !== 0 && row.runningTimeRule !== 0 && row.naturalDateRule !== 0) {
|
|
|
|
|
+ message.error('请先设置保养规则')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ configDialog.current = row
|
|
|
|
|
+
|
|
|
|
|
+ configDialog.form = {
|
|
|
|
|
+ lastRunningKilometers: row.lastRunningKilometers || 0,
|
|
|
|
|
+ lastRunningTime: row.lastRunningTime || 0,
|
|
|
|
|
+ lastNaturalDate: row.lastNaturalDate || null,
|
|
|
|
|
+ // 保养规则 周期值
|
|
|
|
|
+ nextRunningKilometers: row.nextRunningKilometers || 0,
|
|
|
|
|
+ nextRunningTime: row.nextRunningTime || 0,
|
|
|
|
|
+ nextNaturalDate: row.nextNaturalDate || 0,
|
|
|
|
|
+ // 提前量
|
|
|
|
|
+ kiloCycleLead: row.kiloCycleLead || 0,
|
|
|
|
|
+ timePeriodLead: row.timePeriodLead || 0,
|
|
|
|
|
+ naturalDatePeriodLead: row.naturalDatePeriodLead || 0,
|
|
|
|
|
+ // 多个累计时长 累计里程 匹配
|
|
|
|
|
+ accumulatedTimeOption: null, // 累计运行时长选项
|
|
|
|
|
+ accumulatedMileageOption: null // 累计运行公里数选项
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化累计参数选择
|
|
|
|
|
+ /* configDialog.form.accumulatedTimeOption = row.isRuntimeFromTemp
|
|
|
|
|
+ ? row.code
|
|
|
|
|
+ : null;
|
|
|
|
|
+
|
|
|
|
|
+ configDialog.form.accumulatedMileageOption = row.isMileageFromTemp
|
|
|
|
|
+ ? row.type
|
|
|
|
|
+ : null; */
|
|
|
|
|
+
|
|
|
|
|
+ configDialog.form.accumulatedTimeOption = row.code || null
|
|
|
|
|
+ configDialog.form.accumulatedMileageOption = row.type || null
|
|
|
|
|
+
|
|
|
|
|
+ configDialog.visible = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 列宽调整后的处理
|
|
|
|
|
+const handleHeaderDragEnd = () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ tableRef.value?.elTableRef?.doLayout?.()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 保存配置
|
|
|
|
|
+const saveConfig = () => {
|
|
|
|
|
+ if (isReadonly.value) {
|
|
|
|
|
+ configDialog.visible = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ ;(configFormRef.value as any).validate((valid: boolean) => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ if (!configDialog.current) return
|
|
|
|
|
+
|
|
|
|
|
+ // 累计运行时长配置 校验逻辑
|
|
|
|
|
+ if (
|
|
|
|
|
+ configDialog.current.runningTimeRule === 0 &&
|
|
|
|
|
+ configDialog.current.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ !configDialog.form.accumulatedTimeOption &&
|
|
|
|
|
+ (configDialog.current.totalRunTime == null || isNaN(configDialog.current.totalRunTime))
|
|
|
|
|
+ ) {
|
|
|
|
|
+ message.error('请选择累计运行时长')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 累计运行公里数配置 校验逻辑
|
|
|
|
|
+ if (
|
|
|
|
|
+ configDialog.current.mileageRule === 0 &&
|
|
|
|
|
+ configDialog.current.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ !configDialog.form.accumulatedMileageOption &&
|
|
|
|
|
+ (configDialog.current.totalMileage == null || isNaN(configDialog.current.totalMileage))
|
|
|
|
|
+ ) {
|
|
|
|
|
+ message.error('请选择累计运行公里数')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 动态校验逻辑
|
|
|
|
|
+ const requiredFields: any = []
|
|
|
|
|
+ if (configDialog.current.mileageRule === 0) {
|
|
|
|
|
+ requiredFields.push('nextRunningKilometers', 'kiloCycleLead')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (configDialog.current.runningTimeRule === 0) {
|
|
|
|
|
+ requiredFields.push('nextRunningTime', 'timePeriodLead')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (configDialog.current.naturalDateRule === 0) {
|
|
|
|
|
+ requiredFields.push('nextNaturalDate', 'naturalDatePeriodLead')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const missingFields = requiredFields.filter(
|
|
|
|
|
+ (field) => !configDialog.form[field as keyof typeof configDialog.form]
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (missingFields.length > 0) {
|
|
|
|
|
+ message.error('请填写所有必填项')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 强制校验逻辑
|
|
|
|
|
+ if (configDialog.current.naturalDateRule === 0) {
|
|
|
|
|
+ if (!configDialog.form.lastNaturalDate) {
|
|
|
|
|
+ message.error('必须选择自然日期')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 验证日期有效性
|
|
|
|
|
+ const dateValue = dayjs(Number(configDialog.form.lastNaturalDate))
|
|
|
|
|
+ if (!dateValue.isValid()) {
|
|
|
|
|
+ message.error('日期格式不正确')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转换逻辑(关键修改)
|
|
|
|
|
+ const finalDate = configDialog.form.lastNaturalDate
|
|
|
|
|
+ ? Number(configDialog.form.lastNaturalDate)
|
|
|
|
|
+ : null // 改为null而不是0
|
|
|
|
|
+
|
|
|
|
|
+ const updateData = {
|
|
|
|
|
+ ...configDialog.form,
|
|
|
|
|
+ lastNaturalDate: finalDate
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭保养规则,无论是否有选择值,都清除相关数据
|
|
|
|
|
+ // 处理累计运行时长
|
|
|
|
|
+ if (configDialog.current.runningTimeRule !== 0) {
|
|
|
|
|
+ configDialog.current.code = null
|
|
|
|
|
+ configDialog.current.totalRunTime = null
|
|
|
|
|
+ configDialog.form.accumulatedTimeOption = null // 清除选择值
|
|
|
|
|
+ } else if (configDialog.form.accumulatedTimeOption) {
|
|
|
|
|
+ // 查找选中的累计运行时长项
|
|
|
|
|
+ const selectedTimeOption = configDialog.current.timeAccumulatedAttrs?.find(
|
|
|
|
|
+ (item) => item.pointName === configDialog.form.accumulatedTimeOption
|
|
|
|
|
+ )
|
|
|
|
|
+ if (selectedTimeOption) {
|
|
|
|
|
+ configDialog.current.code = selectedTimeOption.pointName
|
|
|
|
|
+ // 优先使用接口值,没有则使用临时值
|
|
|
|
|
+ configDialog.current.tempTotalRunTime = selectedTimeOption.totalRunTime
|
|
|
|
|
+ configDialog.current.isRuntimeFromTemp = true
|
|
|
|
|
+ // 只有接口未提供值时才使用临时值
|
|
|
|
|
+ if (!configDialog.current.totalRunTime) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 处理累计运行公里数
|
|
|
|
|
+ if (configDialog.current.mileageRule !== 0) {
|
|
|
|
|
+ configDialog.current.type = null
|
|
|
|
|
+ configDialog.current.totalMileage = null
|
|
|
|
|
+ configDialog.form.accumulatedMileageOption = null // 清除选择值
|
|
|
|
|
+ } else if (configDialog.form.accumulatedMileageOption) {
|
|
|
|
|
+ // 查找选中的累计运行公里数项
|
|
|
|
|
+ const selectedMileageOption = configDialog.current.mileageAccumulatedAttrs?.find(
|
|
|
|
|
+ (item) => item.pointName === configDialog.form.accumulatedMileageOption
|
|
|
|
|
+ )
|
|
|
|
|
+ if (selectedMileageOption) {
|
|
|
|
|
+ configDialog.current.type = selectedMileageOption.pointName
|
|
|
|
|
+ configDialog.current.tempTotalMileage = selectedMileageOption.totalRunTime
|
|
|
|
|
+ configDialog.current.isMileageFromTemp = true
|
|
|
|
|
+ if (!configDialog.current.totalMileage) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新当前行的数据
|
|
|
|
|
+ if (configDialog.current) {
|
|
|
|
|
+ Object.assign(configDialog.current, updateData)
|
|
|
|
|
+ // 重新计算 下次保养公里数 剩余公里数
|
|
|
|
|
+ configDialog.current.nextMaintenanceKm = calculateNextMaintenanceKm(configDialog.current)
|
|
|
|
|
+ configDialog.current.remainKm = calculateRemainKm(configDialog.current)
|
|
|
|
|
+ // 重新计算 下次保养运行时长 剩余时长
|
|
|
|
|
+ configDialog.current.nextMaintenanceH = calculateNextMaintenanceH(configDialog.current)
|
|
|
|
|
+ configDialog.current.remainH = calculateRemainH(configDialog.current)
|
|
|
|
|
+ // 重新计算 下次保养日期 剩余天数
|
|
|
|
|
+ if (configDialog.form.lastNaturalDate) {
|
|
|
|
|
+ configDialog.current.tempLastNaturalDate = dayjs(configDialog.form.lastNaturalDate).format(
|
|
|
|
|
+ 'YYYY-MM-DD'
|
|
|
|
|
+ )
|
|
|
|
|
+ configDialog.current.nextMaintenanceDate = calculateNextMaintenanceDate(
|
|
|
|
|
+ configDialog.current
|
|
|
|
|
+ )
|
|
|
|
|
+ configDialog.current.remainDay = calculateRemainDay(configDialog.current)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ configDialog.visible = false
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const queryParams = reactive<any>({
|
|
|
|
|
+ deviceIds: undefined,
|
|
|
|
|
+ planId: undefined,
|
|
|
|
|
+ bomFlag: 'b'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 处理保养规则变化 取消保养规则 时 清空已经设置的相应保养规则数据
|
|
|
|
|
+const handleRuleChange = (
|
|
|
|
|
+ row: IotMaintenanceBomVO,
|
|
|
|
|
+ ruleType: 'mileage' | 'runningTime' | 'date'
|
|
|
|
|
+) => {
|
|
|
|
|
+ // 当规则关闭时(inactive-value=1)
|
|
|
|
|
+ console.log('执行了保养规则变化事件' + row.totalRunTime + ' - ' + row.totalMileage)
|
|
|
|
|
+ // 当前保养项行已经返回了 totalRunTime totalMileage 数据 不需要再清空 累计运行时长 累计公里数
|
|
|
|
|
+
|
|
|
|
|
+ // 选择完设备匹配了保养项后 不能直接置空 因为可能是正常的累计时长 累计公里数
|
|
|
|
|
+ if (ruleType === 'runningTime' && row.runningTimeRule === 1) {
|
|
|
|
|
+ // 清除临时来源的值
|
|
|
|
|
+ if (row.isRuntimeFromTemp) {
|
|
|
|
|
+ row.totalRunTime = null
|
|
|
|
|
+ row.tempTotalRunTime = null
|
|
|
|
|
+ row.code = null
|
|
|
|
|
+ // row.isRuntimeFromTemp = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 强制清除配置对话框中的值(如果打开的是当前行)
|
|
|
|
|
+ if (
|
|
|
|
|
+ configDialog.current?.deviceId === row.deviceId &&
|
|
|
|
|
+ configDialog.current?.bomNodeId === row.bomNodeId
|
|
|
|
|
+ ) {
|
|
|
|
|
+ configDialog.form.accumulatedTimeOption = null
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (ruleType === 'mileage' && row.mileageRule === 1) {
|
|
|
|
|
+ if (row.isMileageFromTemp) {
|
|
|
|
|
+ row.totalMileage = null
|
|
|
|
|
+ row.tempTotalMileage = null
|
|
|
|
|
+ row.type = null
|
|
|
|
|
+ // row.isMileageFromTemp = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 强制清除配置对话框中的值(如果打开的是当前行)
|
|
|
|
|
+ if (
|
|
|
|
|
+ configDialog.current?.deviceId === row.deviceId &&
|
|
|
|
|
+ configDialog.current?.bomNodeId === row.bomNodeId
|
|
|
|
|
+ ) {
|
|
|
|
|
+ configDialog.form.accumulatedMileageOption = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果配置对话框打开的是当前行,同步清除对话框中的选择值
|
|
|
|
|
+ if (
|
|
|
|
|
+ configDialog.visible &&
|
|
|
|
|
+ configDialog.current &&
|
|
|
|
|
+ configDialog.current.deviceId === row.deviceId &&
|
|
|
|
|
+ configDialog.current.bomNodeId === row.bomNodeId
|
|
|
|
|
+ ) {
|
|
|
|
|
+ if (ruleType === 'runningTime') {
|
|
|
|
|
+ configDialog.form.accumulatedTimeOption = null
|
|
|
|
|
+ } else if (ruleType === 'mileage') {
|
|
|
|
|
+ configDialog.form.accumulatedMileageOption = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 规则变化后按新条件重新计算 下次保养公里数 剩余公里数
|
|
|
|
|
+ if (ruleType === 'mileage') {
|
|
|
|
|
+ if (row.mileageRule === 0) {
|
|
|
|
|
+ row.nextMaintenanceKm = calculateNextMaintenanceKm(row)
|
|
|
|
|
+ row.remainKm = calculateRemainKm(row)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ row.nextMaintenanceKm = null
|
|
|
|
|
+ row.remainKm = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 规则变化后按新条件重新计算 下次保养时长 剩余时长
|
|
|
|
|
+ if (ruleType === 'runningTime') {
|
|
|
|
|
+ if (row.runningTimeRule === 0) {
|
|
|
|
|
+ row.nextMaintenanceH = calculateNextMaintenanceH(row)
|
|
|
|
|
+ row.remainH = calculateRemainH(row)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ row.nextMaintenanceH = null
|
|
|
|
|
+ row.remainH = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 规则变化后按新条件重新计算 下次保养日期 剩余天数
|
|
|
|
|
+ if (ruleType === 'date') {
|
|
|
|
|
+ if (row.naturalDateRule === 0) {
|
|
|
|
|
+ row.nextMaintenanceDate = calculateNextMaintenanceDate(row)
|
|
|
|
|
+ row.remainDay = calculateRemainDay(row)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ row.nextMaintenanceDate = null
|
|
|
|
|
+ row.remainDay = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const deviceChoose = async (selectedDevices) => {
|
|
|
|
|
+ const newIds = selectedDevices.map((device) => device.id)
|
|
|
|
|
+ deviceIds.value = [...new Set([...deviceIds.value, ...newIds])]
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ deviceIds: newIds.join(',') // 明确传递数组参数
|
|
|
|
|
+ }
|
|
|
|
|
+ queryParams.deviceIds = JSON.parse(JSON.stringify(params.deviceIds))
|
|
|
|
|
+ queryParams.bomFlag = 'b'
|
|
|
|
|
+ // 根据选择的设备筛选出设备BOM中与保养相关的节点项
|
|
|
|
|
+ const res = await IotDeviceApi.deviceAssociateBomList(queryParams)
|
|
|
|
|
+ const rawData = res || []
|
|
|
|
|
+ if (rawData.length === 0) {
|
|
|
|
|
+ message.error('选择的设备不存在待保养BOM项')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!Array.isArray(rawData)) {
|
|
|
|
|
+ console.error('接口返回数据结构异常:', rawData)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建当前列表的唯一键集合(关键修改)
|
|
|
|
|
+ const existingKeys = new Set(list.value.map((item) => `${item.deviceId}-${item.bomNodeId}`))
|
|
|
|
|
+
|
|
|
|
|
+ // 转换数据结构(根据你的接口定义调整)
|
|
|
|
|
+ const newItems = rawData
|
|
|
|
|
+ .filter((device) => {
|
|
|
|
|
+ // 排除已存在的项(设备ID+bom节点ID)
|
|
|
|
|
+ const key = `${device.id}-${device.bomNodeId}`
|
|
|
|
|
+ return !existingKeys.has(key)
|
|
|
|
|
+ })
|
|
|
|
|
+ .map((device) => ({
|
|
|
|
|
+ assetClass: device.assetClass,
|
|
|
|
|
+ deviceCode: device.deviceCode,
|
|
|
|
|
+ deviceName: device.deviceName,
|
|
|
|
|
+ deviceStatus: device.deviceStatus,
|
|
|
|
|
+ deptName: device.deptName,
|
|
|
|
|
+ name: device.name,
|
|
|
|
|
+ code: device.code,
|
|
|
|
|
+ assetProperty: device.assetProperty,
|
|
|
|
|
+ remark: null, // 初始化备注
|
|
|
|
|
+ deviceId: device.id, // 移除操作需要
|
|
|
|
|
+ bomNodeId: device.bomNodeId,
|
|
|
|
|
+ totalRunTime: device.totalRunTime,
|
|
|
|
|
+ totalMileage: device.totalMileage,
|
|
|
|
|
+ nextRunningKilometers: 0,
|
|
|
|
|
+ nextRunningTime: 0,
|
|
|
|
|
+ nextNaturalDate: 0,
|
|
|
|
|
+ lastNaturalDate: null, // 初始化为null而不是0
|
|
|
|
|
+ // 保养规则 提前量
|
|
|
|
|
+ kiloCycleLead: 0,
|
|
|
|
|
+ timePeriodLead: 0,
|
|
|
|
|
+ naturalDatePeriodLead: 0,
|
|
|
|
|
+ tempTotalRunTime: null,
|
|
|
|
|
+ tempTotalMileage: null,
|
|
|
|
|
+ isRuntimeFromTemp: false,
|
|
|
|
|
+ isMileageFromTemp: false,
|
|
|
|
|
+ // 添加累计时长参数列表 属性
|
|
|
|
|
+ timeAccumulatedAttrs: device.timeAccumulatedAttrs || [],
|
|
|
|
|
+ // 添加累计里程参数列表 属性
|
|
|
|
|
+ mileageAccumulatedAttrs: device.mileageAccumulatedAttrs || []
|
|
|
|
|
+ }))
|
|
|
|
|
+ // 获取选择的设备相关的id数组
|
|
|
|
|
+ newItems.forEach((item) => {
|
|
|
|
|
+ deviceIds.value.push(item.deviceId)
|
|
|
|
|
+ })
|
|
|
|
|
+ // 合并到现有列表(去重)
|
|
|
|
|
+ newItems.forEach((item) => {
|
|
|
|
|
+ const exists = list.value.some(
|
|
|
|
|
+ (existing) => existing.deviceId === item.deviceId && existing.bomNodeId === item.bomNodeId
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!exists) {
|
|
|
|
|
+ list.value.push(item as any)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ // 排序保养项
|
|
|
|
|
+ applySorting()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算下次保养公里数(通用函数)
|
|
|
|
|
+const calculateNextMaintenanceKm = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 验证条件:规则开启 + 两个值都存在且 > 0
|
|
|
|
|
+ const isValid =
|
|
|
|
|
+ row.mileageRule === 0 && row.lastRunningKilometers > 0 && row.nextRunningKilometers > 0
|
|
|
|
|
+
|
|
|
|
|
+ return isValid ? row.lastRunningKilometers + row.nextRunningKilometers : null // 不满足条件返回null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算剩余保养公里数(通用函数)
|
|
|
|
|
+const calculateRemainKm = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 确定使用的里程值(优先totalMileage)
|
|
|
|
|
+ const mileageValue = row.totalMileage ?? row.tempTotalMileage ?? 0
|
|
|
|
|
+ // 验证条件:规则开启 + 3个值都存在且 > 0
|
|
|
|
|
+ const isValid =
|
|
|
|
|
+ row.mileageRule === 0 &&
|
|
|
|
|
+ row.lastRunningKilometers > 0 &&
|
|
|
|
|
+ mileageValue > 0 &&
|
|
|
|
|
+ row.nextRunningKilometers > 0
|
|
|
|
|
+
|
|
|
|
|
+ return isValid
|
|
|
|
|
+ ? parseFloat(
|
|
|
|
|
+ (row.nextRunningKilometers - (mileageValue - row.lastRunningKilometers)).toFixed(2)
|
|
|
|
|
+ )
|
|
|
|
|
+ : null // 不满足条件返回null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算下次保养运行时长(通用函数)
|
|
|
|
|
+const calculateNextMaintenanceH = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 验证条件:规则开启 + 两个值都存在且 > 0
|
|
|
|
|
+ const isValid = row.runningTimeRule === 0 && row.lastRunningTime > 0 && row.nextRunningTime > 0
|
|
|
|
|
+
|
|
|
|
|
+ return isValid ? row.lastRunningTime + row.nextRunningTime : null // 不满足条件返回null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算剩余运行时间(通用函数)
|
|
|
|
|
+const calculateRemainH = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 确定使用的 运行时长 值(优先 totalRunTime)
|
|
|
|
|
+ const runTimeValue = row.totalRunTime ?? row.tempTotalRunTime ?? 0
|
|
|
|
|
+ // 验证条件:规则开启 + 3个值都存在且 > 0
|
|
|
|
|
+ const isValid =
|
|
|
|
|
+ row.runningTimeRule === 0 &&
|
|
|
|
|
+ row.lastRunningTime > 0 &&
|
|
|
|
|
+ runTimeValue > 0 &&
|
|
|
|
|
+ row.nextRunningTime > 0
|
|
|
|
|
+
|
|
|
|
|
+ return isValid
|
|
|
|
|
+ ? parseFloat((row.nextRunningTime - (runTimeValue - row.lastRunningTime)).toFixed(2))
|
|
|
|
|
+ : null // 不满足条件返回null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算下次保养日期(通用函数)
|
|
|
|
|
+const calculateNextMaintenanceDate = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 验证条件:规则开启 + 两个值都存在且 > 0
|
|
|
|
|
+ const isValid = row.naturalDateRule === 0 && row.lastNaturalDate && row.nextNaturalDate
|
|
|
|
|
+
|
|
|
|
|
+ return isValid
|
|
|
|
|
+ ? dayjs(row.lastNaturalDate)
|
|
|
|
|
+ .add(row.nextNaturalDate as any, 'day')
|
|
|
|
|
+ .format('YYYY-MM-DD')
|
|
|
|
|
+ : null // 不满足条件返回null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算 自然日期保养 剩余天数(通用函数)
|
|
|
|
|
+const calculateRemainDay = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 验证条件:规则开启 + 两个值都存在且有效
|
|
|
|
|
+ const isValid =
|
|
|
|
|
+ row.naturalDateRule === 0 &&
|
|
|
|
|
+ row.lastNaturalDate !== null &&
|
|
|
|
|
+ row.nextNaturalDate !== null &&
|
|
|
|
|
+ row.nextNaturalDate > 0
|
|
|
|
|
+
|
|
|
|
|
+ if (!isValid) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 上次保养日期:将时间戳转换为 Day.js 对象
|
|
|
|
|
+ const lastNaturalDate = dayjs(row.lastNaturalDate)
|
|
|
|
|
+
|
|
|
|
|
+ // 计算下次保养日期
|
|
|
|
|
+ const nextMaintenanceDate = lastNaturalDate.add(row.nextNaturalDate as any, 'day')
|
|
|
|
|
+
|
|
|
|
|
+ // 计算剩余天数(当前日期到下次保养日期的天数差)
|
|
|
|
|
+ return nextMaintenanceDate.diff(dayjs(), 'day')
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('计算保养剩余天数错误:', error)
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatDate = (value?: number | string | null) => {
|
|
|
|
|
+ if (!value) return '-'
|
|
|
|
|
+ const date = dayjs(Number(value))
|
|
|
|
|
+ return date.isValid() ? date.format('YYYY-MM-DD') : '-'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 单元格类名回调方法
|
|
|
|
|
+const cellClassName = ({ row, column }) => {
|
|
|
|
|
+ // 只对序号列进行处理
|
|
|
|
|
+ if (column.type === 'index') {
|
|
|
|
|
+ // 检查该行所有启用的规则是否都已配置完整
|
|
|
|
|
+ if (checkRowFilled(row)) {
|
|
|
|
|
+ return 'all-filled' // 返回自定义类名
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 检查行数据是否完整填写
|
|
|
|
|
+const checkRowFilled = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 检查是否启用了至少一个规则
|
|
|
|
|
+ const hasRuleEnabled =
|
|
|
|
|
+ row.mileageRule === 0 || row.runningTimeRule === 0 || row.naturalDateRule === 0
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasRuleEnabled) {
|
|
|
|
|
+ return false // 没有任何规则启用,不显示背景色
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查里程规则
|
|
|
|
|
+ const mileageFilled =
|
|
|
|
|
+ row.mileageRule !== 0
|
|
|
|
|
+ ? true // 规则未启用,视为已"填写"
|
|
|
|
|
+ : row.lastRunningKilometers > 0 &&
|
|
|
|
|
+ row.nextRunningKilometers > 0 &&
|
|
|
|
|
+ row.kiloCycleLead > 0 &&
|
|
|
|
|
+ // 检查累计里程参数是否已选择(当条件满足时)
|
|
|
|
|
+ (!(
|
|
|
|
|
+ row.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalMileage == null || isNaN(row.totalMileage))
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (row.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalMileage == null || isNaN(row.totalMileage)) &&
|
|
|
|
|
+ row.type))
|
|
|
|
|
+
|
|
|
|
|
+ // 检查运行时间规则
|
|
|
|
|
+ const runningTimeFilled =
|
|
|
|
|
+ row.runningTimeRule !== 0
|
|
|
|
|
+ ? true
|
|
|
|
|
+ : row.lastRunningTime > 0 &&
|
|
|
|
|
+ row.nextRunningTime > 0 &&
|
|
|
|
|
+ row.timePeriodLead > 0 &&
|
|
|
|
|
+ // 检查累计时间参数是否已选择(当条件满足时)
|
|
|
|
|
+ (!(
|
|
|
|
|
+ row.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalRunTime == null || isNaN(row.totalRunTime))
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (row.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalRunTime == null || isNaN(row.totalRunTime)) &&
|
|
|
|
|
+ row.code))
|
|
|
|
|
+
|
|
|
|
|
+ // 检查自然日期规则
|
|
|
|
|
+ const naturalDateFilled =
|
|
|
|
|
+ row.naturalDateRule !== 0
|
|
|
|
|
+ ? true
|
|
|
|
|
+ : row.lastNaturalDate && (row.nextNaturalDate ?? 0) > 0 && row.naturalDatePeriodLead > 0
|
|
|
|
|
+
|
|
|
|
|
+ return mileageFilled && runningTimeFilled && naturalDateFilled
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算属性 - 检查是否有开启的里程规则
|
|
|
|
|
+const hasMileageRule = computed(() => {
|
|
|
|
|
+ return list.value.some((row) => row.mileageRule === 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 计算属性 - 检查是否有开启的运行时间规则
|
|
|
|
|
+const hasTimeRule = computed(() => {
|
|
|
|
|
+ return list.value.some((row) => row.runningTimeRule === 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 计算属性 - 检查是否有开启的自然日期规则
|
|
|
|
|
+const hasDateRule = computed(() => {
|
|
|
|
|
+ return list.value.some((row) => row.naturalDateRule === 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 为每一行建立lastNaturalDate到tempLastNaturalDate的同步
|
|
|
|
|
+const setupNaturalDateSync = (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ // 如果该行已有watcher则跳过
|
|
|
|
|
+ if (lastNaturalDateWatchers.value.has(row.id)) return
|
|
|
|
|
+
|
|
|
|
|
+ // 为该行创建单独的watcher
|
|
|
|
|
+ const unwatch = watch(
|
|
|
|
|
+ () => row.lastNaturalDate,
|
|
|
|
|
+ (newVal) => {
|
|
|
|
|
+ // 转换日期格式 (时间戳 -> YYYY-MM-DD)
|
|
|
|
|
+ row.tempLastNaturalDate = newVal ? dayjs(newVal).format('YYYY-MM-DD') : null
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true, deep: true }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 保存watcher用于后续清理
|
|
|
|
|
+ lastNaturalDateWatchers.value.set(row.id, unwatch)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 监听规则列变化,重新布局表格
|
|
|
|
|
+watch(
|
|
|
|
|
+ [list, hasMileageRule, hasTimeRule, hasDateRule],
|
|
|
|
|
+ () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ tableRef.value?.elTableRef?.doLayout?.()
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const deviceFormRef = ref<InstanceType<typeof MainPlanDeviceList>>()
|
|
|
|
|
+const openForm = () => {
|
|
|
|
|
+ if (isReadonly.value) return
|
|
|
|
|
+ deviceFormRef.value?.open()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const close = () => {
|
|
|
|
|
+ delView(unref(currentRoute))
|
|
|
|
|
+ push({ name: 'IotMaintenancePlan', params: {} })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 提交表单 */
|
|
|
|
|
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
|
|
|
+const submitForm = async () => {
|
|
|
|
|
+ if (isReadonly.value) return
|
|
|
|
|
+ // 校验表单
|
|
|
|
|
+ await formRef.value.validate()
|
|
|
|
|
+ // 校验表格数据
|
|
|
|
|
+ const isValid = validateTableData()
|
|
|
|
|
+ if (!isValid) return
|
|
|
|
|
+ // 提交请求
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 将值为NULL 的保养规则 设置为 1
|
|
|
|
|
+ list.value.forEach((item) => {
|
|
|
|
|
+ // 确保保养规则不为null
|
|
|
|
|
+ item.mileageRule = item.mileageRule ?? 1
|
|
|
|
|
+ item.runningTimeRule = item.runningTimeRule ?? 1
|
|
|
|
|
+ item.naturalDateRule = item.naturalDateRule ?? 1
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 转换日期格式
|
|
|
|
|
+ const convertedList = list.value.map((item) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ lastNaturalDate:
|
|
|
|
|
+ typeof item.lastNaturalDate === 'number'
|
|
|
|
|
+ ? item.lastNaturalDate
|
|
|
|
|
+ : item.lastNaturalDate
|
|
|
|
|
+ ? dayjs(item.lastNaturalDate).valueOf()
|
|
|
|
|
+ : null
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ const data = {
|
|
|
|
|
+ mainPlan: formData.value,
|
|
|
|
|
+ mainPlanBom: convertedList
|
|
|
|
|
+ }
|
|
|
|
|
+ if (formType.value === 'create') {
|
|
|
|
|
+ await IotMaintenancePlanApi.createIotMaintenancePlan(data)
|
|
|
|
|
+ message.success(t('common.createSuccess'))
|
|
|
|
|
+ close()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await IotMaintenancePlanApi.updatePlan(data)
|
|
|
|
|
+ message.success(t('common.updateSuccess'))
|
|
|
|
|
+ close()
|
|
|
|
|
+ }
|
|
|
|
|
+ // 发送操作成功的事件
|
|
|
|
|
+ emit('success')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ formLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 新增表单校验规则
|
|
|
|
|
+const configFormRules = reactive({
|
|
|
|
|
+ nextRunningKilometers: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '里程周期必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ kiloCycleLead: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '提前量必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nextRunningTime: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '时间周期必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ timePeriodLead: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '提前量必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nextNaturalDate: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '自然日周期必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ naturalDatePeriodLead: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ message: '提前量必须填写',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+/** 校验表格数据 */
|
|
|
|
|
+const validateTableData = (): boolean => {
|
|
|
|
|
+ let isValid = true
|
|
|
|
|
+ const errorMessages: string[] = []
|
|
|
|
|
+ const noRulesErrorMessages: string[] = [] // 未设置任何保养项规则 的错误提示信息
|
|
|
|
|
+ const noRules: string[] = [] // 行记录中设置了保养规则的记录数量
|
|
|
|
|
+ const configErrors: string[] = [] // 保养规则配置弹出框
|
|
|
|
|
+ let shouldBreak = false
|
|
|
|
|
+
|
|
|
|
|
+ if (list.value.length === 0) {
|
|
|
|
|
+ errorMessages.push('请至少添加一条设备保养明细')
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ // 直接返回无需后续校验
|
|
|
|
|
+ message.error('请至少添加一条设备保养明细')
|
|
|
|
|
+ return isValid
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ list.value.forEach((row, index) => {
|
|
|
|
|
+ if (shouldBreak) return
|
|
|
|
|
+ const rowNumber = index + 1 // 用户可见的行号从1开始
|
|
|
|
|
+ const deviceIdentifier = `${row.deviceCode}-${row.name}` // 设备标识
|
|
|
|
|
+
|
|
|
|
|
+ // 累计参数校验逻辑
|
|
|
|
|
+ if (
|
|
|
|
|
+ row.mileageRule === 0 &&
|
|
|
|
|
+ row.mileageAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalMileage == null || isNaN(row.totalMileage)) &&
|
|
|
|
|
+ !row.type
|
|
|
|
|
+ ) {
|
|
|
|
|
+ errorMessages.push(`第 ${rowNumber} 行(${deviceIdentifier}):请选择累计运行公里数参数`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ row.runningTimeRule === 0 &&
|
|
|
|
|
+ row.timeAccumulatedAttrs?.length &&
|
|
|
|
|
+ (row.totalRunTime == null || isNaN(row.totalRunTime)) &&
|
|
|
|
|
+ !row.code
|
|
|
|
|
+ ) {
|
|
|
|
|
+ errorMessages.push(`第 ${rowNumber} 行(${deviceIdentifier}):请选择累计运行时长参数`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 校验逻辑
|
|
|
|
|
+ const checkConfig = (ruleName: string, ruleValue: number, configField: keyof typeof row) => {
|
|
|
|
|
+ if (ruleValue === 0) {
|
|
|
|
|
+ // 规则开启
|
|
|
|
|
+ if (!row[configField] || (row[configField] as any) <= 0) {
|
|
|
|
|
+ configErrors.push(
|
|
|
|
|
+ `第 ${rowNumber} 行(${deviceIdentifier}):请点击【配置】维护${ruleName}上次保养值`
|
|
|
|
|
+ )
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 里程校验逻辑
|
|
|
|
|
+ if (row.mileageRule === 0) {
|
|
|
|
|
+ // 假设 0 表示开启状态
|
|
|
|
|
+ if (!row.nextRunningKilometers || row.nextRunningKilometers <= 0) {
|
|
|
|
|
+ errorMessages.push(`第 ${rowNumber} 行:开启里程规则必须填写有效的里程周期`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+ // 再校验配置值
|
|
|
|
|
+ checkConfig('里程', row.mileageRule, 'lastRunningKilometers')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ noRules.push(`第 ${rowNumber} 行:未设置里程规则`)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 运行时间校验逻辑
|
|
|
|
|
+ if (row.runningTimeRule === 0) {
|
|
|
|
|
+ if (!row.nextRunningTime || row.nextRunningTime <= 0) {
|
|
|
|
|
+ errorMessages.push(`第 ${rowNumber} 行:开启运行时间规则必须填写有效的时间周期`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+ checkConfig('运行时间', row.runningTimeRule, 'lastRunningTime')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ noRules.push(`第 ${rowNumber} 行:未设置运行时间规则`)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 自然日期校验逻辑
|
|
|
|
|
+ if (row.naturalDateRule === 0) {
|
|
|
|
|
+ if (!row.nextNaturalDate) {
|
|
|
|
|
+ errorMessages.push(`第 ${rowNumber} 行:开启自然日期规则必须填写有效的自然日期周期`)
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ }
|
|
|
|
|
+ checkConfig('自然日期', row.naturalDateRule, 'lastNaturalDate')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ noRules.push(`第 ${rowNumber} 行:未设置自然日期规则`)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果选中的一行记录未设置任何保养规则 提示 ‘保养项未设置任何保养规则’
|
|
|
|
|
+ if (noRules.length === 3) {
|
|
|
|
|
+ isValid = false
|
|
|
|
|
+ shouldBreak = true // 设置标志变量为true,退出循环
|
|
|
|
|
+ noRulesErrorMessages.push('保养项至少设置1个保养规则')
|
|
|
|
|
+ }
|
|
|
|
|
+ noRules.length = 0
|
|
|
|
|
+ })
|
|
|
|
|
+ if (errorMessages.length > 0) {
|
|
|
|
|
+ message.error('设置保养规则后,请维护对应的周期值')
|
|
|
|
|
+ } else if (noRulesErrorMessages.length > 0) {
|
|
|
|
|
+ message.error(noRulesErrorMessages.pop() ?? '')
|
|
|
|
|
+ } else if (configErrors.length > 0) {
|
|
|
|
|
+ message.error(configErrors.pop() ?? '')
|
|
|
|
|
+ }
|
|
|
|
|
+ return isValid
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 修改后的排序应用方法
|
|
|
|
|
+const applySorting = () => {
|
|
|
|
|
+ // 创建新数组并排序
|
|
|
|
|
+ const sortedList = sortDeviceList(list.value)
|
|
|
|
|
+
|
|
|
|
|
+ // 使用Vue的响应式方法更新数组
|
|
|
|
|
+ list.value = sortedList
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 保养项排序函数
|
|
|
|
|
+const sortDeviceList = (devices: IotMaintenanceBomVO[]) => {
|
|
|
|
|
+ // 使用slice()创建数组副本,避免修改原数组
|
|
|
|
|
+ return devices.slice().sort((a, b) => {
|
|
|
|
|
+ // 处理可能的空值
|
|
|
|
|
+ const aCode = a.deviceCode || ''
|
|
|
|
|
+ const bCode = b.deviceCode || ''
|
|
|
|
|
+ const aName = a.name || ''
|
|
|
|
|
+ const bName = b.name || ''
|
|
|
|
|
+
|
|
|
|
|
+ // 设备编码排序
|
|
|
|
|
+ if (aCode !== bCode) {
|
|
|
|
|
+ return aCode.localeCompare(bCode)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保养项名称排序
|
|
|
|
|
+ return aName.localeCompare(bName)
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 累计运行时长变更
|
|
|
|
|
+const handleAccumulatedTimeChange = (option) => {}
|
|
|
|
|
+
|
|
|
|
|
+// 累计运行公里数变更
|
|
|
|
|
+const handleAccumulatedMileageChange = (option) => {}
|
|
|
|
|
+
|
|
|
|
|
+/** 重置表单 */
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ formData.value = {
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ deptId: undefined,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ serialNumber: undefined,
|
|
|
|
|
+ responsiblePerson: undefined,
|
|
|
|
|
+ remark: undefined,
|
|
|
|
|
+ failureName: undefined,
|
|
|
|
|
+ status: undefined,
|
|
|
|
|
+ devicePersons: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ formRef.value?.resetFields()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const normalizePlanBom = (item: IotMaintenanceBomVO) => {
|
|
|
|
|
+ if (item.mileageRule === 0) {
|
|
|
|
|
+ item.nextMaintenanceKm = calculateNextMaintenanceKm(item)
|
|
|
|
|
+ item.remainKm = calculateRemainKm(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (item.runningTimeRule === 0) {
|
|
|
|
|
+ item.nextMaintenanceH = calculateNextMaintenanceH(item)
|
|
|
|
|
+ item.remainH = calculateRemainH(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (item.naturalDateRule === 0) {
|
|
|
|
|
+ item.nextMaintenanceDate = calculateNextMaintenanceDate(item)
|
|
|
|
|
+ item.remainDay = calculateRemainDay(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ setupNaturalDateSync(item)
|
|
|
|
|
+ return item
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const initPage = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ list.value = []
|
|
|
|
|
+ deviceIds.value = []
|
|
|
|
|
+ lastNaturalDateWatchers.value.clear()
|
|
|
|
|
+ resetForm()
|
|
|
|
|
+
|
|
|
|
|
+ queryParams.planId = id.value
|
|
|
|
|
+ queryParams.deviceIds = undefined
|
|
|
|
|
+ queryParams.bomFlag = 'b'
|
|
|
|
|
+
|
|
|
|
|
+ const deptId = useUserStore().getUser.deptId
|
|
|
|
|
+ dept.value = await DeptApi.getDept(deptId)
|
|
|
|
|
+ formData.value.name = dept.value.name + ' - 保养计划'
|
|
|
|
|
+ formData.value.deptId = deptId
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (mode.value === 'create') {
|
|
|
|
|
+ formType.value = 'create'
|
|
|
|
|
+ const { wsCache } = useCache()
|
|
|
|
|
+ const userInfo = wsCache.get(CACHE_KEY.USER)
|
|
|
|
|
+ formData.value.responsiblePerson = userInfo.user.id
|
|
|
|
|
+ } else if (id.value) {
|
|
|
|
|
+ formType.value = mode.value === 'detail' ? 'detail' : 'update'
|
|
|
|
|
+ const plan = await IotMaintenancePlanApi.getIotMaintenancePlan(Number(id.value))
|
|
|
|
|
+ deviceLabel.value = plan.deviceName
|
|
|
|
|
+ formData.value = plan
|
|
|
|
|
+ const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams)
|
|
|
|
|
+ if (Array.isArray(data)) {
|
|
|
|
|
+ list.value = data.map(normalizePlanBom)
|
|
|
|
|
+ applySorting()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ formLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => route.fullPath,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ initPage()
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(async () => {})
|
|
|
|
|
+
|
|
|
|
|
+const handleDelete = async (row: IotMaintenanceBomVO) => {
|
|
|
|
|
+ if (isReadonly.value) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ const deviceId = row.deviceId
|
|
|
|
|
+ const bomNodeId = row.bomNodeId
|
|
|
|
|
+ // 删除列表项
|
|
|
|
|
+ const index = list.value.findIndex(
|
|
|
|
|
+ (item) => item.deviceId === deviceId && item.bomNodeId === bomNodeId
|
|
|
|
|
+ )
|
|
|
|
|
+ if (index !== -1) {
|
|
|
|
|
+ list.value.splice(index, 1)
|
|
|
|
|
+ // 删除保养项后对保养项重新排序
|
|
|
|
|
+ applySorting()
|
|
|
|
|
+ deviceIds.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ // 更新设备ID列表(需要检查是否还有该设备的其他项)
|
|
|
|
|
+ const hasOtherItems = list.value.some((item) => item.deviceId === deviceId)
|
|
|
|
|
+ if (!hasOtherItems) {
|
|
|
|
|
+ deviceIds.value = deviceIds.value.filter((id) => id !== deviceId)
|
|
|
|
|
+ }
|
|
|
|
|
+ // message.success('移除成功')
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('移除失败:', error)
|
|
|
|
|
+ message.error('移除失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.maintenance-plan-page {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ height: calc(
|
|
|
|
|
+ 100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
|
|
|
|
|
+ );
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ background: var(--el-bg-color-page);
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-section {
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ background: var(--el-bg-color);
|
|
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-section {
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form :deep(.el-textarea__inner) {
|
|
|
|
|
+ resize: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-table-section {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ flex: 1 1 auto;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+ justify-content: flex-start;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-resizer {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ flex: 1 1 auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.maintenance-plan-table {
|
|
|
|
|
+ --zm-table-radius: 2px;
|
|
|
|
|
+ --zm-table-cell-height: 32px;
|
|
|
|
|
+ --zm-table-header-cell-height: 38px;
|
|
|
|
|
+ --zm-table-header-group-cell-height: 42px;
|
|
|
|
|
+ --zm-table-font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.full-content-cell {
|
|
|
|
|
+ overflow: visible;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ background: var(--el-bg-color);
|
|
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-input-number .el-input__inner) {
|
|
|
|
|
+ padding-left: 10px;
|
|
|
|
|
+ text-align: left !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-group {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ padding: 20px 15px 10px;
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+ border: 1px solid var(--el-border-color);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.group-title {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: -10px;
|
|
|
|
|
+ left: 20px;
|
|
|
|
|
+ padding: 0 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+ background: var(--el-bg-color);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.zm-table .all-filled) {
|
|
|
|
|
+ background-color: #67c23a !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.zm-table .all-filled .cell) {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|