潘超林 2 hete
szülő
commit
abd5d6f6cf

+ 8 - 1
package-lock.json

@@ -11,7 +11,8 @@
         "axios": "^1.13.6",
         "core-js": "^3.8.3",
         "element-ui": "^2.15.14",
-        "vue": "^2.6.14"
+        "vue": "^2.6.14",
+        "vue-router": "^3.6.5"
       },
       "devDependencies": {
         "@babel/core": "^7.12.16",
@@ -11527,6 +11528,12 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
+    "node_modules/vue-router": {
+      "version": "3.6.5",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-3.6.5.tgz",
+      "integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==",
+      "license": "MIT"
+    },
     "node_modules/vue-style-loader": {
       "version": "4.1.3",
       "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",

+ 2 - 1
package.json

@@ -11,7 +11,8 @@
     "axios": "^1.13.6",
     "core-js": "^3.8.3",
     "element-ui": "^2.15.14",
-    "vue": "^2.6.14"
+    "vue": "^2.6.14",
+    "vue-router": "^3.6.5"
   },
   "devDependencies": {
     "@babel/core": "^7.12.16",

BIN
public/favicon.ico


BIN
public/favicon.png


+ 1 - 1
public/index.html

@@ -5,7 +5,7 @@
   <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">
+  <link rel="icon" href="<%= BASE_URL %>favicon.png">
   <title>
     商品详情
   </title>

+ 35 - 5
src/App.vue

@@ -1,23 +1,53 @@
 <template>
   <div id="app">
-    <PddDetail />
+    <component ts :is="componentName"></component>
   </div>
 </template>
 
 <script>
 import PddDetail from './components/pdd-detail.vue'
-
+import JdDetail from './components/jd-detail.vue'
 export default {
   name: 'App',
   components: {
-    PddDetail
+    PddDetail,
+    JdDetail
+  },
+  data() {
+    return {
+      platform: "",
+      componentName: null
+    }
+  },
+  mounted() {
+    this.platform = this.GetQueryString('platform')
+    switch (this.platform) {
+      case 'pdd':
+        this.componentName = PddDetail
+        break;
+      case 'jd':
+        this.componentName = JdDetail
+        break;
+      default:
+        break;
+    }
+    console.log(this.componentName);
+
+  },
+  methods: {
+    GetQueryString(name) {
+      var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+      var r = window.location.search.substr(1).match(reg);
+      if (r != null) return unescape(r[2]);
+      return null;
+    },
   }
 }
 </script>
 
 <style>
 #app {
-margin: 0px;
-padding: 0px;
+  margin: 0px;
+  padding: 0px;
 }
 </style>

+ 26 - 2
src/api/index.js

@@ -2,15 +2,39 @@
 import request from '@/utils/request'
 
 /**
- * 获取商品详情
+ * pdd获取商品详情
  * @param {Object} params - 查询参数(如 pageNum, pageSize)
  * @returns {Promise}
  */
 export const goodsDetail = (data) => {
     return request({
-        url: '/third/pdd/ddk/goods/detail',
+        url: '/external/third/pdd/ddk/goods/detail',
         method: 'post',
         data // get 请求参数拼在 url 后
     })
 }
 
+/**
+ * jd获取商品详情
+ * @param {Object} params - 查询参数(如 pageNum, pageSize)
+ * @returns {Promise}
+ */
+export const jdDetail = (data) => {
+    return request({
+        url: '/external/third/jd/detail',
+        method: 'post',
+        data // get 请求参数拼在 url 后
+    })
+}
+/**
+ * 获取应用最新版本
+ * @param {Object} params - 查询参数(如 pageNum, pageSize)
+ * @returns {Promise}
+ */
+export const getAppLatestVersion = (data) => {
+    return request({
+        url: '/user/common/getAppLatestVersion',
+        method: 'get',
+        params: data // get 请求参数拼在 url 后
+    })
+}

BIN
src/assets/logo.png


+ 484 - 0
src/components/jd-detail.vue

@@ -0,0 +1,484 @@
+<template>
+  <div id="app" class="product-page">
+    <div style="padding-bottom: 100px;">
+      <!-- 商品图片 -->
+      <el-carousel class="product-img">
+        <el-carousel-item v-for="item in spuInfo.imageInfo" :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="hasActivityTag(4)">百亿补贴</span>
+        <span class="tag tag-flash" v-if="hasActivityTag(7)">秒杀</span>
+        <span class="product-name">{{ spuInfo.skuName }}</span>
+      </div>
+
+      <!-- 价格与销量 -->
+      <div class="price-info">
+        <div class="price">¥{{ spuInfo.finalPrice }} <span style="font-size:12px;color:#999;">到手价</span></div>
+        <div class="sales">销量 {{ spuInfo.inOrderCount30Days }}</div>
+      </div>
+
+      <!-- 规格选择 -->
+      <div class="spec-section">
+        <div v-for="(item, index) in spuInfo.propGroups">
+          <div class="spec-title">{{ item.groupName }}</div>
+          <div class="spec-options" v-for="(res, indeix) in item.atts">
+            <div class="spec-title1">{{ res.attName }}</div>
+            <div class="spec-text">{{ res.attVals[0].attValName }}</div>
+          </div>
+        </div>
+      </div>
+
+
+
+      <!-- 商品描述 -->
+      <!-- <div class="desc-section">
+        <div class="desc-title">商品描述</div>
+        <div class="desc-content" v-html="processedIntro">
+        </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 { jdDetail, getAppLatestVersion } from '@/api/index.js'
+export default {
+  name: 'jd-detail',
+  data() {
+    return {
+      androidLinkurl: "https://oss.qianzhi-y.com/appapk/qianzhiyun_V1.0.0_release.apk",
+      spuInfo: {},
+      query: {
+        platform: "",
+        shareUserId: "",
+        token: "",
+        goodsSign: ""
+      },
+    }
+  },
+  mounted() {
+    // this.getAppdownloadUrl()
+    this.query.platform = this.GetQueryString("platform")
+    this.query.shareUserId = this.GetQueryString("shareUserId")
+    this.query.token = this.GetQueryString("token")
+    this.query.goodsSign = this.GetQueryString("goodsSign")
+    this.getgoodDetail()
+  },
+  methods: {
+    hasActivityTag(tagCode) {
+      // 容错:先判断 activityTags 是数组且非空,再判断是否包含目标值
+      return Array.isArray(this.spuInfo.activityTags) && this.spuInfo.activityTags.includes(tagCode);
+    },
+    getAppdownloadUrl() {
+      getAppLatestVersion().then(res => {
+        this.androidLinkurl = res.data.downloadUrl
+      })
+    },
+    getgoodDetail() {
+      jdDetail({
+        skuIds: [],
+        itemIds: ['m6pQblHJgMDvmGeTn2E0FwFh_3lzC2xkU1gHEUbfkrn'],
+      }).then(res => {
+        this.spuInfo = res.data[0]
+        this.spuInfo.propGroups = JSON.parse(this.spuInfo.propGroups)
+      })
+    },
+    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://promotion-goods" + 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唤起足够时间
+      }
+    },
+    GetQueryString(name) {
+      var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+      var r = window.location.search.substr(1).match(reg);
+      if (r != null) return unescape(r[2]);
+      return null;
+    },
+
+
+
+
+  }
+}
+</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;
+  min-width: 60px;
+}
+
+.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-title1 {
+  font-size: 14px;
+  color: #9f9c9c;
+}
+
+.spec-text {
+  font-size: 14px;
+}
+
+.spec-title {
+  font-size: 18px;
+  color: #333;
+  margin-bottom: 12px;
+  font-weight: bold;
+}
+
+.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>

+ 38 - 18
src/components/pdd-detail.vue

@@ -20,8 +20,8 @@
 
       <!-- 标签与商品名 -->
       <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="tag tag-subsidy" v-if="hasActivityTag(4)">百亿补贴</span>
+        <span class="tag tag-flash" v-if="hasActivityTag(7)">秒杀</span>
         <span class="product-name">{{ spuInfo.goodsName }}</span>
       </div>
 
@@ -52,7 +52,8 @@
       </div>
 
       <!-- 规格选择 -->
-      <div class="spec-section">
+
+      <!-- <div class="spec-section">
         <div class="spec-title">规格名称 颜色</div>
         <div class="spec-options">
           <div class="spec-option active" @click="selectSpec('color', '原色钛金属')">原色钛金属</div>
@@ -68,11 +69,11 @@
           <div class="spec-option" @click="selectSpec('storage', '512GB')">512GB</div>
           <div class="spec-option" @click="selectSpec('storage', '1TB')">1TB</div>
         </div>
-      </div>
+      </div> -->
 
       <!-- 店铺信息 -->
       <div class="shop-section" v-if="spuInfo.mallName">
-        <div class="shop-logo">🍎</div>
+        <div class="shop-logo"><img :src="spuInfo.mallImgUrl" style="width: 30px;height: 30px;"></div>
         <div class="shop-info">
           <div class="shop-name">
             <el-tag type="danger" size="mini">{{ spuInfo.merchantType == 3 ? '旗舰店' : spuInfo.merchantType == 4 ? '专卖店' :
@@ -107,29 +108,42 @@
 </template>
 
 <script>
-import { goodsDetail } from '@/api/index.js'
+import { goodsDetail, getAppLatestVersion } 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: {}
-
+      spuInfo: {},
+      query: {
+        platform: "",
+        shareUserId: "",
+        token: "",
+        goodsSign: ""
+      }
     }
   },
   mounted() {
+    this.getAppdownloadUrl()
+    this.query.platform = this.GetQueryString("platform")
+    this.query.shareUserId = this.GetQueryString("shareUserId")
+    this.query.token = this.GetQueryString("token")
+    this.query.goodsSign = this.GetQueryString("goodsSign")
     this.getgoodDetail()
   },
   methods: {
+    hasActivityTag(tagCode) {
+      // 容错:先判断 activityTags 是数组且非空,再判断是否包含目标值
+      return Array.isArray(this.spuInfo.activityTags) && this.spuInfo.activityTags.includes(tagCode);
+    },
+    getAppdownloadUrl() {
+      getAppLatestVersion().then(res => {
+        this.androidLinkurl = res.data.downloadUrl
+      })
+    },
     getgoodDetail() {
       goodsDetail({
-        goodsSign: "E9b2tKijAJZtmKBBwfDAf_9KwfBCfUOT_JkGRUFcnU",
+        goodsSign: this.query.goodsSign,
         needSkuInfo: true
       }).then(res => {
         this.spuInfo = res.data
@@ -148,8 +162,7 @@ export default {
         // 记录唤醒开始时间
         const loadStartTime = new Date().getTime();
         // APP Scheme 唤起链接(带参数)
-        const schemeUrl = "com.benefit.workbox://qianzhiyun?userid=123" + location.search;
-
+        const schemeUrl = "com.benefit.workbox://promotion-goods?userid=123&aaa=1" + location.search;
         // ========== 关键优化1:监听页面隐藏事件(唤起APP会触发) ==========
         // 当页面被隐藏(切换到APP),标记为已唤起
         const onPageHide = () => {
@@ -198,7 +211,13 @@ export default {
           }
         }, 2000); // 检测延迟从1000延长到2000,给APP唤起足够时间
       }
-    }
+    },
+    GetQueryString(name) {
+      var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+      var r = window.location.search.substr(1).match(reg);
+      if (r != null) return unescape(r[2]);
+      return null;
+    },
   }
 }
 </script>
@@ -299,6 +318,7 @@ export default {
   padding: 2px 6px;
   border-radius: 4px;
   font-size: 12px;
+  min-width: 60px;
 }
 
 .promo-text {

+ 10 - 3
src/utils/request.js

@@ -7,7 +7,12 @@ const service = axios.create({
     // 请求超时时间
     timeout: 5000
 })
-
+function GetQueryString(name) {
+    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+    var r = window.location.search.substr(1).match(reg);
+    if (r != null) return unescape(r[2]);
+    return null;
+}
 // 请求拦截器:发送请求前做处理(如添加 token)
 service.interceptors.request.use(
     (config) => {
@@ -18,7 +23,9 @@ service.interceptors.request.use(
         }
         // 设置请求头(如 JSON 格式)
         config.headers['Content-Type'] = 'application/json;charset=utf-8'
-            config.headers['pdd-access-token'] = '2533ff1e4a0d44fb807b8e6635e71f7819133bad'
+        if (GetQueryString('platform') == 'pdd') {
+            config.headers['pdd-access-token'] = GetQueryString('token')
+        }
         return config
     },
     (error) => {
@@ -43,7 +50,7 @@ service.interceptors.response.use(
                 });
                 return Promise.reject(data)
             }
-            
+
             return data // 直接返回核心数据,简化业务层调用
         }
         return response

+ 1 - 1
vue.config.js

@@ -17,7 +17,7 @@ module.exports = defineConfig({
       // 1. 匹配以 /api 开头的请求(自定义前缀)
       '/api': {
         // 目标后端接口地址(替换成你的实际接口地址)
-        target: 'http://47.108.204.221:6104/',
+        target: 'http://192.168.124.67:30000/',
         // 开启跨域(必须设置,否则代理无效)
         changeOrigin: true,
         // 路径重写:去掉请求路径中的 /api 前缀(根据后端接口是否需要前缀决定)