打包工具由webpack变为vite、将vue2代码重构成vue3、vuex替换成pinia,并使用typescript来编码
This commit is contained in:
		
							parent
							
								
									812bf3afc0
								
							
						
					
					
						commit
						8ca1dc38f5
					
				
							
								
								
									
										8
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /build/ | ||||||
|  | /config/ | ||||||
|  | /dist/ | ||||||
|  | /*.js | ||||||
|  | /*.zip | ||||||
|  | /*.rar | ||||||
|  | /node_modules/ | ||||||
|  | /test/unit/coverage/ | ||||||
							
								
								
									
										40
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @ -1,10 +1,32 @@ | |||||||
|  | // https://eslint.org/docs/user-guide/configuring
 | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     root: true, |   extends: [ | ||||||
|     env: { |     'eslint:recommended', | ||||||
|         node: true, |     "plugin:vue/vue3-recommended", | ||||||
|     }, |     "prettier" | ||||||
|     extends: ["plugin:vue/vue3-essential", "eslint:recommended"], |   ], | ||||||
|     parserOptions: { |   // required to lint *.vue files
 | ||||||
|         parser: "@babel/eslint-parser", |   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", | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,24 @@ | |||||||
|  | # Logs | ||||||
|  | logs | ||||||
|  | *.log | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|  | pnpm-debug.log* | ||||||
|  | lerna-debug.log* | ||||||
|  | 
 | ||||||
| node_modules | 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? | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								README.md
									
									
									
									
									
								
							| @ -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 | - [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) | ||||||
| # install dependencies |  | ||||||
| npm install |  | ||||||
| 
 | 
 | ||||||
| # serve with hot reload at localhost:8080 | ## Type Support For `.vue` Imports in TS | ||||||
| npm run dev |  | ||||||
| 
 | 
 | ||||||
| # build for production with minification | 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: | ||||||
| npm run build |  | ||||||
| 
 | 
 | ||||||
| # build for production and view the bundle analyzer 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. | ||||||
| npm run build --report | 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
									
								
							
							
						
						
									
										5
									
								
								auto-imports.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | // Generated by 'unplugin-auto-import'
 | ||||||
|  | export {} | ||||||
|  | declare global { | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -1,3 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
|     presets: ["@vue/cli-plugin-babel/preset"], |  | ||||||
| }; |  | ||||||
							
								
								
									
										14
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
										Normal 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 {} | ||||||
| @ -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
									
								
							
							
						
						
									
										13
									
								
								index.html
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										73
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								package.json
									
									
									
									
									
								
							| @ -1,52 +1,35 @@ | |||||||
| { | { | ||||||
|   "name": "vue_cart", |   "name": "vue3_vite_pinia_cart", | ||||||
|   "version": "1.0.0", |   "private": true, | ||||||
|   "description": "", |   "version": "0.0.0", | ||||||
|   "main": "main.js", |  | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "npm run dev", |     "dev": "vite", | ||||||
|     "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js", |     "build": "vue-tsc --noEmit && vite build", | ||||||
|     "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js" |     "preview": "vite preview" | ||||||
|   }, |  | ||||||
|   "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": { |   "dependencies": { | ||||||
|     "element-plus": "^2.2.6", |     "element-plus": "^2.2.6", | ||||||
|     "vue": "^3.2.33", |     "pinia": "^2.0.14", | ||||||
|     "vue-router": "^4.0.15", |     "vue": "^3.2.25" | ||||||
|     "vuex": "^4.0.2" |  | ||||||
|   }, |   }, | ||||||
|   "browserslist": [ |   "devDependencies": { | ||||||
|     "last 2 version", |     "@babel/eslint-parser": "^7.18.2", | ||||||
|     "> 1%", |     "@typescript-eslint/eslint-plugin": "^5.29.0", | ||||||
|     "not dead" |     "@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
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
| @ -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> |  | ||||||
							
								
								
									
										12
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/App.vue
									
									
									
									
									
								
							| @ -10,16 +10,12 @@ | |||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script setup lang="ts"> | ||||||
| import { mapState } from 'vuex' | import { computed } from 'vue' | ||||||
|  | import { useUser } from './store/user' | ||||||
| import ProductList from './components/ProductList.vue' | import ProductList from './components/ProductList.vue' | ||||||
| import ShoppingCart from './components/ShoppingCart.vue' | import ShoppingCart from './components/ShoppingCart.vue' | ||||||
| import 'element-plus/es/components/button/style/css' | import 'element-plus/es/components/button/style/css' | ||||||
| 
 | 
 | ||||||
| export default { | const email = computed(() => useUser().userInfo.email) | ||||||
|   computed: mapState({ |  | ||||||
|     email: state => state.userInfo.email |  | ||||||
|   }), |  | ||||||
|   components: { ProductList, ShoppingCart } |  | ||||||
| } |  | ||||||
| </script> | </script> | ||||||
| @ -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
									
								
							
							
						
						
									
										30
									
								
								src/api/shop.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.7 KiB | 
| @ -1,69 +1,56 @@ | |||||||
| <template> | <template> | ||||||
|   <ul> | <ul> | ||||||
|     <li |     <li | ||||||
|       v-for="product in products" |     v-for="product in products" | ||||||
|       :key="product.id"> |     :key="product.id"> | ||||||
|       {{ product.title }} - {{ product.price }} x {{ product.inventory }} |     {{ product.title }} - {{ product.price }} x {{ product.inventory }} | ||||||
|       <br> |     <br> | ||||||
|       <el-input-number v-if="product.inventory" :min="1" :max="product.inventory" v-model="numbers[product.id]" /> |     <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-input-number v-else :disabled="true" /> | ||||||
|       <el-button |     <el-button | ||||||
|         :disabled="!product.inventory" |         :disabled="!product.inventory" | ||||||
|         @click="addProductToCart(product)"> |         @click="addProductToCart(product)"> | ||||||
|         加入购物车 |         加入购物车 | ||||||
|       </el-button> |     </el-button> | ||||||
|     </li> |     </li> | ||||||
|   </ul> | </ul> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script lang="ts"> | ||||||
| import { mapState } from 'vuex' | import type { product } from '../api/shop' | ||||||
| import { ElButton } from 'element-plus' |  | ||||||
| 
 | 
 | ||||||
| export default { | type Numbers = { | ||||||
|   data () { |     [id: number]: number | ||||||
|     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]; |  | ||||||
| 
 |  | ||||||
|       this.$store.dispatch('cart/addProductToCart', { product, number: this.numbers[product.id] }); |  | ||||||
|       this.numbers[product.id] = 1; |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   created () { |  | ||||||
|     this.$store.dispatch('products/getAllProducts'); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| </script> | </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' | ||||||
|  | 
 | ||||||
|  | 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] | ||||||
|  | 
 | ||||||
|  |     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> | ||||||
| @ -1,54 +1,29 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="cart"> | <div class="cart"> | ||||||
|     <h2>清单</h2> |     <h2>清单</h2> | ||||||
|     <p v-show="!products.length"><i>请添加产品到购物车</i></p> |     <p v-show="!products.length"><i>请添加产品到购物车</i></p> | ||||||
|     <ul> |     <ul> | ||||||
|       <li |     <li | ||||||
|         v-for="product in products" |         v-for="product in products" | ||||||
|         :key="product.id"> |         :key="product.id"> | ||||||
|         {{ product.title }} - {{ product.price }} x {{ product.quantity }} |         {{ product.title }} - {{ product.price }} x {{ product.quantity }} | ||||||
|       </li> |     </li> | ||||||
|     </ul> |     </ul> | ||||||
|     <p>合计: {{ total }}</p> |     <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> |     <p v-show="checkoutStatus">提交 {{ checkoutStatus }}.</p> | ||||||
|   </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script setup lang="ts"> | ||||||
| import { mapGetters, mapState } from 'vuex' | import { computed } from 'vue' | ||||||
|  | import { useCart } from '../store/cart' | ||||||
| import { ElButton } from 'element-plus' | import { ElButton } from 'element-plus' | ||||||
| 
 | 
 | ||||||
| export default { | const checkoutStatus = computed(() => useCart().checkoutStatus) | ||||||
|   components: { ElButton }, | const products = computed(() => useCart().cartProducts) | ||||||
|   computed: { | const total = computed(() => useCart().cartTotalPrice) | ||||||
|     ...mapState({ | const checkout = function () { | ||||||
|       checkoutStatus: state => state.cart.checkoutStatus |     useCart().checkout() | ||||||
|     }), |  | ||||||
|     ...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> | </script> | ||||||
							
								
								
									
										8
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
										Normal 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 | ||||||
|  | } | ||||||
| @ -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
									
								
							
							
						
						
									
										5
									
								
								src/main.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										102
									
								
								src/store/cart.ts
									
									
									
									
									
										Normal 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) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }) | ||||||
| @ -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 |  | ||||||
|   }, |  | ||||||
| }) |  | ||||||
| @ -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 |  | ||||||
| } |  | ||||||
| @ -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 |  | ||||||
| } |  | ||||||
| @ -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
									
								
							
							
						
						
									
										31
									
								
								src/store/products.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										11
									
								
								src/store/user.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | import { defineStore } from 'pinia' | ||||||
|  | 
 | ||||||
|  | export const useUser = defineStore('user', { | ||||||
|  |     state: () => { | ||||||
|  |         return { | ||||||
|  |             userInfo: { | ||||||
|  |                 email: "xxxxxx@qq.com" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }) | ||||||
| @ -1,7 +0,0 @@ | |||||||
| @forward 'element-plus/theme-chalk/src/common/var.scss' with ( |  | ||||||
|   $colors: ( |  | ||||||
|     'primary': ( |  | ||||||
|       'base': green, |  | ||||||
|     ), |  | ||||||
|   ), |  | ||||||
| ); |  | ||||||
							
								
								
									
										18
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tsconfig.json
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "composite": true, | ||||||
|  |     "module": "esnext", | ||||||
|  |     "moduleResolution": "node" | ||||||
|  |   }, | ||||||
|  |   "include": ["vite.config.ts"] | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vite.config.ts
									
									
									
									
									
										Normal 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()], | ||||||
|  |     }), | ||||||
|  |   ] | ||||||
|  | }) | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user