普兆健康管家前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

441 lines
11 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <view class="page__view">
  3. <navbar title="商品详情页" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" />
  4. <view class="main">
  5. <uv-swiper :list="bannerList" indicator indicatorMode="dot" height="680rpx" keyName="image"></uv-swiper>
  6. <view class="summary">
  7. <view class="card info">
  8. <!-- todo: check key -->
  9. <view class="name">{{ detail.name }}</view>
  10. <!-- todo: check key -->
  11. <view class="flex tags" v-if="detail.tags">
  12. <view class="tag" v-for="(tag, tIdx) in detail.tags" :key="tIdx">
  13. {{ tag }}
  14. </view>
  15. </view>
  16. <view class="flex price">
  17. <view class="flex price-val">¥<text class="highlight">{{ (detail.currentPrice || 0) }}</text><text v-if="detail.unit">/{{ detail.unit }}</text></view>
  18. <view class="price-bef" v-if="detail.originalPrice">¥<text>{{ detail.originalPrice }}</text><text v-if="detail.unit">/{{ detail.unit }}</text></view>
  19. </view>
  20. </view>
  21. <view class="card bar">
  22. <view class="flex row">
  23. <view class="flex row-content">
  24. <view class="label">品类</view>
  25. <!-- todo: check key -->
  26. <view class="value">{{ typeDesc }}</view>
  27. </view>
  28. </view>
  29. <template v-if="detail.specs && detail.specs.length">
  30. <view class="flex row" @click="openPicker">
  31. <view class="flex row-content">
  32. <view class="label">规格</view>
  33. <view class="value">{{ detail.specName || '请选择规格' }}</view>
  34. </view>
  35. <uv-icon name="arrow-right" color="#C6C6C6" size="24rpx"></uv-icon>
  36. </view>
  37. </template>
  38. <template v-else>
  39. <view class="flex row">
  40. <view class="flex row-content">
  41. <view class="label">规格</view>
  42. <view class="value">{{ detail.specName || '--' }}</view>
  43. </view>
  44. </view>
  45. </template>
  46. <view class="flex row">
  47. <view class="flex row-content">
  48. <view class="label">服务</view>
  49. <view class="value">{{ detail.service }}</view>
  50. </view>
  51. </view>
  52. </view>
  53. </view>
  54. <view class="detail" v-if="detail.detail">
  55. <uv-parse :content="detail.detail"></uv-parse>
  56. </view>
  57. <view class="comment">
  58. <view class="header">
  59. <view class="highlight">用户评价</view>
  60. <view>User reviews</view>
  61. </view>
  62. <view class="comment-item" v-for="item in commentList" :key="item.id">
  63. <commentCard :data="item"></commentCard>
  64. </view>
  65. </view>
  66. </view>
  67. <view class="flex bottom">
  68. <button class="flex flex-column btn btn-simple" @click="jumpToComment">
  69. <image class="icon" src="@/pages_order/static/product/comment.png" mode="widthFix"></image>
  70. <view>评价</view>
  71. </button>
  72. <button class="flex btn btn-palin" @click="onAddCart">加入购物车</button>
  73. <button class="flex btn btn-primary" @click="onBuy">立即购买</button>
  74. </view>
  75. <specOptionsPopup ref="specOptionsPopup" :value="detail.specId" :data="detail" :confirmText="next ? '下一步' : '确认'" @confirm="onSpecChange"></specOptionsPopup>
  76. <agreementPopup ref="agreementPopup" @confirm="jumpToCreateOrder"></agreementPopup>
  77. </view>
  78. </template>
  79. <script>
  80. import mixinsList from '@/mixins/list.js'
  81. import commentCard from '@/pages_order/comment/commentCard.vue'
  82. import specOptionsPopup from '@/pages_order/product/specOptionsPopup.vue'
  83. import agreementPopup from './agreementPopup.vue'
  84. // 产品类型(0营养剂,1预约,2课程)
  85. const TYPE_AND_DESC_MAPPING = {
  86. 0: '营养剂',
  87. 1: '检测',
  88. 2: '课程',
  89. }
  90. export default {
  91. mixins: [mixinsList],
  92. components: {
  93. commentCard,
  94. specOptionsPopup,
  95. agreementPopup,
  96. },
  97. data() {
  98. return {
  99. id: null,
  100. detail: {},
  101. next: 'createOrder', // createOrder | addCart
  102. mixinsListApi: 'productEvaluate',
  103. mixinsListKey: 'commentList',
  104. commentList: [],
  105. }
  106. },
  107. computed: {
  108. bannerList() {
  109. const { image } = this.detail
  110. if (!image) {
  111. return []
  112. }
  113. return Array.isArray(image) ? image : image.split(',')
  114. },
  115. typeDesc() {
  116. const { type } = this.detail
  117. return TYPE_AND_DESC_MAPPING[type]
  118. },
  119. },
  120. onLoad(arg) {
  121. console.log('onLoad', arg)
  122. const { id } = arg
  123. this.id = id
  124. this.fetchDetail(id)
  125. this.queryParams.productId = id
  126. this.getData(id)
  127. },
  128. methods: {
  129. async fetchDetail(id) {
  130. try {
  131. const result = await this.$fetch('getProductDetail', { id })
  132. const { specs } = result
  133. let arr = specs
  134. arr?.sort?.((a, b) => a.sortOrder - b.sortOrder)
  135. const spec = arr?.[0]
  136. this.detail = {
  137. ...result,
  138. specId: spec?.id || null,
  139. specName: spec?.specName || null,
  140. }
  141. } catch (err) {
  142. }
  143. },
  144. openPicker() {
  145. this.next = null
  146. this.$refs.specOptionsPopup.open()
  147. },
  148. onAddCart(id) {
  149. const { specId, specs } = this.detail
  150. if (!specId && specs?.length) {
  151. this.next = 'addCart'
  152. this.$refs.specOptionsPopup.open()
  153. return
  154. }
  155. this.$store.dispatch('addCart', this.detail)
  156. },
  157. onBuy() {
  158. const { specId, specs, isCrossBorder } = this.detail
  159. console.log('specId', specId, 'specs', specs, !specId && specs?.length)
  160. if (!specId && specs?.length) {
  161. this.next = 'createOrder'
  162. this.$refs.specOptionsPopup.open()
  163. return
  164. }
  165. if (isCrossBorder == 'Y') {
  166. this.$refs.agreementPopup.open()
  167. return
  168. }
  169. this.$store.commit('createOrder', [this.detail])
  170. },
  171. onSpecChange(obj) {
  172. console.log('onSpecChange', obj)
  173. this.detail.specId = obj.id
  174. this.detail.specName = obj.specName
  175. this.detail.currentPrice = obj.price
  176. console.log('detail', this.detail)
  177. switch(this.next) {
  178. case 'createOrder':
  179. this.onBuy()
  180. break;
  181. case 'addCart':
  182. this.onAddCart()
  183. break;
  184. default:
  185. break;
  186. }
  187. },
  188. jumpToComment() {
  189. this.$utils.navigateTo(`/pages_order/comment/commentRecordsOfProduct?productId=${this.id}`)
  190. },
  191. jumpToCreateOrder() {
  192. this.$store.commit('createOrder', [this.detail])
  193. },
  194. },
  195. }
  196. </script>
  197. <style scoped lang="scss">
  198. .page__view {
  199. width: 100vw;
  200. min-height: 100vh;
  201. background-color: $uni-bg-color;
  202. position: relative;
  203. /deep/ .nav-bar__view {
  204. position: fixed;
  205. top: 0;
  206. left: 0;
  207. }
  208. }
  209. .main {
  210. width: 100vw;
  211. padding: calc(var(--status-bar-height) + 120rpx) 0 198rpx 0;
  212. box-sizing: border-box;
  213. }
  214. .summary {
  215. width: 100%;
  216. padding: 40rpx 32rpx;
  217. box-sizing: border-box;
  218. }
  219. .card {
  220. border-radius: 24rpx;
  221. & + & {
  222. margin-top: 40rpx;
  223. }
  224. &.info {
  225. width: 100%;
  226. padding: 32rpx;
  227. box-sizing: border-box;
  228. background: #FFFFFF;
  229. .name {
  230. font-family: PingFang SC;
  231. font-weight: 400;
  232. font-size: 32rpx;
  233. line-height: 1.4;
  234. color: #181818;
  235. }
  236. .tags {
  237. margin-top: 16rpx;
  238. justify-content: flex-start;
  239. flex-wrap: wrap;
  240. gap: 16rpx;
  241. .tag {
  242. padding: 2rpx 14rpx;
  243. font-family: PingFang SC;
  244. font-weight: 400;
  245. font-size: 24rpx;
  246. line-height: 1.4;
  247. color: #7451DE;
  248. background: #EFEAFF;
  249. border: 2rpx solid #7451DE;
  250. border-radius: 8rpx;
  251. }
  252. }
  253. .price {
  254. margin-top: 32rpx;
  255. justify-content: flex-start;
  256. column-gap: 20rpx;
  257. &-val {
  258. font-family: PingFang SC;
  259. font-weight: 500;
  260. font-size: 24rpx;
  261. line-height: 1.4;
  262. color: #7451DE;
  263. .highlight {
  264. margin: 0 8rpx;
  265. font-size: 48rpx;
  266. }
  267. }
  268. &-bef {
  269. font-family: PingFang SC;
  270. text-decoration: line-through;
  271. font-weight: 400;
  272. font-size: 28rpx;
  273. line-height: 1;
  274. color: #8B8B8B;
  275. }
  276. }
  277. }
  278. &.bar {
  279. width: 100%;
  280. padding: 20rpx 32rpx;
  281. box-sizing: border-box;
  282. background: #FAFAFF;
  283. box-shadow: -4rpx -4rpx 20rpx 0 #FFFFFFC4,
  284. 4rpx 4rpx 20rpx 0 #AAAACC1F,
  285. 2rpx 2rpx 4rpx 0 #AAAACC40,
  286. -2rpx -2rpx 4rpx 0 #FFFFFF;
  287. .row {
  288. padding: 8rpx 0;
  289. &-content {
  290. flex: 1;
  291. justify-content: flex-start;
  292. column-gap: 4rpx;
  293. font-family: PingFang SC;
  294. font-weight: 400;
  295. font-size: 28rpx;
  296. line-height: 1.4;
  297. .label {
  298. color: #8B8B8B;
  299. }
  300. .value {
  301. color: #393939;
  302. }
  303. }
  304. }
  305. .row + .row {
  306. margin-top: 24rpx;
  307. }
  308. }
  309. }
  310. .comment {
  311. padding: 40rpx 32rpx;
  312. .header {
  313. margin-bottom: 24rpx;
  314. font-family: PingFang SC;
  315. font-weight: 400;
  316. font-size: 26rpx;
  317. line-height: 1.4;
  318. color: #252545;
  319. .highlight {
  320. font-weight: 600;
  321. font-size: 48rpx;
  322. }
  323. }
  324. &-item {
  325. & + & {
  326. margin-top: 32rpx;
  327. }
  328. }
  329. }
  330. .bottom {
  331. position: fixed;
  332. left: 0;
  333. bottom: 0;
  334. align-items: flex-start;
  335. column-gap: 32rpx;
  336. width: 100vw;
  337. // height: 198rpx;
  338. padding: 24rpx 40rpx 0 40rpx;
  339. padding-bottom: calc(env(safe-area-inset-bottom) + 24rpx);
  340. background: #FFFFFF;
  341. box-sizing: border-box;
  342. .btn {
  343. font-family: PingFang SC;
  344. &-simple {
  345. font-weight: 400;
  346. font-size: 22rpx;
  347. line-height: 1.1;
  348. color: #999999;
  349. .icon {
  350. width: 52rpx;
  351. height: auto;
  352. }
  353. }
  354. &-palin {
  355. flex: 1;
  356. padding: 14rpx 0;
  357. font-weight: 500;
  358. font-size: 36rpx;
  359. line-height: 1.4;
  360. color: #252545;
  361. border: 2rpx solid #252545;
  362. border-radius: 41rpx;
  363. }
  364. &-primary {
  365. flex: 1;
  366. padding: 16rpx 0;
  367. font-weight: 500;
  368. font-size: 36rpx;
  369. line-height: 1.4;
  370. color: #FFFFFF;
  371. background-image: linear-gradient(to right, #4B348F, #845CFA);
  372. border-radius: 41rpx;
  373. }
  374. }
  375. }
  376. </style>