Prechádzať zdrojové kódy

Merge branch 'master' into feature/v1.5.4

123456 3 rokov pred
rodič
commit
15e43bfbc9

+ 1 - 0
src/api/modules/index.js

@@ -12,6 +12,7 @@ export * from './svip'
 export * from './file'
 export * from './customer'
 export * from './dataExport'
+export * from './marketing'
 export * from './workspace'
 export * from './entbase'
 export * from './public'

+ 47 - 0
src/api/modules/marketing.js

@@ -0,0 +1,47 @@
+import request from '@/api'
+let jyApi = ''
+if (process.env.NODE_ENV === 'production') {
+  jyApi = '/jyapi'
+}
+/**
+ * 轻量化营销服务
+ */
+
+// 获取服务端当前时间戳
+export function getServerInitTime () {
+  return request({
+    baseURL: jyApi + '/marketing',
+    url: '/time/now?t=' + Date.now(),
+    method: 'post'
+  })
+}
+
+// 预约
+export function appointmentAdd (data) {
+  return request({
+    baseURL: jyApi + '/marketing',
+    url: '/appointment/add',
+    method: 'post',
+    data
+  })
+}
+
+// 预热信息
+export function getAppointmentInfo (data) {
+  return request({
+    baseURL: jyApi + '/marketing',
+    url: '/appointment/info',
+    method: 'post',
+    data
+  })
+}
+
+// 是否已预约
+export function getIsAppointment (data) {
+  return request({
+    baseURL: jyApi + '/marketing',
+    url: '/appointment/isAppointment',
+    method: 'post',
+    data
+  })
+}

BIN
src/assets/images/icon/icon-crown.png


BIN
src/assets/images/limited-already.png


BIN
src/assets/images/limited-bg.png


BIN
src/assets/images/limited-empty.png


BIN
src/assets/images/subscribe-btn.png


+ 7 - 6
src/components/collect-info/CollectInfo.vue

@@ -1017,12 +1017,13 @@ export default {
     justify-content: center;
     align-items: center;
     .icon-warning{
-      display: flex;
-      width: 15px;
-      height: 15px;
-      background: url('../../assets/images/icon/icon-warning1.png') no-repeat;
-      background-size: contain;
-    }
+        display: flex;
+        width: 15px;
+        height: 15px;
+        background: url('../../assets/images/icon/icon-warning1.png') no-repeat;
+        background-size: contain;
+        
+        }
     .warm-text{
       // text-align: center;
       line-height: 30px;

+ 11 - 1
src/components/common/SpecCard.vue

@@ -2,6 +2,7 @@
   <div class="spec-card" :class="{ active: active }" @click="clickCard">
     <span class="float-tip" :class="floatTipClass" v-if="showFloatTip">
       <slot name="float-tip">
+        <span class="icon-crown"></span>
         <span v-if="defaultFloatTipText">{{ defaultFloatTipText }}</span>
       </slot>
     </span>
@@ -69,7 +70,8 @@ export default {
 
   .float-tip {
     position: absolute;
-    left: 0px;
+    // left: 0px;
+    left: -2px;
     top: 0px;
     padding: 0 8px;
     color: #fff;
@@ -78,6 +80,14 @@ export default {
     border-radius: 8px 2px 10px 2px;
     background-color: #2abed1;
     z-index: 9;
+    .icon-crown{
+      display: inline-block;
+      width: 12px;
+      height: 10px;
+      margin-right: 4px;
+      background: url('~@/assets/images/icon/icon-crown.png') no-repeat center;
+      background-size: cover;
+    }
   }
 }
 </style>

+ 9 - 1
src/components/coupon/BuySubmit.vue

@@ -36,7 +36,7 @@
           </el-checkbox>
         </div>
         <div class="price-submit-container">
-          <button class="price-submit" :disabled="confirmButtonDisabled" @click="buySubmit">确定支付</button>
+          <button class="price-submit" :disabled="confirmButtonDisabled" @click="buySubmit">{{submitText}}</button>
         </div>
       </div>
     </div>
@@ -114,6 +114,7 @@ export default {
           url: '/front/staticPage/serviceterms.html'
         }
       ],
+      submitText: '确定支付',
       stickyShow: false,
       agreement: false
     }
@@ -131,6 +132,13 @@ export default {
     },
     buySubmit () {
       this.$emit('submit')
+    },
+    changeBtnText (flag) {
+      if (flag) {
+        this.submitText = '立即抢购'
+      } else {
+        this.submitText = '确定支付'
+      }
     }
   }
 }

+ 9 - 1
src/components/coupon/CouponGiftList.vue

@@ -70,9 +70,17 @@ export default {
       if (!info.useProduct) {
         return this.giftListLoaded(0)
       }
+      this.giftList = []
       const { data } = await getGiftList(info)
-      if (Array.isArray(data)) {
+      if (Array.isArray(data) && data.length > 0) {
         this.giftList = data.map(item => {
+          item.preStartTime = item.preheatingTime && item.preheatingTime.toString().length === 10 ? item.preheatingTime * 1000 : item.preheatingTime
+          item.startTime = item.beginDate && item.beginDate.toString().length === 10 ? item.beginDate * 1000 : item.beginDate
+          item.endTime = item.endDate && item.endDate.toString().length === 10 ? item.endDate * 1000 : item.endDate
+          item.isReceive = item.IsReceive
+          if (!item.stockNumber) {
+            item.stockNumber = 0
+          }
           const giftInfo = this.sortGiftInfo(item)
           return {
             ...item,

+ 3 - 3
src/components/coupon/SpecList.vue

@@ -3,7 +3,7 @@
     <SpecCard
       v-for="spec in list"
       :key="spec.id"
-      :showFloatTip="!!spec.tipText && spec[activeType] === active"
+      :showFloatTip="!!spec.tipText"
       floatTipClass="spec-c-tip"
       :defaultFloatTipText="spec.tipText"
       :active="spec[activeType] === active"
@@ -88,8 +88,8 @@ export default {
     }
     .spec-c-tip {
       top: -10px;
-      border-radius: 2px 10px 2px 2px;
-      background: linear-gradient(90deg, #FF3A20 0%, #FF621F 100%);
+      border-radius: 9px 0;
+      background: linear-gradient(98deg, #FF7C32 0%, #F33838 100%);
     }
   }
 }

+ 198 - 0
src/components/limited-banner/index.vue

@@ -0,0 +1,198 @@
+<template>
+  <div class="limited-discount">
+    <div class="limited-bg">
+      <div class="limited-badge">{{ getBadge }}</div>
+      <div class="limited-content">{{ getActivityName }}</div>
+      <div class="limited-countdown">
+        <LimitedCountDown :endTime="activityTime" :startText="startText" @curTime="getCurTime"></LimitedCountDown>
+      </div>
+      <div class="limited-handel">
+        <div class="cycle-running" v-if="unStatus" @click.stop="$emit('sub')"></div>
+        <div v-else>
+          <div class="cycle-already" v-if="alreadyStatus"></div>
+          <div class="cycle-empty" v-if="emptyStatus"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import LimitedCountDown from '@/components/limited-countdown'
+export default {
+  name: 'LimitedDiscount',
+  components: {
+    LimitedCountDown
+  },
+  props: {
+    activityType: {
+      type: Number,
+      default: 0
+    },
+    activityName: {
+      type: String,
+      default: ''
+    },
+    activityId: {
+      type: Number,
+      default: 0
+    },
+    activityDesc: {
+      type: String,
+      default: ''
+    },
+    preStartTime: {
+      type: Number,
+      default: 0
+    },
+    startTime: {
+      type: Number,
+      default: 0
+    },
+    endTime: {
+      type: Number,
+      default: 0
+    },
+    stockNumber: {
+      type: [Number, String],
+      default: -1
+    },
+    status: {
+      type: [Number, String],
+      default: -1
+    }
+  },
+  data () {
+    return {
+      activityTime: this.startTime,
+      startText: '开始倒计时:',
+      getActivityName: this.activityName,
+      curTime: 0,
+      isStart: false,
+      isEnd: false
+    }
+  },
+  computed: {
+    getBadge () {
+      const type = this.activityType
+      const title = {
+        0: '满减',
+        1: '折扣券',
+        2: '满赠',
+        3: '限时特惠',
+        4: '限时折扣',
+        5: '限时减免'
+      }
+      return title[type]
+    },
+    unStatus () {
+      // 当前时间小于活动开始时间且未预约状态且还有库存
+      return !this.isStart && this.status === 0 && this.stockNumber > 0
+    },
+    alreadyStatus () {
+      // 活动没有开始 且已预约且有库存
+      return !this.isStart && this.status === 1 && this.stockNumber > 0
+    },
+    emptyStatus () {
+      return this.stockNumber === 0
+    }
+  },
+  watch: {
+    activityName (val) {
+      this.getActivityName = val
+    },
+    curTime (val) {
+      // console.log(val, 'val')
+      if (val < this.startTime) {
+        this.isStart = false
+      } else {
+        this.isStart = true
+        if (val < this.endTime) {
+          this.isEnd = false
+        } else {
+          this.isEnd = true
+        }
+      }
+    }
+  },
+  mounted () {},
+  methods: {
+    getCurTime (data) {
+      const beginDate = this.startTime.toString().length === 10 ? this.startTime * 1000 : this.startTime
+      if (beginDate > data) {
+        this.activityTime = beginDate
+        this.startText = '开始倒计时:'
+      } else {
+        this.activityTime = this.endTime
+        this.startText = '倒计时:'
+        this.getActivityName = this.stockNumber > 0 ? `优惠仅剩${this.stockNumber}个,快抢快抢!` : '优惠名额已抢空'
+      }
+      this.curTime = data
+      this.$emit('curTime', data)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.limited-discount{
+  width: 100%;
+  background: linear-gradient(173deg, #FF7C32 0%, #F33838 100%);
+  .limited-bg{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 1120px;
+    height: 80px;
+    background: url('~@/assets/images/limited-bg.png') no-repeat center;
+    background-size: contain;
+    margin: 0 auto;
+    color: #fff;
+  }
+  .limited-badge{
+    padding: 7px 16px;
+    background: linear-gradient(163deg, #CB1313 0%, #F82424 100%);
+    border-radius: 16px;
+    font-size: 16px;
+    color: #fff;
+    line-height: 18px;
+    text-align: center;
+  }
+  .limited-content{
+    padding: 0 16px;
+    line-height: 28px;
+    font-size: 18px;
+  }
+  .limited-countdown{
+    min-width: 240px;
+    min-height: 32px;
+    padding: 7px 16px;
+    background: linear-gradient(163deg, #CB1313 0%, rgba(248,36,36,0) 100%);
+    border-radius: 16px;
+    line-height: 18px;
+    margin-right: 16px;
+  }
+  .cycle-running{
+    width: 112px;
+    height: 40px;
+    background: url('~@/assets/images/subscribe-btn.png') no-repeat center;
+    background-size: contain;
+    cursor: pointer;
+  }
+  .cycle-item{
+    width: 112px;
+    height: 76px;
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: contain;
+  }
+  .cycle-empty{
+    @extend .cycle-item;
+    background-image: url('~@/assets/images/limited-empty.png')
+  }
+  .cycle-already{
+    @extend .cycle-item;
+    background-image: url('~@/assets/images/limited-already.png')
+  }
+}
+</style>

+ 139 - 0
src/components/limited-countdown/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="countdown">
+    <span v-if="showStartText">{{startText}}</span>
+    <span>{{content}}</span>
+  </div>
+</template>
+
+<script>
+import { getServerInitTime } from '@/api/modules/'
+export default {
+  name: 'LimitedCountDown',
+  props: {
+    /**
+     * 活动开始文案
+     */
+    startText: {
+      type: String,
+      default: '开始倒计时:'
+    },
+    /**
+     * 开始时间-时间戳
+     */
+    startTime: {
+      type: Number,
+      default: 0
+    },
+    /**
+     * 到期时间-时间戳
+     */
+    endTime: {
+      type: Number,
+      default: 0
+    },
+    /**
+     * 倒计时结束显示文案
+     */
+    endText: {
+      type: String,
+      default: ''
+    },
+    /**
+     * 时间单位为空是否展示占位
+     */
+    placeholder: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      showStartText: false,
+      content: '',
+      serverInitTime: null,
+      localInitTime: null
+    }
+  },
+  watch: {
+    endTime (val) {
+      this.countdown(val)
+    }
+  },
+  created () {
+    this.initAdjustTime()
+  },
+  mounted () {
+    this.countdown(this.endTime)
+    document.addEventListener('visibilitychange', () => {
+      if (document.visibilityState === 'visible') {
+        // console.log('切出当前页')
+        // this.initAdjustTime()
+      }
+    })
+  },
+  methods: {
+    async initAdjustTime () {
+      // 接口响应时服务端的本地时间
+      const { data } = await getServerInitTime()
+      if (data) {
+        this.serverInitTime = data?.time.toString().length === 10 ? data?.time * 1000 : data?.time
+      } else {
+        this.serverInitTime = Date.now()
+      }
+      /**
+       * 浏览器提供了一个performance.timeOrigin,可以大致理解为整个页面加载的初始时间
+       * performance.now接口返回值表示从timeOrigin之后到当前调用时经过的时间,主要用来测试某个函数的执行时间等
+       */
+      this.localInitTime = performance.now()
+    },
+    getCurrentTime () {
+      if (!this.serverInitTime) {
+        console.error('日期校验暂未初始化')
+        return
+      }
+      const localCurrentTime = performance.now()
+      const curTime = Math.floor(this.serverInitTime + (localCurrentTime - this.localInitTime))
+      this.$emit('curTime', curTime)
+      return curTime
+    },
+    async countdown (timestamp) {
+      await this.initAdjustTime()
+      // console.log(this.serverInitTime, timestamp)
+      const timer = setInterval(() => {
+        const nowTime = this.getCurrentTime()
+        const stopTime = timestamp.toString().length === 10 ? timestamp * 1000 : timestamp
+        const t = stopTime - nowTime
+        if (t > 0) {
+          this.showStartText = true
+          const day = Math.floor(t / 86400000)
+          let hour = Math.floor((t / 3600000) % 24)
+          let min = Math.floor((t / 60000) % 60)
+          let sec = Math.floor((t / 1000) % 60)
+          hour = hour < 10 && hour > 0 ? '0' + hour : hour
+          min = min < 10 && min > 0 ? '0' + min : min
+          sec = sec < 10 ? '0' + sec : sec
+          let format = ''
+          if (this.placeholder) {
+            format = `${day} 天 ${hour} 时 ${min} 分 ${sec} 秒`
+          } else {
+            if (day > 0) {
+              format = `${day} 天 ${hour} 时 ${min} 分 ${sec} 秒`
+            }
+            if (day <= 0 && hour > 0) {
+              format = `${hour} 时 ${min} 分 ${sec} 秒`
+            }
+            if (day <= 0 && hour <= 0) {
+              format = `${min} 分 ${sec} 秒`
+            }
+          }
+          this.content = format
+        } else {
+          clearInterval(timer)
+          this.showStartText = false
+          this.content = this.endText
+        }
+      }, 1000)
+    }
+  }
+}
+</script>

+ 331 - 95
src/views/vipsubscribe/Buy.vue

@@ -1,86 +1,91 @@
 <template>
-  <Layout class="vip-subscribe-buy" contentWithState="full" :needAd="false">
-    <div class="vip-subscribe-title">{{ buyTypeText }}超级订阅</div>
-    <div class="vip-subscribe-content">
-      <div class="vip-sub-list">
-        <AreaSelector
-          ref="areaSelector"
-          class="vip-sub-list-item"
-          v-show="moduleShow.areaSelector"
-          :class="{ 'pd-b0': upgradeTipShow }"
-          :showSearchInput="false"
-          :onlyProvince="true"
-          :showSelectResult="true"
-          selectorType="line"
-          @onChange="onAreaChange">
-          <div slot="header" class="vip-sub-item-title">购买区域</div>
-        </AreaSelector>
-        <SelectorCard
-          class="vip-sub-tip"
-          v-if="moduleShow.areaSelector"
-          :cardType="conf.selectorType">
-          <div slot="header" class="vip-sub-item-title"></div>
-          <div class="tip-content font-red" v-show="upgradeTipShow">
-            请增加购买区域进行升级,当前选择省份数量未高于原套餐数,无法升级
-          </div>
-        </SelectorCard>
-        <SelectorCard
-          class="vip-sub-list-item"
-          v-if="moduleShow.specList"
-          :cardType="conf.selectorType">
-          <div slot="header" class="vip-sub-item-title">选择购买周期</div>
-          <div class="vip-sub-item-content">
-            <SpecList :list="specList" v-model="specIdActive" @change="specChange" />
-          </div>
-        </SelectorCard>
-        <SelectorCard
-          class="vip-sub-list-item"
-          v-show="moduleShow.coupon"
-          :cardType="conf.selectorType">
-          <div slot="header" class="vip-sub-item-title">选择优惠券</div>
-          <div class="vip-sub-item-content">
-            <CouponCardList
-              ref="couponRef"
-              :productionId="specActiveItem.productionId"
-              :price="computedPrice.total"
-              @loaded="couponCardLoaded"
-              @change="couponCardChange" />
-          </div>
-        </SelectorCard>
-        <SelectorCard
-          class="vip-sub-list-item"
-          v-show="moduleShow.gift"
-          :cardType="conf.selectorType">
-          <div slot="header" class="vip-sub-item-title">赠品</div>
-          <div class="vip-sub-item-content">
-            <CouponGiftList
-              :productionId="specActiveItem.productionId"
-              @loaded="giftListLoaded" />
-          </div>
-        </SelectorCard>
-        <SelectorCard
-          class="vip-sub-list-item"
-          :cardType="conf.selectorType">
-          <div slot="header" class="vip-sub-item-title">手机号码</div>
-          <div class="vip-sub-item-content">
-            <CheckPhone v-model="userInfo.phone" :pass.sync="phoneRegPass" />
-          </div>
-        </SelectorCard>
+  <div>
+    <LimitedBanner v-if="isActivity" v-bind="activity" :status="activity.status" @sub="onSubscribe"></LimitedBanner>
+    <Layout class="vip-subscribe-buy" contentWithState="full" :needAd="false">
+      <div class="vip-subscribe-title">{{ buyTypeText }}超级订阅</div>
+      <div class="vip-subscribe-content">
+        <div class="vip-sub-list">
+          <AreaSelector
+            ref="areaSelector"
+            class="vip-sub-list-item"
+            :class="{ 'pd-b0': upgradeTipShow }"
+            v-show="moduleShow.areaSelector"
+            :showSearchInput="false"
+            :onlyProvince="true"
+            :showSelectResult="true"
+            selectorType="line"
+            @onChange="onAreaChange">
+            <div slot="header" class="vip-sub-item-title">购买区域</div>
+          </AreaSelector>
+          <SelectorCard
+            class="vip-sub-tip"
+            v-if="moduleShow.areaSelector"
+            :cardType="conf.selectorType">
+            <div slot="header" class="vip-sub-item-title"></div>
+            <div class="tip-content font-red" v-show="upgradeTipShow">
+              请增加购买区域进行升级,当前选择省份数量未高于原套餐数,无法升级
+            </div>
+          </SelectorCard>
+          <SelectorCard
+            class="vip-sub-list-item"
+            v-if="moduleShow.specList"
+            :cardType="conf.selectorType">
+            <div slot="header" class="vip-sub-item-title">选择购买周期</div>
+            <div class="vip-sub-item-content">
+              <SpecList :list="specList" v-model="specIdActive" @change="specChange" />
+            </div>
+          </SelectorCard>
+          <SelectorCard
+            class="vip-sub-list-item"
+            v-show="moduleShow.coupon"
+            :cardType="conf.selectorType">
+            <div slot="header" class="vip-sub-item-title">选择优惠券</div>
+            <div class="vip-sub-item-content">
+              <CouponCardList
+                ref="couponRef"
+                :productionId="specActiveItem.productionId"
+                :price="computedPrice.total"
+                @loaded="couponCardLoaded"
+                @change="couponCardChange" />
+            </div>
+          </SelectorCard>
+          <SelectorCard
+            class="vip-sub-list-item"
+            v-show="moduleShow.gift"
+            :cardType="conf.selectorType">
+            <div slot="header" class="vip-sub-item-title">赠品</div>
+            <div class="vip-sub-item-content">
+              <CouponGiftList
+                :parentProductId="parentProductId"
+                :productionId="specActiveItem.productionId"
+                @loaded="giftListLoaded" />
+            </div>
+          </SelectorCard>
+          <SelectorCard
+            class="vip-sub-list-item"
+            :cardType="conf.selectorType">
+            <div slot="header" class="vip-sub-item-title">手机号码</div>
+            <div class="vip-sub-item-content">
+              <CheckPhone v-model="userInfo.phone" :pass.sync="phoneRegPass" />
+            </div>
+          </SelectorCard>
+        </div>
+        <BuySubmit
+          ref="buySubmitRef"
+          :pass="allPass"
+          :productionTotal="computedPrice.total / 100"
+          :productionDiscount="computedPrice.discount / 100"
+          :productionPay="computedPrice.pay / 100"
+          @submit="submit"
+        ></BuySubmit>
       </div>
-      <BuySubmit
-        :pass="allPass"
-        :productionTotal="computedPrice.total / 100"
-        :productionDiscount="computedPrice.discount / 100"
-        :productionPay="computedPrice.pay / 100"
-        @submit="submit"
-      ></BuySubmit>
-    </div>
-    <div class="vip-subscribe-desc">
-      <Contrast></Contrast>
-    </div>
-    <!-- <div style="height: 800px;background: green"></div>
-    <div class="j-bottom" style="height: 300px;background: red"></div> -->
-  </Layout>
+      <div class="vip-subscribe-desc">
+        <Contrast></Contrast>
+      </div>
+      <!-- <div style="height: 800px;background: green"></div>
+      <div class="j-bottom" style="height: 300px;background: red"></div> -->
+    </Layout>
+  </div>
 </template>
 
 <script>
@@ -93,7 +98,9 @@ import CouponGiftList from '@/components/coupon/CouponGiftList.vue'
 import CheckPhone from '@/components/coupon/CheckPhone.vue'
 import BuySubmit from '@/components/coupon/BuySubmit.vue'
 import Contrast from '@/views/vipsubscribe/components/Contrast.vue'
+import LimitedBanner from '@/components/limited-banner/index.vue'
 import { Dialog } from 'element-ui'
+import { fen2Yuan } from '@/utils/'
 import qs from 'qs'
 
 import {
@@ -101,7 +108,11 @@ import {
   getSelectPrice,
   getSVIPBuyInfo,
   getUserAccountInfo,
-  createCommonOrder
+  createCommonOrder,
+  getGiftList,
+  appointmentAdd,
+  getIsAppointment,
+  getServerInitTime
 } from '@/api/modules/'
 
 export default {
@@ -116,7 +127,8 @@ export default {
     CheckPhone,
     BuySubmit,
     Contrast,
-    [Dialog.name]: Dialog
+    [Dialog.name]: Dialog,
+    LimitedBanner
   },
   data () {
     return {
@@ -132,6 +144,7 @@ export default {
         coupon: false,
         gift: false
       },
+      autoPass: true, // 模拟变量-用于提交订单修改按钮的禁用状态(allPass为计算属性不能直接修改)
       phoneRegPass: false,
       userInfo: {
         phone: ''
@@ -181,12 +194,20 @@ export default {
           tipText: ''
         }
       ],
+      parentProductId: 101,
       token: '', // 用于管理后台创建订单
       computedPrice: {
         total: 0,
         discount: 0,
         pay: 0
-      }
+      },
+      isActivity: false,
+      activity: {},
+      appointmentStatus: -1, // 预约状态
+      curTime: 0,
+      serverInitTime: 0,
+      allActivityData: [], // 所有活动信息
+      timerId: null
     }
   },
   computed: {
@@ -269,7 +290,7 @@ export default {
       }
     },
     allPass () {
-      const basicReg = this.phoneRegPass && !this.noSelect
+      const basicReg = this.phoneRegPass && !this.noSelect && this.autoPass
       if (this.buyType === 'buy') {
         return basicReg
       } else if (this.buyType === 'upgrade') {
@@ -283,11 +304,23 @@ export default {
       } else {
         return false
       }
+    },
+    // 商品规格上是否展示优惠信息、价格是否计算优惠(活动未开始前、库存为0不展示不计算优惠)
+    isDiscounts () {
+      console.log(JSON.stringify(this.activity))
+      return this.activity && this.activity.stockNumber && this.activity.stockNumber > 0
     }
   },
+  destroyed () {
+    clearInterval(this.timerId)
+  },
   async created () {
+    this.initAdjustTime()
     this.getType()
     await this.getUserBuyInfo()
+    try {
+      await this.getAllGiftList()
+    } catch (error) {}
     this.getGoodsPrice()
     if (!this.token) {
       // 有token时候可以不用请求getAccountInfo
@@ -384,7 +417,9 @@ export default {
         this.specIdActive = this.specList[0].id
       } else {
         // 设置城市选择器所有不选中
-        this.$refs.areaSelector.content.setAllNoSelected()
+        if (!this.selectInfo.area) {
+          this.$refs.areaSelector.content.setAllNoSelected()
+        }
         // specIdActive改变会触发specChange
         // 默认选中1年
         this.specIdActive = this.specList[2].id
@@ -492,12 +527,13 @@ export default {
       this.updatePrice()
     },
     specChange (spec) {
+      this.activity.status = this.appointmentStatus
       if (this.buyType === 'upgrade') {
         spec.productionId = 1015
       }
       this.specActiveItem = spec
       this.resetCouponList()
-      this.resetGiftInfo()
+      // this.resetGiftInfo()
       this.updatePrice()
     },
     resetGiftInfo () {
@@ -517,13 +553,25 @@ export default {
       this.couponActiveItem = coupon
       this.updatePrice()
     },
-    giftListLoaded ({ list }) {
-      if (Array.isArray(list) && list.length) {
+    async giftListLoaded ({ list }) {
+      this.activity.reduce = 0
+      this.activity.promotionalPrice = 0
+      this.activity.discount = 0
+      if (Array.isArray(list) && list.length > 0) {
+        const activity = list[0]
+        for (const key in activity) {
+          this.activity[key] = activity[key]
+        }
+        this.isActivity = this.activity.activityType > 2 // 活动类型大于2是秒杀活动
+        this.updatePrice()
         const gift = list[0]
-        this.specActiveItem.tipText = `加赠${gift.giftInfo}`
+        // this.specActiveItem.tipText = gift.giftInfo ? `加赠${gift.giftInfo}` : ''
         this.$set(this.specActiveItem, 'discountId', gift.discountId)
       } else {
-        this.specActiveItem.tipText = ''
+        this.isActivity = false
+        // this.specActiveItem.tipText = ''
+        this.activity = {}
+        this.updatePrice()
         this.$set(this.specActiveItem, 'discountId', '')
       }
     },
@@ -578,12 +626,23 @@ export default {
       })
     },
     async updatePrice () {
+      const { reduce, promotionalPrice, discount, stockNumber } = this.activity
+      // console.log(reduce, promotionalPrice, discount, stockNumber)
       const params = this.getSubmitParam()
       if (this.token) {
         params.token = this.token
       }
       const { errMsg, data, success } = await getSelectPrice(params)
       if (success) {
+        if (stockNumber > 0) {
+          if (reduce) {
+            data.order_price = data.order_price - reduce
+          } else if (promotionalPrice) {
+            data.order_price = promotionalPrice
+          } else if (discount) {
+            data.order_price = data.order_price * (discount / 10)
+          }
+        }
         this.computedPrice.total = data.original_price
         this.computedPrice.pay = data.order_price
         this.computedPrice.discount = data.original_price - data.order_price
@@ -668,6 +727,19 @@ export default {
     async submit () {
       // eslint-disable-next-line
       const { data, error_msg } = await this.submitXHR()
+      let duration = 0
+      if (this.activity && Object.keys(this.activity).length > 0) {
+        const { stockNumber, isReceive } = this.activity
+        if (stockNumber === 0) {
+          this.autoPass = false
+          duration = 2000
+          this.$toast('限时特惠机会已被抢完,需按照原价购买', 3000)
+        } else if (!isReceive) {
+          duration = 2000
+          this.autoPass = false
+          this.$toast('您的特惠购买机会已经用完,需按照原价购买', 3000)
+        }
+      }
       if (data) {
         if (this.token) {
           this.$toast('帮助用户下单成功')
@@ -677,13 +749,177 @@ export default {
           return
         }
         const orderCode = data.order_code
-        if (data.needPay) {
-          window.open(`/front/subvip/orderPay/${orderCode}`)
+        setTimeout(() => {
+          this.autoPass = true
+          if (data.needPay) {
+            window.open(`/front/subvip/orderPay/${orderCode}`)
+          } else {
+            window.open(`/front/subvip/paySuccess/${orderCode}?payTime=${parseInt(Date.now() / 1000)}&from=vipUPgrade`)
+          }
+        }, duration)
+      } else {
+        this.$toast(error_msg)
+      }
+    },
+    async onSubscribe () {
+      const { error_msg: msg, data } = await appointmentAdd({
+        productId: this.activity.productCode,
+        useProductType: 0
+      })
+      if (data) {
+        if (data.status === 1) {
+          this.$toast('预约成功,活动开始前10分钟,将通过站内信再次通知您,请注意查收')
+        } else {
+          this.$toast(msg)
+        }
+        this.appointmentStatus = data.status
+        // this.activity.status = data.status
+        this.$forceUpdate()
+      }
+    },
+    // 查所有规格商品下的特惠信息
+    async getAllGiftList () {
+      var info = {
+        useProduct: this.parentProductId,
+        useProductType: 0
+      }
+      const { data } = await getGiftList(info)
+      if (data && data.length > 0) {
+        await this.getActivityStatus(data[0].activityId)
+        data.forEach(v => {
+          v.beginDate = v.beginDate && v.beginDate.toString().length === 10 ? v.beginDate * 1000 : v.beginDate
+          v.endDate = v.endDate && v.endDate.toString().length === 10 ? v.endDate * 1000 : v.endDate
+          v.preheatingTime = v.preheatingTime && v.preheatingTime.toString().length === 10 ? v.preheatingTime * 1000 : v.preheatingTime
+        })
+        this.allActivityData = data
+      } else {
+        this.allActivityData = []
+        this.specList.forEach(v => {
+          v.tipText = ''
+        })
+      }
+    },
+    sortActivityInfo (item) {
+      if (item.reduce) {
+        return `立减${fen2Yuan(item.reduce)}元`
+      } else if (item.promotionalPrice) {
+        return `特惠价${fen2Yuan(item.promotionalPrice)}元`
+      } else if (item.discount) {
+        return `${item.discount}折`
+      } else {
+        return ''
+      }
+    },
+    sortGiftInfo (gift) {
+      const info = gift
+      let timeType = ''
+      if (info.timeType === 1) {
+        timeType = '天'
+        return `${info.time}${timeType}`
+      } else if (info.timeType === 2) {
+        timeType = '月'
+        return `${info.time}个${timeType}`
+      }
+      return ''
+    },
+    // 获取活动是否已预约
+    async getActivityStatus (id) {
+      if (!id) return
+      const { data } = await getIsAppointment({
+        activityId: id
+      })
+      if (data) {
+        this.appointmentStatus = data?.status
+      } else {
+        this.appointmentStatus = -1
+      }
+    },
+    async initAdjustTime () {
+      // 接口响应时服务端的本地时间
+      const { data } = await getServerInitTime()
+      if (data) {
+        this.serverInitTime = data?.time.toString().length === 10 ? data?.time * 1000 : data?.time
+      } else {
+        this.serverInitTime = Date.now()
+      }
+      this.setIntervalFn()
+    },
+    setIntervalFn () {
+      this.timerId = setInterval(() => {
+        this.serverInitTime = this.serverInitTime + 1000
+        if (this.allActivityData && this.allActivityData.length > 0) {
+          const startTime = this.allActivityData[0].beginDate
+          const endTime = this.allActivityData[0].endDate
+          if (this.serverInitTime < startTime) {
+            // 活动未开始
+            // console.log('活动未开始')
+            this.changGoodsBadge(false)
+            try {
+              this.$refs.buySubmitRef.changeBtnText(false)
+            } catch (error) {}
+          } else if (this.serverInitTime === startTime) {
+            this.updatePrice()
+          } else if (this.serverInitTime >= startTime && this.serverInitTime < endTime) {
+            // 活动开始
+            // console.log('活动开始')
+            this.activity.status = -1
+            this.changGoodsBadge(true)
+            const { stockNumber } = this.activity
+            this.$nextTick(() => {
+              if (stockNumber && stockNumber > 0) {
+                try {
+                  this.$refs.buySubmitRef.changeBtnText(true)
+                } catch (error) {}
+              } else {
+                try {
+                  this.$refs.buySubmitRef.changeBtnText(false)
+                } catch (error) {}
+              }
+            })
+          } else if (this.serverInitTime >= endTime) {
+            // 活动结束
+            this.changGoodsBadge(false)
+            setTimeout(() => {
+              this.isActivity = false
+              this.activity = {}
+              this.getAllGiftList()
+              this.updatePrice()
+              try {
+                this.$refs.buySubmitRef.changeBtnText(false)
+              } catch (error) {}
+            }, 300)
+            clearInterval(this.timerId)
+            this.$forceUpdate()
+          }
         } else {
-          window.open(`/front/subvip/paySuccess/${orderCode}?payTime=${parseInt(Date.now() / 1000)}&from=vipUPgrade`)
+          clearInterval(this.timerId)
         }
+      }, 1000)
+    },
+    changGoodsBadge (isShow) {
+      if (isShow) {
+        const data = this.allActivityData
+        this.specList.forEach(v => {
+          data.forEach(s => {
+            if (!s.stockNumber) {
+              s.stockNumber = 0
+            }
+            if (v.productionId === s.productCode) {
+              if (s.time) {
+                // 赠周期
+                v.tipText = '加赠' + this.sortGiftInfo(s)
+              } else {
+                // 折扣、满减、特惠
+                // 库存大于0限时活动标签
+                v.tipText = s.stockNumber > 0 ? this.sortActivityInfo(s) : ''
+              }
+            }
+          })
+        })
       } else {
-        this.$toast(error_msg)
+        this.specList.forEach(v => {
+          v.tipText = ''
+        })
       }
     }
   }

+ 22 - 7
vue.config.js

@@ -28,8 +28,8 @@ module.exports = {
       //   logLevel: 'debug'
       // },
       '^/bigmember': {
-        target: 'https://jybx2-webtest.jydev.jianyu360.com',
-        // target: 'http://127.0.0.1:814',
+        // target: 'https://jybx2-webtest.jydev.jianyu360.com',
+        target: 'http://192.168.3.240:814',
         changeOrigin: true,
         logLevel: 'debug',
         pathRewrite: {
@@ -37,19 +37,19 @@ module.exports = {
         }
       },
       '^/jypay': {
-        target: 'https://jybx2-webtest.jydev.jianyu360.com',
-        // target: 'http://127.0.0.1:86',
+        // target: 'https://jybx2-webtest.jydev.jianyu360.com',
+        target: 'http://192.168.3.240:86',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/publicapply': {
-        target: 'https://jybx2-webtest.jydev.jianyu360.com',
-        // target: 'http://127.0.0.1:828',
+        // target: 'https://jybx2-webtest.jydev.jianyu360.com',
+        target: 'http://192.168.3.240:828',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/subscribepay': {
-        target: 'https://jybx3-webtest.jydev.jianyu360.com',
+        target: 'https://jybx2-webtest.jydev.jianyu360.com',
         // target: 'http://127.0.0.1:86',
         changeOrigin: true,
         logLevel: 'debug'
@@ -95,6 +95,21 @@ module.exports = {
         // target: 'http://127.0.0.1:829',
         changeOrigin: true,
         logLevel: 'debug'
+      },
+      '^/jyCoupon': {
+        // target: 'https://jybx2-webtest.jydev.jianyu360.com',
+        target: 'http://192.168.3.240:827',
+        changeOrigin: true,
+        logLevel: 'debug'
+      },
+      '^/marketing': {
+        // target: 'https://jybx2-webtest.jydev.jianyu360.com',
+        target: 'http://192.168.3.240:8077',
+        changeOrigin: true,
+        logLevel: 'debug',
+        // pathRewrite: {
+        //   '': '/jyapi'
+        // }
       }
     },
     headers: {