index.vue 89 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351
  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="scan-returned-content with-fixed-notice">
  20. <div class="input-group">
  21. <van-field
  22. ref="scanExpressNoInputRef"
  23. autofocus
  24. v-model="expressNo"
  25. autocomplete="off"
  26. placeholder="输入快递单号"
  27. clearable
  28. @keyup.enter="inputExpressNo"
  29. >
  30. </van-field>
  31. </div>
  32. <div class="button-group">
  33. <van-button
  34. @click="inputExpressNo"
  35. style="width: 100%"
  36. type="primary"
  37. class="confirm-btn"
  38. >确认
  39. </van-button>
  40. </div>
  41. </div>
  42. </div>
  43. <div v-else>
  44. <div class="content-tips fixed-notice">
  45. <van-notice-bar color="#1989fa" background="#ecf9ff"
  46. >{{ ownerQualityInspection }}
  47. </van-notice-bar>
  48. </div>
  49. <!-- 退货信息区域 -->
  50. <div class="return-info-section with-fixed-notice">
  51. <div class="section-header">
  52. <van-icon name="orders-o" size="18" />
  53. <span class="section-title">退货信息</span>
  54. </div>
  55. <van-cell-group inset style="margin-top: 10px">
  56. <van-field
  57. v-model="params.returnNo"
  58. label="快递单号"
  59. readonly
  60. placeholder="请输入快递单号"
  61. />
  62. <van-field
  63. readonly
  64. clickable
  65. label="承运商"
  66. placeholder="选择承运商"
  67. :model-value="getLogisticName()"
  68. @click="logisticPickerShow = true"
  69. />
  70. <van-popup
  71. v-model:show="logisticPickerShow"
  72. position="bottom"
  73. destroy-on-close
  74. >
  75. <van-picker
  76. :columns="logistics"
  77. @cancel="logisticPickerShow = false"
  78. @confirm="selectedLogistic"
  79. />
  80. </van-popup>
  81. <van-field
  82. readonly
  83. clickable
  84. label="仓库"
  85. placeholder="选择仓库"
  86. :model-value="getWarehouseName()"
  87. @click="warehousePickerShow = true"
  88. />
  89. <van-popup
  90. v-model:show="warehousePickerShow"
  91. position="bottom"
  92. destroy-on-close
  93. >
  94. <van-picker
  95. :columns="warehouses"
  96. @cancel="warehousePickerShow = false"
  97. @confirm="selectedWarehouse"
  98. />
  99. </van-popup>
  100. <van-field
  101. readonly
  102. clickable
  103. label="货主"
  104. placeholder="选择货主"
  105. :model-value="getOwnerName(params.ownerCode)"
  106. @click="showOwnerSelectFunc"
  107. />
  108. <van-field
  109. readonly
  110. clickable
  111. label="店铺"
  112. placeholder="选择店铺"
  113. v-model="params.storeName"
  114. @click="storePickerShow = true"
  115. />
  116. <van-popup
  117. v-model:show="storePickerShow"
  118. position="bottom"
  119. destroy-on-close
  120. >
  121. <van-picker
  122. :columns="storeOptions"
  123. @cancel="storePickerShow = false"
  124. @confirm="selectedStore"
  125. />
  126. </van-popup>
  127. <van-field
  128. v-model="params.remark"
  129. rows="1"
  130. autosize
  131. label="备注"
  132. type="textarea"
  133. placeholder="备注"
  134. />
  135. </van-cell-group>
  136. </div>
  137. <!-- 商品信息区域 -->
  138. <div class="product-info-section">
  139. <div class="section-header">
  140. <van-icon name="goods-collect-o" size="18" />
  141. <span class="section-title">商品信息</span>
  142. </div>
  143. <!-- 商品条码扫描输入框 -->
  144. <van-cell-group inset style="margin-top: 10px">
  145. <van-field
  146. ref="productTabScancodeInputRef"
  147. v-model="scancode"
  148. label="商品条码"
  149. autocomplete="off"
  150. placeholder="扫描或输入商品条码"
  151. clearable
  152. @keydown.enter="showQualityStatus"
  153. />
  154. </van-cell-group>
  155. <div class="returned-detail-list">
  156. <template v-if="!params.details || params.details.length === 0">
  157. <van-empty description="暂无信息请进行录入" />
  158. </template>
  159. <template v-for="(item, index) in params.details" :key="`detail-${item.sku || 'unknown'}-${item.barCode || 'nocode'}-${index}`">
  160. <div class="card-div">
  161. <div class="card-div-content">
  162. <div class="info-row">
  163. <div class="info-label">sku</div>
  164. <div class="info-value">{{ item.sku }}</div>
  165. <div class="info-label">质量状态</div>
  166. <div class="info-value">
  167. <van-tag :color="getTagColor(item.qualityStatus)">
  168. {{ item.qualityStatus }}
  169. </van-tag>
  170. </div>
  171. </div>
  172. <div class="info-row">
  173. <div class="info-label">商品编号</div>
  174. <div class="info-value">{{ item.barCode }}</div>
  175. <div class="info-label">商品名称</div>
  176. <div class="info-value">
  177. <van-text-ellipsis
  178. :content="item.tradeName"
  179. rows="1"
  180. expand-text="展开"
  181. collapse-text="收起"
  182. />
  183. </div>
  184. </div>
  185. <div class="info-row">
  186. <div class="info-label">生产日期</div>
  187. <div class="info-value">
  188. {{ item.manufactureTime }}
  189. </div>
  190. <div class="info-label">失效日期</div>
  191. <div class="info-value">{{ item.validityTime }}</div>
  192. </div>
  193. <div class="info-row">
  194. <div class="info-label">批次号</div>
  195. <div class="info-value">{{ item.batchNumber }}</div>
  196. <div class="info-label">数量</div>
  197. <div class="info-value">{{ item.number }}</div>
  198. </div>
  199. </div>
  200. <div class="card-div-footer">
  201. <div class="product-description">
  202. {{ item.remark }}
  203. </div>
  204. </div>
  205. <template v-if="hasBoxItems(item)">
  206. <div>
  207. <van-divider content-position="left">外箱图</van-divider>
  208. <van-row>
  209. <template v-for="(i, imgIndex) in getBoxItems(item)" :key="`box-${item.sku || 'unknown'}-${index}-${imgIndex}-${i.fileName || 'noname'}`">
  210. <van-col span="4">
  211. <van-image
  212. :key="`box-photos-${index}-${imgIndex}`"
  213. @click="
  214. previewImages(item.boxPhotos, imgIndex, '外箱图')
  215. "
  216. width="100%"
  217. height="50"
  218. :src="i.src"
  219. />
  220. </van-col>
  221. </template>
  222. </van-row>
  223. </div>
  224. </template>
  225. <template v-if="hasProductItems(item)">
  226. <div>
  227. <van-divider content-position="left">内物图</van-divider>
  228. <van-row>
  229. <template v-for="(i, imgIndex) in getProductItems(item)" :key="`product-${item.sku || 'unknown'}-${index}-${imgIndex}-${i.fileName || 'noname'}`">
  230. <van-col span="4">
  231. <van-image
  232. :key="`product-photos-${index}-${imgIndex}`"
  233. @click="
  234. previewImages(
  235. item.productPhotos,
  236. imgIndex,
  237. '内物图',
  238. )
  239. "
  240. width="100%"
  241. height="60"
  242. :src="i.src"
  243. />
  244. </van-col>
  245. </template>
  246. </van-row>
  247. </div>
  248. </template>
  249. <div class="card-div-footer-options">
  250. <div class="options-row">
  251. <div class="info-value">
  252. <van-button
  253. size="mini"
  254. type="danger"
  255. block
  256. plain
  257. @click="removeDetails(index)"
  258. >删除
  259. </van-button>
  260. </div>
  261. <div class="info-value">
  262. <van-button
  263. size="mini"
  264. type="default"
  265. block
  266. plain
  267. @click="editDetails(index)"
  268. >编辑
  269. </van-button>
  270. </div>
  271. </div>
  272. </div>
  273. </div>
  274. </template>
  275. </div>
  276. </div>
  277. <!-- 提交按钮区域 -->
  278. <div class="submit-section">
  279. <van-cell-group inset style="margin-top: 20px; margin-bottom: 50px">
  280. <van-button type="primary" block @click="submit">提交</van-button>
  281. </van-cell-group>
  282. </div>
  283. </div>
  284. </div>
  285. <van-dialog
  286. v-model:show="qualityStatusDialog"
  287. title="质量状态"
  288. @confirm="queryBarcode"
  289. show-cancel-button
  290. >
  291. <van-radio-group v-model="qualityStatus">
  292. <template v-for="(item, index) in qualityStatusOptions" :key="`quality-${index}`">
  293. <van-cell
  294. :title="item.text"
  295. clickable
  296. @click="qualityStatus = item.value"
  297. >
  298. <template #right-icon>
  299. <van-radio :name="item.value" />
  300. </template>
  301. </van-cell>
  302. </template>
  303. </van-radio-group>
  304. </van-dialog>
  305. <van-dialog
  306. v-model:show="returnedDetailDialog"
  307. title="商品详情"
  308. show-cancel-button
  309. :lazy-render="true"
  310. :show-confirm-button="checkUploadImages()"
  311. @confirm="addDetails"
  312. @cancel="cancelReturnedDetailDialog"
  313. class="compact-dialog"
  314. >
  315. <div class="dialog-content">
  316. <div class="field-group">
  317. <van-field
  318. v-model="selectDetail.sku"
  319. label="SKU"
  320. placeholder="SKU"
  321. clearable
  322. class="compact-field"
  323. />
  324. <van-field
  325. readonly
  326. v-model="selectDetail.barCode"
  327. label="商品条码"
  328. placeholder="商品条码"
  329. clearable
  330. class="compact-field"
  331. />
  332. <van-field
  333. v-model="selectDetail.tradeName"
  334. label="商品名称"
  335. placeholder="商品名称"
  336. readonly
  337. class="compact-field"
  338. />
  339. </div>
  340. <div class="field-group">
  341. <van-field
  342. readonly
  343. clickable
  344. label="质量状态"
  345. placeholder="质量状态"
  346. v-model="selectDetail.qualityStatus"
  347. @click="selectedDetailQualityStatus = true"
  348. class="compact-field"
  349. />
  350. </div>
  351. <van-popup
  352. v-model:show="selectedDetailQualityStatus"
  353. position="bottom"
  354. destroy-on-close
  355. >
  356. <van-picker
  357. :columns="qualityStatusOptions"
  358. @cancel="selectedDetailQualityStatus = false"
  359. @confirm="selectedDetailQualityStatusFunc"
  360. />
  361. </van-popup>
  362. <div class="field-group date-fields">
  363. <van-field
  364. is-link
  365. readonly
  366. name="datePicker"
  367. label="生产日期"
  368. :placeholder="selectDetail.manufactureTime ? '' : '请选择生产日期'"
  369. :model-value="formatDateDisplay(selectDetail.manufactureTime)"
  370. @click="showManufactureTime = true"
  371. class="compact-field date-field"
  372. />
  373. <van-popup
  374. v-model:show="showManufactureTime"
  375. destroy-on-close
  376. position="bottom"
  377. >
  378. <van-date-picker
  379. :max-date="maxDate"
  380. :min-date="minDate"
  381. :model-value="parseDateValue(selectDetail.manufactureTime)"
  382. @confirm="manufactureTimeConfirm"
  383. @cancel="showManufactureTime = false"
  384. />
  385. </van-popup>
  386. <van-field
  387. :model-value="formatDateDisplay(selectDetail.validityTime)"
  388. is-link
  389. readonly
  390. name="datePicker"
  391. label="失效日期"
  392. :placeholder="selectDetail.validityTime ? '' : '请选择失效日期'"
  393. @click="showValidityTime = true"
  394. class="compact-field date-field"
  395. />
  396. </div>
  397. <van-popup
  398. v-model:show="showValidityTime"
  399. destroy-on-close
  400. position="bottom"
  401. >
  402. <van-date-picker
  403. :max-date="maxDate"
  404. :min-date="minDate"
  405. :model-value="parseDateValue(selectDetail.validityTime)"
  406. @confirm="validityTimeConfirm"
  407. @cancel="showValidityTime = false"
  408. />
  409. </van-popup>
  410. <div class="field-group">
  411. <van-field
  412. autocomplete="off"
  413. v-model="selectDetail.batchNumber"
  414. label="批次号"
  415. placeholder="批次号"
  416. clearable
  417. class="compact-field"
  418. />
  419. <van-field
  420. v-model="selectDetail.number"
  421. label="数量"
  422. type="digit"
  423. placeholder="数量"
  424. clearable
  425. class="compact-field"
  426. />
  427. <van-field
  428. v-model="selectDetail.remark"
  429. label="备注"
  430. placeholder="备注"
  431. label-align="top"
  432. class="compact-field"
  433. />
  434. </div>
  435. <div v-if="showAccessories" class="accessories-section">
  436. <van-divider content-position="left" class="compact-divider">配件信息</van-divider>
  437. <div class="accessories-content">
  438. <template v-for="(item, index) in accessories" :key="`accessory-${index}`">
  439. <div class="accessory-item">
  440. 配件条码: [<span class="accessory-highlight">{{
  441. item.accessory
  442. }}</span
  443. >] [<span class="accessory-desc">{{ item.descrC }}</span
  444. >] 数量: [<span class="accessory-highlight">{{ item.qty }}</span
  445. >]件
  446. </div>
  447. </template>
  448. <div class="accessory-notice">
  449. 请检查商品: 【<span class="accessory-warning">{{
  450. selectDetail.sku
  451. }}</span
  452. >】 {{ selectDetail.tradeName }} 配件
  453. </div>
  454. </div>
  455. </div>
  456. <div v-if="selectDetail.boxPhotos && selectDetail.boxPhotos.length > 0" class="photo-section">
  457. <van-divider content-position="left" class="compact-divider">外箱图</van-divider>
  458. <div class="photo-grid">
  459. <template v-for="(item, index) in selectDetail.boxPhotos" :key="`detail-box-${index}`">
  460. <div class="photo-item">
  461. <van-image
  462. :key="`box-photos-${index}`"
  463. width="100%"
  464. height="40"
  465. :src="getImageUrl(item, '外箱图')"
  466. @click="showBoxImagePreview(index)"
  467. class="compact-image"
  468. />
  469. </div>
  470. </template>
  471. </div>
  472. <van-image-preview
  473. v-model:show="showBoxPreview"
  474. :images="detailBoxImages"
  475. :start-position="startBoxPosition"
  476. @change="onBoxPreviewChange"
  477. closeable
  478. >
  479. <template #index>
  480. <div class="custom-toolbar">
  481. <span
  482. >{{ startPhotosPosition + 1 }}/{{
  483. selectDetail.boxPhotos.length
  484. }}</span
  485. >
  486. <van-button
  487. icon="delete"
  488. type="danger"
  489. @click.stop="handleBoxDelete"
  490. size="mini"
  491. >删除
  492. </van-button>
  493. </div>
  494. </template>
  495. </van-image-preview>
  496. </div>
  497. <div v-if="selectDetail.productPhotos && selectDetail.productPhotos.length > 0" class="photo-section">
  498. <van-divider content-position="left" class="compact-divider">内物图</van-divider>
  499. <div class="photo-grid">
  500. <template v-for="(item, index) in selectDetail.productPhotos" :key="`detail-product-${index}`">
  501. <div class="photo-item">
  502. <van-image
  503. :key="`product-photos-${index}`"
  504. width="100%"
  505. height="40"
  506. :src="getImageUrl(item, '内物图')"
  507. @click="showPhotosImagePreview(index)"
  508. class="compact-image"
  509. />
  510. </div>
  511. </template>
  512. </div>
  513. <van-image-preview
  514. v-model:show="showPhotosPreview"
  515. :images="detailProductImages"
  516. :start-position="startPhotosPosition"
  517. @change="onPhotosPreviewChange"
  518. closeable
  519. >
  520. <template #index>
  521. <div class="custom-toolbar">
  522. <span>
  523. {{ startPhotosPosition + 1 }}/{{
  524. selectDetail.productPhotos.length
  525. }}
  526. </span>
  527. <van-button
  528. icon="delete"
  529. type="danger"
  530. @click.stop="handlePhotosDelete"
  531. size="mini"
  532. >删除
  533. </van-button>
  534. </div>
  535. </template>
  536. </van-image-preview>
  537. </div>
  538. <div class="camera-buttons">
  539. <van-button
  540. type="primary"
  541. class="camera-btn"
  542. @click="invokeCameraToCapture('外箱图')"
  543. size="small"
  544. >外箱图录入
  545. </van-button>
  546. <van-button
  547. type="primary"
  548. class="camera-btn"
  549. @click="invokeCameraToCapture('内物图')"
  550. size="small"
  551. >内物图录入
  552. </van-button>
  553. </div>
  554. <input
  555. type="file"
  556. id="outer-carton-box-input"
  557. capture="user"
  558. accept="image/*"
  559. hidden
  560. @change="outerCartonInput"
  561. />
  562. <input
  563. type="file"
  564. id="inner-contents-input"
  565. capture="user"
  566. accept="image/*"
  567. hidden
  568. @change="innerContentsInput"
  569. />
  570. </div>
  571. </van-dialog>
  572. <van-popup
  573. v-model:show="showOwnerSelect"
  574. destroy-on-close
  575. round
  576. position="bottom"
  577. >
  578. <van-picker
  579. :model-value="getOwnerName(params.ownerCode)"
  580. :columns="ownerSelectedOptions"
  581. @cancel="showOwnerSelect = false"
  582. @confirm="selectOwner"
  583. />
  584. </van-popup>
  585. <owner ref="ownerRef" @onOwner="onOwner" />
  586. <!-- 浮动按钮:仅在非初始页面显示 -->
  587. <van-floating-bubble
  588. v-show="!showInitialPage"
  589. icon="checked"
  590. @click="handleSubmitWithConfirm"
  591. class="submit-floating-btn"
  592. />
  593. </div>
  594. </template>
  595. <script setup>
  596. // ==================== Vue 核心导入 ====================
  597. import { ref, computed, nextTick, onMounted, watch } from 'vue'
  598. // ==================== Vant UI 组件导入 ====================
  599. import {
  600. showFailToast,
  601. showNotify,
  602. showLoadingToast,
  603. closeToast,
  604. showConfirmDialog,
  605. showImagePreview
  606. } from 'vant'
  607. // ==================== 工具类导入 ====================
  608. import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
  609. import { useStore } from '@/store/modules/user'
  610. import { getStatus } from '@/utils/returned.ts'
  611. // ==================== API 接口导入 ====================
  612. import {
  613. deleteDetails,
  614. getQualityInspection,
  615. getQualityInspectionBy,
  616. getQualityStatus,
  617. getReturnedByExpress,
  618. getTagColorBy,
  619. listAsn,
  620. matchAsnBy,
  621. matchCarrierCode,
  622. matchOrderBy,
  623. register,
  624. searchBarcode,
  625. searchOwnerBarcode,
  626. shops,
  627. validateDate
  628. } from '@/api/returned/index.ts'
  629. import { carrierOptions, getOwner, getWarehouse } from '@/api/basic/index.ts'
  630. // ==================== 组件导入 ====================
  631. import Owner from '@/components/Owner.vue'
  632. // ==================== 初始化配置 ====================
  633. try {
  634. getHeader()
  635. } catch (error) {
  636. console.log(error)
  637. }
  638. // ==================== 基础配置常量 ====================
  639. const store = useStore()
  640. const warehouse = store.warehouse
  641. // 日期范围配置
  642. const minDate = ref(new Date(new Date().getFullYear() - 5, 0, 1))
  643. const maxDate = ref(new Date((new Date().getFullYear() + 50, 0, 1)))
  644. // ==================== 页面状态管理 ====================
  645. // 页面显示控制
  646. const showInitialPage = ref(true) // 是否显示初始扫描页面
  647. const expressNo = ref(null) // 快递单号
  648. // 页面标题计算属性
  649. const title = computed(() => {
  650. if (expressNo.value && showInitialPage.value === false) {
  651. return '退货登记:' + params.value.returnNo
  652. }
  653. return '退货登记'
  654. })
  655. // ==================== 对话框状态管理 ====================
  656. // 选择器对话框状态
  657. const logisticPickerShow = ref(false) // 承运商选择器
  658. const ownerPickerShow = ref(false) // 货主选择器
  659. const warehousePickerShow = ref(false) // 仓库选择器
  660. const storePickerShow = ref(false) // 店铺选择器
  661. const showOwnerSelect = ref(false) // 多货主选择器
  662. // 业务对话框状态
  663. const qualityStatusDialog = ref(false) // 质量状态选择对话框
  664. const returnedDetailDialog = ref(false) // 商品详情对话框
  665. const selectedDetailQualityStatus = ref(false) // 详情中的质量状态选择器
  666. // 日期选择器状态
  667. const showManufactureTime = ref(false) // 生产日期选择器
  668. const showValidityTime = ref(false) // 失效日期选择器
  669. // 其他状态
  670. const showAccessories = ref(false) // 配件显示状态
  671. // 检测是否有任何对话框显示
  672. const hasAnyDialogVisible = computed(() => {
  673. return qualityStatusDialog.value ||
  674. returnedDetailDialog.value ||
  675. logisticPickerShow.value ||
  676. warehousePickerShow.value ||
  677. storePickerShow.value ||
  678. ownerPickerShow.value ||
  679. showOwnerSelect.value ||
  680. showManufactureTime.value ||
  681. showValidityTime.value ||
  682. selectedDetailQualityStatus.value ||
  683. showAccessories.value
  684. })
  685. // ==================== 数据存储 ====================
  686. // 基础数据选项
  687. const qualityStatusOptions = ref([]) // 质量状态选项
  688. const owners = ref([]) // 货主列表
  689. const warehouses = ref([]) // 仓库列表
  690. const logistics = ref([]) // 承运商列表
  691. const storeOptions = ref([]) // 店铺选项
  692. const ownerSelectedOptions = ref([]) // 多货主选择选项
  693. // 数据映射对象
  694. const ownerMap = ref({}) // 货主编码到名称的映射
  695. const warehousesMap = ref({}) // 仓库编码到名称的映射
  696. const logisticsMap = ref({}) // 承运商映射
  697. // 业务数据
  698. const asnList = ref({}) // ASN单据列表
  699. const accessories = ref([]) // 配件信息
  700. const selectDetailIndex = ref(-1) // 选中的详情索引
  701. // 图片文件存储
  702. const boxFiles = ref([]) // 外箱图文件列表
  703. const productFiles = ref([]) // 内物图文件列表
  704. // ==================== 表单数据 ====================
  705. // 扫码相关
  706. const scancode = ref('') // 扫描的商品条码
  707. const qualityStatus = ref('') // 商品质量状态
  708. // 质检信息
  709. const qualityInspection = ref(null) // 质检配置
  710. const ownerQualityInspection = computed(() => {
  711. if (!qualityInspection.value || !params.value.ownerCode) {
  712. return '暂无质检信息'
  713. }
  714. if ('原单退回' === params.value.originalNo) {
  715. return '原单退回:快速质检'
  716. }
  717. return getQualityInspectionBy(qualityInspection.value) || '暂无质检信息'
  718. })
  719. // 其他表单字段
  720. const ownerName = ref('') // 货主名称
  721. // 主要提交参数
  722. const params = ref({
  723. id: null,
  724. returnNo: null,
  725. warehouseCode: warehouse,
  726. logisticsName: null,
  727. storeName: null,
  728. upstreamNo: null,
  729. ownerCode: null,
  730. orderUpstream: null,
  731. orderUpSteam: null,
  732. arrivalPayment: null,
  733. buyerName: null,
  734. asnNo: null,
  735. buyerPhone: null,
  736. originalNo: null,
  737. remark: null,
  738. details: []
  739. })
  740. // 商品详情信息
  741. const selectDetail = ref({
  742. id: null,
  743. rejectHeadId: null,
  744. rejectPushTaskNo: null,
  745. status: null,
  746. detailStatus: null,
  747. isGenuine: null,
  748. qualityMark: null,
  749. sku: '',
  750. barCode: '',
  751. tradeName: '',
  752. qualityStatus: '',
  753. manufactureTime: '',
  754. validityTime: '',
  755. batchNumber: '',
  756. remark: '',
  757. asnNo: null,
  758. number: 0,
  759. warehouse: null,
  760. warehouseBin: null,
  761. boxPhotos: [],
  762. productPhotos: [],
  763. files: null,
  764. pieceTag: null,
  765. createTime: null,
  766. creatorId: null,
  767. updateTime: null,
  768. updaterId: null,
  769. deleteTime: null,
  770. version: null,
  771. repairableType: null,
  772. repairableTypes: null,
  773. operatorId: null,
  774. operatorName: null,
  775. operatorTime: null,
  776. skuImage: null,
  777. orginSkuImage: null
  778. })
  779. // ==================== 组件引用 ====================
  780. const scanExpressNoInputRef = ref(null) // 快递单号输入框引用
  781. const productTabScancodeInputRef = ref(null) // 商品信息tab下的扫码输入框引用
  782. const ownerRef = ref(null) // 货主组件引用
  783. // ==================== 图片预览相关状态 ====================
  784. const showPhotosPreview = ref(false) // 内物图预览状态
  785. const startPhotosPosition = ref(0) // 内物图预览起始位置
  786. const showBoxPreview = ref(false) // 外箱图预览状态
  787. const startBoxPosition = ref(0) // 外箱图预览起始位置
  788. // 图片预览计算属性
  789. const detailBoxImages = computed(() => {
  790. try {
  791. if (!selectDetail.value || !selectDetail.value.boxPhotos) {
  792. return []
  793. }
  794. const items = selectDetail.value.boxPhotos
  795. if (!Array.isArray(items) || items.length === 0) {
  796. return []
  797. }
  798. if (!Array.isArray(boxFiles.value) || boxFiles.value.length === 0) {
  799. return []
  800. }
  801. return boxFiles.value
  802. .filter((item) => {
  803. return item && item.fileName && items.includes(item.fileName)
  804. })
  805. .map((item) => item.src)
  806. .filter(Boolean) // 过滤掉可能的undefined值
  807. } catch (error) {
  808. console.error('detailBoxImages 计算错误:', error)
  809. return []
  810. }
  811. })
  812. const detailProductImages = computed(() => {
  813. try {
  814. if (!selectDetail.value || !selectDetail.value.productPhotos) {
  815. return []
  816. }
  817. const items = selectDetail.value.productPhotos
  818. if (!Array.isArray(items) || items.length === 0) {
  819. return []
  820. }
  821. if (!Array.isArray(productFiles.value) || productFiles.value.length === 0) {
  822. return []
  823. }
  824. return productFiles.value
  825. .filter((item) => {
  826. return item && item.fileName && items.includes(item.fileName)
  827. })
  828. .map((item) => item.src)
  829. .filter(Boolean) // 过滤掉可能的undefined值
  830. } catch (error) {
  831. console.error('detailProductImages 计算错误:', error)
  832. return []
  833. }
  834. })
  835. // ==================== 生命周期钩子 ====================
  836. onMounted(() => {
  837. // 初始化日期范围
  838. const now = new Date()
  839. const currentYear = now.getFullYear()
  840. const currentCentury = Math.floor(currentYear / 100) * 100
  841. const endOfCenturyYear = currentCentury + 99
  842. maxDate.value = new Date(endOfCenturyYear, 0, 1)
  843. minDate.value = new Date(currentCentury, 0, 1)
  844. // 初始化基础数据
  845. initializeBaseData()
  846. // 初始化时自动聚焦快递单号输入框
  847. nextTick(() => {
  848. focusExpressNoInput()
  849. })
  850. })
  851. // ==================== 智能聚焦逻辑 ====================
  852. // 聚焦快递单号输入框
  853. function focusExpressNoInput() {
  854. if (scanExpressNoInputRef.value && showInitialPage.value) {
  855. setTimeout(() => {
  856. const input = scanExpressNoInputRef.value?.$el?.querySelector('input')
  857. if (input) {
  858. input.focus()
  859. input.setSelectionRange(0, input.value.length)
  860. console.log('快递单号输入框已聚焦')
  861. }
  862. }, 150) // 增加延迟时间确保 DOM 完全渲染
  863. }
  864. }
  865. // 聚焦商品信息输入框
  866. function focusProductInput() {
  867. if (productTabScancodeInputRef.value && !showInitialPage.value) {
  868. setTimeout(() => {
  869. const input = productTabScancodeInputRef.value?.$el?.querySelector('input')
  870. if (input) {
  871. input.focus()
  872. input.setSelectionRange(0, input.value.length)
  873. console.log('商品条码输入框已聚焦')
  874. }
  875. }, 150) // 增加延迟时间确保DOM完全渲染
  876. }
  877. }
  878. // 监听对话框状态变化,当所有对话框关闭时自动聚焦
  879. watch(hasAnyDialogVisible, (newVal) => {
  880. if (!newVal) {
  881. nextTick(() => {
  882. if (showInitialPage.value) {
  883. focusExpressNoInput()
  884. } else {
  885. focusProductInput()
  886. }
  887. })
  888. }
  889. })
  890. // 监听页面显示状态变化
  891. watch(showInitialPage, (newVal) => {
  892. nextTick(() => {
  893. if (newVal) {
  894. // 切换到初始扫描页面时,聚焦快递单号输入框
  895. setTimeout(() => {
  896. focusExpressNoInput()
  897. }, 300)
  898. } else {
  899. // 页面从初始扫描状态切换到详情页面时,延迟聚焦商品输入框
  900. setTimeout(() => {
  901. focusProductInput()
  902. }, 300)
  903. }
  904. })
  905. })
  906. // 监听商品详情对话框关闭,自动聚焦到商品条码输入框
  907. watch(returnedDetailDialog, (newVal) => {
  908. if (!newVal && !showInitialPage.value) {
  909. nextTick(() => {
  910. focusProductInput()
  911. })
  912. }
  913. })
  914. // ==================== 初始化方法 ====================
  915. // 初始化基础数据
  916. async function initializeBaseData() {
  917. try {
  918. // 并行获取基础数据
  919. const [ownersRes, qualityStatusRes, warehousesRes, logisticsRes] = await Promise.all([
  920. getOwner(),
  921. getQualityStatus(),
  922. getWarehouse(),
  923. carrierOptions()
  924. ])
  925. // 处理货主数据
  926. const ownersData = ownersRes.data
  927. owners.value = ownersData.map((item) => ({ value: item.code, text: item.name }))
  928. const ownerMapping = {}
  929. ownersData.forEach((item) => {
  930. ownerMapping[item.code] = item.name
  931. })
  932. ownerMap.value = ownerMapping
  933. // 处理质量状态数据
  934. qualityStatusOptions.value = qualityStatusRes.data
  935. .filter((item) => item !== '机损')
  936. .map((item) => ({ value: item, text: item }))
  937. // 处理仓库数据
  938. const warehousesData = warehousesRes.data
  939. warehouses.value = warehousesData.map((item) => ({ value: item.code, text: item.name }))
  940. const warehouseMapping = {}
  941. warehousesData.forEach((item) => {
  942. warehouseMapping[item.code] = item.name
  943. })
  944. warehousesMap.value = warehouseMapping
  945. // 处理承运商数据
  946. const logisticsData = logisticsRes.data
  947. logistics.value = logisticsData.map((item) => ({ value: item.name, text: item.name }))
  948. const logisticsMapping = {}
  949. logisticsData.forEach((item) => {
  950. logisticsMapping[item.name] = item.name
  951. })
  952. logisticsMap.value = logisticsMapping
  953. } catch (error) {
  954. console.error('初始化基础数据失败:', error)
  955. showFailToast('初始化数据失败,请刷新页面重试')
  956. }
  957. }
  958. // 初始化商品详情
  959. function initDetail() {
  960. selectDetail.value = {
  961. id: null,
  962. rejectHeadId: null,
  963. rejectPushTaskNo: null,
  964. status: null,
  965. detailStatus: null,
  966. isGenuine: null,
  967. qualityMark: null,
  968. sku: '',
  969. barCode: '',
  970. tradeName: '',
  971. qualityStatus: '',
  972. manufactureTime: '',
  973. validityTime: '',
  974. batchNumber: '',
  975. remark: '',
  976. asnNo: null,
  977. number: null,
  978. warehouse: null,
  979. warehouseBin: null,
  980. boxPhotos: [],
  981. productPhotos: [],
  982. files: null,
  983. pieceTag: null,
  984. createTime: null,
  985. creatorId: null,
  986. updateTime: null,
  987. updaterId: null,
  988. deleteTime: null,
  989. version: null,
  990. repairableType: null,
  991. repairableTypes: null,
  992. operatorId: null,
  993. operatorName: null,
  994. operatorTime: null,
  995. skuImage: null,
  996. orginSkuImage: null
  997. }
  998. }
  999. // 初始化质检状态
  1000. function initQualityInspection(owner) {
  1001. getQualityInspection(owner).then((res) => {
  1002. qualityInspection.value = res.data
  1003. })
  1004. }
  1005. // 重置提交,恢复到扫描快递单号页面
  1006. function init(showInputPage = true) {
  1007. ownerName.value = null
  1008. clearImageFileCache()
  1009. params.value = {
  1010. orderUpstream: null,
  1011. returnNo: null,
  1012. warehouseCode: warehouse,
  1013. logisticsName: null,
  1014. storeName: null,
  1015. orderUpSteam: null,
  1016. arrivalPayment: null,
  1017. buyerName: null,
  1018. asnNo: null,
  1019. upstreamNo: null,
  1020. buyerPhone: null,
  1021. originalNo: null,
  1022. remark: null,
  1023. details: []
  1024. }
  1025. storeOptions.value = []
  1026. expressNo.value = null
  1027. showInitialPage.value = showInputPage
  1028. initDetail()
  1029. }
  1030. // ==================== 业务工具方法 ====================
  1031. // 获取店铺信息
  1032. async function getStoreOptionsBy(owner) {
  1033. const results = await shops(owner)
  1034. const { data } = results
  1035. if (!data || data.length === 0) {
  1036. storeOptions.value = []
  1037. return
  1038. }
  1039. storeOptions.value = data.map((item) => {
  1040. return { value: item.name, text: item.name }
  1041. })
  1042. }
  1043. // 获取承运商名称
  1044. function getLogisticName() {
  1045. return params.value?.logisticsName || ''
  1046. }
  1047. // 获取货主名称
  1048. function getOwnerName(owner) {
  1049. if (!owner || !ownerMap.value) {
  1050. return ''
  1051. }
  1052. return ownerMap.value[owner] || ''
  1053. }
  1054. // 获取仓库名称
  1055. function getWarehouseName() {
  1056. if (!params.value?.warehouseCode || !warehousesMap.value) {
  1057. return ''
  1058. }
  1059. console.log(params.value.warehouseCode)
  1060. return warehousesMap.value[params.value.warehouseCode] || ''
  1061. }
  1062. // 获取质量状态标签颜色
  1063. function getTagColor(qualityStatus) {
  1064. if (!qualityStatus) {
  1065. return '#999999' // 默认颜色
  1066. }
  1067. return getTagColorBy(qualityStatus)
  1068. }
  1069. // 转化加密文本(长文本显示为"加密")
  1070. function handlerLongText(text) {
  1071. return text && text.length > 15 ? '加密' : text ? text : null
  1072. }
  1073. // ==================== 选择器处理方法 ====================
  1074. // 承运商选择处理
  1075. const selectedLogistic = (row) => {
  1076. const { selectedOptions } = row
  1077. logisticPickerShow.value = false
  1078. params.value.logisticsName = selectedOptions[0].text
  1079. }
  1080. // 货主选择处理
  1081. const selectedOwner = (row) => {
  1082. const { selectedValues, selectedOptions } = row
  1083. ownerPickerShow.value = false
  1084. params.value.ownerCode = selectedOptions[0].value
  1085. ownerName.value = selectedOptions[0].text
  1086. initQualityInspection(params.value.ownerCode)
  1087. }
  1088. // 仓库选择处理
  1089. const selectedWarehouse = (row) => {
  1090. const { selectedOptions } = row
  1091. warehousePickerShow.value = false
  1092. params.value.warehouseCode = selectedOptions[0].value
  1093. }
  1094. // 店铺选择处理
  1095. const selectedStore = (row) => {
  1096. const { selectedOptions } = row
  1097. storePickerShow.value = false
  1098. params.value.storeName = selectedOptions[0].text
  1099. }
  1100. // 货主选择(多选场景)
  1101. function selectOwner({ selectedValues }) {
  1102. const ownerCode = selectedValues[0]
  1103. params.value.ownerCode = ownerCode
  1104. showOwnerSelect.value = false
  1105. ownerSelectedOptions.value = []
  1106. getStoreOptionsBy(ownerCode)
  1107. queryOwnerBarcode(params.value.ownerCode, scancode.value)
  1108. }
  1109. // 显示货主选择器
  1110. function showOwnerSelectFunc() {
  1111. ownerRef.value?.show(1)
  1112. }
  1113. // 货主组件回调
  1114. const onOwner = (item, type) => {
  1115. params.value.ownerCode = item.code
  1116. getStoreOptionsBy(item.code)
  1117. }
  1118. // 多货主选择对话框
  1119. function showOwnerSelectDialog(items) {
  1120. if (!items || items.length === 0) {
  1121. ownerSelectedOptions.value = owners.value.map((item) => {
  1122. JSON.parse(JSON.stringify(item))
  1123. })
  1124. showOwnerSelect.value = true
  1125. return
  1126. }
  1127. ownerSelectedOptions.value = owners.value
  1128. .filter((item) => {
  1129. const { value } = item
  1130. return items.includes(value)
  1131. })
  1132. .map((item) => JSON.parse(JSON.stringify(item)))
  1133. showOwnerSelect.value = true
  1134. }
  1135. // ==================== 快递单号处理方法 ====================
  1136. // 输入快递单号处理
  1137. function inputExpressNo() {
  1138. const no = expressNo.value
  1139. const isSuccess = checkExpressNo(no)
  1140. if (!isSuccess) {
  1141. return
  1142. }
  1143. const express_no = initExpressNo(no)
  1144. if (!express_no || express_no.length === 0) {
  1145. showFailToast('快递单号异常')
  1146. scanError()
  1147. return
  1148. }
  1149. params.value.returnNo = express_no
  1150. params.value.warehouseCode = warehouse
  1151. listAsn(express_no).then((res) => {
  1152. if (res.data) {
  1153. asnList.value = res.data
  1154. }
  1155. })
  1156. matchReturned(express_no)
  1157. }
  1158. // 校验快递单号格式
  1159. function checkExpressNo(no) {
  1160. if (!no || no.length === 0) {
  1161. showFailToast('单号长度异常')
  1162. scanError()
  1163. return false
  1164. }
  1165. if (no.startsWith('http')) {
  1166. showFailToast('扫描到了错误的条码')
  1167. scanError()
  1168. return false
  1169. }
  1170. if (no.endsWith('.com')) {
  1171. showFailToast('扫描到了错误的条码')
  1172. scanError()
  1173. return false
  1174. }
  1175. const length = no.length
  1176. if (length > 30 || length < 5) {
  1177. showFailToast('单号长度异常')
  1178. scanError()
  1179. return false
  1180. }
  1181. return true
  1182. }
  1183. // 处理快递单号格式问题
  1184. function initExpressNo(no) {
  1185. // 处理扫描快递单号中的特殊字符和异常情况
  1186. if (!no) {
  1187. return no
  1188. }
  1189. if (no.includes('-')) {
  1190. no = no.substring(0, no.indexOf('-')) // 京东快递异常
  1191. }
  1192. return no
  1193. .replaceAll('-', '')
  1194. .replaceAll(' ', '')
  1195. .replaceAll('R02Z', '')
  1196. .replaceAll('R02T', '') // 圆通退回单号异常
  1197. }
  1198. // ==================== 订单匹配方法 ====================
  1199. // 匹配已存在的退货单
  1200. function matchReturned(express_no) {
  1201. getReturnedByExpress(express_no).then((res) => {
  1202. const { data } = res
  1203. if (!data) {
  1204. matchOrder(express_no)
  1205. return
  1206. }
  1207. const { id, headStatus, returnNo, warehouseCode } = data
  1208. if (!id) {
  1209. matchOrder(express_no)
  1210. expressNo.value = null
  1211. showInitialPage.value = false
  1212. return
  1213. }
  1214. if (id) {
  1215. if (['已入仓', '已拆包'].includes(headStatus)) {
  1216. showNotify({
  1217. type: 'primary',
  1218. message: `当前退回单号${returnNo}--${headStatus}`
  1219. })
  1220. init(false)
  1221. params.value.id = id
  1222. params.value.returnNo = returnNo
  1223. params.value.warehouseCode = warehouseCode
  1224. params.value.warehousingStatus = null
  1225. selectDetailIndex.value = -1
  1226. matchOrder(express_no)
  1227. expressNo.value = null
  1228. showInitialPage.value = false
  1229. scanSuccess()
  1230. } else {
  1231. showNotify({
  1232. type: 'danger',
  1233. message: '当前退回单号已进行过登记 如需修改请前往PC端进行'
  1234. })
  1235. scanSuccess()
  1236. }
  1237. }
  1238. })
  1239. }
  1240. // 匹配原单信息
  1241. function matchOrder(expressNo) {
  1242. matchOrderBy(expressNo).then((res) => {
  1243. const { data } = res
  1244. if (!data) {
  1245. console.log('未匹配到订单信息')
  1246. matchAsn(expressNo)
  1247. return
  1248. }
  1249. console.log('匹配到订单信息')
  1250. if (data) {
  1251. const {
  1252. upstreamNo,
  1253. shopName,
  1254. ownerCode,
  1255. recipientName,
  1256. phone,
  1257. logisticName,
  1258. details
  1259. } = data
  1260. showNotify({ type: 'success', message: '匹配到原单信息' })
  1261. params.value.upstreamNo = upstreamNo
  1262. params.value.ownerCode = ownerCode
  1263. params.value.warehouseCode = warehouse
  1264. params.value.storeName = shopName
  1265. params.value.logisticsName = logisticName
  1266. params.value.buyerName = handlerLongText(recipientName)
  1267. params.value.buyerPhone = handlerLongText(phone)
  1268. params.value.originalNo = '原单退回'
  1269. params.value.sendNo = null
  1270. initQualityInspection(params.value.ownerCode)
  1271. getStoreOptionsBy(ownerCode)
  1272. if (!details || details.length === 0) {
  1273. return
  1274. }
  1275. const list = []
  1276. details.forEach((item) => {
  1277. const {
  1278. sku,
  1279. barCode,
  1280. detailAmount,
  1281. name,
  1282. productionDate,
  1283. expirationDate,
  1284. batchNumber,
  1285. quality,
  1286. attributeBin
  1287. } = item
  1288. let qualityStatus
  1289. if (quality) {
  1290. if (quality === 'ZP') {
  1291. qualityStatus = '正品'
  1292. } else if (quality === 'CP') {
  1293. qualityStatus = '次品'
  1294. } else {
  1295. qualityStatus = '正品'
  1296. }
  1297. } else {
  1298. qualityStatus = '正品'
  1299. }
  1300. list.push({
  1301. sku: sku,
  1302. tradeName: name,
  1303. barCode: barCode,
  1304. number: detailAmount,
  1305. manufactureTime: productionDate,
  1306. validityTime: expirationDate,
  1307. batchNumber: batchNumber,
  1308. warehouse: attributeBin,
  1309. qualityStatus: qualityStatus,
  1310. isOriginal: true
  1311. })
  1312. params.value.details = list
  1313. matchCarrier(expressNo)
  1314. })
  1315. } else {
  1316. matchAsn(expressNo)
  1317. }
  1318. })
  1319. }
  1320. // 匹配承运商信息
  1321. function matchCarrier(expressNo) {
  1322. matchCarrierCode(expressNo).then((res) => {
  1323. const { data } = res
  1324. if (!data || data.length === 0) {
  1325. showNotify({
  1326. type: 'primary',
  1327. message: '请手动选择承运商'
  1328. })
  1329. } else {
  1330. params.value.logisticsName = data
  1331. }
  1332. })
  1333. }
  1334. // 匹配ASN单信息
  1335. function matchAsn(expressNo) {
  1336. matchAsnBy(expressNo).then((res) => {
  1337. const { data } = res
  1338. if (!data) {
  1339. console.log('未匹配asn单信息')
  1340. matchCarrier(expressNo)
  1341. return
  1342. }
  1343. const {
  1344. ownerCode,
  1345. phone,
  1346. shopName,
  1347. recipientName,
  1348. logisticName,
  1349. upstreamNo
  1350. } = data
  1351. params.value.returnNo = expressNo
  1352. params.value.upstreamNo = upstreamNo
  1353. params.value.ownerCode = ownerCode
  1354. params.value.warehouseCode = warehouse
  1355. params.value.storeName = shopName
  1356. params.value.logisticsName = logisticName
  1357. params.value.buyerName = handlerLongText(recipientName)
  1358. params.value.buyerPhone = handlerLongText(phone)
  1359. params.value.originalNo = null
  1360. params.value.sendNo = null
  1361. params.value.details = []
  1362. matchCarrier(expressNo)
  1363. getStoreOptionsBy(ownerCode)
  1364. initQualityInspection(params.value.ownerCode)
  1365. initDetail()
  1366. })
  1367. }
  1368. // ==================== 商品信息处理工具方法 ====================
  1369. // 检查商品是否有外箱图
  1370. function hasBoxItems(detail) {
  1371. try {
  1372. if (!detail || typeof detail !== 'object') {
  1373. return false
  1374. }
  1375. const { boxPhotos } = detail
  1376. if (!Array.isArray(boxPhotos) || boxPhotos.length === 0) {
  1377. return false
  1378. }
  1379. if (!Array.isArray(boxFiles.value) || boxFiles.value.length === 0) {
  1380. return false
  1381. }
  1382. return boxFiles.value.some((item) => {
  1383. return item && item.fileName && boxPhotos.includes(item.fileName)
  1384. })
  1385. } catch (error) {
  1386. console.error('hasBoxItems 错误:', error)
  1387. return false
  1388. }
  1389. }
  1390. // 获取商品外箱图列表
  1391. function getBoxItems(detail) {
  1392. try {
  1393. if (!detail || typeof detail !== 'object') {
  1394. return []
  1395. }
  1396. const { boxPhotos } = detail
  1397. if (!Array.isArray(boxPhotos) || boxPhotos.length === 0) {
  1398. return []
  1399. }
  1400. if (!Array.isArray(boxFiles.value) || boxFiles.value.length === 0) {
  1401. return []
  1402. }
  1403. return boxFiles.value.filter((item) => {
  1404. return item && item.fileName && boxPhotos.includes(item.fileName)
  1405. })
  1406. } catch (error) {
  1407. console.error('getBoxItems 错误:', error)
  1408. return []
  1409. }
  1410. }
  1411. // 检查商品是否有内物图
  1412. function hasProductItems(detail) {
  1413. try {
  1414. if (!detail || typeof detail !== 'object') {
  1415. return false
  1416. }
  1417. const { productPhotos } = detail
  1418. if (!Array.isArray(productPhotos) || productPhotos.length === 0) {
  1419. return false
  1420. }
  1421. if (!Array.isArray(productFiles.value) || productFiles.value.length === 0) {
  1422. return false
  1423. }
  1424. return productFiles.value.some((item) => {
  1425. return item && item.fileName && productPhotos.includes(item.fileName)
  1426. })
  1427. } catch (error) {
  1428. console.error('hasProductItems 错误:', error)
  1429. return false
  1430. }
  1431. }
  1432. // 获取商品内物图列表
  1433. function getProductItems(detail) {
  1434. try {
  1435. if (!detail || typeof detail !== 'object') {
  1436. return []
  1437. }
  1438. const { productPhotos } = detail
  1439. if (!Array.isArray(productPhotos) || productPhotos.length === 0) {
  1440. return []
  1441. }
  1442. if (!Array.isArray(productFiles.value) || productFiles.value.length === 0) {
  1443. return []
  1444. }
  1445. return productFiles.value.filter((item) => {
  1446. return item && item.fileName && productPhotos.includes(item.fileName)
  1447. })
  1448. } catch (error) {
  1449. console.error('getProductItems 错误:', error)
  1450. return []
  1451. }
  1452. }
  1453. // ==================== 数据查询方法 ====================
  1454. // 选择质量状态
  1455. function showQualityStatus() {
  1456. const barcode = scancode.value
  1457. if (!barcode) {
  1458. showFailToast('商品条码异常')
  1459. return
  1460. }
  1461. scanSuccess()
  1462. qualityStatusDialog.value = true
  1463. qualityStatus.value = '正品'
  1464. }
  1465. // 查询商品条码信息
  1466. async function queryBarcode() {
  1467. showLoadingToast({
  1468. duration: 0,
  1469. forbidClick: true,
  1470. message: '查询商品信息中.....'
  1471. })
  1472. const barcode = scancode.value
  1473. const { ownerCode, details } = params.value
  1474. if (ownerCode) {
  1475. const result = await queryOwnerBarcode(ownerCode, barcode)
  1476. if (result) {
  1477. scanSuccess()
  1478. qualityStatusDialog.value = false
  1479. closeToast()
  1480. // 查询成功后清空条码输入框并聚焦
  1481. scancode.value = ''
  1482. setTimeout(() => {
  1483. focusProductInput()
  1484. }, 100)
  1485. return
  1486. }
  1487. }
  1488. if (!details || details.length === 0) {
  1489. await queryBarcodeBy(barcode)
  1490. qualityStatusDialog.value = false
  1491. closeToast()
  1492. // 查询完成后清空条码输入框并聚焦
  1493. scancode.value = ''
  1494. setTimeout(() => {
  1495. focusProductInput()
  1496. }, 100)
  1497. }
  1498. }
  1499. // 根据货主查询商品条码信息
  1500. async function queryOwnerBarcode(ownerCode, barcode) {
  1501. try {
  1502. // 参数验证
  1503. if (!ownerCode || !barcode) {
  1504. console.error('queryOwnerBarcode: 参数不完整', { ownerCode, barcode })
  1505. return false
  1506. }
  1507. const results = await searchOwnerBarcode({ ownerCode, barcode })
  1508. // 响应数据验证
  1509. if (!results || !results.data) {
  1510. console.error('queryOwnerBarcode: 响应数据格式错误', results)
  1511. return false
  1512. }
  1513. const { data: { basSku } } = results
  1514. if (!basSku) {
  1515. return false
  1516. }
  1517. addDetailByBasSku(basSku)
  1518. return true
  1519. } catch (err) {
  1520. closeToast()
  1521. console.error('queryOwnerBarcode 错误:', err)
  1522. showFailToast('查询商品信息失败')
  1523. }
  1524. return false
  1525. }
  1526. // 查询商品条码信息(适用于没有指定货主的情况)
  1527. async function queryBarcodeBy(barcode) {
  1528. try {
  1529. const results = await searchBarcode({ barcode })
  1530. const { data } = results
  1531. const { basSku, ownerCodes } = data
  1532. if (ownerCodes && ownerCodes.length > 1) {
  1533. showOwnerSelectDialog(ownerCodes)
  1534. return null
  1535. }
  1536. if (!basSku) {
  1537. scanError()
  1538. return null
  1539. }
  1540. addDetailByBasSku(basSku)
  1541. scanSuccess()
  1542. return null
  1543. } catch (err) {
  1544. closeToast()
  1545. scanError()
  1546. console.log(err.message)
  1547. }
  1548. scanError()
  1549. return null
  1550. }
  1551. // ==================== 商品详情处理方法 ====================
  1552. // 根据Sku信息添加商品详情
  1553. function addDetailByBasSku(basSku) {
  1554. // 参数安全性检查
  1555. if (!basSku || typeof basSku !== 'object') {
  1556. console.error('addDetailByBasSku: basSku参数无效', basSku)
  1557. showFailToast('商品信息获取失败')
  1558. return
  1559. }
  1560. const { sku, barCode, ownerCode, tradeName, accessories } = basSku
  1561. // 必要字段验证
  1562. if (!sku || !barCode) {
  1563. console.error('addDetailByBasSku: 缺少必要的商品信息', { sku, barCode })
  1564. showFailToast('商品信息不完整')
  1565. return
  1566. }
  1567. selectDetailIndex.value = -1
  1568. selectDetail.value.sku = sku
  1569. if (
  1570. !params.value.ownerCode ||
  1571. !params.value.details ||
  1572. params.value.details.length === 0
  1573. ) {
  1574. params.value.ownerCode = ownerCode
  1575. if (ownerCode) {
  1576. initQualityInspection(params.value.ownerCode)
  1577. getStoreOptionsBy(params.value.ownerCode)
  1578. }
  1579. }
  1580. selectDetail.value.barCode = barCode
  1581. selectDetail.value.qualityStatus = qualityStatus.value || '正品'
  1582. selectDetail.value.tradeName = tradeName || ''
  1583. selectDetail.value.number = 1
  1584. qualityStatusDialog.value = false
  1585. returnedDetailDialog.value = true
  1586. // 显示配件信息
  1587. showAccessoriesItems(accessories)
  1588. }
  1589. // 显示配件信息
  1590. function showAccessoriesItems(items) {
  1591. if (!items || items.length === 0) {
  1592. accessories.value = []
  1593. showAccessories.value = false
  1594. return
  1595. }
  1596. accessories.value = items
  1597. showAccessories.value = true
  1598. }
  1599. // 编辑商品详情
  1600. function editDetails(index) {
  1601. selectDetailIndex.value = index
  1602. const { details } = params.value
  1603. selectDetail.value = JSON.parse(JSON.stringify(details[index]))
  1604. returnedDetailDialog.value = true
  1605. }
  1606. // 删除商品详情
  1607. function removeDetails(index) {
  1608. if (null === index) {
  1609. showFailToast('未找到对应的详情')
  1610. return
  1611. }
  1612. showConfirmDialog({
  1613. title: '系统提示',
  1614. message: '是否删除当前登记信息'
  1615. })
  1616. .then(() => {
  1617. const { id } = params.value.details[index]
  1618. if (!id) {
  1619. params.value.details.splice(index, 1)
  1620. showNotify({ type: 'success', message: '删除成功' })
  1621. } else {
  1622. deleteDetails(id).then((res) => {
  1623. if (res.data) {
  1624. params.value.details.splice(index, 1)
  1625. showNotify({ type: 'success', message: '删除成功' })
  1626. } else {
  1627. showNotify({ type: 'danger', message: '删除失败' })
  1628. }
  1629. })
  1630. }
  1631. })
  1632. .catch(() => {
  1633. showNotify({ type: 'primary', message: '取消删除' })
  1634. })
  1635. }
  1636. // 添加商品详情到列表
  1637. const addDetails = async () => {
  1638. const isCheck = checkUploadImages()
  1639. if (!isCheck) {
  1640. returnedDetailDialog.value = true
  1641. return false
  1642. }
  1643. const index = selectDetailIndex.value
  1644. const detail = JSON.parse(JSON.stringify(selectDetail.value))
  1645. if (index > -1) {
  1646. params.value.details[index] = detail
  1647. initDetail()
  1648. // 编辑完成后聚焦到商品条码输入框
  1649. setTimeout(() => {
  1650. scancode.value = ''
  1651. focusProductInput()
  1652. }, 100)
  1653. return true
  1654. }
  1655. const {
  1656. sku: detail_sku,
  1657. batchNumber: detail_batch_number,
  1658. validityTime: detail_validity_time,
  1659. manufactureTime: detail_manufacture_time,
  1660. qualityStatus: detail_quality_status,
  1661. number: detailNumber
  1662. } = selectDetail.value
  1663. if (['次品', '待修复'].includes(detail_quality_status)) {
  1664. params.value.details.push(detail)
  1665. initDetail()
  1666. // 添加完成后聚焦到商品条码输入框
  1667. setTimeout(() => {
  1668. scancode.value = ''
  1669. focusProductInput()
  1670. }, 100)
  1671. return
  1672. }
  1673. const searchIndex = params.value.details.findIndex((item) => {
  1674. const { sku, batchNumber, qualityStatus, validityTime, manufactureTime } =
  1675. item
  1676. return (
  1677. sku === detail_sku &&
  1678. batchNumber === detail_batch_number &&
  1679. detail_validity_time === validityTime &&
  1680. detail_manufacture_time === manufactureTime &&
  1681. detail_quality_status === qualityStatus
  1682. )
  1683. })
  1684. if (searchIndex > -1) {
  1685. showConfirmDialog({
  1686. title: '系统提示',
  1687. message: '查询到有对应的商品详情是否进行合并'
  1688. })
  1689. .then(() => {
  1690. const { number } = params.value.details[searchIndex]
  1691. params.value.details[searchIndex].number =
  1692. Number(detailNumber) + Number(number)
  1693. showNotify({ type: 'success', message: '合并成功' })
  1694. // 合并完成后聚焦
  1695. setTimeout(() => {
  1696. scancode.value = ''
  1697. focusProductInput()
  1698. }, 100)
  1699. })
  1700. .catch(() => {
  1701. params.value.details.push(detail)
  1702. showNotify({ type: 'success', message: '添加成功' })
  1703. // 添加完成后聚焦
  1704. setTimeout(() => {
  1705. scancode.value = ''
  1706. focusProductInput()
  1707. }, 100)
  1708. })
  1709. } else {
  1710. params.value.details.push(detail)
  1711. showNotify({ type: 'success', message: '添加成功' })
  1712. // 添加完成后聚焦
  1713. setTimeout(() => {
  1714. scancode.value = ''
  1715. focusProductInput()
  1716. }, 100)
  1717. }
  1718. initDetail()
  1719. return true
  1720. }
  1721. // 取消商品详情对话框
  1722. const cancelReturnedDetailDialog = async () => {
  1723. returnedDetailDialog.value = false
  1724. initDetail()
  1725. // 取消对话框后自动聚焦到商品条码输入框
  1726. setTimeout(() => {
  1727. focusProductInput()
  1728. }, 100)
  1729. }
  1730. // ==================== 质量状态和日期处理方法 ====================
  1731. // 质量状态选择处理
  1732. const selectedDetailQualityStatusFunc = (row) => {
  1733. const { selectedOptions } = row
  1734. selectedDetailQualityStatus.value = false
  1735. selectDetail.value.qualityStatus = selectedOptions[0].text
  1736. // 质量状态变化后重新检查图片上传状态
  1737. nextTick(() => {
  1738. // 触发响应式更新,让对话框重新计算确认按钮状态
  1739. checkUploadImages()
  1740. })
  1741. }
  1742. // 日期显示格式化
  1743. const formatDateDisplay = (dateString) => {
  1744. if (!dateString) return ''
  1745. return dateString.replace(/-/g, '/') // 将 2023-05-01 显示为 2023/05/01
  1746. }
  1747. // 日期字符串转数组格式 [year, month, day]
  1748. const parseDateValue = (dateString) => {
  1749. if (!dateString) return []
  1750. const [year, month, day] = dateString.split('-')
  1751. return [year, month, day]
  1752. }
  1753. // 生产日期确认
  1754. const manufactureTimeConfirm = async (result) => {
  1755. if (!result.selectedValues) {
  1756. showManufactureTime.value = false
  1757. return
  1758. }
  1759. // 正确解构参数
  1760. const { selectedValues } = result
  1761. const [year, month, day] = selectedValues
  1762. const formatted_month = month.padStart(2, '0')
  1763. const formatted_day = day.padStart(2, '0')
  1764. const manufactureTime = `${year}-${formatted_month}-${formatted_day}`
  1765. const { sku } = selectDetail.value
  1766. const { ownerCode } = params.value
  1767. const check_validate_date = await checkValidateDate(
  1768. ownerCode,
  1769. sku,
  1770. manufactureTime,
  1771. 'manufactureTime'
  1772. )
  1773. if (!check_validate_date) {
  1774. console.log('检验出现异常')
  1775. return
  1776. }
  1777. selectDetail.value.manufactureTime = manufactureTime
  1778. showManufactureTime.value = false
  1779. }
  1780. // 失效日期确认
  1781. const validityTimeConfirm = async (result) => {
  1782. if (!result.selectedValues) {
  1783. showValidityTime.value = false
  1784. return
  1785. }
  1786. // 正确解构参数
  1787. const { selectedValues } = result
  1788. const [year, month, day] = selectedValues
  1789. const formatted_month = month.padStart(2, '0')
  1790. const formatted_day = day.padStart(2, '0')
  1791. const validityTime = `${year}-${formatted_month}-${formatted_day}`
  1792. const { sku } = selectDetail.value
  1793. const { ownerCode } = params.value
  1794. const check_validate_date = await checkValidateDate(
  1795. ownerCode,
  1796. sku,
  1797. validityTime,
  1798. 'validityTime'
  1799. )
  1800. if (!check_validate_date) {
  1801. return
  1802. }
  1803. selectDetail.value.validityTime = `${year}-${formatted_month}-${formatted_day}`
  1804. showValidityTime.value = false
  1805. }
  1806. // 校验日期合法性
  1807. async function checkValidateDate(ownerCode, sku, newDate, fieldName) {
  1808. if (['NOSKU', 'NOBARCODE'].includes(sku)) {
  1809. return true
  1810. }
  1811. const type = fieldName === 'manufactureTime' ? '生产日期' : '失效日期'
  1812. try {
  1813. await validateDate({
  1814. newDate: newDate,
  1815. fieldName: fieldName,
  1816. ownerCode: ownerCode,
  1817. sku: sku
  1818. })
  1819. showNotify({
  1820. type: 'success',
  1821. message: `${type} 状态校验成功 `
  1822. })
  1823. return true
  1824. } catch (error) {
  1825. showNotify({
  1826. type: 'danger',
  1827. message: `校验${type}出现异常`
  1828. })
  1829. return false
  1830. }
  1831. }
  1832. // ==================== 图片处理方法 ====================
  1833. // Blob转Base64
  1834. function blobToBase64(blob) {
  1835. return new Promise((resolve, reject) => {
  1836. const fileReader = new FileReader()
  1837. fileReader.onload = (e) => {
  1838. resolve(e.target.result)
  1839. }
  1840. fileReader.readAsDataURL(blob)
  1841. fileReader.onerror = () => {
  1842. reject(new Error('blobToBase64 error'))
  1843. }
  1844. })
  1845. }
  1846. // 获取图片URL
  1847. function getImageUrl(file_name, type) {
  1848. let list = []
  1849. if (type === '外箱图') {
  1850. list = boxFiles.value
  1851. } else if (type === '内物图') {
  1852. list = productFiles.value
  1853. }
  1854. for (let i = 0; i < list.length; i++) {
  1855. const item = list[i]
  1856. const { src, fileName } = item
  1857. if (file_name === fileName) {
  1858. return src
  1859. }
  1860. }
  1861. return null
  1862. }
  1863. // 处理图片文件输入
  1864. function pushImageItem(event, type) {
  1865. const file = event.target.files[0]
  1866. if (!file) return
  1867. if (!file.type.startsWith('image/')) {
  1868. alert('请选择图片文件!')
  1869. return
  1870. }
  1871. const filename = file.name
  1872. const item = {
  1873. src: URL.createObjectURL(file),
  1874. file: file,
  1875. fileName: filename,
  1876. type
  1877. }
  1878. if (type === '外箱图') {
  1879. if (!selectDetail.value.boxPhotos) {
  1880. selectDetail.value.boxPhotos = []
  1881. }
  1882. blobToBase64(file).then((_) => {
  1883. const some = boxFiles.value.some((item) => {
  1884. return filename === item.fileName
  1885. })
  1886. if (some) {
  1887. showFailToast('录入重复图片')
  1888. } else {
  1889. boxFiles.value.push(item)
  1890. selectDetail.value.boxPhotos.push(filename)
  1891. }
  1892. })
  1893. } else if (type === '内物图') {
  1894. if (!selectDetail.value.productPhotos) {
  1895. selectDetail.value.productPhotos = []
  1896. }
  1897. blobToBase64(file).then((_) => {
  1898. const some = productFiles.value.some((item) => {
  1899. return filename === item.fileName
  1900. })
  1901. if (some) {
  1902. showFailToast('录入重复图片')
  1903. } else {
  1904. productFiles.value.push(item)
  1905. selectDetail.value.productPhotos.push(filename)
  1906. }
  1907. })
  1908. }
  1909. }
  1910. // 触发相机拍照
  1911. function invokeCameraToCapture(type) {
  1912. const { sku } = selectDetail.value
  1913. if (!sku) {
  1914. showFailToast('当前没有可录入的商品信息')
  1915. return
  1916. }
  1917. if (type === '外箱图') {
  1918. document.getElementById('outer-carton-box-input').click()
  1919. } else if (type === '内物图') {
  1920. document.getElementById('inner-contents-input').click()
  1921. }
  1922. }
  1923. // 外箱图文件输入事件
  1924. function outerCartonInput(event) {
  1925. pushImageItem(event, '外箱图')
  1926. }
  1927. // 内物图文件输入事件
  1928. function innerContentsInput(event) {
  1929. pushImageItem(event, '内物图')
  1930. }
  1931. // 清除图片文件缓存
  1932. function clearImageFileCache() {
  1933. if (productFiles.value && productFiles.value.length > 0) {
  1934. productFiles.value
  1935. .map((item) => item.src)
  1936. .forEach((item) => URL.revokeObjectURL(item))
  1937. }
  1938. if (boxFiles.value && boxFiles.value.length > 0) {
  1939. boxFiles.value
  1940. .map((item) => item.src)
  1941. .forEach((item) => URL.revokeObjectURL(item))
  1942. }
  1943. }
  1944. // 校验图片上传
  1945. function checkUploadImages() {
  1946. const status = ['次品', '待修复']
  1947. const { productPhotos, boxPhotos, qualityStatus } = selectDetail.value
  1948. if (!status.includes(qualityStatus)) {
  1949. return true
  1950. }
  1951. if (!qualityStatus) {
  1952. return true
  1953. }
  1954. const boxPhotoIsNull = !boxPhotos || boxPhotos.length === 0
  1955. const productPhotoIsNull = !productPhotos || productPhotos.length === 0
  1956. if (boxPhotoIsNull) {
  1957. showNotify({
  1958. type: 'warning',
  1959. message: '请传入对应的外箱图'
  1960. })
  1961. return false
  1962. }
  1963. if (productPhotoIsNull) {
  1964. showNotify({
  1965. type: 'warning',
  1966. message: '请传入对应的内物图'
  1967. })
  1968. return false
  1969. }
  1970. return true
  1971. }
  1972. // ==================== 图片预览方法 ====================
  1973. // 显示内物图预览
  1974. const showPhotosImagePreview = (index) => {
  1975. startPhotosPosition.value = index
  1976. showPhotosPreview.value = true
  1977. }
  1978. // 内物图预览位置变化
  1979. const onPhotosPreviewChange = (index) => {
  1980. startPhotosPosition.value = index
  1981. }
  1982. // 删除内物图
  1983. const handlePhotosDelete = () => {
  1984. showConfirmDialog({
  1985. title: '提示',
  1986. message: '确定要删除这张图片吗?'
  1987. })
  1988. .then(() => {
  1989. selectDetail.value.productPhotos.splice(startPhotosPosition.value, 1)
  1990. showNotify({ type: 'success', message: '删除成功' })
  1991. if (selectDetail.value.productPhotos.length === 0) {
  1992. showPhotosPreview.value = false
  1993. } else if (
  1994. startPhotosPosition.value >= selectDetail.value.productPhotos.length
  1995. ) {
  1996. startPhotosPosition.value = selectDetail.value.productPhotos.length - 1
  1997. }
  1998. })
  1999. .catch(() => {
  2000. showNotify({ type: 'primary', message: '取消删除' })
  2001. })
  2002. }
  2003. // 显示外箱图预览
  2004. const showBoxImagePreview = (index) => {
  2005. startBoxPosition.value = index
  2006. showBoxPreview.value = true
  2007. }
  2008. // 外箱图预览位置变化
  2009. const onBoxPreviewChange = (index) => {
  2010. startBoxPosition.value = index
  2011. }
  2012. // 删除外箱图
  2013. const handleBoxDelete = () => {
  2014. showConfirmDialog({
  2015. title: '提示',
  2016. message: '确定要删除这张图片吗?'
  2017. })
  2018. .then(() => {
  2019. selectDetail.value.boxPhotos.splice(startBoxPosition.value, 1)
  2020. showNotify({ type: 'success', message: '删除成功' })
  2021. if (selectDetail.value.boxPhotos.length === 0) {
  2022. showBoxPreview.value = false
  2023. } else if (
  2024. startBoxPosition.value >= selectDetail.value.boxPhotos.length
  2025. ) {
  2026. startBoxPosition.value = selectDetail.value.boxPhotos.length - 1
  2027. }
  2028. })
  2029. .catch(() => {
  2030. showNotify({ type: 'primary', message: '取消删除' })
  2031. })
  2032. }
  2033. // 预览图片列表
  2034. function previewImages(filenames, index, type) {
  2035. let list = []
  2036. if (type === '外箱图') {
  2037. list = boxFiles.value
  2038. } else if (type === '内物图') {
  2039. list = productFiles.value
  2040. }
  2041. const images = []
  2042. for (let i = 0; i < list.length; i++) {
  2043. const item = list[i]
  2044. const { src, fileName } = item
  2045. if (filenames.includes(fileName)) {
  2046. images.push(src)
  2047. }
  2048. }
  2049. if (!images || images.length === 0) {
  2050. return
  2051. }
  2052. showImagePreview({
  2053. images: images,
  2054. startPosition: index
  2055. })
  2056. }
  2057. // ==================== 表单提交和验证方法 ====================
  2058. // 浮动按钮提交处理(带确认对话框)
  2059. const handleSubmitWithConfirm = () => {
  2060. showConfirmDialog({
  2061. title: '提交确认',
  2062. message: '确认提交退货登记信息吗?\n\n提交后将无法修改,请仔细检查所有信息。',
  2063. confirmButtonText: '确认提交',
  2064. cancelButtonText: '取消',
  2065. confirmButtonColor: '#1989fa'
  2066. })
  2067. .then(() => {
  2068. // 用户点击确认后执行提交
  2069. submit()
  2070. })
  2071. .catch(() => {
  2072. // 用户点击取消,不执行任何操作
  2073. showNotify({
  2074. type: 'primary',
  2075. message: '已取消提交'
  2076. })
  2077. })
  2078. }
  2079. // 表单提交
  2080. function submit() {
  2081. const { ownerCode, logisticsName, returnNo, warehouseCode } = params.value
  2082. if (!ownerCode || ownerCode.length === 0) {
  2083. showNotify({
  2084. type: 'warning',
  2085. message: '没有对应的退件详情!请录入对应的退件详情'
  2086. })
  2087. return
  2088. } else if (!logisticsName || logisticsName.length === 0) {
  2089. showNotify({
  2090. type: 'warning',
  2091. message: '请录入承运商'
  2092. })
  2093. return
  2094. } else if (!returnNo || ownerCode.length === 0) {
  2095. showNotify({
  2096. type: 'warning',
  2097. message: '请录入退件单号'
  2098. })
  2099. return
  2100. } else if (!warehouseCode || warehouseCode.length === 0) {
  2101. showNotify({
  2102. type: 'warning',
  2103. message: '请选择登记仓库'
  2104. })
  2105. return
  2106. }
  2107. if (checkDetailNumber()) {
  2108. return
  2109. }
  2110. const formData = new FormData()
  2111. const boxItems = boxFiles && boxFiles.value ? boxFiles.value : []
  2112. const productItems =
  2113. productFiles && productFiles.value ? productFiles.value : []
  2114. const files = [...boxItems, ...productItems]
  2115. const filenames = []
  2116. if (files && files.length > 0) {
  2117. files.forEach((item) => {
  2118. const { file, fileName } = item
  2119. if (!filenames.includes(fileName)) {
  2120. formData.append('files', file)
  2121. filenames.push(fileName)
  2122. }
  2123. })
  2124. }
  2125. handleDetails(params.value.details)
  2126. formData.append('body', JSON.stringify(JSON.parse(JSON.stringify(params.value))))
  2127. showLoadingToast({
  2128. duration: 0,
  2129. forbidClick: true,
  2130. message: '提交登记中.....'
  2131. })
  2132. register(formData)
  2133. .then((res) => {
  2134. const { data } = res
  2135. const { accumulateTaskMap } = data
  2136. closeToast()
  2137. if (data) {
  2138. if (accumulateTaskMap && accumulateTaskMap.length > 0) {
  2139. let messages = []
  2140. const ownerName = getOwnerName(params.value.ownerCode)
  2141. for (let i = 0; i < accumulateTaskMap.length; i++) {
  2142. const { quality, taskCode } = accumulateTaskMap[i]
  2143. messages.push(`进入${ownerName}新的${quality}攒单任务号${taskCode}`)
  2144. }
  2145. showConfirmDialog({
  2146. title: '提交成功',
  2147. message: messages.join('\r\n'),
  2148. theme: 'round-button'
  2149. })
  2150. } else {
  2151. showNotify({
  2152. type: 'success',
  2153. message: '成功提交'
  2154. })
  2155. }
  2156. init()
  2157. params.value.ownerCode = ownerCode
  2158. }
  2159. })
  2160. .catch(() => {
  2161. closeToast()
  2162. })
  2163. }
  2164. // 检查详情数量
  2165. function checkDetailNumber() {
  2166. const { details } = params.value
  2167. if (!details || details.length === 0) {
  2168. showNotify({
  2169. type: 'warning',
  2170. message: '登记详情不能为空'
  2171. })
  2172. return true
  2173. }
  2174. const check = details.some((item) => {
  2175. const { number } = item
  2176. return Number.isNaN(number) || Number(number) <= 0
  2177. })
  2178. if (check) {
  2179. showNotify({
  2180. type: 'warning',
  2181. message: '登记数量不能为 0 和 空 '
  2182. })
  2183. }
  2184. return check
  2185. }
  2186. // 处理详情数据
  2187. function handleDetails(details) {
  2188. for (let i = 0; i < details.length; i++) {
  2189. const { qualityStatus } = details[i]
  2190. const { isGenuineValue, qualityMark } = getStatus(qualityStatus)
  2191. details[i].isGenuine = isGenuineValue
  2192. details[i].qualityMark = qualityMark
  2193. }
  2194. }
  2195. // ==================== 图片处理方法 ====================
  2196. // ==================== 全局事件处理 ====================
  2197. // 页面刷新事件
  2198. window.onRefresh = async () => {
  2199. console.log('window.onRefresh')
  2200. }
  2201. </script>
  2202. <style scoped lang="sass">
  2203. // ==================== 导航栏样式 ====================
  2204. .van-nav-bar
  2205. position: fixed
  2206. top: 0
  2207. left: 0
  2208. right: 0
  2209. width: 100%
  2210. z-index: 1000
  2211. background: linear-gradient(135deg, #1989fa 0%, #1976d2 100%)
  2212. box-shadow: 0 2px 8px rgba(25, 137, 250, 0.3)
  2213. .left-btn
  2214. color: #fff
  2215. height: 46px
  2216. padding-right: 20px
  2217. line-height: 46px
  2218. font-size: 15px
  2219. font-weight: 500
  2220. transition: opacity 0.3s ease
  2221. &:active
  2222. opacity: 0.7
  2223. .right-btn
  2224. color: #fff
  2225. font-size: 15px
  2226. font-weight: 500
  2227. transition: opacity 0.3s ease
  2228. &:active
  2229. opacity: 0.7
  2230. // ==================== 主容器样式 ====================
  2231. .container
  2232. min-height: 100vh
  2233. background: linear-gradient(180deg, #f7f8fa 0%, #ffffff 100%)
  2234. overflow-x: hidden // 防止横向滚动
  2235. max-width: 100vw // 限制最大宽度
  2236. box-sizing: border-box // 确保padding不增加总宽度
  2237. .init-container
  2238. width: 100%
  2239. max-width: 100%
  2240. padding: 16px 8px
  2241. box-sizing: border-box
  2242. margin-top: 10px // 确保不被导航栏遮挡
  2243. min-height: calc(100vh - 100px) // 确保有足够的显示空间
  2244. flex-direction: column
  2245. justify-content: center // 垂直居中显示内容
  2246. .content-tips
  2247. margin-bottom: 10px
  2248. border-radius: 8px
  2249. overflow: hidden
  2250. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08)
  2251. max-width: 100%
  2252. box-sizing: border-box
  2253. display: block // 确保块级显示
  2254. width: 100% // 明确设置宽度
  2255. :deep(.van-notice-bar)
  2256. width: 100%
  2257. box-sizing: border-box
  2258. word-wrap: break-word
  2259. white-space: normal // 允许文字换行
  2260. .van-notice-bar__content
  2261. flex: 1
  2262. min-width: 0 // 允许flex元素正确缩小
  2263. word-break: break-word
  2264. overflow-wrap: break-word
  2265. .van-notice-bar__text
  2266. word-break: break-word
  2267. overflow-wrap: break-word
  2268. white-space: normal
  2269. // 固定定位的提示条样式
  2270. &.fixed-notice
  2271. position: fixed
  2272. top: 46px // 导航栏高度
  2273. left: 0
  2274. right: 0
  2275. z-index: 999
  2276. background: white
  2277. margin: 0
  2278. padding: 8px 16px
  2279. border-bottom: 1px solid #ebedf0
  2280. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)
  2281. border-radius: 0
  2282. max-width: 100vw
  2283. box-sizing: border-box
  2284. .scan-returned-content
  2285. padding: 16px
  2286. background: #fff
  2287. border-radius: 16px
  2288. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08)
  2289. margin-top: 8px
  2290. max-width: 100%
  2291. box-sizing: border-box
  2292. // 当有固定提示条时的上边距
  2293. &.with-fixed-notice
  2294. margin-top: 65px // 与退货信息区域保持一致
  2295. .input-group
  2296. padding: 4px 0
  2297. margin-bottom: 16px
  2298. max-width: 100%
  2299. box-sizing: border-box
  2300. :deep(.van-field)
  2301. border-radius: 12px
  2302. border: 1px solid #ebedf0
  2303. transition: border-color 0.3s ease, box-shadow 0.3s ease
  2304. max-width: 100%
  2305. box-sizing: border-box
  2306. &:focus-within
  2307. border-color: #1989fa
  2308. box-shadow: 0 0 0 2px rgba(25, 137, 250, 0.1)
  2309. .van-field__control
  2310. word-break: break-all
  2311. overflow-wrap: break-word
  2312. .button-group
  2313. padding: 4px 0
  2314. max-width: 100%
  2315. box-sizing: border-box
  2316. .confirm-btn
  2317. height: 44px
  2318. border-radius: 12px
  2319. font-size: 16px
  2320. font-weight: 600
  2321. background: linear-gradient(135deg, #1989fa 0%, #1976d2 100%)
  2322. border: none
  2323. box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3)
  2324. transition: all 0.3s ease
  2325. max-width: 100%
  2326. box-sizing: border-box
  2327. &:active
  2328. transform: translateY(1px)
  2329. box-shadow: 0 2px 8px rgba(25, 137, 250, 0.4)
  2330. .content
  2331. width: 100%
  2332. max-width: 100vw
  2333. margin-top: 46px
  2334. min-height: calc(100vh - 46px)
  2335. background: #f7f8fa
  2336. padding-bottom: 20px
  2337. overflow-x: hidden
  2338. box-sizing: border-box
  2339. padding-top: 10px // 为初始扫描页面添加上边距
  2340. // ==================== 固定定位提示条样式 ====================
  2341. .content-tips.fixed-notice
  2342. position: fixed
  2343. top: 46px // 导航栏高度
  2344. left: 0
  2345. right: 0
  2346. z-index: 999
  2347. background: white
  2348. margin: 0
  2349. padding: 8px 16px
  2350. border-bottom: 1px solid #ebedf0
  2351. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)
  2352. border-radius: 0
  2353. max-width: 100vw
  2354. box-sizing: border-box
  2355. min-height: auto // 允许高度自适应
  2356. :deep(.van-notice-bar)
  2357. width: 100%
  2358. box-sizing: border-box
  2359. word-wrap: break-word
  2360. white-space: normal // 允许文字换行
  2361. .van-notice-bar__content
  2362. flex: 1
  2363. min-width: 0
  2364. word-break: break-word
  2365. overflow-wrap: break-word
  2366. .van-notice-bar__text
  2367. word-break: break-word
  2368. overflow-wrap: break-word
  2369. white-space: normal
  2370. line-height: 1.4
  2371. // ==================== 区块头部样式 ====================
  2372. .section-header
  2373. display: flex
  2374. align-items: center
  2375. padding: 12px 16px
  2376. background: linear-gradient(135deg, #1989fa 0%, #1976d2 100%)
  2377. color: white
  2378. margin: 8px
  2379. border-radius: 12px 12px 0 0
  2380. box-shadow: 0 2px 8px rgba(25, 137, 250, 0.3)
  2381. .section-title
  2382. margin-left: 8px
  2383. font-size: 16px
  2384. font-weight: 600
  2385. // ==================== 退货信息区域 ====================
  2386. .return-info-section
  2387. margin-bottom: 20px
  2388. // 当有固定提示条时的上边距
  2389. &.with-fixed-notice
  2390. margin-top: 65px // 优化上边距:导航栏46px + 提示条约15px + 安全间距4px
  2391. .content-tips
  2392. margin: 8px
  2393. border-radius: 0 0 12px 12px
  2394. overflow: hidden
  2395. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06)
  2396. // ==================== 商品信息区域 ====================
  2397. .product-info-section
  2398. margin-bottom: 20px
  2399. // ==================== 提交区域 ====================
  2400. .submit-section
  2401. margin-top: 20px
  2402. // ==================== 内容提示区域 ====================
  2403. .content-tips
  2404. border-radius: 12px
  2405. overflow: hidden
  2406. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06)
  2407. width: 100%
  2408. box-sizing: border-box
  2409. :deep(.van-notice-bar)
  2410. border-radius: 12px
  2411. font-size: 14px
  2412. font-weight: 500
  2413. width: 100%
  2414. box-sizing: border-box
  2415. .van-notice-bar__content
  2416. flex: 1
  2417. min-width: 0
  2418. word-break: break-word
  2419. overflow-wrap: break-word
  2420. white-space: normal
  2421. .van-notice-bar__text
  2422. word-break: break-word
  2423. overflow-wrap: break-word
  2424. white-space: normal
  2425. line-height: 1.5
  2426. // ==================== 表单组件优化 ====================
  2427. :deep(.van-cell-group)
  2428. border-radius: 16px
  2429. overflow: hidden
  2430. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06)
  2431. margin: 8px
  2432. max-width: calc(100% - 16px)
  2433. box-sizing: border-box
  2434. .van-cell
  2435. padding: 12px 16px
  2436. border-bottom: 1px solid #f5f7fa
  2437. transition: background-color 0.3s ease
  2438. max-width: 100%
  2439. box-sizing: border-box
  2440. overflow: hidden
  2441. &:last-child
  2442. border-bottom: none
  2443. &:active
  2444. background-color: #f8f9fa
  2445. .van-field__label
  2446. font-weight: 600
  2447. color: #323233
  2448. font-size: 14px
  2449. white-space: nowrap
  2450. overflow: hidden
  2451. text-overflow: ellipsis
  2452. .van-field__control
  2453. font-size: 14px
  2454. color: #646566
  2455. word-break: break-all
  2456. overflow-wrap: break-word
  2457. // ==================== 商品卡片样式优化 ====================
  2458. .scan-returned-no
  2459. align-items: center
  2460. padding: 12px
  2461. .returned-detail-list
  2462. padding: 0 8px
  2463. max-width: 100%
  2464. box-sizing: border-box
  2465. .card-div
  2466. background: #fff
  2467. border-radius: 16px
  2468. overflow: hidden
  2469. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08)
  2470. margin: 8px 0
  2471. border: 1px solid #f0f0f0
  2472. transition: all 0.3s ease
  2473. max-width: 100%
  2474. box-sizing: border-box
  2475. &:hover
  2476. box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12)
  2477. transform: translateY(-1px)
  2478. .card-div-content
  2479. padding: 12px
  2480. max-width: 100%
  2481. box-sizing: border-box
  2482. .info-row
  2483. display: flex
  2484. align-items: center
  2485. margin-bottom: 10px
  2486. font-size: 13px
  2487. line-height: 1.4
  2488. max-width: 100%
  2489. overflow: hidden
  2490. &:last-child
  2491. margin-bottom: 0
  2492. .info-label
  2493. width: 80px
  2494. min-width: 80px
  2495. color: #969799
  2496. font-weight: 500
  2497. font-size: 12px
  2498. flex-shrink: 0
  2499. white-space: nowrap
  2500. overflow: hidden
  2501. text-overflow: ellipsis
  2502. .info-value
  2503. flex: 1
  2504. color: #323233
  2505. font-weight: 600
  2506. font-size: 13px
  2507. margin-left: 8px
  2508. word-break: break-all
  2509. overflow-wrap: break-word
  2510. min-width: 0 // 允许flex元素缩小到内容大小
  2511. :deep(.van-tag)
  2512. font-size: 11px
  2513. padding: 2px 8px
  2514. border-radius: 6px
  2515. font-weight: 600
  2516. max-width: 100%
  2517. box-sizing: border-box
  2518. :deep(.van-text-ellipsis)
  2519. font-size: 13px
  2520. line-height: 1.4
  2521. max-width: 100%
  2522. .card-div-footer
  2523. padding: 10px 12px
  2524. background: linear-gradient(135deg, #f8f9fa 0%, #f5f7fa 100%)
  2525. border-top: 1px solid #ebedf0
  2526. color: #646566
  2527. font-size: 13px
  2528. line-height: 1.5
  2529. .product-description
  2530. color: #646566
  2531. font-style: italic
  2532. .card-div-footer-images
  2533. padding: 10px 12px
  2534. background: #fafbfc
  2535. border-top: 1px solid #ebedf0
  2536. :deep(.van-divider)
  2537. margin: 6px 0
  2538. font-size: 12px
  2539. color: #969799
  2540. :deep(.van-image)
  2541. border-radius: 8px
  2542. overflow: hidden
  2543. border: 1px solid #ebedf0
  2544. transition: all 0.3s ease
  2545. &:hover
  2546. transform: scale(1.05)
  2547. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15)
  2548. .card-div-footer-options
  2549. background: #f8f9fa
  2550. border-top: 1px solid #ebedf0
  2551. padding: 10px 12px
  2552. .options-row
  2553. display: flex
  2554. gap: 12px
  2555. .info-value
  2556. flex: 1
  2557. :deep(.van-button)
  2558. height: 32px
  2559. border-radius: 8px
  2560. font-size: 13px
  2561. font-weight: 600
  2562. transition: all 0.3s ease
  2563. &.van-button--danger
  2564. border-color: #ee0a24
  2565. color: #ee0a24
  2566. &:active
  2567. background-color: #fee
  2568. &.van-button--default
  2569. border-color: #1989fa
  2570. color: #1989fa
  2571. &:active
  2572. background-color: #e6f4ff
  2573. // ==================== 对话框优化 ====================
  2574. :deep(.van-dialog)
  2575. border-radius: 16px
  2576. overflow: hidden
  2577. .van-dialog__header
  2578. padding: 16px 20px 12px
  2579. font-size: 18px
  2580. font-weight: 600
  2581. .van-dialog__content
  2582. padding: 0 20px 16px
  2583. max-height: 70vh
  2584. overflow-y: auto
  2585. &::-webkit-scrollbar
  2586. width: 4px
  2587. &::-webkit-scrollbar-thumb
  2588. background: #c8c9cc
  2589. border-radius: 2px
  2590. .van-dialog__footer
  2591. padding: 12px 20px 16px
  2592. border-top: 1px solid #ebedf0
  2593. .van-button
  2594. height: 40px
  2595. border-radius: 8px
  2596. font-weight: 600
  2597. // ==================== 对话框优化 ====================
  2598. :deep(.van-dialog)
  2599. border-radius: 16px
  2600. overflow: hidden
  2601. &.compact-dialog
  2602. .van-dialog__header
  2603. padding: 12px 16px
  2604. font-size: 16px
  2605. font-weight: 600
  2606. .van-dialog__content
  2607. padding: 0
  2608. .van-dialog__footer
  2609. padding: 8px 16px
  2610. .van-button
  2611. height: 36px
  2612. font-size: 14px
  2613. border-radius: 8px
  2614. // ==================== 紧凑型对话框内容样式 ====================
  2615. .dialog-content
  2616. max-height: 75vh
  2617. overflow-y: auto
  2618. padding: 8px 12px
  2619. .field-group
  2620. margin-bottom: 8px
  2621. &.date-fields
  2622. display: flex
  2623. flex-direction: column
  2624. gap: 4px
  2625. .compact-field
  2626. margin-bottom: 4px !important
  2627. :deep(.van-cell)
  2628. padding: 8px 12px
  2629. min-height: 40px
  2630. :deep(.van-field__label)
  2631. font-size: 13px
  2632. font-weight: 500
  2633. width: 60px
  2634. color: #646566
  2635. :deep(.van-field__control)
  2636. font-size: 13px
  2637. &.date-field
  2638. :deep(.van-field__label)
  2639. width: 65px
  2640. .compact-divider
  2641. margin: 8px 0 6px 0 !important
  2642. font-size: 13px
  2643. font-weight: 600
  2644. :deep(.van-divider__content)
  2645. padding: 0 8px
  2646. // 配件信息样式
  2647. .accessories-section
  2648. margin: 8px 0
  2649. .accessories-content
  2650. padding: 6px 8px
  2651. background: #f8f9fa
  2652. border-radius: 8px
  2653. .accessory-item
  2654. font-size: 11px
  2655. line-height: 1.4
  2656. margin-bottom: 4px
  2657. .accessory-highlight
  2658. color: #2ca547
  2659. font-weight: 600
  2660. .accessory-desc
  2661. color: #277b39
  2662. .accessory-notice
  2663. font-size: 11px
  2664. margin-top: 6px
  2665. padding-top: 6px
  2666. border-top: 1px solid #ebedf0
  2667. .accessory-warning
  2668. color: #ff2020
  2669. font-weight: 600
  2670. // 图片区域样式
  2671. .photo-section
  2672. margin: 8px 0
  2673. .photo-grid
  2674. display: grid
  2675. grid-template-columns: repeat(6, 1fr)
  2676. gap: 6px
  2677. padding: 6px 0
  2678. .photo-item
  2679. .compact-image
  2680. border-radius: 6px
  2681. border: 1px solid #ebedf0
  2682. transition: transform 0.2s ease, box-shadow 0.2s ease
  2683. &:hover
  2684. transform: scale(1.05)
  2685. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15)
  2686. // 拍照按钮样式
  2687. .camera-buttons
  2688. display: flex
  2689. gap: 8px
  2690. margin-top: 12px
  2691. padding-top: 8px
  2692. border-top: 1px solid #f0f0f0
  2693. .camera-btn
  2694. flex: 1
  2695. height: 36px
  2696. font-size: 13px
  2697. border-radius: 8px
  2698. font-weight: 500
  2699. // ==================== 图片预览优化 ====================
  2700. :deep(.van-image-preview)
  2701. .custom-toolbar
  2702. position: absolute
  2703. top: 20px
  2704. right: 20px
  2705. display: flex
  2706. align-items: center
  2707. gap: 12px
  2708. padding: 8px 12px
  2709. background: rgba(0, 0, 0, 0.6)
  2710. border-radius: 20px
  2711. color: white
  2712. font-size: 14px
  2713. z-index: 1000
  2714. .van-button
  2715. border-radius: 6px
  2716. font-size: 12px
  2717. padding: 4px 8px
  2718. // ==================== 浮动按钮样式 ====================
  2719. .submit-floating-btn
  2720. position: fixed
  2721. bottom: 80px
  2722. right: 20px
  2723. z-index: 999
  2724. :deep(.van-floating-bubble)
  2725. background: linear-gradient(135deg, #1989fa 0%, #1976d2 100%)
  2726. box-shadow: 0 4px 12px rgba(25, 137, 250, 0.4)
  2727. transition: all 0.3s ease
  2728. &:hover
  2729. transform: scale(1.05)
  2730. box-shadow: 0 6px 16px rgba(25, 137, 250, 0.5)
  2731. &:active
  2732. transform: scale(0.95)
  2733. .van-icon
  2734. color: white
  2735. font-size: 18px
  2736. // ==================== 弹出层优化 ====================
  2737. :deep(.van-popup)
  2738. border-radius: 16px 16px 0 0
  2739. overflow: hidden
  2740. :deep(.van-picker)
  2741. .van-picker__toolbar
  2742. padding: 12px 16px
  2743. background: #f8f9fa
  2744. .van-picker__confirm
  2745. color: #1989fa
  2746. font-weight: 600
  2747. .van-picker__cancel
  2748. color: #969799
  2749. font-weight: 600
  2750. // ==================== 响应式优化 ====================
  2751. // 小屏幕设备适配 (iPhone SE, 小屏幕手机)
  2752. @media (max-width: 375px)
  2753. // 初始扫描页面优化
  2754. .init-container
  2755. padding: 12px 6px
  2756. margin-top: 15px // 增加上边距确保不被遮挡
  2757. min-height: calc(100vh - 120px) // 调整最小高度
  2758. .content-tips
  2759. margin-bottom: 12px
  2760. :deep(.van-notice-bar)
  2761. font-size: 13px
  2762. padding: 8px 12px
  2763. .van-notice-bar__content
  2764. min-width: 0
  2765. .van-notice-bar__text
  2766. font-size: 13px
  2767. line-height: 1.4
  2768. .scan-returned-content
  2769. padding: 16px 12px
  2770. margin-top: 8px
  2771. border-radius: 12px
  2772. // 小屏幕固定提示条适配
  2773. &.with-fixed-notice
  2774. margin-top: 60px // 小屏幕适配:减少上边距
  2775. .input-group
  2776. margin-bottom: 12px
  2777. :deep(.van-field)
  2778. .van-field__label
  2779. font-size: 13px
  2780. .van-field__control
  2781. font-size: 14px
  2782. .button-group
  2783. .confirm-btn
  2784. height: 40px
  2785. font-size: 15px
  2786. // 退货信息区域优化
  2787. .return-info-section
  2788. &.with-fixed-notice
  2789. margin-top: 60px // 小屏幕适配:减少上边距
  2790. // 商品卡片优化
  2791. .returned-detail-list
  2792. .card-div
  2793. margin: 6px 0
  2794. .card-div-content
  2795. padding: 10px
  2796. .info-row
  2797. font-size: 12px
  2798. margin-bottom: 8px
  2799. .info-label
  2800. width: 70px
  2801. font-size: 11px
  2802. .info-value
  2803. font-size: 12px
  2804. // 导航栏优化
  2805. .van-nav-bar
  2806. .left-btn
  2807. font-size: 14px
  2808. padding-right: 16px
  2809. .right-btn
  2810. font-size: 14px
  2811. // 区块头部优化
  2812. .section-header
  2813. padding: 10px 14px
  2814. margin: 6px
  2815. .section-title
  2816. font-size: 15px
  2817. // 紧凑型对话框小屏幕优化
  2818. .dialog-content
  2819. padding: 6px 8px
  2820. .field-group
  2821. margin-bottom: 6px
  2822. .compact-field
  2823. :deep(.van-cell)
  2824. padding: 6px 8px
  2825. min-height: 36px
  2826. :deep(.van-field__label)
  2827. font-size: 12px
  2828. width: 55px
  2829. :deep(.van-field__control)
  2830. font-size: 12px
  2831. .photo-section
  2832. .photo-grid
  2833. grid-template-columns: repeat(5, 1fr)
  2834. gap: 4px
  2835. .camera-buttons
  2836. gap: 6px
  2837. .camera-btn
  2838. height: 32px
  2839. font-size: 12px
  2840. // 浮动按钮小屏幕适配
  2841. .submit-floating-btn
  2842. bottom: 70px
  2843. right: 16px
  2844. :deep(.van-floating-bubble)
  2845. width: 50px
  2846. height: 50px
  2847. .van-icon
  2848. font-size: 16px
  2849. // 中等屏幕设备适配 (iPhone 6/7/8)
  2850. @media (max-width: 414px) and (min-width: 376px)
  2851. .init-container
  2852. padding: 16px 8px
  2853. margin-top: 20px // 适当增加上边距
  2854. min-height: calc(100vh - 110px)
  2855. .scan-returned-content
  2856. padding: 18px 14px
  2857. // 中等屏幕固定提示条适配
  2858. &.with-fixed-notice
  2859. margin-top: 62px // 中等屏幕适中的上边距
  2860. .input-group
  2861. :deep(.van-field)
  2862. .van-field__control
  2863. font-size: 15px
  2864. .button-group
  2865. .confirm-btn
  2866. height: 42px
  2867. font-size: 16px
  2868. // 中等屏幕退货信息区域优化
  2869. .return-info-section
  2870. &.with-fixed-notice
  2871. margin-top: 62px // 中等屏幕适中的上边距
  2872. // 紧凑型对话框中等屏幕优化
  2873. .dialog-content
  2874. padding: 8px 10px
  2875. .compact-field
  2876. :deep(.van-field__label)
  2877. font-size: 13px
  2878. width: 60px
  2879. :deep(.van-field__control)
  2880. font-size: 13px
  2881. // 中等屏幕浮动按钮适配
  2882. .submit-floating-btn
  2883. bottom: 75px
  2884. right: 18px
  2885. // 大屏幕设备适配 (iPhone Plus, 大屏手机)
  2886. @media (min-width: 415px)
  2887. .init-container
  2888. max-width: 500px
  2889. margin: 25px auto 0 auto // 增加上边距并居中
  2890. padding: 20px 10px
  2891. min-height: calc(100vh - 130px)
  2892. .scan-returned-content
  2893. padding: 24px 18px
  2894. // 大屏幕固定提示条适配
  2895. &.with-fixed-notice
  2896. margin-top: 65px // 大屏幕标准上边距
  2897. .input-group
  2898. :deep(.van-field)
  2899. .van-field__control
  2900. font-size: 16px
  2901. .button-group
  2902. .confirm-btn
  2903. height: 46px
  2904. font-size: 17px
  2905. max-width: 300px
  2906. margin: 0 auto
  2907. // 大屏幕浮动按钮适配
  2908. .submit-floating-btn
  2909. bottom: 80px
  2910. right: 20px
  2911. :deep(.van-floating-bubble)
  2912. width: 56px
  2913. height: 56px
  2914. .van-icon
  2915. font-size: 20px
  2916. // 横屏适配
  2917. @media (orientation: landscape) and (max-height: 500px)
  2918. .content
  2919. padding-top: 5px // 横屏时减少上边距
  2920. .init-container
  2921. padding: 8px 4px
  2922. margin-top: 5px
  2923. min-height: calc(100vh - 80px)
  2924. justify-content: flex-start // 横屏时顶部对齐而非居中
  2925. .content-tips
  2926. margin-bottom: 8px
  2927. .scan-returned-content
  2928. padding: 12px 10px
  2929. .input-group
  2930. margin-bottom: 8px
  2931. .button-group
  2932. .confirm-btn
  2933. height: 36px
  2934. // 横屏退货信息区域优化
  2935. .return-info-section
  2936. &.with-fixed-notice
  2937. margin-top: 55px // 横屏模式使用更紧凑的上边距
  2938. // ==================== 空状态优化 ====================
  2939. :deep(.van-empty)
  2940. padding: 30px 20px
  2941. .van-empty__image
  2942. width: 100px
  2943. height: 100px
  2944. .van-empty__description
  2945. color: #969799
  2946. font-size: 14px
  2947. margin-top: 12px
  2948. // ==================== 按钮通用样式 ====================
  2949. :deep(.van-button)
  2950. transition: all 0.3s ease
  2951. max-width: 100%
  2952. box-sizing: border-box
  2953. &:active
  2954. transform: scale(0.98)
  2955. &.van-button--primary
  2956. background: linear-gradient(135deg, #1989fa 0%, #1976d2 100%)
  2957. border: none
  2958. box-shadow: 0 2px 8px rgba(25, 137, 250, 0.3)
  2959. &:active
  2960. box-shadow: 0 4px 12px rgba(25, 137, 250, 0.4)
  2961. // ==================== 全局防溢出规则 ====================
  2962. *
  2963. box-sizing: border-box
  2964. body, html
  2965. overflow-x: hidden
  2966. max-width: 100vw
  2967. // 防止长文本溢出
  2968. .van-field__control input,
  2969. .van-field__control textarea,
  2970. :deep(.van-field__control input),
  2971. :deep(.van-field__control textarea)
  2972. word-break: break-all
  2973. overflow-wrap: break-word
  2974. max-width: 100%
  2975. // 防止图片溢出
  2976. :deep(.van-image img)
  2977. max-width: 100%
  2978. height: auto
  2979. // 防止容器溢出
  2980. .van-cell-group,
  2981. .van-cell,
  2982. .card-div,
  2983. :deep(.van-cell-group),
  2984. :deep(.van-cell),
  2985. :deep(.card-div)
  2986. max-width: 100%
  2987. overflow: hidden
  2988. box-sizing: border-box
  2989. </style>