├── cypress.json ├── .postcssrc.js ├── public ├── favicon.ico └── index.html ├── src ├── shims-vue.d.ts ├── assets │ └── logo.png ├── views │ ├── About.vue │ ├── ShoppingCart.vue │ └── Home.vue ├── store │ ├── mutation-types.ts │ ├── dispatches.ts │ ├── actions.ts │ ├── getters.ts │ ├── index.ts │ └── modules │ │ ├── products.ts │ │ └── cart.ts ├── main.ts ├── shims-tsx.d.ts ├── components │ ├── ShoppingCart.vue │ ├── HelloWorld.vue │ ├── ProductList.vue │ └── Cart.vue ├── router │ └── index.ts ├── App.vue ├── api │ └── shop.ts └── currency.ts ├── babel.config.js ├── tests ├── e2e │ ├── specs │ │ └── test.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── index.js │ │ └── commands.js └── unit │ └── HelloWorld.spec.ts ├── .gitignore ├── tslint.json ├── jest.config.js ├── tsconfig.json ├── README.md └── package.json /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qidaizhe11/vue-vuex-typescript-demo/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qidaizhe11/vue-vuex-typescript-demo/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: [ 4 | [ 5 | 'component', 6 | { 7 | libraryName: 'element-ui', 8 | styleLibraryName: 'theme-chalk' 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const ADD_TO_CART = 'ADD_TO_CART' 2 | export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST' 3 | export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS' 4 | export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE' 5 | export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS' 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | import { currencyFilter } from './currency' 7 | 8 | Vue.filter('currency', currencyFilter) 9 | 10 | new Vue({ 11 | router, 12 | store, 13 | render: (h) => h(App), 14 | }).$mount('#app'); 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/store/dispatches.ts: -------------------------------------------------------------------------------- 1 | // 建议dispatch/commit调用,均采用如下形式包裹一层,以启用类型推导 2 | 3 | import store, { CartProduct, Product } from './index' 4 | 5 | export const dispatchCheckout = (products: CartProduct[]) => { 6 | return store.dispatch('checkout', products) 7 | } 8 | 9 | export const dispatchAddToCart = (product: Product) => { 10 | return store.dispatch('addToCart', product) 11 | } 12 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => { 4 | return Object.assign({}, config, { 5 | fixturesFolder: 'tests/e2e/fixtures', 6 | integrationFolder: 'tests/e2e/specs', 7 | screenshotsFolder: 'tests/e2e/screenshots', 8 | videosFolder: 'tests/e2e/videos', 9 | supportFile: 'tests/e2e/support/index.js' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/HelloWorld.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import HelloWorld from '@/components/HelloWorld.vue'; 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message'; 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg }, 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/views/ShoppingCart.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**" 9 | ] 10 | }, 11 | "rules": { 12 | "quotemark": [true, "single"], 13 | "indent": [true, "spaces", 2], 14 | "interface-name": false, 15 | "ordered-imports": false, 16 | "object-literal-sort-keys": false, 17 | "no-consecutive-blank-lines": false, 18 | 19 | "semicolon": [false, "always"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/store/actions.ts: -------------------------------------------------------------------------------- 1 | import { Commit, Action, ActionTree } from 'vuex' 2 | import * as types from './mutation-types' 3 | import { State, Product, AddToCartPayload } from './index' 4 | 5 | const addToCart: Action = (context: { commit: Commit }, product: Product) => { 6 | if (product.inventory > 0) { 7 | const payload: AddToCartPayload = { 8 | id: product.id, 9 | } 10 | context.commit(types.ADD_TO_CART, payload) 11 | } 12 | } 13 | 14 | const actions: ActionTree = { 15 | addToCart, 16 | } 17 | 18 | export default actions 19 | -------------------------------------------------------------------------------- /src/components/ShoppingCart.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'ts', 4 | 'tsx', 5 | 'js', 6 | 'jsx', 7 | 'json', 8 | 'vue' 9 | ], 10 | transform: { 11 | '^.+\\.vue$': 'vue-jest', 12 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 13 | '^.+\\.tsx?$': 'ts-jest' 14 | }, 15 | moduleNameMapper: { 16 | '^@/(.*)$': '/src/$1' 17 | }, 18 | snapshotSerializers: [ 19 | 'jest-serializer-vue' 20 | ], 21 | testMatch: [ 22 | '/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))' 23 | ] 24 | } -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/views/Home.vue' 4 | import About from '@/views/About.vue' 5 | import ShoppingCart from '@/views/ShoppingCart.vue' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | component: Home, 15 | }, 16 | { 17 | path: '/about', 18 | name: 'about', 19 | component: About, 20 | }, 21 | { 22 | path: '/vuex', 23 | name: 'vuex', 24 | component: ShoppingCart, 25 | }, 26 | ], 27 | }) 28 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-cli-3-typescript-demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/store/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree, Getter } from 'vuex' 2 | import { State, CartProduct } from './index' 3 | 4 | const cartProducts: Getter = (state: State) => { 5 | return state.cart.added.map((shape) => { 6 | const product = state.products.all.find((p) => p.id === shape.id) 7 | if (product) { 8 | const cartProduct: CartProduct = { 9 | title: product.title, 10 | price: product.price, 11 | quantity: shape.quantity, 12 | } 13 | return cartProduct 14 | } 15 | }) 16 | } 17 | 18 | const getterTree: GetterTree = { 19 | cartProducts, 20 | } 21 | 22 | export default getterTree 23 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /src/api/shop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mocking client-server processing 3 | */ 4 | 5 | import { CartProduct } from '../store' 6 | 7 | const initProducts = [ 8 | { id: 1, title: 'iPad 4 Mini', price: 500.01, inventory: 2 }, 9 | { id: 2, title: 'H&M T-Shirt White', price: 10.99, inventory: 10 }, 10 | { id: 3, title: 'Charli XCX - Sucker CD', price: 19.99, inventory: 5 }, 11 | ] 12 | 13 | export default { 14 | getProducts(cb: (products: any[]) => void) { 15 | setTimeout(() => cb(initProducts), 100) 16 | }, 17 | 18 | buyProducts(products: CartProduct[], cb: () => void, errorCb: () => void) { 19 | setTimeout(() => { 20 | // simulate random checkout failure. 21 | Math.random() > 0.5 || navigator.userAgent.indexOf('PhantomJS') > -1 22 | ? cb() 23 | : errorCb() 24 | }, 100) 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /src/currency.ts: -------------------------------------------------------------------------------- 1 | const digitsRE = /(\d{3})(?=\d)/g 2 | 3 | export function currencyFilter(value: any, currency: any, decimals: any) { 4 | value = parseFloat(value) 5 | if (!isFinite(value) || (!value && value !== 0)) { 6 | return '' 7 | } 8 | currency = currency != null ? currency : '$' 9 | decimals = decimals != null ? decimals : 2 10 | const stringified = Math.abs(value).toFixed(decimals) 11 | const intVal = decimals ? stringified.slice(0, -1 - decimals) : stringified 12 | const i = intVal.length % 3 13 | const head = i > 0 ? intVal.slice(0, i) + (intVal.length > 3 ? ',' : '') : '' 14 | const floatVal = decimals ? stringified.slice(-1 - decimals) : '' 15 | const sign = value < 0 ? '-' : '' 16 | return ( 17 | sign + currency + head + intVal.slice(i).replace(digitsRE, '$1,') + floatVal 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "node", 16 | "jest" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "es2015", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | 31 | 32 | 51 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex, { Commit, Dispatch } from 'vuex' 3 | import actions from './actions' 4 | import getters from './getters' 5 | import cart, { State as CardState } from './modules/cart' 6 | import products, { State as ProductsState } from './modules/products' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | actions, 12 | getters, 13 | modules: { 14 | cart, 15 | products, 16 | }, 17 | }) 18 | 19 | export interface ActionContextBasic { 20 | commit: Commit, 21 | dispatch: Dispatch 22 | } 23 | 24 | export interface State { 25 | cart: CardState, 26 | products: ProductsState 27 | } 28 | 29 | export type CheckoutStatus = 'successful' | 'failed' | null 30 | 31 | export interface Product { 32 | id: number, 33 | title: string, 34 | price: number, 35 | inventory: number 36 | } 37 | 38 | export interface CartProduct { 39 | title: string, 40 | price: number, 41 | quantity: number 42 | } 43 | 44 | export interface AddToCartPayload { 45 | id: number 46 | } 47 | -------------------------------------------------------------------------------- /src/store/modules/products.ts: -------------------------------------------------------------------------------- 1 | import shop from '../../api/shop' 2 | import * as types from '../mutation-types' 3 | import { ActionContextBasic, Product, AddToCartPayload } from '../index' 4 | 5 | export interface ProductsPayload { 6 | products: Product[] 7 | } 8 | 9 | export interface State { 10 | all: Product[] 11 | } 12 | 13 | // initial state 14 | const initState = { 15 | all: [], 16 | } 17 | 18 | // getters 19 | const getters = { 20 | allProducts: (state: State) => state.all, 21 | } 22 | 23 | // actions 24 | const actions = { 25 | getAllProducts(context: ActionContextBasic) { 26 | shop.getProducts((products: Product[]) => { 27 | const payload: ProductsPayload = { 28 | products, 29 | } 30 | context.commit(types.RECEIVE_PRODUCTS, payload) 31 | }) 32 | }, 33 | } 34 | 35 | // mutations 36 | const mutations = { 37 | [types.RECEIVE_PRODUCTS](state: State, payload: ProductsPayload) { 38 | state.all = payload.products 39 | }, 40 | 41 | [types.ADD_TO_CART](state: State, payload: AddToCartPayload) { 42 | const product = state.all.find((p) => p.id === payload.id) 43 | if (product) { 44 | product.inventory-- 45 | } 46 | }, 47 | } 48 | 49 | export default { 50 | state: initState, 51 | getters, 52 | actions, 53 | mutations, 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-vuex-typescript-demo 2 | 3 | > Vue2.5+ + Vuex + Typescript 示例 4 | 5 | - 整体基于 [vue-cli](https://github.com/vuejs/vue-cli) 3.0 (rc.3版) 生成代码改造 6 | 7 | - Vuex 基于官方 [shopping-cart](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart) 示例改造 8 | 9 | - 支持 [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator) 及 [vuex-class](https://github.com/ktsn/vuex-class) 组件式写法 10 | 11 | - 支持 vue + vuex 原生写法 12 | 13 | - 支持 tslint 14 | 15 | - 包含 [element-ui](https://github.com/ElemeFE/element) 示例(2.x 版本,支持按需加载) 16 | 17 | - 包含 sass 示例 18 | 19 | > vue-cli 2.x 版本 示例见 [vue-cli-2 分支](https://github.com/qidaizhe11/vue-vuex-typescript-demo/tree/vue-cli-2) 20 | 21 | ## 相关文章 22 | 23 | - [Vue2.5+ Typescript 引入全面指南](https://segmentfault.com/a/1190000011853167) 24 | 25 | - [Vue2.5+ Typescript 引入全面指南 - Vuex篇](https://segmentfault.com/a/1190000011864013) 26 | 27 | ## Build Setup 28 | 29 | ``` bash 30 | # install dependencies 31 | npm install 32 | 33 | # serve with hot reload at localhost:8080 34 | npm run serve 35 | 36 | # build for production with minification 37 | npm run build 38 | 39 | # build for production with the bundle analyzer report 40 | npm run build -- --report 41 | 42 | # run unit tests 43 | npm run test:unit 44 | 45 | # run e2e tests 46 | npm run test:e2e 47 | 48 | ## License 49 | 50 | MIT 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-vuex-typescript-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "test:unit": "vue-cli-service test:unit", 10 | "test:e2e": "vue-cli-service test:e2e" 11 | }, 12 | "dependencies": { 13 | "element-ui": "^2.4.11", 14 | "vue": "^2.5.17", 15 | "vue-class-component": "^6.3.2", 16 | "vue-property-decorator": "^6.1.0", 17 | "vue-router": "^3.0.2", 18 | "vuex": "^3.0.1", 19 | "vuex-class": "^0.3.1" 20 | }, 21 | "devDependencies": { 22 | "@types/jest": "^22.2.3", 23 | "@vue/cli-plugin-babel": "^3.1.1", 24 | "@vue/cli-plugin-e2e-cypress": "^3.1.2", 25 | "@vue/cli-plugin-typescript": "^3.1.1", 26 | "@vue/cli-plugin-unit-jest": "^3.1.1", 27 | "@vue/cli-service": "^3.1.4", 28 | "@vue/test-utils": "^1.0.0-beta.25", 29 | "babel-core": "^7.0.0-bridge.0", 30 | "babel-plugin-component": "^1.1.1", 31 | "lint-staged": "^6.1.1", 32 | "node-sass": "^4.10.0", 33 | "sass-loader": "^7.1.0", 34 | "ts-jest": "^22.4.6", 35 | "typescript": "^3.1.6", 36 | "vue-template-compiler": "^2.5.17" 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "last 2 versions", 41 | "not ie <= 8" 42 | ], 43 | "gitHooks": { 44 | "pre-commit": "lint-staged" 45 | }, 46 | "lint-staged": { 47 | "*.ts": [ 48 | "vue-cli-service lint", 49 | "git add" 50 | ], 51 | "*.vue": [ 52 | "vue-cli-service lint", 53 | "git add" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/ProductList.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 71 | 72 | 84 | 85 | -------------------------------------------------------------------------------- /src/store/modules/cart.ts: -------------------------------------------------------------------------------- 1 | import { Commit } from 'vuex' 2 | import shop from '../../api/shop' 3 | import * as types from '../mutation-types' 4 | import { CartProduct, CheckoutStatus, AddToCartPayload } from '../index' 5 | 6 | interface Shape { 7 | id: number 8 | quantity: number 9 | } 10 | 11 | interface CheckoutFailurePayload { 12 | savedCartItems: Shape[] 13 | } 14 | 15 | export interface State { 16 | added: Shape[] 17 | checkoutStatus: CheckoutStatus 18 | } 19 | 20 | // initial state 21 | // shape: [{ id, quantity }] 22 | const initState: State = { 23 | added: [], 24 | checkoutStatus: null, 25 | } 26 | 27 | // getters 28 | const getters = { 29 | checkoutStatus: (state: State) => state.checkoutStatus, 30 | } 31 | 32 | // actions 33 | const actions = { 34 | checkout(context: { commit: Commit; state: State }, products: CartProduct[]) { 35 | const failurePayload: CheckoutFailurePayload = { 36 | savedCartItems: [...context.state.added], 37 | } 38 | context.commit(types.CHECKOUT_REQUEST) 39 | shop.buyProducts( 40 | products, 41 | () => context.commit(types.CHECKOUT_SUCCESS), 42 | () => context.commit(types.CHECKOUT_FAILURE, failurePayload), 43 | ) 44 | }, 45 | } 46 | 47 | // mutations 48 | const mutations = { 49 | [types.ADD_TO_CART](state: State, payload: AddToCartPayload) { 50 | state.checkoutStatus = null 51 | const record = state.added.find((p) => p.id === payload.id) 52 | if (!record) { 53 | state.added.push({ 54 | id: payload.id, 55 | quantity: 1, 56 | }) 57 | } else { 58 | record.quantity++ 59 | } 60 | }, 61 | 62 | [types.CHECKOUT_REQUEST](state: State) { 63 | // clear cart 64 | state.added = [] 65 | state.checkoutStatus = null 66 | }, 67 | 68 | [types.CHECKOUT_SUCCESS](state: State) { 69 | state.checkoutStatus = 'successful' 70 | }, 71 | 72 | [types.CHECKOUT_FAILURE](state: State, payload: CheckoutFailurePayload) { 73 | // rollback to the cart saved before sending the request 74 | state.added = payload.savedCartItems 75 | state.checkoutStatus = 'failed' 76 | }, 77 | } 78 | 79 | export default { 80 | state: initState, 81 | getters, 82 | actions, 83 | mutations, 84 | } 85 | -------------------------------------------------------------------------------- /src/components/Cart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 89 | --------------------------------------------------------------------------------