创建两个组件
home-categroy.vue 分类菜单 home-banner.vue 轮播图
<template>
<div class="home-category">
<ul class="menu">
<li v-for="item in list" :key="item.id">
<RouterLink to="/">{{item.name}}</RouterLink>
<!--
v-for 和 v-if 不能一起用
-->
<template v-if="item.children.length > 0">
<RouterLink v-for="i in item.children" :key="i.id" to="/">{{i.name}}</RouterLink>
</template>
<!-- 弹层layer位置 -->
</li>
</ul>
</div>
</template>
<script>
// 数据:使用的vuex category中的数据 只不过需要把chilren中的前俩项数据筛选出来
// 基于某些现有的响应式数据经过一定的计算得到新的数据 -> 计算属性
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
name: 'HomeCategory',
setup () {
const store = useStore()
const list = computed(() => {
// 基于vuex中的数据 截取children中的前俩项
// 基于一个数组 针对于数据的每一项都做处理 然后返回一个全新的数组 map
return store.state.category.list.map((item) => {
return {
id: item.id,
name: item.name,
children: item.children.slice(0, 2),
goods: item.goods
}
})
})
return {
list
}
}
}
</script>
<style scoped lang='less'>
.home-category {
width: 250px;
height: 500px;
background: rgba(0, 0, 0, 0.8);
position: relative;
z-index: 99;
.menu {
li {
padding-left: 40px;
height: 55px;
line-height: 55px;
&:hover {
background: @xtxColor;
}
a {
margin-right: 4px;
color: #fff;
&:first-child {
font-size: 16px;
}
}
.layer {
width: 990px;
height: 500px;
background: rgba(255, 255, 255, 1);
position: absolute;
left: 250px;
top: 0;
display: none;
padding: 0 15px;
h4 {
font-size: 20px;
font-weight: normal;
line-height: 80px;
small {
font-size: 16px;
color: #666;
}
}
ul {
display: flex;
flex-wrap: wrap;
li {
width: 310px;
height: 120px;
margin-right: 15px;
margin-bottom: 15px;
border: 1px solid #eee;
border-radius: 4px;
background: #fff;
&:nth-child(3n) {
margin-right: 0;
}
a {
display: flex;
width: 100%;
height: 100%;
align-items: center;
padding: 10px;
&:hover {
background: #e3f9f4;
}
img {
width: 95px;
height: 95px;
}
.info {
padding-left: 10px;
line-height: 24px;
overflow: hidden;
.name {
font-size: 16px;
color: #666;
}
.desc {
color: #999;
}
.price {
font-size: 22px;
color: @priceColor;
i {
font-size: 16px;
}
}
}
}
}
}
}
&:hover {
.layer {
display: block;
}
}
}
}
}
</style>
<template>
<div class="home-banner">
banner
</div>
</template>
<script>
export default {
name: 'HomeBanner'
}
</script>
<style scoped lang='less'>
.home-banner {
width: 1240px;
height: 500px;
position: absolute;
left: 0;
top: 0;
z-index: 98;
}
</style>
导入首页
<template>
<div class="page-home">
<div class="home-entry">
<div class="container">
<!-- 左侧分类 -->
<HomeCategory />
<!-- banner轮播图 -->
<HomeBanner/>
</div>
</div>
</div>
</template>
<script>
import HomeBanner from './components/home-banner.vue'
import HomeCategory from './components/home-categroy.vue'
export default {
name: 'PageHome',
components: {
HomeBanner,
HomeCategory
}
}
</script>
<style>
</style>
分类
分类弹层组件
<<!-- 弹层layer位置 -->
<div class="layer">
<h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4>
<ul>
<li v-for="i in item.goods" :key="i.id">
<RouterLink to="/">
<img :src="i.picture" alt="" />
<div class="info">
<p class="name ellipsis-2">
{{ i.name }}
</p>
<p class="desc ellipsis">{{ i.desc }}</p>
<p class="price"><i>¥</i>{{ i.price }}</p>
</div>
</RouterLink>
</li>
</ul>
</div>
loading效果
骨架屏组件
<template>
<!-- 决定组件的宽高 -->
<div class="xtx-skeleton shan" :style="{ width: width + 'px', height: height + 'px' }">
<!-- 决定组件的背景色 -->
<div class="block" :style="{ backgroundColor: bg}"></div>
</div>
</template>
<script>
export default {
name: 'XtxSkeleton',
props: {
// 宽度定制
width: {
type: Number,
default: 100
},
// 高度定制
height: {
type: Number,
default: 60
},
// 背景颜色定制
bg: {
type: String,
default: '#ccc'
}
}
}
</script>
<style scoped lang="less">
.xtx-skeleton {
display: inline-block;
position: relative;
overflow: hidden;
vertical-align: middle;
.block {
width: 100%;
height: 100%;
border-radius: 2px;
}
}
.shan {
&::after {
content: "";
position: absolute;
animation: shan 1.5s ease 0s infinite;
top: 0;
width: 50%;
height: 100%;
background: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: skewX(-45deg);
}
}
@keyframes shan {
0% {
left: -100%;
}
100% {
left: 120%;
}
}
</style>
封装插件
import Skeleton from './Skeleton/index.vue'
export default {
install (app) {
app.component('Skeleton', Skeleton)
}
}
// 插件
import ComponentsPlugin from '@/components/index'
const app = createApp(App)
app.use(ComponentsPlugin).use(store).use(router).mount('#app')
轮播图
<template>
<div class="box" style="height: 500px">
<div class="xtx-slider" @mouseenter="clearTimer" @mouseleave="startTimer">
<!-- 图片列表 -->
<ul class="slider-body" >
<!--
fade: 当fade类名存在 当前图片就显示 不存在就不显示
-->
<li
class="slider-item"
v-for="(item, i) in sliders"
:key="i"
:class="{ fade: curIndex === i }"
>
<img :src="item.imgUrl" alt="" />
</li>
</ul>
<!-- 圆圈切换按钮 -->
<div class="slider-indicator">
<span
v-for="(item, index) in sliders"
:key="index"
@click="curIndex = index"
:class="{ active: curIndex === index }"
></span>
</div>
</div>
</div>
</template>
<script>
/**
目标:点击圆圈按钮 实现对应图片的切换
思路:
1. 图片和圆圈按钮数量是一样的 下标值是对应的
2. 记录一下当前点击的是哪一项
3. 需要根据记录下来的下标值 去配合:class 控制fade这个类名是否应该显示
*/
/**
目标:图片的自动轮播功能
思路:哪个数据变化决定了图片切换? 从之前手动修改curIndex的值 变成一个自动修改 每隔几秒修改一下
计时器 setInterval
*/
/**
目标:鼠标移入暂停播放 鼠标移除再次开启
思路:暂停 - 清除定时器 定时器id 开启 - 再执行一次定时器
*/
import { onMounted, onUnmounted, ref } from 'vue'
export default {
name: 'XtxSlider',
props: {
// 数据列表
sliders: {
type: Array,
default: () => {
return []
}
},
autoPlay: {
type: Boolean,
default: true
}
},
setup (props) {
const curIndex = ref(0)
// 声明一个存放定时器的数据
const timer = ref(null)
function clearTimer () {
clearInterval(timer.value)
}
function startTimer () {
// 开启定时器 每隔2s中修改一下curIndex的值
initLoop()
}
function initLoop () {
if (!props.autoPlay) {
return false
}
// 开启定时器 每隔2s中修改一下curIndex的值
timer.value = window.setInterval(() => {
// 最大能到多大
// 图片总数为4 length - 1为3 只要我发现你大于3了
// 我就会重新赋值为 0 ,永远不能到达4 最大只能等于3
curIndex.value++
if (curIndex.value > props.sliders.length - 1) {
curIndex.value = 0
}
}, 2000)
}
onMounted(() => {
initLoop()
})
onUnmounted(() => {
// 清理工作
clearInterval(timer.value)
})
return {
curIndex,
clearTimer,
startTimer
}
}
}
</script>
<style scoped lang='less'>
.xtx-slider {
width: 100%;
height: 100%;
min-width: 300px;
min-height: 150px;
position: relative;
.slider {
&-body {
width: 100%;
height: 100%;
}
&-item {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
transition: opacity 0.5s linear;
&.fade {
opacity: 1;
z-index: 1;
}
img {
width: 100%;
height: 100%;
}
}
&-indicator {
position: absolute;
left: 0;
bottom: 20px;
z-index: 2;
width: 100%;
text-align: center;
span {
display: inline-block;
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
cursor: pointer;
~ span {
margin-left: 12px;
}
&.active {
background: #fff;
}
}
}
&-btn {
width: 44px;
height: 44px;
background: rgba(0, 0, 0, 0.2);
color: #fff;
border-radius: 50%;
position: absolute;
top: 228px;
z-index: 2;
text-align: center;
line-height: 44px;
opacity: 0;
transition: all 0.5s;
&.prev {
left: 20px;
}
&.next {
right: 20px;
}
}
}
&:hover {
.slider-btn {
opacity: 1;
}
}
}
</style>
注册后使用
数据懒加载
computed
当依赖项不变的时候会有缓存
不支持异步的操作
不能直接改变值
改值get set
watch
不支持缓存
支持异步操作
属性 deep 深度监听
使用场景