|
@@ -0,0 +1,859 @@
|
|
|
+<template>
|
|
|
+ <ContentWrap v-loading="formLoading">
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ v-loading="formLoading"
|
|
|
+ style="margin-right: 4em; margin-left: 0.5em; margin-top: 1em"
|
|
|
+ label-width="130px"
|
|
|
+ >
|
|
|
+ <div class="base-expandable-content">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item :label="t('main.planName')" prop="name">
|
|
|
+ <el-input type="text" v-model="formData.name" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item :label="t('main.planCode')" prop="serialNumber">
|
|
|
+ <el-input type="text" 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" :placeholder="t('iotMaintain.remarkHolder')" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <!-- 搜索工作栏 -->
|
|
|
+ <el-form
|
|
|
+ class="-mb-15px"
|
|
|
+ :model="queryParams"
|
|
|
+ ref="queryFormRef"
|
|
|
+ :inline="true"
|
|
|
+ label-width="68px"
|
|
|
+ >
|
|
|
+ <el-form-item>
|
|
|
+ <el-button @click="openForm" type="warning">
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <ContentWrap>
|
|
|
+ <el-table v-loading="loading" :data="pagedList" :stripe="true" :show-overflow-tooltip="true">
|
|
|
+ <!-- 添加序号列 -->
|
|
|
+ <el-table-column
|
|
|
+ type="index"
|
|
|
+ :label="t('iotDevice.serial')"
|
|
|
+ width="70"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column label="设备id" align="center" prop="deviceId" v-if="false"/>
|
|
|
+ <el-table-column :label="t('iotMaintain.deviceCode')" align="center" prop="deviceCode" />
|
|
|
+ <el-table-column :label="t('iotMaintain.deviceName')" align="center" prop="deviceName" />
|
|
|
+ <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime" :formatter="erpPriceTableColumnFormatter"/>
|
|
|
+ <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage" :formatter="erpPriceTableColumnFormatter"/>
|
|
|
+ <el-table-column :label="t('bomList.bomNode')" align="center" prop="name" />
|
|
|
+ <el-table-column :label="t('main.mileage')" key="mileageRule" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-switch
|
|
|
+ v-model="scope.row.mileageRule"
|
|
|
+ :active-value="0"
|
|
|
+ :inactive-value="1"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :label="t('main.runTime')" key="runningTimeRule" width="90">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-switch
|
|
|
+ v-model="scope.row.runningTimeRule"
|
|
|
+ :active-value="0"
|
|
|
+ :inactive-value="1"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :label="t('main.date')" key="naturalDateRule" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-switch
|
|
|
+ v-model="scope.row.naturalDateRule"
|
|
|
+ :active-value="0"
|
|
|
+ :inactive-value="1"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :label="t('operationFill.operation')" align="center" min-width="120px">
|
|
|
+ <template #default="scope">
|
|
|
+ <div style="display: flex; justify-content: center; align-items: center; width: 100%">
|
|
|
+ <div>
|
|
|
+ <Icon style="vertical-align: middle; color: #ea3434" icon="ep:zoom-out" />
|
|
|
+ <el-button
|
|
|
+ style="vertical-align: middle"
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ @click="handleDelete(scope.row.deviceId+'-'+scope.row.bomNodeId)"
|
|
|
+ >
|
|
|
+ {{t('modelTemplate.delete')}}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <!-- 新增配置按钮 -->
|
|
|
+ <div style="margin-left: 12px">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="openConfigDialog(scope.row)"
|
|
|
+ >
|
|
|
+ {{t('modelTemplate.update')}}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 添加分页组件 -->
|
|
|
+ <el-pagination
|
|
|
+ style="margin-top: 20px; justify-content: flex-end"
|
|
|
+ :total="list.length"
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ />
|
|
|
+ </ContentWrap>
|
|
|
+ </ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <el-form>
|
|
|
+ <el-form-item style="float: right">
|
|
|
+ <el-button @click="submitForm" type="primary" :disabled="formLoading">{{t('iotMaintain.save')}}</el-button>
|
|
|
+ <el-button @click="close">{{t('iotMaintain.cancel')}}</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+ <MainPlanDeviceList 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ v-model="configDialog.form.lastNaturalDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+ v-model="configDialog.form.naturalDatePeriodLead"
|
|
|
+ :min="0"
|
|
|
+ controls-position="right"
|
|
|
+ :controls="false"
|
|
|
+ style="width: 60%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="configDialog.visible = false">{{ t('common.cancel') }}</el-button>
|
|
|
+ <el-button type="primary" @click="saveConfig">{{ t('common.save') }}</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+</template>
|
|
|
+<script setup lang="ts">
|
|
|
+import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
|
|
|
+import * as UserApi from '@/api/system/user'
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
|
|
|
+import { IotMaintenancePlanApi, IotMaintenancePlanVO } from '@/api/pms/maintenance'
|
|
|
+import { useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
+import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
|
|
|
+import MainPlanDeviceList from "@/views/pms/maintenance/MainPlanDeviceList.vue";
|
|
|
+import * as DeptApi from "@/api/system/dept";
|
|
|
+import {erpPriceTableColumnFormatter} from "@/utils";
|
|
|
+import dayjs from 'dayjs'
|
|
|
+
|
|
|
+/** 保养计划 编辑 */
|
|
|
+defineOptions({ name: 'IotMainPlanEdit' })
|
|
|
+
|
|
|
+const { t } = useI18n() // 国际化
|
|
|
+const message = useMessage() // 消息弹窗
|
|
|
+const { delView } = useTagsViewStore() // 视图操作
|
|
|
+const { currentRoute, push } = useRouter()
|
|
|
+const deptUsers = ref<UserApi.UserVO[]>([]) // 用户列表
|
|
|
+const dept = ref() // 当前登录人所属部门对象
|
|
|
+const configFormRef = ref() // 配置弹出框对象
|
|
|
+const dialogTitle = ref('') // 弹窗的标题
|
|
|
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
|
|
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
|
|
+const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
|
|
|
+const list = ref<IotMaintenanceBomVO[]>([]) // 设备bom关联列表的数据
|
|
|
+
|
|
|
+const deviceIds = ref<number[]>([]) // 已经选择的设备id数组
|
|
|
+
|
|
|
+// 分页相关变量
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+
|
|
|
+// 计算分页后的数据
|
|
|
+const pagedList = computed(() => {
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value
|
|
|
+ const end = start + pageSize.value
|
|
|
+ return list.value.slice(start, end)
|
|
|
+})
|
|
|
+
|
|
|
+// 处理页码变化
|
|
|
+const handlePageChange = (page: number) => {
|
|
|
+ currentPage.value = page
|
|
|
+}
|
|
|
+
|
|
|
+// 处理每页数量变化
|
|
|
+const handleSizeChange = (size: number) => {
|
|
|
+ pageSize.value = size
|
|
|
+ currentPage.value = 1 // 重置到第一页
|
|
|
+}
|
|
|
+
|
|
|
+const { params, name } = useRoute() // 查询参数
|
|
|
+const id = params.id
|
|
|
+const formData = ref({
|
|
|
+ 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: '',
|
|
|
+ // 保养规则 周期
|
|
|
+ nextRunningKilometers: 0,
|
|
|
+ nextRunningTime: 0,
|
|
|
+ nextNaturalDate: 0,
|
|
|
+ // 提前量
|
|
|
+ kiloCycleLead: 0,
|
|
|
+ timePeriodLead: 0,
|
|
|
+ naturalDatePeriodLead: 0
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 打开配置对话框
|
|
|
+const openConfigDialog = (row: IotMaintenanceBomVO) => {
|
|
|
+ configDialog.current = row
|
|
|
+
|
|
|
+ // 处理日期初始化(核心修改)
|
|
|
+ let initialDate = ''
|
|
|
+ if (row.lastNaturalDate) {
|
|
|
+ // 如果已有值:时间戳 -> 日期字符串
|
|
|
+ initialDate = dayjs(row.lastNaturalDate).format('YYYY-MM-DD')
|
|
|
+ } else {
|
|
|
+ // 如果无值:设置默认值避免1970问题
|
|
|
+ initialDate = ''
|
|
|
+ }
|
|
|
+
|
|
|
+ configDialog.form = {
|
|
|
+ lastRunningKilometers: row.lastRunningKilometers || 0,
|
|
|
+ lastRunningTime: row.lastRunningTime || 0,
|
|
|
+ // 时间戳 -> Date对象(用于日期选择器)
|
|
|
+ lastNaturalDate: row.lastNaturalDate
|
|
|
+ ? dayjs(row.lastNaturalDate).format('YYYY-MM-DD')
|
|
|
+ : 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
|
|
|
+ }
|
|
|
+ configDialog.visible = true
|
|
|
+}
|
|
|
+
|
|
|
+// 保存配置
|
|
|
+const saveConfig = () => {
|
|
|
+ (configFormRef.value as any).validate((valid: boolean) => {
|
|
|
+ if (!valid) return
|
|
|
+ if (!configDialog.current) return
|
|
|
+
|
|
|
+ // 动态校验逻辑
|
|
|
+ const requiredFields = []
|
|
|
+ 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(configDialog.form.lastNaturalDate)
|
|
|
+ if (!dateValue.isValid()) {
|
|
|
+ message.error('日期格式不正确')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换逻辑(关键修改)
|
|
|
+ const finalDate = configDialog.form.lastNaturalDate
|
|
|
+ ? dayjs(configDialog.form.lastNaturalDate).valueOf()
|
|
|
+ : null // 改为null而不是0
|
|
|
+
|
|
|
+ // 更新当前行的数据
|
|
|
+ Object.assign(configDialog.current, {
|
|
|
+ ...configDialog.form,
|
|
|
+ // Date对象 -> 时间戳
|
|
|
+ lastNaturalDate: finalDate,
|
|
|
+ })
|
|
|
+ configDialog.visible = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const queryParams = reactive({
|
|
|
+ deviceIds: undefined,
|
|
|
+ planId: id,
|
|
|
+ bomFlag: 'b'
|
|
|
+})
|
|
|
+
|
|
|
+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
|
|
|
+ }))
|
|
|
+ // 获取选择的设备相关的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)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 新增数据后自动跳转到第一页
|
|
|
+ currentPage.value = 1
|
|
|
+}
|
|
|
+
|
|
|
+const deviceFormRef = ref<InstanceType<typeof MainPlanDeviceList>>()
|
|
|
+const openForm = () => {
|
|
|
+ deviceFormRef.value?.open();
|
|
|
+}
|
|
|
+
|
|
|
+const close = () => {
|
|
|
+ delView(unref(currentRoute))
|
|
|
+ push({ name: 'IotMaintenancePlan', params:{}})
|
|
|
+}
|
|
|
+
|
|
|
+/** 提交表单 */
|
|
|
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
|
+const submitForm = async () => {
|
|
|
+ // 校验表单
|
|
|
+ await formRef.value.validate()
|
|
|
+ // 校验表格数据
|
|
|
+ const isValid = validateTableData()
|
|
|
+ if (!isValid) return
|
|
|
+ // 提交请求
|
|
|
+ formLoading.value = true
|
|
|
+ try {
|
|
|
+ // 转换日期格式
|
|
|
+ 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}` // 设备标识
|
|
|
+ // 校验逻辑
|
|
|
+ const checkConfig = (ruleName: string, ruleValue: number, configField: keyof typeof row) => {
|
|
|
+ if (ruleValue === 0) { // 规则开启
|
|
|
+ if (!row[configField] || row[configField] <= 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 resetForm = () => {
|
|
|
+ formData.value = {
|
|
|
+ id: undefined,
|
|
|
+ failureName: undefined,
|
|
|
+ deviceId: undefined,
|
|
|
+ status: undefined,
|
|
|
+ ifStop: undefined,
|
|
|
+ failureTime: undefined,
|
|
|
+ failureInfluence: undefined,
|
|
|
+ failureSystem: undefined,
|
|
|
+ description: undefined,
|
|
|
+ pic: undefined,
|
|
|
+ solution: undefined,
|
|
|
+ maintainStartTime: undefined,
|
|
|
+ maintainEndTime: undefined,
|
|
|
+ remark: undefined,
|
|
|
+ deviceName: undefined,
|
|
|
+ processInstanceId: undefined,
|
|
|
+ auditStatus: undefined,
|
|
|
+ deptId: undefined
|
|
|
+ }
|
|
|
+ formRef.value?.resetFields()
|
|
|
+}
|
|
|
+onMounted(async () => {
|
|
|
+ // 如果已有数据(从缓存恢复),则跳过初始化
|
|
|
+ if (list.value.length > 0) return
|
|
|
+
|
|
|
+ const deptId = useUserStore().getUser.deptId
|
|
|
+ // 查询当前登录人所属部门名称
|
|
|
+ dept.value = await DeptApi.getDept(deptId)
|
|
|
+ // 根据当前登录人部门信息生成生成 保养计划 名称
|
|
|
+ formData.value.name = dept.value.name + ' - 保养计划'
|
|
|
+ formData.value.deptId = deptId
|
|
|
+ if (id){
|
|
|
+ formType.value = 'update'
|
|
|
+ const plan = await IotMaintenancePlanApi.getIotMaintenancePlan(id);
|
|
|
+ deviceLabel.value = plan.deviceName
|
|
|
+ formData.value = plan
|
|
|
+
|
|
|
+ if (list.value.length === 0) {
|
|
|
+ // 查询保养计划明细
|
|
|
+ const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams);
|
|
|
+ list.value = []
|
|
|
+ if (Array.isArray(data)) {
|
|
|
+ list.value = data.map(item => ({
|
|
|
+ ...item,
|
|
|
+ // 这里可以添加必要的字段转换(如果有日期等需要格式化的字段)
|
|
|
+ lastNaturalDate: item.lastNaturalDate
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ formType.value = 'create';
|
|
|
+ const { wsCache } = useCache()
|
|
|
+ const userInfo = wsCache.get(CACHE_KEY.USER)
|
|
|
+ formData.value.responsiblePerson = userInfo.user.id;
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(async () => {
|
|
|
+ console.log('组件卸载......')
|
|
|
+})
|
|
|
+
|
|
|
+const handleDelete = async (str: string) => {
|
|
|
+ try {
|
|
|
+ const [deviceIdStr, bomNodeId] = str.split('-')
|
|
|
+ const deviceId = parseInt(deviceIdStr)
|
|
|
+ // 删除列表项
|
|
|
+ const index = list.value.findIndex((item) => (item.deviceId+'-'+item.bomNodeId) === str)
|
|
|
+ if (index !== -1) {
|
|
|
+ list.value.splice(index, 1)
|
|
|
+ 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('移除失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否需要调整页码
|
|
|
+ if (list.value.length > 0 && pagedList.value.length === 0) {
|
|
|
+ currentPage.value = Math.max(1, currentPage.value - 1)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style scoped>
|
|
|
+.base-expandable-content {
|
|
|
+ overflow: hidden; /* 隐藏溢出的内容 */
|
|
|
+ transition: max-height 0.3s ease; /* 平滑过渡效果 */
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-input-number .el-input__inner) {
|
|
|
+ text-align: left !important;
|
|
|
+ padding-left: 10px; /* 保持左侧间距 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 分组容器样式 */
|
|
|
+.form-group {
|
|
|
+ position: relative;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 20px 15px 10px;
|
|
|
+ margin-bottom: 18px;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分组标题样式 */
|
|
|
+.group-title {
|
|
|
+ position: absolute;
|
|
|
+ top: -10px;
|
|
|
+ left: 20px;
|
|
|
+ background: white;
|
|
|
+ padding: 0 8px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保分页组件右对齐 */
|
|
|
+.el-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+</style>
|