4 changed files with 560 additions and 0 deletions
@ -0,0 +1,429 @@ |
|||||
|
<template> |
||||
|
<scroll-view scroll-y class="viewport"> |
||||
|
<!-- 基本信息 --> |
||||
|
<view class="goods"> |
||||
|
<!-- 商品主图 --> |
||||
|
<view class="preview"> |
||||
|
<swiper circular @change="onChange"> |
||||
|
<swiper-item v-for="item in goods?.mainPictures" :key="item"> |
||||
|
<image @tap="onTapImage(item)" mode="aspectFill" :src="item" /> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<view class="indicator"> |
||||
|
<text class="current">{{ currentIndex + 1 }}</text> |
||||
|
<text class="split">/</text> |
||||
|
<text class="total">{{ goods?.mainPictures.length }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品简介 --> |
||||
|
<view class="meta"> |
||||
|
<view class="price"> |
||||
|
<text class="symbol">¥</text> |
||||
|
<text class="number">{{ goods?.price }}</text> |
||||
|
</view> |
||||
|
<view class="name ellipsis">{{ goods?.name }}</view> |
||||
|
<view class="desc">{{ goods?.desc }}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作面板 --> |
||||
|
<view class="action"> |
||||
|
<view class="item arrow"> |
||||
|
<text class="label">选择</text> |
||||
|
<text class="text ellipsis"> 请选择商品规格 </text> |
||||
|
</view> |
||||
|
<view class="item arrow"> |
||||
|
<text class="label">送至</text> |
||||
|
<text class="text ellipsis"> 请选择收获地址 </text> |
||||
|
</view> |
||||
|
<view class="item arrow"> |
||||
|
<text class="label">服务</text> |
||||
|
<text class="text ellipsis"> 无忧退 快速退款 免费包邮 </text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品详情 --> |
||||
|
<view class="detail panel"> |
||||
|
<view class="title"> |
||||
|
<text>详情</text> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<view class="properties"> |
||||
|
<!-- 属性详情 --> |
||||
|
<view class="item" v-for="item in goods?.details.properties" :key="item.name"> |
||||
|
<text class="label">{{ item.name }}</text> |
||||
|
<text class="value">{{ item.value }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- 图片详情 --> |
||||
|
<image |
||||
|
class="image" |
||||
|
v-for="item in goods?.details.pictures" |
||||
|
:key="item" |
||||
|
mode="widthFix" |
||||
|
:src="item" |
||||
|
></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 同类推荐 --> |
||||
|
<view class="similar panel"> |
||||
|
<view class="title"> |
||||
|
<text>同类推荐</text> |
||||
|
</view> |
||||
|
<view class="content"> |
||||
|
<navigator |
||||
|
v-for="item in goods?.similarProducts" |
||||
|
:key="item.id" |
||||
|
class="goods" |
||||
|
hover-class="none" |
||||
|
:url="`/pages/goods/goods?id=${item.id}`" |
||||
|
> |
||||
|
<image class="image" mode="aspectFill" :src="item.picture"></image> |
||||
|
<view class="name ellipsis">{{ item.name }}</view> |
||||
|
<view class="price"> |
||||
|
<text class="symbol">¥</text> |
||||
|
<text class="number">{{ item.price }}</text> |
||||
|
</view> |
||||
|
</navigator> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 用户操作 --> |
||||
|
<view class="toolbar" :style="{ paddingBottom: safeAreaInsets?.bottom + 'px' }"> |
||||
|
<view class="icons"> |
||||
|
<button class="icons-button"><text class="icon-heart"></text>收藏</button> |
||||
|
<button class="icons-button" open-type="contact"> |
||||
|
<text class="icon-handset"></text>客服 |
||||
|
</button> |
||||
|
<navigator class="icons-button" url="/pages/cart/cart" open-type="switchTab"> |
||||
|
<text class="icon-cart"></text>购物车 |
||||
|
</navigator> |
||||
|
</view> |
||||
|
<view class="buttons"> |
||||
|
<view class="addcart"> 加入购物车 </view> |
||||
|
<view class="buynow"> 立即购买 </view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { onLoad } from '@dcloudio/uni-app' |
||||
|
import { computed, ref } from 'vue' |
||||
|
import type { GoodsResult } from '@/types/goods' |
||||
|
import { getGoodsByIdAPI } from '@/services/goods' |
||||
|
|
||||
|
// 获取屏幕边界到安全区域距离 |
||||
|
const { safeAreaInsets } = uni.getSystemInfoSync() |
||||
|
|
||||
|
// 接收页面参数 |
||||
|
const query = defineProps<{ |
||||
|
id: string |
||||
|
}>() |
||||
|
|
||||
|
// 获取商品详情信息 |
||||
|
const goods = ref<GoodsResult>() |
||||
|
const getGoodsByIdData = async () => { |
||||
|
const res = await getGoodsByIdAPI(query.id) |
||||
|
goods.value = res.result |
||||
|
} |
||||
|
|
||||
|
// 轮播图变化时 |
||||
|
const currentIndex = ref(0) |
||||
|
const onChange: UniHelper.SwiperOnChange = (ev) => { |
||||
|
currentIndex.value = ev.detail.current |
||||
|
} |
||||
|
|
||||
|
// 点击图片时 |
||||
|
const onTapImage = (url: string) => { |
||||
|
// 大图预览 |
||||
|
uni.previewImage({ |
||||
|
current: url, |
||||
|
urls: goods.value!.mainPictures, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 页面加载 |
||||
|
onLoad(() => { |
||||
|
getGoodsByIdData() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
page { |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.viewport { |
||||
|
background-color: #f4f4f4; |
||||
|
} |
||||
|
|
||||
|
.panel { |
||||
|
margin-top: 20rpx; |
||||
|
background-color: #fff; |
||||
|
.title { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
height: 90rpx; |
||||
|
line-height: 1; |
||||
|
padding: 30rpx 60rpx 30rpx 6rpx; |
||||
|
position: relative; |
||||
|
text { |
||||
|
padding-left: 10rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
font-weight: 600; |
||||
|
border-left: 4rpx solid #27ba9b; |
||||
|
} |
||||
|
navigator { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.arrow { |
||||
|
&::after { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
right: 30rpx; |
||||
|
content: '\e6c2'; |
||||
|
color: #ccc; |
||||
|
font-family: 'erabbit' !important; |
||||
|
font-size: 32rpx; |
||||
|
transform: translateY(-50%); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 商品信息 */ |
||||
|
.goods { |
||||
|
background-color: #fff; |
||||
|
.preview { |
||||
|
height: 750rpx; |
||||
|
position: relative; |
||||
|
.image { |
||||
|
width: 750rpx; |
||||
|
height: 750rpx; |
||||
|
} |
||||
|
.indicator { |
||||
|
height: 40rpx; |
||||
|
padding: 0 24rpx; |
||||
|
line-height: 40rpx; |
||||
|
border-radius: 30rpx; |
||||
|
color: #fff; |
||||
|
font-family: Arial, Helvetica, sans-serif; |
||||
|
background-color: rgba(0, 0, 0, 0.3); |
||||
|
position: absolute; |
||||
|
bottom: 30rpx; |
||||
|
right: 30rpx; |
||||
|
.current { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
.split { |
||||
|
font-size: 24rpx; |
||||
|
margin: 0 1rpx 0 2rpx; |
||||
|
} |
||||
|
.total { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.meta { |
||||
|
position: relative; |
||||
|
border-bottom: 1rpx solid #eaeaea; |
||||
|
.price { |
||||
|
height: 130rpx; |
||||
|
padding: 25rpx 30rpx 0; |
||||
|
color: #fff; |
||||
|
font-size: 34rpx; |
||||
|
box-sizing: border-box; |
||||
|
background-color: #35c8a9; |
||||
|
} |
||||
|
.number { |
||||
|
font-size: 56rpx; |
||||
|
} |
||||
|
.brand { |
||||
|
width: 160rpx; |
||||
|
height: 80rpx; |
||||
|
overflow: hidden; |
||||
|
position: absolute; |
||||
|
top: 26rpx; |
||||
|
right: 30rpx; |
||||
|
} |
||||
|
.name { |
||||
|
max-height: 88rpx; |
||||
|
line-height: 1.4; |
||||
|
margin: 20rpx; |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
.desc { |
||||
|
line-height: 1; |
||||
|
padding: 0 20rpx 30rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #cf4444; |
||||
|
} |
||||
|
} |
||||
|
.action { |
||||
|
padding-left: 20rpx; |
||||
|
.item { |
||||
|
height: 90rpx; |
||||
|
padding-right: 60rpx; |
||||
|
border-bottom: 1rpx solid #eaeaea; |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
&:last-child { |
||||
|
border-bottom: 0 none; |
||||
|
} |
||||
|
} |
||||
|
.label { |
||||
|
width: 60rpx; |
||||
|
color: #898b94; |
||||
|
margin: 0 16rpx 0 10rpx; |
||||
|
} |
||||
|
.text { |
||||
|
flex: 1; |
||||
|
-webkit-line-clamp: 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 商品详情 */ |
||||
|
.detail { |
||||
|
padding-left: 20rpx; |
||||
|
.content { |
||||
|
margin-left: -20rpx; |
||||
|
.image { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
.properties { |
||||
|
padding: 0 20rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
.item { |
||||
|
display: flex; |
||||
|
line-height: 2; |
||||
|
padding: 10rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
border-bottom: 1rpx dashed #ccc; |
||||
|
} |
||||
|
.label { |
||||
|
width: 200rpx; |
||||
|
} |
||||
|
.value { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 同类推荐 */ |
||||
|
.similar { |
||||
|
.content { |
||||
|
padding: 0 20rpx 200rpx; |
||||
|
background-color: #f4f4f4; |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
.goods { |
||||
|
width: 340rpx; |
||||
|
padding: 24rpx 20rpx 20rpx; |
||||
|
margin: 20rpx 7rpx; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
.image { |
||||
|
width: 300rpx; |
||||
|
height: 260rpx; |
||||
|
} |
||||
|
.name { |
||||
|
height: 80rpx; |
||||
|
margin: 10rpx 0; |
||||
|
font-size: 26rpx; |
||||
|
color: #262626; |
||||
|
} |
||||
|
.price { |
||||
|
line-height: 1; |
||||
|
font-size: 20rpx; |
||||
|
color: #cf4444; |
||||
|
} |
||||
|
.number { |
||||
|
font-size: 26rpx; |
||||
|
margin-left: 2rpx; |
||||
|
} |
||||
|
} |
||||
|
navigator { |
||||
|
&:nth-child(even) { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 底部工具栏 */ |
||||
|
.toolbar { |
||||
|
position: fixed; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1; |
||||
|
background-color: #fff; |
||||
|
height: 100rpx; |
||||
|
padding: 0 20rpx var(--window-bottom); |
||||
|
border-top: 1rpx solid #eaeaea; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
box-sizing: content-box; |
||||
|
.buttons { |
||||
|
display: flex; |
||||
|
& > view { |
||||
|
width: 220rpx; |
||||
|
text-align: center; |
||||
|
line-height: 72rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #fff; |
||||
|
border-radius: 72rpx; |
||||
|
} |
||||
|
.addcart { |
||||
|
background-color: #ffa868; |
||||
|
} |
||||
|
.buynow, |
||||
|
.payment { |
||||
|
background-color: #27ba9b; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
} |
||||
|
.icons { |
||||
|
padding-right: 10rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex: 1; |
||||
|
.icons-button { |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
line-height: 1.4; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
border-radius: 0; |
||||
|
font-size: 20rpx; |
||||
|
color: #333; |
||||
|
background-color: #fff; |
||||
|
&::after { |
||||
|
border: none; |
||||
|
} |
||||
|
} |
||||
|
text { |
||||
|
display: block; |
||||
|
font-size: 34rpx; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,13 @@ |
|||||
|
import { http } from '@/utils/http' |
||||
|
import type { GoodsResult } from '@/types/goods' |
||||
|
/** |
||||
|
* 商品详情 |
||||
|
* @param id 商品id |
||||
|
*/ |
||||
|
export const getGoodsByIdAPI = (id: string) => { |
||||
|
return http<GoodsResult>({ |
||||
|
method: 'GET', |
||||
|
url: '/goods', |
||||
|
data: { id }, |
||||
|
}) |
||||
|
} |
@ -0,0 +1,111 @@ |
|||||
|
import type { GoodsItem } from './global' |
||||
|
|
||||
|
/** 商品信息 */ |
||||
|
export type GoodsResult = { |
||||
|
/** id */ |
||||
|
id: string |
||||
|
/** 商品名称 */ |
||||
|
name: string |
||||
|
/** 商品描述 */ |
||||
|
desc: string |
||||
|
/** 当前价格 */ |
||||
|
price: number |
||||
|
/** 原价 */ |
||||
|
oldPrice: number |
||||
|
/** 商品详情: 包含详情属性 + 详情图片 */ |
||||
|
details: Details |
||||
|
/** 主图图片集合[ 主图图片链接 ] */ |
||||
|
mainPictures: string[] |
||||
|
/** 同类商品[ 商品信息 ] */ |
||||
|
similarProducts: GoodsItem[] |
||||
|
/** sku集合[ sku信息 ] */ |
||||
|
skus: SkuItem[] |
||||
|
/** 可选规格集合备注[ 可选规格信息 ] */ |
||||
|
specs: SpecItem[] |
||||
|
/** 用户地址列表[ 地址信息 ] */ |
||||
|
userAddresses: AddressItem[] |
||||
|
} |
||||
|
|
||||
|
/** 商品详情: 包含详情属性 + 详情图片 */ |
||||
|
export type Details = { |
||||
|
/** 商品属性集合[ 属性信息 ] */ |
||||
|
properties: DetailsPropertyItem[] |
||||
|
/** 商品详情图片集合[ 图片链接 ] */ |
||||
|
pictures: string[] |
||||
|
} |
||||
|
|
||||
|
/** 属性信息 */ |
||||
|
export type DetailsPropertyItem = { |
||||
|
/** 属性名称 */ |
||||
|
name: string |
||||
|
/** 属性值 */ |
||||
|
value: string |
||||
|
} |
||||
|
|
||||
|
/** sku信息 */ |
||||
|
export type SkuItem = { |
||||
|
/** id */ |
||||
|
id: string |
||||
|
/** 库存 */ |
||||
|
inventory: number |
||||
|
/** 原价 */ |
||||
|
oldPrice: number |
||||
|
/** sku图片 */ |
||||
|
picture: string |
||||
|
/** 当前价格 */ |
||||
|
price: number |
||||
|
/** sku编码 */ |
||||
|
skuCode: string |
||||
|
/** 规格集合[ 规格信息 ] */ |
||||
|
specs: SkuSpecItem[] |
||||
|
} |
||||
|
|
||||
|
/** 规格信息 */ |
||||
|
export type SkuSpecItem = { |
||||
|
/** 规格名称 */ |
||||
|
name: string |
||||
|
/** 可选值名称 */ |
||||
|
valueName: string |
||||
|
} |
||||
|
|
||||
|
/** 可选规格信息 */ |
||||
|
export type SpecItem = { |
||||
|
/** 规格名称 */ |
||||
|
name: string |
||||
|
/** 可选值集合[ 可选值信息 ] */ |
||||
|
values: SpecValueItem[] |
||||
|
} |
||||
|
|
||||
|
/** 可选值信息 */ |
||||
|
export type SpecValueItem = { |
||||
|
/** 是否可售 */ |
||||
|
available: boolean |
||||
|
/** 可选值备注 */ |
||||
|
desc: string |
||||
|
/** 可选值名称 */ |
||||
|
name: string |
||||
|
/** 可选值图片链接 */ |
||||
|
picture: string |
||||
|
} |
||||
|
|
||||
|
/** 地址信息 */ |
||||
|
export type AddressItem = { |
||||
|
/** 收货人姓名 */ |
||||
|
receiver: string |
||||
|
/** 联系方式 */ |
||||
|
contact: string |
||||
|
/** 省份编码 */ |
||||
|
provinceCode: string |
||||
|
/** 城市编码 */ |
||||
|
cityCode: string |
||||
|
/** 区/县编码 */ |
||||
|
countyCode: string |
||||
|
/** 详细地址 */ |
||||
|
address: string |
||||
|
/** 默认地址,1为是,0为否 */ |
||||
|
isDefault: number |
||||
|
/** 收货地址 id */ |
||||
|
id: string |
||||
|
/** 省市区 */ |
||||
|
fullLocation: string |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue