detail.vue 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211
  1. <template>
  2. <view class="page-container">
  3. <uni-nav-bar
  4. fixed="true"
  5. statusBar="true"
  6. left-icon="back"
  7. :title="pageTitle"
  8. background-color="#157A2C"
  9. color="#fff"
  10. @clickLeft="back"
  11. ></uni-nav-bar>
  12. <scroll-view scroll-y class="scroll-area">
  13. <!-- 工单信息 -->
  14. <view class="section-card">
  15. <view class="section-title">
  16. <view class="title-bar"></view>
  17. <text class="title-text">工单信息</text>
  18. </view>
  19. <view class="info-table">
  20. <view class="table-row">
  21. <view class="table-cell">
  22. <text class="cell-label">工单编码</text>
  23. <text class="cell-value">{{
  24. workOrderInfo.workOrderCode || "-"
  25. }}</text>
  26. </view>
  27. <view class="table-cell">
  28. <text class="cell-label">产品编码</text>
  29. <text class="cell-value">{{
  30. workOrderInfo.categoryCode || "-"
  31. }}</text>
  32. </view>
  33. </view>
  34. <view class="table-row">
  35. <view class="table-cell">
  36. <text class="cell-label">产品名称</text>
  37. <text class="cell-value">{{
  38. workOrderInfo.categoryName || "-"
  39. }}</text>
  40. </view>
  41. <view class="table-cell">
  42. <text class="cell-label">批次号</text>
  43. <text class="cell-value">{{ workOrderInfo.batchNo || "-" }}</text>
  44. </view>
  45. </view>
  46. <view class="table-row">
  47. <view class="table-cell">
  48. <text class="cell-label">要求生产数量</text>
  49. <text class="cell-value"
  50. >{{ workOrderInfo.formingNum || 0
  51. }}{{ workOrderInfo.unit || "" }}</text
  52. >
  53. </view>
  54. <view class="table-cell">
  55. <text class="cell-label">要求生产重量</text>
  56. <text class="cell-value"
  57. >{{ workOrderInfo.formingWeight || 0
  58. }}{{ workOrderInfo.weightUnit || "" }}</text
  59. >
  60. </view>
  61. </view>
  62. <view class="table-row">
  63. <view class="table-cell">
  64. <text class="cell-label">已生产数量</text>
  65. <text class="cell-value"
  66. >{{ workOrderInfo.formedNum || 0
  67. }}{{ workOrderInfo.unit || "" }}</text
  68. >
  69. </view>
  70. <view class="table-cell">
  71. <text class="cell-label">已生产重量</text>
  72. <text class="cell-value"
  73. >{{ workOrderInfo.formedWeight || 0
  74. }}{{ workOrderInfo.weightUnit || "" }}</text
  75. >
  76. </view>
  77. </view>
  78. <view class="table-row">
  79. <view class="table-cell table-cell--full">
  80. <text class="cell-label">当前工序</text>
  81. <text class="cell-value">{{ currentTaskName }}</text>
  82. </view>
  83. </view>
  84. </view>
  85. </view>
  86. <!-- 交接信息 -->
  87. <view class="section-card">
  88. <view class="section-title">
  89. <view class="title-bar"></view>
  90. <text class="title-text">交接信息</text>
  91. </view>
  92. <view class="form-area">
  93. <!-- 交接类型 -->
  94. <view class="form-item">
  95. <view class="form-label required">交接类型</view>
  96. <view class="form-content">
  97. <view v-if="formLocked" class="form-text">{{
  98. getTypeName(workOrderInfo.type)
  99. }}</view>
  100. <view v-else class="type-selector">
  101. <view
  102. v-for="opt in typeOptions"
  103. :key="opt.value"
  104. class="type-tag"
  105. :class="{
  106. 'type-tag--active': workOrderInfo.type === opt.value,
  107. }"
  108. @click="workOrderInfo.type = opt.value"
  109. >
  110. {{ opt.label }}
  111. </view>
  112. </view>
  113. </view>
  114. </view>
  115. <!-- 交接时间 -->
  116. <view class="form-item">
  117. <view class="form-label required">交接时间</view>
  118. <view class="form-content">
  119. <view v-if="formLocked" class="form-text">{{
  120. workOrderInfo.initiateTime || "-"
  121. }}</view>
  122. <picker
  123. v-else
  124. mode="date"
  125. :value="dateValue"
  126. @change="onDateChange"
  127. >
  128. <view class="form-picker">
  129. <text
  130. :class="
  131. workOrderInfo.initiateTime
  132. ? 'picker-text'
  133. : 'picker-placeholder'
  134. "
  135. >
  136. {{ workOrderInfo.initiateTime || "请选择交接时间" }}
  137. </text>
  138. <uni-icons type="right" size="16" color="#999"></uni-icons>
  139. </view>
  140. </picker>
  141. </view>
  142. </view>
  143. <!-- 交接单编码 -->
  144. <view class="form-item">
  145. <view class="form-label">交接单编码</view>
  146. <view class="form-content">
  147. <view class="form-text text-grey">{{
  148. handoverCodeDisplay || "系统自动生成"
  149. }}</view>
  150. </view>
  151. </view>
  152. <!-- 交接班组(类型=1时显示) -->
  153. <view class="form-item" v-if="workOrderInfo.type == 1">
  154. <view class="form-label required">交接班组</view>
  155. <view class="form-content">
  156. <view v-if="formLocked" class="form-text">{{
  157. workOrderInfo.acceptTeamName || "-"
  158. }}</view>
  159. <picker
  160. v-else
  161. :range="teamList"
  162. range-key="name"
  163. :value="teamIndex"
  164. @change="onTeamChange"
  165. >
  166. <view class="form-picker">
  167. <text
  168. :class="
  169. workOrderInfo.acceptTeamName
  170. ? 'picker-text'
  171. : 'picker-placeholder'
  172. "
  173. >
  174. {{ workOrderInfo.acceptTeamName || "请选择交接班组" }}
  175. </text>
  176. <uni-icons type="right" size="16" color="#999"></uni-icons>
  177. </view>
  178. </picker>
  179. </view>
  180. </view>
  181. <!-- 交接人(类型=1时显示) -->
  182. <view class="form-item" v-if="workOrderInfo.type == 1">
  183. <view class="form-label required">交接人</view>
  184. <view class="form-content">
  185. <view v-if="formLocked" class="form-text">{{
  186. workOrderInfo.acceptName || "-"
  187. }}</view>
  188. <picker
  189. v-else
  190. :range="personList"
  191. range-key="name"
  192. :value="personIndex"
  193. @change="onPersonChange"
  194. >
  195. <view class="form-picker">
  196. <text
  197. :class="
  198. workOrderInfo.acceptName
  199. ? 'picker-text'
  200. : 'picker-placeholder'
  201. "
  202. >
  203. {{ workOrderInfo.acceptName || "请选择交接人" }}
  204. </text>
  205. <uni-icons type="right" size="16" color="#999"></uni-icons>
  206. </view>
  207. </picker>
  208. </view>
  209. </view>
  210. <!-- 交接数量 -->
  211. <view class="form-item">
  212. <view class="form-label required">交接数量</view>
  213. <view class="form-content">
  214. <view v-if="formLocked" class="form-text"
  215. >{{ workOrderInfo.handoverQuantity || "-" }} PCS</view
  216. >
  217. <view v-else class="form-input-box">
  218. <input
  219. class="form-input"
  220. type="digit"
  221. v-model="workOrderInfo.handoverQuantity"
  222. placeholder="请输入"
  223. @input="onQuantityInput"
  224. />
  225. <text class="form-unit">PCS</text>
  226. </view>
  227. </view>
  228. </view>
  229. <!-- 交接重量 -->
  230. <view class="form-item">
  231. <view class="form-label required">交接重量</view>
  232. <view class="form-content">
  233. <view v-if="formLocked" class="form-text"
  234. >{{ workOrderInfo.handoverWeight || "-" }} KG</view
  235. >
  236. <view v-else class="form-input-box">
  237. <input
  238. class="form-input"
  239. type="digit"
  240. v-model="workOrderInfo.handoverWeight"
  241. placeholder="请输入"
  242. @input="onWeightInput"
  243. />
  244. <text class="form-unit">KG</text>
  245. </view>
  246. </view>
  247. </view>
  248. <!-- 交接注意事项 -->
  249. <view class="form-item form-item--vertical">
  250. <view class="form-label">交接注意事项</view>
  251. <view class="form-content">
  252. <view v-if="formLocked" class="form-text">{{
  253. workOrderInfo.remark || "-"
  254. }}</view>
  255. <textarea
  256. v-else
  257. class="form-textarea"
  258. v-model="workOrderInfo.remark"
  259. placeholder="请输入交接注意事项"
  260. :maxlength="500"
  261. auto-height
  262. ></textarea>
  263. </view>
  264. </view>
  265. </view>
  266. </view>
  267. <!-- 物料清单 -->
  268. <view class="section-card">
  269. <view class="section-title">
  270. <view class="title-bar"></view>
  271. <text class="title-text">物料清单</text>
  272. <view class="title-actions" v-if="!formLocked">
  273. <view class="title-btn" @click="addMaterial">+ 添加物料</view>
  274. </view>
  275. </view>
  276. <view v-if="detailList.length === 0" class="material-empty">
  277. <text class="material-empty-text">暂无物料</text>
  278. </view>
  279. <view
  280. v-for="(item, index) in detailList"
  281. :key="index"
  282. class="material-card"
  283. >
  284. <view class="material-header">
  285. <text class="material-index">{{ index + 1 }}</text>
  286. <text class="material-name">{{ item.categoryName || "-" }}</text>
  287. <view
  288. v-if="!formLocked"
  289. class="material-del"
  290. @click="removeMaterial(index)"
  291. >
  292. <uni-icons type="close" size="16" color="#F5222D"></uni-icons>
  293. </view>
  294. </view>
  295. <view class="material-body">
  296. <view class="material-row">
  297. <view class="material-info">
  298. <text class="m-label">编码</text>
  299. <text class="m-value">{{ item.categoryCode || "-" }}</text>
  300. </view>
  301. <view class="material-info">
  302. <text class="m-label">分类</text>
  303. <text class="m-value">{{ item.categoryLevelPath || "-" }}</text>
  304. </view>
  305. </view>
  306. <view class="material-row">
  307. <view class="material-info">
  308. <text class="m-label">牌号</text>
  309. <text class="m-value">{{ item.brandNum || "-" }}</text>
  310. </view>
  311. <view class="material-info">
  312. <text class="m-label">型号</text>
  313. <text class="m-value">{{ item.modelType || "-" }}</text>
  314. </view>
  315. </view>
  316. <view class="material-row">
  317. <view class="material-info">
  318. <text class="m-label">规格</text>
  319. <text class="m-value">{{ item.specification || "-" }}</text>
  320. </view>
  321. <view class="material-info">
  322. <text class="m-label">计量单位</text>
  323. <text class="m-value">{{ item.measureUnit || "-" }}</text>
  324. </view>
  325. </view>
  326. </view>
  327. </view>
  328. </view>
  329. <!-- 接收信息(待接收状态 mode=accept 时显示) -->
  330. <view class="section-card" v-if="showReceiveBlock">
  331. <view class="section-title">
  332. <view class="title-bar title-bar--blue"></view>
  333. <text class="title-text">接收信息</text>
  334. </view>
  335. <view class="form-area">
  336. <view class="form-item form-item--vertical">
  337. <view class="form-label required">接收建议</view>
  338. <view class="form-content">
  339. <textarea
  340. class="form-textarea"
  341. v-model="workOrderInfo.acceptRemark"
  342. placeholder="请输入接收建议"
  343. :maxlength="500"
  344. auto-height
  345. ></textarea>
  346. </view>
  347. </view>
  348. </view>
  349. </view>
  350. <!-- 底部占位 -->
  351. <view style="height: 200rpx"></view>
  352. </scroll-view>
  353. <!-- 底部操作栏 -->
  354. <view class="footer-bar" v-if="showFooter">
  355. <!-- 编辑模式:提交 + 保存 -->
  356. <view
  357. v-if="mode === 'edit'"
  358. class="footer-bar__btn footer-bar__btn--outline"
  359. @click="submitHandover(0)"
  360. >保存</view
  361. >
  362. <view
  363. v-if="mode === 'edit'"
  364. class="footer-bar__btn footer-bar__btn--primary"
  365. @click="submitHandover(1)"
  366. >提交</view
  367. >
  368. <!-- 接收模式:驳回 + 同意 -->
  369. <view
  370. v-if="mode === 'accept'"
  371. class="footer-bar__btn footer-bar__btn--danger"
  372. @click="handleDecision(3)"
  373. >驳回</view
  374. >
  375. <view
  376. v-if="mode === 'accept'"
  377. class="footer-bar__btn footer-bar__btn--primary"
  378. @click="handleDecision(2)"
  379. >同意</view
  380. >
  381. </view>
  382. </view>
  383. </template>
  384. <script>
  385. import {
  386. getHandoverById,
  387. updateHandover,
  388. acceptOrRejectHandover,
  389. getTeamPage,
  390. } from "@/api/pda/workOrderHandover.js";
  391. const TYPE_MAP = {
  392. 1: "物品移交",
  393. 2: "班次交接",
  394. };
  395. export default {
  396. data() {
  397. return {
  398. id: "",
  399. status: 0,
  400. mode: "detail", // detail / edit / accept
  401. loading: false,
  402. submitLoading: false,
  403. workOrderInfo: {
  404. type: 1,
  405. initiateTime: "",
  406. handoverCode: "",
  407. workOrderCode: "",
  408. categoryCode: "",
  409. categoryName: "",
  410. batchNo: "",
  411. unit: "",
  412. weightUnit: "",
  413. formingNum: "",
  414. formingWeight: "",
  415. formedNum: 0,
  416. formedWeight: 0,
  417. currentTaskDiagram: {},
  418. initiateTaskName: "",
  419. acceptTeamId: "",
  420. acceptTeamName: "",
  421. acceptId: "",
  422. acceptName: "",
  423. handoverQuantity: "",
  424. handoverWeight: "",
  425. remark: "",
  426. acceptRemark: "",
  427. detailList: [],
  428. },
  429. teamList: [],
  430. personList: [],
  431. typeOptions: [
  432. { label: "物品移交", value: 1 },
  433. { label: "班次交接", value: 2 },
  434. ],
  435. };
  436. },
  437. computed: {
  438. pageTitle() {
  439. if (this.mode === "detail") return "交接单详情";
  440. if (this.mode === "accept") return "接收交接单";
  441. return "编辑交接单";
  442. },
  443. formLocked() {
  444. if (this.mode === "detail") return true;
  445. if (this.mode === "accept") return true;
  446. return false;
  447. },
  448. showReceiveBlock() {
  449. return this.mode === "accept";
  450. },
  451. showFooter() {
  452. return this.mode === "edit" || this.mode === "accept";
  453. },
  454. currentTaskName() {
  455. const task = this.workOrderInfo.currentTaskDiagram || {};
  456. return task.taskTypeName || this.workOrderInfo.initiateTaskName || "-";
  457. },
  458. handoverCodeDisplay() {
  459. const w = this.workOrderInfo || {};
  460. return w.handoverCode || w.code || "";
  461. },
  462. detailList() {
  463. return Array.isArray(this.workOrderInfo.detailList)
  464. ? this.workOrderInfo.detailList
  465. : [];
  466. },
  467. dateValue() {
  468. if (!this.workOrderInfo.initiateTime) return "";
  469. return this.workOrderInfo.initiateTime.substring(0, 10);
  470. },
  471. teamIndex() {
  472. if (!this.workOrderInfo.acceptTeamId || !this.teamList.length) return -1;
  473. return this.teamList.findIndex(
  474. (t) => String(t.id) === String(this.workOrderInfo.acceptTeamId),
  475. );
  476. },
  477. personIndex() {
  478. if (!this.workOrderInfo.acceptId || !this.personList.length) return -1;
  479. return this.personList.findIndex(
  480. (p) => String(p.id) === String(this.workOrderInfo.acceptId),
  481. );
  482. },
  483. },
  484. onLoad(options) {
  485. this.id = options.id || "";
  486. this.status = Number(options.status) || 0;
  487. this.mode = options.mode || "detail";
  488. this.init();
  489. },
  490. methods: {
  491. back() {
  492. uni.navigateBack();
  493. },
  494. getTypeName(type) {
  495. return TYPE_MAP[type] || "-";
  496. },
  497. formatDateTimeNow() {
  498. const d = new Date();
  499. const p = (n) => String(n).padStart(2, "0");
  500. return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(
  501. d.getHours(),
  502. )}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
  503. },
  504. async init() {
  505. uni.showLoading({ title: "加载中..." });
  506. try {
  507. await this.loadTeamList();
  508. if (this.id) {
  509. const res = await getHandoverById(this.id);
  510. this.workOrderInfo = this.normalizeDetail(res);
  511. if (this.workOrderInfo.acceptTeamId) {
  512. this.syncTeamUsers(
  513. this.workOrderInfo.acceptTeamId,
  514. this.workOrderInfo.acceptId,
  515. );
  516. }
  517. } else {
  518. this.workOrderInfo.initiateTime = this.formatDateTimeNow();
  519. if (this.teamList.length) {
  520. this.workOrderInfo.acceptTeamId = this.teamList[0].id;
  521. this.syncTeamUsers(this.teamList[0].id);
  522. }
  523. }
  524. } catch (e) {
  525. uni.showToast({
  526. title: (e && e.message) || "加载失败",
  527. icon: "none",
  528. });
  529. } finally {
  530. uni.hideLoading();
  531. }
  532. },
  533. normalizeDetail(raw) {
  534. if (!raw || typeof raw !== "object") return this.workOrderInfo;
  535. const detailList = Array.isArray(raw.detailList) ? raw.detailList : [];
  536. return {
  537. ...raw,
  538. detailList,
  539. categoryCode: raw.categoryCode || raw.productCode || "",
  540. categoryName: raw.categoryName || raw.productName || "",
  541. workOrderCode: raw.workOrderCode || raw.workOrderNum || "",
  542. handoverCode: raw.handoverCode || raw.passonCode || raw.code || "",
  543. acceptRemark: raw.acceptRemark || "",
  544. currentTaskDiagram: raw.currentTaskDiagram || {},
  545. };
  546. },
  547. async loadTeamList() {
  548. try {
  549. const userInfo = uni.getStorageSync("userInfo") || {};
  550. const res = await getTeamPage({
  551. pageNum: 1,
  552. size: -1,
  553. factoryId: userInfo.factoryId || "",
  554. });
  555. this.teamList = Array.isArray(res.list) ? res.list : [];
  556. } catch (e) {
  557. this.teamList = [];
  558. }
  559. },
  560. syncTeamUsers(teamId, preferredAcceptId) {
  561. const team =
  562. this.teamList.find((t) => String(t.id) === String(teamId)) || {};
  563. this.workOrderInfo.acceptTeamName = team.name || "";
  564. const users = Array.isArray(team.userVOList) ? team.userVOList : [];
  565. this.personList = users.map((u) => ({ id: u.id, name: u.name }));
  566. if (!this.personList.length) {
  567. this.workOrderInfo.acceptId = "";
  568. this.workOrderInfo.acceptName = "";
  569. return;
  570. }
  571. if (
  572. preferredAcceptId &&
  573. this.personList.some((u) => String(u.id) === String(preferredAcceptId))
  574. ) {
  575. const pick = this.personList.find(
  576. (u) => String(u.id) === String(preferredAcceptId),
  577. );
  578. this.workOrderInfo.acceptId = pick.id;
  579. this.workOrderInfo.acceptName = pick.name || "";
  580. return;
  581. }
  582. const leader =
  583. this.personList.find(
  584. (u) => String(u.id) === String(team.leaderUserId),
  585. ) || this.personList[0];
  586. this.workOrderInfo.acceptId = leader.id;
  587. this.workOrderInfo.acceptName = leader.name || "";
  588. },
  589. onDateChange(e) {
  590. const date = e.detail.value;
  591. const now = new Date();
  592. const p = (n) => String(n).padStart(2, "0");
  593. this.workOrderInfo.initiateTime = `${date} ${p(now.getHours())}:${p(
  594. now.getMinutes(),
  595. )}:${p(now.getSeconds())}`;
  596. },
  597. onTeamChange(e) {
  598. const idx = Number(e.detail.value);
  599. const team = this.teamList[idx];
  600. if (team) {
  601. this.workOrderInfo.acceptTeamId = team.id;
  602. this.syncTeamUsers(team.id);
  603. }
  604. },
  605. onPersonChange(e) {
  606. const idx = Number(e.detail.value);
  607. const person = this.personList[idx];
  608. if (person) {
  609. this.workOrderInfo.acceptId = person.id;
  610. this.workOrderInfo.acceptName = person.name || "";
  611. }
  612. },
  613. onQuantityInput(e) {
  614. this.workOrderInfo.handoverQuantity = this.sanitizeNumber(e.detail.value);
  615. },
  616. onWeightInput(e) {
  617. this.workOrderInfo.handoverWeight = this.sanitizeNumber(e.detail.value);
  618. },
  619. sanitizeNumber(val) {
  620. let v = String(val == null ? "" : val).replace(/[^\d.]/g, "");
  621. if (v.startsWith(".")) v = "";
  622. const firstDot = v.indexOf(".");
  623. if (firstDot !== -1) {
  624. v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
  625. }
  626. const match = v.match(/^(\d+)(\.\d{0,4})?/);
  627. return match ? match[0] : "";
  628. },
  629. validateForm() {
  630. const w = this.workOrderInfo;
  631. if (!w.type) {
  632. uni.showToast({ title: "请选择交接类型", icon: "none" });
  633. return false;
  634. }
  635. if (!w.initiateTime) {
  636. uni.showToast({ title: "请选择交接时间", icon: "none" });
  637. return false;
  638. }
  639. if (w.type == 1) {
  640. if (!w.acceptTeamId) {
  641. uni.showToast({ title: "请选择交接班组", icon: "none" });
  642. return false;
  643. }
  644. if (!w.acceptId) {
  645. uni.showToast({ title: "请选择交接人", icon: "none" });
  646. return false;
  647. }
  648. }
  649. if (!w.handoverQuantity) {
  650. uni.showToast({ title: "请输入交接数量", icon: "none" });
  651. return false;
  652. }
  653. if (!w.handoverWeight) {
  654. uni.showToast({ title: "请输入交接重量", icon: "none" });
  655. return false;
  656. }
  657. return true;
  658. },
  659. async submitHandover(isRelease) {
  660. if (this.submitLoading) return;
  661. if (!this.validateForm()) return;
  662. const detailList = this.detailList.map((row) => {
  663. const { id, ...rest } = row;
  664. return rest;
  665. });
  666. const param = {
  667. ...this.workOrderInfo,
  668. detailList,
  669. isRelease,
  670. };
  671. this.submitLoading = true;
  672. try {
  673. await updateHandover(param);
  674. uni.showToast({
  675. title: isRelease === 1 ? "提交成功" : "保存成功",
  676. icon: "success",
  677. });
  678. setTimeout(() => {
  679. uni.navigateBack();
  680. }, 1500);
  681. } catch (e) {
  682. uni.showToast({
  683. title: (e && e.message) || "操作失败",
  684. icon: "none",
  685. });
  686. } finally {
  687. this.submitLoading = false;
  688. }
  689. },
  690. async handleDecision(status) {
  691. const advice = (this.workOrderInfo.acceptRemark || "").trim();
  692. if (!advice) {
  693. uni.showToast({ title: "请填写接收建议", icon: "none" });
  694. return;
  695. }
  696. if (!this.workOrderInfo.id) {
  697. uni.showToast({ title: "缺少交接单信息", icon: "none" });
  698. return;
  699. }
  700. this.submitLoading = true;
  701. try {
  702. await acceptOrRejectHandover({
  703. id: this.workOrderInfo.id,
  704. status,
  705. acceptRemark: advice,
  706. });
  707. uni.showToast({
  708. title: status === 2 ? "接收成功" : "已驳回",
  709. icon: "success",
  710. });
  711. setTimeout(() => {
  712. uni.navigateBack();
  713. }, 1500);
  714. } catch (e) {
  715. uni.showToast({
  716. title: (e && e.message) || "操作失败",
  717. icon: "none",
  718. });
  719. } finally {
  720. this.submitLoading = false;
  721. }
  722. },
  723. addMaterial() {
  724. // 跳转到物料选择页面
  725. const storageKey = Date.now() + "";
  726. const existList = this.detailList || [];
  727. uni.setStorageSync(storageKey, existList);
  728. uni.navigateTo({
  729. url: `/pages/pda/workOrder/search/newIndex?id=&storageKey=${storageKey}&isType=pick`,
  730. });
  731. },
  732. removeMaterial(index) {
  733. uni.showModal({
  734. title: "提示",
  735. content: "确定删除此物料?",
  736. success: (res) => {
  737. if (res.confirm) {
  738. this.workOrderInfo.detailList.splice(index, 1);
  739. }
  740. },
  741. });
  742. },
  743. },
  744. onShow() {
  745. // 监听物料选择回调
  746. uni.$off("setSelectList");
  747. uni.$on("setSelectList", (selectList) => {
  748. if (Array.isArray(selectList)) {
  749. selectList.forEach((item) => {
  750. item.categoryCode = item.categoryCode || item.code || "";
  751. item.categoryName = item.categoryName || item.name || "";
  752. item.modelType = item.categoryModel || item.model || "";
  753. });
  754. this.workOrderInfo.detailList = selectList;
  755. }
  756. });
  757. },
  758. beforeDestroy() {
  759. uni.$off("setSelectList");
  760. },
  761. };
  762. </script>
  763. <style lang="scss" scoped>
  764. .page-container {
  765. height: 100vh;
  766. display: flex;
  767. flex-direction: column;
  768. background-color: #f5f6f7;
  769. overflow: hidden;
  770. }
  771. .scroll-area {
  772. flex: 1;
  773. height: 100%;
  774. }
  775. /* 区块卡片 */
  776. .section-card {
  777. margin: 20rpx 24rpx 0;
  778. background: #fff;
  779. border-radius: 16rpx;
  780. padding: 24rpx;
  781. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
  782. }
  783. .section-title {
  784. display: flex;
  785. align-items: center;
  786. margin-bottom: 20rpx;
  787. }
  788. .title-bar {
  789. width: 6rpx;
  790. height: 28rpx;
  791. background: #157a2c;
  792. border-radius: 3rpx;
  793. margin-right: 12rpx;
  794. &--blue {
  795. background: #1890ff;
  796. }
  797. }
  798. .title-text {
  799. font-size: 30rpx;
  800. font-weight: 600;
  801. color: #333;
  802. flex: 1;
  803. }
  804. .title-actions {
  805. flex-shrink: 0;
  806. }
  807. .title-btn {
  808. font-size: 24rpx;
  809. color: #157a2c;
  810. padding: 8rpx 20rpx;
  811. border: 1rpx solid #157a2c;
  812. border-radius: 24rpx;
  813. &:active {
  814. opacity: 0.7;
  815. }
  816. }
  817. /* 信息表格 */
  818. .info-table {
  819. border: 1rpx solid #e8e8e8;
  820. border-radius: 8rpx;
  821. overflow: hidden;
  822. }
  823. .table-row {
  824. display: flex;
  825. border-bottom: 1rpx solid #e8e8e8;
  826. &:last-child {
  827. border-bottom: none;
  828. }
  829. }
  830. .table-cell {
  831. flex: 1;
  832. min-width: 0;
  833. display: flex;
  834. flex-direction: column;
  835. border-right: 1rpx solid #e8e8e8;
  836. &:last-child {
  837. border-right: none;
  838. }
  839. &--full {
  840. flex: none;
  841. width: 100%;
  842. }
  843. }
  844. .cell-label {
  845. font-size: 22rpx;
  846. color: #999;
  847. background: #fafafa;
  848. padding: 12rpx 16rpx 4rpx;
  849. line-height: 1.4;
  850. }
  851. .cell-value {
  852. font-size: 26rpx;
  853. color: #333;
  854. padding: 4rpx 16rpx 12rpx;
  855. line-height: 1.5;
  856. word-break: break-all;
  857. }
  858. /* 表单 */
  859. .form-area {
  860. padding: 0;
  861. }
  862. .form-item {
  863. display: flex;
  864. align-items: center;
  865. padding: 20rpx 0;
  866. border-bottom: 1rpx solid #f5f5f5;
  867. &:last-child {
  868. border-bottom: none;
  869. }
  870. &--vertical {
  871. flex-direction: column;
  872. align-items: flex-start;
  873. .form-content {
  874. width: 100%;
  875. margin-top: 12rpx;
  876. }
  877. }
  878. }
  879. .form-label {
  880. width: 180rpx;
  881. flex-shrink: 0;
  882. font-size: 26rpx;
  883. color: #666;
  884. &.required::before {
  885. content: "*";
  886. color: #f5222d;
  887. margin-right: 4rpx;
  888. }
  889. }
  890. .form-content {
  891. flex: 1;
  892. min-width: 0;
  893. }
  894. .form-text {
  895. font-size: 26rpx;
  896. color: #333;
  897. line-height: 1.5;
  898. word-break: break-all;
  899. &.text-grey {
  900. color: #999;
  901. }
  902. }
  903. .form-picker {
  904. display: flex;
  905. align-items: center;
  906. justify-content: space-between;
  907. min-height: 60rpx;
  908. }
  909. .picker-text {
  910. font-size: 26rpx;
  911. color: #333;
  912. }
  913. .picker-placeholder {
  914. font-size: 26rpx;
  915. color: #c0c0c0;
  916. }
  917. .form-input-box {
  918. display: flex;
  919. align-items: center;
  920. border: 1rpx solid #e8e8e8;
  921. border-radius: 8rpx;
  922. overflow: hidden;
  923. }
  924. .form-input {
  925. flex: 1;
  926. height: 72rpx;
  927. padding: 0 16rpx;
  928. font-size: 26rpx;
  929. color: #333;
  930. }
  931. .form-unit {
  932. padding: 0 20rpx;
  933. font-size: 24rpx;
  934. color: #999;
  935. background: #fafafa;
  936. height: 72rpx;
  937. line-height: 72rpx;
  938. border-left: 1rpx solid #e8e8e8;
  939. }
  940. .form-textarea {
  941. width: 100%;
  942. min-height: 160rpx;
  943. padding: 16rpx;
  944. font-size: 26rpx;
  945. color: #333;
  946. border: 1rpx solid #e8e8e8;
  947. border-radius: 8rpx;
  948. box-sizing: border-box;
  949. }
  950. /* 类型选择 */
  951. .type-selector {
  952. display: flex;
  953. gap: 16rpx;
  954. }
  955. .type-tag {
  956. padding: 12rpx 32rpx;
  957. border-radius: 32rpx;
  958. font-size: 26rpx;
  959. color: #666;
  960. background: #f5f6f7;
  961. &--active {
  962. color: #fff;
  963. background: #157a2c;
  964. }
  965. &:active {
  966. opacity: 0.85;
  967. }
  968. }
  969. /* 物料卡片 */
  970. .material-empty {
  971. padding: 40rpx 0;
  972. text-align: center;
  973. }
  974. .material-empty-text {
  975. font-size: 26rpx;
  976. color: #999;
  977. }
  978. .material-card {
  979. border: 1rpx solid #e8e8e8;
  980. border-radius: 12rpx;
  981. margin-bottom: 16rpx;
  982. overflow: hidden;
  983. &:last-child {
  984. margin-bottom: 0;
  985. }
  986. }
  987. .material-header {
  988. display: flex;
  989. align-items: center;
  990. padding: 16rpx 20rpx;
  991. background: #fafafa;
  992. border-bottom: 1rpx solid #e8e8e8;
  993. }
  994. .material-index {
  995. width: 36rpx;
  996. height: 36rpx;
  997. line-height: 36rpx;
  998. text-align: center;
  999. border-radius: 50%;
  1000. background: #157a2c;
  1001. color: #fff;
  1002. font-size: 20rpx;
  1003. flex-shrink: 0;
  1004. margin-right: 12rpx;
  1005. }
  1006. .material-name {
  1007. flex: 1;
  1008. font-size: 26rpx;
  1009. font-weight: 500;
  1010. color: #333;
  1011. overflow: hidden;
  1012. text-overflow: ellipsis;
  1013. white-space: nowrap;
  1014. }
  1015. .material-del {
  1016. width: 48rpx;
  1017. height: 48rpx;
  1018. display: flex;
  1019. align-items: center;
  1020. justify-content: center;
  1021. flex-shrink: 0;
  1022. &:active {
  1023. opacity: 0.7;
  1024. }
  1025. }
  1026. .material-body {
  1027. padding: 12rpx 20rpx;
  1028. }
  1029. .material-row {
  1030. display: flex;
  1031. margin-bottom: 4rpx;
  1032. }
  1033. .material-info {
  1034. flex: 1;
  1035. display: flex;
  1036. flex-direction: column;
  1037. padding: 8rpx 0;
  1038. min-width: 0;
  1039. }
  1040. .m-label {
  1041. font-size: 22rpx;
  1042. color: #999;
  1043. margin-bottom: 2rpx;
  1044. }
  1045. .m-value {
  1046. font-size: 24rpx;
  1047. color: #333;
  1048. overflow: hidden;
  1049. text-overflow: ellipsis;
  1050. white-space: nowrap;
  1051. }
  1052. /* 底部操作栏 */
  1053. .footer-bar {
  1054. position: fixed;
  1055. left: 0;
  1056. right: 0;
  1057. bottom: 0;
  1058. z-index: 100;
  1059. display: flex;
  1060. align-items: center;
  1061. justify-content: center;
  1062. gap: 24rpx;
  1063. padding: 20rpx 32rpx;
  1064. background: #fff;
  1065. box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
  1066. padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
  1067. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  1068. }
  1069. .footer-bar__btn {
  1070. flex: 1;
  1071. height: 56rpx;
  1072. display: flex;
  1073. align-items: center;
  1074. justify-content: center;
  1075. text-align: center;
  1076. font-size: 30rpx;
  1077. font-weight: 500;
  1078. border-radius: 44rpx;
  1079. position: static;
  1080. width: auto;
  1081. line-height: normal;
  1082. }
  1083. .footer-bar__btn--primary {
  1084. background: #157a2c;
  1085. color: #fff;
  1086. }
  1087. .footer-bar__btn--primary:active {
  1088. opacity: 0.85;
  1089. }
  1090. .footer-bar__btn--outline {
  1091. background: #fff;
  1092. color: #157a2c;
  1093. border: 2rpx solid #157a2c;
  1094. }
  1095. .footer-bar__btn--outline:active {
  1096. opacity: 0.85;
  1097. }
  1098. .footer-bar__btn--danger {
  1099. background: #fff;
  1100. color: #f5222d;
  1101. border: 2rpx solid #f5222d;
  1102. }
  1103. .footer-bar__btn--danger:active {
  1104. opacity: 0.85;
  1105. }
  1106. </style>