mall-detail.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <template>
  2. <view class="detail-container">
  3. <!-- 商品轮播图 -->
  4. <view class="swiper-container">
  5. <swiper class="goods-swiper" :indicator-dots="true" :autoplay="true" :circular="true">
  6. <swiper-item v-for="(image, index) in goodsImages" :key="index">
  7. <image class="swiper-image" :src=image.url mode="aspectFill"></image>
  8. </swiper-item>
  9. </swiper>
  10. </view>
  11. <!-- 商品基本信息 -->
  12. <view class="goods-basic-info">
  13. <view class="goods-title">{{ goodsDetail.title }}</view>
  14. <view class="goods-subtitle">{{ goodsDetail.description }}</view>
  15. <view class="price-container">
  16. <text class="current-price">¥{{ goodsDetail.price }}</text>
  17. <text class="price-unit">{{ goodsDetail.unit }}</text>
  18. <text class="original-price" v-if="goodsDetail.originalPrice">¥{{ goodsDetail.originalPrice }}</text>
  19. </view>
  20. <view class="specs-info">
  21. <text class="specs-label">规格:</text>
  22. <text class="specs-value">{{ goodsDetail.specifications }}</text>
  23. </view>
  24. </view>
  25. <!-- 商品详情介绍 -->
  26. <view class="goods-detail-info">
  27. <view class="detail-title">商品详情</view>
  28. <!-- 产品特点 -->
  29. <view class="detail-section">
  30. <view class="section-title">产品特点</view>
  31. <view class="feature-list">
  32. <view
  33. class="feature-item"
  34. v-for="(feature, index) in goodsDetail.features"
  35. :key="index"
  36. >
  37. <view class="feature-icon">✓</view>
  38. <text class="feature-text">{{ feature }}</text>
  39. </view>
  40. </view>
  41. </view>
  42. <!-- 使用说明 -->
  43. <view class="detail-section">
  44. <view class="section-title">使用说明</view>
  45. <view class="usage-content">
  46. <text>{{ goodsDetail.usageGuide }}</text>
  47. </view>
  48. </view>
  49. <!-- 产品图片展示 -->
  50. <view class="detail-section">
  51. <view class="section-title">产品展示</view>
  52. <view class="detail-images">
  53. <image
  54. class="detail-image"
  55. v-for="(item, index) in goodsDetail.detailImages"
  56. :key="index"
  57. :src="item.url"
  58. mode="widthFix"
  59. @click="previewImage(item, index)"
  60. ></image>
  61. </view>
  62. </view>
  63. </view>
  64. <!-- 底部操作栏 -->
  65. <view class="bottom-actions">
  66. <view class="action-btn consult-btn" @click="handleConsult">
  67. 立即咨询
  68. </view>
  69. </view>
  70. </view>
  71. </template>
  72. <script setup>
  73. import { ref, onMounted } from 'vue'
  74. import { getMallById } from '@/api/services/mall.js'
  75. import { onLoad } from '@dcloudio/uni-app'
  76. const goodsId = ref('')
  77. const goodsDetail = ref({})
  78. const goodsImages = ref([])
  79. // 页面加载
  80. onLoad((options) => {
  81. // const pages = getCurrentPages()
  82. // const currentPage = pages[pages.length - 1]
  83. // const options = currentPage.options
  84. if (options.id) {
  85. goodsId.value = options.id
  86. loadGoodsDetail(goodsId.value)
  87. }
  88. if (options.title) {
  89. uni.setNavigationBarTitle({
  90. title: decodeURIComponent(options.title)
  91. })
  92. }
  93. })
  94. // 加载商品详情
  95. const loadGoodsDetail = (id) => {
  96. uni.showLoading({
  97. title: '加载中'
  98. })
  99. getMallById(id).then(res => {
  100. if (res.data.code === 200) {
  101. const { data } = res.data
  102. goodsDetail.value = data
  103. // 处理图片数据
  104. if (goodsDetail.value.detailImages && goodsDetail.value.swiperImages) {
  105. try {
  106. // 尝试解析图片数据字符串
  107. const imageUrls = goodsDetail.value.detailImages.split(',')
  108. const swiperImages = goodsDetail.value.swiperImages.split(',')
  109. goodsDetail.value.detailImages = imageUrls.filter(url => url && url.trim()).map(url => ({
  110. url: url.trim(),
  111. status: 'success'
  112. }))
  113. goodsImages.value = swiperImages.filter(url => url && url.trim()).map(url => ({
  114. url: url.trim(),
  115. status: 'success'
  116. }))
  117. } catch (e) {
  118. console.error('解析图片数据失败:', e)
  119. goodsDetail.value.detailImages = []
  120. goodsImages.value = []
  121. }
  122. } else {
  123. goodsDetail.value.detailImages = []
  124. goodsImages.value = []
  125. }
  126. uni.hideLoading()
  127. } else {
  128. uni.showToast({
  129. title: res.data.msg || '获取商品信息失败',
  130. icon: 'none'
  131. })
  132. }
  133. })
  134. }
  135. // 预览图片
  136. const previewImage = (image, index) => {
  137. const urls = goodsDetail.value.detailImages.map(item => item.url)
  138. uni.previewImage({
  139. urls: urls,
  140. current: index
  141. })
  142. }
  143. // 立即咨询
  144. const handleConsult = () => {
  145. uni.showModal({
  146. title: '咨询服务',
  147. content: '您可以通过以下方式联系我们:\n1. 拨打客服热线:13379508760\n2. 在线客服(工作时间:9:00-18:00)',
  148. confirmText: '拨打电话',
  149. cancelText: '取消',
  150. success: (res) => {
  151. if (res.confirm) {
  152. uni.makePhoneCall({
  153. phoneNumber: '13379508760'
  154. })
  155. }
  156. }
  157. })
  158. }
  159. </script>
  160. <style lang="scss">
  161. .detail-container {
  162. min-height: 100vh;
  163. background-color: #f5f5f5;
  164. padding-bottom: 120rpx; // 为底部操作栏留出空间
  165. }
  166. .swiper-container {
  167. width: 100%;
  168. height: 600rpx;
  169. background-color: #fff;
  170. }
  171. .goods-swiper {
  172. width: 100%;
  173. height: 100%;
  174. }
  175. .swiper-image {
  176. width: 100%;
  177. height: 100%;
  178. }
  179. .goods-basic-info {
  180. background-color: #fff;
  181. padding: 30rpx;
  182. margin-bottom: 20rpx;
  183. }
  184. .goods-title {
  185. font-size: 36rpx;
  186. font-weight: bold;
  187. color: #333;
  188. line-height: 1.4;
  189. margin-bottom: 16rpx;
  190. }
  191. .goods-subtitle {
  192. font-size: 28rpx;
  193. color: #666;
  194. line-height: 1.5;
  195. margin-bottom: 24rpx;
  196. }
  197. .price-container {
  198. display: flex;
  199. align-items: baseline;
  200. margin-bottom: 24rpx;
  201. }
  202. .current-price {
  203. font-size: 40rpx;
  204. font-weight: bold;
  205. color: #4CAF50;
  206. }
  207. .price-unit {
  208. font-size: 28rpx;
  209. color: #999;
  210. margin-left: 8rpx;
  211. margin-right: 16rpx;
  212. }
  213. .original-price {
  214. font-size: 28rpx;
  215. color: #999;
  216. text-decoration: line-through;
  217. }
  218. .specs-info {
  219. display: flex;
  220. align-items: center;
  221. }
  222. .specs-label {
  223. font-size: 28rpx;
  224. color: #666;
  225. }
  226. .specs-value {
  227. font-size: 28rpx;
  228. color: #333;
  229. }
  230. .goods-detail-info {
  231. background-color: #fff;
  232. padding: 30rpx;
  233. }
  234. .detail-title {
  235. font-size: 32rpx;
  236. font-weight: bold;
  237. color: #333;
  238. margin-bottom: 30rpx;
  239. text-align: center;
  240. }
  241. .detail-section {
  242. margin-bottom: 40rpx;
  243. &:last-child {
  244. margin-bottom: 0;
  245. }
  246. }
  247. .section-title {
  248. font-size: 30rpx;
  249. font-weight: bold;
  250. color: #333;
  251. margin-bottom: 20rpx;
  252. padding-left: 20rpx;
  253. position: relative;
  254. &::before {
  255. content: '';
  256. position: absolute;
  257. left: 0;
  258. top: 50%;
  259. transform: translateY(-50%);
  260. width: 8rpx;
  261. height: 32rpx;
  262. background-color: #4CAF50;
  263. border-radius: 4rpx;
  264. }
  265. }
  266. .feature-list {
  267. .feature-item {
  268. display: flex;
  269. align-items: center;
  270. margin-bottom: 16rpx;
  271. .feature-icon {
  272. width: 32rpx;
  273. height: 32rpx;
  274. background-color: #4CAF50;
  275. color: #fff;
  276. border-radius: 50%;
  277. display: flex;
  278. align-items: center;
  279. justify-content: center;
  280. font-size: 20rpx;
  281. font-weight: bold;
  282. margin-right: 16rpx;
  283. flex-shrink: 0;
  284. }
  285. .feature-text {
  286. font-size: 28rpx;
  287. color: #666;
  288. line-height: 1.5;
  289. }
  290. }
  291. }
  292. .usage-content {
  293. font-size: 28rpx;
  294. color: #666;
  295. line-height: 1.6;
  296. padding: 20rpx;
  297. background-color: #f8f8f8;
  298. border-radius: 12rpx;
  299. }
  300. .detail-images {
  301. display: flex;
  302. flex-direction: column;
  303. gap: 20rpx;
  304. }
  305. .detail-image {
  306. width: 100%;
  307. border-radius: 12rpx;
  308. }
  309. .bottom-actions {
  310. position: fixed;
  311. bottom: 0;
  312. left: 0;
  313. right: 0;
  314. background-color: #fff;
  315. padding: 20rpx 30rpx;
  316. border-top: 1rpx solid #f0f0f0;
  317. z-index: 1000;
  318. }
  319. .action-btn {
  320. text-align: center;
  321. padding: 24rpx 0;
  322. border-radius: 12rpx;
  323. font-size: 32rpx;
  324. font-weight: bold;
  325. }
  326. .consult-btn {
  327. background-color: #4CAF50;
  328. color: #fff;
  329. }
  330. </style>