瀏覽代碼

提交pdd

潘超林 2 周之前
父節點
當前提交
d0a2f2fe34
共有 10 個文件被更改,包括 863 次插入103 次删除
  1. 168 16
      package-lock.json
  2. 2 0
      package.json
  3. 33 15
      public/index.html
  4. 5 10
      src/App.vue
  5. 16 0
      src/api/index.js
  6. 0 58
      src/components/HelloWorld.vue
  7. 503 0
      src/components/pdd-detail.vue
  8. 3 2
      src/main.js
  9. 101 0
      src/utils/request.js
  10. 32 2
      vue.config.js

+ 168 - 16
package-lock.json

@@ -8,7 +8,9 @@
       "name": "spu-detail",
       "version": "0.1.0",
       "dependencies": {
+        "axios": "^1.13.6",
         "core-js": "^3.8.3",
+        "element-ui": "^2.15.14",
         "vue": "^2.6.14"
       },
       "devDependencies": {
@@ -3441,6 +3443,20 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/async-validator": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz",
+      "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
+      "dependencies": {
+        "babel-runtime": "6.x"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
     "node_modules/at-least-node": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
@@ -3488,6 +3504,23 @@
         "postcss": "^8.1.0"
       }
     },
+    "node_modules/axios": {
+      "version": "1.13.6",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz",
+      "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.11",
+        "form-data": "^4.0.5",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/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==",
+      "license": "MIT"
+    },
     "node_modules/babel-loader": {
       "version": "8.4.1",
       "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.4.1.tgz",
@@ -3575,6 +3608,24 @@
         "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
       }
     },
+    "node_modules/babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "license": "MIT",
+      "dependencies": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      }
+    },
+    "node_modules/babel-runtime/node_modules/core-js": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz",
+      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "hasInstallScript": true,
+      "license": "MIT"
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3848,7 +3899,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,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0",
@@ -4183,6 +4233,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "8.3.0",
       "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
@@ -4803,7 +4865,6 @@
       "version": "1.5.2",
       "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz",
       "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -5008,6 +5069,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -5186,7 +5256,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,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.1",
@@ -5228,6 +5297,23 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/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==",
+      "license": "MIT",
+      "dependencies": {
+        "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"
+      },
+      "peerDependencies": {
+        "vue": "^2.5.17"
+      }
+    },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -5330,7 +5416,6 @@
       "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,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -5340,7 +5425,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
       "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -5357,7 +5441,6 @@
       "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,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0"
@@ -5366,6 +5449,21 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/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==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/escalade": {
       "version": "3.2.0",
       "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
@@ -6287,7 +6385,6 @@
       "version": "1.15.11",
       "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
       "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
-      "dev": true,
       "funding": [
         {
           "type": "individual",
@@ -6304,6 +6401,22 @@
         }
       }
     },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
@@ -6387,7 +6500,6 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -6424,7 +6536,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,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.2",
@@ -6449,7 +6560,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,
       "license": "MIT",
       "dependencies": {
         "dunder-proto": "^1.0.1",
@@ -6568,7 +6678,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
       "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -6634,7 +6743,6 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
       "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -6643,6 +6751,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/hash-sum": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz",
@@ -6654,7 +6777,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
@@ -7834,7 +7956,6 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
       "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -7948,7 +8069,6 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.6"
@@ -7958,7 +8078,6 @@
       "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,
       "license": "MIT",
       "dependencies": {
         "mime-db": "1.52.0"
@@ -8299,6 +8418,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/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==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -9545,6 +9670,12 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/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==",
+      "license": "MIT"
+    },
     "node_modules/pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -9738,6 +9869,12 @@
         "node": ">=4"
       }
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+      "license": "MIT"
+    },
     "node_modules/regexpp": {
       "version": "3.2.0",
       "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz",
@@ -9840,6 +9977,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/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==",
+      "license": "MIT"
+    },
     "node_modules/resolve": {
       "version": "1.22.11",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz",
@@ -10956,6 +11099,15 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/thunky": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",

+ 2 - 0
package.json

@@ -8,7 +8,9 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^1.13.6",
     "core-js": "^3.8.3",
+    "element-ui": "^2.15.14",
     "vue": "^2.6.14"
   },
   "devDependencies": {

+ 33 - 15
public/index.html

@@ -1,17 +1,35 @@
 <!DOCTYPE html>
 <html lang="">
-  <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="<%= BASE_URL %>favicon.ico">
-    <title><%= htmlWebpackPlugin.options.title %></title>
-  </head>
-  <body>
-    <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> 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>
+
+<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="<%= BASE_URL %>favicon.ico">
+  <title>
+    商品详情
+  </title>
+</head>
+
+<body>
+  <noscript>
+    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> 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>
+<style>
+  * {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+
+  body {
+    background-color: #f5f5f5;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+  }
+</style>
+
+</html>

+ 5 - 10
src/App.vue

@@ -1,28 +1,23 @@
 <template>
   <div id="app">
-    <img alt="Vue logo" src="./assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js App"/>
+    <PddDetail />
   </div>
 </template>
 
 <script>
-import HelloWorld from './components/HelloWorld.vue'
+import PddDetail from './components/pdd-detail.vue'
 
 export default {
   name: 'App',
   components: {
-    HelloWorld
+    PddDetail
   }
 }
 </script>
 
 <style>
 #app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-  margin-top: 60px;
+margin: 0px;
+padding: 0px;
 }
 </style>

+ 16 - 0
src/api/index.js

@@ -0,0 +1,16 @@
+// 引入封装好的 axios 实例
+import request from '@/utils/request'
+
+/**
+ * 获取商品详情
+ * @param {Object} params - 查询参数(如 pageNum, pageSize)
+ * @returns {Promise}
+ */
+export const goodsDetail = (data) => {
+    return request({
+        url: '/third/pdd/ddk/goods/detail',
+        method: 'post',
+        data // get 请求参数拼在 url 后
+    })
+}
+

+ 0 - 58
src/components/HelloWorld.vue

@@ -1,58 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br>
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
-      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
-      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
-      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
-      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
-      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
-      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
-      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'HelloWorld',
-  props: {
-    msg: String
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 503 - 0
src/components/pdd-detail.vue

@@ -0,0 +1,503 @@
+<template>
+  <div id="app" class="product-page">
+    <!-- 顶部导航 -->
+    <!-- <div class="top-nav">
+			<div class="nav-back">←</div>
+			<div class="nav-title">拼多多</div>
+			<div class="nav-actions">
+				<span>···</span>
+				<span>●</span>
+			</div>
+		</div> -->
+    <div style="padding-bottom: 100px;">
+      <!-- 商品图片 -->
+      <el-carousel class="product-img">
+        <el-carousel-item v-for="item in spuInfo.goodsGalleryUrls" :key="item">
+          <img :src="item" style="width: 100%;height: 100%;">
+        </el-carousel-item>
+      </el-carousel>
+      <!-- <img :src="spuInfo.goodsImageUrl" class="product-img" alt="iPhone 15 Pro Max"> -->
+
+      <!-- 标签与商品名 -->
+      <div class="tag-bar">
+        <span class="tag tag-subsidy" v-if="spuInfo.activityTags.includes(4)">百亿补贴</span>
+        <span class="tag tag-flash" v-if="spuInfo.activityTags.includes(7)">秒杀</span>
+        <span class="product-name">{{ spuInfo.goodsName }}</span>
+      </div>
+
+      <!-- 优惠信息 -->
+      <div class="promo-bar">
+        <span class="coupon-tag" v-if="spuInfo.hasCoupon">{{ spuInfo.couponDiscount }}元券</span>
+        <span class="promo-text">
+          <span v-for="(item, index) in spuInfo.unifiedTags" :key="index">
+            {{ item }} <!-- 展示数组中的每个标签文本 -->
+            <!-- 给除最后一个外的标签添加 | 分隔符 -->
+            <span v-if="index < spuInfo.unifiedTags.length - 1"> | </span>
+          </span></span>
+      </div>
+
+      <!-- 佣金与升级 -->
+      <!-- <div class="price-section">
+        <div>
+          <span class="commission">{{ spuInfo.promotionRate / 10 }}% 比率</span>
+          <span class="commission"> 预估佣金 <span class="commission-value">¥{{ spuInfo.predictPromotionRate }}</span></span>
+        </div>
+        <el-button type="danger" size="mini" round class="upgrade-btn">立即升级</el-button>
+      </div> -->
+
+      <!-- 价格与销量 -->
+      <div class="price-info">
+        <div class="price">¥{{ spuInfo.minNormalPrice }} <span style="font-size:12px;color:#999;">券后价</span></div>
+        <div class="sales">销量 {{ spuInfo.salesTip }}</div>
+      </div>
+
+      <!-- 规格选择 -->
+      <div class="spec-section">
+        <div class="spec-title">规格名称 颜色</div>
+        <div class="spec-options">
+          <div class="spec-option active" @click="selectSpec('color', '原色钛金属')">原色钛金属</div>
+          <div class="spec-option" @click="selectSpec('color', '蓝色钛金属')">蓝色钛金属</div>
+          <div class="spec-option" @click="selectSpec('color', '白色钛金属')">白色钛金属</div>
+          <div class="spec-option" @click="selectSpec('color', '黑色钛金属')">黑色钛金属</div>
+        </div>
+
+        <div class="spec-title">存储型号</div>
+        <div class="spec-options">
+          <div class="spec-option active" @click="selectSpec('storage', '128GB')">128GB</div>
+          <div class="spec-option" @click="selectSpec('storage', '256GB')">256GB</div>
+          <div class="spec-option" @click="selectSpec('storage', '512GB')">512GB</div>
+          <div class="spec-option" @click="selectSpec('storage', '1TB')">1TB</div>
+        </div>
+      </div>
+
+      <!-- 店铺信息 -->
+      <div class="shop-section" v-if="spuInfo.mallName">
+        <div class="shop-logo">🍎</div>
+        <div class="shop-info">
+          <div class="shop-name">
+            <el-tag type="danger" size="mini">{{ spuInfo.merchantType == 3 ? '旗舰店' : spuInfo.merchantType == 4 ? '专卖店' :
+              spuInfo.merchantType == 5 ? '专营店' : '' }}</el-tag>
+            {{ spuInfo.mallName }}
+          </div>
+          <div class="shop-tags">
+            <span>描述相符 {{ spuInfo.descTxt }}</span>
+            <span>服务态度 {{ spuInfo.servTxt }}</span>
+            <span>发货速度 {{ spuInfo.lgstTxt }}</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 商品描述 -->
+      <div class="desc-section">
+        <div class="desc-title">商品描述</div>
+        <div class="desc-content">
+          {{ spuInfo.goodsDesc }}
+        </div>
+      </div>
+    </div>
+    <!-- 底部操作栏 -->
+    <div class="bottom-bar">
+      <!-- <div class="bar-left">
+        <span>推广可赚 <span style="color: red; font-size: 18px;">¥{{ spuInfo.predictPromotionRate }}</span> </span>
+      </div> -->
+      <div class="bar-btn btn-buy" @click="openAPP">APP打开</div>
+      <!-- <div class="bar-btn btn-promote">立即推广</div> -->
+    </div>
+  </div>
+</template>
+
+<script>
+import { goodsDetail } from '@/api/index.js'
+export default {
+  name: 'pdd-detail',
+  data() {
+    return {
+      phone: "",
+      yzm: "",
+      djs: "",
+      timer: null,
+      content: "发送验证码",
+      androidLinkurl: "https://oss.qianzhi-y.com/appapk/qianzhiyun_V1.0.0_release.apk",
+      loading: false,
+      spuInfo: {}
+
+    }
+  },
+  mounted() {
+    this.getgoodDetail()
+  },
+  methods: {
+    getgoodDetail() {
+      goodsDetail({
+        goodsSign: "E9b2tKijAJZtmKBBwfDAf_9KwfBCfUOT_JkGRUFcnU",
+        needSkuInfo: true
+      }).then(res => {
+        this.spuInfo = res.data
+      })
+    },
+    openAPP() {
+      const that = this;
+      // 1. 解析用户代理,判断设备类型
+      const ua = navigator.userAgent;
+      const isAndroid = ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1; // 安卓终端
+      // 新增:标记是否已唤起APP(核心修复)
+      let isAppInvoked = false;
+
+      // 2. 安卓端唤醒逻辑(核心优化)
+      if (isAndroid) {
+        // 记录唤醒开始时间
+        const loadStartTime = new Date().getTime();
+        // APP Scheme 唤起链接(带参数)
+        const schemeUrl = "com.benefit.workbox://qianzhiyun?userid=123" + location.search;
+
+        // ========== 关键优化1:监听页面隐藏事件(唤起APP会触发) ==========
+        // 当页面被隐藏(切换到APP),标记为已唤起
+        const onPageHide = () => {
+          isAppInvoked = true;
+          // 移除监听,避免重复触发
+          document.removeEventListener('visibilitychange', onPageHide);
+          window.removeEventListener('pagehide', onPageHide);
+        };
+        // 监听页面隐藏(兼容不同浏览器)
+        document.addEventListener('visibilitychange', onPageHide);
+        window.addEventListener('pagehide', onPageHide);
+
+        // ========== 关键优化2:简化唤起方式(避免重复触发) ==========
+        // 仅用iframe唤起(去掉location.href兜底,减少误触发)
+        const iframe = document.createElement('iframe');
+        iframe.style.display = 'none';
+        iframe.src = schemeUrl;
+        document.body.appendChild(iframe);
+        // 用完销毁iframe
+        setTimeout(() => {
+          document.body.removeChild(iframe);
+        }, 2000);
+
+        // ========== 关键优化3:精准检测唤起状态 ==========
+        setTimeout(() => {
+          const currentTime = new Date().getTime();
+          const timeDiff = currentTime - loadStartTime;
+
+          // 只有「未标记唤起」且「时间差小于阈值」,才跳转下载
+          if (!isAppInvoked && timeDiff < 5000) { // 阈值从10000缩短到5000,更精准
+            // 校验下载链接是否有效,避免报错
+            if (that.androidLinkurl && that.androidLinkurl.trim()) {
+              const a = document.createElement('a');
+              a.target = '_blank';
+              a.href = that.androidLinkurl;
+              a.click();
+            } else {
+              console.error('安卓下载链接未配置');
+            }
+          } else {
+            // 唤起成功,清空标记
+            isAppInvoked = false;
+            // 移除监听
+            document.removeEventListener('visibilitychange', onPageHide);
+            window.removeEventListener('pagehide', onPageHide);
+          }
+        }, 2000); // 检测延迟从1000延长到2000,给APP唤起足够时间
+      }
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.product-page {
+  /* max-width: 375px; */
+  margin: 0 0px;
+  padding: 0px;
+  background: #fff;
+  min-height: 100vh;
+}
+
+/* 顶部导航栏 */
+.top-nav {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: #fff;
+  border-bottom: 1px solid #eee;
+}
+
+.nav-back {
+  font-size: 20px;
+  color: #333;
+}
+
+.nav-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #ff2f2f;
+}
+
+.nav-actions {
+  display: flex;
+  gap: 12px;
+}
+
+/* 商品图片 */
+.product-img {
+  width: 100%;
+  height: 300px;
+  object-fit: cover;
+  background: #f8f8f8;
+}
+
+/* 标签栏 */
+.tag-bar {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  background: #fff;
+}
+
+.tag {
+  padding: 2px 4px;
+  border-radius: 4px;
+  font-size: 12px;
+  color: #fff;
+  min-width: 60px;
+  text-align: center;
+}
+
+.tag-subsidy {
+  background: #ff4444;
+}
+
+.tag-flash {
+  background: #ff6600;
+}
+
+.product-name {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  -o-text-overflow: ellipsis;
+  /* padding: 0 16px; */
+}
+
+/* 优惠信息 */
+.promo-bar {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  background: #fff9f9;
+}
+
+.coupon-tag {
+  background: #ff4444;
+  color: #fff;
+  padding: 2px 6px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+.promo-text {
+  font-size: 12px;
+  color: #666;
+}
+
+/* 佣金与价格 */
+.price-section {
+  padding: 12px 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.commission {
+  font-size: 14px;
+  color: #666;
+}
+
+.commission-value {
+  color: #ff4444;
+  font-size: 18px;
+  font-weight: 600;
+}
+
+.upgrade-btn {
+  background: #ff4444;
+  color: #fff;
+  border: none;
+  padding: 6px 12px;
+  border-radius: 20px;
+  font-size: 12px;
+}
+
+.price-info {
+  padding: 0 16px 12px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.price {
+  font-size: 18px;
+  color: #ff4444;
+  font-weight: 600;
+}
+
+.sales {
+  font-size: 14px;
+  color: #666;
+}
+
+/* 规格选择 */
+.spec-section {
+  padding: 12px 16px;
+  border-top: 8px solid #f5f5f5;
+}
+
+.spec-title {
+  font-size: 14px;
+  color: #333;
+  margin-bottom: 12px;
+}
+
+.spec-options {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.spec-option {
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  font-size: 14px;
+  color: #333;
+}
+
+.spec-option.active {
+  border-color: #ff4444;
+  color: #ff4444;
+  background: #fff5f5;
+}
+
+/* 店铺信息 */
+.shop-section {
+  padding: 12px 16px;
+  border-top: 8px solid #f5f5f5;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.shop-logo {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background: #f5f5f5;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.shop-info {
+  flex: 1;
+}
+
+.shop-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 4px;
+}
+
+.shop-tags {
+  display: flex;
+  gap: 16px;
+  font-size: 12px;
+  color: #666;
+}
+
+/* 商品描述 */
+.desc-section {
+  padding: 12px 16px;
+  border-top: 8px solid #f5f5f5;
+}
+
+.desc-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 8px;
+}
+
+.desc-content {
+  font-size: 13px;
+  color: #666;
+  line-height: 1.6;
+}
+
+/* 底部操作栏 */
+.bottom-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  /* max-width: 375px; */
+  margin: 0 auto;
+  display: flex;
+  background: #fff;
+  border-top: 1px solid #eee;
+  padding: 8px;
+}
+
+.bar-left {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  font-size: 12px;
+  color: #666;
+}
+
+.bar-btn {
+  flex: 2;
+  padding: 10px;
+  border-radius: 24px;
+  text-align: center;
+  font-size: 16px;
+  font-weight: 500;
+  /* 点击效果核心属性 */
+  touch-action: manipulation;
+  /* 阻止移动端双击缩放,提升点击响应 */
+  user-select: none;
+  /* 禁止文字选中 */
+  cursor: pointer;
+  transition: all 0.2s ease;
+  /* 过渡动画,让效果更丝滑 */
+  transform: scale(1);
+  /* 初始缩放 */
+  box-shadow: 0 2px 8px rgba(244, 51, 60, 0.3);
+  /* 初始阴影 */
+}
+
+.btn-buy {
+  background: #ff9900;
+  color: #fff;
+  margin-right: 8px;
+}
+
+.btn-promote {
+  background: #ff2f2f;
+  color: #fff;
+}
+
+/deep/ .el-carousel__indicators {
+  display: flex;
+}
+</style>

+ 3 - 2
src/main.js

@@ -1,8 +1,9 @@
 import Vue from 'vue'
 import App from './App.vue'
-
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+Vue.use(ElementUI);
 Vue.config.productionTip = false
-
 new Vue({
   render: h => h(App),
 }).$mount('#app')

+ 101 - 0
src/utils/request.js

@@ -0,0 +1,101 @@
+import axios from 'axios'
+import { Message } from 'element-ui';
+// 创建 axios 实例
+const service = axios.create({
+    // 基础路径:匹配 vue.config.js 中的代理前缀
+    baseURL: '/api',
+    // 请求超时时间
+    timeout: 5000
+})
+
+// 请求拦截器:发送请求前做处理(如添加 token)
+service.interceptors.request.use(
+    (config) => {
+        // 示例:添加 token 到请求头(登录后存储在 localStorage/cookie)
+        const token = localStorage.getItem('token')
+        if (token) {
+            config.headers.Authorization = `Bearer ${token}`
+        }
+        // 设置请求头(如 JSON 格式)
+        config.headers['Content-Type'] = 'application/json;charset=utf-8'
+            config.headers['pdd-access-token'] = '2533ff1e4a0d44fb807b8e6635e71f7819133bad'
+        return config
+    },
+    (error) => {
+        // 请求错误处理
+        console.error('请求出错:', error)
+        return Promise.reject(error)
+    }
+)
+
+// 响应拦截器:接收响应后做处理(如统一错误提示)
+service.interceptors.response.use(
+    (response) => {
+        // 解构响应数据
+        const { data, status } = response
+        // 正常响应(状态码 200)
+        if (status === 200) {
+            // 示例:后端自定义错误码(如 code !== 200 为业务错误)
+            if (data.code !== 200) {
+                Message({
+                    message: data.msg || '请求失败',
+                    type: 'error'
+                });
+                return Promise.reject(data)
+            }
+            
+            return data // 直接返回核心数据,简化业务层调用
+        }
+        return response
+    },
+    (error) => {
+        // 网络/服务器错误处理
+        const { response } = error
+        if (response) {
+            // 有响应但状态码异常
+            switch (response.status) {
+                case 401:
+                    Message({
+                        message: '登录失效,请重新登录',
+                        type: 'error'
+                    });
+                    // 示例:跳转到登录页
+                    window.location.href = '/login'
+                    break
+                case 403:
+                    Message({
+                        message: '暂无权限访问',
+                        type: 'error'
+                    });
+                    break
+                case 404:
+                    Message({
+                        message: '接口地址不存在',
+                        type: 'error'
+                    });
+                    break
+                case 500:
+                    Message({
+                        message: '服务器内部错误',
+                        type: 'error'
+                    });
+                    break
+                default:
+                    Message({
+                        message: `请求失败:${response.status}`,
+                        type: 'error'
+                    });
+            }
+        } else {
+            // 无响应(网络问题)
+            Message({
+                message: `网络异常,请检查网络连接`,
+                type: 'error'
+            });
+        }
+        return Promise.reject(error)
+    }
+)
+
+// 导出封装后的实例
+export default service

+ 32 - 2
vue.config.js

@@ -1,4 +1,34 @@
 const { defineConfig } = require('@vue/cli-service')
 module.exports = defineConfig({
-  transpileDependencies: true
-})
+  transpileDependencies: true,
+  // 配置开发服务器(包括代理)
+  publicPath: "/",
+  // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
+  outputDir: 'dist',
+  // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
+  assetsDir: 'static',
+  // 是否开启eslint保存检测,有效值:ture | false | 'error'
+  lintOnSave: true, // 保存时触发ESLint检查,有错误仅警告,不阻断编译
+  // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
+  productionSourceMap: false,
+  devServer: {
+    // 开启代理
+    proxy: {
+      // 1. 匹配以 /api 开头的请求(自定义前缀)
+      '/api': {
+        // 目标后端接口地址(替换成你的实际接口地址)
+        target: 'http://47.108.204.221:6104/',
+        // 开启跨域(必须设置,否则代理无效)
+        changeOrigin: true,
+        // 路径重写:去掉请求路径中的 /api 前缀(根据后端接口是否需要前缀决定)
+        // 例如:前端请求 /api/user → 实际转发到 http://localhost:8080/user
+        pathRewrite: {
+          '^/api': ''
+        },
+        // 可选:如果后端是 https 接口,需要设置为 true
+        // secure: false
+      },
+
+    }
+  }
+})