index.vue 103 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285
  1. <!--
  2. Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
  3. https://www.mall4j.com/
  4. 未经允许,不可做商业用途!
  5. 版权所有,侵权必究!
  6. -->
  7. <template>
  8. <div
  9. class="Mall4j page-detail"
  10. @click="onClosePop"
  11. >
  12. <!-- 面包屑导航 -->
  13. <div
  14. v-if="!scoreFee"
  15. class="crumbs-shop"
  16. >
  17. <div class="content">
  18. <div class="crumbs" />
  19. <div class="shop-box">
  20. <router-link
  21. v-if="prodInfo.shopId"
  22. :to="{
  23. path: '/shop-index',
  24. query: {
  25. sid: prodInfo.shopId
  26. }
  27. }"
  28. class="shop"
  29. >
  30. <i
  31. v-if="shopInfo && shopInfo.shopId === 1"
  32. class="self"
  33. >{{
  34. $t('prodDetail.selfEmployed')
  35. }}</i>
  36. <i
  37. v-if="shopInfo && shopInfo.shopId !== 1"
  38. class="shop-icon"
  39. />
  40. {{ shopInfo ? shopInfo.shopName : '' }}
  41. </router-link>
  42. <a
  43. href="javascript:void(0)"
  44. class="im-chat"
  45. @click="toChatIm(prodInfo)"
  46. ><span class="btn-im" />{{ $t('prodDetail.contactCustomerService') }}</a>
  47. <span
  48. v-if="!isShopCollection"
  49. class="favourite"
  50. @click="toggleShopCollect"
  51. >
  52. <i class="favourite-icon" />{{ $t('prodDetail.collectionStores') }}
  53. </span>
  54. <span
  55. v-else
  56. class="favourite active"
  57. @click="toggleShopCollect"
  58. >
  59. <i class="favourite-icon" />{{ $t('prodDetail.collectedStores') }}
  60. </span>
  61. <a
  62. href="javascript:void(0)"
  63. class="im-chat"
  64. @click="onToQualifications(prodInfo)"
  65. >
  66. <img
  67. src="@/assets/images/qualifications.png"
  68. style="width: 16px;height: 16px;margin-right: 5px;float: left"
  69. >
  70. {{ $t('shopInfo.qualifications') }}
  71. </a>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- /面包屑导航 -->
  76. <!-- 积分面包屑导航 -->
  77. <div
  78. v-if="scoreFee"
  79. class="crumbs-shop"
  80. >
  81. <div class="content">
  82. <div class="crumbs" />
  83. </div>
  84. </div>
  85. <!-- /积分面包屑导航 -->
  86. <div class="content">
  87. <div class="detail-up">
  88. <!-- 商品图片 -->
  89. <div class="img">
  90. <div class="big-img">
  91. <ImgShow
  92. v-if="(!prodInfo.video || !showVideo) && prodInfo.pic"
  93. :src="prodInfo.pic"
  94. @error="changePicUrl"
  95. />
  96. <img
  97. v-if="(!prodInfo.video || !showVideo) && !prodInfo.pic"
  98. src="@/assets/img/def.png"
  99. alt
  100. >
  101. <!-- 商品视频 -->
  102. <video
  103. v-show="showVideo"
  104. id="prodVideo"
  105. class="big-img prod-video"
  106. :src="checkFileUrl(prodInfo.video)"
  107. controls
  108. @error="prodInfo.video=''"
  109. />
  110. <!-- 商品视频end -->
  111. <div
  112. v-if="showPlayBtn && prodInfo.video"
  113. class="oper-btn"
  114. >
  115. <img
  116. src="@/assets/images/play.png"
  117. :alt="$t('prodDetail.play')"
  118. @click="playVideo"
  119. >
  120. </div>
  121. <div
  122. v-if="!showPlayBtn && showVideo"
  123. class="close-btn"
  124. >
  125. <img
  126. src="@/assets/images/close.png"
  127. :alt="$t('userCenter.close')"
  128. @click="stopVideo"
  129. >
  130. </div>
  131. </div>
  132. <div class="small-img">
  133. <i
  134. class="left-arrow"
  135. :class="{
  136. limit: prodImgs.length - 5 <= 0 || offsetCount < 1
  137. }"
  138. @click="prevImg"
  139. >&lt;</i>
  140. <i
  141. class="right-arrow"
  142. :class="{
  143. limit:
  144. prodImgs.length - 5 <= 0 ||
  145. offsetCount >= prodImgs.length - 5
  146. }"
  147. @click="nextImg"
  148. >&gt;</i>
  149. <div class="img-box">
  150. <div
  151. ref="carouserRef"
  152. class="offset-box"
  153. >
  154. <div
  155. v-for="(item, index) in prodImgs"
  156. :key="index"
  157. class="item"
  158. :class="{ active: item.isActive }"
  159. @mouseover="changeProdImg(index)"
  160. >
  161. <ImgShow :src="item.img" />
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. <!-- /商品图片 -->
  168. <!-- 商品详情 -->
  169. <div
  170. v-if="!scoreFee"
  171. class="info"
  172. >
  173. <div class="name-box">
  174. <div class="name">
  175. {{ prodInfo.prodName }}
  176. </div>
  177. <div class="des">
  178. <div
  179. :title="prodInfo.brief"
  180. class="brief"
  181. >
  182. {{ prodInfo.brief }}
  183. </div>
  184. <span
  185. v-if="discountDet.length && prodInfo.prodType !== 5 && (!productActivity.seckill || productActivity.seckill && countdown.obj.signs===0)"
  186. class="discount-info"
  187. >
  188. <a
  189. href="javascript:void(0)"
  190. class="go-discount"
  191. @click="toDiscountDetail(discountDet[0].discountId)"
  192. >
  193. {{ discountDet[0].discountName
  194. }}{{ $t('prodDetail.specialZone') }},{{
  195. discountDet[0].endTime.substring(0, 10)
  196. }}{{ $t('prodDetail.deadline') }},{{
  197. $t('prodDetail.grabYourCopyNow')
  198. }}
  199. <span class="arr">&gt;&gt;</span>
  200. </a>
  201. </span>
  202. </div>
  203. </div>
  204. <!-- 预售商品 -->
  205. <div
  206. v-if="prodInfo.preSellStatus === 1 && prodInfo.prodType !== 1 && prodInfo.prodType !== 2"
  207. class="activity"
  208. >
  209. <div class="name flash-sale">
  210. {{ $t('prodDetail.preSale') }}
  211. </div>
  212. <div class="limit">
  213. {{ $t('prodDetail.expected') }}&nbsp;&nbsp;{{
  214. prodInfo.preSellTime
  215. }}&nbsp;&nbsp;{{ $t('prodDetail.startShipping') }}
  216. </div>
  217. </div>
  218. <!-- /预售商品 -->
  219. <!-- 拼团商品 -->
  220. <div
  221. v-if="(prodInfo.prodType === 1 && productActivity.groupActivity) || (prodInfo.prodType === 2 && productActivity.seckill)"
  222. class="activity"
  223. >
  224. <div :class="['name', prodInfo.prodType === 2 ? 'flash-sale' : 'group-buy']">
  225. <span v-if="prodInfo.prodType === 1">{{ $t('prodDetail.groupingTogetherForMoreBenefits') }}</span>
  226. <span v-else> {{ productActivity.seckill.prodName ? productActivity.seckill.prodName : $t('spike.secondsOfActivity') }} </span>
  227. </div>
  228. <div class="limit">
  229. {{ $t('prodDetail.distanceActivity') }}
  230. {{ countdown.obj.signs ? $t('end') : $t('start') }}
  231. {{ $t('onlyLeft') }}:
  232. <span
  233. v-if="countdown.obj.day"
  234. class="time"
  235. >{{ countdown.obj.day }}{{ $t('day')
  236. }}{{ countdown.obj.hou }}:{{ countdown.obj.min }}:{{
  237. countdown.obj.sec
  238. }}</span>
  239. <span
  240. v-else
  241. class="time"
  242. >{{ countdown.obj.hou }}:{{ countdown.obj.min }}:{{
  243. countdown.obj.sec
  244. }}</span>
  245. </div>
  246. </div>
  247. <!-- /拼团商品 -->
  248. <div class="price-box">
  249. <div class="item goods-price">
  250. <span class="tit">{{ $t('price') }}</span>
  251. <div class="con">
  252. <div
  253. v-if="prodInfo.prodType === 2 && (countdownFlag || seckillDefSku)"
  254. class="price"
  255. >
  256. <span class="big">{{ parsePrice(defaultSku.seckillPrice || seckillDefSku?.seckillPrice)[0] }}</span>
  257. .{{ parsePrice(defaultSku.seckillPrice || seckillDefSku?.seckillPrice)[1] }}
  258. </div>
  259. <div
  260. v-else
  261. class="price"
  262. >
  263. <span class="big">{{ parsePrice(productActivity.groupActivity ? defaultSku.actPrice : defaultSku.price)[0] }}</span>
  264. .{{ parsePrice(productActivity.groupActivity ? defaultSku.actPrice : defaultSku.price)[1] }}
  265. </div>
  266. <!-- 商品销售价大于市场价时 不展示市场价 -->
  267. <div
  268. v-if="
  269. ((productActivity.groupActivity ? defaultSku.price : defaultSku.oriPrice) &&
  270. (productActivity.groupActivity ? defaultSku.price : defaultSku.oriPrice) > (productActivity.groupActivity ? defaultSku.actPrice : defaultSku.price))
  271. || (prodInfo.prodType === 2 && (defaultSku.priceFee || defaultSku.price) > (defaultSku.seckillPrice || seckillDefSku?.seckillPrice))
  272. "
  273. class="old-price"
  274. >
  275. ¥{{ productActivity.groupActivity ? defaultSku.price : (defaultSku.oriPrice || defaultSku.price || defaultSku.priceFee || seckillDefSku?.priceFee) }}
  276. </div>
  277. </div>
  278. <template v-if="findSku && prodInfo.deliveryModeVO?.hasShopDelivery">
  279. <span class="tit">{{
  280. $t('prodDetail.remainingInventory')
  281. }}</span>
  282. <div class="con">
  283. {{ prodInfo.prodType === 2 && countdownFlag ? defaultSku.seckillStocks : defaultSku.stocks }}
  284. </div>
  285. </template>
  286. </div>
  287. <!-- 预售 -->
  288. <div
  289. v-if="!(prodInfo.prodType !== 2 && prodInfo.prodType !== 1) && prodInfo.preSellStatus === 1"
  290. class="item coupons"
  291. >
  292. <span class="tit">{{ $t('prodDetail.preSale') }}</span>
  293. <div class="con pre-sale-red">
  294. {{ $t('prodDetail.thisItemIsAPreSaleItem') }},{{
  295. $t('prodDetail.expected')
  296. }}&nbsp;&nbsp;{{ cutDate(prodInfo.preSellTime) }}&nbsp;&nbsp;{{
  297. $t('prodDetail.startShipping')
  298. }}
  299. </div>
  300. </div>
  301. <!-- /预售 -->
  302. <!-- 领券 -->
  303. <div
  304. v-if="couponList.length > 0 && prodInfo.prodType !== 5"
  305. class="item coupons"
  306. >
  307. <span class="tit">{{ $t('vouchers') }}</span>
  308. <div class="con">
  309. <div
  310. v-for="coupon in couponList"
  311. :key="coupon.couponId"
  312. class="coupon-block"
  313. @click="receiveCoupon(coupon)"
  314. >
  315. <span
  316. v-if="coupon.couponType === 1 && $t('full') === '满'"
  317. class="conpon"
  318. >{{ $t('full') }}{{ $t('yuan') + coupon.cashCondition }}{{ $t('less')
  319. }}{{ $t('yuan') }}{{ coupon.reduceAmount }}</span>
  320. <span
  321. v-if="coupon.couponType === 2 && $t('full') === '满'"
  322. class="conpon"
  323. >{{ $t('full') }}{{ $t('yuan') + coupon.cashCondition }}{{ $t('enjoy')
  324. }}{{ coupon.couponDiscount }}{{ $t('fold') }}</span>
  325. <!-- 英文版优惠卷样式 -->
  326. <span
  327. v-if="coupon.couponType === 1 && $t('full') !== '满'"
  328. class="conpon"
  329. >{{ $t('yuan') + coupon.reduceAmount + ' off over '
  330. }}{{ $t('yuan') + coupon.cashCondition }}</span>
  331. <span
  332. v-if="coupon.couponType === 2 && $t('full') !== '满'"
  333. class="conpon"
  334. >{{ coupon.couponDiscount + '%' + ' off over' }}
  335. {{ $t('yuan') + coupon.cashCondition }}</span>
  336. </div>
  337. <router-link
  338. to="/coupons"
  339. class="more"
  340. >
  341. {{ $t('more') }}
  342. <i class="arrow">>></i>
  343. </router-link>
  344. </div>
  345. </div>
  346. <!-- /领券 -->
  347. <!-- 限时特惠 -->
  348. <div
  349. v-if="(discountDet.length && !productActivity.groupActivity && (!productActivity.seckill || productActivity.seckill && countdown.obj.signs===0)) || productActivity.giveaway && productActivity.giveaway.giveawayProds.length"
  350. class="item discount"
  351. >
  352. <span class="tit">{{ $t('promotion') }}</span>
  353. <div class="con">
  354. <div class="discount-con">
  355. <div
  356. v-if="productActivity.giveaway && productActivity.giveaway.giveawayProds.length"
  357. class="discount-item"
  358. >
  359. <div class="type">
  360. {{ $t('prodDetail.gift') }}
  361. </div>
  362. <router-link
  363. v-for="(item, giveawayProdsIndex) in productActivity.giveaway.giveawayProds"
  364. :key="giveawayProdsIndex"
  365. :to="{
  366. path:'/detail',
  367. query:{
  368. prodId:item.prodId
  369. }
  370. }"
  371. target="_blank"
  372. class="packge-item"
  373. >
  374. <ImgShow
  375. :src="item.pic"
  376. :class-list="['img']"
  377. />
  378. <div class="count">
  379. ×{{ item.giveawayNum }}
  380. </div>
  381. </router-link>
  382. </div>
  383. <div
  384. v-for="(item, index) in discountDet"
  385. :key="item.discountId"
  386. class="discount-item"
  387. :class="index > 0 ? 'item-box' : ''"
  388. >
  389. <div class="type">
  390. {{
  391. [
  392. $t('prodDetail.fullAmountReduction'),
  393. $t('prodDetail.fullPieceDiscount'),
  394. $t('prodDetail.fullDiscount'),
  395. $t('prodDetail.discountOnFullItems')
  396. ][item.discountRule]
  397. }}
  398. </div>
  399. <div class="text">
  400. {{ item.discountName }}
  401. </div>
  402. <a
  403. href="javascript:void(0)"
  404. class="det"
  405. @click="toDiscountDetail(item.discountId)"
  406. >
  407. {{ $t('more') }}
  408. <span class="arr">&gt;&gt;</span>
  409. </a>
  410. </div>
  411. </div>
  412. </div>
  413. </div>
  414. <!-- /限时特惠 -->
  415. <!-- 虚拟商品-说明 -->
  416. <div
  417. v-if="
  418. prodInfo.mold === 1 &&
  419. (prodInfo.writeOffNum !== 0 ||
  420. (prodInfo.writeOffNum === 0 && prodInfo.isRefund === 0))
  421. "
  422. class="item"
  423. >
  424. <span class="tit">{{ $t('prodDetail.instructions') }}</span>
  425. <!-- writeOffNum 0无需核销 1单次核销 -1多次核销 -->
  426. <span v-if="prodInfo.writeOffNum !== 0">
  427. <!-- writeOffTime核销有效期 -1.长期有效 0.自定义 1.当天24点前 x.x天内有效 -->
  428. <span v-if="prodInfo.writeOffTime === -1">{{
  429. $t('prodDetail.longTermValidity')
  430. }}</span>
  431. <span v-else-if="prodInfo.writeOffTime === 0">{{ $t('prodDetail.afterPurchase') }}
  432. {{ prodInfo.writeOffStart }} {{ $t('prodDetail.to') }}
  433. {{ prodInfo.writeOffEnd }}
  434. <i v-if="$t('language') === 'zh_CN'">{{
  435. $t('prodDetail.effective')
  436. }}</i></span>
  437. <span v-else-if="prodInfo.writeOffTime === 1">{{
  438. $t('prodDetail.validOnTheSameDay')
  439. }}</span>
  440. <span v-else>{{ $t('prodDetail.purchase') }}{{ prodInfo.writeOffTime
  441. }}{{ $t('prodDetail.validDay') }}</span>
  442. </span>
  443. <span v-if="prodInfo.isRefund === 0"><span v-if="prodInfo.writeOffNum !== 0">,</span>{{ $t('prodDetail.refundsAreNotAllowed') }}</span>
  444. </div>
  445. </div>
  446. <div
  447. v-if="prodInfo.skuList && prodInfo.skuList.length"
  448. class="sku-box"
  449. >
  450. <div
  451. v-for="(skuLine, key) in skuGroup"
  452. :key="key"
  453. class="items sku-text"
  454. >
  455. <span class="tit">{{ key.replace(';', '') }}</span>
  456. <div class="con">
  457. <span
  458. v-for="(skuLineItem, index) in skuLine"
  459. :key="index"
  460. class="item"
  461. :class="[
  462. selectedProp.find(
  463. el => el.key === key.replace(';', '') && el.value === skuLineItem
  464. )
  465. ? 'active'
  466. : '',
  467. isSkuLineItemNotOptional(
  468. allProperties,
  469. selectedPropObj,
  470. key,
  471. skuLineItem,
  472. propKeys
  473. )
  474. ? 'not-optional'
  475. : ''
  476. ]"
  477. @click="toChooseItem(skuLineItem, key, $event)"
  478. >{{ skuLineItem }}</span>
  479. </div>
  480. </div>
  481. <div
  482. v-if="prodInfo.mold === 2 && combineList?.length > 0"
  483. class="items"
  484. >
  485. <span class="tit">{{ $t('prodDetail.comboDetail') }}</span>
  486. <div class="con">
  487. <combine-detail
  488. :combine="combineList"
  489. />
  490. </div>
  491. </div>
  492. </div>
  493. <!-- 计数器 -->
  494. <div
  495. v-if="prodInfo.deliveryModeVO?.hasShopDelivery"
  496. class="sku-box"
  497. >
  498. <div class="items">
  499. <span class="tit">{{ $t('quantity') }}</span>
  500. <div class="con">
  501. <div class="goods-number">
  502. <span
  503. :class="['reduce', prohibit1 ? 'limit' : '']"
  504. @click="reduce"
  505. >-</span>
  506. <input
  507. v-model="prodNum"
  508. type="number"
  509. class="number"
  510. oninput="value=value.replace(/[^\d]/g,'')"
  511. @blur="judgeInput"
  512. >
  513. <span
  514. :class="['increase', prohibit2 ? 'limit' : '']"
  515. @click="increase"
  516. >+</span>
  517. </div>
  518. <!-- 秒杀/拼团限购提示 -->
  519. <div
  520. v-if="countdown.obj.signs && ((prodInfo.prodType === 1 && productActivity.groupActivity?.maxNum > 0) || (prodInfo.prodType === 2 && productActivity.seckill?.maxNum > 0))"
  521. class="purchase-limit"
  522. >
  523. {{ $t('prodDetail.eventLimit') }}
  524. <span class="font-red"> &nbsp;{{ prodInfo.prodType === 1 ? productActivity.groupActivity?.maxNum : productActivity.seckill?.maxNum }}&nbsp;</span>
  525. {{ $t('prodDetail.piece') }}
  526. </div>
  527. </div>
  528. </div>
  529. </div>
  530. <!-- 配送 -->
  531. <!-- 虚拟商品和组合选品不显示 -->
  532. <div
  533. v-if="addrInfo && prodInfo.mold !== 1 && prodInfo.mold !== 3 && prodInfo.prodType !== 5"
  534. class="sku-box"
  535. >
  536. <div
  537. v-if="prodInfo.deliveryModeVO?.hasShopDelivery"
  538. class="items"
  539. >
  540. <span class="tit">{{ $t('prodDetail.delivery') }}</span>
  541. <div class="delivery-con">
  542. <div
  543. class="delivery-info"
  544. @click.stop="onOpenDeliveryPop()"
  545. >
  546. {{ addrInfo.province }} {{ addrInfo.city }} {{ addrInfo.area }}
  547. <el-icon>
  548. <ArrowDownBold />
  549. </el-icon>
  550. </div>
  551. <div
  552. v-if="isShowDeliveryPop"
  553. class="delivery-pop"
  554. >
  555. <div
  556. v-for="item in deliveryList"
  557. :key="item.addrId"
  558. class="delivery-item"
  559. :class="{active: item.addrId === addrInfo.addrId}"
  560. @click.stop="onSelectDelivery(item.addrId)"
  561. >
  562. {{ item.receiver }}、{{ item.mobile }}、{{ item.province }}{{ item.city }}{{ item.area }}{{ item.addr }}
  563. </div>
  564. </div>
  565. <div class="delivery-free">
  566. <span v-if="([0, 1, 2].includes(prodInfo.prodType) && (!findSku || !defaultSku.warehouseStock))">
  567. {{ $t('prodDetail.noStockTips') }}
  568. </span>
  569. <span v-else-if="!isDelivery">{{ $t('prodDetail.notDeliveryTips') }}</span>
  570. <span v-else-if="!defaultSku.isHasStock">{{ $t('prodDetail.noStockRegionTips') }}</span>
  571. <span v-else>{{ totalTransFee===0 ? $t('prodDetail.freeShipping') :`${$t('prodDetail.shippingFee')} ¥ ${totalTransFee.toFixed(2)}` }}</span>
  572. </div>
  573. </div>
  574. </div>
  575. </div>
  576. <!-- 商品按钮 -->
  577. <div
  578. class="btns"
  579. :class="prodInfo.prodType === 1?'group-btn':''"
  580. >
  581. <!-- 普通商品按钮 -->
  582. <template v-if="prodInfo.prodType === 0 || (prodInfo.prodType === 1 && !productActivity.groupActivity || prodInfo.prodType === 2 && !productActivity.seckill)">
  583. <!-- 立即购买 -->
  584. <a
  585. v-if="!prodInfo.deliveryModeVO?.hasShopDelivery || (findSku && defaultSku.stocks)"
  586. href="javascript:void(0)"
  587. :class="['buy-now', isGrayBtn ? 'shortage' : '' ]"
  588. @click="buyNow"
  589. >{{ $t('buyNow') }}
  590. <span
  591. v-if="!prodInfo.deliveryModeVO?.hasShopDelivery"
  592. class="qr-code"
  593. >
  594. <span class="text"> {{ $t('prodDetail.scanCode') }}</span>
  595. <div :class="['code-img', !prodInfo.deliveryModeVO?.hasShopDelivery ? getQrcode('getQrCode') : 'display-none']">
  596. <canvas id="getQrCode" />
  597. </div>
  598. </span>
  599. </a>
  600. <!-- 无货: 禁用sku; 缺货: 库存为0 -->
  601. <a
  602. v-if="prodInfo.deliveryModeVO.hasShopDelivery && (!findSku || !defaultSku.stocks)"
  603. href="javascript:void(0)"
  604. class="shortage"
  605. >{{ !findSku ? $t('prodDetail.productOutOfStock') : $t('prodDetail.productNotInStock') }}</a>
  606. <!-- 加入购物车 -->
  607. <a
  608. v-if="prodInfo.preSellStatus !== 1 && findSku && defaultSku.stocks&& prodInfo.deliveryModeVO.hasShopDelivery"
  609. href="javascript:void(0)"
  610. :class="['add-cart', !defaultSku.isHasStock || !isDelivery ? 'shortage' : '']"
  611. @click="addToCart"
  612. >{{ $t('prodDetail.addToCart') }}</a>
  613. </template>
  614. <!-- 普通商品按钮 end -->
  615. <!-- 团购商品按钮 -->
  616. <template v-if="prodInfo.prodType === 1 && productActivity.groupActivity">
  617. <a
  618. v-if="findSku"
  619. href="javascript:void(0)"
  620. :class="[
  621. 'build-group',
  622. countdown.obj.signs ? '' : 'disabled-gray',
  623. store.locale === 'en' ? 'en' : ''
  624. ]"
  625. >
  626. <span class="group-price">
  627. ¥{{ defaultSku.actPrice ? defaultSku.actPrice.toFixed(2) : '' }}
  628. </span>
  629. <span class="group-text">
  630. {{ $t('prodDetail.iWantToOpenAGroup') }}
  631. </span>
  632. <div class="group-code">
  633. <span class="text">
  634. {{ countdown.obj.signs ? $t('prodDetail.cellPhoneScanCodeOpenGroup') : $t('prodDetail.groupingActivitiesHaveNotStarted') }}
  635. </span>
  636. <div :class="['code-img', countdown.obj.signs ? '' : 'display-none']">
  637. <canvas id="groupQrcode" />
  638. </div>
  639. </div>
  640. </a>
  641. <a
  642. href="javascript:void(0)"
  643. :class="[
  644. 'alone-group',
  645. !findSku || !defaultSku.isHasStock || !isDelivery ? 'cannotbuy' : '',
  646. store.locale === 'en' ? 'en' : ''
  647. ]"
  648. @click="buyNow"
  649. >
  650. <span class="group-price">¥{{ Number(defaultSku.price).toFixed(2) }}</span>
  651. <span class="group-text">{{ $t(`prodDetail.${prodInfo.prodType === 1 ? 'separatePurchase' : 'grabYourCopyNow'}`) }}</span>
  652. <span
  653. v-if="!prodInfo.deliveryModeVO?.hasShopDelivery"
  654. class="qr-code"
  655. >
  656. <span class="text">{{ $t('prodDetail.scanCode') }}</span>
  657. <div :class="['code-img', !prodInfo.deliveryModeVO?.hasShopDelivery ? getQrcode('groupQrcodeTwo') : 'display-none']">
  658. <canvas id="groupQrcodeTwo" />
  659. </div>
  660. </span>
  661. </a>
  662. <a
  663. v-if="findSku && defaultSku.stocks&&prodInfo.deliveryModeVO?.hasShopDelivery"
  664. href="javascript:void(0)"
  665. class="add-cart add-cart-group"
  666. :class="['add-cart add-cart-group', defaultSku.isHasStock && isDelivery ? '' : 'disabled-gray']"
  667. @click="addToCart"
  668. >
  669. <span>{{ $t('prodDetail.addToCart') }}</span>
  670. </a>
  671. </template>
  672. <!-- 团购商品按钮 end -->
  673. <!-- 秒杀商品按钮 -->
  674. <template v-if="prodInfo.prodType === 2 && productActivity.seckill">
  675. <!-- 无货: 禁用sku; 缺货: 库存为0 -->
  676. <a
  677. v-if="!defaultSku.stationStock && !defaultSku.warehouseStock && (countdown.obj.signs === 1 && (!findSku || !defaultSku.seckillStocks)) || (!countdown.obj.signs && (!findSku || !defaultSku.stocks))"
  678. href="javascript:void(0)"
  679. class="shortage"
  680. >{{ !findSku ? $t('prodDetail.productOutOfStock') : $t('prodDetail.productNotInStock') }}</a>
  681. <a
  682. v-if="(defaultSku.seckillStocks || defaultSku.stationStock || defaultSku.warehouseStock) && countdown.obj.signs === 1"
  683. href="javascript:void(0)"
  684. class="buy-now"
  685. :class="[countdown.obj.signs && !isGrayBtn ? '' : 'disabled-gray']"
  686. @click="buyNow"
  687. >{{ $t('prodDetail.grabYourCopyNow') }}
  688. <span
  689. v-if="!prodInfo.deliveryModeVO?.hasShopDelivery"
  690. class="qr-code"
  691. >
  692. <span class="text">{{ $t('prodDetail.scanCode') }}</span>
  693. <div :class="['code-img', !prodInfo.deliveryModeVO?.hasShopDelivery ? getQrcode('groupQrcodeTwo') : 'display-none']">
  694. <canvas id="groupQrcodeTwo" />
  695. </div>
  696. </span>
  697. </a>
  698. <a
  699. v-if="defaultSku.stocks && countdown.obj.signs !== 1 "
  700. href="javascript:void(0)"
  701. :class="['buy-now', isGrayBtn ? 'shortage' : '']"
  702. @click="buyNow"
  703. >{{ $t('prodDetail.retailPricePurchase') }}
  704. <span
  705. v-if="!prodInfo.deliveryModeVO?.hasShopDelivery"
  706. class="qr-code"
  707. >
  708. <span class="text">{{ $t('prodDetail.scanCode') }}</span>
  709. <div :class="['code-img', !prodInfo.deliveryModeVO?.hasShopDelivery ? getQrcode('groupQrcodeTwo') : 'display-none']">
  710. <canvas id="groupQrcodeTwo" />
  711. </div>
  712. </span></a>
  713. </template>
  714. <!-- 秒杀商品按钮 end -->
  715. <!-- 公共按钮 -->
  716. <!-- 活动商品按钮 -->
  717. <a
  718. v-if="prodInfo.prodType === 5"
  719. href="javascript:void(0)"
  720. class="shortage"
  721. >{{ $t('prodDetail.notAvailableForPurchase') }}</a>
  722. <!-- 收藏/取选收藏 -->
  723. <a
  724. href="javascript:void(0)"
  725. :class="!isCollection ? 'collect': 'collected'"
  726. @click="toggleCollect()"
  727. >
  728. <i class="icon" />{{ !isCollection ? $t('prodDetail.collectionOfProducts') : $t('prodDetail.bookmarked') }}
  729. </a>
  730. </div>
  731. </div>
  732. <!-- 商品详情 -->
  733. <!-- 积分商品详情 -->
  734. <div
  735. v-else
  736. class="info"
  737. >
  738. <div class="name-box">
  739. <div class="name">
  740. {{ prodInfo.prodName }}
  741. </div>
  742. <div class="des">
  743. {{ prodInfo.brief }}
  744. </div>
  745. </div>
  746. <div class="price-box">
  747. <div class="item goods-price">
  748. <span class="tit">{{ $t('price') }}</span>
  749. <div class="con">
  750. <div class="price">
  751. <span class="big">{{ defaultSku.skuScore }}</span>
  752. <span class="text">{{ $t('prodDetail.points') }}</span>
  753. </div>
  754. <div
  755. v-if="defaultSku.price"
  756. class="add-symbol"
  757. >
  758. +
  759. </div>
  760. <div
  761. v-if="defaultSku.price"
  762. class="price"
  763. >
  764. <span class="big">{{ parsePrice(defaultSku.price)[0] }}</span>
  765. .{{ parsePrice(defaultSku.price)[1] }}
  766. </div>
  767. </div>
  768. <template v-if="findSku && prodInfo.deliveryModeVO?.hasShopDelivery">
  769. <span class="tit">{{ $t('prodDetail.remainingInventory') }}</span>
  770. <div class="con">
  771. {{ defaultSku.stocks }}
  772. </div>
  773. </template>
  774. </div>
  775. <!-- 销售价大于等于市场价 市场价不展示 -->
  776. <div
  777. v-if="defaultSku.oriPrice > defaultSku.price"
  778. class="item goods-price"
  779. >
  780. <span class="tit">{{ $t('prodDetail.originalPrice') }}</span>
  781. <div class="con">
  782. <div class="old-price">
  783. ¥{{ defaultSku.oriPrice.toFixed(2) }}
  784. </div>
  785. </div>
  786. </div>
  787. </div>
  788. <div
  789. v-if="prodInfo.skuList && prodInfo.skuList.length"
  790. class="sku-box"
  791. >
  792. <div
  793. v-for="(skuLine, key) in skuGroup"
  794. :key="key"
  795. class="items sku-text"
  796. >
  797. <span class="tit">{{ key.replace(';', '') }}</span>
  798. <div class="con">
  799. <span
  800. v-for="skuLineItem in skuLine"
  801. :key="skuLineItem"
  802. class="item"
  803. :class="[
  804. selectedProp.find(
  805. el => el.key === key.replace(';', '') && el.value === skuLineItem
  806. )
  807. ? 'active'
  808. : '',
  809. isSkuLineItemNotOptional(
  810. allProperties,
  811. selectedPropObj,
  812. key,
  813. skuLineItem,
  814. propKeys
  815. )
  816. ? 'not-optional'
  817. : ''
  818. ]"
  819. @click="toChooseItem(skuLineItem, key, $event)"
  820. >{{ skuLineItem }}</span>
  821. </div>
  822. </div>
  823. </div>
  824. <!-- 计数器 -->
  825. <div
  826. v-if="prodInfo.deliveryModeVO?.hasShopDelivery"
  827. class="sku-box"
  828. >
  829. <div class="items">
  830. <span class="tit">{{ $t('quantity') }}</span>
  831. <div class="con">
  832. <div class="goods-number">
  833. <span
  834. :class="['reduce', prohibit1 ? 'limit' : '']"
  835. @click="reduce"
  836. >-</span>
  837. <input
  838. v-model="prodNum"
  839. type="number"
  840. class="number"
  841. oninput="value=value.replace(/[^\d]/g,'')"
  842. @blur="judgeInput"
  843. >
  844. <span
  845. :class="['increase', prohibit2 ? 'limit' : '']"
  846. @click="increase"
  847. >+</span>
  848. </div>
  849. </div>
  850. </div>
  851. <div
  852. v-if="addrInfo && prodInfo.deliveryModeVO?.hasShopDelivery"
  853. class="items"
  854. >
  855. <span class="tit">{{ $t('prodDetail.delivery') }}</span>
  856. <div class="delivery-con">
  857. <div
  858. class="delivery-info"
  859. @click.stop="onOpenDeliveryPop()"
  860. >
  861. {{ addrInfo.province }} {{ addrInfo.city }} {{ addrInfo.area }}
  862. <el-icon>
  863. <ArrowDownBold />
  864. </el-icon>
  865. </div>
  866. <div
  867. v-if="isShowDeliveryPop"
  868. class="delivery-pop"
  869. >
  870. <div
  871. v-for="item in deliveryList"
  872. :key="item.addrId"
  873. class="delivery-item"
  874. :class="{active: item.addrId === addrInfo.addrId}"
  875. @click.stop="onSelectDelivery(item.addrId)"
  876. >
  877. {{ item.receiver }}、{{ item.mobile }}、{{ item.province }}{{ item.city }}{{ item.area }}{{ item.addr }}
  878. </div>
  879. </div>
  880. <div class="delivery-free">
  881. <span v-if="(prodInfo.prodType === 3 && (!findSku || !defaultSku.warehouseStock))">
  882. {{ $t('prodDetail.noStockTips') }}
  883. </span>
  884. <span v-else-if="!isDelivery">{{ $t('prodDetail.notDeliveryTips') }}</span>
  885. <span v-else-if="!defaultSku.isHasStock">{{ $t('prodDetail.noStockRegionTips') }}</span>
  886. <span v-else>{{ totalTransFee===0 ? $t('prodDetail.freeShipping') :`${$t('prodDetail.shippingFee')} ¥ ${totalTransFee.toFixed(2)}` }}</span>
  887. </div>
  888. </div>
  889. </div>
  890. </div>
  891. <div
  892. v-if="prodInfo.prodType !== 1"
  893. class="btns"
  894. >
  895. <a
  896. v-if="findSku && defaultSku.stocks"
  897. href="javascript:void(0)"
  898. :class="['buy-now', !isDelivery ? 'shortage' : '' ]"
  899. @click="buyNow"
  900. >{{ $t('buyNow') }}</a>
  901. <a
  902. v-else-if="!findSku"
  903. href="javascript:void(0)"
  904. class="shortage"
  905. >{{ $t('prodDetail.productNotInStock') }}</a>
  906. <a
  907. v-else-if="!defaultSku.stocks"
  908. href="javascript:void(0)"
  909. class="shortage"
  910. >{{ $t('prodDetail.productOutOfStock') }}</a>
  911. </div>
  912. </div>
  913. <!-- /积分商品详情 -->
  914. </div>
  915. <div
  916. v-if="comboList && comboList.length"
  917. class="detail-down"
  918. >
  919. <div class="introduce-box packages">
  920. <div class="tab">
  921. <div
  922. v-for="(combo,index) in comboList"
  923. :key="index"
  924. class="item"
  925. :class="{active: selectComboId === combo.comboId }"
  926. @click="selectCombo(combo.comboId)"
  927. >
  928. {{ combo.name }}
  929. </div>
  930. </div>
  931. <!-- defaultCombo -->
  932. <div
  933. v-if="defaultCombo"
  934. class="packages-content"
  935. >
  936. <div class="left">
  937. <div class="prod-box">
  938. <ImgShow
  939. :src="defaultCombo.mainProd.pic"
  940. :class-list="['img']"
  941. />
  942. <div class="prod-name">
  943. {{ defaultCombo.mainProd.prodName }}
  944. </div>
  945. <div class="price">
  946. ¥{{ price(defaultCombo.mainProd.comboPrice) }}
  947. <span class="combo-count">x {{ defaultCombo.mainProd.leastNum }}</span>
  948. </div>
  949. </div>
  950. <div
  951. v-if="defaultCombo.matchingProds.length"
  952. class="add-icon"
  953. />
  954. <div
  955. v-for="(item, index) in defaultCombo.matchingProds"
  956. :key="index"
  957. class="prod-box necessary"
  958. >
  959. <ImgShow
  960. :src="item.pic"
  961. :class-list="['img']"
  962. />
  963. <div class="prod-name">
  964. {{ item.prodName }}
  965. </div>
  966. <div class="price">
  967. <div class="price-count-con">
  968. <input
  969. type="checkbox"
  970. :style="{cursor: item.required ? 'not-allowed' : 'pointer'}"
  971. class="checkbox default"
  972. :class="{ checked: isChecked(item), default: 1 }"
  973. @click="selectComboItem(item)"
  974. >
  975. <span>¥{{ price(item.comboPrice) }}</span>
  976. </div>
  977. <span class="combo-count">x {{ item.leastNum }}</span>
  978. </div>
  979. </div>
  980. </div>
  981. <div class="right">
  982. <div class="mean-icon" />
  983. <div class="settlement-box">
  984. <div class="text">
  985. {{ $t("package.selected") }}{{ choiceCombNum }}{{ $t("package.packageItem") }}
  986. </div>
  987. <div class="text item">
  988. {{ $t("package.packagePrice") }}
  989. <span class="price-text item">¥{{ comboAmount }}</span>
  990. </div>
  991. <div
  992. class="btn item"
  993. @click="handleSelectPackage"
  994. >
  995. {{ $t("buyNow") }}
  996. </div>
  997. <div
  998. class="btn-add-cart item"
  999. @click="handleSelectPackage"
  1000. >
  1001. {{ $t("prodDetail.addToCart") }}
  1002. </div>
  1003. </div>
  1004. </div>
  1005. </div>
  1006. </div>
  1007. </div>
  1008. <div
  1009. v-if="!scoreFee"
  1010. class="detail-down detail-comment"
  1011. >
  1012. <div class="introduce-box">
  1013. <div class="tab">
  1014. <div
  1015. :class="['item', introduceOrCommentInt ? 'active' : '']"
  1016. @click="toggleIntroduceInt"
  1017. >
  1018. {{ $t('prodDetail.productIntroduction') }}
  1019. </div>
  1020. <div
  1021. :class="['item', introduceOrCommentCom ? 'active' : '']"
  1022. @click="toggleIntroduceCom"
  1023. >
  1024. {{ $t('prodDetail.productReviews') }}
  1025. <i class="number">({{ prodCommData.number }})</i>
  1026. </div>
  1027. </div>
  1028. <!-- <transition name="fade"> -->
  1029. <!-- 商品介绍 -->
  1030. <div
  1031. v-show="introduceOrCommentInt"
  1032. class="introduce"
  1033. >
  1034. <div>
  1035. <div
  1036. v-for="(params, index) in prodParameterList"
  1037. :key="index"
  1038. class="params"
  1039. >
  1040. <div
  1041. v-for="item in params"
  1042. :key="item.prodParameterId"
  1043. class="params-box"
  1044. >
  1045. <div class="key">
  1046. {{ item.parameterKey }}
  1047. </div>
  1048. <div
  1049. :title="item.parameterValue"
  1050. class="value"
  1051. >
  1052. {{ item.parameterValue }}
  1053. </div>
  1054. </div>
  1055. </div>
  1056. </div>
  1057. <div
  1058. v-if="prodInfo.content"
  1059. v-rich="prodInfo.content"
  1060. />
  1061. </div>
  1062. <div
  1063. v-if="introduceOrCommentCom"
  1064. class="comment"
  1065. >
  1066. <!-- 好评率 -->
  1067. <div class="good-rates">
  1068. <div class="score">
  1069. <div class="tit">
  1070. {{ $t('prodDetail.favorableRatingRate') }}:
  1071. </div>
  1072. <div class="con">
  1073. {{ prodCommData.positiveRating }}%
  1074. </div>
  1075. </div>
  1076. <div class="average">
  1077. <div class="item">
  1078. <div class="text">
  1079. 5.0
  1080. </div>
  1081. <div class="stars">
  1082. <i class="star" />
  1083. <i class="star" />
  1084. <i class="star" />
  1085. <i class="star" />
  1086. <i class="star" />
  1087. </div>
  1088. <div class="number">
  1089. ({{ prodCommData.scoreNumber5 }})
  1090. </div>
  1091. </div>
  1092. <div class="item">
  1093. <div class="text">
  1094. 4.0
  1095. </div>
  1096. <div class="stars">
  1097. <i class="star" />
  1098. <i class="star" />
  1099. <i class="star" />
  1100. <i class="star" />
  1101. <i class="star-gray" />
  1102. </div>
  1103. <div class="number">
  1104. ({{ prodCommData.scoreNumber4 }})
  1105. </div>
  1106. </div>
  1107. <div class="item">
  1108. <div class="text">
  1109. 3.0
  1110. </div>
  1111. <div class="stars">
  1112. <i class="star" />
  1113. <i class="star" />
  1114. <i class="star" />
  1115. <i class="star-gray" />
  1116. <i class="star-gray" />
  1117. </div>
  1118. <div class="number">
  1119. ({{ prodCommData.scoreNumber3 }})
  1120. </div>
  1121. </div>
  1122. <div class="item">
  1123. <div class="text">
  1124. 2.0
  1125. </div>
  1126. <div class="stars">
  1127. <i class="star" />
  1128. <i class="star" />
  1129. <i class="star-gray" />
  1130. <i class="star-gray" />
  1131. <i class="star-gray" />
  1132. </div>
  1133. <div class="number">
  1134. ({{ prodCommData.scoreNumber2 }})
  1135. </div>
  1136. </div>
  1137. <div class="item">
  1138. <div class="text">
  1139. 1.0
  1140. </div>
  1141. <div class="stars">
  1142. <i class="star" />
  1143. <i class="star-gray" />
  1144. <i class="star-gray" />
  1145. <i class="star-gray" />
  1146. <i class="star-gray" />
  1147. </div>
  1148. <div class="number">
  1149. ({{ prodCommData.scoreNumber1 }})
  1150. </div>
  1151. </div>
  1152. </div>
  1153. </div>
  1154. <!-- /好评率 -->
  1155. <!-- 评论列表 -->
  1156. <div class="comment-tab">
  1157. <div
  1158. class="item"
  1159. :class="evaluate === -1 ? 'active' : ''"
  1160. @click="getProdCommPageByProd(-1)"
  1161. >
  1162. {{ $t('all') }}
  1163. <span class="number">({{ prodCommData.number }})</span>
  1164. </div>
  1165. <div
  1166. class="item"
  1167. :class="evaluate === 0 ? 'active' : ''"
  1168. @click="getProdCommPageByProd(0)"
  1169. >
  1170. {{ $t('prodDetail.goodReview') }}
  1171. <span class="number">({{ prodCommData.praiseNumber }})</span>
  1172. </div>
  1173. <div
  1174. class="item"
  1175. :class="evaluate === 1 ? 'active' : ''"
  1176. @click="getProdCommPageByProd(1)"
  1177. >
  1178. {{ $t('prodDetail.mediumRating') }}
  1179. <span class="number">({{ prodCommData.secondaryNumber }})</span>
  1180. </div>
  1181. <div
  1182. class="item"
  1183. :class="evaluate === 2 ? 'active' : ''"
  1184. @click="getProdCommPageByProd(2)"
  1185. >
  1186. {{ $t('prodDetail.poorReviews') }}
  1187. <span class="number">({{ prodCommData.negativeNumber }})</span>
  1188. </div>
  1189. <div
  1190. class="item"
  1191. :class="evaluate === 3 ? 'active' : ''"
  1192. @click="getProdCommPageByProd(3)"
  1193. >
  1194. {{ $t('prodDetail.withPictures') }}
  1195. <span class="number">({{ prodCommData.picNumber }})</span>
  1196. </div>
  1197. </div>
  1198. <div
  1199. v-if="prodCommList.length"
  1200. class="comment-con"
  1201. >
  1202. <div
  1203. v-for="(item, comIndex) in prodCommList"
  1204. :key="item.prodCommId"
  1205. class="item"
  1206. >
  1207. <div class="buyer-msg">
  1208. <div class="img">
  1209. <img
  1210. v-if="item.pic"
  1211. :src="checkFileUrl(item.pic)"
  1212. alt
  1213. @error="item.pic=''"
  1214. >
  1215. <img
  1216. v-else
  1217. src="@/assets/images/buyer-img.png"
  1218. alt
  1219. >
  1220. </div>
  1221. <div class="name">
  1222. {{
  1223. item.isWriteOff ?
  1224. $t('prodDetail.userOff') :
  1225. (item.nickName
  1226. ? item.nickName
  1227. : $t('prodDetail.anonymousUser'))
  1228. }}
  1229. </div>
  1230. </div>
  1231. <div class="buyer-comment">
  1232. <div style="display: flex">
  1233. <div
  1234. class="stars"
  1235. style="width:95px"
  1236. >
  1237. <i
  1238. v-for="index in item.score"
  1239. :key="index"
  1240. class="star"
  1241. />
  1242. </div>
  1243. <div style="color: #999">
  1244. {{ item.skuName || '' }}
  1245. </div>
  1246. </div>
  1247. <div class="text">
  1248. <span style="white-space:pre-wrap;">{{
  1249. item.content
  1250. }}</span>
  1251. </div>
  1252. <div
  1253. v-if="item.pics"
  1254. class="img-box"
  1255. >
  1256. <div
  1257. v-for="(img, imgIndex1) in item.prodImgs"
  1258. :key="imgIndex1"
  1259. class="img"
  1260. >
  1261. <ImgShow
  1262. :src="img"
  1263. @handle-click="()=>imgShow(comIndex, imgIndex1)"
  1264. />
  1265. <div
  1266. v-if="showBigImg"
  1267. class="big-img-show"
  1268. >
  1269. <i
  1270. class="left-arrow"
  1271. @click="prevImgCom()"
  1272. >&lt;</i>
  1273. <i
  1274. class="right-arrow"
  1275. @click="nextImgCom()"
  1276. >&gt;</i>
  1277. <div
  1278. class="mask"
  1279. @click="closeShowBigImg()"
  1280. />
  1281. <ImgShow :src="imgPath" />
  1282. </div>
  1283. </div>
  1284. </div>
  1285. <div class="time-sku">
  1286. <span class="time">{{ item.recTime }}</span>
  1287. </div>
  1288. <div
  1289. v-if="item.replyContent"
  1290. class="seller-reply"
  1291. >
  1292. <div class="tit">
  1293. {{ $t('prodDetail.merchantResponse') }}:
  1294. </div>
  1295. <div class="con">
  1296. {{ item.replyContent }}
  1297. </div>
  1298. <div class="time">
  1299. {{ item.replyTime }}
  1300. </div>
  1301. </div>
  1302. </div>
  1303. </div>
  1304. </div>
  1305. <div
  1306. v-if="!prodCommList.length"
  1307. class="comment-con"
  1308. >
  1309. <div class="comment-empty">
  1310. {{ $t('prodDetail.noComments') }}
  1311. </div>
  1312. </div>
  1313. <!-- /评论列表 -->
  1314. <!-- 页码 -->
  1315. <div class="pagination">
  1316. <div
  1317. v-if="page.pages >= 1"
  1318. class="pages"
  1319. >
  1320. <a
  1321. href="javascript:void(0);"
  1322. class="item prev"
  1323. :class="{ default: page.current <= 1 }"
  1324. @click="getSearchProdPage(page.current - 1)"
  1325. >{{ $t('pagination.previousPage') }}</a>
  1326. <div
  1327. v-for="item in page.rainbow"
  1328. :key="item.prodId"
  1329. >
  1330. <a
  1331. v-if="item !== '...'"
  1332. href="javascript:void(0);"
  1333. class="item"
  1334. :class="{ cur: page.current === item }"
  1335. @click="getSearchProdPage(item)"
  1336. >{{ item }}</a>
  1337. <span
  1338. v-else
  1339. class="ellipsis"
  1340. >...</span>
  1341. </div>
  1342. <a
  1343. href="javascript:void(0);"
  1344. class="item next"
  1345. :class="{ default: page.current > page.pages - 1 }"
  1346. @click="getSearchProdPage(page.current + 1)"
  1347. >{{ $t('pagination.nextPage') }}</a>
  1348. <div class="total-num">
  1349. {{ $t('pagination.total') }}
  1350. <span class="num">{{ page.pages }}</span>{{ $t('pagination.page') }}
  1351. </div>
  1352. </div>
  1353. <!-- /页码 -->
  1354. </div>
  1355. </div>
  1356. <!-- /商品评论 -->
  1357. <!-- </transition> -->
  1358. </div>
  1359. <div
  1360. v-if="!scoreFee"
  1361. class="side"
  1362. >
  1363. <!-- 店内搜索 -->
  1364. <div class="shop-search">
  1365. <div class="tit">
  1366. {{ $t('prodDetail.inStoreSearch') }}
  1367. </div>
  1368. <div class="con">
  1369. <input
  1370. v-model="prodName"
  1371. type="text"
  1372. class="text"
  1373. >
  1374. <a
  1375. href="javascript:void(0)"
  1376. class="btn"
  1377. @click="toShopIndex"
  1378. />
  1379. </div>
  1380. </div>
  1381. <!-- /店内搜索 -->
  1382. <!-- 店内分类 -->
  1383. <div class="shop-category">
  1384. <div class="tit">
  1385. {{ $t('prodDetail.inStoreCategories') }}
  1386. </div>
  1387. <div class="con">
  1388. <div
  1389. v-for="(item, index) in shopCategorys"
  1390. :key="index"
  1391. class="items active"
  1392. >
  1393. <router-link
  1394. :to="{
  1395. path:'/shop-prod-list',
  1396. query:{
  1397. sid:prodInfo.shopId,
  1398. cid:item.categoryId
  1399. }
  1400. }
  1401. "
  1402. >
  1403. <div class="item-main">
  1404. {{ item.categoryName }}
  1405. </div>
  1406. </router-link>
  1407. </div>
  1408. </div>
  1409. </div>
  1410. <!-- 店内分类 -->
  1411. <!-- 热销产品 -->
  1412. <div class="sale-well">
  1413. <div class="tit">
  1414. {{ $t('prodDetail.hotProducts') }}
  1415. </div>
  1416. <div class="con">
  1417. <div
  1418. v-for="item in hotSales"
  1419. :key="item.prodId"
  1420. class="item"
  1421. >
  1422. <router-link
  1423. :to="{
  1424. path:'/detail',
  1425. query:{
  1426. prodId:item.prodId
  1427. }
  1428. }"
  1429. target="_blank"
  1430. >
  1431. <div class="goods-img">
  1432. <ImgShow :src="item.pic" />
  1433. </div>
  1434. <div class="goods-msg">
  1435. <div class="goods-name">
  1436. {{ item.prodName }}
  1437. </div>
  1438. <div class="goods-price">
  1439. <div class="price">
  1440. <span class="big">{{ parsePrice(item.price)[0] }}</span>
  1441. .{{ parsePrice(item.price)[1] }}
  1442. </div>
  1443. </div>
  1444. </div>
  1445. </router-link>
  1446. </div>
  1447. </div>
  1448. </div>
  1449. <!-- 热销产品 -->
  1450. </div>
  1451. </div>
  1452. <div
  1453. v-if="scoreFee"
  1454. class="detail-down"
  1455. >
  1456. <div class="introduce-box">
  1457. <div class="tab">
  1458. <div class="item active">
  1459. {{ $t('prodDetail.productIntroduction') }}
  1460. </div>
  1461. </div>
  1462. <!-- 商品介绍 -->
  1463. <div class="introduce">
  1464. <div
  1465. v-if="prodInfo.content"
  1466. v-rich="prodInfo.content"
  1467. />
  1468. </div>
  1469. <!-- /商品介绍 -->
  1470. </div>
  1471. <div class="side">
  1472. <!-- 热销产品 -->
  1473. <div
  1474. class="sale-well"
  1475. style="margin:0"
  1476. >
  1477. <div class="tit">
  1478. {{ $t('prodDetail.recommendGoodies') }}
  1479. </div>
  1480. <div class="con">
  1481. <div
  1482. v-for="item in scoreList"
  1483. :key="item.prodId"
  1484. class="item"
  1485. @click="toScoreProdDet(item.prodId,item.scorePrice)"
  1486. >
  1487. <div class="goods-img">
  1488. <ImgShow :src="item.pic" />
  1489. </div>
  1490. <div class="goods-msg">
  1491. <div class="goods-name">
  1492. {{ item.prodName }}
  1493. </div>
  1494. <div class="goods-price">
  1495. <div class="icon">
  1496. <img
  1497. src="@/assets/images/member-pic/integral-icon.png"
  1498. alt
  1499. >
  1500. </div>
  1501. <div class="integral-price">
  1502. {{ item.scorePrice }} {{ $t('prodDetail.points') }}
  1503. </div>
  1504. <div
  1505. v-if="item.price > 0"
  1506. class="add"
  1507. >
  1508. +
  1509. </div>
  1510. <div
  1511. v-if="item.price > 0"
  1512. class="price"
  1513. >
  1514. ¥{{ item.price }}
  1515. </div>
  1516. </div>
  1517. <div class="old-price">
  1518. {{ $t('prodDetail.marketPrice') }}¥{{ item.oriPrice }}
  1519. </div>
  1520. </div>
  1521. </div>
  1522. </div>
  1523. </div>
  1524. <!-- 热销产品 -->
  1525. </div>
  1526. </div>
  1527. </div>
  1528. <!-- 滑动导航 -->
  1529. <transition name="fade">
  1530. <div
  1531. v-if="showScrollTab"
  1532. class="scroll-tab"
  1533. >
  1534. <div class="content">
  1535. <div class="shop-search">
  1536. <input
  1537. v-model="prodName"
  1538. type="text"
  1539. class="text"
  1540. :placeholder="$t('prodDetail.inStoreSearch')"
  1541. >
  1542. <a
  1543. href="javascript:void(0)"
  1544. class="btn"
  1545. @click="toShopIndex"
  1546. />
  1547. </div>
  1548. <div class="tab">
  1549. <div
  1550. :class="['item', introduceOrCommentInt ? 'active' : '']"
  1551. @click="toggleIntroduceInt"
  1552. >
  1553. {{ $t('prodDetail.productIntroduction') }}
  1554. </div>
  1555. <div
  1556. :class="['item', introduceOrCommentCom ? 'active' : '']"
  1557. @click="toggleIntroduceCom"
  1558. >
  1559. {{ $t('prodDetail.productReviews') }}
  1560. <i class="number">({{ prodCommData.number }})</i>
  1561. </div>
  1562. </div>
  1563. <div
  1564. class="add-cart-btn"
  1565. @click="addToCart"
  1566. >
  1567. {{ $t('prodDetail.addToCart') }}
  1568. </div>
  1569. </div>
  1570. </div>
  1571. </transition>
  1572. <!-- /滑动导航 -->
  1573. <!-- 弹窗 -->
  1574. <div
  1575. v-if="false"
  1576. class="popup-mask"
  1577. />
  1578. <div
  1579. v-if="false"
  1580. class="popup-box"
  1581. >
  1582. <div class="tit">
  1583. <div class="text">
  1584. {{ $t('tips') }}
  1585. </div>
  1586. <div class="close" />
  1587. </div>
  1588. <div class="con">
  1589. <div class="tip">
  1590. <div
  1591. class="tip-
  1592. success"
  1593. />
  1594. <div class="tip-info">
  1595. <div class="result">
  1596. {{ $t('prodDetail.congratulationsYouHaveSuccessfullyReceived') }}
  1597. <span class="number"> <i class="small">¥</i>500 </span>{{ $t('discountCoupon') }}!
  1598. </div>
  1599. <div class="date">
  1600. {{ $t('prodDetail.timeOfUse') }}:2019.12.12-2019.12.12
  1601. </div>
  1602. <div class="btns">
  1603. <a
  1604. href="javascript:void(0)"
  1605. class="btn"
  1606. >
  1607. {{ $t('prodDetail.viewCouponsCollected') }}
  1608. <span class="arrow">>></span>
  1609. </a>
  1610. </div>
  1611. </div>
  1612. </div>
  1613. </div>
  1614. </div>
  1615. <!-- /弹窗 -->
  1616. <!-- 登录弹窗组件 -->
  1617. <LoginPopup
  1618. v-if="showLogin"
  1619. @hide-login-pop="hideLoginPop"
  1620. />
  1621. <!-- /登录弹窗组件 -->
  1622. <!-- 选择套餐 -->
  1623. <SelectPackage
  1624. v-if="showSelectPackage"
  1625. ref="selectPackageRef"
  1626. :shop-info="shopInfo"
  1627. :combo-id="selectComboId"
  1628. :select-match-ids="selectMatchIds"
  1629. @hide-select-package="hideSelectPackage"
  1630. />
  1631. </div>
  1632. </template>
  1633. <script setup>
  1634. import QRCode from 'qrcode'
  1635. import Cookie from 'vue-cookies'
  1636. import { bus } from '@/utils/bus.js'
  1637. import Big from 'big.js'
  1638. import * as pageUtil from '@/utils/pageUtil'
  1639. import DOMPurify from 'dompurify'
  1640. import * as util from '@/utils'
  1641. import { tapLog } from '@/utils/flow'
  1642. import LoginPopup from '@/components/login-popup/index.vue'
  1643. import SelectPackage from '@/components/select-package/index.vue'
  1644. import { checkFileUrl } from '@/utils/index.js'
  1645. import { ElMessage, ElMessageBox } from 'element-plus'
  1646. import defaultUrl from '@/assets/img/def.png'
  1647. import CombineDetail from './components/combine-detail/index.vue'
  1648. // 截取日期: 2020-11-25 00:00:00 -> 2020-11-25
  1649. const cutDate = (dateStr) => {
  1650. if (!dateStr) return ''
  1651. return dateStr.split(' ')[0]
  1652. }
  1653. const price = (value) => {
  1654. if (value) {
  1655. return value.toFixed(2)
  1656. }
  1657. return 0.00
  1658. }
  1659. const store = useLanguageStore()
  1660. const h5Path = import.meta.env.VITE_APP_H5_DOMAIN
  1661. const router = useRouter()
  1662. const route = useRoute()
  1663. const scoreFee = route.query.scoreFee
  1664. const prodId = ref(route.query.prodId)
  1665. const defaultCombo = ref(null)
  1666. const selectMatchIds = ref([])
  1667. const comboAmount = computed(() => {
  1668. if (!defaultCombo.value) {
  1669. return 0
  1670. }
  1671. let price = new Big(defaultCombo.value.mainProd.comboPrice).times(defaultCombo.value.mainProd.leastNum)
  1672. defaultCombo.value.matchingProds.forEach(item => {
  1673. if (selectMatchIds.value.findIndex(id => id === item.prodId) >= 0) {
  1674. price = price.plus(new Big(item.comboPrice).times(item.leastNum))
  1675. }
  1676. })
  1677. price = price.toFixed(2)
  1678. return price
  1679. })
  1680. // 按钮置灰
  1681. const isGrayBtn = computed(() => {
  1682. return prodInfo.value?.deliveryModeVO?.hasShopDelivery && (!defaultSku.value?.isHasStock || !isDelivery.value)
  1683. })
  1684. const defaultSku = ref({}) // 选中的sku
  1685. const prodNum = ref(1) // 计数器数量
  1686. const prohibit1 = ref(true) // 计数器-是否禁用
  1687. const prohibit2 = ref(false) // 计数器+是否禁用
  1688. watch(prodNum, (nv) => {
  1689. if (nv <= 1) {
  1690. prohibit1.value = true
  1691. if (defaultSku.value.stocks === 0 || nv === defaultSku.value.stocks) {
  1692. prohibit2.value = true
  1693. }
  1694. } else if (nv === defaultSku.value.stocks) {
  1695. prohibit1.value = false
  1696. prohibit2.value = true
  1697. } else {
  1698. prohibit1.value = false
  1699. prohibit2.value = false
  1700. }
  1701. })
  1702. watch(() => defaultSku.value, () => {
  1703. const key = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  1704. if (!defaultSku.value[key]) {
  1705. prodNum.value = 0
  1706. prohibit1.value = true
  1707. prohibit2.value = true
  1708. }
  1709. })
  1710. let prodVideo = ''
  1711. const scoreList = ref({})
  1712. const prodInfo = ref({})
  1713. const prodCommData = ref({})
  1714. const discountDet = ref([]) // 限时特惠专区详情
  1715. const prodParameterList = ref([])
  1716. const showPlayBtn = ref(true) // 视频播放按钮
  1717. const comboList = ref([])
  1718. onMounted(async () => {
  1719. if (Cookie.get('bbcToken')) await onOpenDeliveryPop(false)
  1720. await getProdInfo()
  1721. if (!scoreFee) {
  1722. getShopHead(prodInfo.value.shopId)
  1723. watchPreSellTime()
  1724. // 监听预售时间
  1725. prodInfo.value.content = prodInfo.value.content ? DOMPurify.sanitize(util.formatHtml(prodInfo.value.content), { ADD_ATTR: ['target'] }) : ''
  1726. let flag = 0
  1727. prodParameterList.value[flag] = []
  1728. const isProdParameterList = prodInfo.value.prodParameterList || []
  1729. for (let i = 0; i < isProdParameterList.length; i++) {
  1730. const params = isProdParameterList[i]
  1731. if (i % 3 === 0 && i !== 0) {
  1732. flag++
  1733. prodParameterList.value[flag] = []
  1734. }
  1735. prodParameterList.value[flag].push(params)
  1736. }
  1737. // 查询商品是否已经收藏
  1738. isProdCollected()
  1739. isShopCollected()
  1740. // 获取店铺分类
  1741. getShopCategory()
  1742. // 获取热销商品
  1743. getHotSales()
  1744. // 获取商品评论数
  1745. http.get('/prod/prodCommData', {
  1746. params: {
  1747. prodId: route.query.prodId
  1748. }
  1749. }).then(({ data }) => {
  1750. handleProdComm(data.prodCommDtoPage)
  1751. prodCommData.value = data
  1752. })
  1753. // 优惠券
  1754. getCouponList()
  1755. // 通过商品id获取商品所有促销活动
  1756. http.get('/marking/discount/getDiscountByProdId', {
  1757. params: {
  1758. prodId: route.query.prodId
  1759. }
  1760. }).then(({ data }) => {
  1761. discountDet.value = data
  1762. })
  1763. // 监听页面滚动
  1764. window.addEventListener('scroll', scrollToTop)
  1765. }
  1766. if (comboList.value && comboList.value.length > 0) {
  1767. selectCombo(comboList.value[0].comboId)
  1768. }
  1769. if (prodInfo.value.video) {
  1770. // 获取商品视频
  1771. prodVideo = document.getElementById('prodVideo')
  1772. showPlayBtn.value = true
  1773. }
  1774. // 组装sku
  1775. if (prodInfo.value.prodType === 0 || prodInfo.value.prodType === 3) {
  1776. groupSkuProp(prodInfo.value.skuList, prodInfo.value.price)
  1777. } else {
  1778. // 秒杀/团购 商品
  1779. groupBuyInfo()
  1780. }
  1781. // 保存足迹 (积分商品不保留足迹)
  1782. if (Cookie.get('bbcToken')) {
  1783. http.post('/p/prodBrowseLog', {
  1784. prodId: route.query.prodId
  1785. })
  1786. }
  1787. // 积分推荐商品列表
  1788. recommendGoods()
  1789. })
  1790. const recommendGoods = () => {
  1791. http.get('/search/page', {
  1792. params: {
  1793. current: 1,
  1794. size: 20,
  1795. prodType: 3,
  1796. sort: 2,
  1797. stationId: 0 // 过滤掉门店发布的商品
  1798. }
  1799. }).then(({ data }) => {
  1800. scoreList.value = data.records[0].products
  1801. })
  1802. }
  1803. let timer = null// 倒计时定时器
  1804. let preSellTimer = null// 倒计时定时器
  1805. onUnmounted(() => {
  1806. // 页面销毁时移除监听
  1807. !scoreFee && window.removeEventListener('scroll', scrollToTop)
  1808. clearTimeout(timer)
  1809. clearTimeout(preSellTimer)
  1810. })
  1811. // 秒杀默认sku
  1812. const seckillDefSku = computed(() => {
  1813. const { seckillSkuList, activityStatus } = seckillActInfo.value
  1814. if (activityStatus === 1) {
  1815. return seckillSkuList.find(f => f.skuId === defaultSku.value.skuId) || null
  1816. }
  1817. return null
  1818. })
  1819. const seckillActInfo = ref({})
  1820. const getSeckillInfo = async (addrId) => {
  1821. let userId = null
  1822. if (Cookie.get('bbcToken')) {
  1823. userId = userStore.userId
  1824. }
  1825. const commonAddrItem = deliveryList.value.find(item => item.commonAddr === 1)
  1826. await http.get('/seckill/prod', {
  1827. params: {
  1828. seckillId: prodInfo.value.activityId,
  1829. addrId: addrId || (commonAddrItem ? commonAddrItem.addrId : 0),
  1830. userId: userId || '',
  1831. needPointStock: false
  1832. }
  1833. }).then(({ data }) => {
  1834. if (data.activityStatus === 1) {
  1835. seckillActInfo.value = data
  1836. const { seckillSkuList } = seckillActInfo.value
  1837. let minProdPriceInfo = {
  1838. price: Number.MAX_VALUE
  1839. }
  1840. seckillSkuList.forEach(item => {
  1841. if (minProdPriceInfo.price > item.seckillPrice) {
  1842. minProdPriceInfo = item
  1843. }
  1844. })
  1845. groupSkuProp(prodInfo.value.skuList, minProdPriceInfo.priceFee)
  1846. return
  1847. }
  1848. data.seckillSkuList.forEach(item => {
  1849. item.seckillStocks = item.stock
  1850. item.isHasStock = item.hasStock
  1851. })
  1852. prodInfo.value.productActivity.seckill.seckillSkuList = data.seckillSkuList
  1853. groupSkuProp(data.seckillSkuList, data.seckillPrice)
  1854. })
  1855. .catch(() => {
  1856. groupSkuProp(prodInfo.value.skuList, prodInfo.value.priceFee)
  1857. })
  1858. }
  1859. const formatDate = (val) => {
  1860. if (val) {
  1861. return util.tsToDate(val.replace(/-/g, '/'), 'Y-M-D')
  1862. }
  1863. }
  1864. const updateMetaTags = (prodInfo) => {
  1865. const prodName = prodInfo?.prodName
  1866. const keyWords = prodInfo?.keyWords
  1867. const brief = prodInfo?.brief
  1868. // 设置页面标题
  1869. if (prodName) {
  1870. document.title = prodName
  1871. } else {
  1872. document.title = $t('product.productDetails')
  1873. }
  1874. // 更新 keywords meta 标签
  1875. if (keyWords) {
  1876. let metaKeywords = document.querySelector("meta[name='keywords']")
  1877. if (!metaKeywords) {
  1878. metaKeywords = document.createElement('meta')
  1879. metaKeywords.setAttribute('name', 'keywords')
  1880. document.head.appendChild(metaKeywords)
  1881. }
  1882. metaKeywords.setAttribute('content', keyWords)
  1883. }
  1884. // 更新 description meta 标签
  1885. if (brief) {
  1886. let metaDescription = document.querySelector("meta[name='description']")
  1887. if (!metaDescription) {
  1888. metaDescription = document.createElement('meta')
  1889. metaDescription.setAttribute('name', 'description')
  1890. document.head.appendChild(metaDescription)
  1891. }
  1892. metaDescription.setAttribute('content', brief)
  1893. }
  1894. }
  1895. const productActivity = computed(() => {
  1896. return prodInfo.value.productActivity || {}
  1897. })
  1898. const totalStocks = ref(0)
  1899. const shopId = ref('')
  1900. const addrInfo = ref(null) // 用户地址
  1901. const totalTransFee = ref(0) // 运费
  1902. let addrId = 0 // 用户选择的地址id
  1903. const isDelivery = ref(true) // 是否支持配送
  1904. const prodImgs = ref([])
  1905. const userStore = useUserStore()
  1906. const getProdInfo = (addrId) => {
  1907. let userId = null
  1908. if (Cookie.get('bbcToken')) {
  1909. userId = userStore.userId
  1910. }
  1911. const commonAddrItem = deliveryList.value.find(item => item.commonAddr === 1)
  1912. return new Promise((resolve, reject) => {
  1913. http.get('/prod/prodInfo', {
  1914. params: {
  1915. prodId: route.query.prodId,
  1916. userId,
  1917. needPointStock: false,
  1918. addrId: addrId || (commonAddrItem ? commonAddrItem.addrId : 0),
  1919. dvyType: 1
  1920. }
  1921. })
  1922. .then(({ data }) => {
  1923. const isProdImgs = []
  1924. if (data.imgs) {
  1925. if (data.imgs[0]) {
  1926. data.imgs.split(',').forEach(imgStr => {
  1927. isProdImgs.push({
  1928. img: imgStr,
  1929. isActive: false
  1930. })
  1931. })
  1932. isProdImgs[0].isActive = true
  1933. }
  1934. }
  1935. data.deliveryModeVO = data.deliveryModeVO || JSON.parse(data.deliveryMode)
  1936. prodInfo.value = data
  1937. updateMetaTags(data)
  1938. totalStocks.value = data.totalStocks
  1939. isDelivery.value = data.isDelivery
  1940. prodInfo.value.preSellTime = formatDate(prodInfo.value.preSellTime)
  1941. // 将组合关联商品拷贝一份至团购规格下
  1942. if (data.productActivity.groupActivity) {
  1943. if (data.productActivity.groupActivity.groupSkuList) {
  1944. data.productActivity.groupActivity.groupSkuList = data.productActivity.groupActivity.groupSkuList.map(item => {
  1945. const skuInfo = data.skuList.find(i => i.skuId === item.skuId)
  1946. item.warehouseStock = skuInfo?.warehouseStock || item.stocks
  1947. data.mold && (item.skuComboList = skuInfo.skuComboList)
  1948. return item
  1949. })
  1950. }
  1951. }
  1952. prodImgs.value = isProdImgs
  1953. shopId.value = data.shopId
  1954. // 处理套餐 (主商品不支持快递不显示该套餐,非必选的搭配商品不支持快递不显示该商品,必选的搭配商品不支持快递不显示该套餐
  1955. if (data.productActivity.comboList && data.productActivity.comboList.length) {
  1956. const conboListLen = data.productActivity.comboList.length
  1957. for (let conboIndex = conboListLen - 1; conboIndex >= 0; conboIndex--) {
  1958. const conboItem = data.productActivity.comboList[conboIndex]
  1959. conboItem.mainProd.deliveryMode = conboItem.mainProd.deliveryMode ? JSON.parse(conboItem.mainProd.deliveryMode) : {}
  1960. if (!conboItem.mainProd.deliveryMode.hasShopDelivery) {
  1961. // 主商品不支持快递配送,则移除该套餐
  1962. data.productActivity.comboList.splice(conboIndex, 1)
  1963. continue
  1964. }
  1965. let flag = false // 判断搭配商品是否存在必选但是不支持快递配送的
  1966. const matchingProdsLen = conboItem.matchingProds?.length || 0 // 搭配商品数组长度
  1967. for (let matchingSpuIndex = matchingProdsLen - 1; matchingSpuIndex >= 0; matchingSpuIndex--) {
  1968. // 搭配商品item
  1969. const matchingProdItem = conboItem.matchingProds[matchingSpuIndex]
  1970. // 配送方式
  1971. matchingProdItem.deliveryMode = matchingProdItem.deliveryMode ? JSON.parse(matchingProdItem.deliveryMode) : {}
  1972. if (matchingProdItem.deliveryMode.hasShopDelivery) {
  1973. continue
  1974. } else if (matchingProdItem.required) {
  1975. // 不支持快递配送,且为必选的搭配商品,则将该套餐移除
  1976. flag = true
  1977. break
  1978. } else {
  1979. // 不支持快递配送,但是非必选,则将该商品移除
  1980. conboItem.matchingProds.splice(matchingSpuIndex, 1)
  1981. }
  1982. }
  1983. if (flag) {
  1984. data.productActivity.comboList.splice(conboIndex, 1)
  1985. continue
  1986. }
  1987. }
  1988. }
  1989. comboList.value = data.productActivity.comboList || []
  1990. totalTransFee.value = data.userDeliveryInfo && data.userDeliveryInfo.totalTransFee
  1991. addrInfo.value = data.userDeliveryInfo && data.userDeliveryInfo.userAddr
  1992. resolve(true)
  1993. })
  1994. .catch(err => {
  1995. reject(err)
  1996. if (err.data.code === 'A00001') {
  1997. setTimeout(() => {
  1998. router.push({ path: '/' })
  1999. }, 1500)
  2000. }
  2001. })
  2002. })
  2003. }
  2004. /**
  2005. * 监听预售时间
  2006. */
  2007. const watchPreSellTime = () => {
  2008. preSellTimer = setInterval(() => {
  2009. const preSellTimeStamp = Date.parse(prodInfo.value.preSellTime)
  2010. const nowTimeStamp = Date.now()
  2011. if (preSellTimeStamp - nowTimeStamp <= 0) {
  2012. clearTimeout(preSellTimer)
  2013. // 开始售卖,重新获取商品信息
  2014. http.get('/prod/prodInfo', {
  2015. params: {
  2016. prodId: route.query.prodId,
  2017. dvyType: 1
  2018. }
  2019. }).then(({ data }) => {
  2020. prodInfo.value = data
  2021. })
  2022. }
  2023. }, 1000)
  2024. }
  2025. /**
  2026. * 页面滚动事件
  2027. */
  2028. const introduceOrCommentInt = ref(true) // true商品介绍 false商品评论
  2029. const showScrollTab = ref(false)
  2030. const scrollToTop = util._throttle(() => {
  2031. showScrollTab.value = getRectTop() <= 0
  2032. }, 100)
  2033. const getRectTop = () => {
  2034. const element = document.querySelector('.detail-comment')
  2035. const rect = element.getBoundingClientRect()
  2036. return rect.top
  2037. }
  2038. /**
  2039. * 评论点击图片显示大图
  2040. */
  2041. const imgPath = ref('') // 当前点击的评论图片
  2042. const prodCommList = ref([])
  2043. const showBigImg = ref(false) // 评论大图显隐
  2044. let bigComIndex = -1 // 评论大图切换
  2045. let bigImgIndex = -1 // 评论大图切换
  2046. const imgShow = (comIndex, imgIndex) => {
  2047. bigComIndex = comIndex
  2048. document.documentElement.style.overflowY = 'hidden'
  2049. const bigProdImgs = prodCommList.value[comIndex].prodImgs.length
  2050. if (imgIndex === -1) {
  2051. closeShowBigImg()
  2052. bigImgIndex = -1
  2053. bigComIndex = -1
  2054. return
  2055. }
  2056. if (imgIndex === bigProdImgs) {
  2057. closeShowBigImg()
  2058. bigImgIndex = -1
  2059. bigComIndex = -1
  2060. return
  2061. }
  2062. imgPath.value = prodCommList.value[comIndex].prodImgs[imgIndex]
  2063. if (imgPath.value) {
  2064. showBigImg.value = true
  2065. }
  2066. bigImgIndex = imgIndex
  2067. }
  2068. const prevImgCom = () => {
  2069. const comIndex = bigComIndex
  2070. const imgIndex = bigImgIndex - 1
  2071. imgShow(comIndex, imgIndex)
  2072. }
  2073. const nextImgCom = () => {
  2074. const comIndex = bigComIndex
  2075. const imgIndex = bigImgIndex + 1
  2076. imgShow(comIndex, imgIndex)
  2077. }
  2078. // 获取选中套餐数量
  2079. const selectMatchNum = () => {
  2080. if (defaultCombo.value.mainProd) {
  2081. let mainProdNum = defaultCombo.value.mainProd.leastNum
  2082. if (defaultCombo.value.matchingProds.length) {
  2083. defaultCombo.value.matchingProds.forEach(item => {
  2084. if (item.isChecked) {
  2085. mainProdNum += item.leastNum
  2086. }
  2087. })
  2088. }
  2089. return mainProdNum
  2090. }
  2091. }
  2092. /**
  2093. * 关闭评论大图显隐
  2094. */
  2095. const closeShowBigImg = () => {
  2096. showBigImg.value = false
  2097. document.documentElement.style.overflowY = ''
  2098. }
  2099. /**
  2100. * 关闭视频
  2101. */
  2102. const showVideo = ref(false) // 是否展示视频
  2103. const stopVideo = () => {
  2104. showVideo.value = false
  2105. if (prodVideo) {
  2106. prodVideo.pause()
  2107. }
  2108. showPlayBtn.value = true
  2109. }
  2110. /**
  2111. * 播放视频
  2112. */
  2113. const playVideo = () => {
  2114. showVideo.value = true
  2115. prodVideo.play(0)
  2116. showPlayBtn.value = false
  2117. }
  2118. // 切换图片
  2119. const carouserRef = ref(null)
  2120. const offsetCount = ref(0) // 图片偏移数
  2121. const prevImg = () => {
  2122. if (prodImgs.value.length - 5 > 0) {
  2123. if (offsetCount.value > 0) {
  2124. offsetCount.value--
  2125. carouserRef.value.style.left = '-' + 78 * offsetCount.value + 'px'
  2126. } else {
  2127. return false
  2128. }
  2129. } else if (prodImgs.value.length - 5 <= 0) {
  2130. return false
  2131. } else {
  2132. return false
  2133. }
  2134. }
  2135. const nextImg = () => {
  2136. if (prodImgs.value.length - 5 > 0) {
  2137. if (offsetCount.value < prodImgs.value.length - 5) {
  2138. offsetCount.value++
  2139. carouserRef.value.style.left = '-' + 78 * offsetCount.value + 'px'
  2140. } else if (prodImgs.value.length - 5 <= 0) {
  2141. return false
  2142. } else {
  2143. return false
  2144. }
  2145. } else {
  2146. return false
  2147. }
  2148. }
  2149. /**
  2150. * 团购信息
  2151. */
  2152. const countdown = ref({ obj: { day: '', hou: '', min: '', sec: '', signs: '' } }) // 倒计时
  2153. const countdownFlag = ref(null) // 判断团购活动开始结束
  2154. const groupBuyInfo = (addrId) => {
  2155. if ((prodInfo.value.prodType !== 1 || !prodInfo.value.productActivity.groupActivity) && (prodInfo.value.prodType !== 2 || !prodInfo.value.productActivity.seckill)) {
  2156. groupSkuProp(prodInfo.value.skuList, prodInfo.value.price)
  2157. return
  2158. }
  2159. let key = 'groupActivity'
  2160. if (prodInfo.value.prodType !== 1) {
  2161. key = 'seckill'
  2162. }
  2163. const betweenTimestamp = util.betweenTimestamp(
  2164. util.dateToTimestamp(prodInfo.value.productActivity[key].startTime),
  2165. util.dateToTimestamp(prodInfo.value.productActivity[key].endTime)
  2166. )
  2167. countdown.value = {
  2168. obj: util.betweenTime(betweenTimestamp),
  2169. stamp: betweenTimestamp
  2170. }
  2171. countdownFlag.value = countdown.value.obj.signs
  2172. startCountdown() // 请求倒计时
  2173. if (key === 'seckill') {
  2174. getSeckillInfo(addrId)
  2175. if (!countdown.value.obj.signs) {
  2176. groupSkuProp(prodInfo.value.skuList, prodInfo.value.price)
  2177. }
  2178. } else {
  2179. groupSkuProp(prodInfo.value.productActivity.groupActivity.groupSkuList, prodInfo.value.productActivity.groupActivity.actPrice)
  2180. getQrcode()
  2181. }
  2182. }
  2183. /**
  2184. * 倒计时
  2185. */
  2186. const startCountdown = () => {
  2187. countdown.value = {
  2188. stamp:
  2189. countdown.value.stamp < 0 ? countdown.value.stamp + 1000 : countdown.value.stamp - 1000,
  2190. obj: util.betweenTime(countdown.value.stamp)
  2191. }
  2192. if (countdown.value.obj.signs !== countdownFlag.value) {
  2193. clearTimeout(timer)
  2194. location.reload()
  2195. return
  2196. }
  2197. timer = setTimeout(() => {
  2198. startCountdown()
  2199. }, 1000)
  2200. }
  2201. const getQrcode = (id = 'groupQrcode') => {
  2202. nextTick(() => {
  2203. QRCode.toCanvas(document.getElementById(id), h5Path + `/package-prod/pages/prod/prod?prodId=${prodId.value}`,
  2204. { version: 6, errorCorrectionLevel: 'L', width: '110', height: '110' })
  2205. })
  2206. }
  2207. /**
  2208. * 小图点击事件
  2209. */
  2210. const changeProdImg = (index) => {
  2211. if (prodImgs.value[index].isActive === true) {
  2212. return
  2213. }
  2214. prodImgs.value.forEach(prodImg => {
  2215. prodImg.isActive = false
  2216. })
  2217. prodImgs.value[index].isActive = true
  2218. prodInfo.value.pic = prodImgs.value[index].img
  2219. stopVideo()
  2220. }
  2221. /**
  2222. * 大图加载失败时往下一张图加载
  2223. */
  2224. const changePicUrl = () => {
  2225. const currentIndex = prodImgs.value.findIndex(prodImg => {
  2226. return prodImg.isActive === true
  2227. }
  2228. )
  2229. if (currentIndex === -1) {
  2230. prodInfo.value.pic = defaultUrl
  2231. return
  2232. }
  2233. prodImgs.value[currentIndex].img = defaultUrl
  2234. const nextIndex = currentIndex + 1
  2235. if (nextIndex >= prodImgs.value.length) {
  2236. prodInfo.value.pic = prodImgs.value[0].img
  2237. return
  2238. }
  2239. changeProdImg(nextIndex)
  2240. }
  2241. /**
  2242. * 获取店铺信息
  2243. */
  2244. const shopInfo = ref({})
  2245. const getShopHead = (shopId) => {
  2246. http.get('/shop/headInfo', {
  2247. params: {
  2248. shopId
  2249. }
  2250. }).then(({ data }) => {
  2251. shopInfo.value = data
  2252. })
  2253. }
  2254. /**
  2255. * 获取店铺分类
  2256. */
  2257. const shopCategorys = ref([])
  2258. const getShopCategory = () => {
  2259. http.get('/category/categoryInfo', {
  2260. params: {
  2261. shopId: shopId.value
  2262. }
  2263. }).then(({ data }) => {
  2264. shopCategorys.value = data.categoryInfo
  2265. })
  2266. }
  2267. /**
  2268. * 价格过滤
  2269. */
  2270. const parsePrice = (value) => {
  2271. let val = Number(value)
  2272. if (!val) {
  2273. val = 0
  2274. }
  2275. // 截取小数点后两位,并以小数点为切割点将val转化为数组
  2276. return val.toFixed(2).split('.')
  2277. }
  2278. /**
  2279. * 切换商品介绍/商品评论
  2280. */
  2281. const introduceOrCommentCom = ref(false)
  2282. const toggleIntroduceInt = () => {
  2283. introduceOrCommentInt.value = true
  2284. introduceOrCommentCom.value = false
  2285. }
  2286. const toggleIntroduceCom = () => {
  2287. introduceOrCommentInt.value = false
  2288. introduceOrCommentCom.value = true
  2289. }
  2290. /**
  2291. * 获取评论分页数据
  2292. */
  2293. const page = ref({
  2294. pages: 0, // 总页数
  2295. rainbow: [], // 分页条
  2296. current: 1
  2297. })
  2298. const evaluate = ref(-1)
  2299. const getSearchProdPage = (current) => {
  2300. if (
  2301. current !== 0 &&
  2302. current !== page.value.current &&
  2303. current <= page.value.pages
  2304. ) {
  2305. getProdCommPageByProd(evaluate.value, current)
  2306. }
  2307. }
  2308. /**
  2309. * 获取商品评论列表
  2310. */
  2311. const getProdCommPageByProd = (evaluateTemp, curPage) => {
  2312. evaluate.value = evaluateTemp
  2313. http.get('/prod/prodCommPageByProd', {
  2314. params: {
  2315. prodId: route.query.prodId,
  2316. current: curPage || 1,
  2317. size: 10,
  2318. evaluate: evaluate.value
  2319. }
  2320. }).then(({ data }) => {
  2321. handleProdComm(data)
  2322. })
  2323. }
  2324. const handleProdComm = (data) => {
  2325. const records = data.records
  2326. prodCommList.value = records.map(prodComm => {
  2327. prodComm.prodImgs = prodComm.pics?.split(',') || []
  2328. return prodComm
  2329. })
  2330. data.rainbow = pageUtil.rainbowWithDot(data.current, data.pages, 5)
  2331. page.value = data
  2332. }
  2333. /**
  2334. * 获取热销商品
  2335. */
  2336. const hotSales = ref([])
  2337. const getHotSales = () => {
  2338. http.get('/search/page', {
  2339. params: {
  2340. shopId: prodInfo.value.shopId,
  2341. size: 5,
  2342. sort: 1,
  2343. orderBy: 1,
  2344. isAllProdType: true,
  2345. current: 1,
  2346. isActive: 1, // 过滤掉活动商品
  2347. stationId: 0 // 过滤掉门店发布的商品
  2348. }
  2349. }).then(({ data }) => {
  2350. hotSales.value = data.records[0].products
  2351. })
  2352. }
  2353. /**
  2354. * 收藏商品
  2355. */
  2356. const isCollection = ref(false) // 商品是否已收藏
  2357. const toggleCollect = () => {
  2358. const prodId = prodInfo.value.prodId
  2359. if (!prodId) {
  2360. return
  2361. }
  2362. http.post('/p/user/collection/addOrCancel', prodId).then(() => {
  2363. isCollection.value = !isCollection.value
  2364. if (isCollection.value) {
  2365. ElMessage({
  2366. message: $t('prodDetail.collectionSuccess'),
  2367. type: 'success',
  2368. duration: 1000
  2369. })
  2370. } else {
  2371. ElMessage({
  2372. message: $t('prodDetail.uncollected'),
  2373. type: 'warning',
  2374. duration: 1000
  2375. })
  2376. }
  2377. })
  2378. }
  2379. /**
  2380. * 查询商品是否已收藏
  2381. */
  2382. const isProdCollected = () => {
  2383. if (!Cookie.get('bbcToken')) {
  2384. return
  2385. }
  2386. http.get('/p/user/collection/isCollection', {
  2387. params: {
  2388. prodId: prodInfo.value.prodId
  2389. }
  2390. }).then(({ data }) => {
  2391. isCollection.value = data
  2392. })
  2393. }
  2394. /**
  2395. * 收藏店铺
  2396. */
  2397. const isShopCollection = ref(false) // 店铺是否已收藏
  2398. const toggleShopCollect = () => {
  2399. const shopId = prodInfo.value.shopId
  2400. if (!shopId) return
  2401. http.post('/p/shop/collection/addOrCancel', shopId).then(() => {
  2402. isShopCollection.value = !isShopCollection.value
  2403. if (isShopCollection.value) {
  2404. ElMessage({
  2405. message: $t('prodDetail.collectionSuccess'),
  2406. type: 'success',
  2407. duration: 1000
  2408. })
  2409. } else {
  2410. ElMessage({
  2411. message: $t('prodDetail.uncollected'),
  2412. type: 'warning',
  2413. duration: 1000
  2414. })
  2415. }
  2416. })
  2417. }
  2418. /**
  2419. * 查询店铺是否已收藏
  2420. */
  2421. const isShopCollected = () => {
  2422. if (!Cookie.get('bbcToken')) {
  2423. return
  2424. }
  2425. http.get('/p/shop/collection/isCollection', {
  2426. params: {
  2427. shopId: prodInfo.value.shopId
  2428. }
  2429. }).then(({ data }) => {
  2430. isShopCollection.value = data
  2431. })
  2432. }
  2433. /**
  2434. * 减少商品数量
  2435. */
  2436. const reduce = () => {
  2437. const newProdNum = parseInt(prodNum.value)
  2438. const key = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  2439. if (defaultSku.value[key]) {
  2440. if (!newProdNum || newProdNum <= 1) {
  2441. prodNum.value = newProdNum
  2442. prohibit1.value = true // 禁用
  2443. prohibit2.value = false
  2444. } else {
  2445. prodNum.value = newProdNum - 1
  2446. prohibit1.value = false
  2447. prohibit2.value = false
  2448. }
  2449. } else {
  2450. prodNum.value = 0
  2451. prohibit1.value = true
  2452. prohibit2.value = true
  2453. }
  2454. }
  2455. /*
  2456. * 失去焦点时对输入框的判断
  2457. */
  2458. const judgeInput = () => {
  2459. const newProdNum = parseInt(prodNum.value)
  2460. const key = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  2461. if (defaultSku.value[key]) {
  2462. if (!newProdNum || newProdNum <= 0) {
  2463. prohibit1.value = true
  2464. prohibit2.value = false
  2465. } else {
  2466. prohibit1.value = false
  2467. prohibit2.value = false
  2468. }
  2469. if (prodInfo.value.productActivity.seckill && countdown.value.obj.signs) {
  2470. if (prodInfo.value.productActivity.seckill.maxNum > 0 && newProdNum > prodInfo.value.productActivity.seckill.maxNum) {
  2471. ElMessage({
  2472. message: $t('purchaseRestrictions') + prodInfo.value.productActivity.seckill.maxNum + $t('items'),
  2473. type: 'warning',
  2474. duration: 1000
  2475. })
  2476. prodNum.value = 1
  2477. prohibit1.value = false // -是否禁用
  2478. prohibit2.value = true // +是否禁用
  2479. return
  2480. }
  2481. }
  2482. if (newProdNum > defaultSku.value[key]) {
  2483. ElMessage({
  2484. message: $t('prodDetail.insufficientInventory'),
  2485. type: 'warning',
  2486. duration: 1000
  2487. })
  2488. prohibit1.value = false
  2489. prohibit2.value = true
  2490. prodNum.value = 1
  2491. }
  2492. } else {
  2493. prodNum.value = 0
  2494. prohibit1.value = true
  2495. prohibit2.value = true
  2496. }
  2497. }
  2498. /**
  2499. * 增加商品数量
  2500. */
  2501. const increase = () => {
  2502. const newProdNum = parseInt(prodNum.value)
  2503. const key = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  2504. if (defaultSku.value[key]) {
  2505. if (!newProdNum) {
  2506. prodNum.value = 1
  2507. return
  2508. }
  2509. if (prodInfo.value.productActivity.seckill && countdown.value.obj.signs) {
  2510. if (prodInfo.value.productActivity.seckill.maxNum > 0 && newProdNum >= prodInfo.value.productActivity.seckill.maxNum) {
  2511. ElMessage({
  2512. message: $t('purchaseRestrictions') + prodInfo.value.productActivity.seckill.maxNum + $t('items'),
  2513. type: 'warning',
  2514. duration: 1000
  2515. })
  2516. prohibit2.value = true // +是否禁用
  2517. return
  2518. }
  2519. }
  2520. if (newProdNum < defaultSku.value[key]) {
  2521. prodNum.value = newProdNum + 1
  2522. } else {
  2523. ElMessage({
  2524. message: $t('prodDetail.insufficientInventory'),
  2525. type: 'warning',
  2526. duration: 1000
  2527. })
  2528. prohibit1.value = false // -是否禁用
  2529. prohibit2.value = true // +是否禁用
  2530. }
  2531. } else {
  2532. prodNum.value = 0
  2533. prohibit1.value = true // -是否禁用
  2534. prohibit2.value = true // +是否禁用
  2535. }
  2536. }
  2537. /**
  2538. * 请求优惠券列表
  2539. */
  2540. const couponList = ref([]) // 优惠券
  2541. const getCouponList = () => {
  2542. http.get('/coupon/listByProdId', {
  2543. params: {
  2544. prodId: route.query.prodId,
  2545. shopId: prodInfo.value.shopId
  2546. }
  2547. }).then(({ data }) => {
  2548. couponList.value = data ? data.slice(0, Math.min(8, data.length)) : []
  2549. })
  2550. }
  2551. /**
  2552. * 组装SKU
  2553. */
  2554. let selectedPropObj = {}
  2555. const allProperties = ref('')
  2556. const propKeys = ref('')
  2557. let imgCounts = 0 // 缩略图数量
  2558. const skuGroup = ref([])
  2559. const combineList = ref([])
  2560. const groupSkuProp = (skuList, defaultPrice, isChangeAddrRefreshSku = false) => {
  2561. if (skuList.length === 1 && !skuList[0].properties) {
  2562. defaultSku.value = skuList[0]
  2563. imgCounts = !scoreFee && prodImgs.value.length
  2564. carouserRef.value.style.width = !scoreFee && imgCounts * 78 + 'px' // 设置图片盒子的初始宽度
  2565. combineList.value = skuList[0].skuComboList || []
  2566. return
  2567. }
  2568. const isSkuGroup = {}
  2569. const isAllProperties = []
  2570. const isPropKeys = []
  2571. const isSelectedPropObj = {}
  2572. let isDefaultSku = null
  2573. const key = prodInfo.value.productActivity.groupActivity ? 'actPrice' : prodInfo.value.productActivity.seckill && countdownFlag.value ? 'seckillPrice' : 'price'
  2574. for (let i = 0; i < skuList.length; i++) {
  2575. let isDefault = false
  2576. if (!isDefaultSku && skuList[i][key] === defaultPrice && (!isChangeAddrRefreshSku || (isChangeAddrRefreshSku && skuList[i].skuId === defaultSku.value?.skuId))) {
  2577. // 找到和商品价格一样的那个SKU,作为默认选中的SKU
  2578. isDefaultSku = skuList[i]
  2579. isDefault = true
  2580. }
  2581. const properties = skuList[i].properties // 版本:公开版;颜色:金色;内存:64GB
  2582. isAllProperties.push(properties)
  2583. const propList = properties.split(';') // ["版本:公开版","颜色:金色","内存:64GB"]
  2584. for (let j = 0; j < propList.length; j++) {
  2585. const index = propList[j].indexOf(':')
  2586. const propval = [propList[j].slice(0, index), propList[j].substring(index + 1)] // ["版本","公开版"]
  2587. let props = isSkuGroup[propval[0] + ';'] // 先取出 版本对应的值数组
  2588. // 如果当前是默认选中的sku,把对应的属性值 组装到selectedProp
  2589. if (isDefault) {
  2590. isPropKeys.push(propval[0])
  2591. isSelectedPropObj[propval[0]] = propval[1]
  2592. }
  2593. if (props === undefined) {
  2594. props = [] // 假设还没有版本,新建个新的空数组
  2595. props.push(propval[1]) // 把 "公开版" 放进空数组
  2596. } else {
  2597. if (props.indexOf(propval[1]) === -1) {
  2598. // 如果数组里面没有"公开版"
  2599. props.push(propval[1]) // 把 "公开版" 放进数组
  2600. }
  2601. }
  2602. isSkuGroup[propval[0] + ';'] = props // 最后把数据 放回版本对应的值
  2603. }
  2604. }
  2605. if (!isDefaultSku) {
  2606. const priceList = skuList.map(x => x[key])
  2607. isDefaultSku = skuList[priceList.indexOf(Math.min(...priceList))]
  2608. }
  2609. if (isDefaultSku) {
  2610. imgCounts = isDefaultSku.pic ? prodImgs.value.length + 1 : prodImgs.value.length
  2611. }
  2612. carouserRef.value.style.width = imgCounts * 78 + 'px' // 设置图片盒子的初始宽度
  2613. defaultSku.value = isDefaultSku
  2614. combineList.value = isDefaultSku.skuComboList || []
  2615. propKeys.value = isPropKeys
  2616. selectedPropObj = isSelectedPropObj
  2617. // 商品图片多出来
  2618. parseSelectedObjToVals(skuList)
  2619. skuGroup.value = isSkuGroup
  2620. allProperties.value = isAllProperties
  2621. }
  2622. /**
  2623. * 将已选的 {key:val,key2:val2}转换成 [val,val2]
  2624. */
  2625. let selectedProperties = ''
  2626. const selectedProp = ref([])
  2627. const findSku = ref(true) // 能不能找得到sku
  2628. const parseSelectedObjToVals = (skuList) => {
  2629. const isSelectedPropObj = selectedPropObj
  2630. let isSelectedProperties = ''
  2631. const isSelectedProp = []
  2632. for (const key in isSelectedPropObj) {
  2633. isSelectedProp.push({ key, value: isSelectedPropObj[key] })
  2634. isSelectedProperties += key + ':' + isSelectedPropObj[key] + ';'
  2635. }
  2636. isSelectedProperties = isSelectedProperties.substring(
  2637. 0,
  2638. isSelectedProperties.length - 1
  2639. )
  2640. selectedProp.value = isSelectedProp
  2641. selectedProperties = isSelectedProperties
  2642. selectedPropObj = isSelectedPropObj
  2643. let isDefaultSku = null
  2644. let isFindSku = false
  2645. for (let i = 0; i < skuList.length; i++) {
  2646. // 解决排序问题导致无法匹配
  2647. if (
  2648. compareArray(
  2649. isSelectedProperties.split(';').sort(),
  2650. skuList[i].properties.split(';').sort()
  2651. )
  2652. ) {
  2653. isFindSku = true
  2654. isDefaultSku = skuList[i]
  2655. changeSkuImg(isDefaultSku.pic || isDefaultSku.imgUrl)
  2656. break
  2657. }
  2658. }
  2659. if (!isDefaultSku) {
  2660. isDefaultSku = {
  2661. pic: setSkuImg(skuList, selectedPropObj) || '',
  2662. price: 0,
  2663. properties: '',
  2664. seckillPrice: 0,
  2665. seckillSkuId: '',
  2666. seckillStocks: 0,
  2667. skuId: null
  2668. }
  2669. changeSkuImg(isDefaultSku.pic || isDefaultSku.imgUrl)
  2670. }
  2671. defaultSku.value = isDefaultSku
  2672. combineList.value = isDefaultSku.skuComboList || []
  2673. findSku.value = isFindSku
  2674. // 组装团购sku
  2675. setDefaultGroupSku()
  2676. }
  2677. const setSkuImg = (skuList, selObj) => {
  2678. for (let i = 0; i < allProperties.value.length; i++) {
  2679. const firstSku = allProperties.value[i].split(';')[0]
  2680. const index = firstSku.indexOf(':')
  2681. if (selObj[firstSku.slice(0, index)] === firstSku.substring(index + 1)) {
  2682. return skuList[i].pic
  2683. }
  2684. }
  2685. }
  2686. /**
  2687. * 比较两个数组中的元素是否相等
  2688. * @param a1 第一个数组
  2689. * @param a2 第二个数组
  2690. * @return boolean 两个数组中的元素都相等则返回 true,反之返回 false
  2691. */
  2692. const compareArray = (a1, a2) => {
  2693. if (!a1 || !a2) {
  2694. return false
  2695. }
  2696. if (a1.length !== a2.length) {
  2697. return false
  2698. }
  2699. for (let i = 0, n = a1.length; i < n; i++) {
  2700. if (a1[i] !== a2[i]) {
  2701. return false
  2702. }
  2703. }
  2704. return true
  2705. }
  2706. /**
  2707. * 切换SKU图片
  2708. */
  2709. const changeSkuImg = (skuPic) => {
  2710. if (!scoreFee) {
  2711. const selectedImg = skuPic || (prodImgs.value.length > 0 && prodImgs.value[0].img)
  2712. prodImgs.value.forEach(prodImg => {
  2713. prodImg.isActive = false
  2714. })
  2715. prodInfo.value.pic = checkFileUrl(selectedImg)
  2716. } else {
  2717. const selectedImg = skuPic || (prodInfo.value.imgs.split(',')[0])
  2718. if (prodImgs.value[0].sku) {
  2719. prodImgs.value.shift()
  2720. }
  2721. if (skuPic) {
  2722. prodImgs.value.forEach(prodImg => {
  2723. prodImg.isActive = false
  2724. })
  2725. prodImgs.value.unshift({
  2726. img: selectedImg,
  2727. isActive: true,
  2728. sku: true
  2729. })
  2730. }
  2731. prodInfo.value.pic = selectedImg
  2732. }
  2733. }
  2734. /**
  2735. * 判断当前的规格值 是否可以选
  2736. */
  2737. const isSkuLineItemNotOptional = (allProperties, selectedPropObj, key, item, propKeys) => {
  2738. const selectedPropObjTemp = Object.assign({}, selectedPropObj)
  2739. let properties = ''
  2740. selectedPropObjTemp[key] = item
  2741. for (let j = 0; j < propKeys.length; j++) {
  2742. if (propKeys[j] === key.substring(0, key.length - 1)) {
  2743. properties += propKeys[j] + ':' + item + ';'
  2744. } else {
  2745. properties += propKeys[j] + ':' + selectedPropObjTemp[propKeys[j]] + ';'
  2746. }
  2747. }
  2748. properties = properties.substring(0, properties.length - 1)
  2749. for (let i = 0; i < allProperties.length; i++) {
  2750. if (properties === allProperties[i]) {
  2751. return false
  2752. }
  2753. }
  2754. for (let i = 0; i < allProperties.length; i++) {
  2755. if (allProperties[i].indexOf(item) >= 0) {
  2756. return true
  2757. }
  2758. }
  2759. return false
  2760. }
  2761. /**
  2762. * 规格点击事件
  2763. */
  2764. let skuList = []
  2765. const toChooseItem = (skuLineItem, key) => {
  2766. selectedPropObj[key.replace(';', '')] = skuLineItem
  2767. if (prodInfo.value.productActivity.groupActivity) {
  2768. skuList = prodInfo.value.productActivity.groupActivity.groupSkuList
  2769. } else {
  2770. skuList = countdownFlag.value && prodInfo.value.productActivity.seckill ? prodInfo.value.productActivity.seckill.seckillSkuList : prodInfo.value.skuList
  2771. }
  2772. parseSelectedObjToVals(skuList)
  2773. resetProdNum()
  2774. }
  2775. // 重置商品数量
  2776. const resetProdNum = () => {
  2777. const objKey = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  2778. if (defaultSku.value[objKey]) {
  2779. // 有库存
  2780. prodNum.value = 1
  2781. prohibit1.value = true
  2782. prohibit2.value = false
  2783. } else {
  2784. prodNum.value = 0
  2785. prohibit1.value = true // 禁用
  2786. prohibit2.value = true // 禁用
  2787. }
  2788. }
  2789. /**
  2790. * 组装团购sku
  2791. */
  2792. const groupSkuList1 = [] // 团购sku列表
  2793. const setDefaultGroupSku = () => {
  2794. const groupSkuList = JSON.parse(JSON.stringify(groupSkuList1))
  2795. const selectedP = selectedProperties.split(';')
  2796. if (groupSkuList.length) {
  2797. for (let i = 0; i < groupSkuList.length; i++) {
  2798. // 进行规格名称对应判断
  2799. const skuNames = groupSkuList[i].properties.split(';')
  2800. const haveSku = selectedP.every(item => {
  2801. return skuNames.some(itemC => {
  2802. return item === itemC
  2803. })
  2804. })
  2805. // 规格名一一对应则进行赋值
  2806. if (haveSku) {
  2807. break
  2808. }
  2809. }
  2810. }
  2811. }
  2812. // 预售/组合选品/秒杀商品/非快递配送商品不能加入购物车
  2813. const verifyAddCart = () => {
  2814. const { prodType, preSellStatus, productActivity } = prodInfo.value
  2815. const preSellFlag = prodType === 0 && preSellStatus === 1 // 预售
  2816. const seckillFlag = prodType === 2 && productActivity.seckill && countdown.value.obj.signs === 1 // 已开始的秒杀
  2817. const actFlag = prodType === 5 // 组合选品
  2818. if (preSellFlag || seckillFlag || actFlag) {
  2819. ElMessage({
  2820. message: $t('prodDetail.addCartTips'),
  2821. type: 'error',
  2822. duration: 1500
  2823. })
  2824. return false
  2825. }
  2826. if (!prodInfo.value.deliveryModeVO?.hasShopDelivery) {
  2827. ElMessage({
  2828. message: $t('prodDetail.addCartTips2'),
  2829. type: 'error',
  2830. duration: 1500
  2831. })
  2832. return false
  2833. }
  2834. return true
  2835. }
  2836. /**
  2837. * 加入购物车
  2838. */
  2839. const addToCart = () => {
  2840. if (!defaultSku.value.isHasStock || !isDelivery.value) {
  2841. return
  2842. }
  2843. if (!verifyAddCart()) return
  2844. if (!findSku.value) {
  2845. return
  2846. }
  2847. if (!prodNum.value || prodNum.value <= 0) {
  2848. ElMessage({
  2849. message: $t('prodDetail.pleaseEnterTheCorrectNumberOfItems'),
  2850. type: 'warning',
  2851. duration: 1000
  2852. })
  2853. return
  2854. }
  2855. http.post('/p/shopCart/changeItem', {
  2856. basketId: 0,
  2857. count: prodNum.value,
  2858. prodId: route.query.prodId,
  2859. shopId: prodInfo.value.shopId,
  2860. shopName: shopInfo.value.shopName,
  2861. skuId: defaultSku.value.skuId,
  2862. stationId: 0,
  2863. type: 1
  2864. }).then(() => {
  2865. tapLog(4, prodNum.value)
  2866. getCartCount()
  2867. ElMessage.success($t('prodDetail.successfullyAddedCart'))
  2868. })
  2869. }
  2870. /**
  2871. * 获取购物车商品总数
  2872. */
  2873. const cartNumberStore = useCartNumberStore()
  2874. const getCartCount = () => {
  2875. http.get('/p/shopCart/prodCount', { params: { type: 1 } }).then(({ data }) => {
  2876. cartNumberStore.changeCartNumber(data)
  2877. })
  2878. }
  2879. /**
  2880. * 立即购买
  2881. */
  2882. const buyNow = () => {
  2883. if (!defaultSku.value.isHasStock || !isDelivery.value) {
  2884. return
  2885. }
  2886. if (!prodInfo.value.deliveryModeVO?.hasShopDelivery) {
  2887. return
  2888. }
  2889. if (!findSku.value) {
  2890. ElMessage.error($t('prodDetail.productNotInStock') + '~')
  2891. return
  2892. }
  2893. const key = countdownFlag.value && prodInfo.value.productActivity.seckill ? 'seckillStocks' : 'stocks'
  2894. if (!defaultSku.value[key] || prodNum.value > defaultSku.value[key]) {
  2895. ElMessage({
  2896. message: $t('prodDetail.insufficientInventory'),
  2897. type: 'warning',
  2898. duration: 1000
  2899. })
  2900. return
  2901. }
  2902. if (!prodNum.value || prodNum.value <= 0) {
  2903. ElMessage({
  2904. message: $t('prodDetail.pleaseEnterTheCorrectNumberOfItems'),
  2905. type: 'warning',
  2906. duration: 1000
  2907. })
  2908. return
  2909. }
  2910. if (!Cookie.get('bbcToken')) {
  2911. bus.emit('showLogin', true)
  2912. } else {
  2913. if (prodInfo.value.prodType === 0 || prodInfo.value.prodType === 1 || (prodInfo.value.prodType === 2 && !prodInfo.value.productActivity.seckill)) {
  2914. toSubmitOrder()
  2915. return
  2916. }
  2917. if (scoreFee) {
  2918. sessionStorage.setItem('bbcOrderItem', JSON.stringify({
  2919. prodId: prodInfo.value.prodId,
  2920. skuId: defaultSku.value.skuId,
  2921. prodCount: prodNum.value,
  2922. shopId: prodInfo.value.shopId
  2923. }))
  2924. router.push({
  2925. path: '/submit-integral-order',
  2926. query: {
  2927. orderEntry: 1,
  2928. addrId: addrId || null
  2929. }
  2930. })
  2931. return
  2932. }
  2933. if (countdownFlag.value && prodInfo.value.productActivity.seckill) {
  2934. const secKillObj = {
  2935. prodCount: prodNum.value,
  2936. seckillSkuId: defaultSku.value.seckillSkuId,
  2937. skuId: defaultSku.value.skuId,
  2938. prodId: prodInfo.value.prodId,
  2939. shopId: prodInfo.value.shopId
  2940. }
  2941. sessionStorage.setItem('bbcSecKillObj', JSON.stringify(secKillObj))
  2942. router.push({
  2943. path: '/secdetail',
  2944. query: {
  2945. seckillI: prodInfo.value.productActivity.seckill.seckillId,
  2946. addrId
  2947. }
  2948. })
  2949. } else {
  2950. // 原价购买弹窗确认
  2951. ElMessageBox.confirm($t('prodDetail.retailPriceTips'), $t('tips'), {
  2952. distinguishCancelAndClose: true,
  2953. confirmButtonText: $t('determine'),
  2954. cancelButtonText: $t('cancel')
  2955. }).then(() => {
  2956. toSubmitOrder()
  2957. })
  2958. }
  2959. }
  2960. }
  2961. /**
  2962. * 立即购买: 购买普通商品 / 原价购买秒杀商品
  2963. */
  2964. const toSubmitOrder = () => {
  2965. sessionStorage.setItem(
  2966. 'bbcOrderItem',
  2967. JSON.stringify({
  2968. prodId: prodInfo.value.prodId,
  2969. skuId: defaultSku.value.skuId,
  2970. prodCount: prodNum.value,
  2971. shopId: prodInfo.value.shopId
  2972. })
  2973. )
  2974. router.push({
  2975. path: '/submit-order',
  2976. query: {
  2977. orderEntry: 1,
  2978. addrId: addrId || null
  2979. }
  2980. })
  2981. }
  2982. /**
  2983. * 领取优惠券
  2984. */
  2985. const receiveCoupon = (coupon) => {
  2986. http.post('/p/myCoupon/receive', coupon.couponId).then(({ data }) => {
  2987. if (data) {
  2988. ElMessage({
  2989. message: data,
  2990. type: 'success',
  2991. duration: 1000
  2992. })
  2993. }
  2994. })
  2995. }
  2996. const prodName = ref('')
  2997. const toShopIndex = () => {
  2998. if (!shopId.value) return
  2999. const searchTerms = prodName.value.trim() // 去除字符串的头尾空格
  3000. if (!searchTerms) {
  3001. ElMessage({
  3002. message: $t('prodDetail.searchContentCannotBeEmpty'),
  3003. type: 'error',
  3004. duration: 1000
  3005. })
  3006. return
  3007. }
  3008. // 跳转到商品列表页
  3009. router.push({
  3010. path: '/shop-prod-list',
  3011. query: {
  3012. sid: shopId.value,
  3013. pn: searchTerms
  3014. }
  3015. })
  3016. }
  3017. // 跳转活动详情
  3018. const toDiscountDetail = (discountId) => {
  3019. router.push({
  3020. path: '/discount-detail',
  3021. query: {
  3022. discountId
  3023. }
  3024. })
  3025. }
  3026. // 跳转商家客服
  3027. const showLogin = ref(false)
  3028. const toChatIm = (prodInfo) => {
  3029. if (!prodInfo.shopId) return
  3030. if (Cookie.get('bbcToken')) {
  3031. const routeUrl = router.resolve({
  3032. path: '/chat',
  3033. query: {
  3034. shopId: prodInfo.shopId,
  3035. prodId: prodInfo.prodId
  3036. }
  3037. })
  3038. window.open(routeUrl.href, 'view_window')
  3039. } else {
  3040. showLogin.value = true
  3041. }
  3042. }
  3043. /**
  3044. * 隐藏登录弹窗
  3045. */
  3046. const hideLoginPop = () => {
  3047. showLogin.value = false
  3048. }
  3049. const selectComboId = ref(0)
  3050. const selectPackageRef = ref(null)
  3051. const showSelectPackage = ref(false)
  3052. const handleSelectPackage = () => {
  3053. if (!Cookie.get('bbcToken')) {
  3054. bus.emit('showLogin', true)
  3055. return
  3056. }
  3057. http.post('/combo/getCombo', {
  3058. comboId: selectComboId.value,
  3059. matchingProdIds: selectMatchIds.value
  3060. }).then(({ data }) => {
  3061. showSelectPackage.value = true
  3062. nextTick(() => {
  3063. selectPackageRef.value?.getComboData(data)
  3064. })
  3065. })
  3066. }
  3067. const hideSelectPackage = () => {
  3068. showSelectPackage.value = false
  3069. }
  3070. const isChecked = (combo) => {
  3071. return combo.required || combo.isChecked || false
  3072. }
  3073. const choiceCombNum = ref(0) // 选中的套餐商品数量
  3074. const selectComboItem = (comboItem) => {
  3075. if (comboItem.required) {
  3076. return
  3077. }
  3078. if (isChecked(comboItem)) {
  3079. comboItem.isChecked = false
  3080. const index = findProdIndex(comboItem.prodId)
  3081. if (index >= 0) {
  3082. selectMatchIds.value.splice(index, 1)
  3083. }
  3084. } else {
  3085. comboItem.isChecked = true
  3086. if (findProdIndex(comboItem.prodId) < 0) {
  3087. selectMatchIds.value.push(comboItem.prodId)
  3088. }
  3089. }
  3090. choiceCombNum.value = selectMatchNum()
  3091. }
  3092. const findProdIndex = (prodId) => {
  3093. return selectMatchIds.value.findIndex(item => item === prodId)
  3094. }
  3095. // 选择套餐
  3096. const selectCombo = (comboId) => {
  3097. selectComboId.value = comboId
  3098. selectMatchIds.value = []
  3099. // defaultCombo
  3100. for (let i = 0; i < comboList.value.length; i++) {
  3101. if (comboList.value[i].comboId === comboId) {
  3102. defaultCombo.value = comboList.value[i]
  3103. break
  3104. }
  3105. }
  3106. defaultCombo.value.matchingProds.forEach(item => {
  3107. if (item.required) {
  3108. item.isChecked = true
  3109. selectMatchIds.value.push(item.prodId)
  3110. } else {
  3111. item.isChecked = false
  3112. }
  3113. })
  3114. choiceCombNum.value = selectMatchNum()
  3115. }
  3116. // 跳转到积分商品详情
  3117. const toScoreProdDet = (prodId, scoreFee) => {
  3118. http.get('/search/page', {
  3119. params: {
  3120. current: 1,
  3121. size: 20,
  3122. prodType: 3,
  3123. sort: 2,
  3124. stationId: 0 // 过滤掉门店发布的商品
  3125. }
  3126. }).then(() => {
  3127. const newPage = router.resolve({
  3128. path: '/detail',
  3129. query: {
  3130. prodId,
  3131. scoreFee
  3132. }
  3133. })
  3134. window.open(newPage.href, '_blank', 'noopener,noreferrer')
  3135. })
  3136. }
  3137. /**
  3138. * 去往 资质证照 页面
  3139. */
  3140. const onToQualifications = (prodInfo) => {
  3141. if (!prodInfo.shopId) return
  3142. router.push({
  3143. path: '/shop-qualifications',
  3144. query: {
  3145. sid: prodInfo.shopId
  3146. }
  3147. })
  3148. }
  3149. const isShowDeliveryPop = ref(false) // 选择地址弹窗显示
  3150. const deliveryList = ref([])
  3151. const onOpenDeliveryPop = (falg = true) => {
  3152. http.get('/p/address/list', {
  3153. params: {
  3154. isDefaultFirst: false
  3155. }
  3156. }).then(({ data }) => {
  3157. data.sort((a, b) => {
  3158. return b.commonAddr - a.commonAddr
  3159. })
  3160. deliveryList.value = data
  3161. if (falg) isShowDeliveryPop.value = true
  3162. })
  3163. }
  3164. // 切换地址
  3165. const onSelectDelivery = async (id) => {
  3166. addrId = id
  3167. await getProdInfo(addrId)
  3168. if (prodInfo.value.prodType === 0 || prodInfo.value.prodType === 3) {
  3169. groupSkuProp(prodInfo.value.skuList, defaultSku.value.price, true)
  3170. } else {
  3171. // 秒杀/团购 商品
  3172. if (timer) clearTimeout(timer)
  3173. groupBuyInfo(addrId)
  3174. }
  3175. isShowDeliveryPop.value = false
  3176. resetProdNum()
  3177. }
  3178. const onClosePop = () => {
  3179. if (isShowDeliveryPop.value) isShowDeliveryPop.value = false
  3180. }
  3181. </script>
  3182. <style lang="scss" scoped>
  3183. @use './index.scss';
  3184. </style>