Q3学习计划
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.

452 lines
10 KiB

  1. <template>
  2. <scroll-view scroll-y class="viewport">
  3. <!-- 基本信息 -->
  4. <view class="goods">
  5. <!-- 商品主图 -->
  6. <view class="preview">
  7. <swiper circular @change="onChange">
  8. <swiper-item v-for="item in goods?.mainPictures" :key="item">
  9. <image @tap="onTapImage(item)" mode="aspectFill" :src="item" />
  10. </swiper-item>
  11. </swiper>
  12. <view class="indicator">
  13. <text class="current">{{ currentIndex + 1 }}</text>
  14. <text class="split">/</text>
  15. <text class="total">{{ goods?.mainPictures.length }}</text>
  16. </view>
  17. </view>
  18. <!-- 商品简介 -->
  19. <view class="meta">
  20. <view class="price">
  21. <text class="symbol">¥</text>
  22. <text class="number">{{ goods?.price }}</text>
  23. </view>
  24. <view class="name ellipsis">{{ goods?.name }}</view>
  25. <view class="desc">{{ goods?.desc }}</view>
  26. </view>
  27. <!-- 操作面板 -->
  28. <view class="action">
  29. <view class="item arrow">
  30. <text class="label">选择</text>
  31. <text class="text ellipsis"> 请选择商品规格 </text>
  32. </view>
  33. <view @tap="openPopup('address')" class="item arrow">
  34. <text class="label">送至</text>
  35. <text class="text ellipsis"> 请选择收获地址 </text>
  36. </view>
  37. <view @tap="openPopup('service')" class="item arrow">
  38. <text class="label">服务</text>
  39. <text class="text ellipsis"> 无忧退 快速退款 免费包邮 </text>
  40. </view>
  41. </view>
  42. </view>
  43. <!-- 商品详情 -->
  44. <view class="detail panel">
  45. <view class="title">
  46. <text>详情</text>
  47. </view>
  48. <view class="content">
  49. <view class="properties">
  50. <!-- 属性详情 -->
  51. <view class="item" v-for="item in goods?.details.properties" :key="item.name">
  52. <text class="label">{{ item.name }}</text>
  53. <text class="value">{{ item.value }}</text>
  54. </view>
  55. </view>
  56. <!-- 图片详情 -->
  57. <image
  58. class="image"
  59. v-for="item in goods?.details.pictures"
  60. :key="item"
  61. mode="widthFix"
  62. :src="item"
  63. ></image>
  64. </view>
  65. </view>
  66. <!-- 同类推荐 -->
  67. <view class="similar panel">
  68. <view class="title">
  69. <text>同类推荐</text>
  70. </view>
  71. <view class="content">
  72. <navigator
  73. v-for="item in goods?.similarProducts"
  74. :key="item.id"
  75. class="goods"
  76. hover-class="none"
  77. :url="`/pages/goods/goods?id=${item.id}`"
  78. >
  79. <image class="image" mode="aspectFill" :src="item.picture"></image>
  80. <view class="name ellipsis">{{ item.name }}</view>
  81. <view class="price">
  82. <text class="symbol">¥</text>
  83. <text class="number">{{ item.price }}</text>
  84. </view>
  85. </navigator>
  86. </view>
  87. </view>
  88. </scroll-view>
  89. <!-- 用户操作 -->
  90. <view class="toolbar" :style="{ paddingBottom: safeAreaInsets?.bottom + 'px' }">
  91. <view class="icons">
  92. <button class="icons-button"><text class="icon-heart"></text>收藏</button>
  93. <button class="icons-button" open-type="contact">
  94. <text class="icon-handset"></text>客服
  95. </button>
  96. <navigator class="icons-button" url="/pages/cart/cart" open-type="switchTab">
  97. <text class="icon-cart"></text>购物车
  98. </navigator>
  99. </view>
  100. <view class="buttons">
  101. <view class="addcart"> 加入购物车 </view>
  102. <view class="buynow"> 立即购买 </view>
  103. </view>
  104. </view>
  105. <!-- uni-ui 弹出层 -->
  106. <uni-popup ref="popup" type="bottom" background-color="#fff">
  107. <AddressPanel v-if="popupName === 'address'" @close="popup?.close()" />
  108. <ServicePanel v-if="popupName === 'service'" @close="popup?.close()" />
  109. </uni-popup>
  110. </template>
  111. <script setup lang="ts">
  112. import { onLoad } from '@dcloudio/uni-app'
  113. import { computed, ref } from 'vue'
  114. import type { GoodsResult } from '@/types/goods'
  115. import { getGoodsByIdAPI } from '@/services/goods'
  116. import AddressPanel from './components/AddressPanel.vue'
  117. import ServicePanel from './components/ServicePanel.vue'
  118. // 获取屏幕边界到安全区域距离
  119. const { safeAreaInsets } = uni.getSystemInfoSync()
  120. // 接收页面参数
  121. const query = defineProps<{
  122. id: string
  123. }>()
  124. // 获取商品详情信息
  125. const goods = ref<GoodsResult>()
  126. const getGoodsByIdData = async () => {
  127. const res = await getGoodsByIdAPI(query.id)
  128. goods.value = res.result
  129. }
  130. // 轮播图变化时
  131. const currentIndex = ref(0)
  132. const onChange: UniHelper.SwiperOnChange = (ev) => {
  133. currentIndex.value = ev.detail.current
  134. }
  135. // 点击图片时
  136. const onTapImage = (url: string) => {
  137. // 大图预览
  138. uni.previewImage({
  139. current: url,
  140. urls: goods.value!.mainPictures,
  141. })
  142. }
  143. // uni-ui 弹出层组件 ref
  144. const popup = ref<{
  145. open: (type?: UniHelper.UniPopupType) => void
  146. close: () => void
  147. }>()
  148. // 弹出层条件渲染
  149. const popupName = ref<'address' | 'service'>()
  150. const openPopup = (name: typeof popupName.value) => {
  151. // 修改弹出层名称
  152. popupName.value = name
  153. // 打开弹出层
  154. popup.value?.open()
  155. }
  156. // 页面加载
  157. onLoad(() => {
  158. getGoodsByIdData()
  159. })
  160. </script>
  161. <style lang="scss">
  162. page {
  163. height: 100%;
  164. overflow: hidden;
  165. display: flex;
  166. flex-direction: column;
  167. }
  168. .viewport {
  169. background-color: #f4f4f4;
  170. }
  171. .panel {
  172. margin-top: 20rpx;
  173. background-color: #fff;
  174. .title {
  175. display: flex;
  176. justify-content: space-between;
  177. align-items: center;
  178. height: 90rpx;
  179. line-height: 1;
  180. padding: 30rpx 60rpx 30rpx 6rpx;
  181. position: relative;
  182. text {
  183. padding-left: 10rpx;
  184. font-size: 28rpx;
  185. color: #333;
  186. font-weight: 600;
  187. border-left: 4rpx solid #27ba9b;
  188. }
  189. navigator {
  190. font-size: 24rpx;
  191. color: #666;
  192. }
  193. }
  194. }
  195. .arrow {
  196. &::after {
  197. position: absolute;
  198. top: 50%;
  199. right: 30rpx;
  200. content: '\e6c2';
  201. color: #ccc;
  202. font-family: 'erabbit' !important;
  203. font-size: 32rpx;
  204. transform: translateY(-50%);
  205. }
  206. }
  207. /* 商品信息 */
  208. .goods {
  209. background-color: #fff;
  210. .preview {
  211. height: 750rpx;
  212. position: relative;
  213. .image {
  214. width: 750rpx;
  215. height: 750rpx;
  216. }
  217. .indicator {
  218. height: 40rpx;
  219. padding: 0 24rpx;
  220. line-height: 40rpx;
  221. border-radius: 30rpx;
  222. color: #fff;
  223. font-family: Arial, Helvetica, sans-serif;
  224. background-color: rgba(0, 0, 0, 0.3);
  225. position: absolute;
  226. bottom: 30rpx;
  227. right: 30rpx;
  228. .current {
  229. font-size: 26rpx;
  230. }
  231. .split {
  232. font-size: 24rpx;
  233. margin: 0 1rpx 0 2rpx;
  234. }
  235. .total {
  236. font-size: 24rpx;
  237. }
  238. }
  239. }
  240. .meta {
  241. position: relative;
  242. border-bottom: 1rpx solid #eaeaea;
  243. .price {
  244. height: 130rpx;
  245. padding: 25rpx 30rpx 0;
  246. color: #fff;
  247. font-size: 34rpx;
  248. box-sizing: border-box;
  249. background-color: #35c8a9;
  250. }
  251. .number {
  252. font-size: 56rpx;
  253. }
  254. .brand {
  255. width: 160rpx;
  256. height: 80rpx;
  257. overflow: hidden;
  258. position: absolute;
  259. top: 26rpx;
  260. right: 30rpx;
  261. }
  262. .name {
  263. max-height: 88rpx;
  264. line-height: 1.4;
  265. margin: 20rpx;
  266. font-size: 32rpx;
  267. color: #333;
  268. }
  269. .desc {
  270. line-height: 1;
  271. padding: 0 20rpx 30rpx;
  272. font-size: 24rpx;
  273. color: #cf4444;
  274. }
  275. }
  276. .action {
  277. padding-left: 20rpx;
  278. .item {
  279. height: 90rpx;
  280. padding-right: 60rpx;
  281. border-bottom: 1rpx solid #eaeaea;
  282. font-size: 26rpx;
  283. color: #333;
  284. position: relative;
  285. display: flex;
  286. align-items: center;
  287. &:last-child {
  288. border-bottom: 0 none;
  289. }
  290. }
  291. .label {
  292. width: 60rpx;
  293. color: #898b94;
  294. margin: 0 16rpx 0 10rpx;
  295. }
  296. .text {
  297. flex: 1;
  298. -webkit-line-clamp: 1;
  299. }
  300. }
  301. }
  302. /* 商品详情 */
  303. .detail {
  304. padding-left: 20rpx;
  305. .content {
  306. margin-left: -20rpx;
  307. .image {
  308. width: 100%;
  309. }
  310. }
  311. .properties {
  312. padding: 0 20rpx;
  313. margin-bottom: 30rpx;
  314. .item {
  315. display: flex;
  316. line-height: 2;
  317. padding: 10rpx;
  318. font-size: 26rpx;
  319. color: #333;
  320. border-bottom: 1rpx dashed #ccc;
  321. }
  322. .label {
  323. width: 200rpx;
  324. }
  325. .value {
  326. flex: 1;
  327. }
  328. }
  329. }
  330. /* 同类推荐 */
  331. .similar {
  332. .content {
  333. padding: 0 20rpx 200rpx;
  334. background-color: #f4f4f4;
  335. display: flex;
  336. flex-wrap: wrap;
  337. .goods {
  338. width: 340rpx;
  339. padding: 24rpx 20rpx 20rpx;
  340. margin: 20rpx 7rpx;
  341. border-radius: 10rpx;
  342. background-color: #fff;
  343. }
  344. .image {
  345. width: 300rpx;
  346. height: 260rpx;
  347. }
  348. .name {
  349. height: 80rpx;
  350. margin: 10rpx 0;
  351. font-size: 26rpx;
  352. color: #262626;
  353. }
  354. .price {
  355. line-height: 1;
  356. font-size: 20rpx;
  357. color: #cf4444;
  358. }
  359. .number {
  360. font-size: 26rpx;
  361. margin-left: 2rpx;
  362. }
  363. }
  364. navigator {
  365. &:nth-child(even) {
  366. margin-right: 0;
  367. }
  368. }
  369. }
  370. /* 底部工具栏 */
  371. .toolbar {
  372. position: fixed;
  373. left: 0;
  374. right: 0;
  375. bottom: 0;
  376. z-index: 1;
  377. background-color: #fff;
  378. height: 100rpx;
  379. padding: 0 20rpx var(--window-bottom);
  380. border-top: 1rpx solid #eaeaea;
  381. display: flex;
  382. justify-content: space-between;
  383. align-items: center;
  384. box-sizing: content-box;
  385. .buttons {
  386. display: flex;
  387. & > view {
  388. width: 220rpx;
  389. text-align: center;
  390. line-height: 72rpx;
  391. font-size: 26rpx;
  392. color: #fff;
  393. border-radius: 72rpx;
  394. }
  395. .addcart {
  396. background-color: #ffa868;
  397. }
  398. .buynow,
  399. .payment {
  400. background-color: #27ba9b;
  401. margin-left: 20rpx;
  402. }
  403. }
  404. .icons {
  405. padding-right: 10rpx;
  406. display: flex;
  407. align-items: center;
  408. flex: 1;
  409. .icons-button {
  410. flex: 1;
  411. text-align: center;
  412. line-height: 1.4;
  413. padding: 0;
  414. margin: 0;
  415. border-radius: 0;
  416. font-size: 20rpx;
  417. color: #333;
  418. background-color: #fff;
  419. &::after {
  420. border: none;
  421. }
  422. }
  423. text {
  424. display: block;
  425. font-size: 34rpx;
  426. }
  427. }
  428. }
  429. </style>