IotMaintenancePlanDetail.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. <template>
  2. <ContentWrap v-loading="formLoading">
  3. <el-form
  4. ref="formRef"
  5. :model="formData"
  6. :rules="formRules"
  7. v-loading="formLoading"
  8. style="margin-top: 1em; margin-right: 4em; margin-left: 0.5em"
  9. label-width="130px">
  10. <div class="base-expandable-content">
  11. <el-row>
  12. <el-col :span="12">
  13. <el-form-item :label="t('main.planName')" prop="name">
  14. <el-input type="text" v-model="formData.name" disabled />
  15. </el-form-item>
  16. </el-col>
  17. <el-col :span="12">
  18. <el-form-item :label="t('main.planCode')" prop="serialNumber">
  19. <el-input type="text" v-model="formData.serialNumber" disabled />
  20. </el-form-item>
  21. </el-col>
  22. <el-col :span="24">
  23. <el-form-item :label="t('form.remark')" prop="remark">
  24. <el-input
  25. v-model="formData.remark"
  26. type="textarea"
  27. :placeholder="t('deviceForm.remarkHolder')"
  28. disabled />
  29. </el-form-item>
  30. </el-col>
  31. </el-row>
  32. </div>
  33. </el-form>
  34. </ContentWrap>
  35. <ContentWrap>
  36. <!-- 列表 -->
  37. <ContentWrap>
  38. <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
  39. <!-- 添加序号列 -->
  40. <el-table-column type="index" :label="t('monitor.serial')" width="70" align="center" />
  41. <el-table-column :label="t('monitor.deviceCode')" align="center" prop="deviceCode" />
  42. <el-table-column :label="t('monitor.deviceName')" align="center" prop="deviceName" />
  43. <el-table-column
  44. :label="t('operationFillForm.sumTime')"
  45. align="center"
  46. prop="totalRunTime"
  47. :formatter="erpPriceTableColumnFormatter">
  48. <template #default="{ row }">
  49. {{ row.totalRunTime ?? row.tempTotalRunTime }}
  50. </template>
  51. </el-table-column>
  52. <el-table-column
  53. :label="t('operationFillForm.sumKil')"
  54. align="center"
  55. prop="totalMileage"
  56. :formatter="erpPriceTableColumnFormatter">
  57. <template #default="{ row }">
  58. {{ row.totalMileage ?? row.tempTotalMileage }}
  59. </template>
  60. </el-table-column>
  61. <el-table-column
  62. label="tempTotalRunTime"
  63. align="center"
  64. prop="tempTotalRunTime"
  65. :formatter="erpPriceTableColumnFormatter"
  66. v-if="false" />
  67. <el-table-column
  68. label="tempTotalMileage"
  69. align="center"
  70. prop="tempTotalMileage"
  71. :formatter="erpPriceTableColumnFormatter"
  72. v-if="false" />
  73. <el-table-column :label="t('bomList.bomNode')" align="center" prop="name" />
  74. <el-table-column :label="t('main.mileage')" key="mileageRule" width="80">
  75. <template #default="scope">
  76. <el-switch
  77. v-model="scope.row.mileageRule"
  78. :active-value="0"
  79. :inactive-value="1"
  80. disabled />
  81. </template>
  82. </el-table-column>
  83. <el-table-column :label="t('main.runTime')" key="runningTimeRule" width="90">
  84. <template #default="scope">
  85. <el-switch
  86. v-model="scope.row.runningTimeRule"
  87. :active-value="0"
  88. :inactive-value="1"
  89. disabled />
  90. </template>
  91. </el-table-column>
  92. <el-table-column :label="t('main.date')" key="naturalDateRule" width="80">
  93. <template #default="scope">
  94. <el-switch
  95. v-model="scope.row.naturalDateRule"
  96. :active-value="0"
  97. :inactive-value="1"
  98. disabled />
  99. </template>
  100. </el-table-column>
  101. <el-table-column :label="t('workplace.operation')" align="center" min-width="120px">
  102. <template #default="scope">
  103. <div style="display: flex; justify-content: center; align-items: center; width: 100%">
  104. <div> </div>
  105. <!-- 新增配置按钮 -->
  106. <div style="margin-left: 12px">
  107. <el-button link type="primary" @click="openConfigDialog(scope.row)">
  108. {{ t('form.set') }}
  109. </el-button>
  110. </div>
  111. </div>
  112. </template>
  113. </el-table-column>
  114. </el-table>
  115. </ContentWrap>
  116. </ContentWrap>
  117. <ContentWrap>
  118. <el-form>
  119. <el-form-item style="float: right">
  120. <el-button @click="close">{{ t('operationFillForm.cancel') }}</el-button>
  121. </el-form-item>
  122. </el-form>
  123. </ContentWrap>
  124. <MainPlanDeviceList ref="deviceFormRef" @choose="deviceChoose" />
  125. <!-- 新增配置对话框 -->
  126. <el-dialog
  127. v-model="configDialog.visible"
  128. :title="`设备 ${configDialog.current?.deviceCode + '-' + configDialog.current?.name} 保养项配置`"
  129. width="600px">
  130. <!-- 使用header插槽自定义标题 -->
  131. <template #header>
  132. <span
  133. >设备
  134. <strong>{{ configDialog.current?.deviceCode }}-{{ configDialog.current?.name }}</strong>
  135. 保养项配置</span
  136. >
  137. </template>
  138. <el-form
  139. :model="configDialog.form"
  140. label-width="200px"
  141. :rules="configFormRules"
  142. ref="configFormRef">
  143. <div class="form-group">
  144. <div class="group-title">{{ t('mainPlan.basicMaintenanceRecords') }}</div>
  145. <!-- 里程配置 -->
  146. <el-form-item
  147. v-if="configDialog.current?.mileageRule === 0"
  148. :label="t('mainPlan.lastMaintenanceMileage')"
  149. prop="lastRunningKilometers">
  150. <el-input-number
  151. v-model="configDialog.form.lastRunningKilometers"
  152. :precision="2"
  153. :controls="false"
  154. controls-position="right"
  155. style="width: 60%"
  156. disabled />
  157. </el-form-item>
  158. <!-- 运行时间配置 -->
  159. <el-form-item
  160. v-if="configDialog.current?.runningTimeRule === 0"
  161. :label="t('mainPlan.lastMaintenanceOperationTime')"
  162. prop="lastRunningTime">
  163. <el-input-number
  164. v-model="configDialog.form.lastRunningTime"
  165. :precision="1"
  166. :controls="false"
  167. controls-position="right"
  168. style="width: 60%"
  169. disabled />
  170. </el-form-item>
  171. <!-- 自然日期配置 -->
  172. <el-form-item
  173. v-if="configDialog.current?.naturalDateRule === 0"
  174. :label="t('mainPlan.lastMaintenanceNaturalDate')"
  175. prop="lastNaturalDate">
  176. <el-date-picker
  177. v-model="configDialog.form.lastNaturalDate"
  178. type="date"
  179. placeholder="选择日期"
  180. format="YYYY-MM-DD"
  181. value-format="YYYY-MM-DD"
  182. style="width: 60%"
  183. disabled />
  184. </el-form-item>
  185. </div>
  186. <div class="form-group" v-if="configDialog.current?.mileageRule === 0">
  187. <div class="group-title">{{ t('mainPlan.operatingMileageRuleConfiguration') }}</div>
  188. <!-- 保养规则周期值 + 提前量 -->
  189. <el-form-item
  190. v-if="configDialog.current?.mileageRule === 0"
  191. :label="t('mainPlan.operatingMileageCycle')"
  192. prop="nextRunningKilometers">
  193. <el-input-number
  194. v-model="configDialog.form.nextRunningKilometers"
  195. :precision="2"
  196. :min="0"
  197. :controls="false"
  198. controls-position="right"
  199. style="width: 60%"
  200. disabled />
  201. </el-form-item>
  202. <el-form-item
  203. v-if="configDialog.current?.mileageRule === 0"
  204. :label="t('mainPlan.OperatingMileageCycle_lead')"
  205. prop="kiloCycleLead">
  206. <el-input-number
  207. v-model="configDialog.form.kiloCycleLead"
  208. :precision="2"
  209. :min="0"
  210. :controls="false"
  211. controls-position="right"
  212. style="width: 60%"
  213. disabled />
  214. </el-form-item>
  215. </div>
  216. <div class="form-group" v-if="configDialog.current?.runningTimeRule === 0">
  217. <div class="group-title">{{ t('mainPlan.RunTimeRuleConfiguration') }}</div>
  218. <el-form-item
  219. v-if="configDialog.current?.runningTimeRule === 0"
  220. :label="t('mainPlan.RunTimeCycle')"
  221. prop="nextRunningTime">
  222. <el-input-number
  223. v-model="configDialog.form.nextRunningTime"
  224. :precision="1"
  225. :min="0"
  226. :controls="false"
  227. controls-position="right"
  228. style="width: 60%"
  229. disabled />
  230. </el-form-item>
  231. <el-form-item
  232. v-if="configDialog.current?.runningTimeRule === 0"
  233. :label="t('mainPlan.RunTimeCycle_Lead')"
  234. prop="timePeriodLead">
  235. <el-input-number
  236. v-model="configDialog.form.timePeriodLead"
  237. :precision="1"
  238. :min="0"
  239. :controls="false"
  240. controls-position="right"
  241. style="width: 60%"
  242. disabled />
  243. </el-form-item>
  244. </div>
  245. <div class="form-group" v-if="configDialog.current?.naturalDateRule === 0">
  246. <div class="group-title">{{ t('mainPlan.NaturalDayRuleConfig') }}</div>
  247. <el-form-item
  248. v-if="configDialog.current?.naturalDateRule === 0"
  249. :label="t('mainPlan.NaturalDailyCycle')"
  250. prop="nextNaturalDate">
  251. <el-input-number
  252. v-model="configDialog.form.nextNaturalDate"
  253. :min="0"
  254. :controls="false"
  255. controls-position="right"
  256. style="width: 60%"
  257. disabled />
  258. </el-form-item>
  259. <el-form-item
  260. v-if="configDialog.current?.naturalDateRule === 0"
  261. :label="t('mainPlan.NaturalDailyCycle_Lead')"
  262. prop="naturalDatePeriodLead">
  263. <el-input-number
  264. v-model="configDialog.form.naturalDatePeriodLead"
  265. :min="0"
  266. :controls="false"
  267. controls-position="right"
  268. style="width: 60%"
  269. disabled />
  270. </el-form-item>
  271. </div>
  272. <!-- 运行记录模板中 多个 累计运行时长 累计运行里程 属性匹配-->
  273. <div
  274. class="form-group"
  275. v-if="
  276. (configDialog.current?.runningTimeRule === 0 ||
  277. configDialog.current?.mileageRule === 0) &&
  278. (configDialog.current?.timeAccumulatedAttrs?.length ||
  279. configDialog.current?.mileageAccumulatedAttrs?.length) &&
  280. !configDialog.current.totalRunTime &&
  281. !configDialog.current.totalMileage
  282. ">
  283. <div class="group-title">{{ t('mainPlan.accumulatedParams') }}</div>
  284. <!-- 累计运行时长 -->
  285. <el-form-item
  286. v-if="
  287. configDialog.current?.runningTimeRule === 0 &&
  288. configDialog.current?.timeAccumulatedAttrs?.length &&
  289. !configDialog.current.totalRunTime
  290. "
  291. :label="t('mainPlan.accumulatedRunTime')"
  292. prop="accumulatedTimeOption"
  293. :rules="[
  294. {
  295. required:
  296. configDialog.current?.runningTimeRule === 0 &&
  297. configDialog.current?.timeAccumulatedAttrs?.length,
  298. message: '请选择累计运行时长',
  299. trigger: 'change'
  300. }
  301. ]">
  302. <el-select
  303. v-model="configDialog.form.accumulatedTimeOption"
  304. placeholder="请选择累计运行时长"
  305. style="width: 80%"
  306. clearable
  307. @change="handleAccumulatedTimeChange"
  308. disabled>
  309. <el-option
  310. v-for="(item, index) in configDialog.current.timeAccumulatedAttrs"
  311. :key="`time-${item.pointName}-${index}`"
  312. :label="item.pointName"
  313. :value="item.pointName" />
  314. </el-select>
  315. </el-form-item>
  316. <!-- 累计运行公里数 -->
  317. <el-form-item
  318. v-if="
  319. configDialog.current?.mileageRule === 0 &&
  320. configDialog.current?.mileageAccumulatedAttrs?.length &&
  321. !configDialog.current.totalMileage
  322. "
  323. :label="t('mainPlan.accumulatedMileage')"
  324. prop="accumulatedMileageOption"
  325. :rules="[
  326. {
  327. required:
  328. configDialog.current?.mileageRule === 0 &&
  329. configDialog.current?.mileageAccumulatedAttrs?.length,
  330. message: '请选择累计运行公里数',
  331. trigger: 'change'
  332. }
  333. ]">
  334. <el-select
  335. v-model="configDialog.form.accumulatedMileageOption"
  336. placeholder="请选择累计运行公里数"
  337. style="width: 80%"
  338. clearable
  339. @change="handleAccumulatedMileageChange"
  340. disabled>
  341. <el-option
  342. v-for="(item, index) in configDialog.current.mileageAccumulatedAttrs"
  343. :key="`mileage-${item.pointName}-${index}`"
  344. :label="item.pointName"
  345. :value="item.pointName" />
  346. </el-select>
  347. </el-form-item>
  348. </div>
  349. </el-form>
  350. <template #footer>
  351. <el-button @click="configDialog.visible = false">{{ t('common.cancel') }}</el-button>
  352. </template>
  353. </el-dialog>
  354. </template>
  355. <script setup lang="ts">
  356. import { IotMaintainApi } from '@/api/pms/maintain'
  357. import { IotDeviceApi } from '@/api/pms/device'
  358. import * as UserApi from '@/api/system/user'
  359. import { useUserStore } from '@/store/modules/user'
  360. import { ref } from 'vue'
  361. import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
  362. import { IotMaintenancePlanApi } from '@/api/pms/maintenance'
  363. import { useTagsViewStore } from '@/store/modules/tagsView'
  364. import MainPlanDeviceList from '@/views/pms/maintenance/maintenance-device-list.vue'
  365. import * as DeptApi from '@/api/system/dept'
  366. import { erpPriceTableColumnFormatter } from '@/utils'
  367. import dayjs from 'dayjs'
  368. /** 保养计划 表单 */
  369. defineOptions({ name: 'IotMaintenancePlanDetail' })
  370. const { t } = useI18n() // 国际化
  371. const message = useMessage() // 消息弹窗
  372. const { delView } = useTagsViewStore() // 视图操作
  373. const { currentRoute, push } = useRouter()
  374. const deptUsers = ref<UserApi.UserVO[]>([]) // 用户列表
  375. const dept = ref() // 当前登录人所属部门对象
  376. const configFormRef = ref() // 配置弹出框对象
  377. const dialogTitle = ref('') // 弹窗的标题
  378. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  379. const formType = ref('') // 表单的类型:create - 新增;update - 修改
  380. const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
  381. const list = ref<IotMaintenanceBomVO[]>([]) // 设备bom关联列表的数据
  382. const deviceIds = ref<number[]>([]) // 已经选择的设备id数组
  383. const { params, name } = useRoute() // 查询参数
  384. const id = params.id
  385. const formData = ref({
  386. id: undefined,
  387. deptId: undefined,
  388. name: '',
  389. serialNumber: undefined,
  390. responsiblePerson: undefined,
  391. remark: undefined,
  392. failureName: undefined,
  393. status: undefined
  394. })
  395. const formRules = reactive({
  396. name: [{ required: true, message: '计划名称不能为空', trigger: 'blur' }],
  397. responsiblePerson: [{ required: true, message: '责任人不能为空', trigger: 'blur' }]
  398. })
  399. const formRef = ref() // 表单 Ref
  400. // 新增配置相关状态
  401. const configDialog = reactive({
  402. visible: false,
  403. current: null as IotMaintenanceBomVO | null,
  404. form: {
  405. lastRunningKilometers: 0,
  406. lastRunningTime: 0,
  407. lastNaturalDate: '',
  408. // 保养规则 周期
  409. nextRunningKilometers: 0,
  410. nextRunningTime: 0,
  411. nextNaturalDate: 0,
  412. // 提前量
  413. kiloCycleLead: 0,
  414. timePeriodLead: 0,
  415. naturalDatePeriodLead: 0,
  416. // 多个累计时长 累计里程 匹配
  417. accumulatedTimeOption: null, // 累计运行时长选项
  418. accumulatedMileageOption: null // 累计运行公里数选项
  419. }
  420. })
  421. // 打开配置对话框
  422. const openConfigDialog = (row: IotMaintenanceBomVO) => {
  423. configDialog.current = row
  424. // 处理日期初始化(核心修改)
  425. let initialDate = ''
  426. if (row.lastNaturalDate) {
  427. // 如果已有值:时间戳 -> 日期字符串
  428. initialDate = dayjs(row.lastNaturalDate).format('YYYY-MM-DD')
  429. } else {
  430. // 如果无值:设置默认值避免1970问题
  431. initialDate = ''
  432. }
  433. configDialog.form = {
  434. lastRunningKilometers: row.lastRunningKilometers || 0,
  435. lastRunningTime: row.lastRunningTime || 0,
  436. lastNaturalDate: initialDate,
  437. // 保养规则 周期值
  438. nextRunningKilometers: row.nextRunningKilometers || 0,
  439. nextRunningTime: row.nextRunningTime || 0,
  440. nextNaturalDate: row.nextNaturalDate || 0,
  441. // 提前量
  442. kiloCycleLead: row.kiloCycleLead || 0,
  443. timePeriodLead: row.timePeriodLead || 0,
  444. naturalDatePeriodLead: row.naturalDatePeriodLead || 0
  445. }
  446. configDialog.form.accumulatedTimeOption = row.code || null
  447. configDialog.form.accumulatedMileageOption = row.type || null
  448. configDialog.visible = true
  449. }
  450. // 保存配置
  451. const saveConfig = () => {
  452. ;(configFormRef.value as any).validate((valid: boolean) => {
  453. if (!valid) return
  454. if (!configDialog.current) return
  455. // 动态校验逻辑
  456. const requiredFields = []
  457. if (configDialog.current.mileageRule === 0) {
  458. requiredFields.push('nextRunningKilometers', 'kiloCycleLead')
  459. }
  460. if (configDialog.current.runningTimeRule === 0) {
  461. requiredFields.push('nextRunningTime', 'timePeriodLead')
  462. }
  463. if (configDialog.current.naturalDateRule === 0) {
  464. requiredFields.push('nextNaturalDate', 'naturalDatePeriodLead')
  465. }
  466. const missingFields = requiredFields.filter(
  467. (field) => !configDialog.form[field as keyof typeof configDialog.form]
  468. )
  469. if (missingFields.length > 0) {
  470. message.error('请填写所有必填项')
  471. return
  472. }
  473. // 强制校验逻辑
  474. if (configDialog.current.naturalDateRule === 0) {
  475. if (!configDialog.form.lastNaturalDate) {
  476. message.error('必须选择自然日期')
  477. return
  478. }
  479. // 验证日期有效性
  480. const dateValue = dayjs(configDialog.form.lastNaturalDate)
  481. if (!dateValue.isValid()) {
  482. message.error('日期格式不正确')
  483. return
  484. }
  485. }
  486. // 转换逻辑(关键修改)
  487. const finalDate = configDialog.form.lastNaturalDate
  488. ? dayjs(configDialog.form.lastNaturalDate).valueOf()
  489. : null // 改为null而不是0
  490. // 更新当前行的数据
  491. Object.assign(configDialog.current, {
  492. ...configDialog.form,
  493. lastNaturalDate: finalDate
  494. })
  495. configDialog.visible = false
  496. })
  497. }
  498. const queryParams = reactive({
  499. planId: id
  500. })
  501. const deviceChoose = async (selectedDevices) => {
  502. const newIds = selectedDevices.map((device) => device.id)
  503. deviceIds.value = [...new Set([...deviceIds.value, ...newIds])]
  504. const params = {
  505. deviceIds: deviceIds.value.join(',') // 明确传递数组参数
  506. }
  507. queryParams.deviceIds = JSON.parse(JSON.stringify(params.deviceIds))
  508. // 根据选择的设备筛选出设备关系的分类BOM中与保养相关的节点项
  509. const res = await IotDeviceApi.deviceAssociateBomList(queryParams)
  510. const rawData = res || []
  511. if (rawData.length === 0) {
  512. message.error('选择的设备不存在待保养BOM项')
  513. }
  514. if (!Array.isArray(rawData)) {
  515. console.error('接口返回数据结构异常:', rawData)
  516. return
  517. }
  518. // 转换数据结构(根据你的接口定义调整)
  519. const newItems = rawData.map((device) => ({
  520. assetClass: device.assetClass,
  521. deviceCode: device.deviceCode,
  522. deviceName: device.deviceName,
  523. deviceStatus: device.deviceStatus,
  524. deptName: device.deptName,
  525. name: device.name,
  526. code: device.code,
  527. assetProperty: device.assetProperty,
  528. remark: null, // 初始化备注
  529. deviceId: device.id, // 移除操作需要
  530. bomNodeId: device.bomNodeId,
  531. totalRunTime: device.totalRunTime,
  532. totalMileage: device.totalMileage,
  533. nextRunningKilometers: 0,
  534. nextRunningTime: 0,
  535. nextNaturalDate: 0,
  536. lastNaturalDate: null, // 初始化为null而不是0
  537. // 保养规则 提前量
  538. kiloCycleLead: 0,
  539. timePeriodLead: 0,
  540. naturalDatePeriodLead: 0
  541. }))
  542. // 获取选择的设备相关的id数组
  543. newItems.forEach((item) => {
  544. deviceIds.value.push(item.deviceId)
  545. })
  546. // 合并到现有列表(去重)
  547. newItems.forEach((item) => {
  548. const exists = list.value.some(
  549. (existing) => existing.deviceId === item.deviceId && existing.bomNodeId === item.bomNodeId
  550. )
  551. if (!exists) {
  552. list.value.push(item)
  553. }
  554. })
  555. }
  556. const deviceFormRef = ref<InstanceType<typeof MainPlanDeviceList>>()
  557. const openForm = () => {
  558. deviceFormRef.value?.open()
  559. }
  560. const close = () => {
  561. delView(unref(currentRoute))
  562. push({ name: 'IotMaintenancePlan', params: {} })
  563. }
  564. // 累计运行时长变更
  565. const handleAccumulatedTimeChange = (option) => {}
  566. // 累计运行公里数变更
  567. const handleAccumulatedMileageChange = (option) => {}
  568. /** 提交表单 */
  569. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  570. const submitForm = async () => {
  571. // 校验表单
  572. await formRef.value.validate()
  573. // 校验表格数据
  574. const isValid = validateTableData()
  575. if (!isValid) return
  576. // 提交请求
  577. formLoading.value = true
  578. try {
  579. const data = {
  580. mainPlan: formData.value,
  581. mainPlanBom: list.value
  582. }
  583. if (formType.value === 'create') {
  584. await IotMaintenancePlanApi.createIotMaintenancePlan(data)
  585. message.success(t('common.createSuccess'))
  586. close()
  587. } else {
  588. await IotMaintainApi.updateIotMaintain(data)
  589. message.success(t('common.updateSuccess'))
  590. close()
  591. }
  592. // 发送操作成功的事件
  593. emit('success')
  594. } finally {
  595. formLoading.value = false
  596. }
  597. }
  598. // 新增表单校验规则
  599. const configFormRules = reactive({
  600. nextRunningKilometers: [
  601. {
  602. required: true,
  603. message: '里程周期必须填写',
  604. trigger: 'blur'
  605. }
  606. ],
  607. kiloCycleLead: [
  608. {
  609. required: true,
  610. message: '提前量必须填写',
  611. trigger: 'blur'
  612. }
  613. ],
  614. nextRunningTime: [
  615. {
  616. required: true,
  617. message: '时间周期必须填写',
  618. trigger: 'blur'
  619. }
  620. ],
  621. timePeriodLead: [
  622. {
  623. required: true,
  624. message: '提前量必须填写',
  625. trigger: 'blur'
  626. }
  627. ],
  628. nextNaturalDate: [
  629. {
  630. required: true,
  631. message: '自然日周期必须填写',
  632. trigger: 'blur'
  633. }
  634. ],
  635. naturalDatePeriodLead: [
  636. {
  637. required: true,
  638. message: '提前量必须填写',
  639. trigger: 'blur'
  640. }
  641. ]
  642. })
  643. /** 校验表格数据 */
  644. const validateTableData = (): boolean => {
  645. let isValid = true
  646. const errorMessages: string[] = []
  647. const noRulesErrorMessages: string[] = [] // 未设置任何保养项规则 的错误提示信息
  648. const noRules: string[] = [] // 行记录中设置了保养规则的记录数量
  649. const configErrors: string[] = [] // 保养规则配置弹出框
  650. let shouldBreak = false
  651. if (list.value.length === 0) {
  652. errorMessages.push('请至少添加一条设备保养明细')
  653. isValid = false
  654. // 直接返回无需后续校验
  655. message.error('请至少添加一条设备保养明细')
  656. return isValid
  657. }
  658. list.value.forEach((row, index) => {
  659. if (shouldBreak) return
  660. const rowNumber = index + 1 // 用户可见的行号从1开始
  661. const deviceIdentifier = `${row.deviceCode}-${row.name}` // 设备标识
  662. // 校验逻辑
  663. const checkConfig = (ruleName: string, ruleValue: number, configField: keyof typeof row) => {
  664. if (ruleValue === 0) {
  665. // 规则开启
  666. if (!row[configField] || row[configField] <= 0) {
  667. configErrors.push(
  668. `第 ${rowNumber} 行(${deviceIdentifier}):请点击【配置】维护${ruleName}上次保养值`
  669. )
  670. isValid = false
  671. }
  672. }
  673. }
  674. // 里程校验逻辑
  675. if (row.mileageRule === 0) {
  676. // 假设 0 表示开启状态
  677. if (!row.nextRunningKilometers || row.nextRunningKilometers <= 0) {
  678. errorMessages.push(`第 ${rowNumber} 行:开启里程规则必须填写有效的里程周期`)
  679. isValid = false
  680. }
  681. // 再校验配置值
  682. checkConfig('里程', row.mileageRule, 'lastRunningKilometers')
  683. } else {
  684. noRules.push(`第 ${rowNumber} 行:未设置里程规则`)
  685. }
  686. // 运行时间校验逻辑
  687. if (row.runningTimeRule === 0) {
  688. if (!row.nextRunningTime || row.nextRunningTime <= 0) {
  689. errorMessages.push(`第 ${rowNumber} 行:开启运行时间规则必须填写有效的时间周期`)
  690. isValid = false
  691. }
  692. checkConfig('运行时间', row.runningTimeRule, 'lastRunningTime')
  693. } else {
  694. noRules.push(`第 ${rowNumber} 行:未设置运行时间规则`)
  695. }
  696. // 自然日期校验逻辑
  697. if (row.naturalDateRule === 0) {
  698. if (!row.nextNaturalDate) {
  699. errorMessages.push(`第 ${rowNumber} 行:开启自然日期规则必须填写有效的自然日期周期`)
  700. isValid = false
  701. }
  702. checkConfig('自然日期', row.naturalDateRule, 'lastNaturalDate')
  703. } else {
  704. noRules.push(`第 ${rowNumber} 行:未设置自然日期规则`)
  705. }
  706. // 如果选中的一行记录未设置任何保养规则 提示 ‘保养项未设置任何保养规则’
  707. if (noRules.length === 3) {
  708. isValid = false
  709. shouldBreak = true // 设置标志变量为true,退出循环
  710. noRulesErrorMessages.push('保养项至少设置1个保养规则')
  711. }
  712. noRules.length = 0
  713. })
  714. if (errorMessages.length > 0) {
  715. message.error('设置保养规则后,请维护对应的周期值')
  716. } else if (noRulesErrorMessages.length > 0) {
  717. message.error(noRulesErrorMessages.pop())
  718. } else if (configErrors.length > 0) {
  719. message.error(configErrors.pop())
  720. }
  721. return isValid
  722. }
  723. /** 重置表单 */
  724. const resetForm = () => {
  725. formData.value = {
  726. id: undefined,
  727. failureName: undefined,
  728. deviceId: undefined,
  729. status: undefined,
  730. ifStop: undefined,
  731. failureTime: undefined,
  732. failureInfluence: undefined,
  733. failureSystem: undefined,
  734. description: undefined,
  735. pic: undefined,
  736. solution: undefined,
  737. maintainStartTime: undefined,
  738. maintainEndTime: undefined,
  739. remark: undefined,
  740. deviceName: undefined,
  741. processInstanceId: undefined,
  742. auditStatus: undefined,
  743. deptId: undefined
  744. }
  745. formRef.value?.resetFields()
  746. }
  747. onMounted(async () => {
  748. const deptId = useUserStore().getUser.deptId
  749. // 查询当前登录人所属部门名称
  750. dept.value = await DeptApi.getDept(deptId)
  751. // 根据当前登录人部门信息生成生成 保养计划 名称
  752. formData.value.name = dept.value.name + ' - 保养计划'
  753. deptUsers.value = await UserApi.getDeptUsersByDeptId(deptId)
  754. formData.value.deptId = deptId
  755. // if (id){
  756. formType.value = 'update'
  757. const plan = await IotMaintenancePlanApi.getIotMaintenancePlan(id)
  758. deviceLabel.value = plan.deviceName
  759. formData.value = plan
  760. // 查询保养责任人
  761. const personId = formData.value.responsiblePerson ? Number(formData.value.responsiblePerson) : 0
  762. UserApi.getUser(personId).then((res) => {
  763. formData.value.responsiblePerson = res.nickname
  764. })
  765. // 查询保养计划明细
  766. const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams)
  767. list.value = []
  768. if (Array.isArray(data)) {
  769. list.value = data.map((item) => ({
  770. ...item,
  771. // 这里可以添加必要的字段转换(如果有日期等需要格式化的字段)
  772. lastNaturalDate: item.lastNaturalDate
  773. ? dayjs(item.lastNaturalDate).format('YYYY-MM-DD')
  774. : null
  775. }))
  776. }
  777. })
  778. const handleDelete = async (str: string) => {
  779. try {
  780. const index = list.value.findIndex((item) => item.id + '-' + item.bomNodeId === str)
  781. if (index !== -1) {
  782. // 通过 splice 删除元素
  783. list.value.splice(index, 1)
  784. deviceIds.value = []
  785. }
  786. } catch {}
  787. }
  788. </script>
  789. <style scoped>
  790. .base-expandable-content {
  791. overflow: hidden; /* 隐藏溢出的内容 */
  792. transition: max-height 0.3s ease; /* 平滑过渡效果 */
  793. }
  794. :deep(.el-input-number .el-input__inner) {
  795. padding-left: 10px; /* 保持左侧间距 */
  796. text-align: left !important;
  797. }
  798. /* 分组容器样式 */
  799. .form-group {
  800. position: relative;
  801. padding: 20px 15px 10px;
  802. margin-bottom: 18px;
  803. border: 1px solid #dcdfe6;
  804. border-radius: 4px;
  805. transition: border-color 0.2s;
  806. }
  807. /* 分组标题样式 */
  808. .group-title {
  809. position: absolute;
  810. top: -10px;
  811. left: 20px;
  812. padding: 0 8px;
  813. font-size: 12px;
  814. font-weight: 500;
  815. color: #606266;
  816. background: white;
  817. }
  818. </style>