Browse Source

双 12 活动

黄琪勇 3 months ago
parent
commit
22ccc95fbc

BIN
src/common/images/icon_studycard.png


+ 4 - 0
src/constant/index.ts

@@ -47,6 +47,10 @@ export const memberSimpleType = {
   PERPETUAL: '永久'
 }
 
+export const studyCardType = {
+  YEAR: '一年期'
+}
+
 export const courseType = {
   NOT_START: '未开始',
   ING: '进行中',

+ 9 - 0
src/router/routes-student.ts

@@ -151,6 +151,15 @@ export default [
         meta: {
           title: '评测曲目'
         }
+      },
+
+      {
+        path: '/double12Active',
+        component: () =>
+          import('@/student/activePage/double12Active'),
+        meta: {
+          title: '双十二限时特惠'
+        }
       }
     ]
   },

BIN
src/student/activePage/double12Active/imgs/boxImg.png


BIN
src/student/activePage/double12Active/imgs/class.png


BIN
src/student/activePage/double12Active/imgs/head.png


BIN
src/student/activePage/double12Active/imgs/shadow.png


BIN
src/student/activePage/double12Active/imgs/subBtn.png


BIN
src/student/activePage/double12Active/imgs/svipCon1.png


BIN
src/student/activePage/double12Active/imgs/svipCon2.png


BIN
src/student/activePage/double12Active/imgs/tip1Img.png


BIN
src/student/activePage/double12Active/imgs/tip2Img.png


+ 336 - 0
src/student/activePage/double12Active/index.module.less

@@ -0,0 +1,336 @@
+.double12Active {
+  width: 100%;
+  background: linear-gradient(180deg, #fdccbf 0%, #fde0cf 100%);
+  padding: 0 16px 108px;
+  box-sizing: border-box;
+  :global {
+    .van-nav-bar .van-nav-bar__left {
+      color: rgb(44, 62, 80);
+    }
+  }
+  .headImg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 369px;
+    z-index: 1;
+    background: url('./imgs//head.png') no-repeat;
+    background-size: 100% 100%;
+  }
+  .activeArea1 {
+    margin-top: calc(
+      369px - var(--van-nav-bar-height) - var(--navBarHeight) - 18px
+    );
+    background-color: #fff;
+    width: 100%;
+    border-radius: 20px;
+    border: 2px solid #ffffff;
+    filter: blur(0px);
+    position: relative;
+    padding: 12px 0px 24px 20px;
+    box-sizing: border-box;
+    .tip1 {
+      position: absolute;
+      left: 2px;
+      top: -24px;
+      width: 187px;
+      height: 43px;
+      z-index: 1;
+    }
+    .shadowImg {
+      position: absolute;
+      left: 0;
+      top: 0;
+      z-index: 0;
+      width: 100%;
+      height: 60px;
+      border-radius: 20px 20px 0 0;
+    }
+    .activeCon {
+      margin-top: 24px;
+      position: relative;
+      z-index: 2;
+      .activeBox {
+        width: 80px;
+        height: 27px;
+        background: #ffdd71;
+        border-radius: 14px;
+        font-weight: 600;
+        font-size: 15px;
+        color: #000000;
+        line-height: 27px;
+        text-align: center;
+      }
+      .activeTimes {
+        margin-top: 12px;
+        font-weight: 500;
+        font-size: 15px;
+        color: #333333;
+        line-height: 21px;
+      }
+      .activeListCon {
+        box-sizing: border-box;
+        padding-left: 13px;
+        margin-top: 12px;
+        .activeList {
+          position: relative;
+          font-weight: 500;
+          font-size: 15px;
+          color: #333333;
+          line-height: 25px;
+          margin-top: 3px;
+          &:first-child {
+            margin-top: 0;
+          }
+          &::after {
+            content: '';
+            position: absolute;
+            width: 5px;
+            height: 5px;
+            background: linear-gradient(
+              162deg,
+              #ff1e3d 0%,
+              #ff0704 51%,
+              #ff7a4f 100%
+            );
+            opacity: 0.6;
+            left: -13px;
+            top: 50%;
+            transform: translateY(-50%);
+            border-radius: 50%;
+          }
+          & > span {
+            color: #ff1a00;
+          }
+          .number {
+            font-family: 'DIN';
+            font-size: 22px;
+            line-height: 1;
+          }
+        }
+      }
+    }
+    .activeContent {
+      margin-top: 16px;
+      width: calc(100% - 20px);
+      height: 187px;
+      background: url('./imgs/boxImg.png') no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .vipNumber {
+        position: absolute;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        & > :nth-child(1) {
+          font-weight: 600;
+          font-size: 11px;
+          color: #5e2b17;
+          line-height: 16px;
+        }
+        & > :nth-child(2) {
+          font-weight: 500;
+          font-size: 11px;
+          color: rgba(94, 43, 23, 50%);
+          line-height: 16px;
+          text-decoration-line: line-through;
+        }
+        &.vipBuy1 {
+          left: 0;
+          bottom: 60px;
+          width: 33.33%;
+        }
+        &.vipBuy2 {
+          left: 33.33%;
+          bottom: 60px;
+          width: 33.33%;
+          box-sizing: border-box;
+          padding-left: 10px;
+        }
+        &.vipBuy3 {
+          left: 66.66%;
+          bottom: 60px;
+          width: 33.33%;
+          box-sizing: border-box;
+          padding-right: 10px;
+        }
+      }
+      .svipDetails {
+        position: absolute;
+        bottom: 6px;
+        left: 12px;
+        display: flex;
+        align-items: flex-end;
+        line-height: 28px;
+        & > span:nth-child(1) {
+          font-weight: 600;
+          font-size: 18px;
+          color: #ffffff;
+        }
+        & > span:nth-child(2) {
+          margin: 0 4px;
+          font-family: 'DIN';
+          font-size: 30px;
+          color: #ffffff;
+          line-height: 38px;
+        }
+        & > span:nth-child(3) {
+          font-weight: 500;
+          font-size: 12px;
+          color: #ffffff;
+        }
+      }
+    }
+  }
+  .activeArea2 {
+    margin-top: 20px;
+    width: 100%;
+    height: 445px;
+  }
+  .activeArea3 {
+    margin-top: 43px;
+    background-color: #fff;
+    width: 100%;
+    border-radius: 20px;
+    border: 2px solid #ffffff;
+    filter: blur(0px);
+    position: relative;
+    padding: 32px 14px 24px 20px;
+    box-sizing: border-box;
+    .tip1 {
+      position: absolute;
+      left: 2px;
+      top: -24px;
+      width: 107px;
+      height: 43px;
+      z-index: 1;
+    }
+    .shadowImg {
+      position: absolute;
+      left: 0;
+      top: 0;
+      z-index: 0;
+      width: 100%;
+      height: 60px;
+      border-radius: 20px 20px 0 0;
+    }
+    .titCon {
+      position: relative;
+      z-index: 2;
+      font-weight: 500;
+      font-size: 15px;
+      color: #333333;
+      line-height: 26px;
+      > span {
+        color: #ff1a00;
+      }
+    }
+    .classCon {
+      margin-top: 16px;
+      width: calc(100% - 6px);
+      height: 128px;
+      background: url('./imgs/class.png') no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .classBox {
+        position: absolute;
+        left: 50%;
+        transform: translateX(-50%);
+        height: 32px;
+        line-height: 32px;
+        display: flex;
+        align-items: flex-end;
+        font-size: 15px;
+        color: #ffffff;
+        & > span:nth-child(1) {
+          font-size: 22px;
+          font-weight: 500;
+          line-height: 34px;
+        }
+        & > span {
+          color: #fff176;
+        }
+      }
+    }
+  }
+  .activeArea4 {
+    margin-top: 20px;
+    width: 100%;
+    height: 186px;
+  }
+  .subBtnCon {
+    position: fixed;
+    z-index: 999;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 78px;
+    padding-top: 10px;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    background-color: #fff;
+    .subBtn {
+      background: url('./imgs/subBtn.png') no-repeat;
+      background-size: 100% 100%;
+      width: 315px;
+      height: 46px;
+      line-height: 46px;
+      font-size: 18px;
+      color: #ffffff;
+      letter-spacing: 1px;
+      text-align: center;
+      &.disable {
+        pointer-events: none;
+        opacity: 0.4;
+      }
+    }
+  }
+}
+// 弹窗样式
+.dialogContainer {
+  width: 287px;
+  box-sizing: border-box;
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 16px 24px 22px;
+  text-align: center;
+
+  .dialogTitle {
+    font-weight: 500;
+    font-size: 18px;
+    color: #333333;
+    line-height: 25px;
+  }
+
+  .dialogContent {
+    padding: 16px 0 21px;
+    font-size: 15px;
+    color: #777777;
+    line-height: 26px;
+  }
+
+  .dialogBtnGroup {
+    padding: 0 16px;
+
+    &.orderGroup {
+      display: flex;
+      align-items: center;
+      padding: 0;
+    }
+
+    :global {
+      .van-button {
+        font-weight: 500;
+        font-size: 16px;
+      }
+    }
+  }
+
+  .dialogBtn {
+    margin-left: 12px;
+    color: #ffffff;
+    line-height: 22px;
+  }
+}

+ 373 - 0
src/student/activePage/double12Active/index.tsx

@@ -0,0 +1,373 @@
+import { defineComponent, onMounted, reactive, computed } from 'vue'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import { postMessage } from '@/helpers/native-message'
+import request from '@/helpers/request'
+import { useEventListener } from '@vant/use'
+import tip1Img from './imgs/tip1Img.png'
+import tip2Img from './imgs/tip2Img.png'
+import shadowImg from './imgs/shadow.png'
+import svipCon1 from './imgs/svipCon1.png'
+import svipCon2 from './imgs/svipCon2.png'
+import dayjs from 'dayjs'
+import { state as baseState } from '@/state'
+import { Toast, Popup, Button } from 'vant'
+import { useRouter } from 'vue-router'
+import { tradeOrder } from '@/student/trade/tradeOrder'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import { memberType, studyCardType } from '@/constant'
+
+export default defineComponent({
+  name: 'double12Active',
+  setup(props, { emit }) {
+    const router = useRouter()
+    const state = reactive({
+      titleOpacity: 0,
+      navBarHeight: 0,
+      orderVisiable: false,
+      orderDetail: {} as any,
+      activityId: '19'
+    })
+    const activitData = reactive({
+      activityStart: '2024-12-12 00:00:00',
+      activityEnd: '2024-12-12 23:59:59',
+      registrationPrice: 0,
+      buyCount: 0, //  buyCount 小于1的时候 代表能无限购买
+      buyNum: 0,
+      vipCardId: 0,
+      activityList: []
+    })
+    const subBtnState = computed(() => {
+      if (Date.now() < new Date(activitData.activityStart).getTime()) {
+        return {
+          disable: true,
+          text: '活动未开始'
+        }
+      } else if (Date.now() > new Date(activitData.activityEnd).getTime()) {
+        return {
+          disable: true,
+          text: '活动已结束'
+        }
+      } else if (activitData.buyCount <= activitData.buyNum) {
+        return {
+          disable: true,
+          text: '您已参与活动'
+        }
+      }
+      return {
+        disable: false,
+        text: '立即购买'
+      }
+    })
+    const userInfo = computed(() => {
+      return baseState.user.data
+    })
+    // 是否为永久会员
+    const isPermanent = computed(() => {
+      return userInfo.value.userVip?.vipType === 'PERMANENT_SVIP' ? true : false
+    })
+    initActivit()
+    function initActivit() {
+      request
+        .get(
+          `/api-student/memberPriceSettings/getDoubleTwelve/${state.activityId}`
+        )
+        .then(res => {
+          if (res.code === 200 && res.data) {
+            const {
+              activityStart,
+              activityEnd,
+              registrationPrice,
+              buyCount,
+              buyNum,
+              vipCardId,
+              activityRewardList
+            } = res.data || {}
+            activitData.activityStart = activityStart
+            activitData.activityEnd = activityEnd
+            activitData.registrationPrice = registrationPrice
+            activitData.buyCount = buyCount < 1 ? 99999 : buyCount //buyCount 小于1的时候 代表能无限购买
+            activitData.buyNum = buyNum
+            activitData.vipCardId = vipCardId
+            activitData.activityList = (activityRewardList || []).map(item => {
+              const { rewardType, vipCardId, unit } = item.activityReward
+              if (rewardType === 'DISCOUNT') {
+                return {
+                  goodType: rewardType,
+                  goodName: `畅学卡 ${studyCardType[unit]}`,
+                  goodNum: 1,
+                  bizContent: vipCardId,
+                  giftFlag: true,
+                  vipEndDays: null,
+                  goodsNum: 1,
+                  unit
+                }
+              }
+              return {
+                goodType: rewardType,
+                goodName: `小酷Ai SVIP ${memberType[unit]}`,
+                goodNum: 1,
+                bizContent: vipCardId,
+                giftFlag: true,
+                vipEndDays: null,
+                goodsNum: 1,
+                unit
+              }
+            })
+          }
+        })
+        .catch(err => {
+          console.log(err)
+        })
+    }
+    async function handleSubmit() {
+      try {
+        // 永久会员
+        if (isPermanent.value) {
+          Toast('您已是永久SVIP会员')
+          return
+        }
+        const vipType = 'SVIP'
+        // 判断是否有待支付订单
+        const resPadding = await request.post(
+          `/api-student/userOrder/getPendingOrder`,
+          {
+            data: { goodType: vipType }
+          }
+        )
+        console.log(resPadding, 'resPadding')
+        if (resPadding?.data?.id) {
+          state.orderVisiable = true
+          state.orderDetail = resPadding.data || {}
+          return
+        }
+        // 组合订单参数
+        const { data } = await request.post(
+          `/api-student/memberPriceSettings/list`,
+          {
+            data: {
+              status: 1
+            }
+          }
+        )
+
+        const member: any = (data?.list || []).find(item => {
+          return item.id === activitData.vipCardId
+        })
+        if (!member) {
+          Toast('未匹配上活动')
+        }
+        member.title = memberType[member.period]
+        const startTime = dayjs(
+          userInfo.value.userVip.svipEndDate || new Date()
+        ).toDate()
+        const endTime = dayjs(startTime).add(1, 'year').toDate()
+
+        orderStatus.orderObject.orderType = vipType
+        orderStatus.orderObject.orderName = `小酷Ai ${vipType} ${member.title}`
+        orderStatus.orderObject.orderDesc = `小酷Ai ${vipType} ${member.title}`
+        orderStatus.orderObject.actualPrice = activitData.registrationPrice
+        orderStatus.orderObject.recomUserId = ''
+        orderStatus.orderObject.activityId = state.activityId
+        orderStatus.orderObject.orderNo = ''
+        const orderData = {
+          orderType: vipType,
+          goodsName: `小酷Ai ${vipType} ${member.title}`,
+          id: member.id,
+          title: member.title,
+          num: 1, // 购买个数
+          salePrice: member.salePrice,
+          period: member.period,
+          vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
+          svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
+          discount: member.discount, // 是否有折扣
+          discountPrice: member.discountPrice, // 折扣金额
+          price: activitData.registrationPrice,
+          startTime: dayjs(startTime).format('YYYY-MM-DD'),
+          endTime: dayjs(endTime).format('YYYY-MM-DD'),
+          recomUserId: '',
+          activityBuyCount: 0, // 活动购买限制次数
+          activityList: [], // 活动赠送的东西
+          discountEndTime: userInfo.value.discountEndTime, // 畅学卡结束时间
+          discountStartTime: userInfo.value.discountStartTime // 畅学卡开始时间
+        }
+        const canBuyNum = activitData.buyCount - activitData.buyNum
+        if (canBuyNum > 0) {
+          orderData.activityBuyCount = canBuyNum
+          orderData.activityList = activitData.activityList
+        }
+        orderStatus.orderObject.orderList = [orderData]
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: vipType
+          }
+        })
+      } catch {
+        //
+      }
+    }
+    // 取消支付
+    const onCancelOrder = async () => {
+      try {
+        await request.post(`/api-student/userOrder/orderCancel`, {
+          data: { orderNo: state.orderDetail.orderNo }
+        })
+        state.orderVisiable = false
+      } catch {
+        //
+      }
+    }
+    // 继续支付
+    const onContinueOrder = async () => {
+      const orderDetail = state.orderDetail || {}
+      tradeOrder(orderDetail, () => {
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: orderDetail.orderType
+          }
+        })
+      })
+    }
+    onMounted(() => {
+      postMessage({ api: 'getNavHeight' }, res => {
+        const { content } = res as any
+        const dpi = content.dpi || 2
+        if (content.navHeight) {
+          const navHeight = content.navHeight / dpi
+          state.navBarHeight = navHeight
+        }
+      })
+    })
+    useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        window.pageYOffset ||
+        document.documentElement.scrollTop
+      state.titleOpacity = height > 30 ? 1 : 0
+    })
+    return () => (
+      <div
+        class={styles.double12Active}
+        style={{
+          '--navBarHeight': `${state.navBarHeight}px`
+        }}
+      >
+        <ColHeader
+          background={`rgba(255,255,255, ${state.titleOpacity})`}
+          color={`rgba(0,0,0, ${state.titleOpacity})`}
+          backIconColor="black"
+          hideHeader={false}
+          border={false}
+        />
+        <div class={styles.headImg}></div>
+        <div class={styles.activeArea1}>
+          <img class={styles.tip1} src={tip1Img} />
+          <img class={styles.shadowImg} src={shadowImg} />
+          <div class={styles.activeCon}>
+            <div class={styles.activeBox}>活动时间</div>
+            <div class={styles.activeTimes}>
+              {`${dayjs(activitData.activityStart).format(
+                'YYYY年MM月DD日HH:MM'
+              )}-${dayjs(activitData.activityEnd).format(
+                'YYYY年MM月DD日HH:MM'
+              )}`}
+            </div>
+          </div>
+          <div class={styles.activeCon}>
+            <div class={styles.activeBox}>活动内容</div>
+            <div class={styles.activeListCon}>
+              <div class={styles.activeList}>
+                SVIP年度会员限时特惠至
+                <span class={styles.number}>
+                  {' '}
+                  {activitData.registrationPrice}{' '}
+                </span>
+                <span>元</span>
+              </div>
+              <div class={styles.activeList}>
+                活动期间<span>买一年送一年</span>
+              </div>
+              <div class={styles.activeList}>
+                同时额外<span>获赠一年畅学卡</span>,享受约课75折!
+              </div>
+            </div>
+          </div>
+          <div class={styles.activeContent}>
+            <div class={[styles.vipNumber, styles.vipBuy1]}>
+              <div>SVIP年度会员</div>
+              <div>价值1280元</div>
+            </div>{' '}
+            <div class={[styles.vipNumber, styles.vipBuy2]}>
+              <div>SVIP年度会员</div>
+              <div>价值1280元</div>
+            </div>{' '}
+            <div class={[styles.vipNumber, styles.vipBuy3]}>
+              <div>畅学卡一年期</div>
+              <div>价值129元</div>
+            </div>
+            <div class={styles.svipDetails}>
+              <span>¥</span>
+              <span>{activitData.registrationPrice}</span>
+              <span>(SVIP会员买1年赠1年,还送1年畅学)</span>
+            </div>
+          </div>
+        </div>
+        <img class={styles.activeArea2} src={svipCon1} />
+        <div class={styles.activeArea3}>
+          <img class={styles.tip1} src={tip2Img} />
+          <img class={styles.shadowImg} src={shadowImg} />
+          <div class={styles.titCon}>
+            专为音乐学习者设计的全方位学习通行证,畅学卡生效期间,购买任何VIP定制课、趣纠课、直播课、视频课
+            <span>享受75折</span>优惠,助您在音乐道路上更进一步。
+          </div>
+          <div class={styles.classCon}>
+            <div class={styles.classBox}>
+              课程全部<span>75</span>
+              <span>折</span>
+            </div>
+          </div>
+        </div>
+        <img class={styles.activeArea4} src={svipCon2} />
+        <div class={styles.subBtnCon}>
+          <div
+            class={[styles.subBtn, subBtnState.value.disable && styles.disable]}
+            onClick={() => {
+              !subBtnState.value.disable && handleSubmit()
+            }}
+          >
+            {subBtnState.value.text}
+          </div>
+        </div>
+        <Popup
+          v-model:show={state.orderVisiable}
+          style={{ background: 'transparent' }}
+          closeOnClickOverlay={false}
+        >
+          <div class={styles.dialogContainer}>
+            <div class={styles.dialogTitle}>提示</div>
+            <div class={styles.dialogContent}>
+              您有待支付的订单,是否继续支付
+            </div>
+
+            <div class={[styles.dialogBtnGroup, styles.orderGroup]}>
+              <Button round type="default" plain block onClick={onCancelOrder}>
+                取消订单
+              </Button>
+              <Button
+                round
+                type="primary"
+                block
+                class={styles.dialogBtn}
+                onClick={onContinueOrder}
+              >
+                继续支付
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

BIN
src/styles/font/DIN_Alternate_Bold.ttf


+ 4 - 0
src/styles/font/index.less

@@ -0,0 +1,4 @@
+@font-face {
+  font-family: 'DIN';
+  src: url('./DIN_Alternate_Bold.ttf');
+}

+ 1 - 0
src/styles/index.less

@@ -1,4 +1,5 @@
 @import url('./iconfont/iconfont.css');
+@import url('./font/index.less');
 
 :root {
   // Color Palette

+ 26 - 0
src/views/member-center/index.module.less

@@ -223,6 +223,19 @@
   .icon_text {
     position: relative;
     margin-top: 3px;
+    .activitTip{
+      background: linear-gradient( 315deg, #FF1E3D 0%, #FF0704 51%, #FF7A4F 100%);
+      border-radius: 11px 11px 11px 0px;
+      font-weight: 600;
+      font-size: 12px;
+      color: #FFFFFF;
+      line-height: 18px;
+      padding: 1px 6px;
+      white-space: nowrap;
+      position: absolute;
+      left: 18px;
+      top: -25px;
+    }
   }
 
   .bottom_line {
@@ -458,6 +471,19 @@
     background-size: cover;
   }
 
+  .activitTip1{
+    position: absolute;
+    background: linear-gradient( 315deg, #FF3C83 0%, #FF0704 51%, #FF7A4F 100%);
+    border-radius: 6px 6px 6px 0px;
+    font-weight: 600;
+    font-size: 12px;
+    color: #FFFFFF;
+    line-height: 17px;
+    padding: 1px 6px 2px;
+    left: -1px;
+    top: -9px;
+  }
+
   .s_title {
     position: relative;
     font-weight: 500;

+ 153 - 28
src/views/member-center/index.tsx

@@ -23,7 +23,7 @@ import ColSticky from '@/components/col-sticky'
 import { moneyFormat } from '@/helpers/utils'
 import { useRoute, useRouter } from 'vue-router'
 import deepClone from '@/helpers/deep-clone'
-import { memberSimpleType, memberType } from '@/constant'
+import { memberSimpleType, memberType, studyCardType } from '@/constant'
 import dayjs from 'dayjs'
 import { orderStatus } from '../order-detail/orderStatus'
 import TheNoticeBar from '@/components/the-noticeBar'
@@ -58,6 +58,20 @@ export default defineComponent({
       memberShowList: [] // 购买商品信息
     })
 
+    // 活动数据
+    const activitData = reactive({
+      activityId: undefined,
+      activityStart: '',
+      activityEnd: '',
+      registrationPrice: 0,
+      buyCount: 0, //buyCount 小于1的时候 代表能无限购买
+      buyNum: 0,
+      vipCardId: 0,
+      activityList: [],
+      extConfig: {} as Record<string, any>,
+      vipType: 'VIP'
+    })
+
     const userInfo = computed(() => {
       const users = baseState.user.data
       return {
@@ -65,7 +79,9 @@ export default defineComponent({
         phone: users?.phone,
         avatar: users?.heardUrl,
         id: users?.userId,
-        userVip: users?.userVip
+        userVip: users?.userVip,
+        discountEndTime: users?.discountEndTime,
+        discountStartTime: users?.discountStartTime
       }
     })
 
@@ -193,6 +209,10 @@ export default defineComponent({
     }
 
     const calcSalePrice = (item: any) => {
+      // 有活动 以活动价格为准
+      if (item.id === activitData.vipCardId) {
+        return activitData.registrationPrice
+      }
       // discount
       if (item.discount === 1) {
         const tempPrice = Number(
@@ -278,28 +298,36 @@ export default defineComponent({
         orderStatus.orderObject.orderDesc = `小酷Ai ${state.tabActive} ${member.title}`
         orderStatus.orderObject.actualPrice = calcSalePrice(member)
         orderStatus.orderObject.recomUserId = state.recomUserId
-        orderStatus.orderObject.activityId = state.activityId
+        orderStatus.orderObject.activityId = activitData.activityId || state.activityId
         orderStatus.orderObject.orderNo = ''
 
-        orderStatus.orderObject.orderList = [
-          {
-            orderType: state.tabActive,
-            goodsName: `小酷Ai ${state.tabActive} ${member.title}`,
-            id: member.id,
-            title: member.title,
-            num: 1, // 购买个数
-            salePrice: member.salePrice,
-            period: member.period,
-            vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
-            svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
-            discount: member.discount, // 是否有折扣
-            discountPrice: member.discountPrice, // 折扣金额
-            price: calcSalePrice(member),
-            startTime: dayjs(startTime).format('YYYY-MM-DD'),
-            endTime: dayjs(endTime).format('YYYY-MM-DD'),
-            recomUserId: state.recomUserId
-          }
-        ]
+        const orderData = {
+          orderType: state.tabActive,
+          goodsName: `小酷Ai ${state.tabActive} ${member.title}`,
+          id: member.id,
+          title: member.title,
+          num: 1, // 购买个数
+          salePrice: member.salePrice,
+          period: member.period,
+          vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
+          svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
+          discount: member.discount, // 是否有折扣
+          discountPrice: member.discountPrice, // 折扣金额
+          price: calcSalePrice(member),
+          startTime: dayjs(startTime).format('YYYY-MM-DD'),
+          endTime: dayjs(endTime).format('YYYY-MM-DD'),
+          recomUserId: state.recomUserId,
+          activityBuyCount: 0, // 活动购买限制次数
+          activityList: [], // 活动赠送的东西
+          discountEndTime: userInfo.value.discountEndTime, // 畅学卡结束时间
+          discountStartTime: userInfo.value.discountStartTime // 畅学卡开始时间
+        }
+        const canBuyNum = activitData.buyCount - activitData.buyNum
+        if (canBuyNum > 0) {
+          orderData.activityBuyCount = canBuyNum
+          orderData.activityList = activitData.activityList
+        }
+        orderStatus.orderObject.orderList = [orderData]
         router.push({
           path: '/orderDetail',
           query: {
@@ -382,6 +410,70 @@ export default defineComponent({
           }
         )
         const { list, ...more } = data
+
+        // 学生端 活动
+        if (baseState.platformType === 'STUDENT') {
+          const activitRes = await request.post(
+            `api-student/memberPriceSettings/getMemberBuyGift`
+          )
+          if (activitRes.code === 200 && activitRes.data) {
+            const {
+              activityStart,
+              activityEnd,
+              registrationPrice,
+              buyCount,
+              buyNum,
+              vipCardId,
+              activityRewardList,
+              extConfig,
+              id
+            } = activitRes.data || {}
+            // 匹配当前的 会员
+            const vipData = list.find(item => {
+              return item.id === vipCardId
+            })
+            // 当匹配上会员 并且没有达到购买限制的时候才有活动
+            if (vipData && (buyCount < 1 || buyCount > buyNum)) {
+              activitData.activityId = id
+              activitData.vipType = vipData.vipType
+              activitData.activityStart = activityStart
+              activitData.activityEnd = activityEnd
+              activitData.registrationPrice = registrationPrice
+              activitData.buyCount = buyCount < 1 ? 99999 : buyCount //buyCount 小于1的时候 代表能无限购买
+              activitData.buyNum = buyNum
+              activitData.vipCardId = vipCardId
+              extConfig && (activitData.extConfig = JSON.parse(extConfig))
+              activitData.activityList = (activityRewardList || []).map(
+                item => {
+                  const { rewardType, vipCardId, unit } = item.activityReward
+                  if (rewardType === 'DISCOUNT') {
+                    return {
+                      goodType: rewardType,
+                      goodName: `畅学卡 ${studyCardType[unit]}`,
+                      goodNum: 1,
+                      bizContent: vipCardId,
+                      giftFlag: true,
+                      vipEndDays: null,
+                      goodsNum: 1,
+                      unit
+                    }
+                  }
+                  return {
+                    goodType: rewardType,
+                    goodName: `小酷Ai SVIP ${memberType[unit]}`,
+                    goodNum: 1,
+                    bizContent: vipCardId,
+                    giftFlag: true,
+                    vipEndDays: null,
+                    goodsNum: 1,
+                    unit
+                  }
+                }
+              )
+            }
+          }
+        }
+
         state.discountTeacher = {
           ...more
         }
@@ -513,6 +605,15 @@ export default defineComponent({
                       <i class={[styles.icon_member]}></i>
                       <span class={styles.icon_text}>
                         <i class={styles.bottom_line}></i>
+                        {/* 活动显示 */}
+                        {activitData.vipCardId &&
+                          activitData.vipType === 'VIP' &&
+                          activitData.extConfig?.title1 && (
+                            <div class={styles.activitTip}>
+                              {activitData.extConfig?.title1}
+                            </div>
+                          )}
+                        <div class={styles}></div>
                       </span>
                     </div>
                     <div class={styles.vip_member_tip}></div>
@@ -528,6 +629,14 @@ export default defineComponent({
                       <i class={[styles.icon_member]}></i>
                       <span class={styles.icon_text}>
                         <i class={styles.bottom_line}></i>
+                        {/* 活动显示 */}
+                        {activitData.vipCardId &&
+                          activitData.vipType === 'SVIP' &&
+                          activitData.extConfig?.title1 && (
+                            <div class={styles.activitTip}>
+                              {activitData.extConfig?.title1}
+                            </div>
+                          )}
                       </span>
                     </div>
                     <div class={styles.svip_member_tip}></div>
@@ -621,9 +730,17 @@ export default defineComponent({
                               state.selectMember = member
                             }}
                           >
-                            {/* 只有永久才会有数量提示 */}
-                            {member.period === 'PERPETUAL' && (
-                              <span class={[styles.iconPermanent]}></span>
+                            {/* 有活动优先展示活动 */}
+                            {activitData.vipCardId === member.id &&
+                            activitData.extConfig?.title2 ? (
+                              <div class={styles.activitTip1}>
+                                {activitData.extConfig?.title2}
+                              </div>
+                            ) : (
+                              /* 只有永久才会有数量提示 */
+                              member.period === 'PERPETUAL' && (
+                                <span class={[styles.iconPermanent]}></span>
+                              )
                             )}
 
                             <p class={styles.s_title}>
@@ -660,9 +777,17 @@ export default defineComponent({
                         {/* 一条数据的样式 */}
                         {state.memberShowList.map((member: any) => (
                           <div class={[styles['system-item']]}>
-                            {/* 只有永久才会有数量提示 */}
-                            {member.period === 'PERPETUAL' && (
-                              <span class={[styles.iconPermanent]}></span>
+                            {/* 有活动优先展示活动 */}
+                            {activitData.vipCardId === member.id &&
+                            activitData.extConfig?.title2 ? (
+                              <div class={styles.activitTip1}>
+                                {activitData.extConfig?.title2}
+                              </div>
+                            ) : (
+                              /* 只有永久才会有数量提示 */
+                              member.period === 'PERPETUAL' && (
+                                <span class={[styles.iconPermanent]}></span>
+                              )
                             )}
 
                             {member.discount === 1 && (

+ 15 - 1
src/views/order-detail/order-vip/index.module.less

@@ -36,7 +36,21 @@
     font-size: 16px;
   }
 }
-
+.activityClass{
+  justify-content: initial;
+}
+.activityTit{
+  padding-top: 8px;
+  font-weight: 600;
+  font-size: 16px;
+  color: #EF2F56;
+}
+.activityTitNum{
+  margin-top: 8px;
+  font-weight: 400;
+  font-size: 14px;
+  color: #777777;
+}
 .timerTitle {
   display: flex;
   align-items: center;

+ 106 - 3
src/views/order-detail/order-vip/index.tsx

@@ -5,8 +5,9 @@ import styles from './index.module.less'
 
 import iconMember from '@common/images/icon_member.png'
 import iconMemberSvip from '@common/images/icon_member_svip.png'
+import iconStudycard from '@common/images/icon_studycard.png'
 import dayjs from 'dayjs'
-import { memberSimpleType } from '@/constant'
+import { memberSimpleType, studyCardType } from '@/constant'
 // import iconTimer from '@common/images/icon_timer.png'
 // import request from '@/helpers/request'
 
@@ -56,6 +57,51 @@ export default defineComponent({
         return dayjs(item.startTime).add(1, 'day').format('YYYY-MM-DD')
       }
       return item.startTime
+    },
+    // 畅学卡 活动计算
+    studyCardTimes() {
+      const { activityList, discountEndTime } = this.item
+      const studyCardVal = (activityList || []).find(item => {
+        return item.goodType === 'DISCOUNT'
+      })
+      if (studyCardVal) {
+        const startTime = dayjs(discountEndTime || new Date()).toDate()
+        let endTime = new Date()
+        if (studyCardVal.unit === 'MONTH') {
+          endTime = dayjs(startTime)
+            .add(1 * studyCardVal.goodsNum, 'month')
+            .toDate()
+        } else if (studyCardVal.unit === 'QUARTERLY') {
+          endTime = dayjs(startTime)
+            .add(3 * studyCardVal.goodsNum, 'month')
+            .toDate()
+        } else if (studyCardVal.unit === 'YEAR_HALF') {
+          endTime = dayjs(startTime)
+            .add(6 * studyCardVal.goodsNum, 'month')
+            .toDate()
+        } else if (studyCardVal.unit === 'YEAR') {
+          endTime = dayjs(startTime)
+            .add(1 * studyCardVal.goodsNum, 'year')
+            .toDate()
+        }
+        return {
+          startTime: dayjs(startTime).format('YYYY-MM-DD'),
+          endTime: dayjs(endTime).format('YYYY-MM-DD')
+        }
+      } else {
+        return {
+          startTime: null,
+          endTime: null
+        }
+      }
+    }
+  },
+  watch: {
+    'item.num'() {
+      ;(this.item.activityList || []).map(item => {
+        item.goodsNum = this.item.num
+        item.goodNum = this.item.num
+      })
     }
   },
   render() {
@@ -91,7 +137,12 @@ export default defineComponent({
                       v-model={item.num}
                       theme="round"
                       min={1}
-                      max={99}
+                      max={
+                        // 活动会设置能买几份
+                        (item.activityList || []).length > 0
+                          ? item.activityBuyCount
+                          : 99
+                      }
                       onChange={() => {
                         let endTime = new Date()
                         if (item.period === 'MONTH') {
@@ -124,6 +175,40 @@ export default defineComponent({
               )
             }}
           />
+          {(item.activityList || []).map(actItem => {
+            return (
+              <Cell
+                titleClass={[styles.titleClass, styles.activityClass]}
+                v-slots={{
+                  icon: () => (
+                    <Image
+                      class={styles.memberLogo}
+                      src={
+                        actItem.goodType === 'DISCOUNT'
+                          ? iconStudycard
+                          : actItem.goodType === 'SVIP'
+                          ? iconMemberSvip
+                          : iconMember
+                      }
+                    />
+                  ),
+                  title: () => (
+                    <>
+                      <div class={styles.container}>
+                        <div class={styles.title}>
+                          {actItem.goodsName || actItem.goodName}
+                        </div>
+                        <div class={styles.activityTit}>赠送</div>
+                      </div>
+                      <div class={styles.activityTitNum}>
+                        {`x${actItem.goodsNum}`}
+                      </div>
+                    </>
+                  )
+                }}
+              />
+            )
+          })}
         </CellGroup>
 
         {item.orderType === 'SVIP' && item.vipEndDays > 0 && (
@@ -155,7 +240,7 @@ export default defineComponent({
                   <div class={styles.timerCell}>
                     <div class={styles.timerTitle}>
                       {/* <Icon name={iconTimer} size={18} /> */}
-                      <span>生效时间</span>
+                      <span>{item.orderType}会员生效时间</span>
                     </div>
                     <div class={styles.timer}>
                       {this.startTime} 至 {item.endTime}
@@ -164,6 +249,24 @@ export default defineComponent({
                 )
               }}
             />
+            {this.studyCardTimes.startTime && (
+              <Cell
+                center
+                v-slots={{
+                  title: () => (
+                    <div class={styles.timerCell}>
+                      <div class={styles.timerTitle}>
+                        <span>畅学卡生效时间</span>
+                      </div>
+                      <div class={styles.timer}>
+                        {this.studyCardTimes.startTime} 至{' '}
+                        {this.studyCardTimes.endTime}
+                      </div>
+                    </div>
+                  )
+                }}
+              />
+            )}
           </CellGroup>
         )}
       </div>

+ 13 - 5
src/views/order-detail/orderStatus.ts

@@ -72,7 +72,6 @@ export const resestState = () => {
   Object.assign(orderStatus, original())
 }
 
-
 // 购买会员多少数量满足条件
 export const memberNeedNumber = (item: any) => {
   if (item.vipEndDays > 0) {
@@ -94,7 +93,6 @@ export const memberNeedNumber = (item: any) => {
   return 1
 }
 
-
 export const orderInfos = () => {
   // 商品列表
   const orderList = orderStatus.orderObject.orderList || []
@@ -138,7 +136,7 @@ export const orderInfos = () => {
       params.bizContent = item.id
       params.goodsNum = item.num
       params.bizPrice = item.salePrice
-    } else if (item.orderType === "SVIP") {
+    } else if (item.orderType === 'SVIP') {
       params.bizContent = item.id
       const needNumber = memberNeedNumber(item)
       params.vipEndDays = item.num > needNumber ? item.vipEndDays : null
@@ -176,7 +174,8 @@ export const orderInfos = () => {
 export const orderTenantInfos = () => {
   // 商品列表
   const orderList = orderStatus.orderObject.orderList || []
-  return orderList.map((item: any) => {
+  const activityList: any[] = [] // 活动赠品
+  const infos = orderList.map((item: any) => {
     const params: any = {
       goodType: item.orderType,
       goodName: item.goodsName,
@@ -187,12 +186,20 @@ export const orderTenantInfos = () => {
       params.bizContent = item.id
       params.bizId = item.id
       params.goodsNum = item.num
-    } else if (item.orderType === "SVIP") {
+      params.goodNum = item.num
+      ;(item.activityList || []).map(actItem => {
+        activityList.push(actItem)
+      })
+    } else if (item.orderType === 'SVIP') {
       params.bizContent = item.id
       // params.vipEndDays = item.vipEndDays
       const needNumber = memberNeedNumber(item)
       params.vipEndDays = item.num > needNumber ? item.vipEndDays : null
       params.goodsNum = item.num
+      params.goodNum = item.num
+      ;(item.activityList || []).map(actItem => {
+        activityList.push(actItem)
+      })
     } else if (item.orderType === 'MUSIC') {
       params.bizContent = {
         musicSheetId: item.id,
@@ -258,6 +265,7 @@ export const orderTenantInfos = () => {
     }
     return params
   })
+  return [...infos, ...activityList]
 }
 
 /**