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

467 lines
12 KiB

4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 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.info }}</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).toFixed(2) }}</text>/{{ detail.unit }}</view>
  18. <view class="price-bef" v-if="detail.originalPrice">¥<text>{{ detail.originalPrice }}</text>/{{ detail.unit }}</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">{{ detail.category }}</view>
  27. </view>
  28. </view>
  29. <view class="flex row" @click="openPicker">
  30. <view class="flex row-content">
  31. <view class="label">规格</view>
  32. <!-- todo: check key -->
  33. <view class="value">{{ detail.specName || '请选择规格' }}</view>
  34. </view>
  35. <uv-picker ref="picker" :columns="[detail.options]" keyName="label" confirmColor="#7451DE" @confirm="onChange"></uv-picker>
  36. <uv-icon name="arrow-right" color="#C6C6C6" size="24rpx"></uv-icon>
  37. </view>
  38. <view class="flex row">
  39. <view class="flex row-content">
  40. <view class="label">服务</view>
  41. <!-- todo: check key -->
  42. <view class="value">{{ detail.deliverService }}</view>
  43. </view>
  44. </view>
  45. </view>
  46. </view>
  47. <view class="detail" v-if="detail.detail">
  48. <uv-parse :content="detail.detail"></uv-parse>
  49. </view>
  50. <view class="comment">
  51. <view class="header">
  52. <view class="highlight">用户评价</view>
  53. <view>User reviews</view>
  54. </view>
  55. <!-- todo: check api -->
  56. <view class="comment-item" v-for="item in commentList" :key="item.id">
  57. <commentCard :data="item"></commentCard>
  58. </view>
  59. </view>
  60. </view>
  61. <view class="flex bottom">
  62. <button class="flex flex-column btn btn-simple" @click="jumpToComment">
  63. <image class="icon" src="@/pages_order/static/product/comment.png" mode="widthFix"></image>
  64. <view>评价</view>
  65. </button>
  66. <button class="flex btn btn-palin" @click="onAddCart">加入购物车</button>
  67. <button class="flex btn btn-primary" @click="onBuy">立即购买</button>
  68. </view>
  69. <countSelectPopup ref="countSelectPopup" :data="detail" @confirm="onSelectCount"></countSelectPopup>
  70. <agreementPopup ref="agreementPopup" @confirm="jumpToCreateOrder"></agreementPopup>
  71. </view>
  72. </template>
  73. <script>
  74. import commentCard from '@/pages_order/comment/commentCard.vue'
  75. import agreementPopup from './agreementPopup.vue'
  76. import countSelectPopup from './countSelectPopup.vue'
  77. export default {
  78. components: {
  79. commentCard,
  80. agreementPopup,
  81. countSelectPopup,
  82. },
  83. data() {
  84. return {
  85. id: null,
  86. detail: {},
  87. commentList: [],
  88. }
  89. },
  90. computed: {
  91. bannerList() {
  92. // todo: check key
  93. const { image } = this.detail
  94. if (!image) {
  95. return []
  96. }
  97. return Array.isArray(image) ? image : image.split(',')
  98. },
  99. },
  100. onLoad(arg) {
  101. console.log('onLoad', arg)
  102. const { id } = arg
  103. this.id = id
  104. this.getData(id)
  105. this.commentList = [
  106. {
  107. id: '001',
  108. userName: '战斗世界',
  109. avatar: '/pages_order/static/report/avatar.png',
  110. specName: '三月装',
  111. createTime: '2023-04-18',
  112. content: '包装很精致,性价比非常高,终于收到啦,期待效果,每日一包,超级方便,真的能坚持服用',
  113. images: [
  114. '/pages_order/static/index/recommend-pic.png',
  115. '/pages_order/static/index/recommend-pic.png',
  116. '/pages_order/static/index/recommend-pic.png',
  117. ],
  118. productServeScore: 5,
  119. questionExperienceScore: 4,
  120. deliverySpeedScore: 4.5,
  121. },
  122. {
  123. id: '002',
  124. userName: '战斗世界',
  125. avatar: '/pages_order/static/report/avatar.png',
  126. specName: '三月装',
  127. createTime: '2023-04-18',
  128. content: '包装很精致,性价比非常高,终于收到啦,期待效果,每日一包,超级方便,真的能坚持服用',
  129. images: [
  130. '/pages_order/static/index/recommend-pic.png',
  131. '/pages_order/static/index/recommend-pic.png',
  132. '/pages_order/static/index/recommend-pic.png',
  133. ],
  134. productServeScore: 5,
  135. questionExperienceScore: 4,
  136. deliverySpeedScore: 4.5,
  137. },
  138. {
  139. id: '003',
  140. userName: '战斗世界',
  141. avatar: '/pages_order/static/report/avatar.png',
  142. specName: '三月装',
  143. createTime: '2023-04-18',
  144. content: '包装很精致,性价比非常高,终于收到啦,期待效果,每日一包,超级方便,真的能坚持服用',
  145. images: [
  146. '/pages_order/static/index/recommend-pic.png',
  147. '/pages_order/static/index/recommend-pic.png',
  148. '/pages_order/static/index/recommend-pic.png',
  149. ],
  150. productServeScore: 5,
  151. questionExperienceScore: 4,
  152. deliverySpeedScore: 4.5,
  153. },
  154. ]
  155. return
  156. this.detail = {
  157. id: '001',
  158. image: new Array(4).fill('/pages_order/static/product/product-detail.png'),
  159. name: 'NMN',
  160. fullName: 'NMN 是 NAD* 的前体,在细胞能量代谢、DNA 修复和抗衰老等方面发挥重要作用。提高 NAD*水平能改善代谢健康1-8。',
  161. tags: ['专业设备', '科学流程', '质量保证'],
  162. sales: 24770,
  163. price: 688.00,
  164. originalPrice: 1664,
  165. category: '逆龄抗衰',
  166. count: null,
  167. specName: null,
  168. options: [
  169. { id: '001', label: '每天1颗', count: 1, value: 688, originValue: 1664 },
  170. { id: '002', label: '每天2颗', count: 2, value: 688*2, originValue: 1664*2 },
  171. { id: '003', label: '每天3颗', count: 3, value: 688*3, originValue: 1664*3 },
  172. { id: '004', label: '每天4颗', count: 4, value: 688*4, originValue: 1664*4 },
  173. ],
  174. deliverService: '上门取件·送货上门',
  175. details: `<p>商品详情↓<br/><br/><br/><br/>商品详情↑</p>`
  176. }
  177. },
  178. methods: {
  179. async getData(id) {
  180. try {
  181. this.detail = await this.$fetch('getProductDetail', { id })
  182. } catch (err) {
  183. }
  184. },
  185. openPicker() {
  186. this.$refs.picker.open();
  187. },
  188. onChange(e) {
  189. const target = e.value[0]
  190. console.log('onChange', target)
  191. this.detail.price = target.value
  192. this.detail.count = target.count
  193. this.detail.specName = target.label
  194. },
  195. jumpToComment() {
  196. this.$utils.navigateTo('/pages_order/comment/commentRecords')
  197. },
  198. jumpToCreateOrder() {
  199. // todo
  200. },
  201. onSelectCount(id) {
  202. const { specs } = this.detail
  203. let target = specs.find(item => item.id === id)
  204. // todo: check price
  205. // this.detail.price = target.value
  206. // todo: check count ?
  207. // this.detail.count = target.count
  208. this.detail.skuId = target.id
  209. this.detail.specName = target.specName
  210. this.onBuy()
  211. },
  212. onAddCart(id) {
  213. this.$store.commit('addCart', this.detail)
  214. },
  215. onBuy() {
  216. const { count, options } = this.detail
  217. if (!count && options?.length) {
  218. this.$refs.countSelectPopup.open()
  219. return
  220. }
  221. // todo: check timing
  222. // this.$refs.agreementPopup.open()
  223. this.$store.commit('createOrder', [this.detail])
  224. },
  225. },
  226. }
  227. </script>
  228. <style scoped lang="scss">
  229. .page__view {
  230. width: 100vw;
  231. min-height: 100vh;
  232. background-color: $uni-bg-color;
  233. position: relative;
  234. /deep/ .nav-bar__view {
  235. position: fixed;
  236. top: 0;
  237. left: 0;
  238. }
  239. }
  240. .main {
  241. width: 100vw;
  242. padding: calc(var(--status-bar-height) + 120rpx) 0 198rpx 0;
  243. box-sizing: border-box;
  244. }
  245. .summary {
  246. width: 100%;
  247. padding: 40rpx 32rpx;
  248. box-sizing: border-box;
  249. }
  250. .card {
  251. border-radius: 24rpx;
  252. & + & {
  253. margin-top: 40rpx;
  254. }
  255. &.info {
  256. width: 100%;
  257. padding: 32rpx;
  258. box-sizing: border-box;
  259. background: #FFFFFF;
  260. .name {
  261. font-family: PingFang SC;
  262. font-weight: 400;
  263. font-size: 32rpx;
  264. line-height: 1.4;
  265. color: #181818;
  266. }
  267. .tags {
  268. margin-top: 16rpx;
  269. justify-content: flex-start;
  270. flex-wrap: wrap;
  271. gap: 16rpx;
  272. .tag {
  273. padding: 2rpx 14rpx;
  274. font-family: PingFang SC;
  275. font-weight: 400;
  276. font-size: 24rpx;
  277. line-height: 1.4;
  278. color: #7451DE;
  279. background: #EFEAFF;
  280. border: 2rpx solid #7451DE;
  281. border-radius: 8rpx;
  282. }
  283. }
  284. .price {
  285. margin-top: 32rpx;
  286. justify-content: flex-start;
  287. column-gap: 20rpx;
  288. &-val {
  289. font-family: PingFang SC;
  290. font-weight: 500;
  291. font-size: 24rpx;
  292. line-height: 1.4;
  293. color: #7451DE;
  294. .highlight {
  295. margin: 0 8rpx;
  296. font-size: 48rpx;
  297. }
  298. }
  299. &-bef {
  300. font-family: PingFang SC;
  301. text-decoration: line-through;
  302. font-weight: 400;
  303. font-size: 28rpx;
  304. line-height: 1;
  305. color: #8B8B8B;
  306. }
  307. }
  308. }
  309. &.bar {
  310. width: 100%;
  311. padding: 20rpx 32rpx;
  312. box-sizing: border-box;
  313. background: #FAFAFF;
  314. box-shadow: -4rpx -4rpx 20rpx 0 #FFFFFFC4,
  315. 4rpx 4rpx 20rpx 0 #AAAACC1F,
  316. 2rpx 2rpx 4rpx 0 #AAAACC40,
  317. -2rpx -2rpx 4rpx 0 #FFFFFF;
  318. .row {
  319. padding: 8rpx 0;
  320. &-content {
  321. flex: 1;
  322. justify-content: flex-start;
  323. column-gap: 4rpx;
  324. font-family: PingFang SC;
  325. font-weight: 400;
  326. font-size: 28rpx;
  327. line-height: 1.4;
  328. .label {
  329. color: #8B8B8B;
  330. }
  331. .value {
  332. color: #393939;
  333. }
  334. }
  335. }
  336. .row + .row {
  337. margin-top: 24rpx;
  338. }
  339. }
  340. }
  341. .comment {
  342. padding: 40rpx 32rpx;
  343. .header {
  344. margin-bottom: 24rpx;
  345. font-family: PingFang SC;
  346. font-weight: 400;
  347. font-size: 26rpx;
  348. line-height: 1.4;
  349. color: #252545;
  350. .highlight {
  351. font-weight: 600;
  352. font-size: 48rpx;
  353. }
  354. }
  355. &-item {
  356. & + & {
  357. margin-top: 32rpx;
  358. }
  359. }
  360. }
  361. .bottom {
  362. position: fixed;
  363. left: 0;
  364. bottom: 0;
  365. align-items: flex-start;
  366. column-gap: 32rpx;
  367. width: 100vw;
  368. height: 198rpx;
  369. padding: 24rpx 40rpx 0 40rpx;
  370. background: #FFFFFF;
  371. box-sizing: border-box;
  372. .btn {
  373. font-family: PingFang SC;
  374. &-simple {
  375. font-weight: 400;
  376. font-size: 22rpx;
  377. line-height: 1.1;
  378. color: #999999;
  379. .icon {
  380. width: 52rpx;
  381. height: auto;
  382. }
  383. }
  384. &-palin {
  385. flex: 1;
  386. padding: 14rpx 0;
  387. font-weight: 500;
  388. font-size: 36rpx;
  389. line-height: 1.4;
  390. color: #252545;
  391. border: 2rpx solid #252545;
  392. border-radius: 41rpx;
  393. }
  394. &-primary {
  395. flex: 1;
  396. padding: 16rpx 0;
  397. font-weight: 500;
  398. font-size: 36rpx;
  399. line-height: 1.4;
  400. color: #FFFFFF;
  401. background-image: linear-gradient(to right, #4B348F, #845CFA);
  402. border-radius: 41rpx;
  403. }
  404. }
  405. }
  406. </style>