Browse Source

提交

master
chenkainan 3 months ago
parent
commit
368ec992d8
  1. 191
      package-lock.json
  2. 7
      package.json
  3. 95
      src/App.vue
  4. 49
      src/assets/css/common.css
  5. 72
      src/components/common/BackToTop.vue
  6. 149
      src/components/layout/Footer.vue
  7. 372
      src/components/layout/HeaderNav.vue
  8. 227
      src/components/layout/HomeLayout.vue
  9. 191
      src/components/product/ProductList.vue
  10. 58
      src/main.js
  11. 158
      src/router/index.js
  12. 189
      src/store/index.js
  13. 5
      src/views/AboutView.vue
  14. 384
      src/views/Home.vue
  15. 18
      src/views/HomeView.vue
  16. 88
      src/views/Index.vue
  17. 0
      src/views/User/OrderList.vue
  18. 0
      src/views/User/UserCenter.vue
  19. 3
      vue.config.js

191
package-lock.json

@ -2346,6 +2346,19 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"dev": true
},
"async-validator": {
"version": "1.8.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz",
"integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
"requires": {
"babel-runtime": "6.x"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
@ -2366,6 +2379,21 @@
"postcss-value-parser": "^4.2.0"
}
},
"axios": {
"version": "1.11.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"babel-helper-vue-jsx-merge-props": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
"integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
},
"babel-loader": {
"version": "8.4.1",
"resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.4.1.tgz",
@ -2417,6 +2445,22 @@
"@babel/helper-define-polyfill-provider": "^0.6.5"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
}
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2586,7 +2630,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@ -2795,6 +2838,14 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
@ -3195,8 +3246,7 @@
"deepmerge": {
"version": "1.5.2",
"resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz",
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
"dev": true
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ=="
},
"default-gateway": {
"version": "6.0.3",
@ -3325,6 +3375,11 @@
"object-keys": "^1.1.1"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@ -3448,7 +3503,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@ -3479,6 +3533,19 @@
"integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==",
"dev": true
},
"element-ui": {
"version": "2.15.14",
"resolved": "https://registry.npmmirror.com/element-ui/-/element-ui-2.15.14.tgz",
"integrity": "sha512-2v9fHL0ZGINotOlRIAJD5YuVB8V7WKxrE9Qy7dXhRipa035+kF7WuU/z+tEmLVPBcJ0zt8mOu1DKpWcVzBK8IA==",
"requires": {
"async-validator": "~1.8.1",
"babel-helper-vue-jsx-merge-props": "^2.0.0",
"deepmerge": "^1.2.0",
"normalize-wheel": "^1.0.1",
"resize-observer-polyfill": "^1.5.0",
"throttle-debounce": "^1.0.1"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -3543,14 +3610,12 @@
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"es-module-lexer": {
"version": "1.7.0",
@ -3562,11 +3627,21 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
@ -3865,8 +3940,19 @@
"follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
},
"form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
"forwarded": {
"version": "0.2.0",
@ -3920,8 +4006,7 @@
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"gensync": {
"version": "1.0.0-beta.2",
@ -3939,7 +4024,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@ -3957,7 +4041,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@ -4018,8 +4101,7 @@
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"graceful-fs": {
"version": "4.2.11",
@ -4060,8 +4142,15 @@
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": {
"has-symbols": "^1.0.3"
}
},
"hash-sum": {
"version": "2.0.0",
@ -4073,7 +4162,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
@ -4501,6 +4589,11 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="
},
"js-message": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz",
@ -4801,8 +4894,7 @@
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mdn-data": {
"version": "2.0.14",
@ -4877,14 +4969,12 @@
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"requires": {
"mime-db": "1.52.0"
}
@ -5122,6 +5212,11 @@
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -5874,6 +5969,11 @@
}
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz",
@ -6004,6 +6104,11 @@
"regenerate": "^1.4.2"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"regexpu-core": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.2.0.tgz",
@ -6078,6 +6183,11 @@
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
@ -6404,6 +6514,11 @@
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"dev": true
},
"shvl": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/shvl/-/shvl-2.0.3.tgz",
"integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw=="
},
"side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
@ -6803,6 +6918,11 @@
}
}
},
"throttle-debounce": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
"integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg=="
},
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",
@ -6991,6 +7111,11 @@
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
"dev": true
},
"vue-lazyload": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-1.3.3.tgz",
"integrity": "sha512-uHnq0FTEeNmqnbBC2aRKlmtd9LofMZ6Q3mWvgfLa+i9vhxU8fDK+nGs9c1iVT85axSua/AUnMttIq3xPaU9G3A=="
},
"vue-loader": {
"version": "17.4.2",
"resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-17.4.2.tgz",
@ -7066,6 +7191,22 @@
"resolved": "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz",
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
},
"vuex-persistedstate": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz",
"integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==",
"requires": {
"deepmerge": "^4.2.2",
"shvl": "^2.0.3"
},
"dependencies": {
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
}
}
},
"watchpack": {
"version": "2.4.4",
"resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.4.tgz",

7
package.json

@ -7,10 +7,15 @@
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^1.11.0",
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"js-cookie": "^3.0.5",
"vue": "^2.6.14",
"vue-lazyload": "^1.3.3",
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
"vuex": "^3.6.2",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",

95
src/App.vue

@ -1,32 +1,99 @@
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<!-- 加载中状态 -->
<!-- <el-loading
v-if="loading"
fullscreen
text="加载中..."
background="rgba(255, 255, 255, 0.7)"
></el-loading> -->
<!-- 顶部导航 -->
<HeaderNav v-if="$route.name !== 'Login' && $route.name !== 'Register'" />
<!-- 主内容区 -->
<main class="main-container">
<router-view />
</main>
<!-- 页脚 -->
<Footer v-if="$route.name !== 'Login' && $route.name !== 'Register'" />
<!-- 回到顶部按钮 -->
<BackToTop />
</div>
</template>
<script>
import HeaderNav from "./components/layout/HeaderNav";
import Footer from './components/layout/Footer'
import BackToTop from "./components/common/BackToTop";
import { mapGetters } from "vuex";
export default {
name: "App",
components: {
HeaderNav,
Footer,
BackToTop,
},
computed: {
...mapGetters(["getLoadingStatus"]),
loading() {
return this.getLoadingStatus;
},
},
};
</script>
<style lang="scss">
//
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
color: #333;
min-height: 100vh;
display: flex;
flex-direction: column;
}
nav {
padding: 30px;
.main-container {
flex: 1;
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
a {
font-weight: bold;
color: #2c3e50;
//
@media (max-width: 1200px) {
padding: 15px;
}
&.router-link-exact-active {
color: #42b983;
@media (max-width: 768px) {
padding: 10px;
}
}
//
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style: none;
}
img {
vertical-align: middle;
}
</style>

49
src/assets/css/common.css

@ -0,0 +1,49 @@
body, html {
padding: 0;
margin: 0;
}
div {
box-sizing: border-box;
}
/*单行隐藏*/
.text-overflow {
overflow-x: hidden;
overflow-y: inherit;
text-overflow: ellipsis;
white-space: nowrap;
}
/*两行隐藏,其他行设置-webkit-line-clamp:n */
.text-overflowRows {
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-around {
display: flex;
justify-content: space-around;
align-items: center;
}
.flex-column {
display: flex;
flex-direction: column;
}

72
src/components/common/BackToTop.vue

@ -0,0 +1,72 @@
<template>
<div
class="back-to-top"
v-if="showBackToTop"
@click="scrollToTop"
>
<el-icon name="el-icon-arrow-up"></el-icon>
</div>
</template>
<script>
export default {
name: 'BackToTop',
data() {
return {
showBackToTop: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll() {
// 500px
this.showBackToTop = window.pageYOffset > 500
},
scrollToTop() {
//
const scrollToTop = window.setInterval(() => {
const position = window.pageYOffset
if (position > 0) {
window.scrollTo(0, position - Math.max(20, position / 10))
} else {
window.clearInterval(scrollToTop)
}
}, 16)
}
}
}
</script>
<style lang="scss" scoped>
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 40px;
height: 40px;
background-color: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
z-index: 1000;
&:hover {
background-color: #337ab7;
transform: translateY(-3px);
}
.el-icon-arrow-up {
font-size: 20px;
}
}
</style>

149
src/components/layout/Footer.vue

@ -0,0 +1,149 @@
<template>
<div class="footer-container">
<div class="footer-top">
<div class="footer-column">
<h3>关于我们</h3>
<ul>
<li>平台简介</li>
<li>政策文件</li>
<li>平台标识指南</li>
<li>营业执照</li>
<li>食品经营许可证</li>
</ul>
</div>
<div class="footer-column">
<h3>新手上路</h3>
<ul>
<li>供应商入驻流程</li>
<li>供应商操作手册</li>
<li>采购人入驻流程</li>
<li>采购人操作手册</li>
<li>供应商入驻联系方式</li>
</ul>
</div>
<div class="footer-column">
<h3>交易流程</h3>
<ul>
<li>直购交易流程</li>
<li>竞购交易流程</li>
<li>货款结算流程</li>
</ul>
</div>
<div class="footer-column">
<h3>常见问题</h3>
<ul>
<li>预留份额填报</li>
<li>支付结算问题</li>
<li>账号管理问题</li>
<li>在线客服问题</li>
</ul>
</div>
<div class="contact-info">
<p>联系方式</p>
<p>客服电话222-222-222</p>
<p>工作时间工作日 9:00-18:00</p>
<p>客服邮箱xxxxxxxxx@stn.com</p>
<p>商务合作18999999999</p>
</div>
<div class="qrcode-group">
<div class="qrcode-item">
<!-- 这里用 Element UI 布局占位实际替换成真实二维码图片 -->
<el-empty
description="二维码"
style="width: 100px; height: 100px"
></el-empty>
<p>时味苏州小程序</p>
</div>
<div class="qrcode-item">
<el-empty
description="二维码"
style="width: 100px; height: 100px"
></el-empty>
<p>时味苏州服务号</p>
</div>
</div>
</div>
<div class="footer-bottom">
<p>版权所有 苏州市特色农产品发展有限公司 | 苏ICP备2023023300号-1</p>
<p>本网站由 江苏大运远见文化科技发展有限公司 运营维护</p>
</div>
</div>
</template>
<script>
export default {
name: "Footer",
data() {
return {
//
};
},
};
</script>
<style scoped>
.footer-container {
background-color: #fff;
color: #666;
font-size: 12px;
border-top: 1px solid #eaeaea;
}
.footer-top {
display: flex;
justify-content: space-around;
padding: 20px 0;
flex-wrap: wrap;
}
.footer-link {
display: flex;
gap: 20px;
align-items: center;
}
.footer-link span {
cursor: pointer;
transition: color 0.3s ease;
}
.footer-link span:hover {
color: #1890ff;
}
.contact-info p {
margin: 5px 0;
}
.footer-column {
margin-bottom: 20px;
}
.footer-column h3 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.footer-column ul {
list-style: none;
padding: 0;
margin: 0;
}
.footer-column ul li {
margin: 5px 0;
cursor: pointer;
transition: color 0.3s ease;
}
.footer-column ul li:hover {
color: #1890ff;
}
.qrcode-group {
display: flex;
gap: 40px;
}
.qrcode-item {
text-align: center;
}
.footer-bottom {
text-align: center;
padding: 10px 0;
border-top: 1px solid #eaeaea;
}
.footer-bottom p {
margin: 5px 0;
}
</style>

372
src/components/layout/HeaderNav.vue

@ -0,0 +1,372 @@
<template>
<header class="header-nav">
<!-- 顶部通知栏 -->
<div class="top-notice">
<div class="container">
<p>
欢迎来到企业采购平台
<a href="/register" class="highlight">立即注册</a>
</p>
<div class="top-links">
<a href="/user" v-if="isLogin">
<img v-lazy="userInfo.avatar" alt="用户头像" class="avatar" />
{{ userInfo.username }}
</a>
<a href="/login" v-else>登录</a>
<span class="separator" v-if="isLogin">|</span>
<a href="/register" v-if="isLogin">注册</a>
<a href="/user">采购人中心</a>
<a href="/userCenter" v-if="isLogin">我的订单</a>
<a href="">商户后台</a>
<a href="javascript:;" @click="handleLogout" v-if="isLogin">退出</a>
</div>
</div>
</div>
<!-- 主导航栏 -->
<div class="main-nav">
<div class="container">
<div class="logo">
<a href="/">
<h1>精品商城</h1>
</a>
</div>
<!-- 搜索框 -->
<div class="search-box">
<el-input
placeholder="请输入搜索内容"
v-model="searchText"
class="search-input"
@keyup.enter.native="handleSearch"
>
<el-button
slot="append"
icon="el-icon-search"
@click="handleSearch"
></el-button>
</el-input>
<div class="hot-tags">
<span>热门搜索</span>
<a
href="javascript:;"
@click="
searchText = '手机';
handleSearch();
"
>手机</a
>
<a
href="javascript:;"
@click="
searchText = '电脑';
handleSearch();
"
>电脑</a
>
<a
href="javascript:;"
@click="
searchText = '服装';
handleSearch();
"
>服装</a
>
</div>
</div>
<!-- 购物车入口 -->
<div class="cart-entry">
<a href="/cart" class="cart-link">
<i class="el-icon-shopping-cart-full cart-icon"></i>
<span>购物车</span>
<span class="cart-count" v-if="cartTotalCount > 0">{{
cartTotalCount
}}</span>
</a>
</div>
</div>
</div>
<!-- 分类导航 -->
<div class="category-nav" v-if="false">
<div class="container">
<ul class="nav-list">
<li class="nav-item">
<a
href="/"
class="nav-link"
:class="{ active: $route.path === '/' }"
>首页</a
>
</li>
<li
v-for="category in categories"
:key="category.id"
class="nav-item"
>
<a
href="/category/:id"
:to="`/category/${category.id}`"
class="nav-link"
:class="{ active: $route.params.id == category.id }"
>
{{ category.name }}
</a>
</li>
</ul>
</div>
</div>
</header>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
name: "HeaderNav",
data() {
return {
searchText: "",
};
},
computed: {
...mapGetters([
"getCategories",
"getCartTotalCount",
"isUserLogin",
"getUserInfo",
]),
categories() {
return this.getCategories;
},
cartTotalCount() {
return this.getCartTotalCount;
},
isLogin() {
// return this.isUserLogin;
return true;
},
userInfo() {
return this.getUserInfo || {};
},
},
created() {
this.fetchCategories();
},
methods: {
...mapActions(["fetchCategories", "logout"]),
handleSearch() {
if (this.searchText.trim()) {
//
this.$message.success(`搜索: ${this.searchText}`);
} else {
this.$message.warning("请输入搜索内容");
}
},
handleLogout() {
this.$confirm("确定要退出登录吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.logout();
this.$message.success("退出登录成功");
this.$router.push("/");
})
.catch(() => {
// 退
});
},
},
};
</script>
<style lang="scss" scoped>
.header-nav {
width: 100%;
.top-notice {
background-color: #f5f5f5;
padding: 8px 0;
.container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
p {
font-size: 14px;
color: #666;
.highlight {
color: #ff4400;
margin: 0 5px;
font-weight: 500;
}
}
.top-links {
display: flex;
align-items: center;
gap: 15px;
font-size: 14px;
.avatar {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 5px;
vertical-align: middle;
}
.separator {
color: #ccc;
}
a {
color: #666;
transition: color 0.2s;
&:hover {
color: #409eff;
}
}
}
}
}
.main-nav {
background-color: #fff;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
.container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.logo {
a {
display: block;
h1 {
font-size: 28px;
color: #409eff;
margin: 0;
font-weight: 700;
}
}
}
.search-box {
flex: 0 0 500px;
@media (max-width: 992px) {
flex: 0 0 350px;
}
@media (max-width: 768px) {
display: none;
}
.search-input {
width: 100%;
}
.hot-tags {
margin-top: 8px;
font-size: 12px;
color: #999;
span {
margin-right: 5px;
}
a {
margin: 0 5px;
color: #666;
&:hover {
color: #409eff;
text-decoration: underline;
}
}
}
}
.cart-entry {
.cart-link {
display: flex;
align-items: center;
color: #333;
font-size: 16px;
.cart-icon {
font-size: 20px;
margin-right: 5px;
}
.cart-count {
display: inline-block;
width: 18px;
height: 18px;
background-color: #ff4400;
color: white;
border-radius: 50%;
font-size: 12px;
text-align: center;
line-height: 18px;
margin-left: 5px;
}
}
}
}
.category-nav {
background-color: #409eff;
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.nav-list {
display: flex;
margin: 0;
padding: 0;
@media (max-width: 992px) {
overflow-x: auto;
white-space: nowrap;
}
.nav-item {
list-style: none;
.nav-link {
display: inline-block;
padding: 12px 20px;
color: #fff;
font-size: 16px;
transition: background-color 0.2s;
&:hover,
&.active {
background-color: #337ab7;
}
}
}
}
}
}
</style>

227
src/components/layout/HomeLayout.vue

@ -0,0 +1,227 @@
<template>
<div class="home-layout-container">
<!-- 左侧导航栏 -->
<div class="left-nav">
<ul class="nav-list">
<li class="nav-item"><i class="el-icon-sell"></i>全部商品</li>
<li class="nav-item"><i class="el-icon-burger"></i>米面油</li>
<li class="nav-item"><i class="el-icon-fork-spoon"></i>调味干货</li>
<li class="nav-item"><i class="el-icon-potato-strips"></i>休闲食品</li>
<li class="nav-item"><i class="el-icon-dish"></i>禽畜肉蛋</li>
<li class="nav-item"><i class="el-icon-grape"></i>鲜果蔬菜</li>
<li class="nav-item"><i class="el-icon-present"></i>组合集市</li>
</ul>
</div>
<!-- 轮播图 -->
<div class="main-content">
<el-carousel style="height: 100%">
<el-carousel-item v-for="item in 4" :key="item.id">
<img
src="
https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png
"
class="carousel-img"
/>
</el-carousel-item>
</el-carousel>
</div>
<!-- 右侧用户信息栏 -->
<div class="right-info">
<div class="avatar-container">
<img
src="https://picsum.photos/id/64/120/120"
alt="用户头像"
class="user-avatar"
/>
<div class="welcome-text">Hi-欢迎您</div>
</div>
<div class="btn-group">
<el-button type="danger" size="mini">登录</el-button>
<el-button type="warning" size="mini">注册</el-button>
<el-button type="primary" size="mini">客服</el-button>
</div>
<div class="func-icons">
<div class="icon-item">
<i class="icon el-icon-user"></i>
<span>个人中心</span>
</div>
<div class="icon-item">
<i class="icon el-icon-goods"></i>
<span>我的订单</span>
</div>
<div class="icon-item">
<i class="icon el-icon-star-off"></i>
<span>我的收藏</span>
</div>
<div class="icon-item">
<i class="icon el-icon-pie-chart"></i>
<span>议价单</span>
</div>
</div>
<div class="announcement">
<el-tag type="danger" size="mini">公告</el-tag>
<span>2099年12月平台重要新规速递</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "HomeLayout",
data() {
return {
//
carouselItems: [
{
id: 1,
imageUrl: "https://picsum.photos/id/26/1200/500",
altText: "新鲜水果促销",
},
{
id: 2,
imageUrl: "https://picsum.photos/id/292/1200/500",
altText: "有机蔬菜专场",
},
{
id: 3,
imageUrl: "https://picsum.photos/id/431/1200/500",
altText: "粮油特惠活动",
},
],
};
},
};
</script>
<style scoped lang="scss">
.home-layout-container {
display: flex;
width: 100%;
height: auto;
}
/* 左侧导航栏样式 */
.left-nav {
width: 200px;
background-color: #f8f9fa;
padding: 20px 0;
box-sizing: border-box;
}
.nav-list {
list-style: none;
margin: 0;
padding: 0;
}
.nav-item {
padding: 12px 20px;
cursor: pointer;
color: #333;
transition: all 0.3s ease;
i {
color: #f63131;
margin-right: 5px;
}
}
.nav-item:hover {
background-color: #e9ecef;
padding-left: 25px;
}
/* 中间主内容样式 */
.main-content {
flex: 1;
height: auto;
overflow: hidden;
}
.carousel-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.main-content ::v-deep .el-carousel__container {
height: 100% !important;
}
/* 右侧用户信息栏样式 */
.right-info {
width: 280px;
background-color: #fff;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
border-left: 1px solid #eee;
}
.avatar-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-bottom: 10px;
border: 2px solid #f0f0f0;
}
.welcome-text {
font-size: 14px;
color: #333;
}
.btn-group {
display: flex;
gap: 10px;
margin-bottom: 30px;
width: 100%;
}
.func-icons {
display: flex;
justify-content: space-around;
width: 100%;
margin-bottom: 30px;
padding: 10px 0;
border-top: 1px dashed #eee;
border-bottom: 1px dashed #eee;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
color: #666;
transition: color 0.3s ease;
width: 50px;
}
.icon-item:hover {
color: #1890ff;
}
.icon {
font-size: 24px;
margin-bottom: 5px;
}
.icon-item span {
font-size: 12px;
}
.announcement {
display: flex;
align-items: center;
font-size: 12px;
color: #666;
width: 100%;
padding-top: 10px;
span {
margin-left: 10px;
}
}
</style>

191
src/components/product/ProductList.vue

@ -0,0 +1,191 @@
<template>
<div class="product-list">
<div class="product-grid">
<div v-for="product in products" :key="product.id" class="product-card">
<div class="product-img">
<a :href="`/product/${product.id}`" :to="`/product/${product.id}`">
<img
v-lazy="product.image"
:alt="product.name"
class="product-pic"
/>
</a>
</div>
<div class="product-info">
<div class="flex-between">
<div class="product-price">
<span class="current-price">¥{{ product.price.toFixed(2) }}</span>
<span class="original-price" v-if="product.originalPrice"
>¥{{ product.originalPrice.toFixed(2) }}</span
>
</div>
<div class="product-sales" v-if="product.sales">
<span>已售 {{ product.sales }} </span>
</div>
</div>
<h3 class="product-name">
<a :href="`/product/${product.id}`" :to="`/product/${product.id}`">
{{ product.name }}
</a>
</h3>
<div class="product-actions">
<el-button
type="primary"
size="small"
style="background-color: #ff6e90; border: none"
@click="addToCart(product)"
>
<el-icon name="el-icon-shopping-cart"></el-icon>
</el-button>
</div>
</div>
</div>
</div>
<!-- 无商品时显示 -->
<div class="no-products" v-if="products.length === 0">
<el-empty description="暂无相关商品"></el-empty>
</div>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
name: "ProductList",
props: {
products: {
type: Array,
default: () => [],
},
},
methods: {
...mapActions(["addToCart"]),
addToCart(product) {
//
if (!this.$store.getters.isUserLogin) {
this.$confirm("您尚未登录,是否前往登录?", "提示", {
confirmButtonText: "登录",
cancelButtonText: "取消",
type: "info",
})
.then(() => {
this.$router.push({
path: "/login",
query: { redirect: this.$route.fullPath },
});
})
.catch(() => {
//
});
return;
}
this.addToCart({
id: product.id,
name: product.name,
price: product.price,
image: product.image,
quantity: 1,
});
this.$message.success("已加入购物车");
},
},
};
</script>
<style lang="scss" scoped>
.product-list {
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.product-card {
border: 1px solid #eaeaea;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
padding: 10px;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.product-img {
height: 250px;
overflow: hidden;
.product-pic {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
&:hover {
transform: scale(1.05);
}
}
}
.product-info {
padding: 10px;
.product-name {
font-size: 14px;
line-height: 20px;
min-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 10px;
a {
color: #333;
&:hover {
color: #409eff;
}
}
}
.product-price {
margin-bottom: 15px;
.current-price {
color: #ff4400;
font-size: 16px;
font-weight: 700;
}
.original-price {
color: #999;
font-size: 12px;
text-decoration: line-through;
margin-left: 8px;
}
}
.product-actions {
margin-bottom: 10px;
}
.product-sales {
font-size: 12px;
color: #999;
margin-bottom: 15px;
}
}
}
.no-products {
padding: 50px 0;
text-align: center;
}
}
</style>

58
src/main.js

@ -1,12 +1,64 @@
import Vue from 'vue'
import App from './App.vue'
import App from './App'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
import VueLazyload from 'vue-lazyload'
import '@/assets/css/common.css'
// 全局配置
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.prototype.$http = axios
// 配置图片懒加载
Vue.use(VueLazyload, {
preLoad: 1.3, // 预加载高度比例
error: require('./assets/logo.png'),
loading: require('./assets/logo.png'),
attempt: 3, // 增加尝试次数
listenEvents: ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'], // 确保监听事件完整
adapter: {
// 增加加载状态日志
loaded({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error, Init }) {
console.log('图片加载完成:', src)
},
error({ bindType, el, error, $parent, src, loading }) {
console.log('图片加载失败:', src)
}
}
})
// 请求拦截器设置
axios.interceptors.request.use(
config => {
// 可以在这里添加token等信息
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器设置
axios.interceptors.response.use(
response => {
return response.data
},
error => {
// 统一错误处理
ElementUI.Message.error('请求失败,请稍后重试')
return Promise.reject(error)
}
)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
render: h => h(App)
}).$mount('#app')
components: { App },
template: '<App/>'
})

158
src/router/index.js

@ -1,29 +1,153 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Router from 'vue-router'
Vue.use(VueRouter)
Vue.use(Router)
const routes = [
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: HomeView
name: 'Index',
meta: {
title: '首页 - 精品商城',
keepAlive: false
},
component: () => import('@/views/Index.vue')
},
{
path: '/Home',
name: 'Home',
meta: {
title: '首页 - 精品商城',
keepAlive: false
},
component: () => import('@/views/Home.vue')
},
// {
// path: '/category/:id?',
// name: 'Category',
// meta: {
// title: '商品分类 - 精品商城',
// keepAlive: false
// },
// component: () => import('@/views/Category.vue')
// },
// {
// path: '/product/:id',
// name: 'ProductDetail',
// meta: {
// title: '商品详情 - 精品商城',
// keepAlive: false
// },
// component: () => import('@/views/ProductDetail.vue')
// },
// {
// path: '/cart',
// name: 'Cart',
// meta: {
// title: '购物车 - 精品商城',
// requireAuth: true,
// keepAlive: false
// },
// component: () => import('@/views/Cart.vue')
// },
// {
// path: '/checkout',
// name: 'Checkout',
// meta: {
// title: '结算 - 精品商城',
// requireAuth: true,
// keepAlive: false
// },
// component: () => import('@/views/Checkout.vue')
// },
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
path: '/userCenter',
name: 'UserCenter',
meta: {
title: '个人中心 - 精品商城',
requireAuth: true,
keepAlive: false
},
component: () => import('@/views/User/UserCenter.vue')
},
{
path: 'orderList',
name: 'OrderList',
meta: {
title: '我的订单 - 精品商城',
requireAuth: true,
keepAlive: false
},
component: () => import('@/views/User/OrderList.vue')
},
// {
// path: '/user/orders/:id',
// name: 'OrderDetail',
// meta: {
// title: '订单详情 - 精品商城',
// requireAuth: true,
// keepAlive: false
// },
// component: () => import('@/views/OrderDetail.vue')
// },
// {
// path: '/login',
// name: 'Login',
// meta: {
// title: '登录 - 精品商城',
// keepAlive: false
// },
// component: () => import('@/views/Login.vue')
// },
// {
// path: '/register',
// name: 'Register',
// meta: {
// title: '注册 - 精品商城',
// keepAlive: false
// },
// component: () => import('@/views/Register.vue')
// },
// {
// path: '*',
// name: 'NotFound',
// meta: {
// title: '页面不存在 - 精品商城',
// keepAlive: false
// },
// component: () => import('@/views/NotFound.vue')
// }
],
scrollBehavior(to, from, savedPosition) {
// 页面滚动到顶部
return { x: 0, y: 0 }
}
]
})
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
// 路由守卫
router.beforeEach((to, from, next) => {
// 设置页面标题
if (to.meta.title) {
document.title = to.meta.title
}
// 验证登录状态
if (to.meta.requireAuth) {
const token = localStorage.getItem('token')
if (token) {
next()
} else {
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
} else {
next()
}
})
export default router

189
src/store/index.js

@ -1,17 +1,202 @@
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
plugins: [
createPersistedState({
storage: window.localStorage,
reducer(val) {
return {
// 只持久化需要的状态
cart: val.cart,
user: val.user
}
}
})
],
state: {
// 购物车状态
cart: {
items: [], // 购物车商品列表
totalCount: 0, // 商品总数
totalPrice: 0 // 商品总价
},
getters: {
// 用户状态
user: {
isLogin: false,
info: null,
token: ''
},
// 分类数据
categories: [],
// 全局加载状态
loading: false
},
mutations: {
// 更新分类数据
UPDATE_CATEGORIES(state, categories) {
state.categories = categories
},
// 更新加载状态
UPDATE_LOADING(state, status) {
state.loading = status
},
// 用户登录
USER_LOGIN(state, { userInfo, token }) {
state.user.isLogin = true
state.user.info = userInfo
state.user.token = token
localStorage.setItem('token', token)
},
// 用户登出
USER_LOGOUT(state) {
state.user.isLogin = false
state.user.info = null
state.user.token = ''
localStorage.removeItem('token')
},
// 添加商品到购物车
ADD_TO_CART(state, product) {
const existingItem = state.cart.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += product.quantity || 1
} else {
state.cart.items.push({
...product,
quantity: product.quantity || 1
})
}
this.commit('UPDATE_CART_TOTAL')
},
// 从购物车移除商品
REMOVE_FROM_CART(state, productId) {
state.cart.items = state.cart.items.filter(item => item.id !== productId)
this.commit('UPDATE_CART_TOTAL')
},
// 更新购物车商品数量
UPDATE_CART_ITEM_QUANTITY(state, { productId, quantity }) {
const item = state.cart.items.find(item => item.id === productId)
if (item) {
item.quantity = quantity
this.commit('UPDATE_CART_TOTAL')
}
},
// 清空购物车
CLEAR_CART(state) {
state.cart.items = []
this.commit('UPDATE_CART_TOTAL')
},
// 更新购物车总计
UPDATE_CART_TOTAL(state) {
state.cart.totalCount = state.cart.items.reduce((total, item) => {
return total + item.quantity
}, 0)
state.cart.totalPrice = state.cart.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
}
},
actions: {
// 获取分类数据
fetchCategories({ commit }) {
commit('UPDATE_LOADING', true)
// 模拟API请求
return new Promise(resolve => {
setTimeout(() => {
const categories = [
{ id: 1, name: '电子产品', icon: 'el-icon-laptop' },
{ id: 2, name: '服装鞋帽', icon: 'el-icon-shopping-bag-1' },
{ id: 3, name: '家居用品', icon: 'el-icon-home' },
{ id: 4, name: '美妆个护', icon: 'el-icon-present' },
{ id: 5, name: '食品饮料', icon: 'el-icon-dish' },
{ id: 6, name: '图书音像', icon: 'el-icon-document' }
]
commit('UPDATE_CATEGORIES', categories)
commit('UPDATE_LOADING', false)
resolve(categories)
}, 500)
})
},
// 用户登录
login({ commit }, { username, password }) {
commit('UPDATE_LOADING', true)
// 模拟登录API请求
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === 'test' && password === '123456') {
const userInfo = { id: 1, username: 'test', avatar: 'https://picsum.photos/200' }
const token = 'fake-token-123456'
commit('USER_LOGIN', { userInfo, token })
commit('UPDATE_LOADING', false)
resolve(userInfo)
} else {
commit('UPDATE_LOADING', false)
reject(new Error('用户名或密码错误'))
}
}, 1000)
})
},
// 用户登出
logout({ commit }) {
commit('USER_LOGOUT')
},
// 添加商品到购物车
addToCart({ commit }, product) {
commit('ADD_TO_CART', product)
},
// 从购物车移除商品
removeFromCart({ commit }, productId) {
commit('REMOVE_FROM_CART', productId)
},
modules: {
// 更新购物车商品数量
updateCartItemQuantity({ commit }, payload) {
commit('UPDATE_CART_ITEM_QUANTITY', payload)
},
// 清空购物车
clearCart({ commit }) {
commit('CLEAR_CART')
}
},
getters: {
// 获取分类列表
getCategories: state => state.categories,
// 获取购物车信息
getCart: state => state.cart,
// 获取购物车商品总数
getCartTotalCount: state => state.cart.totalCount,
// 获取购物车商品总价
getCartTotalPrice: state => state.cart.totalPrice,
// 获取用户登录状态
isUserLogin: state => state.user.isLogin,
// 获取用户信息
getUserInfo: state => state.user.info,
// 获取加载状态
getLoadingStatus: state => state.loading
}
})

5
src/views/AboutView.vue

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

384
src/views/Home.vue

@ -0,0 +1,384 @@
<template>
<div class="home-page">
<!-- 轮播图 -->
<el-carousel
height="500px"
indicator-position="outside"
class="home-carousel"
>
<el-carousel-item v-for="item in 4" :key="item">
<img
v-lazy="`https://picsum.photos/1200/500?random=${item}`"
alt="轮播图片"
class="carousel-img"
>
</el-carousel-item>
</el-carousel>
<!-- 分类导航 -->
<div class="category-nav">
<h2 class="section-title">商品分类</h2>
<div class="category-list">
<div
v-for="category in categories"
:key="category.id"
class="category-item"
@click="$router.push(`/category/${category.id}`)"
>
<el-icon :name="category.icon" class="category-icon"></el-icon>
<span class="category-name">{{ category.name }}</span>
</div>
</div>
</div>
<!-- 热门商品 -->
<div class="hot-products">
<div class="section-header">
<h2 class="section-title">热门商品</h2>
<a href="#" class="more-link">查看更多 <i class="el-icon-arrow-right"></i></a>
</div>
<ProductList :products="hotProducts" />
</div>
<!-- 新品上市 -->
<div class="new-products">
<div class="section-header">
<h2 class="section-title">新品上市</h2>
<a href="#" class="more-link">查看更多 <i class="el-icon-arrow-right"></i></a>
</div>
<ProductList :products="newProducts" />
</div>
<!-- 促销活动 -->
<div class="promotion-section">
<h2 class="section-title">限时促销</h2>
<div class="promotion-container">
<div class="promotion-item">
<img
v-lazy="`https://picsum.photos/600/300?random=10`"
alt="促销活动图片"
class="promotion-img"
>
<div class="promotion-info">
<h3>夏季大促</h3>
<p>全场商品低至5折</p>
<el-button type="primary" size="medium">立即抢购</el-button>
</div>
</div>
<div class="promotion-item">
<img
v-lazy="`https://picsum.photos/600/300?random=11`"
alt="促销活动图片"
class="promotion-img"
>
<div class="promotion-info">
<h3>新品首发</h3>
<p>限量发售先到先得</p>
<el-button type="primary" size="medium">立即抢购</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import ProductList from '../components/product/ProductList'
export default {
name: 'Home',
components: {
ProductList
},
data() {
return {
//
hotProducts: [
{
id: 1,
name: '超薄笔记本电脑',
price: 5999,
originalPrice: 6999,
image: 'https://picsum.photos/300/300?random=1',
sales: 1254,
categoryId: 1
},
{
id: 2,
name: '智能手表',
price: 1599,
originalPrice: 1799,
image: 'https://picsum.photos/300/300?random=2',
sales: 856,
categoryId: 1
},
{
id: 3,
name: '纯棉T恤',
price: 99,
originalPrice: 199,
image: 'https://picsum.photos/300/300?random=3',
sales: 2356,
categoryId: 2
},
{
id: 4,
name: '休闲牛仔裤',
price: 199,
originalPrice: 399,
image: 'https://picsum.photos/300/300?random=4',
sales: 1890,
categoryId: 2
},
{
id: 5,
name: '舒适沙发',
price: 2999,
originalPrice: 3999,
image: 'https://picsum.photos/300/300?random=5',
sales: 324,
categoryId: 3
},
{
id: 6,
name: '智能扫地机器人',
price: 1899,
originalPrice: 2299,
image: 'https://picsum.photos/300/300?random=6',
sales: 754,
categoryId: 3
}
],
//
newProducts: [
{
id: 7,
name: '高清投影仪',
price: 3299,
originalPrice: 3699,
image: 'https://picsum.photos/300/300?random=7',
sales: 156,
categoryId: 1
},
{
id: 8,
name: '保湿面霜',
price: 299,
originalPrice: 359,
image: 'https://picsum.photos/300/300?random=8',
sales: 423,
categoryId: 4
},
{
id: 9,
name: '有机水果礼盒',
price: 159,
originalPrice: 199,
image: 'https://picsum.photos/300/300?random=9',
sales: 287,
categoryId: 5
},
{
id: 10,
name: '经典文学名著',
price: 129,
originalPrice: 199,
image: 'https://picsum.photos/300/300?random=10',
sales: 342,
categoryId: 6
},
{
id: 11,
name: '无线蓝牙耳机',
price: 799,
originalPrice: 999,
image: 'https://picsum.photos/300/300?random=11',
sales: 567,
categoryId: 1
},
{
id: 12,
name: '运动鞋',
price: 499,
originalPrice: 699,
image: 'https://picsum.photos/300/300?random=12',
sales: 432,
categoryId: 2
}
]
}
},
computed: {
...mapGetters([
'getCategories'
]),
categories() {
return this.getCategories
}
},
created() {
this.fetchCategories()
},
methods: {
...mapActions([
'fetchCategories'
])
}
}
</script>
<style lang="scss" scoped>
.home-page {
.home-carousel {
margin-bottom: 30px;
.carousel-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.category-nav {
margin-bottom: 40px;
.category-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
.category-item {
flex: 1;
min-width: 120px;
height: 150px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #eaeaea;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
border-color: #409eff;
}
.category-icon {
font-size: 36px;
color: #409eff;
margin-bottom: 15px;
}
.category-name {
font-size: 16px;
font-weight: 500;
}
}
}
.section-title {
font-size: 24px;
color: #333;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
display: inline-block;
margin-bottom: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.more-link {
color: #409eff;
font-size: 14px;
display: flex;
align-items: center;
&:hover {
text-decoration: underline;
}
i {
margin-left: 5px;
font-size: 16px;
}
}
}
.hot-products, .new-products {
margin-bottom: 40px;
}
.promotion-section {
margin: 40px 0;
.promotion-container {
display: flex;
gap: 20px;
margin-top: 20px;
@media (max-width: 768px) {
flex-direction: column;
}
}
.promotion-item {
flex: 1;
position: relative;
height: 300px;
border-radius: 8px;
overflow: hidden;
.promotion-img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
&:hover {
transform: scale(1.05);
}
}
.promotion-info {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
h3 {
font-size: 24px;
margin-bottom: 10px;
}
p {
font-size: 16px;
margin-bottom: 20px;
}
}
}
}
}
</style>
</script>

18
src/views/HomeView.vue

@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>

88
src/views/Index.vue

@ -0,0 +1,88 @@
<template>
<div class="bg">
<HomeLayout />
<div class="product-box">
<h2>今日推荐</h2>
<ProductList :products="newProducts" />
</div>
<div class="product-box">
<h2>热销排行</h2>
<ProductList :products="newProducts" />
</div>
<div class="product-box">
<h2>新品上市</h2>
<ProductList :products="newProducts" />
</div>
</div>
</template>
<script>
import HomeLayout from "@/components/layout/HomeLayout.vue";
import ProductList from "@/components/product/ProductList.vue";
export default {
components: {
HomeLayout,
ProductList,
},
data() {
return {
newProducts: [
{
id: 7,
name: "高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪",
price: 3299,
originalPrice: 3699,
image:
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png",
sales: 156,
categoryId: 1,
},
{
id: 8,
name: "保湿面霜",
price: 299,
originalPrice: 359,
image:
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png",
sales: 423,
categoryId: 4,
},
{
id: 9,
name: "有机水果礼盒",
price: 159,
originalPrice: 199,
image:
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png",
sales: 287,
categoryId: 5,
},
{
id: 10,
name: "经典文学名著",
price: 129,
originalPrice: 199,
image:
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png",
sales: 342,
categoryId: 6,
},
],
};
},
};
</script>
<style lang="scss" scoped>
.product-box {
margin: 30px 0;
h2 {
margin-bottom: 20px;
}
}
</style>

0
src/views/User/OrderList.vue

0
src/views/User/UserCenter.vue

3
vue.config.js

@ -1,4 +1,5 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
transpileDependencies: true,
runtimeCompiler: true
})

Loading…
Cancel
Save