记账项目总结
使用vue router
文章目录
const routes: Array<RouteConfig> = [
{
path:'/',
redirect:'/money'
},
{
path:'/money',
component: Money
},
{
path:"/labels",
component:Labels
},
{
path:'/statistics',
component:Statistics
},
{
path:'/labels/edit/:id',
component:EditLabel
},
{
path:"*",//当路径不是上面几个时显示404页面
component:NotFound
}
];
在这里我们设置好路由根据不同的路径进入到不同的组件
同时我们需要在 main.js 里面对 router 进行初始化
new Vue({
router,
render: h => h(App)
}).$mount('#app');
然后再 App.vue里面设置需要显示的地方
<template>
<div id="app">
<router-view/>//这里就是我们组件显示的地方,当输入对应的路径时就会在里显示对应的组件内容
</div>
</template>
使用slot插槽
在进行导航兰设置时我发现我的css重复了很多遍,作为始终坚持着与重复不共戴天的信念的我是坚决不能忍受这一种情况的,于是我使用了vue 的插槽功能
在这里我先设置了一个 Layout.vue 组件来放插槽
<div class="nav-wrapper">
<div class="content" :class="classPrefix &&`${classPrefix}-content`">
<slot /> /*相当于<p>我是插槽里面的内容</p>*/
</div>
<Nav/>
这里的slot就是用来存放我们传入的内容的,那么从哪里来传入呢
当我们使用 Layout.vue组件时,里面的内容就是我们这里的 slot的内容
例如:
<Layout>
<p>我是插槽里面的内容</p>
</Layout>
这里我们就会将所有的Layout里面呢内容全部传到 slot , 包括标签
vue里面引入svg
我们都知道在引入svg时是不可以直接使用的
如果我们直接使用import引入会直接报错,在这里我们就需要做一些配置了
首先在一个以 .d.ts 结尾的文件里面添加下面的代码
declare module "*.svg"{
const content:string;
export default content;
}
这样就不会报错了,但是到这里我们都还不可以直接使用
这里我们导入的只是一个路径,并不是我们想要的svg文件,这里我们就需要一个loader将这个路径里面的内容转化为我们想要的svg
这里我们需要先安装这个loader
npm install svg-sprite-loader -D
#via yarn
yarn add svg-sprite-loader
下载之后我们要在vue里面配置这个loader
在vue.config.js里面添加下面的代码
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$/)
.include.add(dir).end()
.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的地方添加svg标签就好了
如下
<svg>
<use xlink:href = '#xxx'/>//xxx是想要引入的svg的名字
</svg>
当然上面使用这个svg时我们是需要引入的但是呢当svg比较多时就会这就会成为一件很繁琐的事情,那么有没有什么方法可以让我们一次全部把所有的svg引入呢,这里我们可以直接引入一个Icons目录
批量引入svg
我们只需要添加下面的代码添加到你想要引入到的组件就可以直接引入一个存放svg的目录
let importAll = (requireContext:__WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try{importAll(require.context('../assets/icons',true,/\.svg$/))
}catch{ console.log(Error);}
svgo-loader删除fill
在使用svg时我们会遇到一个坑,那就是当svg自带有fill属性时我们通过css是无法改变它的颜色的
所以我们需要将svg自带的fill属性删掉,但是当svg比较多是,一个一个的删就比较麻烦了,所以我们就需要批量的删除svg的自带的fill属性
这里我们需要先安装svgo-loader
npm install svgo-loader -D
#via yarn
yarn add --dev svgo-loader
然后在vue.config.ts里面加入下面的代码
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$/)
.include.add(dir).end()
.use('svg-sprite-loader').loader('svg-sprite-loader').options({extract:false}).end()
//删除svg自带的fill属性
.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)
}
}
用TS实现money组件准备
这里我为什么要用TypeScript呢,这里我看列举一下他的三大好处:
用TypeScript的三大好处
- 类型提示:更加智能的类型提示,当我们把鼠标浮到一个变量或则函数上面时,TypeScript会提示我们变量的类型或则是函数的参数以及返回值
- 编译时报错:还没有运行时就知道自己写错了
- 类型检查:无法点出错误的属性
写vue单文件组件的三种方式
//用js对象
export default {data,props,methods,created,...}
//用js类
@Component
export default class xxx extends Vue{
xxx = 'hi'
}
//用JS类
@Component
export default class xxx extends Vue{
@Prop(Number) num :number |undefined
xxx:string = 'hi'
}
将数据保存到 LocalStorage
考虑到每次刷新之前输入的数据就会消失,因此这里我决定采用LocalStorage来将数据保存到本地
//保存数据,考虑到localStorage只能保存字符串,因此这里我转换了一下
window.localStorage.setItem(localStorageKeyNameTag, JSON.stringify(state.tagList))
//获取数据,
state.tagList = JSON.parse(window.localStorage.getItem(localStorageKeyNameTag) || '[]');
使用ID生成器
在保存tag因为一开始我是直接将tag的类型设置为{id:string,name:string}这里为了方便当时我直接将name当作了它的id为了避免重复的情况,应该每一个tag都拥有一个单独的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
使用全局数据管理Store(手写Store)
全局数据管理的优点
- 解耦:将所有数据相关的逻辑放入store(也就是MVC中的Model,换了个名字而已)
- 数据读写更加方便:任何组件不管咋哪里,都可以直接读写数据
- 控制力更强:组件对数据的读写只能使用store提供的API进行
手写store
这是我的store.ts
import recordStore from '@/store/recordStore';
import tagStore from '@/store/tagStore';
const store = {
...recordStore,
...tagStore,
};
export default store;
recordStore.ts
import clone from '@/lib/clone';
const localStorageKeyName = 'recordList';
const recordStore={
recordList:[] as RecordItem[],
fetchRecords(){
this.recordList = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]') as RecordItem[];
return this.recordList
},
createRecordList(record: RecordItem) {
const record2:RecordItem = clone(record)
record2.creatAt = new Date();
this.recordList.push(record2);
this.saveRecords()
},
saveRecords(){
window.localStorage.setItem(localStorageKeyName, JSON.stringify(this.recordList));
}
}
recordStore.fetchRecords()
export default recordStore;
tagStore.ts
import createId from '@/lib/createId';
const localStorageKeyName = 'tageList';
const tagStore = {
tagList: [] as Tag[],
fetchTag() {
this.tagList = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]');
return this.tagList;
},
findTag: function (id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
createTag (name: string) {
const names = this.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
alert('重复了!')
return 'fail';
}
const id = createId().toString();
this.tagList.push({id,name: name});
this.saveTags();
alert('添加成功!')
return 'success'
},
removeTag(id: string) {
let index=-1
for (let i = 0; i < this.tagList.length; i++) {
if (this.tagList[i].id === id) {
index = i;
}
}
if (index !==-1) {
this.tagList.splice(index, 1);
this.saveTags();
return 'success';
} else {
return 'not found';
}
},
updateTag (id: string, name: string){
const idList = this.tagList.map(item => item.id);
if (idList.indexOf(id) >= 0) {
const names = this.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
return 'fail';
} else {
const tag = this.tagList.filter(item => item.id === id)[0];
tag.name = name;
this.saveTags();
return 'success';
}
} else {
return 'not found';
}
},
saveTags() {
window.localStorage.setItem(localStorageKeyName, JSON.stringify(this.tagList));
}
}
tagStore.fetchTag();
export default tagStore;
使用Vuex
import Vue from 'vue'
import Vuex from 'vuex'
import clone from '@/lib/clone';
import createId from '@/lib/createId';
const localStorageKeyNameRecord = 'recordList';
const localStorageKeyNameTag = 'tageList';
Vue.use(Vuex)
type rootState = {
currentTag?:Tag,
recordList: RecordItem[],
tagList: Tag[]
creatTagError:'duplicated' | 'success' |null
updateTagError:'duplicated' | 'success' |null
}
const store = new Vuex.Store({
state: {
currentTag:undefined,
creatTagError: null,
updateTagError:null,
recordList:[] as RecordItem[],
tagList: [] as Tag[],
} as rootState,
mutations: {
fetchRecords(state){
state.recordList = JSON.parse(window.localStorage.getItem(localStorageKeyNameRecord) || '[]') as RecordItem[];
return state.recordList
},
createRecordList(state,record: RecordItem) {
const record2:RecordItem = clone(record)
record2.creatAt = new Date().toISOString();
state.recordList.push(record2);
store.commit('saveRecords')
},
setCurrentTag(state,id:string){
state.currentTag = state.tagList.filter(t => t.id === id)[0];
},
saveRecords(state){
window.localStorage.setItem(localStorageKeyNameRecord, JSON.stringify(state.recordList));
},
fetchTag(state) {
state.tagList = JSON.parse(window.localStorage.getItem(localStorageKeyNameTag) || '[]');
if(!state.tagList||state.tagList.length === 0){
store.commit('createTag','衣')
store.commit('createTag','食')
store.commit('createTag','住')
store.commit('createTag','行')
}
},
createTag (state,name: string) {
state.creatTagError=null
const names = state.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
state.creatTagError = 'duplicated'
return
}
const id = createId().toString();
state.tagList.push({id,name: name});
store.commit('saveTags')
state.creatTagError = 'success'
},
removeTag(state,id: string) {
let index=-1
for (let i = 0; i < state.tagList.length; i++) {
if (state.tagList[i].id === id) {
index = i;
}
}
if (index !==-1) {
state.tagList.splice(index, 1);
store.commit('saveTags')
alert('删除成功!')
}
},
updateTag(state,tag:Tag){
state.updateTagError = null
const idList = state.tagList.map(item => item.id);
if (idList.indexOf(tag.id) >= 0) {
const names = state.tagList.map(item => item.name);
if (names.indexOf(tag.name) >= 0) {
state.updateTagError = 'duplicated'
} else {
const tag1 = state.tagList.filter(item => item.id === tag.id)[0];
tag1.name = tag.name;
store.commit('saveTags')
state.updateTagError ='success'
}
}
},
saveTags(state) {
window.localStorage.setItem(localStorageKeyNameTag, JSON.stringify(state.tagList));
}
}
})
store.commit('fetchTag')
store.commit('fetchRecords')
export default store;
这里不得不说一下vuex的一个不友好的地方,那就是$store.commit()没有返回值,因此为了能够知道我的commit的状态,我只能在state里面来设置一个变量来记录。
当调用后判断state里面这个方法对应的值就可以判断commit的状态了
使用deep语法
在我将tab组件封装成通用组件的时候,发现我无法在其他组件里面改变tab里面标签的样式,这是因为我对tab里面的SCSS添加了scoped
因此我想在其他组件里面改变tab的样式就需要用到deep语法,使用也很简单 如下
<Tab class = 'x'/>
<style scoped lang = 'scss'>
.x ::v-deep li{
color:red;
}
</style>
dayjs
js自带的Date我们基本不会使用
而moment.js体积又太大了,
所以这里我选用了更加轻量化的dayjs
安装:
npm install dayjs --save
介绍几个简单api
获取当前时间
const now = dayjs()
返回克隆的对象并且减去指定的时间
dayjs().subtract(7,'year')
返回克隆的Day.js对象,并将其设置为一个时间单位的开始。
dayjs().startOf('year')
通过一个毫秒数的整数值来创建Day.js
dayjs(1318781876406)
更多的API去
查看