普兆健康管家前端代码仓库
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. <!-- 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. <!-- todo: check key -->
  50. <view class="value">{{ detail.service }}</view>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <view class="detail" v-if="detail.detail">
  56. <uv-parse :content="detail.detail"></uv-parse>
  57. </view>
  58. <view class="comment">
  59. <view class="header">
  60. <view class="highlight">用户评价</view>
  61. <view>User reviews</view>
  62. </view>
  63. <view class="comment-item" v-for="item in commentList" :key="item.id">
  64. <commentCard :data="item"></commentCard>
  65. </view>
  66. </view>
  67. </view>
  68. <view class="flex bottom">
  69. <button class="flex flex-column btn btn-simple" @click="jumpToComment">
  70. <image class="icon" src="@/pages_order/static/product/comment.png" mode="widthFix"></image>
  71. <view>评价</view>
  72. </button>
  73. <button class="flex btn btn-palin" @click="onAddCart">加入购物车</button>
  74. <button class="flex btn btn-primary" @click="onBuy">立即购买</button>
  75. </view>
  76. <specOptionsPopup ref="specOptionsPopup" :value="detail.specId" :data="detail" :confirmText="next ? '下一步' : '确认'" @confirm="onSpecChange"></specOptionsPopup>
  77. <agreementPopup ref="agreementPopup" @confirm="jumpToCreateOrder"></agreementPopup>
  78. </view>
  79. </template>
  80. <script>
  81. import mixinsList from '@/mixins/list.js'
  82. import commentCard from '@/pages_order/comment/commentCard.vue'
  83. import specOptionsPopup from '@/pages_order/product/specOptionsPopup.vue'
  84. import agreementPopup from './agreementPopup.vue'
  85. // 产品类型(0营养剂,1预约,2课程)
  86. const TYPE_AND_DESC_MAPPING = {
  87. 0: '营养剂',
  88. 1: '检测',
  89. 2: '课程',
  90. }
  91. export default {
  92. mixins: [mixinsList],
  93. components: {
  94. commentCard,
  95. specOptionsPopup,
  96. agreementPopup,
  97. },
  98. data() {
  99. return {
  100. id: null,
  101. detail: {},
  102. next: 'createOrder', // createOrder | addCart
  103. mixinsListApi: 'productEvaluate',
  104. mixinsListKey: 'commentList',
  105. commentList: [],
  106. }
  107. },
  108. computed: {
  109. bannerList() {
  110. const { image } = this.detail
  111. if (!image) {
  112. return []
  113. }
  114. return Array.isArray(image) ? image : image.split(',')
  115. },
  116. typeDesc() {
  117. const { type } = this.detail
  118. return TYPE_AND_DESC_MAPPING[type]
  119. },
  120. },
  121. onLoad(arg) {
  122. console.log('onLoad', arg)
  123. const { id } = arg
  124. this.id = id
  125. this.fetchDetail(id)
  126. this.queryParams.productId = id
  127. this.getData(id)
  128. },
  129. methods: {
  130. async fetchDetail(id) {
  131. try {
  132. const result = await this.$fetch('getProductDetail', { id })
  133. const { specs } = result
  134. let arr = specs
  135. arr?.sort?.((a, b) => a.sortOrder - b.sortOrder)
  136. const spec = arr?.[0]
  137. this.detail = {
  138. ...result,
  139. specId: spec?.id || null,
  140. specName: spec?.specName || null,
  141. }
  142. } catch (err) {
  143. }
  144. },
  145. openPicker() {
  146. this.next = null
  147. this.$refs.specOptionsPopup.open()
  148. },
  149. onAddCart(id) {
  150. const { specId, specs } = this.detail
  151. if (!specId && specs?.length) {
  152. this.next = 'addCart'
  153. this.$refs.specOptionsPopup.open()
  154. return
  155. }
  156. this.$store.dispatch('addCart', this.detail)
  157. },
  158. onBuy() {
  159. const { specId, specs } = this.detail
  160. console.log('specId', specId, 'specs', specs, !specId && specs?.length)
  161. if (!specId && specs?.length) {
  162. this.next = 'createOrder'
  163. this.$refs.specOptionsPopup.open()
  164. return
  165. }
  166. // todo: check timing
  167. // this.$refs.agreementPopup.open()
  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>