- 底部导航(VueRouter)
- 将Nav做成全局组件
- 布局
- Layout组件 & slot插槽
- 使用svg-sprite-loader引入icon
- git commit会报错
- import一个目录问题解决
- 封装icon组件
- 优化样式
- 路由激活
- 使用淘宝meta vp
- svg的bug
-曾老湿, 江湖人称曾老大。
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
底部导航(VueRouter)
确定每个页面的url |
---|
/money 记账
/labels 标签
/statistics 统计
// 默认进入 #/money
// 添加一个404页面
添加router |
---|
先设置一下typescript的格式化

router/index.ts
代码语言:javascript复制import Vue from 'vue';
import VueRouter, {RouteConfig} from 'vue-router';
import Money from '@/views/Money.vue';
import Labels from '@/views/Labels.vue';
import Statistics from '@/views/Statistics.vue';
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: '/',
redirect: '/money'
},
{
path: '/money',
component: Money
},
{
path: '/labels',
component: Labels
},
{
path: '/statistics',
component: Statistics
}
];
const router = new VueRouter({
routes
});
export default router;
创建vue文件 |
---|
在写router的过程中,一边写,一边在views目录下创建对应的vue文件,方便导入。
代码语言:javascript复制views/Money.vue
views/Labels.vue
views/Statistics.vue
修改App.vue |
---|
我们来测试一下,如何将刚才创建的三个vue组件,显示在页面上
代码语言:javascript复制<template>
<div>
App
<hr>
<router-view/>
</div>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
Labels.vue
代码语言:javascript复制<template>
<div>Labels.vue</div>
</template>
<script lang="ts">
export default {
name: 'Labels'
};
</script>
<style lang="scss" scoped>
</style>
Money.vue
代码语言:javascript复制<template>
<div>Money.vue</div>
</template>
<script lang="ts">
export default {
name: 'Money'
};
</script>
<style lang="scss" scoped>
</style>
Statistics.vue
代码语言:javascript复制<template>
<div>Statistics.vue</div>
</template>
<script lang="ts">
export default {
name: 'statistics'
};
</script>
<style lang="scss" scoped>
</style>



这样一来,我们就成功的使用路由来渲染页面。
初级导航栏 |
---|
App.vue
代码语言:javascript复制<template>
<div>
<router-view/>
<hr>
<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
代码语言:javascript复制<template>
<div>
<router-link to="/money">记账</router-link>
|
<router-link to="/labels">标签</router-link>
|
<router-link to="/statistics">统计</router-link>
</div>
</template>
App.vue
代码语言:javascript复制<template>
<div>
<router-view/>
</div>
</template>
Money.vue引入导航栏
代码语言:javascript复制<template>
<div>
Money.vue
<Nav/>
</div>
</template>
<script lang="ts">
import Nav from '@/components/Nav.vue';
export default {
name: 'Money',
components: {Nav},
};
</script>
<style lang="scss" scoped>
</style>
这样一来,谁想要使用导航栏,谁就引入Nav,但是还是有点点麻烦,可不可以全局引入?
全局引入 |
---|
main.ts
代码语言:javascript复制import Vue from 'vue';
import App from './App.vue';
import './registerServiceWorker';
import router from './router';
import store from './store';
// 此处导入Nav
import Nav from '@/components/Nav.vue';
Vue.config.productionTip = false;
// 此处全局引入Nav
Vue.component('Nav', Nav);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
然后我们就可以把,Money、Labels、Statistics中导入Nav删掉了。
代码语言:javascript复制<template>
<div>
Labels.vue
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Labels',
};
</script>
<style lang="scss" scoped>
</style>
代码语言:javascript复制<template>
<div>
Money.vue
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
</style>
代码语言:javascript复制<template>
<div>
Statistics.vue
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'statistics',
};
</script>
<style lang="scss" scoped>
</style>
404页面 |
---|
router/index.ts
代码语言:javascript复制import Vue from 'vue';
import VueRouter, {RouteConfig} from 'vue-router';
import Money from '@/views/Money.vue';
import Labels from '@/views/Labels.vue';
import Statistics from '@/views/Statistics.vue';
import NotFound from '@/views/NotFound.vue';
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: '/',
redirect: '/money'
},
{
path: '/money',
component: Money
},
{
path: '/labels',
component: Labels
},
{
path: '/statistics',
component: Statistics
},
{
path: '*',
component: NotFound
}
];
const router = new VueRouter({
routes
});
export default router;
创建NotFound组件
views/NotFound.vue
代码语言:javascript复制<template>
<div>
404 Not Found
<hr>
当前页面不存在,请检查url是否正确
<div>
<router-link to="/">返回首页</router-link>
|
<a href="#/">a标签返回首页</a>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'NotFound'
};
</script>
<style lang="scss" scoped>
</style>

这里有两种返回首页的方式,一种是router-link
另一种是a
标签,建议使用高端的,router-link
布局
千万不要在手机上,使用fixed定位,使用flex布局
Money页面布局 |
---|
Money.vue
代码语言:javascript复制<template>
<div class="nav-wrapper">
<div class="content">
Money.vue
</div>
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
.nav-wrapper {
border: 1px solid green;
display: flex;
flex-direction: column;
height: 100vh;
}
.content {
flex-grow: 1;
overflow: auto;
}
</style>
Labels页面布局 |
---|
Labels.vue
代码语言:javascript复制<template>
<div class="nav-wrapper">
<div class="content">
Labels.vue
</div>
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Labels',
};
</script>
<style lang="scss" scoped>
.nav-wrapper {
border: 1px solid green;
display: flex;
flex-direction: column;
height: 100vh;
}
.content {
flex-grow: 1;
overflow: auto;
}
</style>
会发现一个问题,我们写了重复的代码,CSS重复,这样的话如果有一天老板说,我们需要换布局,目前咱们才两个地方 重复,如果还有更多页面都是这样样式100个,1000个,10000个...改去吧。
Layout组件 & slot插槽
我与重复不共戴天...
创建Layout组件 |
---|
然后把上面代码,相同部分都拷贝过去。
在main.ts中全局引入layout
代码语言:javascript复制import Vue from 'vue';
import App from './App.vue';
import './registerServiceWorker';
import router from './router';
import store from './store';
import Nav from '@/components/Nav.vue';
import Layout from '@/components/Layout.vue';
Vue.config.productionTip = false;
Vue.component('Nav', Nav);
Vue.component('Layout', Layout);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
components/Layout.vue
代码语言:javascript复制<template>
<div class="nav-wrapper">
<div class="content">
<slot/> <!--此处就是插槽,可以传递引用的内容-->
</div>
<Nav/>
</div>
</template>
<script lang="ts">
export default {
name: 'Layout'
};
</script>
<style lang="scss" scoped>
.nav-wrapper {
border: 1px solid green;
display: flex;
flex-direction: column;
height: 100vh;
}
.content {
flex-grow: 1;
overflow: auto;
}
</style>
引用Layout组件 |
---|
Money.vue 引用Layout
代码语言:javascript复制<template>
<div>
<Layout>
<p>Money.vue</p>
</Layout>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
</style>
Labels.vue 引用Layout
代码语言:javascript复制<template>
<div>
<Layout>
<p>Labels.vue</p>
</Layout>
</div>
</template>
<script lang="ts">
export default {
name: 'Labels',
};
</script>
<style lang="scss" scoped>
</style>
Statistics.vue 引用Layout
代码语言:javascript复制<template>
<div>
<Layout>
<p>Statistics.vue</p>
</Layout>
</div>
</template>
<script lang="ts">
export default {
name: 'statistics',
};
</script>
<style lang="scss" scoped>
</style>

使用svg-sprite-loader引入icon
下载svg |
---|
使用iconfont.cn
搜索并下载svg

然后将svg放入项目中,在src/assets/下创建icons目录

引入svg |
---|
注意,刚才我们只是给他放到了项目的目录下,并没有引入,引入的话相对来说比较复杂。
很显然,直接引入svg是有点困难的,所以我们需要给TS加入一段配置
shims-vue.d.ts
代码语言:javascript复制// 在原本的内容下,加入下面内容
declare module '*.svg' {
const content: string;
export default content;
}
Nav.vue测试是否可以导入svg
代码语言:javascript复制<template>
<div>
<router-link to="/money">记账</router-link>
|
<router-link to="/labels">标签</router-link>
|
<router-link to="/statistics">统计</router-link>
</div>
</template>
<script lang="ts">
import x from '@/assets/icons/label.svg'
console.log(`这里是x:${x}`);
export default {
name: 'Nav'
};
</script>
<style lang="scss" scoped>
</style>

打印出了,svg的路径,不过这不是我们想要的,我们需要的是一个svg use的使用方法,此时我们需要svg sprite loader
安装svg sprite loader |
---|
MacBook-pro:morney driverzeng$ yarn add svg-sprite-loader -D
修改webpack配置文件 |
---|
我们会发现,mmp压根没有webpack配置文件,肿么办?
我们只能修改vue.config.js这个文件,把webpack语法翻译一下。
vue.config.js
代码语言:javascript复制const path = require('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons')
// config是Vue把webpack配置封装成了对象暴露给我们的接口
config.module
// 添加一个规则的名字,叫做svg-sprite
.rule('svg-sprite')
// 如果文件能匹配上下面的正则,则使用这个规则,就是以.svg结尾的
.test(/.svg$/)
// 因为我们不想让整个项目的svg都走这个规则所以,我们需要include包含指定的目录,只包含icons目录
.include.add(dir).end()
// 使用哪些loader,使用svg-sprite-loader,extract:false是不要把它解析出文件来
.use('svg-sprite-loader').loader('svg-sprite-loader').options({extract: false}).end()
// 使用svg-sprite-loader目录下的plugin
config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
// 排除其他的svg,只要不在icons目录下的svg都不走这个规则
config.module.rule('svg').exclude.add(dir)
}
}
重启服务,之后就会发现,这回打印出来的x就不一样了,并且在element中,多了svg的标签

然后我们就可以使用use
标签,使用svg
Nav.vue
代码语言:javascript复制<template>
<div>
<router-link to="/money">
<svg>
<use xlink:href="#label"/>
</svg>
记账
</router-link>
|
<router-link to="/labels">
标签
</router-link>
|
<router-link to="/statistics">统计</router-link>
</div>
</template>
<script lang="ts">
import x from '@/assets/icons/label.svg';
console.log(`这里是x:${x}`);
export default {
name: 'Nav'
};
</script>
<style lang="scss" scoped>
</style>

存在两个问题:
1.那如果我有100个icon文件就需要import 100次?
2.而且每次都需要写:xlink:href="#label"
?
git commit会报错
在此我们需要先解决一个问题,我们加完刚才那个webpack配置后,会出现eslint的报错,于是git commit就会报错。我们需要在开头加上 /* eslint-disable */
,然后重新 git add .
,git commit
/* eslint-disable */
const path = require('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons')
// config是Vue把webpack配置封装成了对象暴露给我们的接口
config.module
// 添加一个规则的名字,叫做svg-sprite
.rule('svg-sprite')
// 如果文件能匹配上下面的正则,则使用这个规则,就是以.svg结尾的
.test(/.svg$/)
// 因为我们不想让整个项目的svg都走这个规则所以,我们需要include包含指定的目录,只包含icons目录
.include.add(dir).end()
// 使用哪些loader,使用svg-sprite-loader,extract:false是不要把它解析出文件来
.use('svg-sprite-loader-mod').loader('svg-sprite-loader-mod').options({extract: false}).end()
.use('svgo-loader').loader('svgo-loader')
.tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end()
// 使用svg-sprite-loader目录下的plugin
config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
// 排除其他的svg,只要不在icons目录下的svg都不走这个规则
config.module.rule('svg').exclude.add(dir)
}
}
import一个目录问题解决
只需要两句话。
Nav.vue
代码语言:javascript复制<template>
<div>
<router-link to="/money">
<svg>
<use xlink:href="#label"/>
</svg>
记账
</router-link>
|
<router-link to="/labels">
标签
</router-link>
|
<router-link to="/statistics">统计</router-link>
</div>
</template>
<script lang="ts">
// import x from '@/assets/icons/label.svg';
// console.log(`这里是x:${x}`);
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../assets/icons/',true,/.svg$/));} catch (error) {console.log(error);}
export default {
name: 'Nav'
};
</script>
<style lang="scss" scoped>
</style>

只需要两句话,三个全部导入进去了。
封装icon组件
首封Icon |
---|
新建一个components/Icon.vue,提取重复内容
代码语言:javascript复制<template>
<svg>
<use xlink:href="#label"/>
</svg>
</template>
<script lang="ts">
const 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'
};
</script>
<style lang="scss" scoped>
</style>
引用Icon |
---|
main.ts 设置全局引用
代码语言:javascript复制import Vue from 'vue';
import App from './App.vue';
import './registerServiceWorker';
import router from './router';
import store from './store';
import Nav from '@/components/Nav.vue';
import Layout from '@/components/Layout.vue';
import Icon from '@/components/Icon.vue';
Vue.config.productionTip = false;
Vue.component('Nav', Nav);
Vue.component('Layout', Layout);
Vue.component('Icon', Icon);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
Nav.vue
代码语言:javascript复制<template>
<div>
<router-link to="/money">
<Icon/>
记账
</router-link>
|
<router-link to="/labels">
<Icon/>
标签
</router-link>
|
<router-link to="/statistics">
<Icon/>
统计
</router-link>
</div>
</template>
<script lang="ts">
// import x from '@/assets/icons/label.svg';
// console.log(`这里是x:${x}`);
import Icon from '@/components/Icon.vue';
export default {
name: 'Nav',
components: {Icon}
};
</script>
<style lang="scss" scoped>
</style>

怎么样能让三个是不一样的呢,给Icon添加一个外部属性。
外部属性props |
---|
Icon.vue
代码语言:javascript复制<template>
<svg>
<!-- 我们可以从后端传递name过来,因为name要加上#,直接加会报错,所以当做字符串 -->
<use :xlink:href="'#' name"/>
<!-- v-bind可以省略 -->
<!-- use v-bind:xlink:href="'#' name"/-->
</svg>
</template>
<script lang="ts">
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('../assets/icons/',true,/.svg$/));} catch (error) {console.log(error);}
export default {
props:['name'],
name: 'Icon'
};
</script>
<style lang="scss" scoped>
</style>
引用Icon传递props |
---|
Nav.vue
代码语言:javascript复制<template>
<div>
<router-link to="/money">
<Icon name="money"/>
记账
</router-link>
|
<router-link to="/labels">
<Icon name="label"/>
标签
</router-link>
|
<router-link to="/statistics">
<Icon name="statistics"/>
统计
</router-link>
</div>
</template>
<script lang="ts">
// import x from '@/assets/icons/label.svg';
// console.log(`这里是x:${x}`);
import Icon from '@/components/Icon.vue';
export default {
name: 'Nav',
components: {Icon}
};
</script>
<style lang="scss" scoped>
</style>

修改Icon样式 |
---|
让icon跟字体一样大小,1em
Icon.vue
代码语言:javascript复制<template>
<svg class="icon">
<use :xlink:href="'#' name"/>
</svg>
</template>
<script lang="ts">
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {
importAll(require.context('../assets/icons/', true, /.svg$/));
} catch (error) {
console.log(error);
}
export default {
props: ['name'],
name: 'Icon'
};
</script>
<style lang="scss" scoped>
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

优化样式
Nav.vue |
---|
<template>
<nav>
<router-link to="/money" class="item">
<Icon name="money"/>
记账
</router-link>
<router-link to="/labels" class="item">
<Icon name="label"/>
标签
</router-link>
<router-link to="/statistics" class="item">
<Icon name="statistics"/>
统计
</router-link>
</nav>
</template>
<script lang="ts">
// import x from '@/assets/icons/label.svg';
// console.log(`这里是x:${x}`);
import Icon from '@/components/Icon.vue';
export default {
name: 'Nav',
components: {Icon}
};
</script>
<style lang="scss" scoped>
nav {
display: flex;
flex-direction: row;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
> .item {
padding: 2px 0;
width: 33.33333%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.icon {
width: 32px;
height: 32px;
}
}
}
</style>
App.vue |
---|
<template>
<div>
<router-view/>
</div>
</template>
<style lang="scss">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
line-height: 1.5;
}
a {
text-decoration: none;
color: inherit;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

路由激活
点击链接变色 |
---|
active-class的使用
Nav.vue
代码语言:javascript复制<template>
<nav>
<router-link to="/money" class="item" active-class="selected">
<Icon name="money"/>
记账
</router-link>
<router-link to="/labels" class="item" active-class="selected">
<Icon name="label"/>
标签
</router-link>
<router-link to="/statistics" class="item" active-class="selected">
<Icon name="statistics"/>
统计
</router-link>
</nav>
</template>
<script lang="ts">
// import x from '@/assets/icons/label.svg';
// console.log(`这里是x:${x}`);
import Icon from '@/components/Icon.vue';
export default {
name: 'Nav',
components: {Icon}
};
</script>
<style lang="scss" scoped>
nav {
display: flex;
flex-direction: row;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
> .item {
padding: 2px 0;
width: 33.33333%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.icon {
width: 32px;
height: 32px;
}
}
> .item.selected {
background: #ffb139;
box-shadow: 0 0 10px rgba(0, 0, 0, 1);
border-radius: 6%;
}
}
</style>

使用淘宝meta vp
查找淘宝meta vp |
---|
淘宝手机网站:TP
点开F12开发者工具,在Elment中
标签下找到meta vp
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
引用meta vp |
---|
public/index.html
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
svg的bug
svg的fill |
---|
如果svg文件中,有fill,那么svg的颜色是无法更改的,他本身就有颜色。

所以我们需要删除svg的fill属性,那么就需要使用一个svgo-loader
的工具
修改vue.config.js |
---|
/* eslint-disable */
const path = require('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons')
// config是Vue把webpack配置封装成了对象暴露给我们的接口
config.module
// 添加一个规则的名字,叫做svg-sprite
.rule('svg-sprite')
// 如果文件能匹配上下面的正则,则使用这个规则,就是以.svg结尾的
.test(/.svg$/)
// 因为我们不想让整个项目的svg都走这个规则所以,我们需要include包含指定的目录,只包含icons目录
.include.add(dir).end()
// 使用哪些loader,使用svg-sprite-loader,extract:false是不要把它解析出文件来
.use('svg-sprite-loader-mod').loader('svg-sprite-loader-mod').options({extract: false}).end()
// 使用 svgo-loader
.use('svgo-loader').loader('svgo-loader')
// 删除fill属性
.tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end()
// 使用svg-sprite-loader目录下的plugin
config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
// 排除其他的svg,只要不在icons目录下的svg都不走这个规则
config.module.rule('svg').exclude.add(dir)
}
}
安装svgo-loader |
---|
MacBook-pro:morney driverzeng$ yarn add --dev svgo-loader