index.vue 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188
  1. <template>
  2. <div class="container">
  3. <van-nav-bar
  4. title="退货登记"
  5. left-arrow
  6. @click-left="goBack"
  7. @click-right="init"
  8. >
  9. <template #left>
  10. <van-icon name="arrow-left" size="25" />
  11. <div class="left-btn">返回</div>
  12. </template>
  13. <template #right>
  14. <div class="nav-right right-btn">重置</div>
  15. </template>
  16. </van-nav-bar>
  17. <div class="content">
  18. <div v-if="showInitialPage" class="init-container">
  19. <div class="content-tips">
  20. <div style="flex: 1">
  21. <van-notice-bar left-icon="volume-o">请扫描退回单号</van-notice-bar>
  22. </div>
  23. </div>
  24. <div class="scan-returned-content">
  25. <div class="input-group">
  26. <van-field
  27. ref="scan-express-no-input"
  28. autofocus
  29. v-model="expressNo"
  30. autocomplete="off"
  31. placeholder="输入快递单号"
  32. clearable
  33. @keydown.enter="inputExpressNo"
  34. >
  35. </van-field>
  36. </div>
  37. <div class="button-group">
  38. <van-button
  39. @click="inputExpressNo"
  40. style="width: 100%"
  41. type="primary"
  42. class="confirm-btn"
  43. >确认
  44. </van-button>
  45. </div>
  46. </div>
  47. </div>
  48. <div v-else>
  49. <van-tabs>
  50. <van-tab title="退货信息">
  51. <div>
  52. <div class="content-tips">
  53. <div style="flex: 1">
  54. <van-notice-bar color="#1989fa" background="#ecf9ff"
  55. >{{ ownerQualityInspection }}
  56. </van-notice-bar>
  57. </div>
  58. </div>
  59. </div>
  60. <van-cell-group inset style="margin-top: 10px">
  61. <van-field
  62. v-model="params.returnNo"
  63. label="快递单号"
  64. readonly
  65. placeholder="请输入快递单号"
  66. />
  67. <van-field
  68. readonly
  69. clickable
  70. label="承运商"
  71. placeholder="选择承运商"
  72. :model-value="getLogisticName()"
  73. @click="logisticPickerShow = true"
  74. />
  75. <van-popup
  76. v-model:show="logisticPickerShow"
  77. position="bottom"
  78. destroy-on-close
  79. >
  80. <van-picker
  81. :columns="logistics"
  82. @cancel="logisticPickerShow = false"
  83. @confirm="selectedLogistic"
  84. />
  85. </van-popup>
  86. <van-field
  87. readonly
  88. clickable
  89. label="仓库"
  90. placeholder="选择仓库"
  91. :model-value="getWarehouseName()"
  92. @click="warehousePickerShow = true"
  93. />
  94. <van-popup
  95. v-model:show="warehousePickerShow"
  96. position="bottom"
  97. destroy-on-close
  98. >
  99. <van-picker
  100. :columns="warehouses"
  101. @cancel="warehousePickerShow = false"
  102. @confirm="selectedWarehouse"
  103. />
  104. </van-popup>
  105. <van-field
  106. readonly
  107. clickable
  108. label="货主"
  109. placeholder="选择货主"
  110. :model-value="getOwnerName(params.ownerCode)"
  111. @click="showOwnerSelectFunc"
  112. />
  113. <van-field
  114. readonly
  115. clickable
  116. label="店铺"
  117. placeholder="选择店铺"
  118. v-model="params.storeName"
  119. @click="storePickerShow = true"
  120. />
  121. <van-popup
  122. v-model:show="storePickerShow"
  123. position="bottom"
  124. destroy-on-close
  125. >
  126. <van-picker
  127. :columns="storeOptions"
  128. @cancel="storePickerShow = false"
  129. @confirm="selectedStore"
  130. />
  131. </van-popup>
  132. <van-field
  133. v-model="params.orderUpstream"
  134. label="上游平台"
  135. placeholder="上游平台"
  136. />
  137. <van-field
  138. v-model="params.arrivalPayment"
  139. label="到付费用"
  140. placeholder="到付费用"
  141. />
  142. <van-field
  143. v-model="params.remark"
  144. rows="1"
  145. autosize
  146. label="备注"
  147. type="textarea"
  148. placeholder="备注"
  149. />
  150. </van-cell-group>
  151. <van-cell-group inset style="margin-top: 10px">
  152. <van-field
  153. :model-value="params.originalNo"
  154. label="是否原单"
  155. readonly
  156. placeholder="是否原单"
  157. />
  158. <van-field
  159. v-model="params.upstreamNo"
  160. label="客户订单号"
  161. placeholder="客户订单号"
  162. />
  163. <van-field
  164. v-model="params.asnNo"
  165. label="ASN单号"
  166. readonly
  167. placeholder="ASN单号"
  168. />
  169. <van-field
  170. v-model="params.buyerName"
  171. label="客户姓名"
  172. readonly
  173. placeholder="客户姓名"
  174. />
  175. <van-field
  176. v-model="params.buyerPhone"
  177. label="电话号码"
  178. readonly
  179. placeholder="电话号码"
  180. />
  181. </van-cell-group>
  182. <van-cell-group inset style="margin-top: 20px; margin-bottom: 50px">
  183. <van-button type="primary" block @click="submit">提交</van-button>
  184. </van-cell-group>
  185. </van-tab>
  186. <van-tab title="商品信息" class="returned-detail-list">
  187. <div>
  188. <div class="content-tips">
  189. <div style="flex: 1">
  190. <van-notice-bar color="#1989fa" background="#ecf9ff"
  191. >{{ ownerQualityInspection }}
  192. </van-notice-bar>
  193. </div>
  194. </div>
  195. </div>
  196. <template v-if="!params.details || params.details.length === 0">
  197. <van-empty description="暂无信息请进行录入" />
  198. </template>
  199. <template v-for="(item, index) in params.details">
  200. <div class="card-div">
  201. <div class="card-div-content">
  202. <div class="info-row">
  203. <div class="info-label">sku</div>
  204. <div class="info-value">{{ item.sku }}</div>
  205. <div class="info-label">质量状态</div>
  206. <div class="info-value">
  207. <van-tag :color="getTagColor(item.qualityStatus)">
  208. {{ item.qualityStatus }}
  209. </van-tag>
  210. </div>
  211. </div>
  212. <div class="info-row">
  213. <div class="info-label">商品编号</div>
  214. <div class="info-value">{{ item.barCode }}</div>
  215. <div class="info-label">商品名称</div>
  216. <div class="info-value">
  217. <van-text-ellipsis
  218. :content="item.tradeName"
  219. rows="1"
  220. expand-text="展开"
  221. collapse-text="收起"
  222. />
  223. </div>
  224. </div>
  225. <div class="info-row">
  226. <div class="info-label">生产日期</div>
  227. <div class="info-value">
  228. {{ item.manufactureTime }}
  229. </div>
  230. <div class="info-label">失效日期</div>
  231. <div class="info-value">{{ item.validityTime }}</div>
  232. </div>
  233. <div class="info-row">
  234. <div class="info-label">批次号</div>
  235. <div class="info-value">{{ item.batchNumber }}</div>
  236. <div class="info-label">数量</div>
  237. <div class="info-value">{{ item.number }}</div>
  238. </div>
  239. </div>
  240. <div class="card-div-footer">
  241. <div class="product-description">
  242. {{ item.remark }}
  243. </div>
  244. </div>
  245. <template v-if="hasBoxItems(item)">
  246. <div>
  247. <van-divider content-position="left">外箱图</van-divider>
  248. <van-row>
  249. <template v-for="(i, imgIndex) in getBoxItems(item)">
  250. <van-col span="4">
  251. <van-image
  252. :key="`box-photos-${index}-${imgIndex}`"
  253. @click="
  254. previewImages(item.boxPhotos, imgIndex, '外箱图')
  255. "
  256. width="100%"
  257. height="50"
  258. :src="i.src"
  259. />
  260. </van-col>
  261. </template>
  262. </van-row>
  263. </div>
  264. </template>
  265. <template v-if="hasProductItems(item)">
  266. <div>
  267. <van-divider content-position="left">内物图</van-divider>
  268. <van-row>
  269. <template v-for="(i, imgIndex) in getProductItems(item)">
  270. <van-col span="4">
  271. <van-image
  272. :key="`product-photos-${index}-${imgIndex}`"
  273. @click="
  274. previewImages(
  275. item.productPhotos,
  276. imgIndex,
  277. '内物图',
  278. )
  279. "
  280. width="100%"
  281. height="60"
  282. :src="i.src"
  283. />
  284. </van-col>
  285. </template>
  286. </van-row>
  287. </div>
  288. </template>
  289. <div class="card-div-footer-options">
  290. <div class="options-row">
  291. <div class="info-value">
  292. <van-button
  293. size="mini"
  294. type="danger"
  295. block
  296. plain
  297. @click="removeDetails(index)"
  298. >删除
  299. </van-button>
  300. </div>
  301. <div class="info-value">
  302. <van-button
  303. size="mini"
  304. type="default"
  305. block
  306. plain
  307. @click="editDetails(index)"
  308. >编辑
  309. </van-button>
  310. </div>
  311. </div>
  312. </div>
  313. </div>
  314. </template>
  315. <van-floating-bubble
  316. axis="xy"
  317. icon="add"
  318. magnetic="x"
  319. @click="showScancode"
  320. />
  321. </van-tab>
  322. </van-tabs>
  323. </div>
  324. </div>
  325. <van-dialog
  326. v-model:show="scancodeDialog"
  327. title="扫描条码"
  328. @open="openScanCode"
  329. @confirm="showQualityStatus"
  330. show-cancel-button
  331. >
  332. <van-field
  333. ref="scancodeInputRef"
  334. v-model="scancode"
  335. label="商品条码"
  336. autocomplete="off"
  337. @keyup.enter="showQualityStatus"
  338. placeholder="商品条码"
  339. />
  340. </van-dialog>
  341. <van-dialog
  342. v-model:show="qualityStatusDialog"
  343. title="质量状态"
  344. @confirm="queryBarcode"
  345. show-cancel-button
  346. >
  347. <van-radio-group v-model="qualityStatus">
  348. <template v-for="(item, index) in qualityStatusOptions">
  349. <van-cell
  350. :title="item.text"
  351. clickable
  352. @click="qualityStatus = item.value"
  353. >
  354. <template #right-icon>
  355. <van-radio :name="item.value" />
  356. </template>
  357. </van-cell>
  358. </template>
  359. </van-radio-group>
  360. </van-dialog>
  361. <van-dialog
  362. v-model:show="returnedDetailDialog"
  363. title="商品详情"
  364. show-cancel-button
  365. :lazy-render="true"
  366. :show-confirm-button="checkUploadImages()"
  367. @confirm="addDetails"
  368. @cancel="cancelReturnedDetailDialog"
  369. >
  370. <div style="max-height: 70vh; overflow-y: auto">
  371. <van-field
  372. v-model="selectDetail.sku"
  373. label="SKU"
  374. placeholder="SKU"
  375. clearable
  376. />
  377. <van-field
  378. readonly
  379. v-model="selectDetail.barCode"
  380. label="商品条码"
  381. placeholder="商品条码"
  382. clearable
  383. />
  384. <van-field
  385. v-model="selectDetail.tradeName"
  386. label="商品名称"
  387. placeholder="商品名称"
  388. readonly
  389. />
  390. <van-field
  391. readonly
  392. clickable
  393. label="质量状态"
  394. placeholder="质量状态"
  395. v-model="selectDetail.qualityStatus"
  396. @click="selectedDetailQualityStatus = true"
  397. />
  398. <van-popup
  399. v-model:show="selectedDetailQualityStatus"
  400. position="bottom"
  401. destroy-on-close
  402. >
  403. <van-picker
  404. :columns="qualityStatusOptions"
  405. @cancel="selectedDetailQualityStatus = false"
  406. @confirm="selectedDetailQualityStatusFunc"
  407. />
  408. </van-popup>
  409. <van-field
  410. is-link
  411. readonly
  412. name="datePicker"
  413. label="生产日期"
  414. :placeholder="selectDetail.manufactureTime ? '' : '请选择生产日期'"
  415. :model-value="formatDateDisplay(selectDetail.manufactureTime)"
  416. @click="showManufactureTime = true"
  417. />
  418. <van-popup
  419. v-model:show="showManufactureTime"
  420. destroy-on-close
  421. position="bottom"
  422. >
  423. <van-date-picker
  424. :max-date="maxDate"
  425. :min-date="minDate"
  426. :model-value="parseDateValue(selectDetail.manufactureTime)"
  427. @confirm="manufactureTimeConfirm"
  428. @cancel="showManufactureTime = false"
  429. />
  430. </van-popup>
  431. <van-field
  432. :model-value="formatDateDisplay(selectDetail.validityTime)"
  433. is-link
  434. readonly
  435. name="datePicker"
  436. label="失效日期"
  437. :placeholder="selectDetail.validityTime ? '' : '请选择失效日期'"
  438. @click="showValidityTime = true"
  439. />
  440. <van-popup
  441. v-model:show="showValidityTime"
  442. destroy-on-close
  443. position="bottom"
  444. >
  445. <van-date-picker
  446. :max-date="maxDate"
  447. :min-date="minDate"
  448. :model-value="parseDateValue(selectDetail.validityTime)"
  449. @confirm="validityTimeConfirm"
  450. @cancel="showValidityTime = false"
  451. />
  452. </van-popup>
  453. <van-field
  454. autocomplete="off"
  455. v-model="selectDetail.batchNumber"
  456. label="批次号"
  457. placeholder="批次号"
  458. clearable
  459. />
  460. <van-field
  461. v-model="selectDetail.number"
  462. label="数量"
  463. type="digit"
  464. placeholder="数量"
  465. clearable
  466. />
  467. <van-field
  468. v-model="selectDetail.remark"
  469. label="备注"
  470. placeholder="备注"
  471. label-align="top"
  472. />
  473. <van-row v-if="showAccessories">
  474. <div style="font-size: 12px">
  475. <template v-for="(item, index) in accessories">
  476. <p>
  477. 配件条码: [<span style="color: #2ca547">{{
  478. item.accessory
  479. }}</span
  480. >] [<span style="color: #277b39">{{ item.descrC }}</span
  481. >] 数量: [<span style="color: #2ca547">{{ item.qty }}</span
  482. >]件
  483. </p>
  484. </template>
  485. <p style="font-size: 12px">
  486. 请检查商品: 【<span style="color: #ff2020">{{
  487. selectDetail.sku
  488. }}</span
  489. >】 {{ selectDetail.tradeName }} 配件
  490. </p>
  491. </div>
  492. </van-row>
  493. <template
  494. v-if="selectDetail.boxPhotos && selectDetail.boxPhotos.length > 0"
  495. >
  496. <van-divider content-position="left" style="margin: 0px"
  497. >外箱图
  498. </van-divider>
  499. <van-row>
  500. <template
  501. v-if="selectDetail.boxPhotos && selectDetail.boxPhotos.length > 0"
  502. >
  503. <template v-for="(item, index) in selectDetail.boxPhotos">
  504. <van-col span="4">
  505. <van-image
  506. :key="`box-photos-${index}`"
  507. width="100%"
  508. height="50"
  509. :src="getImageUrl(item, '外箱图')"
  510. @click="showBoxImagePreview(index)"
  511. />
  512. </van-col>
  513. </template>
  514. <van-image-preview
  515. v-model:show="showBoxPreview"
  516. :images="detailBoxImages"
  517. :start-position="startBoxPosition"
  518. @change="onBoxPreviewChange"
  519. closeable
  520. >
  521. <template #index>
  522. <div class="custom-toolbar">
  523. <span
  524. >{{ startPhotosPosition + 1 }}/{{
  525. selectDetail.boxPhotos.length
  526. }}</span
  527. >
  528. <van-button
  529. icon="delete"
  530. type="danger"
  531. @click.stop="handleBoxDelete"
  532. size="mini"
  533. >删除
  534. </van-button>
  535. </div>
  536. </template>
  537. </van-image-preview>
  538. </template>
  539. </van-row>
  540. </template>
  541. <template
  542. v-if="
  543. selectDetail.productPhotos && selectDetail.productPhotos.length > 0
  544. "
  545. >
  546. <van-divider content-position="left" style="margin: 0px"
  547. >内物图
  548. </van-divider>
  549. <van-row>
  550. <template v-for="(item, index) in selectDetail.productPhotos">
  551. <van-col span="4">
  552. <van-image
  553. :key="`product-photos-${index}`"
  554. width="100%"
  555. height="50"
  556. :src="getImageUrl(item, '内物图')"
  557. @click="showPhotosImagePreview(index)"
  558. />
  559. </van-col>
  560. </template>
  561. </van-row>
  562. <van-image-preview
  563. v-model:show="showPhotosPreview"
  564. :images="detailProductImages"
  565. :start-position="startPhotosPosition"
  566. @change="onPhotosPreviewChange"
  567. closeable
  568. >
  569. <template #index>
  570. <div class="custom-toolbar">
  571. <span>
  572. {{ startPhotosPosition + 1 }}/{{
  573. selectDetail.productPhotos.length
  574. }}
  575. </span>
  576. <van-button
  577. icon="delete"
  578. type="danger"
  579. @click.stop="handlePhotosDelete"
  580. size="mini"
  581. >删除
  582. </van-button>
  583. </div>
  584. </template>
  585. </van-image-preview>
  586. </template>
  587. <van-row>
  588. <van-col span="12">
  589. <van-button
  590. type="primary"
  591. block
  592. @click="invokeCameraToCapture('外箱图')"
  593. >外箱图录入
  594. </van-button>
  595. </van-col>
  596. <van-col span="12">
  597. <van-button
  598. type="primary"
  599. block
  600. @click="invokeCameraToCapture('内物图')"
  601. >内物图录入
  602. </van-button>
  603. </van-col>
  604. </van-row>
  605. <input
  606. type="file"
  607. id="outer-carton-box-input"
  608. capture="user"
  609. accept="image/*"
  610. hidden
  611. @change="outerCartonInput"
  612. />
  613. <input
  614. type="file"
  615. id="inner-contents-input"
  616. capture="user"
  617. accept="image/*"
  618. hidden
  619. @change="innerContentsInput"
  620. />
  621. </div>
  622. </van-dialog>
  623. <van-popup
  624. v-model:show="showOwnerSelect"
  625. destroy-on-close
  626. round
  627. position="bottom"
  628. >
  629. <van-picker
  630. :model-value="getOwnerName(params.ownerCode)"
  631. :columns="ownerSelectedOptions"
  632. @cancel="showOwnerSelect = false"
  633. @confirm="selectOwner"
  634. />
  635. </van-popup>
  636. <owner ref="ownerRef" @onOwner="onOwner" />
  637. </div>
  638. </template>
  639. <script setup>
  640. import { ref, computed, nextTick, onMounted } from 'vue'
  641. import {
  642. showFailToast,
  643. showNotify,
  644. showLoadingToast,
  645. closeToast,
  646. showConfirmDialog,
  647. showImagePreview
  648. } from 'vant'
  649. import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
  650. import {
  651. deleteDetails,
  652. getQualityInspection,
  653. getQualityInspectionBy,
  654. getQualityStatus,
  655. getReturnedByExpress,
  656. getTagColorBy,
  657. listAsn,
  658. matchAsnBy,
  659. matchCarrierCode,
  660. matchOrderBy,
  661. register,
  662. searchBarcode,
  663. searchOwnerBarcode,
  664. shops,
  665. validateDate
  666. } from '@/api/returned/index.ts'
  667. import { carrierOptions, getOwner, getWarehouse } from '@/api/basic/index.ts'
  668. import { useStore } from '@/store/modules/user'
  669. import { getStatus } from '@/utils/returned.ts'
  670. import Owner from '@/components/Owner.vue'
  671. try {
  672. getHeader()
  673. } catch (error) {
  674. console.log(error)
  675. }
  676. const minDate = ref(new Date(new Date().getFullYear() - 5, 0, 1))
  677. const maxDate = ref(new Date((new Date().getFullYear() + 50, 0, 1)))
  678. const showInitialPage = ref(true)
  679. const expressNo = ref(null)
  680. const store = useStore()
  681. const warehouse = store.warehouse
  682. // 当前选择的货主
  683. const title = computed(() => {
  684. if (expressNo.value && showInitialPage.value === false) {
  685. return '退货登记:' + params.value.returnNo
  686. }
  687. return '退货登记'
  688. })
  689. const qualityStatusOptions = ref([])
  690. // 货主
  691. const owners = ref([])
  692. // 仓库
  693. const warehouses = ref([])
  694. // 承运商
  695. const logistics = ref([])
  696. // 店铺
  697. const storeOptions = ref([])
  698. // 选中相关信息的下标
  699. const selectDetailIndex = ref(-1)
  700. const ownerMap = ref({})
  701. const warehousesMap = ref({})
  702. const logisticsMap = ref({})
  703. const asnList = ref({})
  704. // 外箱图
  705. const boxFiles = ref([])
  706. // 内物图
  707. const productFiles = ref([])
  708. onMounted(() => {
  709. const now = new Date()
  710. const currentYear = now.getFullYear()
  711. const currentCentury = Math.floor(currentYear / 100) * 100
  712. const endOfCenturyYear = currentCentury + 99
  713. maxDate.value = new Date(endOfCenturyYear, 0, 1)
  714. minDate.value = new Date(currentCentury, 0, 1)
  715. console.log(warehouse)
  716. getOwner().then((res) => {
  717. const { data } = res
  718. owners.value = data.map((item) => {
  719. return { value: item.code, text: item.name }
  720. })
  721. const map = {}
  722. data.forEach((item) => {
  723. map[item.code] = item.name
  724. })
  725. ownerMap.value = map
  726. })
  727. getQualityStatus().then((res) => {
  728. const { data } = res
  729. qualityStatusOptions.value = data
  730. .filter((item) => {
  731. return item !== '机损'
  732. })
  733. .map((item) => {
  734. return { value: item, text: item }
  735. })
  736. })
  737. getWarehouse().then((res) => {
  738. const { data } = res
  739. warehouses.value = data.map((item) => {
  740. return { value: item.code, text: item.name }
  741. })
  742. const map = {}
  743. data.forEach((item) => {
  744. map[item.code] = item.name
  745. })
  746. warehousesMap.value = map
  747. })
  748. carrierOptions().then((res) => {
  749. const { data } = res
  750. logistics.value = data.map((item) => {
  751. return { value: item.name, text: item.name }
  752. })
  753. const map = {}
  754. data.forEach((item) => {
  755. map[item.name] = item.name
  756. })
  757. logisticsMap.value = map
  758. })
  759. })
  760. // 商品外箱图
  761. function hasBoxItems(detail) {
  762. const { boxPhotos } = detail
  763. if (!boxPhotos || boxPhotos.length === 0) {
  764. return false
  765. }
  766. return boxFiles.value.some((item) => {
  767. return boxPhotos.includes(item.fileName)
  768. })
  769. }
  770. // 商品外箱图
  771. function getBoxItems(detail) {
  772. const { boxPhotos } = detail
  773. if (!boxPhotos || boxPhotos.length === 0) {
  774. return []
  775. }
  776. return boxFiles.value.filter((item) => boxPhotos.includes(item.fileName))
  777. }
  778. // 商品外箱图
  779. function hasProductItems(detail) {
  780. const { productPhotos } = detail
  781. if (!productPhotos || productPhotos.length === 0) {
  782. return false
  783. }
  784. return productFiles.value.some((item) => {
  785. return productPhotos.includes(item.fileName)
  786. })
  787. }
  788. // 商品内物图
  789. function getProductItems(detail) {
  790. const { productPhotos } = detail
  791. if (!productPhotos || productPhotos.length === 0) {
  792. return []
  793. }
  794. return productFiles.value.filter((item) => {
  795. return productPhotos.includes(item.fileName)
  796. })
  797. }
  798. // 获取对应的店铺信息
  799. async function getStoreOptionsBy(owner) {
  800. const results = await shops(owner)
  801. const { data } = results
  802. if (!data || data.length === 0) {
  803. storeOptions.value = []
  804. return
  805. }
  806. storeOptions.value = data.map((item) => {
  807. return { value: item.name, text: item.name }
  808. })
  809. }
  810. const qualityInspection = ref(null)
  811. // 初始化质检状态
  812. function initQualityInspection(owner) {
  813. getQualityInspection(owner).then((res) => {
  814. qualityInspection.value = res.data
  815. })
  816. }
  817. const ownerQualityInspection = computed(() => {
  818. return getQualityInspectionBy(qualityInspection.value)
  819. })
  820. // 扫描商品条码
  821. const scancode = ref('')
  822. // 商品质量状态
  823. const qualityStatus = ref('')
  824. // 提交的商品信息
  825. const params = ref({
  826. id: null,
  827. returnNo: null,
  828. warehouseCode: warehouse,
  829. logisticsName: null,
  830. storeName: null,
  831. upstreamNo: null,
  832. ownerCode: null,
  833. orderUpstream: null,
  834. orderUpSteam: null,
  835. arrivalPayment: null,
  836. buyerName: null,
  837. asnNo: null,
  838. buyerPhone: null,
  839. originalNo: null,
  840. remark: null,
  841. details: []
  842. })
  843. // 商品信息
  844. const selectDetail = ref({
  845. id: null,
  846. rejectHeadId: null,
  847. rejectPushTaskNo: null,
  848. status: null,
  849. detailStatus: null,
  850. isGenuine: null,
  851. qualityMark: null,
  852. sku: '',
  853. barCode: '',
  854. tradeName: '',
  855. qualityStatus: '',
  856. manufactureTime: '',
  857. validityTime: '',
  858. batchNumber: '',
  859. remark: '',
  860. asnNo: null,
  861. number: 0,
  862. warehouse: null,
  863. warehouseBin: null,
  864. boxPhotos: [],
  865. productPhotos: [],
  866. files: null,
  867. pieceTag: null,
  868. createTime: null,
  869. creatorId: null,
  870. updateTime: null,
  871. updaterId: null,
  872. deleteTime: null,
  873. version: null,
  874. repairableType: null,
  875. repairableTypes: null,
  876. operatorId: null,
  877. operatorName: null,
  878. operatorTime: null,
  879. skuImage: null,
  880. orginSkuImage: null
  881. })
  882. function initDetail() {
  883. selectDetail.value = {
  884. id: null,
  885. rejectHeadId: null,
  886. rejectPushTaskNo: null,
  887. status: null,
  888. detailStatus: null,
  889. isGenuine: null,
  890. qualityMark: null,
  891. sku: '',
  892. barCode: '',
  893. tradeName: '',
  894. qualityStatus: '',
  895. manufactureTime: '',
  896. validityTime: '',
  897. batchNumber: '',
  898. remark: '',
  899. asnNo: null,
  900. number: null,
  901. warehouse: null,
  902. warehouseBin: null,
  903. boxPhotos: [],
  904. productPhotos: [],
  905. files: null,
  906. pieceTag: null,
  907. createTime: null,
  908. creatorId: null,
  909. updateTime: null,
  910. updaterId: null,
  911. deleteTime: null,
  912. version: null,
  913. repairableType: null,
  914. repairableTypes: null,
  915. operatorId: null,
  916. operatorName: null,
  917. operatorTime: null,
  918. skuImage: null,
  919. orginSkuImage: null
  920. }
  921. }
  922. // 承运商选择
  923. const logisticPickerShow = ref(false)
  924. const selectedLogistic = (row) => {
  925. const { selectedOptions } = row
  926. logisticPickerShow.value = false
  927. params.value.logisticsName = selectedOptions[0].text
  928. }
  929. // 货主选择
  930. const ownerPickerShow = ref(false)
  931. const ownerName = ref('')
  932. const selectedOwner = (row) => {
  933. const { selectedValues, selectedOptions } = row
  934. ownerPickerShow.value = false
  935. params.value.ownerCode = selectedOptions[0].value
  936. ownerName.value = selectedOptions[0].text
  937. initQualityInspection(params.value.ownerCode)
  938. }
  939. // 仓库选择
  940. const warehousePickerShow = ref(false)
  941. const selectedWarehouse = (row) => {
  942. const { selectedOptions } = row
  943. warehousePickerShow.value = false
  944. params.value.warehouseCode = selectedOptions[0].value
  945. }
  946. // 店铺选择
  947. const storePickerShow = ref(false)
  948. const selectedStore = (row) => {
  949. const { selectedOptions } = row
  950. storePickerShow.value = false
  951. params.value.storeName = selectedOptions[0].text
  952. }
  953. // 承运商名称
  954. function getLogisticName() {
  955. return params.value.logisticsName
  956. }
  957. // 货主名
  958. function getOwnerName(owner) {
  959. return ownerMap.value[owner]
  960. }
  961. // 仓库名
  962. function getWarehouseName() {
  963. console.log(params.value.warehouseCode)
  964. return warehousesMap.value[params.value.warehouseCode]
  965. }
  966. // 输入快递单号
  967. function inputExpressNo() {
  968. const no = expressNo.value
  969. const isSuccess = checkExpressNo(no)
  970. if (!isSuccess) {
  971. return
  972. }
  973. const express_no = initExpressNo(no)
  974. if (!express_no || express_no.length === 0) {
  975. showFailToast('快递单号异常')
  976. scanError()
  977. return
  978. }
  979. params.value.returnNo = express_no
  980. params.value.warehouseCode = warehouse
  981. listAsn(express_no).then((res) => {
  982. if (res.data) {
  983. asnList.value = res.data
  984. }
  985. })
  986. matchReturned(express_no)
  987. }
  988. function matchReturned(express_no) {
  989. getReturnedByExpress(express_no).then((res) => {
  990. const { data } = res
  991. if (!data) {
  992. matchOrder(express_no)
  993. return
  994. }
  995. const { id, headStatus, returnNo, warehouseCode } = data
  996. if (!id) {
  997. matchOrder(express_no)
  998. expressNo.value = null
  999. showInitialPage.value = false
  1000. return
  1001. }
  1002. if (id) {
  1003. if (['已入仓', '已拆包'].includes(headStatus)) {
  1004. showNotify({
  1005. type: 'primary',
  1006. message: `当前退回单号${returnNo}--${headStatus}`
  1007. })
  1008. init(false)
  1009. params.value.id = id
  1010. params.value.returnNo = returnNo
  1011. params.value.warehouseCode = warehouseCode
  1012. params.value.warehousingStatus = null
  1013. selectDetailIndex.value = -1
  1014. matchOrder(express_no)
  1015. expressNo.value = null
  1016. showInitialPage.value = false
  1017. scanSuccess()
  1018. } else {
  1019. showNotify({
  1020. type: 'danger',
  1021. message: '当前退回单号已进行过登记 如需修改请前往PC端进行'
  1022. })
  1023. scanSuccess()
  1024. }
  1025. }
  1026. })
  1027. }
  1028. // 匹配原单信息
  1029. function matchOrder(expressNo) {
  1030. matchOrderBy(expressNo).then((res) => {
  1031. const { data } = res
  1032. if (!data) {
  1033. console.log('未匹配到订单信息')
  1034. matchAsn(expressNo)
  1035. return
  1036. }
  1037. console.log('匹配到订单信息')
  1038. if (data) {
  1039. const {
  1040. upstreamNo,
  1041. shopName,
  1042. ownerCode,
  1043. recipientName,
  1044. phone,
  1045. logisticName,
  1046. details
  1047. } = data
  1048. showNotify({ type: 'success', message: '匹配到原单信息' })
  1049. params.value.upstreamNo = upstreamNo
  1050. params.value.ownerCode = ownerCode
  1051. params.value.warehouseCode = warehouse
  1052. params.value.storeName = shopName
  1053. params.value.logisticsName = logisticName
  1054. params.value.buyerName = handlerLongText(recipientName)
  1055. params.value.buyerPhone = handlerLongText(phone)
  1056. params.value.originalNo = '原单退回'
  1057. params.value.sendNo = null
  1058. initQualityInspection(params.value.ownerCode)
  1059. getStoreOptionsBy(ownerCode)
  1060. if (!details || details.length === 0) {
  1061. return
  1062. }
  1063. const list = []
  1064. details.forEach((item) => {
  1065. const {
  1066. sku,
  1067. barCode,
  1068. detailAmount,
  1069. name,
  1070. productionDate,
  1071. expirationDate,
  1072. batchNumber,
  1073. quality,
  1074. attributeBin
  1075. } = item
  1076. let qualityStatus
  1077. if (quality) {
  1078. if (quality === 'ZP') {
  1079. qualityStatus = '正品'
  1080. qualityStatus = '正品'
  1081. } else if (quality === 'CP') {
  1082. qualityStatus = '次品'
  1083. } else {
  1084. qualityStatus = '正品'
  1085. }
  1086. } else {
  1087. qualityStatus = '正品'
  1088. }
  1089. list.push({
  1090. sku: sku,
  1091. tradeName: name,
  1092. barCode: barCode,
  1093. number: detailAmount,
  1094. manufactureTime: productionDate,
  1095. validityTime: expirationDate,
  1096. batchNumber: batchNumber,
  1097. warehouse: attributeBin,
  1098. qualityStatus: qualityStatus,
  1099. isOriginal: true
  1100. })
  1101. params.value.details = list
  1102. matchCarrier(expressNo)
  1103. })
  1104. } else {
  1105. matchAsn(expressNo)
  1106. }
  1107. })
  1108. }
  1109. function matchCarrier(expressNo) {
  1110. matchCarrierCode(expressNo).then((res) => {
  1111. const { data } = res
  1112. if (!data || data.length === 0) {
  1113. showNotify({
  1114. type: 'primary',
  1115. message: '请手动选择承运商'
  1116. })
  1117. } else {
  1118. params.value.logisticsName = data
  1119. }
  1120. })
  1121. }
  1122. // 转化加密
  1123. function handlerLongText(text) {
  1124. return text && text.length > 15 ? '加密' : text ? text : null
  1125. }
  1126. // 匹配对应的ASN
  1127. function matchAsn(expressNo) {
  1128. matchAsnBy(expressNo).then((res) => {
  1129. const { data } = res
  1130. if (!data) {
  1131. console.log('未匹配asn单信息')
  1132. matchCarrier(expressNo)
  1133. return
  1134. }
  1135. const {
  1136. ownerCode,
  1137. phone,
  1138. shopName,
  1139. recipientName,
  1140. logisticName,
  1141. upstreamNo
  1142. } = data
  1143. params.value.returnNo = expressNo
  1144. params.value.upstreamNo = upstreamNo
  1145. params.value.ownerCode = ownerCode
  1146. params.value.warehouseCode = warehouse
  1147. params.value.storeName = shopName
  1148. params.value.logisticsName = logisticName
  1149. params.value.buyerName = handlerLongText(recipientName)
  1150. params.value.buyerPhone = handlerLongText(phone)
  1151. params.value.originalNo = null
  1152. params.value.sendNo = null
  1153. params.value.details = []
  1154. matchCarrier(expressNo)
  1155. getStoreOptionsBy(ownerCode)
  1156. initQualityInspection(params.value.ownerCode)
  1157. initDetail()
  1158. })
  1159. }
  1160. // 校验快递单号格式
  1161. function checkExpressNo(no) {
  1162. if (!no || no.length === 0) {
  1163. showFailToast('单号长度异常')
  1164. scanError()
  1165. return false
  1166. }
  1167. if (no.startsWith('http')) {
  1168. showFailToast('扫描到了错误的条码')
  1169. scanError()
  1170. return false
  1171. }
  1172. if (no.endsWith('.com')) {
  1173. showFailToast('扫描到了错误的条码')
  1174. scanError()
  1175. return false
  1176. }
  1177. const length = no.length
  1178. if (length > 30 || length < 5) {
  1179. showFailToast('单号长度异常')
  1180. scanError()
  1181. return false
  1182. }
  1183. return true
  1184. }
  1185. // 处理快递单号相关问题
  1186. function initExpressNo(no) {
  1187. // 处理扫描快递单号
  1188. if (!no) {
  1189. return no
  1190. }
  1191. if (no.includes('-')) {
  1192. no = no.substring(0, no.indexOf('-')) // 京东快递异常
  1193. }
  1194. return no
  1195. .replaceAll('-', '')
  1196. .replaceAll(' ', '')
  1197. .replaceAll('R02Z', '')
  1198. .replaceAll('R02T', '') // 圆通退回单号异常
  1199. }
  1200. // 重置提交 恢复到扫描快递单号页面
  1201. function init(showInputPage = true) {
  1202. ownerName.value = null
  1203. clearImageFileCache()
  1204. params.value = {
  1205. orderUpstream: null,
  1206. returnNo: null,
  1207. warehouseCode: warehouse,
  1208. logisticsName: null,
  1209. storeName: null,
  1210. orderUpSteam: null,
  1211. arrivalPayment: null,
  1212. buyerName: null,
  1213. asnNo: null,
  1214. upstreamNo: null,
  1215. buyerPhone: null,
  1216. originalNo: null,
  1217. remark: null,
  1218. details: []
  1219. }
  1220. storeOptions.value = []
  1221. expressNo.value = null
  1222. showInitialPage.value = showInputPage
  1223. initDetail()
  1224. }
  1225. // 扫码弹框
  1226. const scancodeDialog = ref(false)
  1227. const scancodeInputRef = ref(false)
  1228. const qualityStatusDialog = ref(false)
  1229. // 扫描条码dialog
  1230. function showScancode() {
  1231. scancodeDialog.value = true
  1232. }
  1233. function openScanCode() {
  1234. nextTick(() => {
  1235. setTimeout(() => {
  1236. const input = scancodeInputRef.value?.$el?.querySelector('input')
  1237. input?.focus()
  1238. input?.setSelectionRange(0, input.value.length)
  1239. }, 150)
  1240. })
  1241. }
  1242. // 选择质量状态
  1243. function showQualityStatus() {
  1244. const barcode = scancode.value
  1245. if (!barcode) {
  1246. showFailToast('商品条码异常')
  1247. scancodeDialog.value = true
  1248. return
  1249. }
  1250. scanSuccess()
  1251. qualityStatusDialog.value = true
  1252. qualityStatus.value = '正品'
  1253. }
  1254. // 查询商品
  1255. async function queryBarcode() {
  1256. showLoadingToast({
  1257. duration: 0,
  1258. forbidClick: true,
  1259. message: '查询商品信息中.....'
  1260. })
  1261. const barcode = scancode.value
  1262. const { ownerCode, details } = params.value
  1263. if (ownerCode) {
  1264. const result = await queryOwnerBarcode(ownerCode, barcode)
  1265. if (result) {
  1266. scanSuccess()
  1267. scancodeDialog.value = false
  1268. qualityStatusDialog.value = false
  1269. closeToast()
  1270. return
  1271. }
  1272. }
  1273. if (!details || details.length === 0) {
  1274. await queryBarcodeBy(barcode)
  1275. scancodeDialog.value = false
  1276. qualityStatusDialog.value = false
  1277. closeToast()
  1278. }
  1279. }
  1280. // 根据获取查询信息
  1281. async function queryOwnerBarcode(ownerCode, barcode) {
  1282. try {
  1283. const results = await searchOwnerBarcode({ ownerCode, barcode })
  1284. const {
  1285. data: { basSku }
  1286. } = results
  1287. if (!basSku) {
  1288. return false
  1289. }
  1290. addDetailByBasSku(basSku)
  1291. return true
  1292. } catch (err) {
  1293. closeToast()
  1294. console.log(err.message)
  1295. }
  1296. return false
  1297. }
  1298. async function queryBarcodeBy(barcode) {
  1299. try {
  1300. const results = await searchBarcode({ barcode })
  1301. const { data } = results
  1302. const { basSku, ownerCodes } = data
  1303. if (ownerCodes && ownerCodes.length > 1) {
  1304. showOwnerSelectDialog(ownerCodes)
  1305. return null
  1306. }
  1307. if (!basSku) {
  1308. scanError()
  1309. return null
  1310. }
  1311. addDetailByBasSku(basSku)
  1312. scanSuccess()
  1313. return null
  1314. } catch (err) {
  1315. closeToast()
  1316. scanError()
  1317. console.log(err.message)
  1318. }
  1319. scanError()
  1320. return null
  1321. }
  1322. // 添加详情
  1323. function addDetailByBasSku(basSku) {
  1324. const { sku, barCode, ownerCode, tradeName, accessories } = basSku
  1325. selectDetailIndex.value = -1
  1326. selectDetail.value.sku = sku
  1327. if (
  1328. !params.value.ownerCode ||
  1329. !params.value.details ||
  1330. params.value.details.length === 0
  1331. ) {
  1332. params.value.ownerCode = ownerCode
  1333. initQualityInspection(params.value.ownerCode)
  1334. getStoreOptionsBy(params.value.ownerCode)
  1335. }
  1336. selectDetail.value.barCode = barCode
  1337. selectDetail.value.qualityStatus = qualityStatus.value
  1338. selectDetail.value.tradeName = tradeName
  1339. selectDetail.value.number = 1
  1340. scancodeDialog.value = false
  1341. qualityStatusDialog.value = false
  1342. returnedDetailDialog.value = true
  1343. // 显示
  1344. showAccessoriesItems(accessories)
  1345. }
  1346. const accessories = ref([])
  1347. const showAccessories = ref(false)
  1348. function showAccessoriesItems(items) {
  1349. if (!items || items.length === 0) {
  1350. accessories.value = []
  1351. showAccessories.value = false
  1352. return
  1353. }
  1354. accessories.value = items
  1355. showAccessories.value = true
  1356. }
  1357. // 质量状态
  1358. const returnedDetailDialog = ref(false)
  1359. const selectedDetailQualityStatus = ref(false)
  1360. const selectedDetailQualityStatusFunc = (row) => {
  1361. const { selectedOptions } = row
  1362. selectedDetailQualityStatus.value = false
  1363. selectDetail.value.qualityStatus = selectedOptions[0].text
  1364. }
  1365. // 辅助函数:格式化日期显示(可选)
  1366. const formatDateDisplay = (dateString) => {
  1367. if (!dateString) return ''
  1368. return dateString.replace(/-/g, '/') // 将 2023-05-01 显示为 2023/05/01
  1369. }
  1370. // 辅助函数:将日期字符串转换为数组格式 [年, 月, 日]
  1371. const parseDateValue = (dateString) => {
  1372. if (!dateString) return []
  1373. const [year, month, day] = dateString.split('-')
  1374. return [year, month, day]
  1375. }
  1376. // 生产日期
  1377. const showManufactureTime = ref(false)
  1378. const manufactureTimeConfirm = async (result) => {
  1379. if (!result.selectedValues) {
  1380. showManufactureTime.value = false
  1381. return
  1382. }
  1383. // 正确解构参数
  1384. const { selectedValues } = result
  1385. const [year, month, day] = selectedValues
  1386. const formatted_month = month.padStart(2, '0')
  1387. const formatted_day = day.padStart(2, '0')
  1388. const manufactureTime = `${year}-${formatted_month}-${formatted_day}`
  1389. const { sku } = selectDetail.value
  1390. const { ownerCode } = params.value
  1391. const check_validate_date = await checkValidateDate(
  1392. ownerCode,
  1393. sku,
  1394. manufactureTime,
  1395. 'manufactureTime'
  1396. )
  1397. if (!check_validate_date) {
  1398. console.log('检验出现异常')
  1399. return
  1400. }
  1401. selectDetail.value.manufactureTime = manufactureTime
  1402. showManufactureTime.value = false
  1403. }
  1404. // 失效日期
  1405. const showValidityTime = ref(false)
  1406. const validityTimeConfirm = async (result) => {
  1407. if (!result.selectedValues) {
  1408. showValidityTime.value = false
  1409. return
  1410. }
  1411. // 正确解构参数
  1412. const { selectedValues } = result
  1413. const [year, month, day] = selectedValues
  1414. const formatted_month = month.padStart(2, '0')
  1415. const formatted_day = day.padStart(2, '0')
  1416. const validityTime = `${year}-${formatted_month}-${formatted_day}`
  1417. const { sku } = selectDetail.value
  1418. const { ownerCode } = params.value
  1419. const check_validate_date = await checkValidateDate(
  1420. ownerCode,
  1421. sku,
  1422. validityTime,
  1423. 'validityTime'
  1424. )
  1425. if (!check_validate_date) {
  1426. return
  1427. }
  1428. selectDetail.value.validityTime = `${year}-${formatted_month}-${formatted_day}`
  1429. showValidityTime.value = false
  1430. }
  1431. // 质量状态标签
  1432. function getTagColor(qualityStatus) {
  1433. return getTagColorBy(qualityStatus)
  1434. }
  1435. const cancelReturnedDetailDialog = async () => {
  1436. returnedDetailDialog.value = false
  1437. initDetail()
  1438. }
  1439. const addDetails = async () => {
  1440. const isCheck = checkUploadImages()
  1441. if (!isCheck) {
  1442. returnedDetailDialog.value = true
  1443. return false
  1444. }
  1445. const index = selectDetailIndex.value
  1446. const detail = JSON.parse(JSON.stringify(selectDetail.value))
  1447. if (index > -1) {
  1448. params.value.details[index] = detail
  1449. initDetail()
  1450. return true
  1451. }
  1452. const {
  1453. sku: detail_sku,
  1454. batchNumber: detail_batch_number,
  1455. validityTime: detail_validity_time,
  1456. manufactureTime: detail_manufacture_time,
  1457. qualityStatus: detail_quality_status,
  1458. number: detailNumber
  1459. } = selectDetail.value
  1460. if (['次品', '待修复'].includes(detail_quality_status)) {
  1461. params.value.details.push(detail)
  1462. initDetail()
  1463. return
  1464. }
  1465. const searchIndex = params.value.details.findIndex((item) => {
  1466. const { sku, batchNumber, qualityStatus, validityTime, manufactureTime } =
  1467. item
  1468. return (
  1469. sku === detail_sku &&
  1470. batchNumber === detail_batch_number &&
  1471. detail_validity_time === validityTime &&
  1472. detail_manufacture_time === manufactureTime &&
  1473. detail_quality_status === qualityStatus
  1474. )
  1475. })
  1476. if (searchIndex > -1) {
  1477. showConfirmDialog({
  1478. title: '系统提示',
  1479. message: '查询到有对应的商品详情是否进行合并'
  1480. })
  1481. .then(() => {
  1482. const { number } = params.value.details[searchIndex]
  1483. params.value.details[searchIndex].number =
  1484. Number(detailNumber) + Number(number)
  1485. showNotify({ type: 'success', message: '合并成功' })
  1486. })
  1487. .catch(() => {
  1488. params.value.details.push(detail)
  1489. showNotify({ type: 'success', message: '添加成功' })
  1490. })
  1491. } else {
  1492. params.value.details.push(detail)
  1493. }
  1494. initDetail()
  1495. return true
  1496. }
  1497. // 删除对应的详情
  1498. function removeDetails(index) {
  1499. if (null === index) {
  1500. showFailToast('未找到对应的详情')
  1501. return
  1502. }
  1503. showConfirmDialog({
  1504. title: '系统提示',
  1505. message: '是否删除当前登记信息'
  1506. })
  1507. .then(() => {
  1508. const { id } = params.value.details[index]
  1509. if (!id) {
  1510. params.value.details.splice(index, 1)
  1511. showNotify({ type: 'success', message: '删除成功' })
  1512. } else {
  1513. deleteDetails(id).then((res) => {
  1514. if (res.data) {
  1515. params.value.details.splice(index, 1)
  1516. showNotify({ type: 'success', message: '删除成功' })
  1517. } else {
  1518. showNotify({ type: 'danger', message: '删除失败' })
  1519. }
  1520. })
  1521. }
  1522. })
  1523. .catch(() => {
  1524. showNotify({ type: 'primary', message: '取消删除' })
  1525. })
  1526. }
  1527. function selectOwner({ selectedValues }) {
  1528. const ownerCode = selectedValues[0]
  1529. params.value.ownerCode = ownerCode
  1530. showOwnerSelect.value = false
  1531. ownerSelectedOptions.value = []
  1532. getStoreOptionsBy(ownerCode)
  1533. queryOwnerBarcode(params.value.ownerCode, scancode.value)
  1534. }
  1535. const ownerRef = ref(null)
  1536. function showOwnerSelectFunc() {
  1537. ownerRef.value?.show(1)
  1538. }
  1539. // 指定货主
  1540. const onOwner = (item, type) => {
  1541. params.value.ownerCode = item.code
  1542. getStoreOptionsBy(item.code)
  1543. }
  1544. const showOwnerSelect = ref(false)
  1545. const ownerSelectedOptions = ref([])
  1546. // 多货主选择
  1547. function showOwnerSelectDialog(items) {
  1548. if (!items || items.length === 0) {
  1549. ownerSelectedOptions.value = owners.value.map((item) => {
  1550. JSON.parse(JSON.stringify(item))
  1551. })
  1552. showOwnerSelect.value = true
  1553. return
  1554. }
  1555. ownerSelectedOptions.value = owners.value
  1556. .filter((item) => {
  1557. const { value } = item
  1558. return items.includes(value)
  1559. })
  1560. .map((item) => JSON.parse(JSON.stringify(item)))
  1561. showOwnerSelect.value = true
  1562. }
  1563. // 校验日期
  1564. async function checkValidateDate(ownerCode, sku, newDate, fieldName) {
  1565. if (['NOSKU', 'NOBARCODE'].includes(sku)) {
  1566. return true
  1567. }
  1568. const type = fieldName === 'manufactureTime' ? '生产日期' : '失效日期'
  1569. try {
  1570. await validateDate({
  1571. newDate: newDate,
  1572. fieldName: fieldName,
  1573. ownerCode: ownerCode,
  1574. sku: sku
  1575. })
  1576. showNotify({
  1577. type: 'success',
  1578. message: `${type} 状态校验成功 `
  1579. })
  1580. return true
  1581. } catch (error) {
  1582. showNotify({
  1583. type: 'danger',
  1584. message: `校验${type}出现异常`
  1585. })
  1586. return false
  1587. }
  1588. }
  1589. function blobToBase64(blob) {
  1590. return new Promise((resolve, reject) => {
  1591. const fileReader = new FileReader()
  1592. fileReader.onload = (e) => {
  1593. resolve(e.target.result)
  1594. }
  1595. fileReader.readAsDataURL(blob)
  1596. fileReader.onerror = () => {
  1597. reject(new Error('blobToBase64 error'))
  1598. }
  1599. })
  1600. }
  1601. // 编辑等级详情
  1602. function editDetails(index) {
  1603. selectDetailIndex.value = index
  1604. const { details } = params.value
  1605. selectDetail.value = JSON.parse(JSON.stringify(details[index]))
  1606. returnedDetailDialog.value = true
  1607. }
  1608. // 写入当前商品详情
  1609. function pushImageItem(event, type) {
  1610. const file = event.target.files[0]
  1611. if (!file) return
  1612. if (!file.type.startsWith('image/')) {
  1613. alert('请选择图片文件!')
  1614. return
  1615. }
  1616. const filename = file.name
  1617. const item = {
  1618. src: URL.createObjectURL(file),
  1619. file: file,
  1620. fileName: filename,
  1621. type
  1622. }
  1623. if (type === '外箱图') {
  1624. if (!selectDetail.value.boxPhotos) {
  1625. selectDetail.value.boxPhotos = []
  1626. }
  1627. blobToBase64(file).then((_) => {
  1628. const some = boxFiles.value.some((item) => {
  1629. return filename === item.fileName
  1630. })
  1631. if (some) {
  1632. showFailToast('录入重复图片')
  1633. } else {
  1634. boxFiles.value.push(item)
  1635. selectDetail.value.boxPhotos.push(filename)
  1636. }
  1637. })
  1638. } else if (type === '内物图') {
  1639. if (!selectDetail.value.productPhotos) {
  1640. selectDetail.value.productPhotos = []
  1641. }
  1642. blobToBase64(file).then((_) => {
  1643. const some = productFiles.value.some((item) => {
  1644. return filename === item.fileName
  1645. })
  1646. if (some) {
  1647. showFailToast('录入重复图片')
  1648. } else {
  1649. productFiles.value.push(item)
  1650. selectDetail.value.productPhotos.push(filename)
  1651. }
  1652. })
  1653. }
  1654. }
  1655. // 触发拍照
  1656. function invokeCameraToCapture(type) {
  1657. const { sku } = selectDetail.value
  1658. if (!sku) {
  1659. showFailToast('当前没有可录入的商品信息')
  1660. return
  1661. }
  1662. if (type === '外箱图') {
  1663. document.getElementById('outer-carton-box-input').click()
  1664. } else if (type === '内物图') {
  1665. document.getElementById('inner-contents-input').click()
  1666. }
  1667. }
  1668. function getImageUrl(file_name, type) {
  1669. let list = []
  1670. if (type === '外箱图') {
  1671. list = boxFiles.value
  1672. } else if (type === '内物图') {
  1673. list = productFiles.value
  1674. }
  1675. for (let i = 0; i < list.length; i++) {
  1676. const item = list[i]
  1677. const { src, fileName } = item
  1678. if (file_name === fileName) {
  1679. return src
  1680. }
  1681. }
  1682. return null
  1683. }
  1684. // 外箱图事件
  1685. function outerCartonInput(event) {
  1686. pushImageItem(event, '外箱图')
  1687. }
  1688. // 内物图异常
  1689. function innerContentsInput(event) {
  1690. pushImageItem(event, '内物图')
  1691. }
  1692. function submit() {
  1693. const { ownerCode, logisticsName, returnNo, warehouseCode } = params.value
  1694. if (!ownerCode || ownerCode.length === 0) {
  1695. showNotify({
  1696. type: 'warning',
  1697. message: '没有对应的退件详情!请录入对应的退件详情'
  1698. })
  1699. return
  1700. } else if (!logisticsName || logisticsName.length === 0) {
  1701. showNotify({
  1702. type: 'warning',
  1703. message: '请录入承运商'
  1704. })
  1705. return
  1706. } else if (!returnNo || ownerCode.length === 0) {
  1707. showNotify({
  1708. type: 'warning',
  1709. message: '请录入退件单号'
  1710. })
  1711. return
  1712. } else if (!warehouseCode || warehouseCode.length === 0) {
  1713. showNotify({
  1714. type: 'warning',
  1715. message: '请选择登记仓库'
  1716. })
  1717. return
  1718. }
  1719. console.log('checkDetailNumber')
  1720. if (checkDetailNumber()) {
  1721. return
  1722. }
  1723. const formData = new FormData()
  1724. const boxItems = boxFiles && boxFiles.value ? boxFiles.value : []
  1725. const productItems =
  1726. productFiles && productFiles.value ? productFiles.value : []
  1727. const files = [...boxItems, ...productItems]
  1728. const filenames = []
  1729. if (files && files.length > 0) {
  1730. files.forEach((item) => {
  1731. const { file, fileName } = item
  1732. if (!filenames.includes(fileName)) {
  1733. formData.append('files', file)
  1734. filenames.push(fileName)
  1735. }
  1736. })
  1737. }
  1738. handleDetails(params.value.details)
  1739. formData.append('body', JSON.stringify(JSON.parse(JSON.stringify(params.value))))
  1740. console.log(formData)
  1741. showLoadingToast({
  1742. duration: 0,
  1743. forbidClick: true,
  1744. message: '提交登记中.....'
  1745. })
  1746. register(formData)
  1747. .then((res) => {
  1748. const { data } = res
  1749. const { accumulateTaskMap } = data
  1750. closeToast()
  1751. if (data) {
  1752. if (accumulateTaskMap && accumulateTaskMap.length > 0) {
  1753. let messages = []
  1754. const ownerName = getOwnerName(params.value.ownerCode)
  1755. for (let i = 0; i < accumulateTaskMap.length; i++) {
  1756. const { quality, taskCode } = accumulateTaskMap[i]
  1757. messages.push(`进入${ownerName}新的${quality}攒单任务号${taskCode}`)
  1758. }
  1759. showConfirmDialog({
  1760. title: '提交成功',
  1761. message: messages.join('\r\n'),
  1762. theme: 'round-button'
  1763. })
  1764. } else {
  1765. showNotify({
  1766. type: 'success',
  1767. message: '成功提交'
  1768. })
  1769. }
  1770. init()
  1771. params.value.ownerCode = ownerCode
  1772. }
  1773. })
  1774. .catch(() => {
  1775. closeToast()
  1776. })
  1777. }
  1778. function checkDetailNumber() {
  1779. const { details } = params.value
  1780. if (!details || details.length === 0) {
  1781. showNotify({
  1782. type: 'warning',
  1783. message: '登记详情不能为空'
  1784. })
  1785. return true
  1786. }
  1787. const check = details.some((item) => {
  1788. const { number } = item
  1789. return Number.isNaN(number) || Number(number) <= 0
  1790. })
  1791. if (check) {
  1792. showNotify({
  1793. type: 'warning',
  1794. message: '登记数量不能为 0 和 空 '
  1795. })
  1796. }
  1797. return check
  1798. }
  1799. function handleDetails(details) {
  1800. for (let i = 0; i < details.length; i++) {
  1801. const { qualityStatus } = details[i]
  1802. const { isGenuineValue, qualityMark } = getStatus(qualityStatus)
  1803. details[i].isGenuine = isGenuineValue
  1804. details[i].qualityMark = qualityMark
  1805. }
  1806. }
  1807. const showPhotosPreview = ref(false)
  1808. const startPhotosPosition = ref(0)
  1809. const showPhotosImagePreview = (index) => {
  1810. startPhotosPosition.value = index
  1811. showPhotosPreview.value = true
  1812. }
  1813. const onPhotosPreviewChange = (index) => {
  1814. startPhotosPosition.value = index
  1815. }
  1816. const handlePhotosDelete = () => {
  1817. showConfirmDialog({
  1818. title: '提示',
  1819. message: '确定要删除这张图片吗?'
  1820. })
  1821. .then(() => {
  1822. selectDetail.value.productPhotos.splice(startPhotosPosition.value, 1)
  1823. showNotify({ type: 'success', message: '删除成功' })
  1824. if (selectDetail.value.productPhotos.length === 0) {
  1825. showPhotosPreview.value = false
  1826. } else if (
  1827. startPhotosPosition.value >= selectDetail.value.productPhotos.length
  1828. ) {
  1829. startPhotosPosition.value = selectDetail.value.productPhotos.length - 1
  1830. }
  1831. })
  1832. .catch(() => {
  1833. showNotify({ type: 'primary', message: '取消删除' })
  1834. })
  1835. }
  1836. const showBoxPreview = ref(false)
  1837. const startBoxPosition = ref(0)
  1838. const showBoxImagePreview = (index) => {
  1839. startBoxPosition.value = index
  1840. showBoxPreview.value = true
  1841. }
  1842. const onBoxPreviewChange = (index) => {
  1843. startBoxPosition.value = index
  1844. }
  1845. const handleBoxDelete = () => {
  1846. showConfirmDialog({
  1847. title: '提示',
  1848. message: '确定要删除这张图片吗?'
  1849. })
  1850. .then(() => {
  1851. selectDetail.value.boxPhotos.splice(startBoxPosition.value, 1)
  1852. showNotify({ type: 'success', message: '删除成功' })
  1853. if (selectDetail.value.boxPhotos.length === 0) {
  1854. showBoxPreview.value = false
  1855. } else if (
  1856. startBoxPosition.value >= selectDetail.value.boxPhotos.length
  1857. ) {
  1858. startBoxPosition.value = selectDetail.value.boxPhotos.length - 1
  1859. }
  1860. })
  1861. .catch(() => {
  1862. showNotify({ type: 'primary', message: '取消删除' })
  1863. })
  1864. }
  1865. const detailBoxImages = computed(() => {
  1866. const items = selectDetail.value.boxPhotos
  1867. if (!items || items.length === 0) {
  1868. return []
  1869. }
  1870. return boxFiles.value
  1871. .filter((item) => {
  1872. return items.includes(item.fileName)
  1873. })
  1874. .map((item) => item.src)
  1875. })
  1876. const detailProductImages = computed(() => {
  1877. const items = selectDetail.value.productPhotos
  1878. if (!items || items.length === 0) {
  1879. return []
  1880. }
  1881. return productFiles.value
  1882. .filter((item) => {
  1883. return items.includes(item.fileName)
  1884. })
  1885. .map((item) => item.src)
  1886. })
  1887. // 校验是否上次图片
  1888. function checkUploadImages() {
  1889. const status = ['次品', '待修复']
  1890. const { productPhotos, boxPhotos, qualityStatus } = selectDetail.value
  1891. if (!status.includes(qualityStatus)) {
  1892. return true
  1893. }
  1894. if (!qualityStatus) {
  1895. return true
  1896. }
  1897. const boxPhotoIsNull = !boxPhotos || boxPhotos.length === 0
  1898. const productPhotoIsNull = !productPhotos || productPhotos.length === 0
  1899. if (boxPhotoIsNull) {
  1900. showNotify({
  1901. type: 'warning',
  1902. message: '请传入对应的外箱图'
  1903. })
  1904. return false
  1905. }
  1906. if (productPhotoIsNull) {
  1907. showNotify({
  1908. type: 'warning',
  1909. message: '请传入对应的内物图'
  1910. })
  1911. return false
  1912. }
  1913. return true
  1914. }
  1915. window.onRefresh = async () => {
  1916. console.log('window.onRefresh')
  1917. }
  1918. function clearImageFileCache() {
  1919. if (productFiles.value && productFiles.value.length > 0) {
  1920. productFiles.value
  1921. .map((item) => item.src)
  1922. .forEach((item) => URL.revokeObjectURL(item))
  1923. }
  1924. if (boxFiles.value && boxFiles.value.length > 0) {
  1925. boxFiles.value
  1926. .map((item) => item.src)
  1927. .forEach((item) => URL.revokeObjectURL(item))
  1928. }
  1929. }
  1930. function previewImages(filenames, index, type) {
  1931. let list = []
  1932. if (type === '外箱图') {
  1933. list = boxFiles.value
  1934. } else if (type === '内物图') {
  1935. list = productFiles.value
  1936. }
  1937. const images = []
  1938. for (let i = 0; i < list.length; i++) {
  1939. const item = list[i]
  1940. const { src, fileName } = item
  1941. if (filenames.includes(fileName)) {
  1942. images.push(src)
  1943. }
  1944. }
  1945. if (!images || images.length === 0) {
  1946. return
  1947. }
  1948. showImagePreview({
  1949. images: images,
  1950. startPosition: index
  1951. })
  1952. }
  1953. </script>
  1954. <style scoped lang="sass">
  1955. .van-nav-bar
  1956. .left-btn
  1957. color: #fff
  1958. height: 46px
  1959. padding-right: 20px
  1960. line-height: 46px
  1961. .right-btn
  1962. color: #fff
  1963. .container
  1964. .init-container
  1965. width: 100%
  1966. .scan-returned-content
  1967. padding: 10px
  1968. .input-group
  1969. padding: 5px
  1970. .button-group
  1971. padding: 5px
  1972. .content
  1973. width: 100%
  1974. .scan-returned-no
  1975. align-items: center
  1976. padding: 15px
  1977. .returned-detail-list
  1978. .card-div
  1979. background: #fff
  1980. border-radius: 12px
  1981. overflow: hidden
  1982. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05)
  1983. margin: 5px 0
  1984. padding: 5px 0
  1985. .card-div-content
  1986. padding: 3px
  1987. .info-row
  1988. display: flex
  1989. margin-bottom: 12px
  1990. font-size: 12px
  1991. .info-label
  1992. width: 100px
  1993. color: #969799
  1994. .info-value
  1995. flex: 1
  1996. color: #333333
  1997. font-weight: 500
  1998. .card-div-footer
  1999. padding: 5px
  2000. background: #fafafa
  2001. border-top: 1px solid #f5f5f5
  2002. color: #646566
  2003. font-size: 14px
  2004. line-height: 1.5
  2005. .card-div-footer-images
  2006. padding: 5px
  2007. background: #fafafa
  2008. border-top: 1px solid #f5f5f5
  2009. color: #646566
  2010. font-size: 14px
  2011. line-height: 1.5
  2012. .card-div-footer-options
  2013. background: #fafafa
  2014. border-top: 1px solid #f5f5f5
  2015. color: #646566
  2016. font-size: 14px
  2017. .options-row
  2018. display: flex
  2019. font-size: 12px
  2020. .info-label
  2021. width: 100px
  2022. color: #969799
  2023. .info-value
  2024. flex: 1
  2025. color: #333333
  2026. font-weight: 500
  2027. .returned-details
  2028. .van-field
  2029. padding: 0
  2030. </style>