代码
This commit is contained in:
commit
a68336073b
10
.eslintrc.js
Normal file
10
.eslintrc.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
|
||||||
|
parserOptions: {
|
||||||
|
parser: "@babel/eslint-parser",
|
||||||
|
},
|
||||||
|
};
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
dist
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# vue_cart
|
||||||
|
|
||||||
|
> 使用 Vue 2 脚手架搭建一个简易购物车功能示例
|
||||||
|
|
||||||
|
## Build Setup
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
# install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# serve with hot reload at localhost:8080
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# build for production with minification
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# build for production and view the bundle analyzer report
|
||||||
|
npm run build --report
|
||||||
|
```
|
||||||
|
|
||||||
|
For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
|
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"],
|
||||||
|
};
|
225
config/webpack.config.js
Normal file
225
config/webpack.config.js
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
|
||||||
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||||
|
const TerserWebpackPlugin = require("terser-webpack-plugin");
|
||||||
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
|
const { VueLoaderPlugin } = require("vue-loader");
|
||||||
|
const { DefinePlugin } = require("webpack");
|
||||||
|
const AutoImport = require("unplugin-auto-import/webpack");
|
||||||
|
const Components = require("unplugin-vue-components/webpack");
|
||||||
|
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
|
||||||
|
// 需要通过 cross-env 定义环境变量
|
||||||
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
const getStyleLoaders = (preProcessor) => {
|
||||||
|
return [
|
||||||
|
isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
|
||||||
|
"css-loader",
|
||||||
|
{
|
||||||
|
loader: "postcss-loader",
|
||||||
|
options: {
|
||||||
|
postcssOptions: {
|
||||||
|
plugins: ["postcss-preset-env"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preProcessor && {
|
||||||
|
loader: preProcessor,
|
||||||
|
options:
|
||||||
|
preProcessor === "sass-loader"
|
||||||
|
? {
|
||||||
|
// 自定义主题:自动引入我们定义的scss文件
|
||||||
|
additionalData: `@use "@/styles/element/index.scss" as *;`,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./src/main.js",
|
||||||
|
output: {
|
||||||
|
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
|
||||||
|
filename: isProduction
|
||||||
|
? "static/js/[name].[contenthash:10].js"
|
||||||
|
: "static/js/[name].js",
|
||||||
|
chunkFilename: isProduction
|
||||||
|
? "static/js/[name].[contenthash:10].chunk.js"
|
||||||
|
: "static/js/[name].chunk.js",
|
||||||
|
assetModuleFilename: "static/js/[hash:10][ext][query]",
|
||||||
|
clean: true,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: getStyleLoaders(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: getStyleLoaders("less-loader"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/,
|
||||||
|
use: getStyleLoaders("sass-loader"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
use: getStyleLoaders("stylus-loader"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg)$/,
|
||||||
|
type: "asset",
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10 * 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(ttf|woff2?)$/,
|
||||||
|
type: "asset/resource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(jsx|js)$/,
|
||||||
|
include: path.resolve(__dirname, "../src"),
|
||||||
|
loader: "babel-loader",
|
||||||
|
options: {
|
||||||
|
cacheDirectory: true,
|
||||||
|
cacheCompression: false,
|
||||||
|
plugins: [
|
||||||
|
// "@babel/plugin-transform-runtime" // presets中包含了
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// vue-loader不支持oneOf
|
||||||
|
{
|
||||||
|
test: /\.vue$/,
|
||||||
|
loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
|
||||||
|
options: {
|
||||||
|
// 开启缓存
|
||||||
|
cacheDirectory: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/.cache/vue-loader"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ESLintWebpackPlugin({
|
||||||
|
context: path.resolve(__dirname, "../src"),
|
||||||
|
exclude: "node_modules",
|
||||||
|
cache: true,
|
||||||
|
cacheLocation: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../node_modules/.cache/.eslintcache"
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: path.resolve(__dirname, "../public/index.html"),
|
||||||
|
}),
|
||||||
|
new CopyPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: path.resolve(__dirname, "../public"),
|
||||||
|
to: path.resolve(__dirname, "../dist"),
|
||||||
|
toType: "dir",
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
globOptions: {
|
||||||
|
ignore: ["**/index.html"],
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
minimized: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
isProduction &&
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: "static/css/[name].[contenthash:10].css",
|
||||||
|
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
|
||||||
|
}),
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
new DefinePlugin({
|
||||||
|
__VUE_OPTIONS_API__: "true",
|
||||||
|
__VUE_PROD_DEVTOOLS__: "false",
|
||||||
|
}),
|
||||||
|
// 按需加载element-plus组件样式
|
||||||
|
AutoImport({
|
||||||
|
resolvers: [ElementPlusResolver()],
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [
|
||||||
|
ElementPlusResolver({
|
||||||
|
importStyle: "sass", // 自定义主题
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
].filter(Boolean),
|
||||||
|
optimization: {
|
||||||
|
minimize: isProduction,
|
||||||
|
// 压缩的操作
|
||||||
|
minimizer: [
|
||||||
|
new CssMinimizerPlugin(),
|
||||||
|
new TerserWebpackPlugin(),
|
||||||
|
],
|
||||||
|
splitChunks: {
|
||||||
|
chunks: "all",
|
||||||
|
cacheGroups: {
|
||||||
|
// layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
|
||||||
|
// 可以单独打包,从而复用
|
||||||
|
// 如果项目中没有,请删除
|
||||||
|
/*layouts: {
|
||||||
|
name: "layouts",
|
||||||
|
test: path.resolve(__dirname, "../src/layouts"),
|
||||||
|
priority: 40,
|
||||||
|
},*/
|
||||||
|
// 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
|
||||||
|
// 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
|
||||||
|
// 如果项目中没有,请删除
|
||||||
|
elementUI: {
|
||||||
|
name: "chunk-elementPlus",
|
||||||
|
test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
|
||||||
|
priority: 30,
|
||||||
|
},
|
||||||
|
// 将vue相关的库单独打包,减少node_modules的chunk体积。
|
||||||
|
vue: {
|
||||||
|
name: "vue",
|
||||||
|
test: /[\\/]node_modules[\\/]vue(.*)[\\/]/,
|
||||||
|
chunks: "initial",
|
||||||
|
priority: 20,
|
||||||
|
},
|
||||||
|
libs: {
|
||||||
|
name: "chunk-libs",
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
priority: 10, // 权重最低,优先考虑前面内容
|
||||||
|
chunks: "initial",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtimeChunk: {
|
||||||
|
name: (entrypoint) => `runtime~${entrypoint.name}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [".vue", ".js", ".json"],
|
||||||
|
alias: {
|
||||||
|
// 路径别名
|
||||||
|
"@": path.resolve(__dirname, "../src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
open: true,
|
||||||
|
host: "localhost",
|
||||||
|
port: 3000,
|
||||||
|
hot: true,
|
||||||
|
compress: true,
|
||||||
|
historyApiFallback: true, // 解决vue-router刷新404问题
|
||||||
|
},
|
||||||
|
mode: isProduction ? "production" : "development",
|
||||||
|
devtool: isProduction ? "source-map" : "cheap-module-source-map",
|
||||||
|
performance: false,
|
||||||
|
};
|
52
package.json
Normal file
52
package.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "vue_cart",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run dev",
|
||||||
|
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
|
||||||
|
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.17.10",
|
||||||
|
"@babel/eslint-parser": "^7.17.0",
|
||||||
|
"@vue/cli-plugin-babel": "^5.0.4",
|
||||||
|
"babel-loader": "^8.2.5",
|
||||||
|
"copy-webpack-plugin": "^10.2.4",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"css-loader": "^6.7.1",
|
||||||
|
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||||
|
"eslint-plugin-vue": "^8.7.1",
|
||||||
|
"eslint-webpack-plugin": "^3.1.1",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"less-loader": "^10.2.0",
|
||||||
|
"mini-css-extract-plugin": "^2.6.0",
|
||||||
|
"postcss-preset-env": "^7.5.0",
|
||||||
|
"sass": "^1.52.3",
|
||||||
|
"sass-loader": "^12.6.0",
|
||||||
|
"stylus-loader": "^6.2.0",
|
||||||
|
"unplugin-auto-import": "^0.8.8",
|
||||||
|
"unplugin-vue-components": "^0.19.6",
|
||||||
|
"vue-loader": "^17.0.0",
|
||||||
|
"vue-style-loader": "^4.1.3",
|
||||||
|
"vue-template-compiler": "^2.6.14",
|
||||||
|
"webpack": "^5.72.0",
|
||||||
|
"webpack-cli": "^4.9.2",
|
||||||
|
"webpack-dev-server": "^4.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"element-plus": "^2.2.6",
|
||||||
|
"vue": "^3.2.33",
|
||||||
|
"vue-router": "^4.0.15",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 2 version",
|
||||||
|
"> 1%",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
17
public/index.html
Normal file
17
public/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= htmlWebpackPlugin.options.url %>favicon.ico">
|
||||||
|
<title>hello-world</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
25
src/App.vue
Normal file
25
src/App.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<h1>购物车示例</h1>
|
||||||
|
<p>账号: {{email}}</p>
|
||||||
|
<hr>
|
||||||
|
<h2>产品</h2>
|
||||||
|
<ProductList/>
|
||||||
|
<hr>
|
||||||
|
<ShoppingCart/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import ProductList from './components/ProductList.vue'
|
||||||
|
import ShoppingCart from './components/ShoppingCart.vue'
|
||||||
|
import 'element-plus/es/components/button/style/css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: mapState({
|
||||||
|
email: state => state.userInfo.email
|
||||||
|
}),
|
||||||
|
components: { ProductList, ShoppingCart }
|
||||||
|
}
|
||||||
|
</script>
|
23
src/api/shop.js
Normal file
23
src/api/shop.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Mocking client-server processing
|
||||||
|
*/
|
||||||
|
const _products = [
|
||||||
|
{"id": 1, "title": "华为 Mate 20", "price": 3999, "inventory": 2},
|
||||||
|
{"id": 2, "title": "小米 9", "price": 2999, "inventory": 0},
|
||||||
|
{"id": 3, "title": "OPPO R17", "price": 2999, "inventory": 5}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getProducts (cb) {
|
||||||
|
setTimeout(() => cb(_products), 100)
|
||||||
|
},
|
||||||
|
|
||||||
|
buyProducts (products, cb, errorCb) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// simulate random checkout failure.
|
||||||
|
Math.random() > 0.5
|
||||||
|
? cb()
|
||||||
|
: errorCb()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
48
src/components/ProductList.vue
Normal file
48
src/components/ProductList.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="product in products"
|
||||||
|
:key="product.id">
|
||||||
|
{{ product.title }} - {{ product.price }} x {{ product.inventory }}
|
||||||
|
<br>
|
||||||
|
<el-input-number v-if="product.inventory" :min="1" :max="product.inventory" v-model="preSelectProducts[product.id]" />
|
||||||
|
<el-input-number v-else :disabled="true" />
|
||||||
|
<el-button
|
||||||
|
:disabled="!product.inventory"
|
||||||
|
@click="addProductToCart({ product: product, num: preSelectProducts[product.id] })">
|
||||||
|
加入购物车
|
||||||
|
</el-button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState, mapActions } from 'vuex'
|
||||||
|
import { ElButton } from 'element-plus'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { ElButton },
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
products: state => state.products.all,
|
||||||
|
preSelectProducts: state => state.cart.preSelectProducts
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// products(){
|
||||||
|
// return this.$store.state.products.all
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
methods: mapActions('cart', [
|
||||||
|
'addProductToCart'
|
||||||
|
]),
|
||||||
|
// methods: {
|
||||||
|
// addProductToCart(product){
|
||||||
|
// this.$store.dispatch('cart/addProductToCart', product)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
created () {
|
||||||
|
this.$store.dispatch('products/getAllProducts');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
54
src/components/ShoppingCart.vue
Normal file
54
src/components/ShoppingCart.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cart">
|
||||||
|
<h2>清单</h2>
|
||||||
|
<p v-show="!products.length"><i>请添加产品到购物车</i></p>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="product in products"
|
||||||
|
:key="product.id">
|
||||||
|
{{ product.title }} - {{ product.price }} x {{ product.quantity }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>合计: {{ total }}</p>
|
||||||
|
<p><el-button type="primary" :disabled="!products.length" @click="checkout(products)">提交</el-button></p>
|
||||||
|
<p v-show="checkoutStatus">提交 {{ checkoutStatus }}.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapState } from 'vuex'
|
||||||
|
import { ElButton } from 'element-plus'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { ElButton },
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
checkoutStatus: state => state.cart.checkoutStatus
|
||||||
|
}),
|
||||||
|
...mapGetters('cart', {
|
||||||
|
products: 'cartProducts',
|
||||||
|
total: 'cartTotalPrice'
|
||||||
|
}),
|
||||||
|
// ...mapGetters({
|
||||||
|
// products: 'cart/cartProducts',
|
||||||
|
// total: 'cart/cartTotalPrice'
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
// computed: {
|
||||||
|
// checkoutStatus(){
|
||||||
|
// return this.$store.state.cart.checkoutStatus
|
||||||
|
// },
|
||||||
|
// products() {
|
||||||
|
// return this.$store.getters['cart/cartProducts']
|
||||||
|
// },
|
||||||
|
// total() {
|
||||||
|
// return this.$store.getters['cart/cartTotalPrice']
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
methods: {
|
||||||
|
checkout (products) {
|
||||||
|
this.$store.dispatch('cart/checkout', products)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
5
src/main.js
Normal file
5
src/main.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import store from "./store"
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
createApp(App).use(store).mount(document.getElementById("app"));
|
17
src/store/index.js
Normal file
17
src/store/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createStore } from 'vuex'
|
||||||
|
import cart from './modules/cart'
|
||||||
|
import products from './modules/products'
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
state () {
|
||||||
|
return {
|
||||||
|
userInfo: {
|
||||||
|
email: "xxxxxx@qq.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
cart,
|
||||||
|
products
|
||||||
|
},
|
||||||
|
})
|
101
src/store/modules/cart.js
Normal file
101
src/store/modules/cart.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import shop from '../../api/shop'
|
||||||
|
import { CART, PRODUCTS } from '../mutation-types'
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
// shape: [{ id, quantity }]
|
||||||
|
const state = {
|
||||||
|
// 购物车商品列表数据
|
||||||
|
items: [],
|
||||||
|
// 用于存放要加入购物车的商品跟数量的对应关系
|
||||||
|
preSelectProducts: {},
|
||||||
|
// 标记购买状态
|
||||||
|
checkoutStatus: null
|
||||||
|
}
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const getters = {
|
||||||
|
cartProducts: (state, getters, rootState) => {
|
||||||
|
return state.items.map(({ id, quantity }) => {
|
||||||
|
const product = rootState.products.all.find(product => product.id === id)
|
||||||
|
return {
|
||||||
|
title: product.title,
|
||||||
|
price: product.price,
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
cartTotalPrice: (state, getters) => {
|
||||||
|
return getters.cartProducts.reduce((total, product) => {
|
||||||
|
return total + product.price * product.quantity
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
const actions = {
|
||||||
|
checkout ({ commit, state }, products) {
|
||||||
|
const savedCartItems = [...state.items]
|
||||||
|
commit(CART.SET_CHECKOUT_STATUS, null)
|
||||||
|
// empty cart
|
||||||
|
commit(CART.SET_CART_ITEMS, { items: [] })
|
||||||
|
shop.buyProducts(
|
||||||
|
products,
|
||||||
|
() => commit(CART.SET_CHECKOUT_STATUS, 'successful'),
|
||||||
|
() => {
|
||||||
|
commit(CART.SET_CHECKOUT_STATUS, 'failed')
|
||||||
|
// rollback to the cart saved before sending the request
|
||||||
|
commit(CART.SET_CART_ITEMS, { items: savedCartItems })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
addProductToCart ({ state, commit }, { product, num} ) {
|
||||||
|
num = num < 1 ? 1 : num;
|
||||||
|
num = num > product.inventory ? product.inventory : num;
|
||||||
|
commit(CART.SET_CHECKOUT_STATUS, null)
|
||||||
|
|
||||||
|
if (product.inventory > 0) {
|
||||||
|
const cartItem = state.items.find(item => item.id === product.id)
|
||||||
|
if (!cartItem) {
|
||||||
|
commit(CART.PUSH_PRODUCT_TO_CART, { id: product.id, num: num })
|
||||||
|
} else {
|
||||||
|
commit(CART.INCREMENT_ITEM_QUANTITY, { id: cartItem.id, num: num })
|
||||||
|
}
|
||||||
|
// remove 1 item from stock
|
||||||
|
commit(`products/${PRODUCTS.DECREMENT_PRODUCT_INVENTORY}`, { id: product.id, num: num }, { root: true })
|
||||||
|
state.preSelectProducts[product.id] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutations
|
||||||
|
const mutations = {
|
||||||
|
[CART.PUSH_PRODUCT_TO_CART] (state, { id, num }) {
|
||||||
|
state.items.push({
|
||||||
|
id,
|
||||||
|
quantity: Number(num)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
[CART.INCREMENT_ITEM_QUANTITY] (state, { id, num }) {
|
||||||
|
const cartItem = state.items.find(item => item.id === id)
|
||||||
|
cartItem.quantity += Number(num)
|
||||||
|
},
|
||||||
|
|
||||||
|
[CART.SET_CART_ITEMS] (state, { items }) {
|
||||||
|
state.items = items
|
||||||
|
},
|
||||||
|
|
||||||
|
[CART.SET_CHECKOUT_STATUS] (state, status) {
|
||||||
|
state.checkoutStatus = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
43
src/store/modules/products.js
Normal file
43
src/store/modules/products.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import shop from '../../api/shop'
|
||||||
|
import {PRODUCTS} from '../mutation-types'
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
const state = {
|
||||||
|
// 商品列表
|
||||||
|
all: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const getters = {}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
const actions = {
|
||||||
|
getAllProducts ({ commit, rootState }) {
|
||||||
|
shop.getProducts(products => {
|
||||||
|
for (let product of products) {
|
||||||
|
rootState.cart.preSelectProducts = Object.assign({}, rootState.cart.preSelectProducts, { [product.id]: 1 });
|
||||||
|
}
|
||||||
|
commit(PRODUCTS.SET_PRODUCTS, products)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutations
|
||||||
|
const mutations = {
|
||||||
|
[PRODUCTS.SET_PRODUCTS] (state, products) {
|
||||||
|
state.all = products
|
||||||
|
},
|
||||||
|
|
||||||
|
[PRODUCTS.DECREMENT_PRODUCT_INVENTORY] (state, { id, num }) {
|
||||||
|
const product = state.all.find(product => product.id === id)
|
||||||
|
product.inventory -= Number(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
11
src/store/mutation-types.js
Normal file
11
src/store/mutation-types.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const CART = {
|
||||||
|
PUSH_PRODUCT_TO_CART: 'pushProductToCart',
|
||||||
|
INCREMENT_ITEM_QUANTITY: 'incrementItemQuantity',
|
||||||
|
SET_CART_ITEMS: 'setCartItems',
|
||||||
|
SET_CHECKOUT_STATUS: 'setCheckoutStatus'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PRODUCTS = {
|
||||||
|
SET_PRODUCTS:'setProducts',
|
||||||
|
DECREMENT_PRODUCT_INVENTORY: 'decrementProductInventory'
|
||||||
|
}
|
7
src/styles/element/index.scss
Normal file
7
src/styles/element/index.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||||
|
$colors: (
|
||||||
|
'primary': (
|
||||||
|
'base': green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user