前端实习——Vue学习笔记(二)

十八、项目目录优化

为了提升项目的可维护性和可扩展性,我们把src/components目录下的组件进行目录结构的优化,把所有可以通过路由地址访问带的页面组件放到pages文件夹下,把所有页面组成部分的组件(不通过路由地址访问的组件)放到views文件夹下。

以上所有的文件夹的名称没有强制要求交这个名字,可以自行设置

调整后的目录结构为

project项目名称
	-src
		-components
            -pages 存放页面组件
            ... 其他目录可以根据需求自行设置
            -views 存放页面组成部分的组件

一旦组件目录结构调整以后,在引用该组件的地方也要跟着把引入的路径地址进行改变

十九、stylus样式预处理器

1.特性

  • 冒号可有可无
  • 分号可有可无
  • 逗号可有可无
  • 括号可有可无
  • 变量
  • 插值(Interpolation)
  • 混合(Mixin)
  • 数学计算
  • 强制类型转换
  • 动态引入
  • 条件表达式
  • 迭代
  • 嵌套选择器
  • 父级引用
  • Variable function calls
  • 词法作用域
  • 内置函数(超过 60 个)
  • 语法内函数(In-language functions)
  • 压缩可选
  • 图像内联可选
  • Stylus 可执行程序
  • 健壮的错误报告
  • 单行和多行注释
  • CSS 字面量
  • 字符转义
  • TextMate 捆绑

2.安装

npm i stylus stylus-loader --save

3.引入

是在页面组件中的style标签中添加一个lang属性(默认值为css),并把他的属性值明确设置为stylus

<style lang="stylus"></style>

4.使用

<template>
  <div class="wrapper">
      <div class="mask">
        <div class="content">
          <h1>登录页面</h1>
          <div class="item">
            <input type="text" name="" id="" placeholder="请输入用户名">
          </div>
          <div class="item">
              <input type="password" name="" id="" placeholder="请输入密码">
          </div>
          <div class="item">
            <button>登录</button>
          </div>
        </div>
      </div>
  </div>
</template>

<script>
export default {

}
</script>

<style lang="stylus" scpoed>
  .mask
    width 100vw
    height 100vh
    background rgba(0,0,0,0.5)
    .content
      width 400px
      height 300px
      background #fff
      transform translateY(50%)
      margin 0 auto
      border-radius 20px
      text-align center
      h1
        padding-top 10px
      .item
        padding 10px
        input
          height 30px
          width 300px
          line-height 30px
          font-size 20px
        button 
          width 300px
          height 40px
          background  skyblue 
          border none 
          color  #fff
          font-size 20px
</style>

5.函数

如果有不同的页面要使用相同的样式代码,可以在stylus中封装一个函数,把需要重复使用的样式代码放到函数中,在页面组件中引入函数即可

(1)src/common/css/fn.styl

mask(){
    width 100vw
    height 100vh
    background rgba(0,0,0,0.5)
}

(2)在页面组件中引入

<style lang="stylus" scpoed>
@import '../../common/css/fn.styl'
  .mask
    mask()
 </style>

6.变量的使用(预处理器)

可以预先设置好一些初始的样式信息,包括颜色,尺寸、字体、表格、表单

/src/common/color.styl

$bgColor1 = #33ad3c
$bgColor2 = #2468a2
$bgColor3 = #1b315e

只要在页面组件中引入相关的.styl文件就可以使用预先设置好的变量信息

<style lang="stylus" scoped>
@import '../../common/css/color.styl'
    .nav
        width 100px
        background  $bgColor2
</style>

二十、状态管理-Vuex

1.什么是vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

2.安装

npm i vuex --save

3.核心

(1)state

state是vuex仓库中所有的状态,类似vue组件中的data。

import Vuex from 'vuex';
Vue.use(Vuex);
let store = new Vuex.Store({
  state: {
    num:100,
    name:'vuex name'
  }
})
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

在任意组件中读取仓库中的数据

{{$store.state.num}}

(2)mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于methods

mutations: {
    addNum(state,txt){
      state.num+=txt;
    }
  }

组件中

<button @click="$store.commit('addNum',5)"></button>

在页面组件中使用mutation中调用,不能执行异步操作

mutations必须是一个同步函数

(3)Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
 actions: {
    addNumSync(context){
    	setTimeout({
			context.commit('addNum')
    	},1000)
    }
  }

组件中

<button @click="$store.dispatch('addNumSync')"></button>

(4)getters

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

示例代码

let store = new Vuex.Store({
  getters:{
    showNum(state){
      return `最新的数量是${state.num}`;
    }
  }
})

在组件中使用计算属性

<p>{{$store.getters.showNum}}</p>

(5)Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

namespaced:true 启用命名空间

①设置模块

export default new Vuex.Store({
....
  modules:{
      shop:{
          namespaced:true,// 启用命名空间
          state:{
              num:1
          },
          mutations:{
            addNum(state, step) {
                state.num += step;
            }
          }
      }
  }
})

②组件中使用state和mutation

<template>
  <div>
      <h1>shop模块</h1>
      <p>shop:{{$store.state.shop.num}}</p>
      <!-- 根模块 -->
      <button @click="$store.commit('addNum',5)">改变根数量</button>
      <!-- shop模块 -->
      <button @click="$store.commit('shop/addNum',5)">改变shop数量</button>
  </div>
</template>

③使用助手函数

...mapGetters('命名空间名', ["getCartGoods"])

4.助手函数

(1)mapState

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗.

在页面组件中

<script>
import {mapState} from 'vuex'
export default {
  computed: {
    ...mapState(['num'])
  },
}
</script>

(2)mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

在页面组件中

<script>
import {mapState,mapGetters} from 'vuex'
export default {
  computed: {
    ...mapState(['num']),
    ...mapGetters(['showNum'])
  },
}
</script>

(3)mapActions

使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store

在页面组件中

<button @click="addNumSync(3)">action+N</button>
<script>
import {mapState,mapGetters,mapActions} from 'vuex'
export default {
  methods: {
    ...mapActions(['addNumSync'])
  }
}
</script>

(4)mapMutations

使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store

在页面组件中

<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
  methods: {
    ...mapActions(['addNumSync']),
    ...mapMutations(['addNum'])
  }
}
</script>

5.目录结构优化

把代码都写在main.js中使非常不明智且不合理的,因为仓库中的状态非常多时,对应的代码量也会变得非常多

(1)我们在src目录下床架一个store文件夹,zai store文件夹下再创建一个index.js

(2)然后就可以把之前写在main.js中关于vuex的代码都放到/src/store/index.js中,但是,所有的代码都放在index.js中也是不合适的,以为状态和改变状态的方法会有很多个,所以在此基础上继续进行目录结构的细分,把state、mutation、actions、getters中对应的代码分别拆分到对应的js文件中,在index.js中引入即可

优化之后的代码:

/src/main.js

import store from './store'
new Vue({
    el: '#app',
    router,
    store:store,//一定要把仓库挂到vue实例上
    components: { App },
    template: '<App/>'
})

/src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//引入状态
import state from './state'
//引入修改状态的方法
import mutations from './mutation'
//引入异步操作mutation的方法
import actions from './action'
//引入计算属性
import getters from './getter'
//实例化vuex仓库
export default new Vuex.Store({
    state:state,//key和val相同时,state:state可以简写成state
    mutations,
    actions,
    getters
});

/src/store/state.js

export default{
    num: 100
}

/src/store/mutation.js

export default{
    addNumByOne(state){
        state.num++
    },
    addNumByNum(state,num){
        state.num+=num
    }
}

/src/store/action.js

export default{
    addNumByOneSync(context){
        setTimeout(()=>{
            context.commit('addNumByOne')
        },1000)
    },
    addNumByNumSync(context,n){
        context.commit('addNumByNum',n)
    }
}

/src/store/getter.js

export default {
    showNum(state){
        //业务逻辑
        return `最新的数量是:${state.num}`;
    }
}

状态持久化

(1)使用本地存储结合vuex

(2)使用插件实现数据持久化

安装

npm i vuex-persistedstate --save

使用:

/src/store/index.js

import creatPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
    mutations,
    state,
    getters,
    actions,
    plugins:[creatPersistedState()]
})

6.购物车案例

1.业务流程

商品列表页->商品详情页->加入购物车

2.示例代码

(1)商品列表页–展示商品

<template>
  <div>
      <h1>商品列表</h1>
      <div class="list">
          <div class="item" v-for="good of goodsArr" :key="good.id" @click="toInfo(good.id)">
              <div class="left">
                  <p>商品名称:{{good.name}}</p>
                  <p>商品价格:{{good.price}}</p>
              </div>
              <div class="right">
                  <img :src="good.img" alt="good.name">
              </div>
          </div>
      </div>
  </div>
</template>

<script>
export default {
    methods: {
        toInfo(id){
            this.$router.push('/goods/'+id);
        }
    },
    data () {
        return {
        }
    }
}
</script>

(2)商品详情页–展示具体信息,加入购物车

<template>
  <div>
    <h1>商品详情</h1>
    <p>商品名称:{{info.name}}</p>
    <p>商品价格:{{info.price}}</p>
    <p>商品图片:
        <img :src="info.img" alt="">
        </p>
    <button @click="addCart">加入购物车</button>
  </div>
</template>

<script>
export default {
  mounted() {
    let id = this.$route.params.gid;
    this.info = this.goodsArr.find(item => (item.id = id));
  },
  methods: {
      //点击加入购物车按钮
    addCart(){
      //触发vuex中的action this.$store.dispatch('shop/addCartGoodsSync',this.info);
        this.$router.push('/cart')
    }  
  },
  data() {
    return {
      info: {
        name: "",
        img: "",
        price: "",
        id: ""
      },
    };
  }
};
</script>

(3)初始化vue状态,并定义好改变状态的方法

/src/store/shop/index.js定义初始状态

state:{
        num:1,
        cartGoods:[]//定义购物车空数组
    },

/src/store/shop/index.js定义直接改变状态的方法

mutations:{
      addCartGoods(state,obj){
        state.cartGoods.push(obj)//把指定的内容追加到初始状态中
      }
    },

/src/store/shop/index.js定义触发mutations的action–可以执行异步操作

 actions: {
        addCartGoodsSync(context,obj){
            context.commit('addCartGoods',obj)
        }
    },

/src/store/shop/index.js定义计算属性,方便页面去获取

 getters: {
        getCartGoods(state){
            return state.cartGoods;
        }
    }

(4)购物车页–通过计算属性来获取到已经加入到购物车中的商品信息

<script>
import {mapGetters} from 'vuex'
export default {
    computed:{
        ...mapGetters('shop',['getCartGoods'])
    }
}
</script>

二十一、ui库

1.element-ui

(1)安装

npm i element-ui -S

(2)引入

①完整移入

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';//非常重要
Vue.use(ElementUI);
new Vue({
  ...
  render: h => h(App)
});

②按需引入

需要哪个组件就引入哪个组件

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);

(3)使用

参照官网使用

2.iview

3.mint-ui

二十二、数据变化后页面不更新

在vue项目,受对象数据类型的影响,有时直接通过下标操作数组,数组内容变化了,但是页面并没有跟着进行重新渲染。

1.解决办法

可以通过JSON序列化来实现

vuex的mutations

addCartNum(state,id){
	let idx = state.cartGoods.findIndex((item)=>item.id == id);
    let goodsArr = JSON.parse(JSON.stringify(state.cartGoods));
    goodsArr[idx].num++;
    state.cartGoods = goodsArr;
}

2.解决办法

可以使用vue提供的$forceUpdate方法

vuex的actions

addCartNumSync(context,id){
	context.commit('addCartNum',id)
}

vuex的mutations

addCartNum(state,id){
    let idx = state.cartGoods.findIndex((item)=>item.id == id);
    state.cartGoods[idx].num++;
}

页面组件代码

methods:{
    ...mapActions(['addCartNumSync']),
    add(id){
        this.addCartNumSync(id);
        //调用完成vuex中的actions操作方法对数据进行改变后
        //强制重新渲染页面,触发update生命周期钩子函数
        this.$forceUpdate();
    }
}

二十三、项目页面准备

1.表单组件

element-ui中提供了表单中常用的组件,比如输入框、选择框、单选框、开关等

<el-form label-width="80px"  style="width:600px;" >
    <el-form-item label="菜单名称">
    	<el-input v-model="info.title"></el-input>
    </el-form-item>
    <el-form-item label="上级菜单">
    <el-select v-model="info.pid" placeholder="请选择">
        <!-- 
            value 	设置选中项的值
            label   设置选中项的名称
        -->
        <el-option value="">请选择</el-option>
        <el-option value="0" label="顶级菜单">顶级菜单</el-option>
        <el-option value="1" label="系统设置">系统设置</el-option>
    </el-select>
    </el-form-item>
    <el-form-item label="菜单图标">
    	<el-input v-model="info.icon"></el-input>
    </el-form-item>
    <el-form-item label="菜单地址">
    	<el-input v-model="info.address"></el-input>
    </el-form-item>
    <el-form-item label="状态">
    	<el-switch v-model="info.status"></el-switch>
    </el-form-item>
    <el-form-item>
    	<el-button type="primary">提交</el-button>
    </el-form-item>
</el-form>

其中,el-select组件中的el-option组件,如果不设置label和value属性的话,则默认把el-option中的内容当成值和label,但是实际项目中一般都不会直接把el-option中的内容进行传递,所以需要设置value属性和label属性,value属性用来控制传递的值,label属性用来控制匹配的值在option中显示的内容。

2.表单验证

element-ui的表单组件中内置了验证功能,可以防止数据的丢失

(1)rules属性

需要给表单组件添加一个rules属性,用来告知表单具体的验证规则是什么

<el-form :rules="具体的验证规则名称">

(2)验证规则

具体的验证规则需要写在data里来进行预定义

<script>
export default {
    data(){
    	return{
    		验证规则名称:{
                要验证的字段名:[
                    { required:true,message:'菜单名称不能为空',trigger:'blur' },
                    { min:1,max:20,message:'菜单名称长度不符合要求' }
                ]
            }
    	}
    }
}
</script>

required 设置元素必填

messeage设置元素不符合验证规则显示的文字内容

trigger 设置元素触发规则的机制

min 设置元素内容的最小长度

max设置元素内容的最大长度

(3)prop属性

给需要进行验证的表单元素设置一个prop属性,属性值要和在验证规则中设置的名称保持一致

<el-form-item label="展示的名称" prop="要验证的字段名">

(4)model属性和ref属性

在进行表单验证时,需要给表单组件设置model属性,用来进行具体数据内容的验证

<el-form :model="要进行验证的数据对象" ref="表单自定义名称">

(5)validate

在点击提交按钮时,需要执行表单组件内置的validate方法来实现表单内容的验证

<el-form-item>
	<el-button type="primary" @click="自定义方法('表单的ref属性值')">提交</el-button>
</el-form-item>
<script>
export default {
	...
	methods:{
        自定义方法(形参) {
            this.$refs[形参].validate((valid) => {
                if (valid) {
                    //验证规则满足时,才执行数据添加操作
                }
            });
        }
    }
}
</script>

示例代码:

<template>
    <div>
        <h1>菜单信息页</h1>
        <!-- el-form验证时使用的属性
                rules   表单的验证规则
                model   表单验证时使用的数据
         -->
        <el-form 
            label-width="80px" 
            style="width:600px;"
            :rules="rules"
            :model="info"
            ref="menuForm"
        >
            <el-form-item label="菜单名称" prop="title">
                <el-input v-model="info.title"></el-input>
            </el-form-item>
            <el-form-item label="上级菜单" prop="pid">
                <el-select v-model="info.pid" placeholder="请选择">
                    <!-- 
                        value 
                        label   设置选中的选项名称
                     -->
                     <el-option value="">请选择</el-option>
                    <el-option value="0" label="顶级菜单">顶级菜单</el-option>
                    <el-option value="1" label="系统设置">系统设置</el-option>
                </el-select>
            </el-form-item>
            <el-form-item label="菜单图标">
                <el-input v-model="info.icon"></el-input>
            </el-form-item>
            <el-form-item label="菜单地址">
                <el-input v-model="info.address"></el-input>
            </el-form-item>
            <el-form-item label="状态">
                <el-switch v-model="info.status"></el-switch>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="submitInfo('menuForm')">提交</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
export default {
    data(){
        return{
            info:{
                title:'',
                pid:'',
                icon:'',
                address:'',
                status:true
            },
            rules:{
                title:[
                    { required:true,message:'菜单名称不能为空',trigger:'blur' },
                    { min:1,max:20,message:'菜单名称长度不符合要求' }
                ],
                pid:[
                    { required:true,message:'请选择上级菜单' }
                ]
            }
        }
    },
    methods:{
        submitInfo(formName) {
            this.$refs[formName].validate((valid) => {
                if (valid) {
                    //验证规则满足时,才执行数据添加操作
                }
            });
        }
    }
}
</script>

<style scoped>
    .el-form{
        margin:20px;
    }
</style>

3.面包屑

显示当前页面的路径,快速返回之前的任意页面。

el-breadcrumb

el-breadcrumb-item

<el-breadcrumb separator=">">
    <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
    <el-breadcrumb-item>
    	<a href="#/menu">菜单列表</a>
    </el-breadcrumb-item>
    <el-breadcrumb-item>菜单添加</el-breadcrumb-item>
</el-breadcrumb>

可以直接给el-breadcrumb-item组件设置to属性来进行页面的跳转,也可以在其中添加a标签/router-link标签来进行页面的跳转,如果不需要页面跳转,则直接写文字内容即可。

el-breadcrumb-item之间的分隔符默认是斜杠,可以通过separator属性来自行设置分隔符。

4.NavMenu菜单导航

default-active 当前激活菜单的 index

router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转

启用路由模式后,el-menu-item组件的index属性就是要跳转的路由地址,不需要再使用router-link标签也可以实现路由跳转。

但是dafault-active设置为固定值的话,再次刷新页面后,左侧菜单还是选中的固定值的菜单

所以需要把default-active的设置为一个变量

(1)页面加载时

/src/components/views/Nav.vue

<script>
export default {
    data(){
        return{
            defaultActive:''
        }
    },
    mounted(){
        //页面加载时,控住左侧菜单选中效果
        //把当前路由中的meta的自定义属性赋值给默认选中变量
        this.defaultActive = this.$route.meta.select;
    }
}
</script>

由于在路由切换时,信息页面的路由地址并没有在左侧菜单中,可以通过路由的meta属性来自行设置选中哪个左侧菜单

/src/router/index.js

{
    path:'menu',
    component:()=>import('../components/pages/Menu/Index'),
    meta:{select:'/menu'}
},
{
    path:'menu/add',
    component:()=>import('../components/pages/Menu/Info'),
    meta:{select:'/menu'}
}

meta属性是路由信息中内置的一个属性,它的属性值类型为对象,在对象中自定义一个键值对用来告知左侧菜单应该选中哪个即可实现。

(2)路由地址变化时

/src/components/views/Nav.vue

<script>
export default {
	...
	watch:{
        $route(newVal){
            this.defaultActive = newVal.meta.select;
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值