初学 Vue 和 TypeScript,决定做一个简易的记账本来练手。
从自身需求出发,这个小项目实现以下几点功能:
- 记账:可以快速记录一笔账单的消费/收入来源(标签),选择这笔账单的时间,以及记录金额。
- 标签管理:可以对修改已有标签的名字,或者自定义新的标签。
- 查看账单:可以逐条展示所有的账单记录,也可以根据年份和月份来筛选出记录。同时添加统计图可以更直观地显示记录。 基于上述功能,共设计记账页(主页)、标签管理页、删除标签页、统计页四个页面。
本项目用 Vue Cli 搭建,用到了 Vue、Vue Router、Vuex、TypeScript、Sass 技术。
一些思路和总结
1. 添加底部导航,实现页面跳转
首先确定每个页面的 url
,然后添加 Vue Router
。每个页面对应各自的组件,用 redirect
重定向实现进入默认页面。
const routes: Array<RouteConfig> = [
{
path: '/',
redirect: '/money'
},
{
path: '/money',
component: Money
},
{
path: '/labels',
component: Labels
},
{
path: '/labels/edit/:type/:id', //传递参数 type 和 id
component: EditLabel
},
{
path:'/statistics',
component: Statistics
},
{
path: '*',
component: NotFound
}
]
const router = new VueRouter({
routes
})
在导航栏组件中添加 router-link
,并且添加 active-class
属性,设置链接激活时使用的 CSS 类名。以此来添加样式。
<nav>
<router-link to="/labels" class="item" active-class="selected">
<Icon name="tag" class="icon"/>
标签
</router-link>
<router-link to="/money" class="item" active-class="selected">
<Icon name="money"/>
记一笔
</router-link>
<router-link to="/statistics" class="item" active-class="selected">
<Icon name="statistics"/>
统计
</router-link>
</nav>
2. 引入 svg
从 iconfont 下载所需的 svg
图标。按照教程添加如下代码:
- 添加 HTML 代码
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>
- 添加样式
<style type="text/css">
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
- 引入
import '@/assets/icons/xxx.svg'
此时还是不能显示 svg, 发现是缺 svg-sprite-loader
。解决办法:
- 安装
svg-sprite-loader
yarn add svg-sprite-loader --dev (或者使用 npm)
- 配置
vue.config.js
const dir = path.resolve(__dirname, 'src/assets/icons') // icons 目录
config.module
.rule('svg-sprite')
.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) // 其他 svg loader 排除 icons 目录
以上的设置就可以顺利引入 svg 了。
- 但是在使用的过程中,会遇到 svg 的一个坑是:发现在 scss 中设置了 svg 的颜色后,依然无法生效。
首先确保样式没有写错,检查 svg 源文件后,发现有些 svg 自带
fill
属性,这个属性使得图标拥有一个默认的颜色且不可被更改。
有一个简单粗暴地方法是,把所有 svg 地 fill
属性手动移除, 就可以给 svg 添加自定义颜色了。
但是问题来了,如果项目中引入几百个图标,总不能靠手动一一删除,不太现实。所以还是要找一个更优的方案。最好可以在引入时就自动删除 svg 的 fill
属性。
经过我的一番搜索,终于发现了一个可行方案!解决方法如下:
首先下载 svgo-loader
yarn add svgo-loader --dev (或者使用npm)
在 vue.config.js
中添加以下两行:
.use('svgo-loader').loader('svgo-loader')
.tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end()
好了,这样就借助svgo-loader
自动删除所有引入的 svg 的 fill
属性了!
3. 关于整体布局
为了适配各种大小的手机页面,根据我的设计稿,采用一种有头部和底部导航栏,且上下固定中间可滑动显示的布局。所以用 Flex 布局来实现。 思路如下:
- 设置 body 的高度为 100vh;
- 给最外层 div 添加样式
display: flex;
flex-direction: column;
height: 100%;
- 给中间部分添加样式
flex:1;
overflow: auto;
效果如下:
4. 利用 localStorage 进行数据的存储
因为这只是一个本地版的 app,将数据存储到 localStorage
是一个比较好的选择。我用到的仅仅是 localStorage
存储数据和保存数据的用法。通过 window.localStorage
即可访问到。
localStorage.getItem("name"); //读取保存在localStorage对象里名为name的变量的值
localStorage.setItem("data", data); // 存储名字为name值为 data 的变量
localStorage
的更多用法可以参考 localStorage用法小总结。
5. 每个页面的逻辑实现
- 记账页 作为最主要的功能,所以这个页面也是这个 app 的主页。为了更好地模块化,将这一页面又分为不同的组件,分别负责一条记录的标签、类型(支出或收入)、备注、金额输入的数据逻辑以及样式处理。
父子组件之间数据的传递方式为:
- 父组件可以使用
props
把数据传给子组件。 - 子组件可以使用
$emit
触发父组件的自定义事件。 这样,在记账页面可以生成一条完整的账单记录。
- 标签管理页
读取
localStorage
中所有的标签并且显示出来。其中每一个标签可以点进进入标签编辑页,所以需要设置每一个标签为router-link
传递type
和id
(唯一标识每一个标签) 参数。 - 标签编辑页
通过
router-link
传递来的type
和id
可以从localStorage
中找到对应的标签对其进行操作。这里主要是删除和修改的功能。 - 统计页
获取到
localStorage
中的记录数据,进行展示。需要解决的是对所有的记录的筛选,引入了第三方组件datetime-picker
,根据选中的年月来展示对应的记录。还引入了echarts
柱状图,来更直观的展示数据。
6. 全局数据管理之 Vuex
考虑到多个页面都有对标签和记录数据的读取和更改操作,传参会非常繁琐且难以维护。所以采用 Vuex
对数据进行管理。这样做的好处是:
- 解耦:将所有数据相关的逻辑放入 store。
- 数据读写更方便:任何组件不管在哪里,都可以直接读写数据。
- 控制力更强:组件对数据的读写只能使用 store 提供的 API 进行。
我在 store 中主要实现了以下 API, 无非是对记录和标签的增删改查。供各组件进行调用。
Vue.use(Vuex);
const store = new Vuex.Store({
state: {},
mutations: {
fetchRecords() {},
createRecord() {},
saveRecords() {},
fetchTags() {},
createTag() {},
saveTags() {},
updateTag() {},
removeTag() {},
},
actions: {},
modules: {}
});
export default store;
7. 其他
- 在项目中引入 Vant 根据官网提示:
# 安装 vant
npm i vant -S
按需引入组件
# 安装插件
npm i babel-plugin-import -D
// 在 babel.config.js 中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
// 接着你可以在代码中直接引入 Vant 组件
import { Button } from 'vant';
根据文档即可快速上手,对数据进行更快捷的处理和展示。更多可查看 Vant文档
- 实现一个 ID 生成器
在对标签存储时,需要给每一个 标签添加一个独一无二的 ID,从而可以获取到对应的标签对其管理。于是我写了这么一个小工具。
let id:number = parseInt(window.localStorage.getItem('_idMax') || '0') || 0;
function createId() {
id++;
window.localStorage.setItem('_idMax', id.toString());
return id;
}
export default createId;
每次从 localStorage
中读取到最大 id
, 对其 +1 后返回,并且将新的 id
置为最大 id
,使得所有的 id
不会发生重复。
总结
除了以上整理的思路,项目中还有许多细节不再赘述。刚开始写项目还有许多不足之处需要改进。通过这个小项目让我对 Vue 有了深刻的理解和运用。也希望通过更多的运用精进自己的技术。