【项目-喵内记账-meoney-01】导航栏
大纲链接 §
[toc]
项目思路 ⇧
- 根据设计稿,使用
Vue Router
分别导航三个页面 Money.Vue
Labels.vue
Statistics.vue
- 提取
Layout.vue
组件,作为整体布局模板
- 后期根据修改的设计稿重构页面
使用Vue Router
⇧
- 设计稿:Figma
- 根据设计稿页面,确定路由页面
url
使用 Vue Router
添加路由,本地版默认使用 哈希模式
#/money
记账(默认页面)重定向页面redirect: Money
#/labels
标签编辑页面
#/statistics
统计页面
/
默认页面 记账页面,重定向路径redirect: '/money'
(注意是和路径path: '/money'
相同,而不是组件名Money
)
#/404
保底页面
- 添加代码
router/index.ts
添加router
,配置4个路径相对应组件到src/views/
路径下(路径名小写,组件名大写)
- 初始化组,将
router
传给new Vue()
:
- 在路径中
src/router/index.ts
自动直接识别index.ts
文件
import router from './router'
相当于import router from './router/index.ts'
new Vue({router, store, render: h => h(App)}).$mount('#app')
- 在
App
组件里用<router-view>/
给出router
渲染区域
- 每个组件里都必须写
<script></script>
标签,即使不包含逻辑代码,否则不会被引用到
@/router/index.ts
1
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
28
29
30
31
32
33
|
import Vue from 'vue';
import VueRouter, {RouteConfig} from 'vue-router';
import Home from '@/views/Home.vue';
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;
|
将Nav
组件做成全局组件 ⇧
并不是所有页面都需要展示导航,比如404页面,抽成全局组件,按需引入;
不要在App.vue
中写任何有关页面展示的详细代码,只引入组件和负责渲染根节点
- 思路分析:
- 在
App.vue
还是每个组件中写<Nav/>
—有的页面不需要展示<Nav/>
,会添加冗余的代码去处理<Nav/>
逻辑
- 全局引入
<Nav/>
组件还是局部引入—三个页面都需要展示<Nav/>
,统一在全局引入
Vue Router
404 页面 ⇧
样式注意点 ⇧
用 Fixed
还是用 Flex
布局
- 不要在手机上使用
fixed
定位;不要在手机上使用fixed
定位;不要在手机上使用fixed
定位;
- 移动端的
flex
定位的坑相对少,解决方案成熟
vue处理隔离样式scoped
,类加上属性选择器,随机字符串作为属性名
组件名-作用 命名 样式
App.vue
的样式不能加scoped
内容容器样式 ⇧
Layout
组件 & slot
插槽 复用UI结构 ⇧
重复就是Bug - 与重复不共戴天
Money.vue
1
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
28
29
|
<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 red;
display: flex;
flex-direction: column;
height: 100vh;
.content {
border: 1px solid cornflowerblue;
flex-grow: 1;
overflow: auto;
}
}
</style>
|
- 抽取
Money.vue
会复用相同结构的组件为Layout.vue
Layout.vue
1
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
28
|
<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 red;
display: flex;
flex-direction: column;
height: 100vh;
.content {
border: 1px solid cornflowerblue;
flex-grow: 1;
overflow: auto;
}
}
</style>
|
- 使用slot 插槽
- 在
Layout
组件中获取各引用它的组件的数据,即 内容分发,将 <slot/>
元素作为承载分发内容的出口
- 模板组件中的
<slot/>
部分将会被替换为引用这个模板的组件双标签中的部分
<yourComponent>replace Part for Slot</yourComponent>
- 插槽内可以包含任何模板代码,包括 HTML
Money.vue
简化为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<template>
<div class="nav-wrapper">
<Layout>
<p>Money.vue</p>
</Layout>
</div>
</template>
<script lang="ts">
export default {
name: 'Money',
};
</script>
<style lang="scss" scoped>
</style>
|
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的
使用svg-sprite-loader
引入icon
⇧
- 使用
svg-sprite-loader
- 安装
svg-sprite-loader
:
yarn add svg-sprite-loader -D
- 配置
vue.config.js
shims-vue.d.ts
中添加svg
声明
- 将在
iconfont
下载的svg
图标引入到Nav
组件中
vue.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
const path = require('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons') // 确定目录
config.module.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后
.rule('svg-sprite')
.test(/\.svg$/)// .test(/\.(svg)(\?.*)?$/)
.include.add(dir).end() // 指定 仅包含 icons 的目录
.use('svg-sprite-loader').loader('svg-sprite-loader')
.options({extract: false}).end() // 不解析出文件
config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
config.module.rule('svg').exclude.add(dir)
}
}
|
shims-vue.d.ts
1
2
3
4
5
6
7
8
9
|
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
declare module '*.svg' {
const content: string;
export default content;
}
|
Nav.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<template>
<div>
<nav>
<router-link to="/money">记账</router-link>
|
<router-link to="/labels">标签</router-link>
|
<router-link to="/statistics">统计</router-link>
</nav>
</div>
</template>
<script lang="ts">
import x from '@/assets/icons/bills.svg';
console.log(x);
export default {
name: 'Nav'
};
</script>
|
svg-sprite-loader
会导致 css
的 import ~@
在 WebStorm
里报错
Eslint
报错如何解决 ⇧
鸵鸟
- 在IDE和命令行中关闭eslint提示
- 或者在
vue.config.js
里代码顶端添加/* eslint-disable */
配置.eslintrc.js
,关闭相应的检查
1
2
3
4
5
6
7
8
|
module.exports = {
...
rules: {
...
'@typescript-eslint/no-var-requires': 0,
...
},
}
|
代码
svg-sprite-loader
相关文章
如何 import
一个目录,引入所有svg
图标文件 ⇧
Nav.vue
的script
中(后续将Icon
的逻辑提取到单独组件中)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
requireContext.keys().forEach(requireContext);
};
// 指定目录 只能用相对路径 不支持@别名路径
// 使用importAll加载所有的svg
importAll(require.context('../assets/icons/', true, /\.svg$/));
try {
importAll(require.context('../assets/icons/', true, /\.svg$/));
} catch (error) {
console.log(error);
}
|
封装 Icon.vue
组件 ⇧
样式 symbol引用
1
2
3
4
5
6
7
8
|
<style lang="scss">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
|
使用<svg></svg>
标签
1
2
3
|
<svg class="icon">
<use xlink:href="<NAME>"/>
</svg>
|
向父组件传递点击事件
1
2
3
|
<svg class="icon" @click="$emit('click', $event)">
<use :xlink:href="<NAME>"/>
</svg>
|
Icon.vue
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<template>
<svg class="icon" @click="$emit('click', $event)">
<use :xlink:href="'#' + name"/>
</svg>
</template>
<script lang="ts">
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
requireContext.keys().forEach(requireContext);
};
// 指定目录 只能用相对路径 不支持@别名路径
// 使用importAll加载所有的svg
importAll(require.context('../assets/icons/', true, /\.svg$/));
try {
importAll(require.context('../assets/icons/', true, /\.svg$/));
} catch (error) {
console.log(error);
}
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
@Component
export default class Numpad extends Vue {
// 动态加载
@Prop({default: ''}) ['name']: string;
}
</script>
<style lang="scss" scoped>
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
|
单一逻辑原则,抽出importAllSvg
方法
src/lib/importAllSvg.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
export default function importAllSvg() {
// 批量导入 svg
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
requireContext.keys().forEach(requireContext);
};
// 指定目录 只能用相对路径 不支持 @ 别名路径
// 使用 importAll 方法加载所有的 *.svg 文件
importAll(require.context('../assets/icons/', true, /\.svg$/));
// 捕获报错
try {
importAll(require.context('../assets/icons/', true, /\.svg$/));
} catch (error) {
console.log(error);
}
}
|
重构src/components/Icon.vue
1
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
28
29
|
<script lang="ts">
import importAllSvg from '@/lib/importAllSvg';
import {Component, Prop, Vue} from 'vue-property-decorator';
importAllSvg();
@Component
export default class Icon extends Vue {
// 动态引入 svg name
@Prop({default: ''}) ['name']: string;
}
</script>
<template>
<svg class="icon" @click="$emit('click', $event)">
<use :xlink:href="'#' + name"/>
</svg>
</template>
<style lang="scss" scoped>
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
|
导航栏样式与路由激活样式active-class
⇧
- 不写具体高度值
- 链接默认颜色设为
color: inherit;
active-class
的使用(路由激活) ⇧
激活一个路由时,将对应的图标变为高亮
- 标签中添加
active-class
属性,
- 当前元素处于被选中状态时,可以看到动态路由匹配
router-link
中自动添加active-class
属性
- 在
router-link
标签上加class="item"
和active-class="selected"
属性
- 添加
.item.selected
的样式
使用 svgo-loader
插件 删除 svg 标签的 fill
填充色属性 ⇧
可以记录为项目遇到的困难 bug 踩坑
- 自动批量去除字体图标的填充色
- 安装
yarn add --dev svgo-loader@2.2.1
- 配置
vue.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
const path = require('path')
module.exports = {
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons') // 确定目录
config.module
.rule('svg-sprite')
.test(/\.svg$/) // .test(/\.(svg)(\?.*)?$/)
.include.add(dir).end() // 指定 仅包含 icons 的目录
.use('svg-sprite-loader').loader('svg-sprite-loader')
.options({extract: false}).end() // 不解析出文件
.use('svgo-loader').loader('svgo-loader') // SVG优化插件
.tap(options =>({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end() // 去除 SVG填充色属性
config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
config.module.rule('svg').exclude.add(dir)
},
}
|
- 无视
SVG
自带填充色,都由css控制
- 注意
svgo-loader
的版本"svgo-loader": "2.2.1"
- 如果使用目前最新版
"svgo-loader": "3.0.0"
的编译错误的话,就回退一个版本
后续修复vue.config.js
:本地预览 dist 目录发现 JS 路径错误 ⇧
使用 yarn build
得到 dist
目录后,再用 serve -s dist
然后在本地浏览器打开 http://localhost:5000
会看到:
- 这是因为在本地环境,这个
JS
的路径不对
- 虽然在
GitHub Pages
里,这个JS
的路径其实是对的
- 看
vue.config.js
,其实已经考虑了这个问题
vue.config.js
会在 production
环境(也就是 GitHub Pages
上)使用 /morney-3-website/
作为路径前缀
- 在本地使用
/
作为路径前缀:
- 但实际情况是,本地的
dist
使用了 /morney-3-website/
,正确的前缀应该是 /
- 为什么
vue.config.js
考虑了这个问题,还是会出现这个问题呢?
怎么解决这个问题
步骤如下:
yarn add cross-env
(这个 cross-env
是Windows
用户必须的,其他系统的用户装了它也没事,不会有任何副作用)
- 在
package.json
的script
字段里添加
"build:dev":"cross-env NODE_ENV=development yarn build"
注意行尾的逗号
- 使用
yarn build:dev
得到的 dist
即可在本地用 serve
预览
- 使用
yarn build
得到的 dist
即可在 GitHub Pages
上正常预览
vue.confi.js
1
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
|
// ESLint ignore first line
/* eslint-disable */
const path = require('path')
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/meowney-0-website/'
: '/',
lintOnSave: false,
chainWebpack: config => {
const dir = path.resolve(__dirname, 'src/assets/icons') // 确定目录
config.module
.rule('svg-sprite')
.test(/\.svg$/) // .test(/\.(svg)(\?.*)?$/)
.include.add(dir).end() // 指定 仅包含 icons 的目录
.use('svg-sprite-loader').loader('svg-sprite-loader')
.options({extract: false}).end() // 不解析出文件
.use('svgo-loader').loader('svgo-loader')
.tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end()
config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录
*/
},
// pluginOptions: {}
}
|
提取公共样式 ⇧
- 创建目录
src/assets/style/global.scss
- 无需将scss文件变为css文件,
webpack
自动编译
1
2
3
4
5
|
<html lang="zh-CN">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,viewport-fit=cover">
...
</html>
|