Vue3 小兔鲜4:Layout-静态模版结构搭建
Date: May 31, 2023
目标效果:
分成Nav、Heade、二级路由出口、Footer区域
组件结构快速搭建
Nav
<script setup> </script> <template> <nav class="app-topnav"> <div class="container"> <ul> <template v-if="true"> <li><a href="javascript:;"><i class="iconfont icon-user"></i>周杰伦</a></li> <li> <el-popconfirm title="确认退出吗?" confirm-button-text="确认" cancel-button-text="取消"> <template #reference> <a href="javascript:;">退出登录</a> </template> </el-popconfirm> </li> <li><a href="javascript:;">我的订单</a></li> <li><a href="javascript:;">会员中心</a></li> </template> <template v-else> <li><a href="javascript:;">请先登录</a></li> <li><a href="javascript:;">帮助中心</a></li> <li><a href="javascript:;">关于我们</a></li> </template> </ul> </div> </nav> </template> <style scoped lang="scss"> .app-topnav { background: #333; ul { display: flex; height: 53px; justify-content: flex-end; align-items: center; li { a { padding: 0 15px; color: #cdcdcd; line-height: 1; display: inline-block; i { font-size: 14px; margin-right: 2px; } &:hover { color: $xtxColor; } } ~li { a { border-left: 2px solid #666; } } } } } </style>
Header
<script setup> </script> <template> <header class='app-header'> <div class="container"> <h1 class="logo"> <RouterLink to="/">小兔鲜</RouterLink> </h1> <ul class="app-header-nav"> <li class="home"> <RouterLink to="/">首页</RouterLink> </li> <li> <RouterLink to="/">居家</RouterLink> </li> <li> <RouterLink to="/">美食</RouterLink> </li> <li> <RouterLink to="/">服饰</RouterLink> </li> </ul> <div class="search"> <i class="iconfont icon-search"></i> <input type="text" placeholder="搜一搜"> </div> <!-- 头部购物车 --> </div> </header> </template> <style scoped lang='scss'> .app-header { background: #fff; .container { display: flex; align-items: center; } .logo { width: 200px; a { display: block; height: 132px; width: 100%; text-indent: -9999px; background: url('@/assets/images/logo.png') no-repeat center 18px / contain; } } .app-header-nav { width: 820px; display: flex; padding-left: 40px; position: relative; z-index: 998; li { margin-right: 40px; width: 38px; text-align: center; a { font-size: 16px; line-height: 32px; height: 32px; display: inline-block; &:hover { color: $xtxColor; border-bottom: 1px solid $xtxColor; } } .active { color: $xtxColor; border-bottom: 1px solid $xtxColor; } } } .search { width: 170px; height: 32px; position: relative; border-bottom: 1px solid #e7e7e7; line-height: 32px; .icon-search { font-size: 18px; margin-left: 5px; } input { width: 140px; padding-left: 5px; color: #666; } } .cart { width: 50px; .curr { height: 32px; line-height: 32px; text-align: center; position: relative; display: block; .icon-cart { font-size: 22px; } em { font-style: normal; position: absolute; right: 0; top: 0; padding: 1px 6px; line-height: 1; background: $helpColor; color: #fff; font-size: 12px; border-radius: 10px; font-family: Arial; } } } } </style>
Footer
<template> <footer class="app_footer"> <!-- 联系我们 --> <div class="contact"> <div class="container"> <dl> <dt>客户服务</dt> <dd><i class="iconfont icon-kefu"></i> 在线客服</dd> <dd><i class="iconfont icon-question"></i> 问题反馈</dd> </dl> <dl> <dt>关注我们</dt> <dd><i class="iconfont icon-weixin"></i> 公众号</dd> <dd><i class="iconfont icon-weibo"></i> 微博</dd> </dl> <dl> <dt>下载APP</dt> <dd class="qrcode"><img src="@/assets/images/qrcode.jpg" /></dd> <dd class="download"> <span>扫描二维码</span> <span>立马下载APP</span> <a href="javascript:;">下载页面</a> </dd> </dl> <dl> <dt>服务热线</dt> <dd class="hotline">400-0000-000 <small>周一至周日 8:00-18:00</small></dd> </dl> </div> </div> <!-- 其它 --> <div class="extra"> <div class="container"> <div class="slogan"> <a href="javascript:;"> <i class="iconfont icon-footer01"></i> <span>价格亲民</span> </a> <a href="javascript:;"> <i class="iconfont icon-footer02"></i> <span>物流快捷</span> </a> <a href="javascript:;"> <i class="iconfont icon-footer03"></i> <span>品质新鲜</span> </a> </div> <!-- 版权信息 --> <div class="copyright"> <p> <a href="javascript:;">关于我们</a> <a href="javascript:;">帮助中心</a> <a href="javascript:;">售后服务</a> <a href="javascript:;">配送与验收</a> <a href="javascript:;">商务合作</a> <a href="javascript:;">搜索推荐</a> <a href="javascript:;">友情链接</a> </p> <p>CopyRight © 小兔鲜儿</p> </div> </div> </div> </footer> </template> <style scoped lang='scss'> .app_footer { overflow: hidden; background-color: #f5f5f5; padding-top: 20px; .contact { background: #fff; .container { padding: 60px 0 40px 25px; display: flex; } dl { height: 190px; text-align: center; padding: 0 72px; border-right: 1px solid #f2f2f2; color: #999; &:first-child { padding-left: 0; } &:last-child { border-right: none; padding-right: 0; } } dt { line-height: 1; font-size: 18px; } dd { margin: 36px 12px 0 0; float: left; width: 92px; height: 92px; padding-top: 10px; border: 1px solid #ededed; .iconfont { font-size: 36px; display: block; color: #666; } &:hover { .iconfont { color: $xtxColor; } } &:last-child { margin-right: 0; } } .qrcode { width: 92px; height: 92px; padding: 7px; border: 1px solid #ededed; } .download { padding-top: 5px; font-size: 14px; width: auto; height: auto; border: none; span { display: block; } a { display: block; line-height: 1; padding: 10px 25px; margin-top: 5px; color: #fff; border-radius: 2px; background-color: $xtxColor; } } .hotline { padding-top: 20px; font-size: 22px; color: #666; width: auto; height: auto; border: none; small { display: block; font-size: 15px; color: #999; } } } .extra { background-color: #333; } .slogan { height: 178px; line-height: 58px; padding: 60px 100px; border-bottom: 1px solid #434343; display: flex; justify-content: space-between; a { height: 58px; line-height: 58px; color: #fff; font-size: 28px; i { font-size: 50px; vertical-align: middle; margin-right: 10px; font-weight: 100; } span { vertical-align: middle; text-shadow: 0 0 1px #333; } } } .copyright { height: 170px; padding-top: 40px; text-align: center; color: #999; font-size: 15px; p { line-height: 1; margin-bottom: 20px; } a { color: #999; line-height: 1; padding: 0 10px; border-right: 1px solid #999; &:last-child { border-right: none; } } } } </style>
index.vue
<script setup> import LayoutNav from './components/LayoutNav.vue' import LayoutHeader from './components/LayoutHeader.vue' import LayoutFooter from './components/LayoutFooter.vue' </script> <template> <LayoutNav /> <LayoutHeader /> <RouterView /> <LayoutFooter /> </template>
字体图标渲染
效果:
具体操作:
1.在index.html中导入图标库
字体图标采用的是阿里的字体图标库,样式文件已经准备好,在 index.html文件中引入即可
<link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">
1.在标签中具体使用即可
一级导航渲染
使用后端接口渲染一级路由导航:
实现步骤
1.封装接口函数
2.调用接口函数
3.v-for渲染模版
代码落地
import httpInstance from '@/utils/http' export function getCategoryAPI () { return httpInstance({ url: '/home/category/head' }) }
<script setup> import { getCategoryAPI } from '@/apis/layout' import { onMounted, ref } from 'vue' const categoryList = ref([]) const getCategory = async () => { const res = await getCategoryAPI() categoryList.value = res.result } onMounted(() => getCategory()) </script> <template> <header class='app-header'> <div class="container"> <h1 class="logo"> <RouterLink to="/">小兔鲜</RouterLink> </h1> <ul class="app-header-nav"> <li class="home" v-for="item in categoryList" :key="item.id"> <RouterLink to="/">{{ item.name }}</RouterLink> </li> </ul> <div class="search"> <i class="iconfont icon-search"></i> <input type="text" placeholder="搜一搜"> </div> <!-- 头部购物车 --> </div> </header> </template>
吸顶导航交互实现
吸顶交互要求:
页面在上下滚动的过程中,如果距离顶部的滚动距离大手78pX,吸顶导航显示,小于78px隐藏
实现思路:
准备吸顶导航组件⇒获取滚动距离⇒以滚动距离做判断条件控制组件盒子展示隐藏
1. 准备组件静态结构
实现步骤:
1.搞定顶吸组件:LayoutFixed.vue
<script setup> // vueUse import { useScroll } from '@vueuse/core' const { y } = useScroll(window) // 这里的y就是滚动时的实时距离 </script>
效果:
2. 渲染基础数据
原始代码:
Code
<script setup> </script> <template> <div class="app-header-sticky"> <div class="container"> <RouterLink class="logo" to="/" /> <!-- 导航区域 --> <ul class="app-header-nav "> <li class="home"> <RouterLink to="/">首页</RouterLink> </li> <li> <RouterLink to="/">居家</RouterLink> </li> <li> <RouterLink to="/">美食</RouterLink> </li> <li> <RouterLink to="/">服饰</RouterLink> </li> <li> <RouterLink to="/">母婴</RouterLink> </li> <li> <RouterLink to="/">个护</RouterLink> </li> <li> <RouterLink to="/">严选</RouterLink> </li> <li> <RouterLink to="/">数码</RouterLink> </li> <li> <RouterLink to="/">运动</RouterLink> </li> <li> <RouterLink to="/">杂项</RouterLink> </li> </ul> <div class="right"> <RouterLink to="/">品牌</RouterLink> <RouterLink to="/">专题</RouterLink> </div> </div> </div> </template> <style scoped lang='scss'> .app-header-sticky { width: 100%; height: 80px; position: fixed; left: 0; top: 0; z-index: 999; background-color: #fff; border-bottom: 1px solid #e4e4e4; // 此处为关键样式!!! // 状态一:往上平移自身高度 + 完全透明 transform: translateY(-100%); opacity: 0; // 状态二:移除平移 + 完全不透明 &.show { transition: all 0.3s linear; transform: none; opacity: 1; } .container { display: flex; align-items: center; } .logo { width: 200px; height: 80px; background: url("@/assets/images/logo.png") no-repeat right 2px; background-size: 160px auto; } .right { width: 220px; display: flex; text-align: center; padding-left: 40px; border-left: 2px solid $xtxColor; a { width: 38px; margin-right: 40px; font-size: 16px; line-height: 1; &:hover { color: $xtxColor; } } } } .app-header-nav { width: 820px; display: flex; padding-left: 40px; position: relative; z-index: 998; li { margin-right: 40px; width: 38px; text-align: center; a { font-size: 16px; line-height: 32px; height: 32px; display: inline-block; &:hover { color: $xtxColor; border-bottom: 1px solid $xtxColor; } } .active { color: $xtxColor; border-bottom: 1px solid $xtxColor; } } } </style>
这里我们修改一下,通过v-for进行循环:
LayoutFixed.vue
<li class="home" v-for="item in categoryList" :key="item.id"> <RouterLink to="/">{{ item.name }}</RouterLink> </li>
3. 实现吸顶交互
核心逻辑:根据滚动距离判断当前show类名是否显示,大于78显示,小于78,不显示
<script setup> import LayoutHeaderUl from './LayoutHeaderUl.vue' // vueUse import { useScroll } from '@vueuse/core' const { y } = useScroll(window) </script> <template> <div class="app-header-sticky" :class="{ show: y > 78 }"> <!-- 省略部分代码 --> </div> </template>
Pinia优化重复请求
问题:
两个导航中的列表是完全一致的,但是要发送两次网络请求,存在浪费。
优化方式:
通过Pinia集中管理数据,再把数据给组件使用。值的注意的是,我们会在两个子组件的公共父组件中把action触发起来,这样能够保证这个action只会触发一次。
代码:
import { ref } from 'vue' import { defineStore } from 'pinia' import { getCategoryAPI } from '@/apis/layout' export const useCategoryStore = defineStore('category', () => { // 导航列表的数据管理 // state 导航列表数据 const categoryList = ref([]) // action 获取导航数据的方法 const getCategory = async () => { const res = await getCategoryAPI() categoryList.value = res.result } return { categoryList, getCategory } })