我的GitHub主页~ 欢迎大家 Star⭐⭐⭐
「观感度:🌟🌟🌟🌟🌟」
「口味:红烧猪蹄」
「烹饪时间:15min」
前言
有人问我为什么叫 富贵记账?
古训:富贵三更梦,平安两字金;心安即是家,何处不自在!
一、项目介绍
富贵记账是一款极简的记账应用,也是一款基于 Vue、VueRouter、Vuex、TypeScript、ECharts 的单页面应用,UI模仿业内知名的鲨鱼记账,实现原创,期间遇到了很多weboack和TypeScript方面的问题,解决过程我这里记录下来。源码几乎完全用TS实现,用到了装饰器语法。 源码开源 预览地址
二、项目笔记
2.1 安装脚手架的问题
安装脚手架使用了yarn,但是我发现我的 yarn 不能使用taobao.org 源
修复方法就是清楚npm缓存
npm cache clean --force
通过修改 administrator 文件夹中的vuer 的 packageManege,让他为yarn,即可切换使用 yarn 来下载依赖。
2.2 在JS或TS中使用@导入
-
在
js或ts中,有时候需要在一个文件中引入其他的组件,那么需要思考文件的目录层级,这个时候可以使用@,@代表的就是src,它会自动的去找到对应的路径; -
例如我在
view文件夹的某个组件中导入component文件夹的某个组件。@是src的别名
importbase.vuefrom'@.com/onent/home.vue'//需要加后缀名,否则报错
2.3 在css或者scss中使用@
在css或者ss中同样也可以使用@剌弋表src,一般公共的样式在asset文件夹中的;
- 在css中:
@import '@/assets/base.css'
- 在scss中:
@import '~@/assets/base.scss'
//因为scss的规定,得加一个波浪线;
2.4 什么文件需要卸载router里面
在一般项目中,作为视图页面,也就是在 views 文件夹的,都是需要在router 里声明如何调整到它那么页面上去的。
而对于组件,也就是在 component 件夹里面,那个组件需要用到它就直接导入就行了,不用在 router 里面声明。
2.5 引入全局组件还是子组件的抉择
刚开始我要引入一个全局组件,但是直接在App.vue引入,有些页面并不需要这个组件,就改为另外一种,在每个需要导入组件的页面都要写一遍,但样重复的代码太多了,于是就改用Vue.component('Nav',Nav)在 main.js 全局导入,这样在各个页面就可以不用导入,直接使用这个组件了。
2.6 Router的404页面制作
路由的查找规则是先去查找route上面已经的页面,然后剩余的所有页面统称呼为 *
{
path:'*',
component:NotFound
}
2.7 写 Vue 组件的三种方式(单文件组件)
* 用 JS 对象
export default { data, props, methods, created, ...}
* 用 TS 类
@Component
export default class XXX extends Vue{
xxx: string = 'hi';
@Prop(Number) xxx: number|undefined;
}
@Component
export default class XXX extends Vue{
xxx: string = 'hi';
@Prop(Number) xxx: number|undefined;
}
* 用 JS 类
@Component
export default class XXX extends Vue{
xxx = 'hi'
}
@Component
export default class XXX extends Vue{
xxx = 'hi'
}
2.8 TypeScript 好处
- 类型提示:更智能的提示
- 编译时报错:还没运行代码就知道自己写错了
- 类型检查:无法点出错误的属性
2.9 怎么把JS代码改写为TS
先把<script>标签的类型由js改为ts,然后修改类型报错就好了!
2.10 使用TypeScript
2.11 在TS项目中引入svg
ts中引入svg 会报错
- 在 shims-vue.d.ts文件中
declare module '*.svg' {
const content: string;
export default content;
}
声明了这个类型之后,我们把它log 出来,发现是个字符串,而我们需要的是一个 svg. use的一个使用方法,我们就需要这个 loader
- 安装 svg-sprite-loader svg-sprite-loader 库的链接
npm install svg-sprite-loader -D
# via yarn
yarn add svg-sprite-loader -D
在 vue.config.js 文件中
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() // 包含 icons 目录
.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()
config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录
}
}
2.12 SVG无法更改颜色
1.原因解释
当从阿里图库中图标被添加至项目,如果编辑过项目图标的颜色或者图标本身是有颜色的,那么在通过
symbol 获取图标时会在 svg 的 path 中增加属性,导致无法更改颜色,如果需要动态修改颜色,需要从
新添加该图标(本身图标无色),获取在symbol 的js文件中删除 fill属性。
2.有三个解决方法:
- 打开每张svg,并将其内部的輔属性删除;
- 通过color属性改变svg的颜色,换为使用改变svg的颜色;
- 安装
svgo-loader,自动化的删除attrs为fill的属性,这样就可以通过co|or来修改颜色,而不是fill 来修改颜色。
2.13 Type切换的显示条
虚线必须要通过伪类添加,不然会出现抖动
2.14 Class实现父组件控制子组件的样式
我们通常在写组件的时候,把组件自己的样式都写到了组件内部,那父亲组件也需要控制呢? 这时候就使用class传值
---Money.vue--
<NumberPad class-prefix='money'/>
<style>
.money-numberPad{
border: 1px solid red;
}
.money-content{
border: 1px solid blue;
}
</style>
NumberPad 组件全部加载到的占位符那里,而NumberPad 属性 被传到组件里面;
---NumberPad.vue---
<template>
<div class="numberPad" :class="classPrefix && `${classPrefix}-numberPad`">
<div class="content" :class="classPrefix `${classPrefix}-content`">
<slot></slot>
</div>
</div>
</template>
export default{
name:"NumberPad",
props:["classPrefix"]
}
2.15 用TS 写 Vue 时,prop 的问题
---某组件.vue---
<script lang="ts">
import Vue from 'vue';
import {Component,Prop} from 'vue-property-decorator';
@Component //装饰器
export default class Types extends Vue {
@Prop(String) classPrefix?: string;
}
</script>
@Prop(String) classPrefix?: string; 这段Ts代码做了4件事:
- @Prop 首先告诉Vue,classPrefix 是外界传来的值,不是data里的值
- (String) 接着告诉Vue,classPrefix在运行时是 String类型
- classPrefix 是属性名
- string 最后冒号后面的 string 是告诉 TS classPrefix的编译时的类型 那么下次外界再给这个组件的 classPrefix 传值的时候,就必须传一个字符串,否则TS就会报错!
2.16 使用TS的强制类型转换
在js中,方法的第一个参数代表调用本身。
例如: add(evt){} // evt 代表调用add 这个方法的对象
而在TS中,需要声明类型:
input(evt:MouseEvent){ // 说明evt 是鼠标事件,点击的是鼠标
const button = (evt.target as HTMLButtonElement) //声明target 是元素
const input = button.textContent as string; //强制断言为string类型
}
2.17 .sync 和 $emit 的结合
2.18 Ts的类型声明
在 ts 代码中并不能直接创建一个object,需要先声明一个类型
type RecordItem = { //声明Record类的类型
id?: number;
tag: TagItem; //各自的类型如下
type: string;
note: string;
amount: number;
createAt?: Date;
// ? 问好 代表 这个 属性可以没有,而且他可以是类
//不是一个使用这个类的组件 都需要有这个属性,是可有可无的
}
export default class Money extends Vue{
record:Record={ // redord 的类型是 Record类 ,然后初始值为如下:
tags:[],
notes:'',
type:'+',zhj
amount:0
//既然 createAt 是可有可无的 ,用到的时候再写
}
}
record.createdAt= new Date()
//需要加的时候,加上就行了;
2.19 TS 中的 components
2.20 数据的深拷贝
数据的深拷贝一般借助 JSON.parse 和 JSON.stringify 的相互配合
saveRecord(state){
const record2:Record=JSON.parse(JSON.stringify('this.record')) //将record 深拷贝
record2.creadAt = new Date()
this.recordList.push(record2)
}
2.21 数据的存储问题
取数据:
recordList: JSON.parse(window.localStorage.getItem('record-list') || '[]')
存数据
saveRecord(state){
window.localStorage.setItem('record-list',JSON.stringify(state.recordList))
}
2.22 在TS中引入JS
首先在JS中将对象暴露出来,只能使用require,不能使用import
- export default xxx;
- const model = require('path').default
其他方法:
- export {xxx}
- const {xxx} = require('path')