index.vue 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. <template>
  2. <view class="rf-product-detail">
  3. <!--顶部返回按钮-->
  4. <!--#ifdef MP-WEIXIN-->
  5. <text class="back-btn iconfont iconzuo" @tap="navBack"></text>
  6. <!--#endif-->
  7. <!--header-->
  8. <view class="detail" v-if="product.name">
  9. <!--顶部礼品轮播图-->
  10. <view class="carousel">
  11. <swiper indicator-dots circular="true" duration="400" controls touchable>
  12. <!--#ifdef APP-PLUS-->
  13. <swiper-item class="swiper-item" v-if="product.video_url">
  14. <video muted :poster="product.covers[0]" object-fit="contain" :src="product.video_url"></video>
  15. </swiper-item>
  16. <!--#endif-->
  17. <swiper-item
  18. class="swiper-item"
  19. v-for="(item, index) in product.covers"
  20. :key="index"
  21. >
  22. <view class="image-wrapper">
  23. <image :src="item" class="loaded" mode="aspectFill"></image>
  24. </view>
  25. <uni-tag class="content" circle size="small" :text="`${index + 1} / ${product.covers.length}`"></uni-tag>
  26. </swiper-item>
  27. </swiper>
  28. </view>
  29. <!--礼品信息-->
  30. <view class="introduce-section">
  31. <view class="introduce-first-line">
  32. <view
  33. class="price-box point-box"
  34. v-if="product.point_exchange_type == 4"
  35. >
  36. 该礼品仅需
  37. <text class="price">{{ product.point_exchange }} 积分</text>
  38. </view>
  39. <view class="price-box" v-else>
  40. <view class="price-first-line">
  41. <image
  42. class="member-level"
  43. mode="aspectFit"
  44. v-if="product.memberDiscount != [] && product.memberDiscount && product.memberDiscount.discount > 0 && userInfo"
  45. :src="vipPrice">
  46. </image>
  47. <text class="price" :class="'text-' + themeColor.name">{{ moneySymbol }}{{ currentProductPrice }}</text>
  48. </view>
  49. <view class="m-price-wrapper" v-if="product.market_price > product.price">
  50. 价格 <text class="m-price">{{ moneySymbol }}{{ product.market_price }}</text>
  51. </view>
  52. </view>
  53. <view class="collect" @tap="toFavorite">
  54. <view class="iconfont" :class="[ favorite ? `text-${themeColor.name} iconshixin1` : 'iconguanzhu']"></view>
  55. <text>收藏</text>
  56. </view>
  57. </view>
  58. <view class="introduce-second-line">
  59. <view class="title">
  60. <text>{{ product.name }}</text>
  61. <text class="sketch">{{ product.sketch }}</text>
  62. </view>
  63. <view class="share">
  64. <rf-tag
  65. type="gray"
  66. size="small"
  67. tui-tag-class="tui-tag-share tui-size"
  68. shape="circleLeft"
  69. >
  70. <button class="share-btn" open-type="share" :class="'text-' + themeColor.name" @tap.stop="share()">
  71. <text class="iconfont iconfenxiang"></text>
  72. <text class="tui-share-text tui-gray">分享</text>
  73. </button>
  74. </rf-tag>
  75. </view>
  76. </view>
  77. <view class="product-tag">
  78. <uni-tag
  79. @tap="openPoster"
  80. class="tag"
  81. circle
  82. type="base"
  83. text="生成海报"
  84. size="small"
  85. />
  86. </view>
  87. <view class="data" v-if="product">
  88. <text class="item">快递: {{ product.shipping_type === '1' ? '包邮' : '买家自付' }}</text>
  89. <text class="item">月销 {{ product.total_sales }}</text>
  90. <text v-if="product.address_name" class="item in1line">{{ product.address_name }}</text>
  91. </view>
  92. </view>
  93. <!--礼品参数-->
  94. <view class="c-list">
  95. <!--礼品库存-->
  96. <rf-item-popup
  97. title="礼品库存"
  98. v-if="parseInt(product.is_stock_visible, 10) == 1"
  99. :isEmpty="parseInt(currentStock, 10) === 0"
  100. empty="库存不足"
  101. >
  102. <view slot="content">
  103. {{ currentStock || product.stock || 0 }} {{ product.unit || '件' }}
  104. </view>
  105. </rf-item-popup>
  106. <!--满减送-->
  107. <rf-item-popup
  108. v-if="product.fullGiveRule.length > 0"
  109. title="满减送"
  110. @hide="hideService"
  111. @show="
  112. showPopupService(
  113. 'fullGiveClass',
  114. product.fullGiveRule
  115. )
  116. "
  117. :specClass="fullGiveClass"
  118. >
  119. <view slot="content" class="con-list">
  120. <text :class="'text-' + themeColor.name">{{ product.fullGiveRule[0] }}</text>
  121. </view>
  122. <view slot="right" v-if="product.fullGiveRule.length > 0"
  123. ><text class="iconfont iconyou"></text
  124. ></view>
  125. <view slot="popup" class="service">
  126. <view class="content">
  127. <view
  128. class="row"
  129. v-for="(item, index) in product.fullGiveRule"
  130. :key="index"
  131. >
  132. <text>{{ item }}</text>
  133. </view>
  134. </view>
  135. <button class="btn" :class="'bg-' + themeColor.name" @tap="hideService">完成</button>
  136. </view>
  137. </rf-item-popup>
  138. <!--满包邮-->
  139. <rf-item-popup
  140. v-if="product.fullMail && product.fullMail.is_open === '1' && product.shipping_type !== '1'"
  141. title="满包邮"
  142. >
  143. <view slot="content" :class="'text-' + themeColor.name">满{{ product.fullMail.full_mail_money }}元包邮</view>
  144. </rf-item-popup>
  145. <!--购买类型-->
  146. <rf-item-popup
  147. title="购买类型"
  148. @hide="hideService"
  149. :specClass="specClass"
  150. @show="toggleSpec"
  151. >
  152. <view slot="content">
  153. <text class="selected-text" v-if="currentSkuName === singleSkuText">{{ currentCartCount }} {{ product.unit || '件' }}</text>
  154. <text class="selected-text" v-else-if="currentSkuName">{{ currentSkuName }} * {{ currentCartCount }}</text>
  155. <text class="selected-text" v-else>请选择规格</text>
  156. </view>
  157. <view slot="right"><text class="iconfont iconyou"></text></view>
  158. <view slot="popup" @click.stop="stopPrevent">
  159. <rf-attr-content
  160. :type="type"
  161. :product="product"
  162. :minNum="minNum"
  163. :maxNum="maxNum"
  164. @toggle="toggleSpec"
  165. ></rf-attr-content>
  166. </view>
  167. </rf-item-popup>
  168. <!--优惠券-->
  169. <rf-item-popup
  170. title="优惠券"
  171. @hide="hideService"
  172. :specClass="couponClass"
  173. @show="showPopupService('couponClass', product.canReceiveCoupon)"
  174. :isEmpty="product.canReceiveCoupon.length === 0"
  175. empty="暂无可领取优惠券"
  176. >
  177. <view slot="content">
  178. <text class="con t-r">领取优惠券</text>
  179. </view>
  180. <view slot="right" v-if="product.canReceiveCoupon.length > 0"><text class="iconfont iconyou"></text></view>
  181. <view slot="popup" class="service">
  182. <!-- 优惠券列表 -->
  183. <view class="sub-list valid">
  184. <view
  185. class="row"
  186. v-for="(item, index) in product.canReceiveCoupon"
  187. :key="index"
  188. @tap.stop="getCoupon(item)"
  189. >
  190. <view class="carrier">
  191. <view class="title">
  192. <view>
  193. <text class="cell-icon" :class="'bg-' + themeColor.name">{{
  194. parseInt(item.range_type, 10) === 2 ? '限' : '全'
  195. }}</text>
  196. <text class="cell-title">{{ item.title }}</text>
  197. </view>
  198. <view :class="'text-' + themeColor.name">
  199. <text class="price" v-if="item.type === '1'">{{ moneySymbol }}{{ item.money }}</text>
  200. <text class="price-discount" v-else>{{ `${item.discount / 10}折` }}</text>
  201. </view>
  202. </view>
  203. <view class="term">
  204. <text>{{ item.start_time | time }} ~ {{ item.end_time | time }}</text>
  205. <text class="at_least">满{{ item.at_least }}可用</text>
  206. </view>
  207. <view class="usage">
  208. <text>
  209. {{
  210. parseInt(item.range_type, 10) === 2
  211. ? '部分产品使用'
  212. : '全场产品使用'
  213. }}
  214. </text>
  215. <view>
  216. {{
  217. parseInt(item.max_fetch, 10) === 0
  218. ? '不限'
  219. : `每人限领${item.max_fetch}`
  220. }}
  221. 已领{{ item.get_count }}
  222. <text class="last" v-if="item.percentage"
  223. >剩余{{ item.percentage }}%</text
  224. >
  225. </view>
  226. </view>
  227. </view>
  228. </view>
  229. </view>
  230. </view>
  231. </rf-item-popup>
  232. <!--限购说明-->
  233. <rf-item-popup title="限购说明" v-if="type === 'buy_now' && parseInt(product.max_buy, 10) > 0">
  234. <view slot="content">
  235. <text>{{ `${product.max_buy} ${product.unit || '件'}` }}</text>
  236. </view>
  237. </rf-item-popup>
  238. <!--积分活动-->
  239. <rf-item-popup title="积分活动" v-if="product.point_exchange_type !== '1'">
  240. <view slot="content" class="con-list">
  241. <text v-if="product.point_exchange_type">兑换类型: {{ product.point_exchange_type | pointExchangeTypeFilter }}</text>
  242. <text v-if="parseInt(product.give_point, 10) > 0">赠送类型: {{ product.integral_give_type | integralGiveTypeFilter }}</text>
  243. <text v-if="parseInt(product.give_point, 10) > 0">下单可获得: {{ product | givePointFilter }}积分</text>
  244. <text v-if="product.point_exchange != 0">兑换所需积分: {{ product.point_exchange }}
  245. </text>
  246. <text v-if="product.max_use_point != 0">可使用抵扣积分: {{ product.max_use_point }}</text>
  247. <text
  248. class="buy-now"
  249. @tap="addCart('buy', true)"
  250. v-if="product.point_exchange_type == 3"
  251. >积分兑换 >>
  252. </text>
  253. </view>
  254. </rf-item-popup>
  255. <!--服务-->
  256. <rf-item-popup
  257. v-if="product.tags.length > 0"
  258. title="服务"
  259. @hide="hideService"
  260. @show="showPopupService('serviceClass', product.tags)"
  261. :specClass="serviceClass"
  262. >
  263. <view slot="content">
  264. <text>{{ product.tags[0] }}</text>
  265. </view>
  266. <view slot="right" v-if="product.tags.length > 0"
  267. ><text class="iconfont iconyou"></text
  268. ></view>
  269. <view slot="popup" class="service">
  270. <view class="content">
  271. <view
  272. class="row"
  273. v-for="(item, index) in product.tags"
  274. :key="index"
  275. >
  276. <view class="description">{{ item }}</view>
  277. </view>
  278. </view>
  279. <button class="btn" :class="'bg-' + themeColor.name" @tap="hideService">完成</button>
  280. </view>
  281. </rf-item-popup>
  282. <!--阶梯优惠-->
  283. <rf-item-popup
  284. title="阶梯优惠"
  285. @hide="hideService"
  286. @show="
  287. showPopupService(
  288. 'ladderPreferentialClass',
  289. product.ladderPreferential
  290. )
  291. "
  292. :specClass="ladderPreferentialClass"
  293. v-if="product.ladderPreferential.length > 0"
  294. >
  295. <view slot="content" class="con-list">
  296. <text>
  297. 满{{
  298. product.ladderPreferential &&
  299. product.ladderPreferential[0] &&
  300. product.ladderPreferential[0].quantity
  301. }}{{ product.unit || '件' }}
  302. <text
  303. v-if="
  304. parseInt(
  305. product.ladderPreferential &&
  306. product.ladderPreferential[0] &&
  307. product.ladderPreferential[0].type,
  308. 10
  309. ) === 1
  310. "
  311. >
  312. 每{{ product.unit || '件' }}减{{
  313. product.ladderPreferential &&
  314. product.ladderPreferential[0] &&
  315. product.ladderPreferential[0].price
  316. }}元</text
  317. >
  318. <text
  319. v-if="
  320. parseInt(
  321. product.ladderPreferential &&
  322. product.ladderPreferential[0] &&
  323. product.ladderPreferential[0].type,
  324. 10
  325. ) === 2
  326. "
  327. >
  328. 每{{ product.unit || '件' }}{{
  329. parseInt(
  330. product.ladderPreferential &&
  331. product.ladderPreferential[0] &&
  332. product.ladderPreferential[0].price,
  333. 10
  334. )
  335. }}折</text
  336. >
  337. </text>
  338. </view>
  339. <view slot="right" v-if="product.ladderPreferential.length > 0"
  340. ><text class="iconfont iconyou"></text
  341. ></view>
  342. <view slot="popup" class="service">
  343. <view class="content">
  344. <view
  345. class="row"
  346. v-for="(item, index) in product.ladderPreferential"
  347. :key="index"
  348. >
  349. <view class="title"
  350. >满{{ item.quantity }}{{ product.unit || '件' }}
  351. <text v-if="parseInt(item.type, 10) === 1"
  352. >每{{ product.unit || '件' }}减{{ item.price }}元</text
  353. >
  354. <text v-if="parseInt(item.type, 10) === 2"
  355. >每{{ product.unit || '件' }}{{ parseInt(item.price, 10) }}折</text
  356. >
  357. </view>
  358. </view>
  359. </view>
  360. <button class="btn" :class="'bg-' + themeColor.name" @tap="hideService">完成</button>
  361. </view>
  362. </rf-item-popup>
  363. <!--礼品参数-->
  364. <rf-item-popup
  365. title="礼品参数"
  366. @hide="hideService"
  367. @show="
  368. showPopupService(
  369. 'attributeValueClass',
  370. product.attributeValue
  371. )
  372. "
  373. :specClass="attributeValueClass"
  374. v-if="product.attributeValue.length > 0"
  375. >
  376. <view slot="content">
  377. <text>
  378. {{
  379. `${product.attributeValue &&
  380. product.attributeValue[0] &&
  381. product.attributeValue[0]
  382. .title}: ${product.attributeValue &&
  383. product.attributeValue[0] &&
  384. product.attributeValue[0].value}`
  385. }}</text
  386. >
  387. </view>
  388. <view slot="right" v-if="product.attributeValue.length > 0"
  389. ><text class="iconfont iconyou"></text
  390. ></view>
  391. <view slot="popup" class="service">
  392. <view class="content">
  393. <view
  394. class="row"
  395. v-for="(item, index) in product.attributeValue"
  396. :key="index"
  397. >
  398. <view class="title">
  399. {{ `${item.title}: ${item.value}` }}
  400. </view>
  401. </view>
  402. </view>
  403. <button class="btn" :class="'bg-' + themeColor.name" @tap="hideService">完成</button>
  404. </view>
  405. </rf-item-popup>
  406. </view>
  407. <!-- 评价 -->
  408. <view class="eva-section" @tap="toEvaluateList">
  409. <view class="e-header">
  410. <text class="tit">评价({{ product.comment_num || 0 }})</text>
  411. <text class="tip" v-if="product.match_ratio"
  412. >好评率 {{ product.match_ratio }}%</text
  413. >
  414. <text class="tip" v-else>暂无评价信息</text>
  415. <i class="iconfont iconyou"></i>
  416. </view>
  417. <view
  418. class="eva-box"
  419. v-if="product.evaluate && product.evaluate.length > 0"
  420. >
  421. <image
  422. class="portrait"
  423. :src="
  424. (product.evaluate &&
  425. product.evaluate[0] &&
  426. product.evaluate[0].member_head_portrait) ||
  427. headImg
  428. "
  429. mode="aspectFill"
  430. ></image>
  431. <view class="right">
  432. <view class="name">
  433. <text>
  434. {{
  435. (product.evaluate &&
  436. product.evaluate[0] &&
  437. product.evaluate[0].member_nickname) ||
  438. '匿名用户'
  439. }}
  440. </text>
  441. <rf-rate
  442. v-if="evaluateList.length > 0"
  443. size="16"
  444. disabled="true"
  445. :value="evaluateList[0].scores"
  446. :active-color="themeColor.color"
  447. />
  448. </view>
  449. <text class="con in2line">{{
  450. (product.evaluate &&
  451. product.evaluate[0] &&
  452. product.evaluate[0].content) ||
  453. '这个人很懒,什么都没留下~'
  454. }}</text>
  455. <view class="bot">
  456. <text class="attr"
  457. >购买类型:{{
  458. (product.evaluate &&
  459. product.evaluate[0] &&
  460. product.evaluate[0].sku_name) ||
  461. singleSkuText
  462. }}</text>
  463. <text class="time">{{
  464. product.evaluate &&
  465. product.evaluate[0] &&
  466. product.evaluate[0].created_at | time
  467. }}</text>
  468. </view>
  469. </view>
  470. </view>
  471. </view>
  472. <!--底部礼品详情-->
  473. <view class="detail-desc">
  474. <view class="d-header">
  475. <text>礼品详情</text>
  476. </view>
  477. <rf-parser :html="product.intro" lazy-load></rf-parser>
  478. </view>
  479. <!-- 底部操作菜单 -->
  480. <view class="page-bottom">
  481. <view class="page-bottom-bth-wrapper">
  482. <navigator
  483. url="/pages/index/index"
  484. open-type="switchTab"
  485. class="p-b-btn"
  486. >
  487. <i class="iconfont iconzhuyedefuben"></i>
  488. <text>首页</text>
  489. </navigator>
  490. <navigator
  491. url="/pages/service/service"
  492. open-type="switchTab"
  493. class="p-b-btn cart"
  494. >
  495. <i class="iconfont icongouwuche2"></i>
  496. <text>服务</text>
  497. <rf-badge
  498. v-if="hasLogin && cartNum && cartNum > 0"
  499. type="error"
  500. size="small"
  501. class="badge"
  502. :text="cartNum"
  503. ></rf-badge>
  504. </navigator>
  505. <view @tap="kefuShow = true" class="p-b-btn">
  506. <i class="iconfont iconkefu2"></i>
  507. <text>客服</text>
  508. </view>
  509. </view>
  510. <view
  511. class="action-btn-group"
  512. v-if="parseInt(this.currentStock || this.product.stock, 10) > 0"
  513. >
  514. <button
  515. class="action-btn"
  516. :class="'bg-' + themeColor.name"
  517. :disabled="buyBtnDisabled"
  518. @tap="addCart('buy')"
  519. >
  520. 立即购买
  521. </button>
  522. <button
  523. :disabled="addCartBtnDisabled"
  524. class="action-btn"
  525. :class="'bg-' + themeColor.name"
  526. @tap="addCart('cart')"
  527. >
  528. 加入购物车
  529. </button>
  530. </view>
  531. <view class="action-btn">
  532. <button
  533. v-if="parseInt(this.currentStock || this.product.stock, 10) === 0"
  534. class="action-btn-submit"
  535. :disabled="buyBtnDisabled"
  536. >
  537. 库存不足
  538. </button>
  539. </view>
  540. </view>
  541. </view>
  542. <!-- 分享引导 -->
  543. <view
  544. class="popup spec show"
  545. v-if="shareClass === 'show'"
  546. @touchmove.stop.prevent="stopPrevent"
  547. @tap="hideShareSpec"
  548. >
  549. <!-- 遮罩层 -->
  550. <view class="mask" @tap="hideShareSpec"></view>
  551. <view class="share-bg">
  552. <image :src="shareBg"></image>
  553. </view>
  554. </view>
  555. <view class="hideCanvasView" v-if="canvasShow">
  556. <canvas class="hideCanvas" canvas-id="default_PosterCanvasId" :style="{width: (poster.width||10) + 'px', height: (poster.height||10) + 'px'}"></canvas>
  557. </view>
  558. <!--回到顶部-->
  559. <rf-live v-if="product.name"></rf-live>
  560. <!--#ifdef MP-->
  561. <rf-nav></rf-nav>
  562. <!--#endif-->
  563. <view
  564. class="popup spec show"
  565. v-if="kefuShow"
  566. @touchmove.stop.prevent="stopPrevent"
  567. @tap="hide"
  568. >
  569. <!-- 遮罩层 -->
  570. <view class="mask" @tap="hide"></view>
  571. <view class="kefu-bg">
  572. <image :src="appServiceQr"></image>
  573. </view>
  574. </view>
  575. </view>
  576. </template>
  577. <script>
  578. /**
  579. *@des 封装礼品详情
  580. *@author stav stavyan@qq.com
  581. *@blog https://stavtop.club
  582. *@date 2020/05/15 16:22:24
  583. */
  584. import rfItemPopup from '@/components/rf-item-popup';
  585. import moment from '@/common/moment';
  586. import rfAttrContent from '@/components/rf-attr-content';
  587. import rfRate from '@/components/rf-rate/rf-rate';
  588. import rfBadge from '@/components/rf-badge/rf-badge';
  589. import uniTag from '@/components/uni-tag/uni-tag';
  590. import rfNav from '@/components/rf-nav';
  591. import rfLive from '@/components/rf-live';
  592. import { cartItemCount, cartItemCreate } from '@/api/product';
  593. import { collectCreate, collectDel, pickupPointIndex, transmitCreate } from '@/api/basic';
  594. import { couponReceive, addressList } from '@/api/userInfo';
  595. import { mapMutations } from 'vuex';
  596. export default {
  597. name: 'rfProductDetail',
  598. props: {
  599. product: {
  600. type: Object,
  601. default() {
  602. return {
  603. };
  604. }
  605. },
  606. userInfo: {
  607. type: Object,
  608. default() {
  609. return {};
  610. }
  611. },
  612. url: {
  613. type: String,
  614. default: ''
  615. },
  616. marketType: {
  617. type: String,
  618. default: 'buy_now'
  619. }
  620. },
  621. components: {
  622. rfNav,
  623. rfItemPopup,
  624. rfBadge,
  625. rfLive,
  626. rfRate,
  627. uniTag,
  628. rfAttrContent
  629. },
  630. data() {
  631. return {
  632. appServiceQr: this.$mSettingConfig.appServiceQr,
  633. kefuShow: false,
  634. addressClass: 'none',
  635. canvasShow: true,
  636. logo: this.$mSettingConfig.appLogo,
  637. vipPrice: this.$mAssetsPath.vipPrice,
  638. posterShow: false,
  639. serviceClass: 'none', // 服务弹窗
  640. ladderPreferentialClass: 'none', // 阶梯优惠弹窗
  641. attributeValueClass: 'none', // 礼品参数弹窗
  642. specClass: 'none', // 礼品参数弹窗
  643. couponClass: 'none', // 优惠券弹窗
  644. shareClass: 'none', // 分享引导弹窗
  645. fullGiveClass: 'none', // 满减送弹窗
  646. cartType: null, // 下单类型
  647. couponList: [], // 优惠券列表
  648. currentStock: null,
  649. currentSkuPrice: null,
  650. currentSkuName: null,
  651. currentCartCount: 1,
  652. evaluateList: [],
  653. hasLogin: this.$mStore.getters.hasLogin,
  654. cartNum: uni.getStorageSync('cartNum'),
  655. addressTypeList: this.$mConstDataConfig.addressTypeList,
  656. tabCurrentIndex: 0,
  657. loading: true,
  658. errorInfo: '',
  659. headImg: this.$mAssetsPath.headImg,
  660. isPointExchange: false,
  661. shareBg: this.$mAssetsPath.shareBg,
  662. appServiceType: this.$mSettingConfig.appServiceType,
  663. productPosterQrType: this.$mSettingConfig.productPosterQrType,
  664. appName: this.$mSettingConfig.appName,
  665. shareFrom: '',
  666. poster: {},
  667. promoCode: '',
  668. addressList: [],
  669. moneySymbol: this.moneySymbol,
  670. state: 1,
  671. singleSkuText: this.singleSkuText,
  672. thirdPartyQrCodeImg: ''
  673. };
  674. },
  675. async onShareAppMessage () {
  676. // #ifdef MP
  677. await this.$http.post(`${transmitCreate}`, {
  678. topic_type: 'product',
  679. topic_id: this.productId
  680. }).then(() => {
  681. return {
  682. title: this.productDetail.name,
  683. path: `/pages/product/product?id=${this.productId}`
  684. };
  685. });
  686. // #endif
  687. },
  688. filters: {
  689. time(val) {
  690. return moment(val * 1000).format('YYYY-MM-DD HH:mm');
  691. },
  692. pointExchangeTypeFilter(val) {
  693. const type = [
  694. '',
  695. '非积分兑换',
  696. '积分加现金',
  697. '积分兑换或直接购买',
  698. '只支持积分兑换'
  699. ];
  700. return type[parseInt(val, 10)];
  701. },
  702. integralGiveTypeFilter(val) {
  703. const type = ['固定积分', '百分比'];
  704. return type[parseInt(val, 10)];
  705. },
  706. givePointFilter(val) {
  707. return val.integral_give_type === '1'
  708. ? Math.round((parseInt(val.give_point, 10) / 100) * parseInt(val.minSkuPrice, 10))
  709. : parseInt(val.give_point, 10);
  710. }
  711. },
  712. computed: {
  713. type() {
  714. return 'buy_now';
  715. },
  716. // 购买按钮禁用
  717. buyBtnDisabled() {
  718. return parseInt(this.currentStock || this.product.stock, 10) === 0;
  719. },
  720. // 添加购物车按钮禁用
  721. addCartBtnDisabled() {
  722. return (
  723. this.product.point_exchange_type === '2' ||
  724. this.product.point_exchange_type === '4' ||
  725. parseInt(this.currentStock || this.product.stock, 10) === 0 ||
  726. this.product.is_virtual === '1'
  727. );
  728. },
  729. // 最小购买数量
  730. minNum() {
  731. return 1;
  732. },
  733. // 最小购买数量
  734. maxNum() {
  735. let maxNum = 0;
  736. return maxNum;
  737. },
  738. favorite () {
  739. return !!this.product.myCollect;
  740. },
  741. // 计算倒计时时间
  742. second() {
  743. return function(val) {
  744. return Math.floor(val - new Date() / 1000);
  745. };
  746. },
  747. currentProductPrice () {
  748. let price;
  749. if (this.type === 'buy_now') {
  750. if (this.product.memberDiscount && this.product.memberDiscount.length !== 0) {
  751. // eslint-disable-next-line
  752. this.product.minSkuPrice = this.product.minSkuPrice * (1 - this.product.memberDiscount.discount / 100).toFixed(2);
  753. // eslint-disable-next-line
  754. this.product.maxSkuPrice = this.product.maxSkuPrice ? (this.product.maxSkuPrice * (1 - this.product.memberDiscount.discount / 100)).toFixed(2) : 0;
  755. }
  756. // eslint-disable-next-line
  757. price = this.currentSkuPrice || ((this.product.maxSkuPrice && (this.product.minSkuPrice !== this.product.maxSkuPrice)) ? (this.product.minSkuPrice + ' ~ ' + this.product.maxSkuPrice) : parseFloat(this.product.minSkuPrice).toFixed(2));
  758. return price;
  759. }
  760. return parseFloat(price || '0').toFixed(2);
  761. }
  762. },
  763. methods: {
  764. ...mapMutations(['setCartNum']),
  765. // 返回上一页
  766. navBack() {
  767. this.$mRouter.back();
  768. },
  769. hide() {
  770. this.kefuShow = false;
  771. },
  772. // 分享礼品
  773. share() {
  774. // #ifdef H5
  775. if (this.$mPayment.isWechat()) {
  776. this.shareClass = 'show';
  777. } else {
  778. this.$mHelper.h5Copy(this.url);
  779. }
  780. // #endif
  781. // #ifdef APP-PLUS
  782. this.$mHelper.handleAppShare(this.url, this.appName, this.product.name, this.product.picture);
  783. // #endif
  784. },
  785. // 通用跳转
  786. navTo(route) {
  787. if (this.appServiceType === '1' && route === '/pages/product/service/index') {
  788. this.kefuShow = true;
  789. return;
  790. }
  791. if (!this.hasLogin) {
  792. this.$mHelper.backToLogin();
  793. } else {
  794. if (this.appServiceType === '0') {
  795. this.$mHelper.toast('暂不提供客服功能');
  796. } else {
  797. this.$mRouter.push({ route });
  798. }
  799. }
  800. },
  801. // 弹窗显示
  802. showPopupService(type, list) {
  803. if (list.length === 0) return;
  804. this[type] = 'show';
  805. },
  806. // 关闭服务弹窗
  807. hideService() {
  808. this.specClass = 'none';
  809. this.couponClass = 'none';
  810. this.serviceClass = 'none';
  811. this.ladderPreferentialClass = 'none';
  812. this.attributeValueClass = 'none';
  813. this.fullGiveClass = 'none';
  814. },
  815. // 获取优惠券
  816. async getCoupon(item) {
  817. if (!this.hasLogin) {
  818. await this.$mHelper.backToLogin();
  819. return;
  820. }
  821. await this.$http
  822. .post(`${couponReceive}`, {
  823. id: item.id
  824. })
  825. .then(() => {
  826. this.$mHelper.toast('领取成功');
  827. });
  828. },
  829. // 跳转至评价列表
  830. toEvaluateList() {
  831. if (!this.product.evaluateStat || parseInt(this.product.comment_num, 10) === 0) return;
  832. this.$mRouter.push({
  833. route: `/pages/order/evaluation/list?comment_num=${
  834. this.product.comment_num
  835. }&evaluateStat=${JSON.stringify(this.product.evaluateStat)}`
  836. });
  837. },
  838. // 顶部tab点击
  839. tabClick(index, state) {
  840. this.page = 1;
  841. this.addressList.length = 0;
  842. this.tabCurrentIndex = index;
  843. this.state = state;
  844. const api = (this.state === 1 ? addressList : pickupPointIndex);
  845. this.getAddressList(api);
  846. }, // 获取收货地址列表
  847. async getAddressList(api) {
  848. await this.$http
  849. .get(api, {
  850. })
  851. .then(r => {
  852. this.addressList = r.data;
  853. });
  854. },
  855. // 规格弹窗开关
  856. toggleSpec(row) {
  857. if (!this.product.id) return;
  858. if (this.specClass === 'show') {
  859. this.currentStock = row.stock;
  860. this.currentSkuPrice = row.price;
  861. this.currentSkuName = row.skuName;
  862. this.currentCartCount = row.cartCount;
  863. const skuId = row.skuId;
  864. if (parseInt(this.currentStock, 10) === 0) {
  865. this.$mHelper.toast('库存不足');
  866. return;
  867. }
  868. if (this.cartType === 'cart') {
  869. this.handleCartItemCreate(skuId);
  870. } else if (this.cartType === 'buy') {
  871. this.buy(skuId);
  872. }
  873. this.cartType = null;
  874. this.specClass = 'hide';
  875. setTimeout(() => {
  876. this.specClass = 'none';
  877. }, 250);
  878. } else if (this.specClass === 'none') {
  879. this.specClass = 'show';
  880. }
  881. },
  882. // 海报弹窗开关
  883. async openPoster() {
  884. this.$mHelper.toast('该版本不支持生成海报');
  885. },
  886. hideSpec() {
  887. this.specClass = 'hide';
  888. setTimeout(() => {
  889. this.specClass = 'none';
  890. }, 250);
  891. },
  892. hideShareSpec() {
  893. this.shareClass = 'hide';
  894. setTimeout(() => {
  895. this.shareClass = 'none';
  896. }, 250);
  897. },
  898. // 添加礼品至购物车
  899. async handleCartItemCreate(skuId) {
  900. await this.$http
  901. .post(`${cartItemCreate}`, {
  902. sku_id: skuId,
  903. num: this.currentCartCount
  904. })
  905. .then(() => {
  906. this.$mHelper.toast('添加购物车成功');
  907. this.$http.get(`${cartItemCount}`).then(r => {
  908. this.setCartNum(r.data);
  909. this.cartNum = r.data;
  910. });
  911. });
  912. },
  913. // 收藏
  914. async toFavorite() {
  915. if (!this.product.id) return;
  916. if (!this.hasLogin) {
  917. this.specClass = 'none';
  918. await this.$mHelper.backToLogin();
  919. } else {
  920. this.favorite ? this.handleCollectDel() : this.handleCollectCreate();
  921. }
  922. },
  923. // 收藏礼品
  924. async handleCollectCreate() {
  925. await this.$http
  926. .post(`${collectCreate}`, {
  927. topic_id: this.product.id,
  928. topic_type: 'product'
  929. })
  930. .then(() => {
  931. this.$mHelper.toast('收藏成功');
  932. this.$emit('product');
  933. });
  934. },
  935. // 取消收藏礼品
  936. async handleCollectDel() {
  937. await this.$http
  938. .delete(`${collectDel}?id=${this.product.myCollect.id}`)
  939. .then(() => {
  940. this.$mHelper.toast('取消收藏成功');
  941. this.$emit('product');
  942. });
  943. },
  944. async buy(skuId) {
  945. const params = {};
  946. params.data = JSON.stringify({ sku_id: skuId, num: this.currentCartCount });
  947. if (
  948. this.product.point_exchange_type === '2' ||
  949. this.product.point_exchange_type === '4' ||
  950. (this.product.point_exchange_type === '3' &&
  951. this.isPointExchange)
  952. ) {
  953. params.type = 'point_exchange';
  954. } else {
  955. params.type = this.type;
  956. }
  957. this.$mRouter.push({
  958. route: `/pages/order/create/order?data=${JSON.stringify(params)}&promo_code=${this.promoCode}`
  959. });
  960. },
  961. addCart(type, isPointExchange) {
  962. if (!this.product.id) return;
  963. if (!this.hasLogin) {
  964. this.$mHelper.backToLogin();
  965. return;
  966. }
  967. this.specClass = 'show';
  968. this.cartType = type;
  969. this.isPointExchange = isPointExchange;
  970. },
  971. stopPrevent() {}
  972. }
  973. };
  974. </script>
  975. <style lang="scss">
  976. .rf-product-detail {
  977. .back-btn {
  978. position: fixed;
  979. left: 40upx;
  980. z-index: 9999;
  981. padding-top: var(--status-bar-height);
  982. top: 40upx;
  983. font-size: 40upx;
  984. color: $font-color-dark;
  985. }
  986. .carousel {
  987. height: 722upx;
  988. position: relative;
  989. swiper {
  990. height: 100%;
  991. }
  992. .image-wrapper {
  993. width: 100%;
  994. height: 100%;
  995. }
  996. .swiper-item {
  997. display: flex;
  998. justify-content: center;
  999. align-content: center;
  1000. height: 750upx;
  1001. overflow: hidden;
  1002. border-bottom: 1upx solid rgba(0, 0, 0, 0.01);
  1003. image {
  1004. width: 100%;
  1005. height: 100%;
  1006. }
  1007. .content {
  1008. position: absolute;
  1009. right: $spacing-base;
  1010. bottom: $spacing-base;
  1011. }
  1012. }
  1013. }
  1014. .detail {
  1015. padding-bottom: 60upx;
  1016. }
  1017. .service {
  1018. padding: $spacing-base $spacing-lg 0;
  1019. .row {
  1020. font-size: $font-lg;
  1021. margin-bottom: $spacing-sm;
  1022. }
  1023. }
  1024. .selected-text {
  1025. margin-right: 4upx;
  1026. }
  1027. .sub-list {
  1028. margin: 40upx 0 80upx;
  1029. .row {
  1030. width: 100%;
  1031. margin-bottom: $spacing-lg;
  1032. }
  1033. }
  1034. .share-bg {
  1035. image {
  1036. position: fixed;
  1037. z-index: 100;
  1038. width: 70vw;
  1039. height: 45vw;
  1040. right: $spacing-base;
  1041. top: $spacing-base;
  1042. }
  1043. }
  1044. .layer {
  1045. position: fixed;
  1046. z-index: 99;
  1047. bottom: 0;
  1048. width: 100%;
  1049. border-radius: 10upx 10upx 0 0;
  1050. background-color: #fff;
  1051. .rf-list {
  1052. max-height: 60vh;
  1053. padding-bottom: 0;
  1054. margin-bottom: $spacing-sm;
  1055. }
  1056. }
  1057. // 拼团公告
  1058. .rf-swiper-slide {
  1059. margin-top: 20upx;
  1060. .label {
  1061. margin-left: 10upx;
  1062. }
  1063. }
  1064. // 玩法介绍
  1065. .play-way {
  1066. background-color: $color-white;
  1067. padding: 0 20upx;
  1068. margin: 20upx 0;
  1069. font-size: $font-base;
  1070. .title {
  1071. border-bottom: 1px solid #eee;
  1072. padding: $spacing-base 0;
  1073. display: flex;
  1074. justify-content: space-between;
  1075. .iconfont {
  1076. margin-left: 0.13rem;
  1077. font-size: 0.28rem;
  1078. color: #717171;
  1079. }
  1080. }
  1081. .way {
  1082. font-size: $font-base - 2upx;
  1083. padding: 20upx 0;
  1084. display: flex;
  1085. .item {
  1086. flex: 1;
  1087. text-align: center;
  1088. .tip {
  1089. font-size: 0.22rem;
  1090. color: #a5a5a5;
  1091. }
  1092. }
  1093. .arrow {
  1094. width: 40upx;
  1095. .iconfont {
  1096. color: $font-color-light;
  1097. font-weight: 100;
  1098. }
  1099. }
  1100. }
  1101. }
  1102. .assemble {
  1103. background-color: #fff;
  1104. .assemble-item {
  1105. height: 120upx;
  1106. border-bottom: 1px solid #f0f0f0;
  1107. .pictxt {
  1108. display: flex;
  1109. justify-content: space-between;
  1110. .picture {
  1111. display: flex;
  1112. image {
  1113. width: 80upx;
  1114. height: 80upx;
  1115. margin: 20upx 0;
  1116. border-radius: 50%;
  1117. }
  1118. .text {
  1119. line-height: 120upx;
  1120. margin-left: 20upx;
  1121. }
  1122. }
  1123. .right {
  1124. display: flex;
  1125. align-items: center;
  1126. .time-wrapper {
  1127. text-align: right;
  1128. margin-right: 20upx;
  1129. .lack {
  1130. font-size: $font-sm;
  1131. .font-color-red {
  1132. margin: 0 4upx;
  1133. }
  1134. }
  1135. .time {
  1136. font-size: $font-sm;
  1137. color: $font-color-light;
  1138. }
  1139. }
  1140. .spellBnt {
  1141. font-size: $font-sm;
  1142. width: 120upx;
  1143. height: 48upx;
  1144. display: flex;
  1145. justify-content: center;
  1146. align-items: center;
  1147. border-radius: 48upx;
  1148. }
  1149. }
  1150. }
  1151. }
  1152. }
  1153. .c-list {
  1154. font-size: $font-sm + 2upx;
  1155. color: $font-color-base;
  1156. background: #fff;
  1157. .c-row {
  1158. display: flex;
  1159. align-items: center;
  1160. padding: 20upx 30upx;
  1161. position: relative;
  1162. }
  1163. .tit {
  1164. width: 140upx;
  1165. }
  1166. .con {
  1167. flex: 1;
  1168. color: $font-color-dark;
  1169. .selected-text {
  1170. margin-right: 10upx;
  1171. }
  1172. }
  1173. .bz-list {
  1174. height: 40upx;
  1175. font-size: $font-sm + 2upx;
  1176. color: $font-color-dark;
  1177. text {
  1178. display: inline-block;
  1179. margin-right: 30upx;
  1180. }
  1181. }
  1182. .con-list {
  1183. flex: 1;
  1184. display: flex;
  1185. flex-direction: column;
  1186. color: $font-color-dark;
  1187. line-height: 40upx;
  1188. .buy-now {
  1189. color: $uni-color-primary;
  1190. }
  1191. }
  1192. .red {
  1193. color: $uni-color-primary;
  1194. }
  1195. }
  1196. .kefu-bg {
  1197. height: 100vh;
  1198. display: flex;
  1199. justify-content: center;
  1200. align-items: center;
  1201. z-index: 98;
  1202. image {
  1203. width: 60vw;
  1204. height: 60vw;
  1205. border-radius: 12upx;
  1206. z-index: 98;
  1207. }
  1208. }
  1209. }
  1210. </style>