项目介绍
项目前的准备及项目构思
模块化
封装通用组件
@语法导入相关文件
CSS的变种形式
Vue中CSS的deep选择器
Vue中CSS类名的表驱动编程
使用VueRouter的active-class动态添加类名
使用svg-sprite-loader引入icon与封装icon组件
JavaSript组件
TypeScript组件
custom.d.ts怎么用
Vue单文件组件的三种写法
Layout组件&插槽
使用VueRouter
在TS组件里使用mixins
Vuex--全局数据管理
Vue修饰符
ISO8601和dayjs
使用localstorage存储数据
源代码github/gitee
该项目是一个
极简的记账应用
。是一款基于Vue
框架、VueRouter
、Vuex
、TypeScript
的单页面应用。
记账:金额、标签、备注、收入或支出等。
管理标签:添加、改名、删除
查看历史:按天、按周、按月
Vue VueRouter Vuex TypeScript JavaScript CSS HTML Nodejs Yarn等
版本不搭配会引起报错的!以下版本是我自己使用的,不会报错。
npm install -g nrm --registry=https://registry.npm.taobao.org
nrm use taobao
@vue/cli 版本-- 4.1.2
yarn global add @vue/[email protected]
# npm i -g @vue/[email protected]
vue --version # 版本号应该是 4.1.2
webStorm或者Vscode
创建项目
vue create /my-project/ //可自己修改自己喜欢的名字
cd /my-project/
yarn serve //npm run serve
毫无疑问,做项目肯定要先进行一番设计构思。基于模仿记账应用的一些外观和功能,我进行了一些简化和打磨。最终确定了以下的项目样式。
很显然,页面的URL需要用到
前端路由router
#/money记账
#/labels标签
#/labels/Edit/:id编辑标签页面
#/statistic统计
404页面
#/money
router/index.ts添加router,配置5个路径对应组件
初始化组件:Money.vue、Labels.vue、Statistic.vue、NotFound.vue
将router传给new Vue()
在App组件里面用 展示router渲染的页面
两种思路:1、在
全局展示。2、每个组件自己引入。
优化:
使用Vue.component全局组件
Nav.vue组件
<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="/statistic" class="item" active-class="selected">
<Icon name="statistic"/>
统计
</router-link>
</nav>
</template>
<script lang="ts">
export default {
name: "Nav"
}
</script>
<style lang="scss" scoped>
@import "~@/assets/style/helper.scss";
nav {
@extend %outerShadow;
display: flex;
font-size: 14px;
>.item {
padding: 2px 0;
width: 33.3%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.icon{
width: 30px;
height: 30px;
}
}
> .item.selected{
color: $color-highlight;
}
}
</style>
事不过三,我与重复不共戴天。
将布局抽离成组件
某个函数经常用到就单独封装
先写出一样代码效果,最后权衡利弊再进行优化。
模块化
是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。
模块化(modular)编程
,是强调将程序的功能分离成独立的、可相互改变的模块
(module)的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面(aspect)所必需的所有东西。 模块接口表达了这个模块所提供的和所要求的元素。 这些在接口中定义的元素可以被其他模块检测
。
显然的,在这次项目中,我们要做的模块化操作就是使每个组件中的代码量减少。
一旦某个组件代码量超过150行的时候,你就该考虑封装或者模块化了
。
封装通用组件是一个重要的思想概念
在本次项目中,我将频繁使用到封装通用组件的目的,为的就是减少重复,代码冗余
当你重复某个组件三次及以上,你就该考虑封装通用组件可。
JS中的@语法
<script lang='JS | TS'>
/* 这是JS或者TS中的@:表示根目录src */
import NumberPad from '@/components/Money.vue/Number';
</script>
webStorm需要设置webpack的路径
打开settings,搜索webpack
在路径中填上webpack.config.js的路径即可。
完整的路径:D:....\项目名\node_modules\@vue\cli-service\webpack.config.js
CSS中的~@语法
Vue组件中style标签的scoped属性
我们都知道在Vue中的Vue组件的style有个scoped属性。这个属性就是为了防止当我们使用CSS给页面添加样式时会影响到其他的组件中的CSS样式,造成这种情况的原因很多,比如类名重名、CSS选择器、还有一些选择器优先级的影响。所以
scoped
这个属性就是为了解决这个问题。在scoped
的`style里面写的CSS样式只会在当前的组件生效。不会影响到其他的组件或者页面的样式。
那么问题来了,我们一般都使用scoped限制当前组件的样式。但是偶尔会在设置样式时会想要深入修改某个HTML元素或者Vue组件的单一样式,怎么办?这就是
::v-deep
的用处了。
演示示例
在很多时候,我们在项目开发中都会用到
动态绑定类名
的时候。例如,点击某个item就让它拥有某个类名。取消点击后就让该item消除这个类名,可能我们绑定的类名也不止一个,这个时候就要用到类名的表驱动编程
了。
类名->表驱动编程
在Vue中,使用
v-bind:属性='xxx'
,表示动态绑定该属性
,即xxx所展示的是一个变量
,而不是写死的字符串
。
-
{{item.name}}
小结:
在以上代码示例中,:class='{selected:isSelected(item)'
,这句代码的意思就是,当isSelected(item)的结果为true时,存在这个类名。
同样的,我们可以通过逗号,
来分隔更多的类名表示,如::class='{selected:isSelected(item),className1:fn1,className2:fn2}'
。这样,当fn1
或fn2
的结果为true
时,那么这个li标签
就同时
存在这些类名:selected、className1、className2
。
在VueRouter中,
设置链接激活时使用的 CSS 类名
。
情景一:点击跳转静态页面
记账
|
标签
|
统计
小结:
在以上的代码中,每个router-link
都有一个active-class
属性,这个意思就是:当前页面的路径是该router-link,那么这个router-link就存在这个selected类名。
这样就可以做一些CSS样式
来装饰,例如在当前页面被选择时高亮
。
在做本项目中,需要一些
图标
来装饰,那毫无疑问就是使用阿里巴巴或者Icon Font中的一些图标了。那么问题来了,如何在Vue
中使用这些Svg图标
呢?
因为使用
Vue create
创建的项目中不存在webpack的配置
。所以需要根据官方文档去vue.config.js
中配置。
shims-vue.d.ts中添加如下代码
declare module "*svg" {
const content:string;
export default content;
}
在Vue组件中import一个svg icon -- 假设所有的icon图片都放在此路径:src/assets/icons
<template>
<div>啥也没有</div>
</template>
<script lang="ts">
/* 导入svg标签,xxx是随意取得 */
import xxx from "@/assets/icons/label.svg";
console.log(xxx);
export default {
name :'VueComponent',
}
</script>
<style lang="scss" scoped>
</style>
结果:
输出的只是一个路径字符串,显然不是我们想要的结果,接下来一一解决。使用yarn或者npm安装svg-sprite-loader
yarn add svg-sprite-loader -D
# npm i svg-sprite-loader -D
svg-sprite-loader该loader代码是JetBrains公司放在github上的,
它只给出了webpack的配置信息
。但是在Vue的项目中没有webpack.config.js的文件,意思就是我们要将JetBrains给出的配置信息翻译到Vue.config.js的配置中。
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() //只包含icons
.use('svg-sprite-loader').loader('svg-sprite-loader').options({extract:false}).end()
.use('svgo-loader').loader('svgo-loader') // 删除svg中自带的颜色(即删除svg的fill属性)
.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目录
}
}
使用的正确姿势
<svg>
/* #不能删除,label是引入的label.svg的名字 */
<use xlink:href='#label'/>
</svg>
<template>
<div>
<svg>
/* #不能删除,label是引入的label.svg的名字 use里面没有文字内容就自闭和。在Vue中使用的是xml标签语法*/
<use xlink:href='#label'/>
</svg>
</div>
</template>
<script lang="ts">
/* 导入svg标签,xxx是随意取得 */
import xxx from "@/assets/icons/label.svg";
export default {
name :'VueComponent',
}
</script>
<style lang="scss" scoped>
</style>
结果:
得到该svg图标
的相应图示展现在页面上小结:
xml标签的语法
xlink:href='#xxxx'
的形式,xxxx是该svg的名字
use标签中没有东西就记得自闭和
因为svg图标可能在哪儿都用得上,我们就可以
将Icon提取成为一个Vue的全局组件
。而且对于我们来说,区分svg的不同就是svg的名称。所以在封装Icon组件时,只需要我们单独传一下svg的名字就行了。
,用外部数据prop传递该值
就行了。
全局组件:在main.ts中声明
Vue.component('Icon', Icons); //Icon是Vue的组件别名(一般大写),Icons就是封装Icon的组件的名字
封装Icon组件
动态绑定href中的name
,且必须要求使用Icon组件时传递一个svg的名字
,这样才能显示对应的icon图标
<template>
<svg class="icon">
<use :xlink:href="'#'+name" />
</svg>
</template>
<script lang="ts">
import x1 from '@/assets/components/icons/x1.svg';
import x2 from '@/assets/components/icons/x2.svg';
import x3 from '@/assets/components/icons/x3.svg';
import x4 from '@/assets/components/icons/x4.svg';
import x5 from '@/assets/components/icons/x5.svg';
import x6 from '@/assets/components/icons/x6.svg';
.
.
.
.
export default {
props:['name'],
name: "icons"
}
</script>
<style lang="scss" scoped>
.icon{
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: auto;
}
</style>
这个问题的初衷:
一个一个引入svg不傻吗?我都感觉没有动力去复制粘贴一个一个引入svg。
将上面一长串的import替换
let importAll=(requireContext:__WebpackModuleApi.RequireContext)=>requireContext.keys().forEach(requireContext);
try{importAll(require.context('../assets/icons',true,/\.svg$/));} catch (err){console.log('err!');}
JS组件模板代码
–Vue的构造选项
- 支出
- 收入
TS比JS更加严谨
,TS数据必须有类型
。即TS比JS多一个类型检查
在
编译时
,TS会进行类型检查,如果TS检查出类型错误或者未给出指定类型,终端会进行Error报错
。但是你所写的JS代码一样可以进行运行(妥协)
。
这里写HTML
该
@Prop装饰器
是Vue的第三方vue-property-decorator
提供的API,这个第三方库非常好用,一般都使用它来表示JS中的外部数据prop
基于vue-property-decorator
使用npm或yarn安装该第三方库
npm i -S vue-property-decorator
# yarn add -S vue-property-decorator
基本用法:
import { Vue, Component, Prop } from 'vue-property-decorator'
/* 首先先导入该第三方库 */
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
小结:
propA不是data,而是prop
Number、String、Boolean(@Prop括号里面的类型)告诉Vue propA是个Number
propA是属性名
number | undefuned 就是 propA的类型
坑1:@Prop里面最好写清楚类型,不然会有潜在的bug
同样的,
@Watch也是第三方库vue-property-decorator的
,它的功能就是实现原生Vue组件Watch监听的功能
基本用法
import { Vue, Component, Watch } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Watch('child')
onChildChanged(val: string, oldVal: string) {}
|
@Watch('person', { immediate: true, deep: true })
onPersonChanged1(val: Person, oldVal: Person) {}
|
@Watch('person')
onPersonChanged2(val: Person, oldVal: Person) {}
}
custom.d.ts怎么用
custom是自己随意取的名字,一般为custom -- 表示自定义
.d.ts d是define的意思,ts是TypeScript的后缀。只要TypeScript发现是以.d.ts结尾的,就知道这是个全局的定义声明。
custom.d.ts代码
type rootState = {
recordList: RecordItem[],
tagList: Tag[],
currentTag?: Tag,
isCreateRecordError:Error |null;
isCreateTagError:Error |null;
}
type Tag={
id:string;
name:string;
}
//注:Record这个变量名已经有内置了
type RecordItem={
tags:Tag[],
notes:string,
type:string,
amount:number,
/* 表示类型时可以使用日期类型 用?表示可能该属性不存在*/
createTime?:string,
}
type Result={
title:string,
total?:number,
items:RecordItem[],
}
小结
custom.d.ts中声明全局用到的数据类型等等
在以下三种写法中,
只有script里面的内容不一样
export default { data, props, methods, created, ...}
@Component
export default class XXX extends Vue{
xxx: string = 'hi';
@Prop(Number) xxx: number|undefined;
}
@Component
export default class XXX extends Vue{
xxx = 'hi'
}
很多时候,我们都会遇见重复的HTML和CSS,这个时候虽然ctrl+c/v能解决问题,但是代码量却是上去了,有没有什么解决方案呢?接下来就来介绍一下
Layout组件和
插槽吧。
Layout:布局
,显然,Layout组件就是重复的布局,包括HTML和CSS
,我们将重复的布局片段全部放在Layout组件
里面。不同的内容就放入插槽
。
Layout组件& 插槽
例如在Money.vue中使用Layout组件, 标签中间的内容。相当于原来Layout组件中的 插槽的内容,即 就是替换Money.vue中 中间的内容
小结:
Layout组件
是一个HTML样式和CSS样式重复抽离出来的组件。独立的HTML内容就使用 插槽插入内容
在做这个单页面应用的项目中,
前端路由
无疑是最好的选择了。
而在Vue创建的项目中,在router文件目录下已经配置好了。
注意router需要在main.ts的new Vue()中引入
router/index.ts -- 配置路径
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Money from '@/views/Money.vue';
import Label from '@/views/Labels.vue';
import Statistic from '@/views/Statistic.vue';
import NotFound from '@/views/NotFound.vue';
import EditLabel from '@/components/Label/EditLabel.vue';
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path:'/',
redirect:'/money'
},
{
path:'/money',
component: Money
},
{
path:'/labels',
component: Label
},
{
path:'/statistic',
component: Statistic
},
{
path:'/labels/edit/:id',
component:EditLabel
},
{
path:'*',
component:NotFound
}
]
const router = new VueRouter({
routes
})
export default router
使用 +
是用来展示对应路径下的页面,与它搭配使用的是
。我们将它
放在App.vue
中。
App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
<style lang="scss">
@import "~@/assets/style/reset.scss";
@import "~@/assets/style/helper.scss";
body{
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
font-family: $font-hei;
font-size: 16px;
background: #f5f5f5;
}
</style>
我们在本次项目中选择将
页面导航栏封装成一个Nav组件
Nav.vue
VueRouter小结:
在router/index.ts下配置路径
,path是路径名,redirect是重定向,component是表示显示对应的组件名。
展示对应路径下的页面。 与 搭配使用,就好像是一个标签,点击后 就展示对应的页面。
对于导航栏,最好做成一个Nav组件
mixins解释传送门
在Vue组件中使用TS+minxins
我们一般都是在普通的情景下知道mixins的基本用法,但是在这次项目中我们将怎样使用呢?
大概思路:先在src目录下创建文件夹mixins,里面
放入一些想要进行全局混入的mixins组件
,然后导出这些组件
。
TagHelper.ts -- 将创建标签的功能抽离出来成为一个组件
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export class TagHelper extends Vue{
createTag(){
const name=window.prompt('请输入标签名');
if(name){
if(name.trim()==='') {
return window.alert('标签名不能为空');
}
this.$store.commit('createTag',name);
if(this.$store.state.isCreateTagError){
window.alert('标签名重复了!')
this.$store.state.isCreateTagError=null;
return;
}
else {
window.alert('标签创建成功!');
}
}
}
}
export default TagHelper;
在Labels.vue里面使用mixins组件TagHelper.ts
<script lang="ts">
import {mixins} from 'vue-class-component';
import TagHelper from '@/mixins/TagHelper.ts';
@Component
export default class Labels extends mixins(TagHelper){
.
. /* 这里面自动就继承了TagHelper里面的所有东西 */
.
}
</script>
小结:
将需要mixin(混入)的重复组件抽离统一放在mixins下。
vue-class-component
vue-class-component
需要继承mixins
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
换句话说:
Vuex就是一个全局的状态管理,包括数据和函数行为等。能够操作数据的读写。
Vuex在项目目录
store
中,注意在main.ts的new Vue()中引入
npm install vuex --save
# yarn add vuex
import Vue from 'vue';
import Vuex from 'vuex';
import clone from '@/lib/clone';
import createId from '@/lib/createId';
import router from '@/router';
Vue.use(Vuex); // 使用Vue的use将Vuex绑到Vue.prototype上 -- 下面的代码就是:Vue.prototype.$store=store;
/* 有this和Vue的全局的区别,有this的可以直接在template中使用,且可以省去this */
const store = new Vuex.Store({
state:{},
mutations:{},
actions: {},
modules: {},
})
export default store;
目录结构小结
state
:里面存放全局状态的唯一数据源即变量
,类似Vue组件的datamutations
:里面存放函数和方法
,类似Vue组件中的methodsactions
:里面也是存放函数和方法,与mutations不同的是,这里面放的是异步操作,如发送Ajax请求
modules
:当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module),modules代码示例
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
store下的index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import clone from '@/lib/clone';
import createId from '@/lib/createId';
import router from '@/router';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
recordList: [],
tagList: [],
currentTag: undefined,
isCreateRecordError:null,
isCreateTagError:null,
} as rootState,
mutations: {
setCurrentTag(state, id: string) {
state.currentTag = state.tagList.filter(t => t.id === id)[0];
},
fetchTags(state) {
state.tagList = JSON.parse(localStorage.getItem('tagList') || '[]');
if(!state.tagList.length){
store.commit('createTag','衣');
store.commit('createTag','食');
store.commit('createTag','住');
store.commit('createTag','行');
}
},
fetchRecords(state) {
state.recordList = JSON.parse(localStorage.getItem('recordList') || '[]') as RecordItem[];
},
createdRecord(state, record:RecordItem) {
const record2 = clone(record);
/* toISOString是日期专有的字符串形式 */
record2.createTime = new Date().toISOString();
state.recordList.push(record2);
store.commit('saveRecords');
},
createTag(state, name: string) {
const names = state.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
state.isCreateTagError=new Error('tag name duplicated');
return ;
} else {
const id = createId().toString();
state.tagList.push({id: id, name: name});
store.commit('saveTags');
}
},
updateTag(state, payload: Tag) {
const {id,name} = payload;
const idList = state.tagList.map(item => item.id);
if (idList.indexOf(id) >= 0) {
const names = state.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
window.alert('标签名重复!')
} else {
const tag = state.tagList.filter(item => item.id === id)[0];
tag.name = name;
store.commit('saveTags');
}
}
},
removeTag(state,id: string){
let index=-1;
for (let i = 0; i < state.tagList.length; i++) {
if(state.tagList[i].id===id){
index=i;
break;
}
}
if(index>=0){
state.tagList.splice(index,1)
store.commit('saveTags');
router.back();
}
else {
window.alert('删除失败!');
}
},
saveRecords(state) {
localStorage.setItem('recordList', JSON.stringify(state.recordList));
},
saveTags(state) {
localStorage.setItem('tagList', JSON.stringify(state.tagList));
},
},
actions: {},
modules: {}
});
export default store;
使用Vuex小结
state和mutations就类似于Vue组件的data和methods
在mutations中写函数方法。
mutations中的函数至多接受两个参数,第一个参数默认就是state,但是你要使用state,也要进行参数声明。第二个参数就是你自己额外想要传递的参数了。
使用commit在store中触发mutations中自己封装的函数
store
在对象中读操作 -- 使用computed
<script lang='ts/js'>
import {Component} from 'vue-property-decorator';
@Component({
computed:{
/* 在计算属性computed中写函数的形式表示该变量 */
tagList(){
/* 在store的state中有个tagList变量名 */
return this.$store.state.tagList;
}
}
})
export default class Money extends Vue{
.
.
.
}
</script>
在TS/JS类中读操作 -- 使用getter
<script lang='ts/js'>
import {Component} from 'vue-property-decorator';
@Component
export default class Money extends Vue{
get tagList(){
return this.$store.state.tagList;
}
}
</script>
对store中的数据进行写操作 -- commit
// 1、Vuex的commit
store.commit('<函数名>',参数payload)
组件中使用store小结
读操作:
①对象使用computed计算属性。②TS/JS类使用getter。写操作:
使用store的commmit接口,在mutations中自己封装写操作的函数,然后调用commit告诉Vuex中的store触发该函数进行写数据。修饰符就是为了简化某种操作过程,它实际上是一些操作的
语法糖
.native
使用情景
将原生事件绑定到组件上。例如,你
封装了一个Button组件,里面包含的是一个按钮,
当你点击页面上的按钮时,你会发现触发不了按钮的点击事件,这是因为你点击的是Button组件而不是
,所以触发不了的事件
解决方案:
使用$emit('',$event) 触发传递事件给 Button组件标签。
使用.native修饰符
代码演示
Button.vue组件
在EditLabel.vue中使用Button.vue组件
.sync
使用情景
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。一般使用以 update:myPropName 的模式触发事件取而代之。然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。
上面所说的这种模式就是
.sync修饰符
,就是子组件调用父组件的数据,某种情况下子组件需要对数据进行操作,就触发事件来通知父组件来修改,并且父组件要拿到更新数据变更后的值。
代码演示
Tags.vue组件 -- 子组件
Money.vue中使用Tags.vue组件
国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1998”与2000年的第二版“ISO8601:2000”。
合并表示时,要在时间前面加一大写字母T,如要表示东八区时间2004年5月3日下午5点30分8秒,可以写成
2004-05-03T17:30:08+08:00或20040503T173008+08
顺便说一下,
momentjs和dayjs相比体积太大了。我们一般选择使用dayjs
安装dayjs
npm install dayjs
# yarn add dayjs
导入dayjs
var dayjs = require('dayjs')
//import dayjs from 'dayjs' // ES 2015
简单的dayjs API用法:判断今天,昨天,前天
beautify(date:string){
/* dayjs的相关总结,before和after只是判断在前或在后,没有对应的准确昨天和后天之说。 */
// 还是要通过isSame来判断。而且dayjs()提供了subtract的API,参数1位减去的数字,参数2位减去的年月日等。
const now=new Date();
if(dayjs(date).isSame(now,'day')){
return '今天';
}
else if(dayjs(date).isSame(dayjs().subtract(1,'day'),'day')){
return '昨天';
}
else if(dayjs(date).isSame(dayjs().subtract(2,'day'),'day')){
return '前天';
}
else {
return dayjs(date).format('YYYY年M月D日');
}
}
localStorage存储数据是浏览器的缓存,刷新后数据不变化,但是清除缓存或浏览器数据时就会受影响,就是单机版存储数据