Vue从入门到实战Day07

news2024/11/14 17:56:08

一、vuex概述

目标:明确vuex是什么,应用场景,优势

1. 是什么:

vuex是一个vue的状态管理工具,状态就是数据。

大白话:vuex是一个插件,可以帮助我们管理vue通用的数据(多组件共享的数据)

2. 场景:

①某个状态在很多个组件来使用(个人信息)

②多个组件共同维护一份数据(购物车)

3. 优势:

①共同维护一份数据,数据集中化管理;

②响应式变化;

③操作简洁(vuex提供了一些辅助函数)。

二、构建vuex[多组件数据共享]环境

目标:基于脚手架创建项目,构建vuex多组件数据共享环境

效果:三个组件,共享一份数据,任意一个组件都可以修改数据,三个组件的数据是同步的。

1. 创建项目“vuex-demo”

①勾选Babel、CSS Pre-processors、Linter / Formatter

②配置如下

②创建一个空仓库

目标:安装vuex插件,初始化一个空仓库。

2. 核心概念 - state状态

目标:明确如何给仓库提供数据,如何使用仓库的数据

1. 提供数据:

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。在state对象中可以添加我们要共享的数据。

// 创建仓库
const store = new Vuex.Store({
    // state状态,即数据,类似于vue组件中的data
    // 区别:
    // 1. data是组件自己的数据
    // 2. state是所有组件共享的数据
    state: {
        count: 101
    }
})

2. 使用数据

①通过store直接访问;

// 获取store
// (1) this.$store
// (2) import 导入 store

// 模板中: {{ $store.state.xxx }}
// 组件逻辑中:this.$store.state.xxx
// JS模块中:store.state.xxx

②通过辅助函数(简化)

mapState是辅助函数,帮助我们把store中的数据自动映射到组件的计算属性中。

3. 核心概念 - mutations

目标:明确vuex同样遵循单向数据流,组件中不能直接修改仓库中的数据

错误写法:this.$store.state.count++

通过 strict: true 可以开启严格模式

1. 定义mutations对象,对象中存放修改state的方法

const store = new Vuex.Store({
    state: {
        count: 101
    },
    // 定义mutations
    mutations: {
        // 第一个参数是当前store的state属性
        addCount(state) {
            state.count += 1
        }
    }
})

2. 组件中提交调用mutations

this.$store.commit('addCount')

目标:掌握mutations传参语法

提交mutation是可以传递参数的 `this.$store.commit('xxx', 参数)`

1. 提供mutations函数(带参数 - 提交载荷payload)

mutations: {
    ...
    addCount (state, n) {
        state.count += n
    }
},

2. 页面中提交调用mutation

this.$store.commit('addCount', 10)

目标:实时输入,实时更新,巩固mutations传参语法

辅助函数:mapMutations

目标:掌握辅助函数mapMutations,映射方法

mapMutations和mapState很像,它是把位于mutations中的方法提取了出来,映射到组件methods中。

mutations: {
    subCount (state, n) {
        state.count -= n
    },
}
import { mapMutations } from 'vuex'

methods: {
    ...mapMutations(['subCount'])
}
this.subCount(10) // 调用

4. 核心概念 - actions

目标:明确actions的基本语法,处理异步操作

需求:一秒钟之后,修改state的count成666。

说明:mutations必须是同步的(便于监测数据变化,记录调试)

1. 提供action方法

actions: {
    setAsyncCount (context, num) {
        // 一秒后,给一个数,去修改num
        setTimeout(() => {
            context.commit('changeCount', num)
        }, 1000)
    }
)

2. 页面中dispatch调用

this.$store.dispatch('setAsyncCount', 200)

辅助函数 - mapActions

目标:掌握辅助函数mapActions,映射方法

mapActions是把位于actions中的方法提取了出来,映射到组件methods

actions: {
    setAsyncCount (context, num) {
        // 一秒后,给一个数,去修改num
        setTimeout(() => {
            context.commit('changeCount', num)
        }, 1000)
    }
)
import { mapActions } from 'vuex'

methods: {
    ...mapActions(['changeCountAction'])
}
this.changeCountAction(666)  //  调用

5. 核心概念 - getters

目标:掌握核心概念getters的基本语法(类似于计算属性)

说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state,此时会用到getters

例如:state中定义了list,为1-10的数组,组件中,需要显示所有大于5的数据

state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

1. 定义getters

getters: {
    // 注意
    // (1) getters函数的第一个参数必须是state
    // (2) getters函数必须要有返回值
    filterList (state) {
        return state.list.filter(item => item > 5)
    }
}

2. 访问getters

方式①通过store访问getters

{{ $store.getters.filterList }}

方式②通过辅助函数mapGetters映射

computed: {
    ...mapGetters(['filterList'])
},
{{ filterList }}  // 使用

6. 核心概念 - 模块module(进阶语法)

目标:掌握核心概念module模块的创建

由于vuex使用单一状态树,应用的所有状态都会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。(当项目变得越来越大时,Vuex会变得越来越难以维护)

模块拆分:

目标:掌握模块中 state 的访问语法

尽管以及分模块了,但其实子模块的状态,还是会挂到根级别的state中,属性名就是模块名

使用模块中的数据:

①直接通过模块名访问 $store.state.模块名.xxx

②通过mapState映射

  • 默认根级别的映射 mapState(['xxx'])
  • 子模块的映射 mapState('模块名', ['xxx']) - 需要开启命名空间
export default {
  namespaced: true,  // 开启命名空间
  state,
  mutations,
  actions,
  getters
}

目标:掌握模块中 getters 的访问语法

使用模块中的getters中的数据:

①直接通过模块名访问 $store.getters['模块名/xxx']

②通过mapGetters映射

  • 默认根级别的映射 mapGetters(['xxx'])
  • 子模块的映射 mapGetters('模块名', ['xxx']) - 需要开启命名空间

目标:掌握模块中 mutation 的调用语法

注意:默认模块中的mutation和actions会被挂载到全局,需要开启命名空间,才会挂载到子模块。

调用子模块中mutation:

①直接通过store调用 $store.commit('模块名/xxx', 额外参数)

②通过mapMutations映射

  • 默认根级别的映射 mapMutations(['xxx'])
  • 子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间

目标:掌握模块中 action 的调用语法(同理 - 直接类比 mutation 即可)

注意:默认模块中的mutation和actions会被挂载到全局,需要开启命名空间,才会挂载到子模块。

调用子模块中action:

①直接通过store调用 $store.dispatch('模块名/xxx', 额外参数)

②通过mapActions映射

  • 默认根级别的映射 mapActions(['xxx'])
  • 子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间

示例代码:

main.js

import Vue from 'vue'
import App from './App.vue'
import store from '@/store/index'
Vue.config.productionTip = false

// console.log(store.state.count)

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

App.vue

<template>
  <div id="app">
    <h1>
      根组件
      - {{ title }}
      - {{ count }}
    </h1>
    <input :value="count" @input="handleInput" type="text">
    <Son1></Son1>
    <hr>
    <Son2></Son2>
  </div>
</template>

<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
import { mapState } from 'vuex'
console.log(mapState(['count', 'title']))

export default {
  name: 'app',
  created () {
    // console.log(this.$router) // 没配
    // console.log(this.$store.state.count)
  },
  computed: {
    ...mapState(['count', 'title'])
  },
  data: function () {
    return {

    }
  },
  methods: {
    handleInput (e) {
      // 1. 实时获取输入框的值
      const num = +e.target.value
      // 2. 提交mutation,调用mutation函数
      this.$store.commit('changeCount', num)
    }
  },
  components: {
    Son1,
    Son2
  }
}
</script>

<style>
#app {
  width: 600px;
  margin: 20px auto;
  border: 3px solid #ccc;
  border-radius: 3px;
  padding: 10px;
}
</style>

src/store/index.js

// 这里存放的就是vuex相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import setting from './modules/setting'

// 插件安装
Vue.use(Vuex)

// 创建仓库(空仓库)
const store = new Vuex.Store({
  // 开启严格模式(有利于初学者,检查不规范的代码 => 上线时需要移除,消耗性能)
  strict: true,
  // 1. 通过state可以提供数据(所有组件共享)
  state: {
    title: '大标题',
    count: 100,
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  },

  // 2. 通过mutations 可以提供修改数据的方法
  mutations: {
    // 所有mutation函数,第一个参数,都是state
    // 注意点:mutation参数有且只能有一个,如果需要多个参数,可以包装成一个对象或数组
    addCount (state, obj) {
      // 修改数据
      state.count += obj.count
    },
    subCount (state, n) {
      state.count -= n
    },
    changeCount (state, newCount) {
      state.count = newCount
    },
    changeTitle (state, newTitle) {
      state.title = newTitle
    }
  },

  // 3. actions 处理异步操作,不能直接操作state,还是需要commit mutation
  actions: {
    // context 上下文
    // context.commit('mutation名字', 额外参数)
    changeCountAction (context, num) {
      // 这里是setTimeout模拟异步,以后大部分场景是发请求
      setTimeout(() => {
        context.commit('changeCount', num)
      }, 1000)
    }
  },

  // 4. getters类似于计算属性
  getters: {
    // 注意点
    // 1. 形参第一个参数,就是state
    // 2. 必须要有返回值,返回值就是getters的值
    filterList (state) {
      return state.list.filter(item => item > 5)
    }
  },

  // 5. modules 模块
  modules: {
    user,
    setting
  }
})

// 导出给main.js使用
export default store

src/store/modules/setting.js

// setting模块
const state = {
  theme: 'light', // 主题色
  desc: '测试demo'
}
const mutations = {
  setTheme (state, newTheme) {
    state.theme = newTheme
  }
}
const actions = {
  setThemeSecond (context, newTheme) {
    setTimeout(() => {
      context.commit('setTheme', newTheme)
    }, 1000)
  }
}
const getters = {}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}

src/store/modules/user.js

// user模块
const state = {
  userInfo: {
    name: 'zs',
    age: 18
  },
  score: 80
}
const mutations = {
  setUser (state, newUserInfo) {
    state.userInfo = newUserInfo
  }
}
const actions = {
  setUserSecond (context, newUserInfo) {
    // 将异步在action中进行封装
    setTimeout(() => {
      // 调用mutation   context上下文,默认提交的就是自己模块的action和mutation
      context.commit('setUser', newUserInfo)
    }, 1000)
  }
}
const getters = {
  // 分模块后,state指代子模块的state
  UpperCaseName (state) {
    return state.userInfo.name.toUpperCase()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}

src/components/Son1.vue

<template>
    <div class="box">
      <h2>Son1 子组件</h2>
      从vuex中获取的值: <label>{{ $store.state.count }}</label>
      <br>
      <button @click="handleAdd(1)">值 + 1</button>
      <button @click="handleAdd(5)">值 + 5</button>
      <button @click="handleAdd(10)">值 + 10</button>
      <button @click="handleChange">一秒后修改成666</button>
      <button @click="changeFn">改标题</button>

      <hr>
      <!-- 计算属性getters -->
      <div>{{ $store.state.list }}</div>
      <div>{{ $store.getters.filterList }}</div>

      <hr>
      <!-- 测试访问模块中的state - 原生 -->
      <div>{{ $store.state.user.userInfo.name }}</div>
      <button @click="updateUser">更新个人信息</button>
      <button @click="updateUser2">一秒后更新个人信息</button>

      <div>{{ $store.state.setting.theme }}</div>
      <button @click="updateTheme">更新主题色</button>
      <button @click="updateTheme2">一秒后更新主题色</button>
      <hr>
      <!-- 测试访问模块中的getters - 原生 -->
      <div>{{ $store.getters['user/UpperCaseName'] }}</div>
    </div>
  </template>

<script>
export default {
  name: 'Son1Com',
  created () {
    console.log(this.$store.getters)
  },
  methods: {
    updateUser () {
      // $store.commit('模块名/mutation名', 额外传参)
      this.$store.commit('user/setUser', {
        name: 'xiaowang',
        age: 25
      })
    },
    updateUser2 () {
      // 调用action dispatch
      this.$store.dispatch('user/setUserSecond', {
        name: 'xiaohong',
        age: 28
      })
    },
    updateTheme () {
      this.$store.commit('setting/setTheme', 'pink')
    },
    updateTheme2 () {
      this.$store.dispatch('setting/setThemeSecond', 'blue')
    },
    handleAdd (n) {
      // 错误代码(vue默认不会监测,监测需要成本)
      // this.$store.state.count++
      // console.log(this.$store.state.count)

      // 应该通过 mutation 核心概念,进行修改数据
      // 需要提交调用mutation
      // this.$store.commit('addCount')

      // console.log(n)
      // 调用带参数的mutation函数
      this.$store.commit('addCount', {
        count: n,
        msg: '哈哈'
      })
    },
    changeFn () {
      this.$store.commit('changeTitle', '传智教育')
    },
    handleChange () {
      // 调用action
      // this.$store.dispatch('action名字', 额外参数)
      this.$store.dispatch('changeCountAction', 666)
    }
  }
}
</script>

  <style lang="css" scoped>
  .box{
    border: 3px solid #ccc;
    width: 400px;
    padding: 10px;
    margin: 20px;
  }
  h2 {
    margin-top: 10px;
  }
  </style>

src/components/Son2.vue

<template>
    <div class="box">
      <h2>Son2 子组件</h2>
      从vuex中获取的值:<label>{{ count }}</label>
      <br />
      <button @click="subCount(1)">值 - 1</button>
      <button @click="subCount(5)">值 - 5</button>
      <button @click="subCount(10)">值 - 10</button>
      <button @click="changeCountAction(888)">1秒后改成888</button>
      <button @click="changeTitle('前端程序员')">改标题</button>
      <hr>
      <div>{{ filterList }}</div>
      <hr>
      <!-- 访问模块中的state -->
      <div>{{ user.userInfo.name }}</div>
      <div>{{ setting.theme }}</div>
      <hr>
      <!-- 访问模块中的state -->
      <div>user模块的数据:{{ userInfo }}</div>
      <button @click="setUser({ name: 'xiaoli', age: 80 })">更新个人信息</button>
      <button @click="setUserSecond({ name: 'xiaoli', age: 80 })">一秒后更新个人信息</button>

      <div>setting模块的数据:{{ theme }} - {{ desc }}</div>
      <button @click="setTheme('skyblue')">更新主题</button>
      <button @click="setThemeSecond('green')">一秒后更新主题色</button>
      <hr>
      <!-- 访问模块中的getters -->
      <div>{{ UpperCaseName }}</div>
    </div>
  </template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
  name: 'Son2Com',
  computed: {
    // mapState 和 mapGetters 都是映射属性
    ...mapState(['count', 'user', 'setting']),
    // 模块名 数据
    ...mapState('user', ['userInfo']),
    ...mapState('setting', ['theme', 'desc']),
    ...mapGetters(['filterList']),
    ...mapGetters('user', ['UpperCaseName'])
  },
  methods: {
    // mapMutations 和 mapActions 都是映射方法
    // 全局级别的映射
    ...mapMutations(['subCount', 'changeTitle']),
    ...mapActions(['changeCountAction']),

    // 分模块的映射
    ...mapMutations('setting', ['setTheme']),
    ...mapMutations('user', ['setUser']),
    ...mapActions('user', ['setUserSecond']),
    ...mapActions('setting', ['setThemeSecond'])

    // handleSub (n) {
    //   this.subCount(n)
    // }
  }
}
</script>

  <style lang="css" scoped>
  .box {
    border: 3px solid #ccc;
    width: 400px;
    padding: 10px;
    margin: 20px;
  }
  h2 {
    margin-top: 10px;
  }
  </style>

效果:

三、综合案例 - 购物车

目标:功能分析,创建项目,构建分析基本结构

1. 功能模块分析

①请求动态渲染购物车,数据存vuex

②数字框控件 修改数据

③动态计算 总价和总数量

2. 脚手架新建项目(注意:勾选veux)

vue create vue-cart-demo

③将原本src内容清空,替换成素材的《vuex-cart-准备代码》并分析

目标:构建cart购物车模块

说明:既然明确数据要存vuex,建议分模块存储,购物车数据存cart模块,将来还会有user模块,article模块···

1. 新建`store/modules/cart.js`

export default {
    namespaced: true,
    state() {
        return {
            list: []
        }
    },
}

2. 挂载到vuex仓库上`store/index.js`

import cart from './modules/cart'

const store = new Vuex.Store({
    modules: {
        cart
    }
})

export default store

目标:基于json-server工具,准备后端接口服务环境

1. 安装全局工具 json-server (全局工具仅需要安装一次) [官网]

yarn global add json-server 或 npm install json-server -g

2. 代码根目录新建一个db目录

3. 将资料index.json移入db目录

{
  "cart": [
    {
      "id": 100001,
      "name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
      "price": 128,
      "count": 1,
      "thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
    },
    {
      "id": 100002,
      "name": "网易味央黑猪猪肘330g*1袋",
      "price": 39,
      "count": 10,
      "thumb": "https://yanxuan-item.nosdn.127.net/d0a56474a8443cf6abd5afc539aa2476.jpg"
    },
    {
      "id": 100003,
      "name": "KENROLL男女简洁多彩一片式室外拖",
      "price": 128,
      "count": 2,
      "thumb": "https://yanxuan-item.nosdn.127.net/eb1556fcc59e2fd98d9b0bc201dd4409.jpg"
    },
    {
      "id": 100004,
      "name": "云音乐定制IN系列intar民谣木吉他",
      "price": 589,
      "count": 1,
      "thumb": "https://yanxuan-item.nosdn.127.net/4d825431a3587edb63cb165166f8fc76.jpg"
    }
  ],
  "friends": [
    {
      "id": 1,
      "name": "zs",
      "age": 18
    },
    {
      "id": 2,
      "name": "ls",
      "age": 19
    },
    {
      "id": 3,
      "name": "ww",
      "age": 20
    }
  ]
}

4. 进入db目录,执行命令,启动后端接口服务

注意:Node.js的版本要高于14.0.0,低于14.0.0的可以重装Node.js [Node.js官网]

json-server index.json

5. 访问接口测试 http://localhost:3000/cart

目标:请求获取数据存入vuex,映射渲染

目标:修改数量功能完成

注意:前端vuex数据 以及 后端数据库数据都要更新

目标:底部getters统计

1. 提供getters

getters: {
    total (state) {
        return state.list.reduce((sum, item) => sum + item.count, 0)
    },
    totalPrice (state) {
        return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
    }
}

2. 使用getters

computed: {
    ...mapGetters('cart', ['total', 'totalPrice'])
}

所有代码:

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

App.vue

<template>
  <div class="app-container">
    <!-- Header 区域 -->
    <cart-header></cart-header>

    <!-- 商品 Item 项组件 -->
    <cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>

    <!-- Foote 区域 -->
    <cart-footer></cart-footer>
  </div>
</template>

<script>
import CartHeader from '@/components/cart-header.vue'
import CartFooter from '@/components/cart-footer.vue'
import CartItem from '@/components/cart-item.vue'

import { mapState } from 'vuex'

export default {
  name: 'App',
  created () {
    this.$store.dispatch('cart/getList')
  },
  computed: {
    ...mapState('cart', ['list'])
  },
  components: {
    CartHeader,
    CartFooter,
    CartItem
  }
}
</script>

<style lang="less" scoped>
.app-container {
  padding: 50px 0;
  font-size: 14px;
}
</style>

src/components/cart-header.vue

<template>
  <div class="header-container">购物车案例</div>
</template>

<script>
export default {
  name: 'CartHeader'
}
</script>

<style lang="less" scoped>
.header-container {
  height: 50px;
  line-height: 50px;
  font-size: 16px;
  background-color: #42b983;
  text-align: center;
  color: white;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 999;
}
</style>

src/components/cart-item.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片区域 -->
    <div class="left">
      <img :src="item.thumb" class="avatar" alt="">
    </div>
    <!-- 右侧商品区域 -->
    <div class="right">
      <!-- 标题 -->
      <div class="title">{{ item.name }}</div>
      <div class="info">
        <!-- 单价 -->
        <span class="price">¥{{ item.price }}</span>
        <div class="btns">
          <!-- 按钮区域 -->
          <button class="btn btn-light" @click="btnClick(-1)">-</button>
          <span class="count">{{ item.count }}</span>
          <button class="btn btn-light" @click="btnClick(1)">+</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CartItem',
  methods: {
    btnClick (step) {
      const newCount = this.item.count + step
      const id = this.item.id

      if (newCount < 1) return

      this.$store.dispatch('cart/updateCountAsync', {
        id,
        newCount
      })
    }
  },
  props: {
    item: {
      type: Object,
      required: true
    }
  }
}
</script>

<style lang="less" scoped>
.goods-container {
  display: flex;
  padding: 10px;
  + .goods-container {
    border-top: 1px solid #f8f8f8;
  }
  .left {
    .avatar {
      width: 100px;
      height: 100px;
    }
    margin-right: 10px;
  }
  .right {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;
    .title {
      font-weight: bold;
    }
    .info {
      display: flex;
      justify-content: space-between;
      align-items: center;
      .price {
        color: red;
        font-weight: bold;
      }
      .btns {
        .count {
          display: inline-block;
          width: 30px;
          text-align: center;
        }
      }
    }
  }
}

.custom-control-label::before,
.custom-control-label::after {
  top: 3.6rem;
}
</style>

src/components/cart-footer.vue

<template>
  <div class="footer-container">
    <!-- 中间的合计 -->
    <div>
      <span>共 {{ total }} 件商品,合计:</span>
      <span class="price">¥{{ totalPrice }}</span>
    </div>
    <!-- 右侧结算按钮 -->
    <button class="btn btn-success btn-settle">结算</button>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  name: 'CartFooter',
  computed: {
    ...mapGetters('cart', ['total', 'totalPrice'])
  }
}
</script>

<style lang="less" scoped>
.footer-container {
  background-color: white;
  height: 50px;
  border-top: 1px solid #f8f8f8;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 0 10px;
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 999;
}

.price {
  color: red;
  font-size: 13px;
  font-weight: bold;
  margin-right: 10px;
}

.btn-settle {
  height: 30px;
  min-width: 80px;
  margin-right: 20px;
  border-radius: 20px;
  background: #42b983;
  border: none;
  color: white;
}
</style>

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    cart
  }
})

src/modules/cart.js

import axios from 'axios'
export default {
  namespaced: true,
  state () {
    return {
      // 购物车数据 [{}, {}]
      list: []
    }
  },
  mutations: {
    updateList (state, newList) {
      state.list = newList
    },
    // obj: {id: xxx, newCount: xxx}
    updateCount (state, obj) {
      // console.log(obj)
      // 根据id找到对应的对象,更新count属性即可
      const goods = state.list.find(item => item.id === obj.id)
      goods.count = obj.newCount
    }
  },
  actions: {
    // 请求方式:get
    // 请求地址:http://localhost:3000/cart
    async getList (context) {
      const res = await axios.get('http://localhost:3000/cart')
      // console.log(res)
      context.commit('updateList', res.data)
    },

    // 请求方式:patch
    // 请求地址:http://localhost:3000/cart/:id
    // 请求参数:
    // {
    //     name: '新值', [可选]
    //     price: '新值', [可选]
    //     count: '新值', [可选]
    //     thumb: '新值', [可选]
    // }
    async updateCountAsync (context, obj) {
      // 将修改更新同步到后台服务器
      // console.log(obj)
      await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
        count: obj.newCount
      })

      // 将修改更新同步到vuex
      context.commit('updateCount', {
        id: obj.id,
        newCount: obj.newCount
      })
    }
  },
  getters: {
    // 商品总数量
    total (state) {
      return state.list.reduce((sum, item) => sum + item.count, 0)
    },
    // 商品总价
    totalPrice (state) {
      return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
    }
  }
}

效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1696408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构|C语言版】树、二叉树和堆

前言1. 树1.1 树的概念1.2 树的表示1.3 树在实际中的运用1.4 树的实现 2. 二叉树2.1 二叉树的概念和结构2.2 二叉树的性质2.3 二叉树的存储结构2.4 二叉树的代码实现 3. 堆4. 二叉树顺序结构的实现5. 二叉树链式结构的实现结语 #include<GUIQU.h> int main { 上期回顾: 【…

2024 一键批量下载雪球和东方财富文章导出excel和pdf

之前分享过雪球批量下载工具2023 批量下载雪球文章导出pdf&#xff0c;以市场高标解读这个号为例&#xff0c;下载效果&#xff1a; 下载文章后用我开发的htmltopdf.exe批量转换html为pdf&#xff0c;不过要注意不要放在中文目录下&#xff0c;否则提示错误 utf-8 codec cant d…

新建一个STM32工程(精简版)

一、新建一个STM32工程 二、建立三个文件夹 1、Start文件夹里的东西 &#xff08;1&#xff09;启动文件&#xff1a;STM32入门教程资料\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm &#xff08;2&#xff09;STM32…

RobotFramework测试框架(13)--内置测试库

Builtln Evaluate方法 Evaluate。它可以做很多事情&#xff0c;主要的作用是可以直接调用Python的方法 一般用Evaluate都是前面放变量接收值&#xff0c;第三列是具体的运算表达式&#xff0c;第四列是要用到的Python的module。这里就是用random来进行一个随机数的生成 Cons…

买房送户口!多城加入“抢人大战”

业内人士认为&#xff0c;近期&#xff0c;多地推出的购房落户政策已区别于此前的人才落户政策&#xff0c;更聚焦于住房消费&#xff0c;降低了落户门槛&#xff0c;体现了各地对导入人口的重视&#xff0c;有利于人才流动&#xff0c;推动新型城镇化建设。 千万人口城市“后…

【C语言深度解剖】(16):C语言的文件读写操作

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏链接查看&…

Vue移动端登录页面

使用的是vant组件&#xff0c;引用和使用组件请去官网学习&#xff0c;链接↓vant组件官网 <div class"center"><!-- 背景图片 --><div class"background"><imgsrc"/assets/background.jpg"width"100%"heigh…

一篇文章教你入门Python

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

Vue3解决“找不到模块“@/components/xxx.vue”或其相应的类型声明”

文章目录 前言背景问题描述解决方案总结 前言 在使用 Vue 3 开发项目时&#xff0c;遇到“找不到模块 ‘/components/xxx.vue’ 或其相应的类型声明”的错误是一个常见问题。这通常与 TypeScript 和模块解析相关的配置不当有关。本文将详细介绍如何解决此问题&#xff0c;确保…

LeetCode 1738.找出第 K 大的异或坐标值:二维前缀和——五彩斑斓的题解

【LetMeFly】1738.找出第 K 大的异或坐标值&#xff1a;二维前缀和 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-kth-largest-xor-coordinate-value/ 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, …

一文讲解——Java多态

目录 一、什么是多态&#xff1f;二、转型向上转型向下转型 三、方法覆盖与方法重载四、绑定动态绑定静态绑定 五、理解多态 一、什么是多态&#xff1f; 多态的词组字面意思是&#xff1a; 某种事物多种形态。 但是对于我们学习Java 的程序原来说&#xff0c;就不不能简单这样…

Jupyter Notebook:FileNotFoundError: [WinError 2] 系统找不到指定的文件怎么解决

打开Jupyter notebook时New 文件没有搭建的tensorflow环境&#xff0c;同时新建python3时&#xff0c;提示kernels 错误&#xff0c;提示信息&#xff1a;FileNotFoundError: [WinError 2] 系统找不到指定的文件。 由于Anaconda安装多个版本的python&#xff0c;或者由于多次卸…

55. UE5 RPG 处理当前功能在多人模式中的问题

在UE里面&#xff0c;我们运行项目可以设置多种网络模式&#xff0c;主要是分为三种&#xff1a; 运行Standalone 就是单人模式&#xff0c;没有网络交互以监听服务器运行&#xff0c;在界面里运行的游戏会作为服务器使用以客户端运行&#xff0c;UE会单独运行一个线程作为服务…

操作系统课程实验1-进程调度模拟实验

操作系统课程实验1-进程调度模拟实验 一、实验介绍 1.1 实验目的 本实验模拟在单处理机环境下的处理机调度&#xff0c;帮助理解进程调度的概念&#xff0c;深入了解进程控制块的功能&#xff0c;以及进程的创建、撤销和进程各个状态间的转换过程。 1.2 实验内容 进程调度算…

当HR问你是否单身时,该怎么回答?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;我是晶圆厂厂务工程师&#xff0c;最近在面试新工作&#xff0c;但是几乎每家HR都会问我同一个问题&#xff1a;你结婚没有&#xff1…

专业的力量:在自己的领域成为专家

专业的力量:在自己的领域成为专家 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 现在稀缺的已不再是信息资源&#xff0c;而是运用信息的能力。过去的海…

【全开源】场馆预定系统源码(ThinkPHP+FastAdmin+UniApp)

一款基于ThinkPHPFastAdminUniApp开发的多场馆场地预定小程序&#xff0c;提供运动场馆运营解决方案&#xff0c;适用于体育馆、羽毛球馆、兵乒球馆、篮球馆、网球馆等场馆。 场馆预定系统源码&#xff1a;打造高效便捷的预定体验 一、引言&#xff1a;数字化预定时代的来临 …

计算机毕业设计Python+Spark+PyTroch游戏推荐系统 游戏可视化 游戏爬虫 神经网络混合CF推荐算法 协同过滤推荐算法 steam 大数据

毕业设计&#xff08;论文&#xff09; 基于SpringBoot的游戏防沉迷系统的设计与实现 摘 要 随着网络游戏市场的持续火爆,其最明显的负面影响----“网络游戏沉迷问题”已成为当前社会普遍关心的热点问题。根据2010年8月1日实施的《网络游戏管理暂行办法》&#xff0c;网络游…

记忆力和人才测评,如何提升记忆力?

什么是记忆力&#xff1f; 如何通俗意义上的记忆力&#xff1f;我们可以把人的经历、经验理解成为一部纪录片&#xff0c;那么已经过去发生的事情&#xff0c;就是影片之前的情节&#xff0c;对于这些信息&#xff0c;在脑海里&#xff0c;人们会将其进行处理和组合&#xff…

如何关闭或者减少屏蔽 CloudFlare 的真人检测

经常浏览境外网站的应该常碰到一个真人检测的提示(如下图所示)。最近,明月就收到了一个知乎上的付费咨询:问我如何去掉这个提示,由此明月也特别的研究了一下这个“真人检测”,这算是 CloudFlare 的一个特色了,基本上大家看到站点访问有这个提示的几乎都是用了 CloudFlar…