打包工具由webpack变为vite、将vue2代码重构成vue3、vuex替换成pinia,并使用typescript来编码

This commit is contained in:
fantasticbin 2022-06-22 22:00:12 +08:00
parent 812bf3afc0
commit 8ca1dc38f5
33 changed files with 2250 additions and 615 deletions

8
.eslintignore Normal file
View File

@ -0,0 +1,8 @@
/build/
/config/
/dist/
/*.js
/*.zip
/*.rar
/node_modules/
/test/unit/coverage/

View File

@ -1,10 +1,32 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
parserOptions: {
parser: "@babel/eslint-parser",
},
};
extends: [
'eslint:recommended',
"plugin:vue/vue3-recommended",
"prettier"
],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-labels': ['warn', {'allowLoop': true}],
'no-unused-vars': 'off',
'vue/no-mutating-props': 'off',
'vue/require-default-prop': 'off',
'vue/no-unused-vars': 'off',
'vue/prop-name-casing': 'off',
'vue/attribute-hyphenation': 'off',
'vue/no-template-shadow': 'off',
'vue/component-definition-name-casing': 'off'
},
parserOptions: {
parser: "@babel/eslint-parser",
},
}

25
.gitignore vendored
View File

@ -1,3 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
package-lock.json
dist
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,21 +1,16 @@
# vue_cart
# 使用 Vue 3 + Pinia + TypeScript + Vite 搭建一个简易购物车功能示例
> 使用 Vue 2 脚手架搭建一个简易购物车功能示例
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Build Setup
## Recommended IDE Setup
``` bash
# install dependencies
npm install
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
# serve with hot reload at localhost:8080
npm run dev
## Type Support For `.vue` Imports in TS
# build for production with minification
npm run build
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
# build for production and view the bundle analyzer report
npm run build --report
```
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
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).
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

5
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

14
components.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ProductList: typeof import('./src/components/ProductList.vue')['default']
ShoppingCart: typeof import('./src/components/ShoppingCart.vue')['default']
}
}
export {}

View File

@ -1,225 +0,0 @@
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,
};

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,52 +1,35 @@
{
"name": "vue_cart",
"version": "1.0.0",
"description": "",
"main": "main.js",
"name": "vue3_vite_pinia_cart",
"private": true,
"version": "0.0.0",
"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"
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"element-plus": "^2.2.6",
"vue": "^3.2.33",
"vue-router": "^4.0.15",
"vuex": "^4.0.2"
"pinia": "^2.0.14",
"vue": "^3.2.25"
},
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
"devDependencies": {
"@babel/eslint-parser": "^7.18.2",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"@vitejs/plugin-vue": "^2.3.3",
"eslint": "^8.18.0",
"eslint-config-alloy": "^4.5.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^9.1.1",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.8.8",
"unplugin-vue-components": "^0.19.6",
"vite": "^2.9.9",
"vite-plugin-eslint": "^1.6.1",
"vue-eslint-parser": "^9.0.2",
"vue-tsc": "^0.34.7"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,17 +0,0 @@
<!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>

View File

@ -10,16 +10,12 @@
</div>
</template>
<script>
import { mapState } from 'vuex'
<script setup lang="ts">
import { computed } from 'vue'
import { useUser } from './store/user'
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 }
}
const email = computed(() => useUser().userInfo.email)
</script>

View File

@ -1,23 +0,0 @@
/**
* 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)
}
}

30
src/api/shop.ts Normal file
View File

@ -0,0 +1,30 @@
type product = {
id: number,
title: string,
price: number,
inventory: number,
}
const _products: product[] = [
{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}
]
const shop = {
getProducts (cb: (product: product[]) => void) {
setTimeout(() => cb(_products), 100)
},
buyProducts (cb: () => void, errorCb: () => void) {
setTimeout(() => {
// simulate random checkout failure.
Math.random() > 0.5
? cb()
: errorCb()
}, 100)
}
}
export { shop }
export type { product }

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,69 +1,56 @@
<template>
<ul>
<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="numbers[product.id]" />
<el-input-number v-else :disabled="true" />
<el-button
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="numbers[product.id]" />
<el-input-number v-else :disabled="true" />
<el-button
:disabled="!product.inventory"
@click="addProductToCart(product)">
加入购物车
</el-button>
</el-button>
</li>
</ul>
</ul>
</template>
<script>
import { mapState } from 'vuex'
<script lang="ts">
import type { product } from '../api/shop'
type Numbers = {
[id: number]: number
}
</script>
<script setup lang="ts">
import { reactive, computed, watch } from 'vue'
import { useCart } from '../store/cart'
import { useProducts } from '../store/products'
import { ElButton } from 'element-plus'
export default {
data () {
return {
numbers: {}
};
},
components: { ElButton },
computed: mapState({
products: state => state.products.all,
}),
// computed: {
// products(){
// return this.$store.state.products.all
// }
// },
watch: {
products: {
handler: function (val) {
val.forEach(product => {
if (this.numbers[product.id] === undefined) {
// this.$set(this.numbers, product.id, 1);
// Vue 3
this.numbers[product.id] = 1;
}
});
},
// immediate: true
immediate: true
}
},
// methods: mapActions('cart', [
// 'addProductToCart'
// ]),
methods: {
addProductToCart(product){
this.numbers[product.id] = this.numbers[product.id] < 1 ? 1 : this.numbers[product.id];
this.numbers[product.id] = this.numbers[product.id] > product.inventory ? product.inventory : this.numbers[product.id];
const numbers = reactive({}) as Numbers
const products = computed(() => useProducts().all)
const addProductToCart = (product: product) => {
numbers[product.id] = numbers !== {} && numbers[product.id] < 1 ? 1 : numbers[product.id]
numbers[product.id] = numbers[product.id] > product.inventory ? product.inventory : numbers[product.id]
this.$store.dispatch('cart/addProductToCart', { product, number: this.numbers[product.id] });
this.numbers[product.id] = 1;
}
},
created () {
this.$store.dispatch('products/getAllProducts');
}
useCart().addProductToCart(product, numbers[product.id])
numbers[product.id] = 1
}
watch(products, (val) => {
val.forEach((product) => {
if (!numbers.hasOwnProperty(product.id)) {
numbers[product.id] = 1
}
})
}, {
// immediate: true
immediate: true
})
//
useProducts().getAllProducts()
</script>

View File

@ -1,54 +1,29 @@
<template>
<div class="cart">
<div class="cart">
<h2>清单</h2>
<p v-show="!products.length"><i>请添加产品到购物车</i></p>
<ul>
<li
<li
v-for="product in products"
:key="product.id">
{{ product.title }} - {{ product.price }} x {{ product.quantity }}
</li>
</li>
</ul>
<p>合计: {{ total }}</p>
<p><el-button type="primary" :disabled="!products.length" @click="checkout(products)">提交</el-button></p>
<p><el-button type="primary" :disabled="!products.length" @click="checkout()">提交</el-button></p>
<p v-show="checkoutStatus">提交 {{ checkoutStatus }}.</p>
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex'
<script setup lang="ts">
import { computed } from 'vue'
import { useCart } from '../store/cart'
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)
}
},
const checkoutStatus = computed(() => useCart().checkoutStatus)
const products = computed(() => useCart().cartProducts)
const total = computed(() => useCart().cartTotalPrice)
const checkout = function () {
useCart().checkout()
}
</script>

8
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,5 +0,0 @@
import { createApp } from "vue";
import store from "./store"
import App from "./App";
createApp(App).use(store).mount(document.getElementById("app"));

5
src/main.ts Normal file
View File

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
createApp(App).use(createPinia()).mount('#app')

102
src/store/cart.ts Normal file
View File

@ -0,0 +1,102 @@
import { defineStore } from 'pinia'
import { shop, product } from '../api/shop'
import { useProducts } from './products'
/**
*
*/
type CartItem = {
id: number,
quantity: number,
}
/**
*
*/
type CartItemInfo = {
// 商品ID
id: number,
// 标题
title: string,
// 价格
price: number,
// 数量
quantity: number,
}
/**
* state定义
*/
type state = {
// 购物车商品列表数据
items: CartItem[],
// 标记购买状态
checkoutStatus: null | string
}
export const useCart = defineStore('cart', {
state: (): state => {
return {
items: [],
checkoutStatus: null
}
},
getters: {
cartProducts (): CartItemInfo[] {
return this.items.map(({ id, quantity }) => {
const product = useProducts().all.find(product => product.id === id) as product
return {
id: product.id,
title: product.title,
price: product.price,
quantity
}
})
},
cartTotalPrice () {
const products = this.cartProducts as CartItemInfo[] // 不知道为啥直接this.cartProducts.reduce就会报错必须要转换一下
return products.reduce((total, product) => {
return total + product.price * product.quantity
}, 0)
}
},
actions: {
checkout () {
const savedCartItems = [...this.items]
this.checkoutStatus = null
// empty cart
this.items = []
shop.buyProducts(
() => this.checkoutStatus = 'successful',
() => {
this.checkoutStatus = 'failed'
this.items = savedCartItems
}
)
},
addProductToCart (product: product, number: number) {
this.checkoutStatus = null
if (product.inventory > 0) {
const cartItem = this.items.find(item => item.id === product.id)
if (!cartItem) {
this.items.push({
id: product.id,
quantity: number
})
} else {
cartItem.quantity += number
}
// remove 1 item from stock
useProducts().decrementProductInventory(product.id, number)
}
}
}
})

View File

@ -1,17 +0,0 @@
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
},
})

View File

@ -1,95 +0,0 @@
import shop from '../../api/shop'
import { CART, PRODUCTS } from '../mutation-types'
// initial state
// shape: [{ id, quantity }]
const state = {
// 购物车商品列表数据
items: [],
// 标记购买状态
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, number} ) {
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, number: number })
} else {
commit(CART.INCREMENT_ITEM_QUANTITY, { id: cartItem.id, number: number })
}
// remove 1 item from stock
commit(`products/${PRODUCTS.DECREMENT_PRODUCT_INVENTORY}`, { id: product.id, number: number }, { root: true })
}
}
}
// mutations
const mutations = {
[CART.PUSH_PRODUCT_TO_CART] (state, { id, number }) {
state.items.push({
id,
quantity: Number(number)
})
},
[CART.INCREMENT_ITEM_QUANTITY] (state, { id, number }) {
const cartItem = state.items.find(item => item.id === id)
cartItem.quantity += Number(number)
},
[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
}

View File

@ -1,40 +0,0 @@
import shop from '../../api/shop'
import {PRODUCTS} from '../mutation-types'
// initial state
const state = {
// 商品列表
all: []
}
// getters
const getters = {}
// actions
const actions = {
getAllProducts ({ commit }) {
shop.getProducts(products => {
commit(PRODUCTS.SET_PRODUCTS, products)
})
}
}
// mutations
const mutations = {
[PRODUCTS.SET_PRODUCTS] (state, products) {
state.all = products
},
[PRODUCTS.DECREMENT_PRODUCT_INVENTORY] (state, { id, number }) {
const product = state.all.find(product => product.id === id)
product.inventory -= Number(number)
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@ -1,11 +0,0 @@
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'
}

31
src/store/products.ts Normal file
View File

@ -0,0 +1,31 @@
import { defineStore } from 'pinia'
import { shop, product } from '../api/shop'
/**
* state定义
*/
type state = {
all: product[]
}
export const useProducts = defineStore('products', {
state: (): state => {
return {
// 商品列表
all: []
}
},
actions: {
getAllProducts () {
shop.getProducts(products => {
this.all = products
})
},
decrementProductInventory (id: number, number: number) {
const product = this.all.find(product => product.id === id) as product
product.inventory -= number
}
}
})

11
src/store/user.ts Normal file
View File

@ -0,0 +1,11 @@
import { defineStore } from 'pinia'
export const useUser = defineStore('user', {
state: () => {
return {
userInfo: {
email: "xxxxxx@qq.com"
}
}
}
})

View File

@ -1,7 +0,0 @@
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': green,
),
),
);

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

8
tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

18
vite.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
]
})

1828
yarn.lock Normal file

File diff suppressed because it is too large Load Diff