14.状态管理器

 

Vuex 是什么? | Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

状态传值

  • 父组件给子组件传值

  • 子组件给父组件传值

  • 非父子组件(兄弟组件)传值

  • 子组件直接使用父组件的数据和方法

  • 父组件直接使用子组件的数据和方法

  • 跨组件传值

  • 状态管理器

  • 多个视图依赖于同一状态。

  • 来自不同视图的行为需要变更同一状态。

 

14.0 构建首页的头部组件

<template>
  <div class="box">
    <header class="header">
      <ul>
        <li>北京</li>
        <li>搜索框</li>
        <li v-if="true">登录</li>
        <li v-else>我的</li>
      </ul>
    </header>
    <div class="content" ref="content">
    ....
    </div>  
  </div>
</template>
<script>
.....
</script>
​
<style lang='stylus' scoped>
// scoped 代表该样式只在当前组件是有效的,不会影响其他组件的样式
....
.header
  ul
    height 100%
    display flex
    li
      flex 1
</style>
​

14.1 第一版登录状态

异步操作在组件,组件中直接提交mutations

定义状态管理器

// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
​
Vue.use(Vuex)
​
export default new Vuex.Store({
  state: {
    // ? 为什么要从本地存储设置 状态管理器的初始值
    // 因为每次刷新页面都会使 状态管理器 重置
    loginState: localStorage.getItem('loginState') === 'true'
  },
  mutations: {
    changeLoginState (state, payload) { // state 所有的状态  payload 参数
      state.loginState = payload
    }
  },
  actions: {
  },
  modules: {
  }
})
​

登录时保存状态

// 修改状态管理器中的代码 --- 代码不要看图,只看图中的代码位置,实际代码替换如下
this.$store.commit('changeLoginState', true)

底部组件获取登录状态

<template>
  <!-- div.container>div.box>header.header+div.content -->
  <div class="container">
    <router-view></router-view>
    <footer class="footer" v-if="!$route.meta.hidden">
      <ul>
        <router-link to="/home" tag="li">
          <span class="iconfont icon-shouye"></span>
          <p>首页</p>
        </router-link>
        <router-link to="/kind" tag="li">
          <span class="iconfont icon-fenlei"></span>
          <p>分类</p>
        </router-link>
        <router-link to="/cart" tag="li">
          <span class="iconfont icon-gouwuche"></span>
          <p>购物车</p>
        </router-link>
        <router-link v-if="loginState" to="/user" tag="li">
          <span class="iconfont icon-My"></span>
          <p>我的</p>
        </router-link>
        <router-link v-else to="/login" tag="li">
          <span class="iconfont icon-My"></span>
          <p>未登录</p>
        </router-link>
      </ul>
    </footer>
    <div class="tip">请将屏幕竖向浏览</div>
  </div>
</template>
<script>
export default {
  // 使用计算属性获取状态管理器中的状态
  computed: {
    loginState () {
      return this.$store.state.loginState
    }
  }
}
</script>
​
<style lang="stylus">
....
</style>
​
// src/views/home/index.vue
<template>
  <div class="box">
    <header class="header">
      <ul>
        <li>北京</li>
        <li>搜索框</li>
        <router-link tag="li" to="/user" v-if="loginState">我的</router-link>
        <router-link tag="li" to="/login" v-else>登录</router-link>
      </ul>
    </header>
    <div class="content" ref="content">
    ....
    </div>  
  </div>
</template>
<script>
export default {
     computed: {
    loginState () {
      return this.$store.state.loginState
    }
  },
  ....
}
</script>
​
<style lang='stylus' scoped>
// scoped 代表该样式只在当前组件是有效的,不会影响其他组件的样式
....
.header
  ul
    height 100%
    display flex
    li
      flex 1
</style>
​
​

退出登录

<template>
  <div class="box">
    <header class="header">user header</header>
    <div class="content">
​
      <button v-if="loginState" @click="logout">退出</button>
      <button v-else @click="login"> 登录</button>
    </div>
  </div>
</template>
<script>
export default {
  computed: {
    loginState () {
      return this.$store.state.loginState
    }
  },
  methods: {
    logout () {
      localStorage.removeItem('token')
      localStorage.removeItem('userid')
      localStorage.removeItem('loginState')
      this.$store.commit('changeLoginState', false) // 二次渲染的关键
      this.$router.push('/login')
    },
    login () {
      this.$router.push('/login')
    }
  }
}
</script>
​

总结:登录时,异步的登录操作放到了 登录组件中

14.2 第二版登录状态

登录时,异步的登录操作放到了 状态管理器中,通过组件触发状态管理器执行 异步操作

登录的代码拷贝到了 store/index.js中的actions内

import Vue from 'vue'
import Vuex from 'vuex'
import router from '../router'
import { Dialog, Toast } from 'vant'
import { login } from './../api/user'
Vue.use(Vuex)
​
export default new Vuex.Store({
  state: {
    // ? 为什么要从本地存储设置 状态管理器的初始值
    // 因为每次刷新页面都会使 状态管理器 重置
    loginState: localStorage.getItem('loginState') === 'true'
  },
  mutations: {
    changeLoginState (state, payload) { // state 所有的状态  payload 参数
      state.loginState = payload
    }
  },
  actions: {
    // context 上下文对象 ---  store
    // params 参数
    loginAction (context, parmas) {
      login({
        loginname: parmas.loginname,
        password: parmas.password
      }).then(res => {
        if (res.data.code === '10010') {
          // 账户不存在,提醒用户是否要立即注册
          Dialog.confirm({
            message: '该用户还未注册,是否立即注册',
            confirmButtonText: '立即注册',
            confirmButtonColor: '#ff6666',
            cancelButtonText: '取消',
            cancelButtonColor: '#999'
          })
            .then(() => {
              router.push('/register/step1')
            })
            .catch(() => {
              // on cancel
            })
        } else if (res.data.code === '10011') {
          // 提醒用户密码错误, 视情况而定是否需要清空密码输入框 this.password = ''
          Toast('密码错误')
        } else {
          // 登录成功
          Toast('登录成功')
          localStorage.setItem('userid', res.data.data.userid) // 知道是谁
          localStorage.setItem('token', res.data.data.token) // 后端验证用户的登录状态
          localStorage.setItem('loginState', true) // 前端自检登录状态
​
          // 使用状态管理器修改状态
          context.commit('changeLoginState', true) // 修改状态管理器中的loginState的值为true
          // 返回上一页
          router.back()
        }
      })
    }
  },
  modules: {
  }
})
​
<template>
  <div class="box">
    <header class="header">
      <van-nav-bar
        title="嗨购登录"
        left-arrow
        @click-left="$router.back()"
      ></van-nav-bar>
    </header>
    <div class="content">
      <div class="form" v-if="type === '1'">
        <van-field v-model="loginname" placeholder="用户名/手机号/邮箱" clearable/>
        <van-field v-model="password" type="password" placeholder="请输入密码" clearable/>
        <p class="passwordTip" v-if="passwordTipFlag">输入至少6位,包含至少一个大写字母,1个小写字母,1个数字</p>
        <div class="my-button">
          <van-button color="#ff6666" :disabled="adminameFlag"  block round @click="adminameLoginFn">登录</van-button>
        </div>
      </div>
      <div class="form" v-if="type === '0'">
        <van-field v-model="tel" type="tel" placeholder="手机号码" clearable/>
        <van-field
          v-model="telcode"
          center
          clearable
          placeholder="请输入短信验证码"
        >
          <template #button>
            <van-button size="small" >发送短信验证码</van-button>
          </template>
        </van-field>
        <div class="my-button">
          <van-button color="#ff6666"  block round>登录</van-button>
        </div>
      </div>
      <!-- 登录方式以及 注册提示 -->
      <ul class="more">
        <li @click="changeType">
          <span v-if="type === '1'">短信验证码登录</span>
          <span v-else>账号密码登录</span>
        </li>
        <router-link to="/register/step1" tag="li">
          手机快速注册
        </router-link>
      </ul>
      <div class="my-divider">
        <van-divider>其他登录方式</van-divider>
      </div>
      <div class="my-login-type">
        <van-row type="flex" justify="center">
          <van-col span="6">
            <van-image :src="qq" width="48" height="48"/>
            <p>QQ</p>
          </van-col>
          <van-col span="6">
            <van-image :src="wx" width="48" height="48"/>
            <p>微信</p>
          </van-col>
          <van-col span="6">
            <van-image :src="apple" width="48" height="48"/>
            <p>苹果</p>
          </van-col>
        </van-row>
      </div>
    </div>
  </div>
</template>
<script>
import Vue from 'vue'
import { NavBar, Field, Button, Divider, Col, Row, Image as VanImage } from 'vant'
​
Vue.use(NavBar)
Vue.use(Field)
Vue.use(Button)
Vue.use(Divider)
Vue.use(Col)
Vue.use(Row)
Vue.use(VanImage)
export default {
  data () {
    return {
      qq: 'data:image/png;base64,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',
      wx: 'data:image/png;base64,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',
      apple: 'data:image/png;base64,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',
      tel: '',
      password: '',
      loginname: '',
      telcode: '',
      type: '1' // 1表示账户名密码 0表示手机验证码
    }
  },
  computed: {
    adminameFlag () {
      if (this.loginname !== '' && /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    },
    passwordTipFlag () {
      if (this.password === '' || /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    }
  },
  methods: {
    changeType () {
      this.type = this.type === '1' ? '0' : '1'
    },
    adminameLoginFn () {
      console.log('1111111')
      // 异步操作
      this.$store.dispatch('loginAction', {
        loginname: this.loginname,
        password: this.password
      })
    }
  }
}
</script>
​
<style lang="stylus">
.container .box .content
  padding 30px 15px
  background-color #ffffff
​
.form
  .my-button
    margin-top 30px
.more
  margin-top 20px
  display flex
  li
    flex 1
    &:nth-child(1)
      text-align left
    &:nth-child(2)
      text-align right
.my-divider
  margin-top 80px
.passwordTip
  color #f66
  font-size 12px
</style>
​

异步操作在组件,组件中通过

this.$store.commit()改变数据

异步操作在状态管理器,组件中通过

this.$store.dispatch() 触发,通过其内部的 context.commit() 改变数据

组件中通过this.$store.state获取状态,可以配合计算属性使用

14.3 使用辅助函数mapState获取状态

之前的案例是 通过 计算属性 计算得到 状态,通过 this.$store.state获取得到

mapState的是一个对象或者数组

// App.vue

<template>
  <!-- div.container>div.box>header.header+div.content -->
  <div class="container">
    <!-- <div class="box">
      <header class="header">header</header>
      <div class="content">content</div>
    </div> -->
    <router-view></router-view>
    <footer class="footer" v-if="!$route.meta.hidden">
      <ul>
        <router-link to="/home" tag="li">
          <span class="iconfont icon-shouye"></span>
          <p>首页</p>
        </router-link>
        <router-link to="/kind" tag="li">
          <span class="iconfont icon-fenlei"></span>
          <p>分类</p>
        </router-link>
        <router-link to="/cart" tag="li">
          <span class="iconfont icon-gouwuche"></span>
          <p>购物车</p>
        </router-link>
        <router-link v-if="loginState" to="/user" tag="li">
          <span class="iconfont icon-My"></span>
          <p>我的</p>
        </router-link>
        <router-link v-else to="/login" tag="li">
          <span class="iconfont icon-My"></span>
          <p>未登录</p>
        </router-link>
      </ul>
    </footer>
    <div class="tip">请将屏幕竖向浏览</div>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  // 使用计算属性获取状态管理器中的状态
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.loginState
    })
  }
}
</script>
​
<style lang="stylus">
...
</style>
​

// src/views/home/index.vue

<template>
  <div class="box">
    ....
  </div>
</template>
<script>
...
import { mapState } from 'vuex'
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.loginState
    })
  },
  ....
}
</script>
​
<style lang='stylus' scoped>
/...
</style>
​

// src/views/user/index.vue

<template>
  <div class="box">
    <header class="header">user header</header>
    <div class="content">
​
      <button v-if="loginState" @click="logout">退出</button>
      <button v-else @click="login"> 登录</button>
    </div>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.loginState
    })
  },
  methods: {
    logout () {
      localStorage.removeItem('token')
      localStorage.removeItem('userid')
      localStorage.removeItem('loginState')
      this.$store.commit('changeLoginState', false) // 二次渲染的关键
      this.$router.push('/login')
    },
    login () {
      this.$router.push('/login')
    }
  }
}
</script>
​

14.4 使用辅助函数mapMutations 改变状态

这个辅助函数使用场景: 组件中去改变 mutation

// views/user/index.vue

<template>
  <div class="box">
    <header class="header">user header</header>
    <div class="content">
​
      <button v-if="loginState" @click="logout">退出</button>
      <button v-else @click="login"> 登录</button>
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.loginState
    })
  },
  methods: {
    ...mapMutations({
      changeLoginState: 'changeLoginState' // key随意 - 事件,value即为mutation中的值,一般情况下二者相同
    }),
    logout () {
      localStorage.removeItem('token')
      localStorage.removeItem('userid')
      localStorage.removeItem('loginState')
      // this.$store.commit('changeLoginState', false) // 二次渲染的关键
      this.changeLoginState(false) // 事件中通过 this.key事件(参数)
      this.$router.push('/login')
    },
    login () {
      this.$router.push('/login')
    }
  }
}
</script>
​

14.5 使用辅助函数 mapActions 触发 acitons

组件触发 action,aciton提交mutation

// views/login/index.vue

<template>
  <div class="box">
    <header class="header">
      <van-nav-bar
        title="嗨购登录"
        left-arrow
        @click-left="$router.back()"
      ></van-nav-bar>
    </header>
    <div class="content">
      <div class="form" v-if="type === '1'">
        <van-field v-model="loginname" placeholder="用户名/手机号/邮箱" clearable/>
        <van-field v-model="password" type="password" placeholder="请输入密码" clearable/>
        <p class="passwordTip" v-if="passwordTipFlag">输入至少6位,包含至少一个大写字母,1个小写字母,1个数字</p>
        <div class="my-button">
          <van-button color="#ff6666" :disabled="adminameFlag"  block round @click="adminameLoginFn">登录</van-button>
        </div>
      </div>
      <div class="form" v-if="type === '0'">
        <van-field v-model="tel" type="tel" placeholder="手机号码" clearable/>
        <van-field
          v-model="telcode"
          center
          clearable
          placeholder="请输入短信验证码"
        >
          <template #button>
            <van-button size="small" >发送短信验证码</van-button>
          </template>
        </van-field>
        <div class="my-button">
          <van-button color="#ff6666"  block round>登录</van-button>
        </div>
      </div>
      <!-- 登录方式以及 注册提示 -->
      <ul class="more">
        <li @click="changeType">
          <span v-if="type === '1'">短信验证码登录</span>
          <span v-else>账号密码登录</span>
        </li>
        <router-link to="/register/step1" tag="li">
          手机快速注册
        </router-link>
      </ul>
      <div class="my-divider">
        <van-divider>其他登录方式</van-divider>
      </div>
      <div class="my-login-type">
        <van-row type="flex" justify="center">
          <van-col span="6">
            <van-image :src="qq" width="48" height="48"/>
            <p>QQ</p>
          </van-col>
          <van-col span="6">
            <van-image :src="wx" width="48" height="48"/>
            <p>微信</p>
          </van-col>
          <van-col span="6">
            <van-image :src="apple" width="48" height="48"/>
            <p>苹果</p>
          </van-col>
        </van-row>
      </div>
    </div>
  </div>
</template>
<script>
import Vue from 'vue'
import { NavBar, Field, Button, Divider, Col, Row, Image as VanImage } from 'vant'
import { mapActions } from 'vuex'
Vue.use(NavBar)
Vue.use(Field)
Vue.use(Button)
Vue.use(Divider)
Vue.use(Col)
Vue.use(Row)
Vue.use(VanImage)
export default {
  data () {
    return {
      qq: 'data:image/png;base64,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',
      wx: 'data:image/png;base64,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',
      apple: 'data:image/png;base64,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',
      tel: '',
      password: '',
      loginname: '',
      telcode: '',
      type: '1' // 1表示账户名密码 0表示手机验证码
    }
  },
  computed: {
    adminameFlag () {
      if (this.loginname !== '' && /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    },
    passwordTipFlag () {
      if (this.password === '' || /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    }
  },
  methods: {
    ...mapActions({
      loginAction: 'loginAction'
    }),
    changeType () {
      this.type = this.type === '1' ? '0' : '1'
    },
    adminameLoginFn () {
      console.log('1111111')
      // 异步操作
      // this.$store.dispatch('loginAction', {
      //   loginname: this.loginname,
      //   password: this.password
      // })
      this.loginAction({
        loginname: this.loginname,
        password: this.password
      })
    }
  }
}
</script>
​
<style lang="stylus">
.container .box .content
  padding 30px 15px
  background-color #ffffff
​
.form
  .my-button
    margin-top 30px
.more
  margin-top 20px
  display flex
  li
    flex 1
    &:nth-child(1)
      text-align left
    &:nth-child(2)
      text-align right
.my-divider
  margin-top 80px
.passwordTip
  color #f66
  font-size 12px
</style>
​

14.6 getters以及ma pGetters

// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import router from '../router'
import { Dialog, Toast } from 'vant'
import { login } from './../api/user'
Vue.use(Vuex)
​
export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5],
    // ? 为什么要从本地存储设置 状态管理器的初始值
    // 因为每次刷新页面都会使 状态管理器 重置
    loginState: localStorage.getItem('loginState') === 'true'
  },
  getters: { // 可以看作是state的计算属性,类似于computed
    len: state => state.list.length
  },
  mutations: {
    changeLoginState (state, payload) { // state 所有的状态  payload 参数
      state.loginState = payload
    }
  },
  actions: {
    // context 上下文对象 ---  store
    // params 参数
    loginAction (context, parmas) {
      login({
        loginname: parmas.loginname,
        password: parmas.password
      }).then(res => {
        if (res.data.code === '10010') {
          // 账户不存在,提醒用户是否要立即注册
          Dialog.confirm({
            message: '该用户还未注册,是否立即注册',
            confirmButtonText: '立即注册',
            confirmButtonColor: '#ff6666',
            cancelButtonText: '取消',
            cancelButtonColor: '#999'
          })
            .then(() => {
              router.push('/register/step1')
            })
            .catch(() => {
              // on cancel
            })
        } else if (res.data.code === '10011') {
          // 提醒用户密码错误, 视情况而定是否需要清空密码输入框 this.password = ''
          Toast('密码错误')
        } else {
          // 登录成功
          Toast('登录成功')
          localStorage.setItem('userid', res.data.data.userid) // 知道是谁
          localStorage.setItem('token', res.data.data.token) // 后端验证用户的登录状态
          localStorage.setItem('loginState', true) // 前端自检登录状态
​
          // 使用状态管理器修改状态
          context.commit('changeLoginState', true) // 修改状态管理器中的loginState的值为true
          // 返回上一页
          router.back()
        }
      })
    }
  },
  modules: {
  }
})
​

// src/views/user/index.vue

<template>
  <div class="box">
    <header class="header">user header</header>
    <div class="content">
​
      <button v-if="loginState" @click="logout">退出</button>
      <button v-else @click="login"> 登录</button>
      {{ listLen }}
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations, mapGetters } from 'vuex'
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.loginState
    }),
    // listLen () {
    //   return this.$store.getters.len
    // }
    ...mapGetters({
      listLen: 'len'
    })
  },
  methods: {
    ...mapMutations({
      changeLoginState: 'changeLoginState' // key随意 - 事件,value即为mutation中的值,一般情况下二者相同
    }),
    logout () {
      localStorage.removeItem('token')
      localStorage.removeItem('userid')
      localStorage.removeItem('loginState')
      // this.$store.commit('changeLoginState', false) // 二次渲染的关键
      this.changeLoginState(false) // 事件中通过 this.key事件(参数)
      this.$router.push('/login')
    },
    login () {
      this.$router.push('/login')
    }
  }
}
</script>
​

15 分模块的状态管理器

便于团队的开发协作

vue的实例中 可以 使用 computed 计算属性

vuex 中可以 使用 getters 计算属性

15.1 构建用户的相关的模块

// store/modules/user.js
import router from '../../router'
import { Dialog, Toast } from 'vant'
import { login } from './../../api/user'
export default {
  namespaced: true, // 命名空间,作用很大,用来标识是哪一个模块
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5],
    // ? 为什么要从本地存储设置 状态管理器的初始值
    // 因为每次刷新页面都会使 状态管理器 重置
    loginState: localStorage.getItem('loginState') === 'true'
  },
​
  mutations: {
    changeLoginState (state, payload) { // state 所有的状态  payload 参数
      state.loginState = payload
    }
  },
  actions: {
    // context 上下文对象 ---  store
    // params 参数
    loginAction (context, parmas) {
      login({
        loginname: parmas.loginname,
        password: parmas.password
      }).then(res => {
        if (res.data.code === '10010') {
          // 账户不存在,提醒用户是否要立即注册
          Dialog.confirm({
            message: '该用户还未注册,是否立即注册',
            confirmButtonText: '立即注册',
            confirmButtonColor: '#ff6666',
            cancelButtonText: '取消',
            cancelButtonColor: '#999'
          })
            .then(() => {
              router.push('/register/step1')
            })
            .catch(() => {
              // on cancel
            })
        } else if (res.data.code === '10011') {
          // 提醒用户密码错误, 视情况而定是否需要清空密码输入框 this.password = ''
          Toast('密码错误')
        } else {
          // 登录成功
          Toast('登录成功')
          localStorage.setItem('userid', res.data.data.userid) // 知道是谁
          localStorage.setItem('token', res.data.data.token) // 后端验证用户的登录状态
          localStorage.setItem('loginState', true) // 前端自检登录状态
​
          // 使用状态管理器修改状态
          context.commit('changeLoginState', true) // 修改状态管理器中的loginState的值为true
          // 返回上一页
          router.back()
        }
      })
    }
  }
}
​
​
​
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
​
export default new Vuex.Store({
  getters: { // 可以看作是state的计算属性,类似于computed
    len: state => state.user.list.length
  },
  modules: { // 整合模块
    user
  }
})
​

Src/App.vue

<template>
  <!-- div.container>div.box>header.header+div.content -->
  <div class="container">
    <!-- <div class="box">
      <header class="header">header</header>
      <div class="content">content</div>
    </div> -->
    <router-view></router-view>
    <footer class="footer" v-if="!$route.meta.hidden">
      <ul>
        <router-link to="/home" tag="li">
          <span class="iconfont icon-shouye"></span>
          <p>首页</p>
        </router-link>
        <router-link to="/kind" tag="li">
          <span class="iconfont icon-fenlei"></span>
          <p>分类</p>
        </router-link>
        <router-link to="/cart" tag="li">
          <span class="iconfont icon-gouwuche"></span>
          <p>购物车</p>
        </router-link>
        <router-link v-if="loginState" to="/user" tag="li">
          <span class="iconfont icon-My"></span>
          <p>我的</p>
        </router-link>
        <router-link v-else to="/login" tag="li">
          <span class="iconfont icon-My"></span>
          <p>未登录</p>
        </router-link>
      </ul>
    </footer>
    <div class="tip">请将屏幕竖向浏览</div>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  // 使用计算属性获取状态管理器中的状态
  computed: {
    // loginState () {
    //   return this.$store.state.user.loginState
    // }
    ...mapState({
      loginState: state => state.user.loginState
    })
  }
}
</script>
​
<style lang="stylus">
...
</style>
​

// src/views/home/index.vue

<template>
  <div class="box">
   ....
</template>
<script>
import Vue from 'vue'
import { Swipe, SwipeItem, Grid, GridItem, Image as VanImage, CountDown, Icon, List, PullRefresh } from 'vant'
import { getBannerList, getSeckillList, getProList } from '@/api/home' // @ 代表的就是src的目录
import ProList from '@/components/ProList'
import { mapState } from 'vuex'
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)
Vue.use(GridItem)
Vue.use(VanImage)
Vue.use(CountDown)
Vue.use(Icon)
Vue.use(List)
Vue.use(PullRefresh)
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.loginState
    // }
    ...mapState({
      loginState: state => state.user.loginState
    })
  },
  ....
}
</script>
​
<style lang='stylus' scoped>
....
</style>
​

15.2 状态管理器中使用模块

15.3 登录时改变状态

Src/views/login/index.vue

<template>
  <div class="box">
    <header class="header">
      <van-nav-bar
        title="嗨购登录"
        left-arrow
        @click-left="$router.back()"
      ></van-nav-bar>
    </header>
    <div class="content">
      <div class="form" v-if="type === '1'">
        <van-field v-model="loginname" placeholder="用户名/手机号/邮箱" clearable/>
        <van-field v-model="password" type="password" placeholder="请输入密码" clearable/>
        <p class="passwordTip" v-if="passwordTipFlag">输入至少6位,包含至少一个大写字母,1个小写字母,1个数字</p>
        <div class="my-button">
          <van-button color="#ff6666" :disabled="adminameFlag"  block round @click="adminameLoginFn">登录</van-button>
        </div>
      </div>
      <div class="form" v-if="type === '0'">
        <van-field v-model="tel" type="tel" placeholder="手机号码" clearable/>
        <van-field
          v-model="telcode"
          center
          clearable
          placeholder="请输入短信验证码"
        >
          <template #button>
            <van-button size="small" >发送短信验证码</van-button>
          </template>
        </van-field>
        <div class="my-button">
          <van-button color="#ff6666"  block round>登录</van-button>
        </div>
      </div>
      <!-- 登录方式以及 注册提示 -->
      <ul class="more">
        <li @click="changeType">
          <span v-if="type === '1'">短信验证码登录</span>
          <span v-else>账号密码登录</span>
        </li>
        <router-link to="/register/step1" tag="li">
          手机快速注册
        </router-link>
      </ul>
      <div class="my-divider">
        <van-divider>其他登录方式</van-divider>
      </div>
      <div class="my-login-type">
        <van-row type="flex" justify="center">
          <van-col span="6">
            <van-image :src="qq" width="48" height="48"/>
            <p>QQ</p>
          </van-col>
          <van-col span="6">
            <van-image :src="wx" width="48" height="48"/>
            <p>微信</p>
          </van-col>
          <van-col span="6">
            <van-image :src="apple" width="48" height="48"/>
            <p>苹果</p>
          </van-col>
        </van-row>
      </div>
    </div>
  </div>
</template>
<script>
import Vue from 'vue'
import { NavBar, Field, Button, Divider, Col, Row, Image as VanImage } from 'vant'
import { mapActions } from 'vuex'
Vue.use(NavBar)
Vue.use(Field)
Vue.use(Button)
Vue.use(Divider)
Vue.use(Col)
Vue.use(Row)
Vue.use(VanImage)
export default {
  data () {
    return {
      qq: 'data:image/png;base64,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',
      wx: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAD5VJREFUeAHtXXuMXUUZn5lz72532W27t9CWttKuGjSUmEBaBQxYBLYIVVEpAfyDSkxIS4xGIgE1ppqgCSRq4qMSDVajxnSFmIBAF0jrgyKKaLAlhKBtTVvabXt3u7vsdveeM+Pvd9qzO+fcs3fv45y7t+29fZx5fvPN75v5zsw3jyNFg/42b96sei/pzQy0DWROOiczrnIzWmtHOEIax0jjGiWUkD77WhiZkVp60ghPGKWUl9EZd443x+0a63LXv77eBT3diFU9VYEG4Gyz2ay27tzacsI90VrQhVYtdSZJtpRRblZlx+dl5o1vWLNhYrNsDIHMqgAI+pa+LW2jLaNtXsFrSRLwmWg5WWeifaJ9bGPPxrHZFEbdBWCMkd07u1vz4/l2qJU5MwFVj3ioq5O51tzo3jV7x6WEGqvjr24CIPBLn1raNtQy1JG0ekkKL6qpuRNzRw6uOzhWL0GkLgACv7hvcfuIM9JhtHGSAitNOlJJr8PrGDncc3g0bUGkKoCVe1a27D+wf36jtviZhMgesXzZ8sE9K/dMzJS22vhUBLB+23qnr71vbqGl0FYtY42ULzuRHesZ7Rnqva3XS5qvxAWwbNuytvzc/PzJMXrSHM8WPcw1ckO5wQO3HRhLkoXEBEBdn3s+N3fcjJ+XJIONRqtVtr6Tvz4/lNS7IREBrNmxJvPK2CtdOqOzjQZYGvwoVxVWta0a2HntTrdW+jULYOW2lS37LtiXMwWYBs6hn8xKveLoivye22p7QdckgBU7Vszpd/q7xMnTNplzSAB+VecIs9BbOLDv2n0nq6161QJY8uSS9sHWwfnVFnw25Zs/Pn/w0McPjVZTp6oE0AS/GOpqhVCx3qbaabb8YgEQE2JTHFM6pCIB8IXr6/zSNM/ZWGJDjCoBoGwBcKjJ0c45+8ItB1UMRogRsSonOdOU9Q7gJKvzmc7zz5VxfrngTZeO84Thjw0fK2eyVlYP4Ay3Cf50cBeHEytiVhxTHDKjAGjbOdvNC8Ww1B5CzIjdTJRKCoBWTd+wNhOVZnwsAsSOGMZGng4s+Q6Y99S8rrPFpFwKhDTjaMo+se7EwHRlTNsDuJjSBH862MoPJ4bEcrocsQLgqIcrWdNlaoZXhgCxJKZxuWIFwDXcM3UZMa6Ssx1GLIlpHB9FEwZKquOFjg7RIPvItNGtUunLhZRXgqflQskFUpucwVMYs8CvlJTHEXYcYXmBJ1bj9iPuJaPVq0qq8biK1zvM35RgTNEif1G3aARDG0B/v3TMTdhweJUQ5nKA1VolYABfvorNjLuMJ5+GMN6okk4i2eIMdiEB+DPevs4LZkv9oNzrwdDnAfrVidS4iIj8M3Zd/Qy7HZ4viqpDAHdZDPcMH7VnyCEB+Asshf5cHXgJFWEc706h5SYA3x2KSM/zX6ipLdJzfpNeEfGUF2YX5u0FnJAA5j47N1fP7YJGTSwXJvNdAH9FPLunQ6X4D9TRy0j3CtTJ21rKvOPpvG5z8kyhxryc56icMiaHdBdC7axCug8JI95Tkq6QLwnp3id1y/7S6ZKL5TbIoRuHfL5JdVIA3Cj78HMPL06uqNKUtHQ/J4X8GlJNM12XL0plfl2QelfWzR4tTS0+tpApXJA16iqj5WchvA/HpxJjRpiHlMn8fJr4xIPvv+H+w8GG4EkBLNq+6LxhOTwv8dIiBLWjs0qbR6GL10aigJEYxWjnccQ9Bn35ZlF8DQF4v1yMyt6NQm4FmSKhI267VvIe5alCDcWUlbXTdJ44svbIO0w8OQ/gFvGycteQyB9SGvNYHPhSiudco6+RRj2QNPh+RSFQ0na1vpplRatBniR4I4/RuKT9Nta+AKh+0t6fr5WeIx2xFa38o3aFoIaOo/VtEtrZkJXZt+24NNx+GSiLZbLsUBngjTyS11B4wh5iTcxJFnwIUZfRj/SKW74Ub7pZfXt2PHuEfNT7V2gtLMoU1G/RKC62y6Y6Esa52w5L2h2Mhnwp8FhQ0gXY9HBwa31U7aCS//aU+fRsgU/+WDZ5IC9hfsVa8myHJe0OMPcFwDNZSRcQ0APtxVKJbwZ+PlHh1zxXrM+4mWnNtHb6NN3kgbyQJ7sc8kze7bAk3QHmiqcR05z5ZjLqEWPE1OgKIx3jqE2O4wwnWaFaaJEX8uSPwk4TIs/kvRa6pfISc2LvHN10NDs4ZzDWUleKQDlxxnExGZIPhNIq83XMQP8UCmsADw65Dgql0SPlDRY73cLRL2L0dNAKS8x5qPPQScVzuIlRjBBCpe4JBUm5C4L/VSgs4uE8Afr3q0JqGNH0q3QzLJJsRm81dHzewKNNvKgOdmSNbmKveAi6Rjqx2V3pzsMSxHV2JPw/s/1xbumZr+AlcS9mp4v4j24/LC5xibBq6UR5ZB1YlxJFVR1F7LGFxU1FAI6S10OnTtLGmPuIdOWMVkhYCjlTDf3iwkIJYjxxeeLColnJI3mdDEcd/LpMBiTnIPbKP/6fHM0pSlqunvLAAGBMLwBI/IyVXUYSbvJIXkO0InUJxdXgIfaKdy/UQGParDjvvNKOxLBut+2fzo3K/y4aFxcWTRP1x+WJC4vmoz/Ka7QucXmqCgP2GV58kcryI85l07oW/Dwp/ldyg8zphMaRj6ANwiZ3ShURtFNhAaXynrXQIa9qinVUg3VJ/kfsZfuz7RfCJJd4LzDKewv4Txr4PM9cmlGzP/EqB0ZXu12OI6d6rBRjUjvvLSdvRWlw8nLqypeKcpaRGMTtVLJVNsTiuM3TdG7ZJsMHsw0UdRo/NHzfFJEGbdA8ZNPFeHqJ7W9ktxyXF9n8YVQ0ZPuTdCvo/1BLTYy4lKHZI140qejRxPi1CTniXbYXy5v9IX9SHqog3jSVFL0InVAPEFp9IBLfsF7lhc3TRovX02CW2ONekJTux5FYQLd/0nzK9jay2yjzGZs/+P9p+5NyE3vFO9aSImjTwajnafinXrxGvA8rTZfYaRrSLd3VQCS0QIM17L5UeD11vx06XAq/jMwMYWy7wyaNNdd7bX8jurEz864QX1KiJ2fD6jSUoHoPLxdUvF2wehKlc2ojH4+kuMUYN6Vdb5GSqvBi49rVmBPdYmeFevip7U/STewVr3ZMkqhNC5OMpzGj/YcdJpT6TtqL3qHyyvTQ4imF+h7Ujz0pfUM6kqo0lR+xV7xXMxXqp4lCzT0Ip6XmTLcS5icwMaQzuamiMjQJZKTEyp3BrrrTPylcrcQXJXaGBUFJP4m94qWmSRO26Tna2YMm9ZgdhuW+G7D69H07bLbcfkPQ+ofg6eYQD1L8wPGcKXNEKDIZD7H3u1vH9o6Faa4Le1mv3XEFd7yF5gIwAz6qtPOtZKpTORWd1S3K9Xfp9di5sXFrjzHqJmlkao2TO6VH1o70+6YI3ihrM5C02yk4o65j7oR+fcumjRnIPSZTtx3RdtHYjK0vVp55Ao0iBD6M0XuF1hvSBJ+MBJj7AuB1viHuUvBw+wfMEXfArjK5M9gvRnvhaX8KZdsk/bVi5X1ZCrMdaucyOw7ut9wWD5OwdIaddlkB5r4AeJeyHZmWWysnD50b3vZnnANplWfT5cgLJvI7sNbwPHrifYhrseMx9nnTdfSt9dooFmA+OeTq3NF5ftr7Q4XjfRJ2lR9PVhwGO1hDPhj4jSxcJJWzDgbCJRiPH8QIaheGsq/VYi7BPevvxu4K9rw7IPyuoKzQU8lez9Xf4OQxFJ6Sh/dWD187fIzkJxfNeZE1tqeHW0XCDGCIfbu9SgZAnmQL8KR3GcakX4J1/DrcrnuqUWDwx+6J5cC8kfovaKF/1Frs1q36aGYiczyqozmURAOaj3Ngi6SjV0HJX4EbcLEvySxBi8df/Bf5QSiHtRH3Yz76QiZVy3y4YB9rcWpf2mQPSPuARsEULswo9Tew4qs9nyUlvoAX8Sf8YWmYx9I+KHAwfgK96RgEA7fMAWCeay53bjEByW7zPP1QvVq9XaHYAxpMkOYRJa3cu9Bqv20zQvDwZ7IRhOJS8EBQx6CSfgHV9kuYAXwVkEIxJUlGjyhNqiDm4hXu/YX+8EuyJLnyI6VUl0EdhDPEgQ/jFyTyDFTRpTBjXAMJLQhnqtAnxTB62cso+Q/4JMTvVcGZCFW6QnK1JifGQ2LqVRPihffnd/Z1umlMynCQujMCf6Qu8u94WT6Mxe9djPC7BTJox7sU6v0jcF6J3rICq1PL8IzbqqjRwgfQwnngYz8o/NVz/Jf4bgFzAunJQqTIOns5+fK/UWB1+pAA0OIMDmqPpHEpH042vgRz9I3ROgOYf6GVP4IPw+zkHoHo77Q5YDfS/YhxmLgpz/NyjjHtRjvtGEawwRx3jDOItznwn6LgpGbFmSqjEhe/TUCM7TxsGKEfRia8qmBh0nf9GxxXFJ5+EEPMDWDiJN6cfWjxT0gv82KIgbPUw28SjFw30j+jAFj/ep2YPEuxjq2WfTLSTlDUAxjJXoB3waxdWWAzeDa4qfujVxQE9SpWuohhN+GXI4JEzWdtCBDLqOoJKMb2gCCyeWVZgET1z6qvLGOR/GxHahu3qq/TmZMTG698DEtwXLIHMJ//SZL5+XgjVgnCzShMbAdzAzN98mRGARDIrue65jXvDq2sSfFTJwM3DJyYKVfsSziaid9M4XW80fCmPx4BYkXM4mPDoWUJgG9wfjOFn+0IZ2/6oggQIx+ryIw3mi7wl6WCgsS8mn3v4r0LmjeoB4hEnvikSffh7uOVfFemIgGwuLpc7BGp15niDS7gqITfslSQTZD3nfH2Pzus6RaCmBCbSrGouAcEBTTC9ZYBL7P9jLuOslyeqhYAC/DVUfMzVjV9xqpiFWRLll2OL51zcXTEOrPu1agdG8OaBEBCfOOvFquPnUvzBNaVda5ktGODbrtrUkE2IZqwmx/ztBEpz11zDwiK4WSNU2/aP85KAx4Ma6wb6zidaTnAopJnYgIICqXx6eahm/tphg3CzvQn68I6zWRYq6aeiamguMKbnzSPQyUclqoAWBTfDfx4gX9/vm6cUzFhGMI+LqB3eB0jh3sOF933H05Zuy91AQQsUhBLn1raNtQy1JHGvqOgnFqeXLvl1pGD6w6OJannS/FUNwEETFAQ3Tu7W/Pj+fZ63tQelB/35HZB7ljzN02VacWMo1NNWN0FYDPJDcFb+ra08S7l1LfG2wXDzS3i3KW8sWfjWHCTeSRJXbyzKgC7hhTG1p1bW3ijLC81TVpNUb3wWBBPpvBwxGyCbte7YQRgM0U3LzXtvaQ3w6sdebsgL7jz77fDNV88C2Bc664jjNF58YV/kOPU8X+Ph6B5DpdHQXkaEfQacjHp/9O9fBVNexFBAAAAAElFTkSuQmCC',
      apple: 'data:image/png;base64,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',
      tel: '',
      password: '',
      loginname: '',
      telcode: '',
      type: '1' // 1表示账户名密码 0表示手机验证码
    }
  },
  computed: {
    adminameFlag () {
      if (this.loginname !== '' && /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    },
    passwordTipFlag () {
      if (this.password === '' || /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])\S*$/.test(this.password)) {
        return false
      } else {
        return true
      }
    }
  },
  methods: {
    ...mapActions({
      loginAction: 'user/loginAction'
    }),
    changeType () {
      this.type = this.type === '1' ? '0' : '1'
    },
    adminameLoginFn () {
      console.log('1111111')
      // 异步操作
      // this.$store.dispatch('user/loginAction', {
      //   loginname: this.loginname,
      //   password: this.password
      // })
      this.loginAction({
        loginname: this.loginname,
        password: this.password
      })
    }
  }
}
</script>
​
<style lang="stylus">
.container .box .content
  padding 30px 15px
  background-color #ffffff
​
.form
  .my-button
    margin-top 30px
.more
  margin-top 20px
  display flex
  li
    flex 1
    &:nth-child(1)
      text-align left
    &:nth-child(2)
      text-align right
.my-divider
  margin-top 80px
.passwordTip
  color #f66
  font-size 12px
</style>
​

15.4 退出时改变状态

s r c/views/user/idnex.vue

<template>
  <div class="box">
    <header class="header">user header</header>
    <div class="content">
​
      <button v-if="loginState" @click="logout">退出</button>
      <button v-else @click="login"> 登录</button>
      {{ listLen }}
    </div>
  </div>
</template>
<script>
import { mapState, mapMutations, mapGetters } from 'vuex'
export default {
  computed: {
    // loginState () {
    //   return this.$store.state.user.loginState
    // }
    ...mapState({
      loginState: state => state.user.loginState
    }),
    // listLen () {
    //   return this.$store.getters.len
    // }
    ...mapGetters({
      listLen: 'len'
    })
  },
  methods: {
    ...mapMutations({
      changeLoginState: 'user/changeLoginState' // key随意 - 事件,value即为mutation中的值,一般情况下二者相同
    }),
    logout () {
      localStorage.removeItem('token')
      localStorage.removeItem('userid')
      localStorage.removeItem('loginState')
      // this.$store.commit('user/changeLoginState', false) // 二次渲染的关键
      this.changeLoginState(false) // 事件中通过 this.key事件(参数)
      this.$router.push('/login')
    },
    login () {
      this.$router.push('/login')
    }
  }
}
</script>
​

16.使用状态管理器管理购物车的数据 - 作业

16.1 添加模块

s r c/store/cart.js

import { getCartList } from './../../api/cart'
export default {
  namespaced: true,
  state: {
    cartList: []
  },
  mutations: {
    changeCartList (state, data) {
      state.cartList = data
    }
  },
  actions: {
    getCartListAction (context, params) {
      return new Promise(resolve => {
        // 后续的操作交给组件
        getCartList(params).then(res => {
          resolve(res)
        })
      })
    }
  }
}
​

16.2 注册模块

// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
Vue.use(Vuex)
​
export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  getters: { // 可以看作状状态管理器的计算属性
    showNum (state) { // 控制底部选项卡中数量显示还是不显示
      // state 这个参数其实就是 所有的状态
      return state.user.isLogin
    },
   totalNum (state) { // 判断很关键
      return state.cart.cartList && state.cart.cartList.reduce((sum, item) => {
        return item.flag ? sum + item.num : sum + 0
      }, 0)
    },
    totalPrice (state) {
      return state.cart.cartList && state.cart.cartList.reduce((sum, item) => {
        return item.flag ? sum + item.originprice * item.num : sum + 0
      }, 0) * 100
    }
  },
  modules: {
    user,
    cart
  }
})
​

16.3 组件使用状态

s r c/views/cart/index.vue

<template>
  <div class="box">
    <header class="header">
      <van-nav-bar
        :title="'购物车-' + totalNum"
        left-arrow
        @click-left="$router.back()"
      />
    </header>
    <div class="content">
      <div v-if="empty">
        <van-empty description="购物车空空如也">
          <router-link to="/kind">
            <van-button round type="danger" class="bottom-button">
              立即选购
            </van-button>
          </router-link>
        </van-empty>
      </div>
      <div v-else>
        <van-row v-for="item of cartList" :key="item.cartid">
          <van-col span="3" class="checkboxList">
            <van-checkbox @change="updateFlag(item.cartid, item.flag)" v-model="item.flag"></van-checkbox>
          </van-col>
          <van-col span="21">
            <van-swipe-cell>
              <van-card
                :price="item.originprice"
                :title="item.proname"
                :thumb="item.img1"
              >
                <template #num>
                  <van-stepper @change="updateNum(item.cartid, item.num)" v-model="item.num" max="5" theme="round" button-size="22" />
                </template>
              </van-card>
              <template #right>
                <van-button @click="removeItem(item.cartid)" square text="删除" type="danger" class="delete-button" />
              </template>
            </van-swipe-cell>
          </van-col>
        </van-row>
        <van-submit-bar :disabled="flag" :price="totalPrice" button-text="提交订单" @submit="onSubmit">
          <van-checkbox @click="updateAllFlag" v-model="checked">全选</van-checkbox>
        </van-submit-bar>
      </div>
    </div>
  </div>
</template>
<script>
import Vue from 'vue'
import { mapState, mapActions, mapMutations, mapGetters } from 'vuex'
import { NavBar, Stepper, Empty, Button, Col, SubmitBar, Row, Card, Checkbox, SwipeCell } from 'vant'
import { remove, updateCartNum, selectall, selectone } from './../../api/cart'
Vue.use(NavBar)
Vue.use(Empty)
Vue.use(Button)
Vue.use(Col)
Vue.use(Row)
Vue.use(Checkbox)
Vue.use(Card)
Vue.use(SwipeCell)
Vue.use(Stepper)
Vue.use(SubmitBar)
export default {
  data () {
    return {
      // cartList: [], // 8.删除购物车的初始化状态
      empty: true,
      checked: false
    }
  },
  computed: {
    // 1.获取状态管理器中的登录状态
    ...mapState({
      isLogin: state => state.user.isLogin,
      // 9.获取购物车中的状态
      cartList: state => state.cart.cartList
    }),
    // 10.获取vuex中的totalNum - 不添加命名空间
    ...mapGetters({
      // key 代表当前组件中需要的字段,value代表的是vuex中的 getters中设置的计算属性
      totalNum: 'totalNum',
      totalPrice: 'totalPrice'
    }),
    // totalNum () {
    //   return this.cartList.reduce((sum, item) => {
    //     return item.flag ? sum + item.num : sum + 0
    //   }, 0)
    // },
    // totalPrice () {
    //   return this.cartList.reduce((sum, item) => {
    //     return item.flag ? sum + item.originprice * item.num : sum + 0
    //   }, 0) * 100
    // },
    flag () { // 提交订单按钮可点不可点
      return this.totalNum <= 0
    }
  },
  methods: {
    // 3.生成获取购物车数据的方法
    ...mapActions({
      getCartListAction: 'cart/getCartListAction'
    }),
    // 5.生成修改购物车数据的方法
    ...mapMutations({
      changeCartList: 'cart/changeCartList'
    }),
    onSubmit () {
    },
    updateFlag (cartid, flag) {
      console.log(cartid, flag)
      selectone({
        cartid, flag
      }).then(res => {
        this.getCartListData()
      })
    },
    updateAllFlag () {
      selectall({
        userid: localStorage.getItem('userid'),
        type: this.checked
      }).then(res => {
        this.getCartListData()
      })
    },
    getCartListData () {
      // 4.调用vuex中的actions
      this.getCartListAction({ userid: localStorage.getItem('userid') }).then(res => {
        // 没有数据
        if (res.data.code === '10020') {
          this.empty = true
          // 6.修改vuex中的状态
          this.changeCartList([])
        } else {
          this.empty = false
          // 7.修改vuex中的状态
          this.changeCartList(res.data.data)
          // 判断全选是不是被选中
          // every 所有的条件都满足返回为true,只要有一个不满足返回为false
          this.checked = res.data.data.every(item => item.flag)
        }
      })
    },
    updateNum (cartid, num) {
      console.log(cartid, num)
      updateCartNum({
        cartid,
        num
      }).then(res => {
        this.getCartListData() // 重新获取最新的数据
      })
    },
    removeItem (cartid) {
      remove({
        cartid
      }).then(res => {
        this.getCartListData() // 重新获取最新的数据
      })
    }
  },
  mounted () {
    // 2.依据状态管理器中的登录状态进一步操作
    if (this.isLogin) {
      this.getCartListData()
    } else {
      this.$router.push('/login')
    }
  }
}
</script>
<style lang="stylus" scoped>
.checkboxList
  display flex
  height 1.04rem
  justify-content center
  align-items center
  background-color #fafafa
.delete-button {
  height: 100%;
}
</style>
​

16.4 添加底部的数量标签

s r c/app.vue

<template>
  <div class="container">
    <!-- <div class="box">
      <header class="header">header</header>
      <div class="content">content</div>
    </div> -->
    <router-view></router-view>
    <footer class="footer" v-if="!$route.meta.hidden">
      <ul>
        <!-- router-link 默认会渲染为 a 标签
        使用tag属性设置 转换的标签 -->
        <router-link to="/home" tag="li">
          <span class="iconfont icon-shouye"></span>
          <p>首页</p>
        </router-link>
        <router-link to="/kind" tag="li">
          <span class="iconfont icon-leimupinleifenleileibie"></span>
          <p>分类</p>
        </router-link>
        <router-link to="/cart" tag="li">
          <span class="iconfont icon-shopping-cart"></span>
          <p>购物车{{ isLogin ? totalNum : ''}}</p>
        </router-link>
        <router-link v-if="isLogin" to="/user" tag="li">
          <span class="iconfont icon-wode"></span>
          <p>我的</p>
        </router-link>
        <router-link v-else to="/login" tag="li">
          <span class="iconfont icon-wode"></span>
          <p>未登录</p>
        </router-link>
      </ul>
    </footer>
    <div class="tip">请将屏幕竖向浏览</div>
  </div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
// 组件中获取状态管理器的状态可以使用计算属性
export default {
  methods: { // 2.页面的刷新,导致数据的重置,获取最新的数据
    ...mapActions({
      getCartListAction: 'cart/getCartListAction'
    }),
    ...mapMutations({
      changeCartList: 'cart/changeCartList'
    })
  },
  mounted () { // 3.获取实时的购物车中的数据
    this.getCartListAction({ userid: localStorage.getItem('userid') }).then(res => {
      if (res.data.code === '10020') {
        this.changeCartList([])
      } else {
        this.changeCartList(res.data.data)
      }
    })
  },
  computed: {
    // isLogin () {
    //   return this.$store.state.user.isLogin
    // }
    // ... 扩展运算符其实代表的就是合并对象
    // computed 是一个对象,还可以写其他的自定义的计算属性
    ...mapState({ // 使用mapState辅助函数获取状态管理器中的状态,结合 扩展运算符完成
      isLogin: state => state.user.isLogin
​
    }),
    // 1.获取购物车中的数量
    ...mapGetters({
      totalNum: 'totalNum'
    })
  }
}
</script>
​
<style lang="stylus">
。。。。
</style>
​

16.5 详情页面底部的数量

<template>
  <div class="box">
    <header class="header">
      <!-- 使用导航栏组件 -->
      <van-nav-bar
        :title="proname"
        left-arrow
        @click-left="$router.back()"
      >
        <template #right>
          <!-- 头部右侧的下拉选择菜单 -->
          <van-popover
            v-model="showPopover"
            theme="dark"
            trigger="click"
            :actions="actions"
            placement="bottom-end"
            @select = "moreEvent"
          >
            <template #reference>
              <van-icon name="more-o" size="18"/>
            </template>
          </van-popover>
​
        </template>
      </van-nav-bar>
    </header>
    <div class="content">
      <van-swipe class="detail-swipe" indicator-color="white">
        <van-swipe-item @click="previewImage(index)" v-for="(item, index) of banners" :key="index">
          <van-image
            fit="cover"
            :src="item"
          />
        </van-swipe-item>
      </van-swipe>
      <!-- 播放视频的图标 -->
      <van-icon v-if="flag" @click="openOverLay" name="play-circle-o" class="playBtn" size="36" color="#f66"/>
      <!-- 详情 -->
      <van-tag type="danger">{{ brand }}</van-tag> {{ category }}
      <h3>{{ proname }}</h3>
      <div>{{ originprice }}</div>
​
      <van-goods-action>
        <van-goods-action-icon icon="chat-o" text="客服" color="#ee0a24" />
        <!-- 2.展示数量 -->
        <van-goods-action-icon icon="cart-o" text="购物车" :badge=" isLogin && totalNum > 0 ? totalNum : ''" />
        <van-goods-action-icon icon="star" text="已收藏" color="#ff5000" />
        <van-goods-action-button type="warning" @click="addProToCart" text="加入购物车" />
        <van-goods-action-button type="danger" text="立即购买" />
      </van-goods-action>
      <!-- 分享面板 -->
      <van-share-sheet
        v-model="showShare"
        title="立即分享给好友"
        :options="options"
      />
    </div>
    <!-- 视频播放的遮罩层 -->
    <van-overlay :show="show" @click="closeOverLay" class="vdoBox">
      <video ref="vdo" width="100%" src="https://vod.300hu.com/4c1f7a6atransbjngwcloud1oss/1b147065379705072794210305/v.f20.mp4?dockingId=f782a297-b570-4701-bcea-9ae0c0749efe&storageSource=3" controls>
      </video>
    </van-overlay>
​
  </div>
</template>
<script>
import Vue from 'vue'
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
import { getProDetail } from './../../api/detail'
import { addCart } from './../../api/cart'
import { Toast, ImagePreview, Overlay, ShareSheet, Popover, NavBar, Swipe, SwipeItem, Tag, GoodsAction, GoodsActionIcon, GoodsActionButton } from 'vant'
Vue.use(Toast)
Vue.use(ImagePreview)
Vue.use(Overlay)
Vue.use(ShareSheet)
Vue.use(Popover)
Vue.use(NavBar)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Tag)
Vue.use(GoodsAction)
Vue.use(GoodsActionIcon)
Vue.use(GoodsActionButton)
export default {
  computed: {
    //
    ...mapState({
      isLogin: state => state.user.isLogin
    }),
    // 1.获取vuex中的totalNum数量
    ...mapGetters({
      totalNum: 'totalNum'
    })
  },
  data () {
    return {
      proid: '',
      proname: '',
      originprice: 0,
      discount: 0,
      banners: [],
      sales: 0,
      category: '',
      brand: '',
      showPopover: false, // 控制右侧更多菜单的显示和隐藏
      actions: [{ text: '首页' }, { text: '分类' }, { text: '购物车' }, { text: '我的' }, { text: '分享' }],
      showShare: false, // 控制分享面板的显示和隐藏
      options: [ // 分享面板
        { name: '微信', icon: 'wechat' },
        { name: '微博', icon: 'weibo' },
        { name: '复制链接', icon: 'link' },
        { name: '分享海报', icon: 'poster' },
        { name: '二维码', icon: 'qrcode' }
      ],
      show: false, // 控制视频播放遮罩层的显示和隐藏
      flag: true // 控制播放按钮的显示和隐藏
    }
  },
  mounted () {
    this.proid = this.$route.params.proid
    getProDetail(this.proid).then(res => {
      console.log(res)
      // 当前接口的banenrs数据有问题,需要前端自行处理 ---- 不意味着以后别的项目也需要
      this.banners = res.data.data.banners[0].split(',')
      this.proname = res.data.data.proname
      this.originprice = res.data.data.originprice
      this.discount = res.data.data.discount
      this.sales = res.data.data.sales
      this.category = res.data.data.category
      this.brand = res.data.data.brand
    })
  },
  methods: {
    // 3.为了加入购物车之后更新数据
    ...mapActions({
      getCartListAction: 'cart/getCartListAction'
    }),
    ...mapMutations({
      changeCartList: 'cart/changeCartList'
    }),
    addProToCart () {
      // 前端校验登录  本地存储的都是字符串
      if (localStorage.getItem('isLogin') === 'true') { // 前端已经表示登录
        // 调用加入购物车接口
        addCart({ // 一定要把参数传递完整,否则会有小发现
          userid: localStorage.getItem('userid'),
          proid: this.proid,
          num: 1
        }).then(res => {
          Toast('加入购物车成功')
          // 4.更新购物车的数据
          this.getCartListAction({ userid: localStorage.getItem('userid') }).then(res => {
            if (res.data.code === '10020') {
              this.changeCartList([])
            } else {
              this.changeCartList(res.data.data)
            }
          })
        })
      } else {
        this.$router.push('/login')
      }
    },
    previewImage (index) {
      ImagePreview({ // 预览图片
        images: this.banners,
        startPosition: index
      })
    },
    openOverLay () { // 打开遮罩层,播放视频,隐藏按钮
      this.show = true
      this.flag = false
      this.$refs.vdo.play()
    },
    closeOverLay () { // 关闭遮罩层,暂停视频,显示按钮
      this.$refs.vdo.pause()
      this.flag = true
      this.show = false
    },
    moreEvent (action, index) { // 右上角更多菜单
      console.log(index, action)
      switch (index) {
        case 0:
          this.$router.push('/home')
          break
        case 1:
          this.$router.push('/kind')
          break
        case 2:
          this.$router.push('/cart')
          break
        case 3:
          this.$router.push('/user')
          break
        case 4:
          this.showShare = true // 控制分享面板的显示
          break
        default:
          break
      }
    }
  }
}
</script>
​
<style lang="stylus">
.detail-swipe
  height 2.6rem
.van-popover[data-popper-placement=bottom-end] .van-popover__arrow {
    right: 0px;
}
.playBtn
  position fixed
  top 2.4rem
  left 50%
  transform translateX(-50%)
  z-index 1000
.vdoBox
  display flex
  justify-content center
  align-items center
</style>
​

如果删掉本地存储的数据,当用户在首页后者详情页面重新刷新页面时,自动跳转到了 登录页面, ------ qq 微信

但是我们希望 首页,分类,详情,活动,即使用户不登录也可以查看 ---- 修改axios的配置

// http://www.axios-js.com/zh-cn/docs/
import axios from 'axios'
import router from './../router'
import store from './../store' // **************************重中之重****************************************
// 开发环境 yarn serve
// 生产环境 yarn build
// development  production
const isDev = process.env.NODE_ENV === 'development'
​
// 创建axios实例
// http://www.axios-js.com/zh-cn/docs/#axios-create-config
const request = axios.create({
  // baseUrl 实际请求的地址是 baseURL + 请求地址
  // http://121.89.205.189/api/banner/list  ===》 baseURL + '/banner/list'
  // baseURL: 'http://121.89.205.189/api',
  // 项目上线时无需修改baseURL地址 ---- 需要提前知道线上接口的地址
  // baseURL: isDev ? 'http://121.89.205.189/api' : 'http://121.89.205.189/api',
  // 如果使用了 代理 解决跨域问题
  baseURL: isDev ? '' : 'http://121.89.205.189/api',
  timeout: 6000 // 网络超时时间
})
​
// axios 的拦截器
// http://www.axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8
// 请求拦截器 ---- 所有的数据在请求之前都会执行的代码 --- 显示loading的动画效果/给接口添加token
request.interceptors.request.use((config) => {
  // 在请求之前做些什么
  // 给所有的请求都传递token信息
  config.headers.common.token = localStorage.getItem('token') || ''
  return config
}, (error) => {
  return Promise.reject(error)
})
// 响应拦截器 ---- 在拿到接口的数据之前都会执行的代码 --- 隐藏loading的动画效果/验证token
request.interceptors.response.use((response) => {
  // 在响应时做些什么
  // token 没有传递  token 传递了只不过是错误的  token 传递了 失效了
  if (response.data.code === '10119') {
    console.log(111111111)
    // 引入路由器,跳转到登录页面 ****************************重中之重****************************************
    if (store.state.user.isLogin) {
      router.push('/login')
    }
  }
  return response
}, (error) => {
  return Promise.reject(error)
})
​
// 暴露自定义的axios
export default request
​

16.6 总结

  • 选择是否需要分模块(不管项目大小都可分 - 参照16点,但是一般小项目部分模块 - 参照 15点)

  • 分模块步骤

    • 1.先写模块(state, mutations, actions, namespaced)

    namespaced 是关键,使用 mutations 和 actions 时需要 指明模块的名称

    • 2.注册模块 (state, getters, mutations, actions, modules)

    modules是关键,它的key就是模块的名称

    getters 是计算属性

    state 可以是 所有组件都需要的模块的状态

    • 3.组件中使用辅助函数执行业务(mapState, mapGetters,mapMutations, mapActions)

Logo

前往低代码交流专区

更多推荐