五、底部导航
(一)需求
- 底部导航栏有三个内容:记账、标签、统计。点击之后需要跳转到对应的页面。
- 底部导航栏在除了404页面都有。
(二)步骤:导航栏该写在哪

- 首先尝试把导航栏写在App.vue的
<template>标签。不对!那404页面也会有啊!
<template>
<div>
<router-view />
<div>
<router-link to="/money">记账</router-link>
|
<router-link to="/labels">标签</router-link>
|
<router-link to="/statistics">统计</router-link>
</div>
</div>
</template>


- 那就把导航栏写成一个名叫Nav的组件,哪个页面要用就让他用
- 新建components/Nav.vue。把导航栏的代码写到该文件里。这就是专门用来表示导航栏的组件。
-
(把Nav组件当局部组件使用)记账、标签、统计页面都要用Nav组件。那就导入到他们的component里去然后声明这是他要用的组件然后才能使用。
-
(把Nav组件当全部组件使用)第三步好麻烦啊!虽然webstorm会帮我们做,还是觉得有点麻烦。那就把Nav组件声明为一个全局组件吧!就不用三个组件都要导入然后声明这是他们要用的组件了。
- 在main.ts里声明Nav组件是全局组件。
- 哪个页面要用Nav全局组件?直接用在你们的对应组件的
<template>标签里用就完事了!
(三)步骤:用CSS让导航栏到页面最底部---插槽
- 导航栏在最底部。手机上绝对不要用fixed定位。用flex布局,你遇到的坑会少点
- 在App.vue里加CSS Reset
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
- 首先设置记账页面的底部导航栏
- 所以在Money.vue组件里编辑。用flex布局。
<template>
<div class="nav-wrapper">
<div class="content">
<p>Money.vue</p>
</div>
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
.nav-wrapper {
border: 1px solid black;
display: flex;
flex-direction: column;
/*让整个记账页面的高度固定为全部屏幕。需要固定高度100vh*/
height: 100vh;
}
.content {
/*让内容能多宽(高)就多宽(高),这样导航栏就到最底部了*/
flex-grow: 1;
/* 让内容溢出的部分可以滚动*/
overflow: auto;
}
</style>
- 那标签页面和统计页面呢?难道要在重复写两次?
- 发现他们仨只有content里的内容不一样!
- 重复的玩意放进组件,不重复的玩意用插槽
- 新建组件components/Layout.vue,做成全局组件
- 把相同的部分拷贝过来。不同的那部分先占位插槽。
- 之后谁用这个Layout组件,里面写的内容就会替换
Layout.vue
<template>
<div class="nav-wrapper">
<div class="content">
<!-- 只有content里的内容不一样。所以用插槽先占位。以后谁用了本组件就在本组件里面写上属于你自己的内容就行了 -->
<slot />
</div>
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Layout'
};
</script>
<style lang="scss" scoped>
.nav-wrapper {
border: 1px solid black;
display: flex;
flex-direction: column;
/*让整个记账页面的高度固定为全部屏幕。需要固定高度100vh*/
height: 100vh;
}
.content {
/*让内容能多宽(高)就多宽(高),这样导航栏就到最底部了*/
flex-grow: 1;
/* 让内容溢出的部分可以滚动*/
overflow: auto;
}
</style>
Money.Vue
<template>
<div>
<Layout>
<p>Money.vue</p> //把插槽替换
</Layout>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
</style>
(四)步骤:引入SVG
1. 导航栏需要有三个icon。
- 去iconfont找三个icon。点击SVG下载,改名字,复制粘贴到assets/icons
2. 在Nav.vue里导入这三个SVG文件(其实是xml标签)。
import x from "@/assets/icons/label.svg"发现有红色波浪线。搜索"Typescript SVG can"抄就完事- 在shims-vue.d.ts文件里
declare module "*.svg" {
const content: string;
export default content;
}
3. 搜索"svg sprite loader",抄就完事
- 打开控制台:安装
yarn add svg-sprite-loader -D - 做些配置:你得把搜索到的webpack.config.js的内容改成vue.config.js认识的内容。
vue.config.js
const path = import('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons') //我们的icons全处于这个目录
//config就是vue把webpack的API封装了,然后暴露给我们的一个对象
config.module
.rule('svg-sprite') //添加一个规则
.test(/\.svg$/ //文件如果匹配上了这个正则表达式(整个目录下的.svg结尾的文件)就用这个规则
.include.add(dir).end() // 只包含 icons 目录就结束,其他目录一概不走这个规则(也就是icons目录的.svg文件)
.use('svg-sprite-loader').loader('svg-sprite-loader').options({extract: false}).end(), //使用哪些loader
//有个坑!打开一个svg文件,其实是个xml文件,用soft wrap自动换行看得更清楚。在<path>标签里添加fill=某种color,那这个icon svg 就会变色!相当于这个svg默认就带颜色了,会覆盖我们自己给他的颜色!那我们不想要我们的svg自带颜色(因为我们改不了),我们也不可能一个个svg都去删掉他们的fill,万一有一百个svg呢!
.use('svgo-loader').loader('svgo-loader') //可以使用一个svgo-loader,记得安装(yarn add --dev svgo-loader)!
.tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end() //添加了一个选项,删除fill属性
//配置插件
config.plugin('svg-sprite').use(import('svg-sprite-loader/plugin'), [{plainSprite: true}]),
//其他的svg loader要排除我们现在这个目录,不然dir又走了上面这个又走了其他的
config.module.rule('svg').exclude.add(dir) , // 其他 svg loader 排除 icons 目录
// config.module
// .rule('svg-sprite')
// .test(/\.(svg)(\?.*)?$/)
// .include.add(dir).end()
// .use('svg-sprite-loader-mod').loader('svg-sprite-loader-mod').options({extract: false}).end()
//有个坑!打开一个svg文件,其实是个xml文件,用soft wrap自动换行看得更清楚。在<path>标签里添加fill=某种color,那这个icon svg 就会变色!相当于这个svg默认就带颜色了,会覆盖我们自己给他的颜色!那我们不想要我们的svg自带颜色(因为我们改不了),我们也不可能一个个svg都去删掉他们的fill,万一有一百个svg呢!
//可以使用一个svgo-loader,记得安装!
// .use('svgo-loader').loader('svgo-loader')
//添加了一个选项,删除fill属性
// .tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]}))
// .end()
// config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
// config.module.rule('svg').exclude.add(dir)
}
}
- svg sprite loader的作用:他把我们导入的svg文件们变成symbol标签们(会有个id),把symbol标签外面包一个svg标签,把svg标签放入body里。接下来我们就可以用svg标签包着use标签(用xlink:href指向那个id)就可以使用那个svg了。

Nav.vue
<template>
<div class="nav">
<router-link to="/label">
<!--最后,这样使用svg-->
<svg>
<use xlink:href="#label"/>
</svg>
标签
</router-link>
<router-link to="/money">
<svg>
<use xlink:href="#money"/>
</svg>
记账
</router-link>
<router-link to="/statistics">
<svg>
<use xlink:href="#statistics"/>
</svg>
统计
</router-link>
<!--上面代码重复了三遍,请接着看下面的解决办法!-->
</div>
</template>
<script lang="ts">
import x from '@/assets/icons/label.svg' //我们先导入svg文件,然后svg-sprite-loader会帮我们做好多事情。
import x from '@/assets/icons/money.svg'
import x from '@/assets/icons/statistics.svg'
//上面代码重复了三遍!请接着看下面的解决办法
export default {
name: 'Nav'
};
</script>
<style lang="scss" scoped>
.nav {
border: 1px solid red;
}
</style>
4. 我不想每个svg都导入一遍,我想直接把装svg文件的文件夹直接导入。
- 把上面代码中三次引入svg删去
- 添加以下代码可以将一个目录里的某种后缀的文件全部导入
let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../assets/icons', true, /\.svg$/));} catch (error) {console.log(error);}
5. 封装icon组件
- 新建components/Icon.vue,做成全局组件(因为不止一个地方要用)
- 把重复的内容复制粘贴进去。重复的内容就是
<svg>和<use>。而且也得把svg的导入给弄过来 - 那
<use>里的id都不一样。那就让Icon.vue接受一个外部数据iconId,每次谁要使用icon就传入iconId。 - 让Nav.vue里的对应的内容替换成
<Icon iconId="id名"> - 把icon的样式设置一下,可以从iconfont.io的帮助文档里面抄过来
icon.vue
<template>
<svg class="icon">
<use v-bind:xlink:href="'#'+iconId"/>
</svg>
</template>
<script lang="ts">
let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../assets/icons', true, /\.svg$/));} catch (error) {console.log(error);}
export default {
name: 'Icon',
props:["iconId"]
};
</script>
<style lang="scss" scoped>
.icon {
/*从iconfont的帮助文档抄过来*/
/*因为icon放在字旁边,所以和字一样大在这就算好看的了*/
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
Nav.vue
<template>
<div class="nav">
<router-link to="/labels">
<Icon iconId="label"/>
标签
</router-link>
<router-link to="/money">
<Icon iconId="money"/>
记账
</router-link>
<router-link to="/statistics">
<Icon iconId="statistics"/>
统计
</router-link>
</div>
</template>
<script lang="ts">
export default {
name: 'Nav',
};
</script>
<style lang="scss" scoped>
.nav {
border: 1px solid red;
}
</style>
(五)步骤:差不多可以给底部导航加CSS样式了
路由激活active-class
- 点了一个路由链接,应该高亮
- 给你想要高亮的路由链接们添加一个属性active-class='selected'
<router-link to="/labels" class="item" active-class="selected"> - 意思是说,你点了哪个路由链接,就会给该路由添加一个class叫selected
- 原理:每个页面都有nav标签,nav标签里面有三个路由链接。每当你点了一个路由链接,那肯定会跳转到另外的页面,之前页面的nav标签会消失,出现新的页面的nav标签。新的nav标签发现当前的路径路由和我的某一个
router-link to=是一样的,就会给这个router-link to=加上selected的class - 那就给.selected加上一个CSS样式:
color: mediumvioletred。
