首页主体
# 首页主体
创建轮播图和首页入口组件
home-category.vue
# 左侧入口
将发送请求的 topCategory
的数据 拿到
export default{
setup(){
const store = useStore();
const brand = reactive({
id:'brand',
name:'品牌',
children:[{id:'brand-children',name:'品牌推荐'}]
})
const menuList = computed((){
const list = store.state.category.list.map(item=>{
return{
id:item.id,
name:item.name,
children:item.chilren&&item.children.slice(0,2)
goods:itea.goods
}
})
list.push(brand)
return list
})
return {meanList}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用
<ul class="menu">
<li v-for="item in menuList" :key="item.id">
<RouterLink :to="`/category/${item.id}`">{{item.name}}</RouterLink>
<template v-if="item.children">
<RouterLink
v-for="sub in item.children"
:key="sub.id"
:to="`/category/${sub.id}`">
{{sub.name}}
</RouterLink>
</template>
</li>
</ul>
2
3
4
5
6
7
8
9
10
11
12
13
# 弹层展示
写样式
<!-- 弹层 --> <div class="layer"> <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4> <ul> <li v-for="i in 9" :key="i"> <RouterLink to="/"> <img src="https://yanxuan-item.nosdn.127.net/5a115da8f2f6489d8c71925de69fe7b8.png" alt=""> <div class="info"> <p class="name ellipsis-2">【定金购】严选零食大礼包(12件)</p> <p class="desc ellipsis">超值组合装,满足馋嘴欲</p> <p class="price"><i>¥</i>100.00</p> </div> </RouterLink> </li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16使用计算属性得到弹出层推荐商品数据
const categoryId = ref(null) const currCategory = computed(()=>{ return menuList.value.find(item=>item.id=category.value) }) return {menuList,categoryId,currCategory}
1
2
3
4
5分类id
CategoryId
传入<li v-for = "item in menuList" @mouseenter = "categoryId = item.id"
1铺设数据
<!-- 弹层 --> <div class="layer"> <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4> <ul v-if="currCategory && currCategory.goods && currCategory.goods.length"> <li v-for="item in currCategory.goods" :key="item.id"> <RouterLink to="/"> <img :src="item.picture" alt=""> <div class="info"> <p class="name ellipsis-2">{{item.name}}</p> <p class="desc ellipsis">{{item.desc}}</p> <p class="price"><i>¥</i>{{item.price}}</p> </div> </RouterLink> </li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 处理品牌
注意
因为 vue3
中的 setup()
函数是立即执行的,所以在发送请求时
async setup(){await API()}
会有问题 ,尽量避免使用 async
const brand = reactive({
id:'brand',
name:'品牌',
children:[{id:'brand-children',name:'品牌推荐'}],
brands:[]
})
findBrand().then(data=>{
brand.brands = data.result
})
2
3
4
5
6
7
8
9
10
使用返回的 Promise
- 编写样式
<ul>
<li class="brand" v-for="i in 6" :key="i">
<RouterLink to="/">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/brand_goods_1.jpg" alt="">
<div class="info">
<p class="place"><i class="iconfont icon-dingwei"></i>北京</p>
<p class="name ellipsis">DW</p>
<p class="desc ellipsis-2">DW品牌闪购</p>
</div>
</RouterLink>
</li>
</ul>
2
3
4
5
6
7
8
9
10
11
12
发送请求 ,修改显示逻辑
<h4 v-if="currCategory">{{currCategory.id==='brand'?'品牌':'分类'}}推荐 <small>根据您的购买或浏览记录推荐</small></h4>
1品牌分类显示逻辑
<ul v-if="currCategory && currCategory.goods && currCategory.goods.length"> <li v-for="item in currCategory.goods" :key="item.id"> <RouterLink to="/"> <img :src="item.picture" alt=""> <div class="info"> <p class="name ellipsis-2">{{item.name}}</p> <p class="desc ellipsis">{{item.desc}}</p> <p class="price"><i>¥</i>{{item.price}}</p> </div> </RouterLink> </li> </ul> + <ul v-if="currCategory && currCategory.brands && currCategory.brands.length"> + <li class="brand" v-for="item in currCategory.brands" :key="item.id"> + <RouterLink to="/"> + <img :src="item.picture" alt=""> + <div class="info"> + <p class="place"><i class="iconfont icon-dingwei"></i>{{item.place}}</p> + <p class="name ellipsis">{{item.name}}</p> + <p class="desc ellipsis-2">{{item.desc}}</p> + </div> + </RouterLink> + </li> + </ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24不同的页面展示处理
激活样式逻辑编写
&:hover,&:avtive{ background:@xtxColor; }
1
2
3激活条件
<li :class="{active:categoryId===item.id}"
1移除条件
<div class="home-category" @mouseleave="categoryId=null"></div>
1
# 加载骨架效果
为了在加载的过程中等待效果更好,封装一个骨架屏组件
使用插件注册的方式,对骨架进行注册使用
library/index.js
//在 vue2 中的写法
// 导入对象 - install函数 - 传入Vue的构造函数 - Vue的基础之上扩展
//在 vue3 中的写法
// 导入对象 - install函数 - 传入了app应用实例 - app基础之上扩展
2
3
4
import xtxSkeleton from './xtx-skeleton.vue'
export default{
install(app){
}
}
2
3
4
5
6
使用插件
import UI from '@/components/library'
createApp(APP).use(UI)
2
# 轮播图
home/components
下装载轮播图的 HomeBanner
容器作为组件
轮播图组件 carousel.vue
//HomeBanner
2
carousel.vue
接收的参数 props
sliders:{
type:Array,
default:()=>[]
},
autoPlay:{
type:Boolean,
default:false
},
duration:{
type:Number,
default:3000
}
2
3
4
5
6
7
8
9
10
11
12
控制轮播图的变量
index
控制图片显示的索引timer
autoPlayFn
自动播放函数const autoPlayFn=()=>{ clearInterval(); timer = setInterval(()=>{ index.value++; if(index.value>=props.sliders.length){ index.value = 0 } },props.duration) } // 监听 sliders 数据变化,判断如果有数据且 autoPlay 为true watch(()=>props.sliders,(newVal)=>{ if(newVal.length&&props.autoPlay){ autoPlayFn() } },{immediate:true})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15stop
停止轮播图const stop = ()=>{ if(timer) clearInterval(timer) }
1
2
3start
开始轮播图const start = ()=>{ if(props.sliders.length&&props.length){ autoPlayFn() } }
1
2
3
4
5toggle()
切换index
const toggle = (step) => { const newIndex = index.value + step if(newIndex>props.sliders.length-1){ index.value = 0 return } if(newIndex<0){ index.value = props.sliders.length-1 return } index.value = newIndex }
1
2
3
4
5
6
7
8
9
10
11
12
组件卸载时清楚定时器
onUnmouted(()=>{
clearInterval(timer)
})
2
3
# 公共面板
首页展示模块的面板封装 ,方便复用
# home-panel
主面板
<slot name="right">
具名插槽
<slot>
默认插槽
// 有 一级标题 和 二级标题
具名插槽对面板顶部标题栏的显示
默认插槽是对具体面板内容的显示
home-new
新鲜好物模块去套用 面板模块
# home-new
新鲜好物
# home-hot
人气推荐
# Vue
动画
官方内置 transition
组件
<transition>
<div>
100px
</div>
</transition>
<style>
.v-enter-from{
opacity:0;
}
.v-enter-active{
transition:all 2s;
}
.v-enter-to{
opacity:1;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果需要多组动画 , 需要给transition加上名字
<transition name="xxx">
# 挂架组件
# 数据懒加载
新建文件 hooks
,为数据懒加载创建工具模块
使用 vueuse
第三方组合API模块,封装自己的数据懒加载的工具类
# 官方示例
<div ref="target">
<h1>Hello world</h1>
</div>
<script>
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
setup() {
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
},
)
return {
target,
targetIsVisible,
}
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
当内容移至到可视区域时触发 stop 停止监听
target
监听的目标 DOM 容器isIntersecting
是否在可视区域obseverElement
监听的元素ref
通过对targer
的放回,绑定了其容器的懒加载
# 实现
/**
* 数据懒加载
* @param {Element} target - Dom对象
* @param {Funciton} apiFn - API函数
*/
import {useIntersectionObserve} from '@vueuse/core'
import {ref} from 'vue'
export const useLazyData = (apiFn)=>{
const target = ref(null)
const result = ref([])
const {stop} = useIntersectionObserver(
target,
([isIntersecting],observerElement)=>{
if(isIntersecting){//监听到内容移至到可视区域
stop() //停止监听
apiFn().then(data=>{//调用API
result.value = data.value
})
}
}
)
return {target,result}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 热门品牌
# 布局渲染
# 加入骨架屏
# 商品区块
用到的组件
home-good
商品块组件home-product
整个商品区块 里面使用了面板模块home-panel
# 修改可视区阀值
threshold
: 临界值
setIntersectionObserve({
target,
()=>{
//...
},
// 配置选项,相交的比例大于 0 就触发
{
threshold:0
}
})
2
3
4
5
6
7
8
9
10
# 图片懒加载
# IntersectionObserve
# 封装
先存储图片地址,不放在 src
中 ,当图片进入可视区域,将存储的图片地址设置给图片元素
import defaultImg from '@/assets/images/200.png'
// 指令
const defineDirective = (app) => {
// 图片懒加载指令
app.directive('lazyload', {
mounted (el, binding) {
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) {
observer.unobserve(el)
el.onerror = () => {
el.src = defaultImg
}
el.src = binding.value
}
}, {
threshold: 0.01
})
observer.observe(el)
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 懒加载失败情况
给 el
元素加上默认的图片