work-order-boms.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <template>
  2. <scroll-view scroll-y="true" class="work-order-boms">
  3. <view
  4. class="device-section"
  5. :class="{ active: bom.id === currentBom.id }"
  6. v-for="bom in bomsData"
  7. :key="bom.id"
  8. >
  9. <!-- 已保养标识 -->
  10. <view class="device-section-selected" v-if="bom.maintainedFlag">
  11. <uni-icons type="checkmarkempty" size="13" color="#FFF"></uni-icons>
  12. </view>
  13. <view @click="onSelect(bom)">
  14. <!-- 设备名称 -->
  15. <view class="item-module">
  16. <view class="item-content flex-row align-center justify-between">
  17. <view class="item-title">
  18. <span class="item-title-width"
  19. >{{ $t("device.deviceName") }}:</span
  20. >
  21. </view>
  22. <view class="item-title">
  23. <span>{{ bom.deviceName }}</span>
  24. </view>
  25. </view>
  26. <view class="module-border"> </view>
  27. </view>
  28. <!-- 设备编码 -->
  29. <view class="item-content flex-row align-center justify-between">
  30. <view class="item-title">
  31. <span class="item-title-width">{{ $t("device.deviceCode") }}:</span>
  32. </view>
  33. <view class="item-title">
  34. <span>{{ bom.deviceCode }}</span>
  35. </view>
  36. </view>
  37. <!-- 保养项 -->
  38. <view class="item-content flex-row align-center justify-between">
  39. <view class="item-title">
  40. <span class="item-title-width"
  41. >{{ $t("maintenanceWorkOrder.maintenanceItems") }}:</span
  42. >
  43. </view>
  44. <view class="item-title">
  45. <span>{{ bom.name }}</span>
  46. </view>
  47. </view>
  48. </view>
  49. <!-- 累计运行时间 bom.totalRunTime > 0 时展示 -->
  50. <view
  51. class="item-content flex-row align-center justify-between"
  52. v-if="bom.totalRunTime > 0"
  53. >
  54. <view class="item-title">
  55. <span class="item-title-width">
  56. {{ $t("maintenanceWorkOrder.accumulatedRunningTime") }}(h):
  57. </span>
  58. </view>
  59. <view class="item-title">
  60. <span>{{ bom.totalRunTime }}</span>
  61. </view>
  62. </view>
  63. <!-- 累计运行里程 bom.totalMileage > 0 时展示 -->
  64. <view
  65. class="item-content flex-row align-center justify-between"
  66. v-if="bom.totalMileage > 0"
  67. >
  68. <view class="item-title">
  69. <span class="item-title-width">
  70. {{ $t("maintenanceWorkOrder.accumulatedRunningMileage") }}(km):
  71. </span>
  72. </view>
  73. <view class="item-title">
  74. <span>{{ bom.totalMileage }}</span>
  75. </view>
  76. </view>
  77. <!-- 运行时间周期 runningTimeRule=0时需要展示-->
  78. <view
  79. class="item-content flex-row align-center justify-between"
  80. v-if="bom.runningTimeRule == 0"
  81. >
  82. <view class="item-title">
  83. <span class="item-title-width">
  84. {{ $t("maintenanceWorkOrder.runningTimeCycle") }}:
  85. </span>
  86. </view>
  87. <uni-easyinput
  88. style="text-align: right"
  89. :inputBorder="false"
  90. :clearable="true"
  91. type="digit"
  92. :styles="{ disableColor: '#fff' }"
  93. v-model="bom.nextRunningTime"
  94. :placeholder="$t('operation.PleaseFillIn')"
  95. />
  96. </view>
  97. <!-- 是否消耗物料 -->
  98. <view class="item-content flex-row align-center justify-between">
  99. <view class="item-title">
  100. <span class="item-title-width">
  101. {{ $t("workOrder.isConsumptionMaterial") }}:
  102. </span>
  103. </view>
  104. <view class="switch-container">
  105. <switch
  106. class="switch"
  107. :checked="bom.rule == 0 ? true : false"
  108. :disabled="true"
  109. />
  110. <view class="switch-cover" @click="bomRuleChange(bom)"></view>
  111. </view>
  112. </view>
  113. <!-- 无物料保养原因 -->
  114. <view
  115. class="item-content flex-row align-center justify-between"
  116. v-if="bom.rule == 1"
  117. >
  118. <view class="item-title">
  119. <span class="item-title-width">
  120. {{ $t("maintenanceWorkOrder.noMaterialMaintenanceReason") }}:
  121. </span>
  122. </view>
  123. <uni-easyinput
  124. style="text-align: right"
  125. :styles="{ disableColor: '#fff' }"
  126. :inputBorder="false"
  127. :autoHeight="true"
  128. type="textarea"
  129. :placeholder="$t('operation.PleaseFillIn')"
  130. v-model="bom.remark"
  131. />
  132. </view>
  133. <!-- 物料数量 -->
  134. <view class="item-content flex-row align-center justify-between">
  135. <view class="item-title">
  136. <span class="item-title-width"
  137. >{{ $t("workOrder.materialCount") }}:</span
  138. >
  139. </view>
  140. <view class="item-title">
  141. <span>{{ bom.materialCount }}</span>
  142. </view>
  143. </view>
  144. <!-- 保养状态 (status 0保养中 1保养完成)-->
  145. <view class="item-content flex-row align-center justify-between">
  146. <view class="item-title">
  147. <span class="item-title-width">
  148. {{ $t("maintenanceWorkOrder.status") }}:
  149. </span>
  150. </view>
  151. <view class="item-title flex-row align-center">
  152. <view v-if="bom.status == 0">
  153. {{
  154. bom.isDelay
  155. ? $t("maintenanceWorkOrder.delayed")
  156. : $t("maintenanceWorkOrder.maintenanceInProgress")
  157. }}
  158. </view>
  159. <view v-if="bom.status == 1">
  160. {{ $t("maintenanceWorkOrder.completed") }}
  161. </view>
  162. <view class="switch-container">
  163. <switch
  164. class="switch"
  165. :checked="bom.status == 1 ? true : false"
  166. :disabled="true"
  167. />
  168. <view class="switch-cover" @click="bomStatusChange(bom)"></view>
  169. </view>
  170. </view>
  171. </view>
  172. <!-- 操作按钮 -->
  173. <view class="item-opera flex-row justify-end">
  174. <!-- 延保 只有计划生成时展示 -->
  175. <button
  176. type="primary"
  177. :plain="true"
  178. @click="ondelay(bom)"
  179. v-if="workOrderType == 1"
  180. >
  181. {{ $t("maintenanceWorkOrder.extendMaintenance") }}
  182. </button>
  183. </view>
  184. </view>
  185. </scroll-view>
  186. <!-- 延保 -->
  187. <maintenance-delay
  188. ref="maintenanceDelayRef"
  189. :maintenance="delayItem"
  190. @delay-set="onDelaySet"
  191. ></maintenance-delay>
  192. <!-- 全局提示信息弹窗 -->
  193. <global-message-popup
  194. class="boms-message-popup"
  195. ref="bomsMessageGlobalRef"
  196. :msgType="msgType"
  197. :messageText="messageText"
  198. />
  199. </template>
  200. <script setup>
  201. import { onLoad, onReady, onBackPress } from "@dcloudio/uni-app";
  202. import { ref, reactive, computed, getCurrentInstance } from "vue";
  203. import dayjs from "dayjs";
  204. // --------------------------引用组件--------------------------------------
  205. import maintenanceDelay from "@/components/maintenance/delay.vue";
  206. // --------------------------引用全局变量$t---------------------------------
  207. const { appContext } = getCurrentInstance();
  208. const t = appContext.config.globalProperties.$t;
  209. // --------------------------接收参数---------------------------------------
  210. const props = defineProps({
  211. // 保养项列表
  212. bomsData: {
  213. type: Array,
  214. default: () => [],
  215. },
  216. // 工单类型(1计划生成 2临时新建),
  217. workOrderType: {
  218. type: Number,
  219. default: 2,
  220. },
  221. // 当前选中的保养项
  222. currentBom: {
  223. type: Object,
  224. default: () => {},
  225. },
  226. });
  227. console.log("🚀 ~ props.bomsData:", props.bomsData);
  228. // ----------------------------提示信息弹窗---全局组件-------------------------------
  229. const bomsMessageGlobalRef = ref(null);
  230. // 提示信息弹窗类型
  231. const msgType = ref("success");
  232. // 提示信息弹窗内容
  233. const messageText = ref("");
  234. // 提示信息弹窗方法
  235. const showMessage = (type, message) => {
  236. msgType.value = type;
  237. messageText.value = message;
  238. bomsMessageGlobalRef.value.openMessage();
  239. };
  240. // -------------------------- 切换选中保养项 ---------------------------------------
  241. const onSelect = (bom) => {
  242. console.log("onSelect", bom);
  243. emit("onSelectBom", bom);
  244. };
  245. // -------------------------- 切换是否消耗物料 ---------------------------------------
  246. const bomRuleChange = (bom) => {
  247. console.log("bomRuleChange", bom);
  248. // 先校验保养项的保养状态是否为已完成(status 0保养中 1保养完成)
  249. if (bom.status == 1) {
  250. showMessage(
  251. "warn",
  252. bom.deviceCode +
  253. bom.name +
  254. " " +
  255. t("maintenanceWorkOrder.maintenanceStatusCompleted")
  256. );
  257. return;
  258. }
  259. // 是否消耗物料 rule (0需要 1不需要)
  260. if (bom.rule == "0") {
  261. bom.rule = "1";
  262. bom.deviceBomMaterials = []; // 清空对应物料列表
  263. } else {
  264. bom.rule = "0";
  265. bom.remark = ""; // 无物料保养原因清空
  266. }
  267. // 通知父组件更新统计
  268. emit("updateStatistics");
  269. };
  270. // -------------------------- 延保 -------------------------------------------
  271. const maintenanceDelayRef = ref(null);
  272. const delayItem = ref({});
  273. // 延保弹窗开启
  274. const ondelay = (item) => {
  275. console.log("ondelay", item);
  276. delayItem.value = item;
  277. maintenanceDelayRef.value.open(item);
  278. };
  279. // 设置延保
  280. const onDelaySet = (item) => {
  281. console.log("onDelaySet", item);
  282. // 查找对应的bom,并更新
  283. props.bomsData.forEach((bom) => {
  284. if (bom.workOrderBomOnlyKey === item.workOrderBomOnlyKey) {
  285. bom = {
  286. ...bom,
  287. ...item,
  288. };
  289. }
  290. });
  291. // 通知父组件更新统计
  292. emit("updateStatistics");
  293. };
  294. // 切换保养状态
  295. const bomStatusChange = (bom) => {
  296. console.log("bomStatusChange", bom);
  297. // 保养状态 (status 0保养中 1保养完成)
  298. if (bom.status == 0) {
  299. /**
  300. * 切换为保养完成状态时
  301. * 1. rule = 1 (不需要消耗物料),
  302. * 2. rule = 0 (需要消耗物料)时,校验是否设置了物料且物料信息完整(物料名称、消耗数量、单价)
  303. */
  304. // 校验是否开启物料消耗
  305. if (bom.rule == 0) {
  306. // 校验是否设置了物料
  307. if (bom.deviceBomMaterials.length == 0) {
  308. showMessage("error", t("maintenanceWorkOrder.materialEmpty"));
  309. return;
  310. }
  311. // 校验是否存在无效价格和数量的物料
  312. if (bom.deviceBomMaterials.length > 0) {
  313. let isMaterial = [];
  314. bom.deviceBomMaterials.map((material, index) => {
  315. let invalidMaterialStrArr = [];
  316. // 提示: 序号(index) 物料名称未填写
  317. if (!material.materialName) {
  318. const serialNumberStr = `${t("workOrder.serialNumber")}(${
  319. index + 1
  320. })`;
  321. const materialNameStr = `${t("workOrder.materialName")}${t(
  322. "status.unfilled"
  323. )}`;
  324. invalidMaterialStrArr.push(
  325. `${serialNumberStr}:${materialNameStr}`
  326. );
  327. }
  328. // 提示: 无效消耗数量: 0
  329. if (!material.quantity || material.quantity <= 0) {
  330. invalidMaterialStrArr.push(
  331. `${t("workOrder.invalid")}${t("workOrder.consumptionQuantity")}:${
  332. material.quantity || 0
  333. }`
  334. );
  335. }
  336. // 提示: 无效单价: 0
  337. if (!material.unitPrice || material.unitPrice <= 0) {
  338. invalidMaterialStrArr.push(
  339. `${t("workOrder.invalid")}${t("workOrder.unitPrice")}:${
  340. material.unitPrice || 0
  341. }`
  342. );
  343. }
  344. // 最终拼接提示信息并返回
  345. if (invalidMaterialStrArr.length > 0) {
  346. let invalidMaterialStr = material.materialName
  347. ? material.materialName + ":" + invalidMaterialStrArr.join(";")
  348. : invalidMaterialStrArr.join(";");
  349. console.log(
  350. "🚀 ~ bomStatusChange ~ invalidMaterialStrArr:",
  351. invalidMaterialStrArr
  352. );
  353. isMaterial.push(invalidMaterialStr);
  354. }
  355. });
  356. console.log("🚀 ~ bomStatusChange ~ isMaterial:", isMaterial);
  357. if (isMaterial.length > 0) {
  358. // 存在无效价格或数量的物料,提示用户
  359. isMaterial.unshift(t("maintenanceWorkOrder.invalidMaterial"));
  360. showMessage("error", isMaterial.join("\n"));
  361. return;
  362. }
  363. }
  364. }
  365. // 切换为保养完成状态
  366. bom.status = 1;
  367. // 校验成功,提示保养成功
  368. showMessage(
  369. "success",
  370. bom.deviceCode +
  371. bom.name +
  372. " " +
  373. t("maintenanceWorkOrder.maintenanceStatusCompleted")
  374. );
  375. } else {
  376. bom.status = 0;
  377. }
  378. // 通知父组件更新统计
  379. emit("updateStatistics");
  380. };
  381. // -------------------------- 暴露给父组件的外部方法 --------------------------
  382. defineExpose({});
  383. // -------------------------- 事件派发 ---------------------------------------
  384. const emit = defineEmits(["onSelectBom", "updateStatistics"]);
  385. </script>
  386. <style lang="scss" scoped>
  387. @import "@/style/work-order-detail.scss";
  388. .item-content {
  389. &:last-child {
  390. border-bottom: 1px dashed #cacccf;
  391. }
  392. }
  393. .work-order-boms {
  394. height: 100%;
  395. }
  396. .device-section {
  397. position: relative;
  398. &.active {
  399. border: #004098 1px solid;
  400. }
  401. }
  402. .device-section-selected {
  403. position: absolute;
  404. top: 0;
  405. right: 0;
  406. width: 0;
  407. height: 0;
  408. // 初始状态为透明
  409. border-style: solid;
  410. transition: all 0.3s ease;
  411. // 创建右上角倒三角
  412. border-width: 0 23px 23px 0;
  413. border-color: transparent #004098 transparent transparent; // 三角
  414. .uni-icons {
  415. position: absolute;
  416. top: 0;
  417. right: -21px;
  418. font-size: 11px;
  419. color: #fff;
  420. }
  421. }
  422. :deep(.uni-switch-input) {
  423. width: 46px;
  424. height: 23px;
  425. border-radius: 16px;
  426. margin-right: 0;
  427. &::before {
  428. width: 46px;
  429. height: 23px;
  430. background: #9ca0a5;
  431. }
  432. &::after {
  433. width: 19px;
  434. height: 19px;
  435. top: 2px;
  436. left: 3px;
  437. }
  438. }
  439. :deep(.uni-switch-input-checked) {
  440. background: #004098;
  441. &::after {
  442. top: 1px;
  443. left: 3px;
  444. }
  445. }
  446. :deep(uni-switch[disabled] .uni-switch-input) {
  447. opacity: 0.9;
  448. }
  449. .switch-container {
  450. position: relative;
  451. display: inline-block;
  452. width: 46px;
  453. height: 23px;
  454. }
  455. .switch {
  456. position: absolute;
  457. top: 0;
  458. left: 0;
  459. width: 100%;
  460. height: 100%;
  461. z-index: 0;
  462. }
  463. .switch-cover {
  464. position: absolute;
  465. top: 0;
  466. left: 0;
  467. width: 100%;
  468. height: 100%;
  469. z-index: 1;
  470. }
  471. </style>