05-商品详情-基本信息展示
目的:展示商品基本信息
大致步骤:
- 商品销售属性组件
- 商品名称信息组件
落地代码:
- ⑴基础布局:
红色区域1src/views/goods/components/goods-sales.vue
<template>
<ul class="goods-sales">
<li>
<p>销量人气</p>
<p>200+</p>
<p><i class="iconfont icon-task-filling"></i>销量人气</p>
</li>
<li>
<p>商品评价</p>
<p>400+</p>
<p><i class="iconfont icon-comment-filling"></i>查看评价</p>
</li>
<li>
<p>收藏人气</p>
<p>600+</p>
<p><i class="iconfont icon-favorite-filling"></i>收藏商品</p>
</li>
<li>
<p>品牌信息</p>
<p>苏宁电器</p>
<p><i class="iconfont icon-dynamic-filling"></i>品牌主页</p>
</li>
</ul>
</template>
<script>
export default {
name: 'GoodsSales'
}
</script>
<style scoped lang='less'>
.goods-sales {
display: flex;
width: 400px;
align-items: center;
text-align: center;
height: 140px;
li {
flex: 1;
position: relative;
~ li::after {
position: absolute;
top: 10px;
left: 0;
height: 60px;
border-left: 1px solid #e4e4e4;
content: "";
}
p {
&:first-child {
color: #999;
}
&:nth-child(2) {
color: @priceColor;
margin-top: 10px;
}
&:last-child {
color: #666;
margin-top: 10px;
i {
color: @xtxColor;
font-size: 14px;
margin-right: 2px;
}
&:hover {
color: @xtxColor;
cursor: pointer;
}
}
}
}
}
</style>
红色区域2 src/views/goods/components/goods-name.vue
<template>
<p class="g-name">2件装 粉釉花瓣心意点缀 点心盘*2 碟子盘子</p>
<p class="g-desc">花瓣造型干净简约 多功能使用堆叠方便</p>
<p class="g-price">
<span>108.00</span>
<span>199.00</span>
</p>
<div class="g-service">
<dl>
<dt>促销</dt>
<dd>12月好物放送,App领券购买直降120元</dd>
</dl>
<dl>
<dt>配送</dt>
<dd>至 </dd>
</dl>
<dl>
<dt>服务</dt>
<dd>
<span>无忧退货</span>
<span>快速退款</span>
<span>免费包邮</span>
<a href="javascript:;">了解详情</a>
</dd>
</dl>
</div>
</template>
<script>
export default {
name: 'GoodName'
}
</script>
<style lang="less" scoped>
.g-name {
font-size: 22px
}
.g-desc {
color: #999;
margin-top: 10px;
}
.g-price {
margin-top: 10px;
span {
&::before {
content: "¥";
font-size: 14px;
}
&:first-child {
color: @priceColor;
margin-right: 10px;
font-size: 22px;
}
&:last-child {
color: #999;
text-decoration: line-through;
font-size: 16px;
}
}
}
.g-service {
background: #f5f5f5;
width: 500px;
padding: 20px 10px 0 10px;
margin-top: 10px;
dl {
padding-bottom: 20px;
display: flex;
align-items: center;
dt {
width: 50px;
color: #999;
}
dd {
color: #666;
&:last-child {
span {
margin-right: 10px;
&::before {
content: "•";
color: @xtxColor;
margin-right: 2px;
}
}
a {
color: @xtxColor;
}
}
}
}
}
</style>
- ⑵使用组件
src/views/goods/index.vue
import GoodsSales from './components/goods-sales'
import GoodsName from './components/goods-name'
components: { GoodsRelevant, GoodsImage, GoodsSales, GoodsName },
<!-- 商品信息 -->
<div class="goods-info">
<div class="media">
<GoodsImage :images="goods.mainPictures" />
+ <GoodsSales />
</div>
<div class="spec">
+ <GoodsName :goods="goods"/>
</div>
</div>
- ⑶渲染数据
src/views/goods/components/goods-name.vue
<p class="g-name">{{goods.name}}</p>
<p class="g-desc">{{goods.desc}}</p>
<p class="g-price">
<span>{{goods.price}}</span>
<span>{{goods.oldPrice}}</span>
</p>
06-商品详情-城市组件-基础布局
目的:完成城市组件的基础布局和基本显示隐藏切换效果。
大致步骤:
- 准备基本组件结构
- 完成切换显示隐藏
- 完成点击外部隐藏
落地代码:
src/components/library/xtx-city.vue
- 结构
<template>
<div class="xtx-city" ref="target">
<div class="select" @click="toggleDialog" :class="{active}">
<span class="placeholder">请选择配送地址</span>
<span class="value"></span>
<i class="iconfont icon-angle-down"></i>
</div>
<div class="option" v-if="active">
<span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
</div>
</div>
</template>
- 逻辑
<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
export default {
name: 'XtxCity',
setup () {
// 控制展开收起,默认收起
const active = ref(false)
const openDialog = () => {
active.value = true
}
const closeDialog = () => {
active.value = false
}
// 切换展开收起
const toggleDialog = () => {
if (active.value) closeDialog()
else openDialog()
}
// 点击其他位置隐藏
const target = ref(null)
onClickOutside(target, () => {
closeDialog()
})
return { active, toggleDialog, target }
}
}
</script>
- 样式
<style scoped lang="less">
.xtx-city {
display: inline-block;
position: relative;
z-index: 400;
.select {
border: 1px solid #e4e4e4;
height: 30px;
padding: 0 5px;
line-height: 28px;
cursor: pointer;
&.active {
background: #fff;
}
.placeholder {
color: #999;
}
.value {
color: #666;
font-size: 12px;
}
i {
font-size: 12px;
margin-left: 5px;
}
}
.option {
width: 542px;
border: 1px solid #e4e4e4;
position: absolute;
left: 0;
top: 29px;
background: #fff;
min-height: 30px;
line-height: 30px;
display: flex;
flex-wrap: wrap;
padding: 10px;
> span {
width: 130px;
text-align: center;
cursor: pointer;
border-radius: 4px;
padding: 0 3px;
&:hover {
background: #f5f5f5;
}
}
}
}
</style>
06-商品详情-城市组件-获取数据
目的:组件初始化的时候获取城市数据,进行默认展示。
大致步骤:
- 获取数据函数封装且支持缓存。
- 获取数据渲染且加上加载中效果。
- 加上一个
vue-cli
配置,处理图片为base64
落地代码:src/components/library/xtx-city.vue
- 获取数据的函数
// 获取城市数据
// 1. 数据在哪里?https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json
// 2. 何时获取?打开城市列表的时候,做个内存中缓存
// 3. 怎么使用数据?定义计算属性,根据点击的省份城市展示
const getCityData = () => {
// 这个位置可能有异常操作,封装成promise
return new Promise((resolve, reject) => {
if (window.cityData) {
// 有缓存
resolve(window.cityData)
} else {
// 无缓存
const url = 'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
axios.get(url).then(res => {
window.cityData = res.data
resolve(window.cityData)
})
}
})
}
- open使用函数
// 获取城市数据,显示当前地方列表
// 2. 显示和隐藏函数(为什么是函数,做更多事情)
const loading = ref(false)
const cityData = ref([])
const open = () => {
visible.value = true
loading.value = true
// 获取数据
getCityData().then(data => {
cityData.value = data
loading.value = false
})
}
// 定义计算属性
const currList = computed(() => {
const currList = cityData.value
// TODO 根据点击的省份城市获取对应的列表
return currList
})
return { active, toggleDialog, target, currList, loading }
- 加载中样式
.option {
// 省略...
.loading {
height: 290px;
width: 100%;
background: url(../../assets/images/loading.gif) no-repeat center;
}
}
- 模板中使用
<div class="option" v-if="visible">
+ <div v-if="loading" class="loading"></div>
+ <template v-else>
+ <span class="ellipsis" v-for="item in currList" :key="item.code">{{item.name}}</span>
+ </template>
</div>
注意事项: 需要配置10kb下的图片打包成base64的格式 vue.config.js
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, { limit: 10000 }))
}
07-商品详情-城市组件-交互逻辑
目的:显示省市区文字,让组件能够选择省市区并且反馈给父组件。
大致步骤:
- 明确和后台交互的时候需要产生哪些数据,省code,市code,地区code,它们组合再一起的文字。
- 商品详情的默认地址,如果登录了又地址列表,需要获取默认的地址,设置商品详情的地址。
- 然后默认的地址需要传递给
xtx-city
组件做默认值显示 - 然后
xtx-city
组件产生数据的时候,需要给出:省code,市code,地区code,它们组合再一起的文字。
落的代码:
- 第一步:父组件设置 省市区的code数据,对应的文字数据。
src/views/goods/components/goods-name.vue
setup (props) {
// 默认情况
const provinceCode = ref('110000')
const cityCode = ref('119900')
const countyCode = ref('110101')
const fullLocation = ref('北京市 市辖区 东城区')
// 有默认地址
if (props.goods.userAddresses) {
const defaultAddr = props.goods.userAddresses.find(addr => addr.isDefault === 1)
if (defaultAddr) {
provinceCode.value = defaultAddr.provinceCode
cityCode.value = defaultAddr.cityCode
countyCode.value = defaultAddr.countyCode
fullLocation.value = defaultAddr.fullLocation
}
}
return { fullLocation }
}
<XtxCity :fullLocation="fullLocation" />
- 第二步:监听用户点击 省,市 展示 市列表和地区列表。
src/components/xtx-city.vue
<div class="option" v-show="visible">
+ <span @click="changeItem(item)" class="ellipsis"
const changeResult = reactive({
provinceCode: '',
provinceName: '',
cityCode: '',
cityName: '',
countyCode: '',
countyName: '',
fullLocation: ''
})
const changeItem = (item) => {
// 省份
if (item.level === 0) {
changeResult.provinceCode = item.code
changeResult.provinceName = item.name
}
// 市
if (item.level === 1) {
changeResult.cityCode = item.code
changeResult.cityName = item.name
}
}
计算出需要展示列表
// 定义计算属性
const currList = computed(() => {
// 省份
let currList = cityData.value
// 城市
if (changeResult.provinceCode) {
currList = currList.find(p => p.code === changeResult.provinceCode).areaList
}
// 地区
if (changeResult.cityCode) {
currList = currList.find(c => c.code === changeResult.cityCode).areaList
}
return currList
})
打开弹层清空之前的选择
const open = () => {
visible.value = true
loading.value = true
// 获取数据
getCityData().then(data => {
cityData.value = data
loading.value = false
})
// 清空选择结果
+ for (const key in changeResult) {
+ changeResult[key] = ''
+ }
}
- 第三步:点击地区的时候,将数据通知给父组件使用,关闭对话框
src/components/xtx-city.vue
const changeItem = (item) => {
// 省份
if (item.level === 0) {
changeResult.provinceCode = item.code
changeResult.provinceName = item.name
}
// 市
if (item.level === 1) {
changeResult.cityCode = item.code
changeResult.cityName = item.name
}
+ // 地区
+ if (item.level === 2) {
+ changeResult.countyCode = item.code
+ changeResult.countyName = item.name
+ changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
+ close()
+ emit('change', changeResult)
+ }
}
src/views/goods/components/goods-name.vue
// 选择城市
const changeCity = (result) => {
provinceCode.value = result.provinceCode
cityCode.value = result.cityCode
countyCode.value = result.countyCode
fullLocation.value = result.fullLocation
}
return { fullLocation, changeCity }
<XtxCity @change="changeCity" :fullLocation="fullLocation" />