普兆健康管家前端代码仓库
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.

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