Vue项目细节

1,605 阅读2分钟

项目介绍

这是一个记账的项目。

基于vue+vue-router+vuex的一个项目。

我没有采取获取后台接口的形式。我使用的是在本地存储,通过window.localStorage的方式来存储。

在这个项目中使用的是TS。我也是第一次使用TS,如果有错误的请大家及时指出。

源码

项目配置细节

//我的vue/cli版本是4.1.2
//我用的是命令行创建
$ vue create mymoney

项目目录

TS与JS的导入的区别

//导入vue文件
//JS
import HelloWorld from './components/HelloWorld.vue';
//TS
import HelloWorld from '@/components/HelloWorld.vue';
//导入CSS
@import  "assets/styles/test.scss";
@import  "../assets/styles/test.scss";

//TS 引入
@import "~@/assets/styles/test.scss";
//WebStrom用户可能会出现红色波浪线
//需要在需要在webStorm -> setting -> WebPack设置添加
// 你的项目路径/node_modules\@vue\cli-service\webpack.config.js

思路

创建页面URL

#/money - 记账

#/labels - 标签

#/statistics - 统计

默认路由设置

 {
    path:"/",
    redirect:Money,
  },

404路由设置

 {
    path:"*",
    component:NotFound,
 }

导航栏问题

当我们要创建导航栏的时候。在我们一些页面当中可能不需要使用到导航栏。就没有必要把导航栏放在App.vue当中。如果我们在每个页面中引入。需要大量的引入代码。 可以通过Vue.component()在main.ts中全局引入

布局选择问题

CSS-导航栏fixed定位和flex布局的选择 fixed在移动端有很多坑。我就选择flex布局

组件封装问题

我们要设计相同页面的时候。如果我们把这个页面复制多遍。我们可以把他封装到组件当中。通过<slot></slot>来获取外部数据。

svg-sprite-loader的导入

npm install svg-sprite-loader -D
//or 
yarn add svg-sprite-loader -D
//在shims-vue.d.ts中添加
declare module '*.svg' {
  const content: string;
  export default content
}

//在vue.config.js中配置svg-sprite-loader
// eslint-disable-next-line @typescript-eslint/no-var-requires
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();

    // eslint-disable-next-line @typescript-eslint/no-var-requires
    config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
    config.module.rule("svg").exclude.add(dir)
  }
}
<!--在template中引用-->
<svg><use xlink:href="#labels" /></svg>

样式封装

<!--Layout.vue-->
<template>
  <div>
    <div class="nav-wrapper">
      <div class="content" :class="classProfix && `${classProfix}-content`">
        <slot></slot>
      </div>
      <Nav />
    </div>
  </div>
</template>
<!-- Money.vue-->
 <Layout class-profix="layout">
      <Tags :data-source.sync="tags" @update:tag="onUpdateTag"></Tags>
      <Notes @update:value="onUpdateNotes"></Notes>
      <Types :data-type.sync="record.type"></Types>
      <NumberPad @update:Amount="onUpdateAmount" @submit="onSubmit"></NumberPad>
    </Layout>


icons引入问题

当我们svg图标多了,我们需要手动引入多个。有没有一次性全部引入的办法呢?

const importAll=(requireContext: __WebpackModuleApi.RequireContext)=>requireContext.keys().forEach(requireContext);
try{
  importAll(require.context("../assets/icons",true, /\.svg$/));
}catch (error){
  console.log(error);
}

封装SVG

//Icon.vue
//通过props由父组件传递给子组件
<template>
  <div>
    <svg><use :xlink:href="'#'+name"/></svg>
  </div>
</template>

<script lang="ts">
const importAll=(requireContext: __WebpackModuleApi.RequireContext)=>requireContext.keys().forEach(requireContext);
try{
  importAll(require.context("../assets/icons",true, /\.svg$/));
}catch (error){
  console.log(error);
}

export default {
  props:["name"],
  name: "Icon"
}
</script>

svgo-loader

当我们点击svg的时候我们需要改变svg的颜色,会发现有的svg会改变有的不会。 原因是在svg的文件中有个fill属性,他会导致我们点击不会改变成我们想要变得颜色。我们可以用svgo-loader来处理

$ npm install svgo svgo-loader --save-dev
//or
$ yarn add svgo svgo-loader -D
//在上述得svg-loader中添加代码vue.config.js文件中再添加
        .use("svgo-loader").loader("svgo-loader")
        .tap(options=>({...options,plugins:[{removeAttrs:{attrs:"fill"}}]})).end()
//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").loader("svg-sprite-loader").options({extract:false}).end()
 /*添加的代码*/       .use("svgo-loader").loader("svgo-loader")
 /*添加的代码*/       .tap(options=>({...options,plugins:[{removeAttrs:{attrs:"fill"}}]})).end()
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
    config.module.rule("svg").exclude.add(dir)
  }
}

字体问题

/*3种字体的CSS*/
$font-hei: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
$font-kai: Baskerville, Georgia, "Liberation Serif", "Kaiti SC", STKaiti, "AR PL UKai CN", "AR PL UKai HK", "AR PL UKai TW", "AR PL UKai TW MBE", "AR PL KaitiM GB", KaiTi, KaiTi_GB2312, DFKai-SB, "TW\-Kai", serif;
$font-song: Georgia, "Nimbus Roman No9 L", "Songti SC", "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif CN", STSong, "AR PL New Sung", "AR PL SungtiL GB", NSimSun, SimSun, "TW\-Sung", "WenQuanYi Bitmap Song", "AR PL UMing CN", "AR PL UMing HK", "AR PL UMing TW", "AR PL UMing TW MBE", PMingLiU, MingLiU, serif;

编程字体

font-family: Consolas,monospace;

按键布局问题

/*如果我们的button布局产生这样的问题那么用flex是无法解决的*/
/*可以通过float来解决*/
  .buttons{
    >button{
      width: 25%;
      height: 64px;
      float: left;
      &.ok{
        height: 64*2px;
      }
    }
  }
/*注意:当我使用这段代码的时候情况还是跟上面的一样。*/
/*需要在ok按钮中把ok放到右边不然他会占住左边的位置*/
  .buttons{
    >button{
      width: 25%;
      height: 64px;
      float: left;
      &.ok{
        float: right;
        height: 64*2px;
      }
    }
  }

按键的处理

需求:

  1. 当我们输入一个小数点之后,我们不能再输入小数点了。
  2. 当我们初始化为0时,我们输入小数需要在0的后面加.但不能覆盖原来的0。
  3. 最大的输入数值位数为16位。
inputContent(event: MouseEvent) {
    const button = (event.target as HTMLButtonElement);
    const input = button.textContent!;
    if (this.output.length === 16) {
      return;
    }
    if (this.output === '0') {
      if (('0123456789').indexOf(input) >= 0) {
        this.output = input;
      } else {
        this.output += input;
      }
      return;
    }
    if (this.output.indexOf('.') >= 0 && input === '.') {
      return;
    }
    this.output += input;
  }

删除按钮

  remove() {
    this.output = this.output.slice(0, -1);
    if (this.output.length === 0) {
      this.output = '0';
    }
  }

标签处理

需求

  1. 点击标签的时候要判断标签是否已经被选取
  2. 选取的标签变更样式
      <li v-for="tag in dataSource" :key="tag" @click="toggle(tag)" :class="{selected:selectedTag.indexOf(tag)>=0}" >{{ tag }}</li>
      <!--class是判断样式的逻辑-->
  selectedTag: string[]=[] ;
  toggle(tag: string){
    const index=this.selectedTag.indexOf(tag);
    if(index>=0){
      this.selectedTag.splice(index,1);
    }else{
      this.selectedTag.push(tag);
    }
  }

新增标签

需求

  1. 用户输入标签,对标签进行判断。如果为空则输入失败。
  2. 将新增的标签信息添加到对应的数据当中并显示出来。
  create() {
    const name = window.prompt('请输入标签');
    if (name === '') {
      window.alert('标签不能为空字符串');
    } else if (this.dataSource) {
      console.log(name);
      this.$emit('update:dataSource', [...this.dataSource,name]);
    }
  }
//在这里就要注意,我们不能够应该直接修改其他组件传过来的数据,我们需要通过$emit来传数据。在接收数据的组件通过.sync来接收数据。

数组问题

当我们的代码是下图时,数据会发生错误 错误情况 前面的数据会被后面的数据覆盖,此时我们就要进行深拷贝。

  onSubmit(value: number){
    const record2=JSON.parse(JSON.stringify(this.record));
    this.recordList.push(record2);
    window.localStorage.setItem("recordList",JSON.stringify(this.recordList));
  }

封装Input表单时数据的变化

当我们把表单封装成组件的时候,要特别注意数据之间的传递关系。

//我们在外部引用该组件的时候。实际上是在本组件操作数据。所以说该组件时属于子组件。当我们要把子组件的值传给父组件。需要用到
this.$emit()

因为子组件的$emit传了个参数所以能够直接获取到参数。

封装成组件之后@Click失效问题

<!--我的们组件是-->
<template>
  <div>
    <svg class="icon"><use :xlink:href="'#'+name"/></svg>
  </div>
</template>
<!--使用组件,并添加点击事件-->
<Icon @click="goBack"  name="left" class="left"></Icon>
<!--会发现点击之后没有反应-->
<!--方法1.需要把事件类型传过来,通过$emit-->
<template>
  <div @click="$emit('click',$event)">
    <svg class="icon"><use :xlink:href="'#'+name"/></svg>
  </div>
</template>


<!--方法2.通过.native来实现-->
<Icon @click.native="goBack"  name="left" class="left"></Icon>

地址与传值问题

当我的代码为

recordList: RecordItem[]=store.recordList;

他传的是地址而不是值,只是恰巧能够传到。需要通过

  computed: {
    recordList() {
      return store.recordList;
    }
  }

通过computed来获取store.recordList的值

初始Vuex

一个简单的例子

const store= new Vuex.Store({
  state: {//类似data()
    count:0
  },
  mutations: {//类似methods
    increment(state,n:number){
      state.count+=n;
    }
  },
  actions: {
  },
  modules: {
  }
})

console.log(store.state.count)
store.commit("increment",10);//调用vuex当中的函数
console.log(store.state.count)
export default store;

TS中使用mixins

import Vue from "vue"
import Component from 'vue-class-component'
@Component
export class tagHelper extends Vue{
    createTag(){
        const name=window.prompt("请输入标签名");
        if(name){
            this.$store.commit("createTag",name);
        }else{
            window.alert("请输入正确标签");
        }
    }

}
export default class Tags extends mixins(tagHelper) {
	......
}

通过computed获取state数据的问题

//当我们的代码为下面的代码时
@Component({
  components: {FormItem,Button},
  computed:{
    tag(){
      return this.$store.state.tagList;
    }
  }
})
export default class Edit extends Vue {
  tag?: { id: string ;  name: string } = undefined;
  created() {
    this.$store.commit("findTag",this.$route.params.id);
    if (this.tag) {
      this.tag=this.$store.state.currentTag;
    } else {
      this.$router.replace('/404');
    }
  }
  ......
}
//这样的数据无法在类当中进行设置
//需要用到getter
export default class Edit extends Vue {

  get currentTag(){
    return this.$store.state.currentTag;
  }

  created() {
    this.$store.commit("fetchTags");
    this.$store.commit("findTag",this.$route.params.id);
    if (this.currentTag) {
      this.tag=this.$store.state.currentTag;
    } else {
      this.$router.replace('/404');
    }
  }
  ......
}

obj.freeze()

无法操作对象的API;

::v-deep

/*当我们需要在组件外修改CSS样式时,但由于组件有Scoped属性。
此时可以用到*/
::V-deep

ISO 8601

new Date().toISOString()

森林结构数据

  get recordList(){
    return this.$store.state.recordList;
  }

  get result(){
    const {recordList}=this;
    type HashTableValue={ title:string,items:RecordItem[]};
    const hashTable:{[key:string]:HashTableValue}={};
    for(let i=0;i<recordList.length;i++){
      const date=recordList[i].createdAt.split("T")[0];
      const time=recordList[i].createdAt.split("T")[1];
      hashTable[date]=hashTable[date]||{title:date,items:[]};
      hashTable[date].items.push(recordList[i]);
      console.log(hashTable);
    }
    return [];
  }

Day.js格式化时间

npm install dayjs / yarn add dayjs
  beautify(string: string) {
    const day = dayjs(string);
    const now = dayjs();
    if (day.isSame(now, 'day')) {
      return '今天';
    } else if (day.isSame(now.subtract(1, 'day'), 'day')) {
      return '昨天';
    } else if (day.isSame(now.subtract(2, 'day'), 'day')) {
      return '前天';
    } else if (day.isSame(now, 'year')) {
      return day.format('M月D日');
    } else {
      return day.format('YYYY年M月D日');
    }
  }

统计页面数据的设置

通过树型结构来存储

  get groupList(){
    const {recordList}=this;
    const newList=clone(recordList).filter(r=>r.type===this.type).sort((a:any, b:any) => dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf());
    type Result={title:string,total?:number,items:RecordItem[]}[]
    const result:Result=[{title:dayjs(newList[0].createdAt).format("YYYY-MM-DD"),items:[newList[0]]}]
    for(let i=1;i<newList.length;i++){
      const current=newList[i];
      const last=result[result.length-1];
      if(dayjs(last.title).isSame(current.createdAt),'day'){
          last.items.push(current);
      }else{
        result.push({title:dayjs(current.createdAt).format('YYYY-MM-DD'),items:[current]})
      }
    }
    result.map(group=>group.total=group.items.reduce((sum,item)=>{
          return sum+item.amount;
    },0)
    )
    return result;
  }

部署

通过官方文档来操作vue/cli部署

BUG修复

键盘挤压样式问题

BUG: 解决方案: 我们需要获取当前页面的高度,然后将高度固定。

  data(){
    return{
      n:0,
    }
  },
  created(){
    this.n=document.documentElement.clientHeight;
  }
  <div>
    <div class="nav-wrapper" :style="{height:n+'px'}">
      <div class="content" :class="classProfix && `${classProfix}-content`">
        <slot></slot>
      </div>
      <Nav />
    </div>
  </div>