shuisheng 1 год назад
Родитель
Сommit
cef768ed06

+ 35 - 0
src/api/giftRecord.ts

@@ -0,0 +1,35 @@
+import { IGiftRecord, IList, IPaging } from '@/interface';
+import request from '@/utils/request';
+
+export function fetchGiftGroupList(params: IList<IGiftRecord>) {
+  return request.get<IPaging<IGiftRecord>>('/gift_record/gift_group_list', {
+    params: {
+      live_room_id: params.live_room_id,
+      status: params.status,
+      nowPage: params.nowPage,
+      pageSize: params.pageSize,
+    },
+  });
+}
+export function fetchGiftRecordList(params: IList<IGiftRecord>) {
+  return request.get<IPaging<IGiftRecord>>('/gift_record/list', {
+    params: {
+      live_room_id: params.live_room_id,
+      status: params.status,
+      nowPage: params.nowPage,
+      pageSize: params.pageSize,
+    },
+  });
+}
+
+export function fetchGiftRecordCreate(data: {
+  goodsId: number;
+  liveRoomId: number;
+  goodsNums: number;
+}) {
+  return request.post('/gift_record/create', {
+    live_room_id: data.liveRoomId,
+    goods_id: data.goodsId,
+    goods_nums: data.goodsNums,
+  });
+}

+ 1 - 5
src/api/order.ts

@@ -3,15 +3,11 @@ import request from '@/utils/request';
 
 /**
  * 开始支付
- * @param total_amount 订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000],金额不能为 0。
- * @param subject 订单标题。注意:不可使用特殊字符,如 /,=,& 等。
- * @param body 订单附加信息。如果请求时传递了该参数,将在异步通知、对账单中原样返回,同时会在商户和用户的pc账单详情中作为交易描述展示
- * @returns
  */
 export function fetchAliPay(data: {
   goodsId: number;
   liveRoomId: number;
-  money?: string;
+  money?: number;
 }) {
   return request.instance({
     url: '/order/pay',

+ 5 - 3
src/api/wallet.ts

@@ -1,9 +1,11 @@
+import { IWallet } from '@/interface';
 import request from '@/utils/request';
 
 export function fetchWalletList(params) {
-  return request.instance({
-    url: '/wallet/list',
-    method: 'get',
+  return request.get('/wallet/list', {
     params,
   });
 }
+export function fetchMyWallet() {
+  return request.get<IWallet>('/wallet/my_wallet');
+}

+ 12 - 5
src/components/Avatar/index.vue

@@ -19,11 +19,18 @@
 </template>
 
 <script lang="ts" setup>
-defineProps({
-  avatar: { type: String, default: '' },
-  size: { type: Number, default: 100 },
-  living: { type: Boolean, default: false },
-});
+withDefaults(
+  defineProps<{
+    avatar: string;
+    size: number;
+    living: boolean;
+  }>(),
+  {
+    avatar: '',
+    size: 100,
+    living: false,
+  }
+);
 </script>
 
 <style lang="scss" scoped>

+ 4 - 12
src/components/Message/index.vue

@@ -1,21 +1,13 @@
 <template>
-  <div>
-    <NMessageProvider>
-      <ContentCpt></ContentCpt>
-    </NMessageProvider>
-  </div>
+  <NMessageProvider :max="3">
+    <ContentCpt></ContentCpt>
+  </NMessageProvider>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
 import { NMessageProvider } from 'naive-ui';
-import { defineComponent } from 'vue';
 
 import ContentCpt from './content/index.vue';
-
-export default defineComponent({
-  components: { NMessageProvider, ContentCpt },
-  setup() {},
-});
 </script>
 
 <style lang="scss" scoped></style>

+ 57 - 34
src/components/QrPay/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="qr-pay-wrap">
-    <div class="money">金额:{{ props.money }}元</div>
+    <div class="money">金额:{{ props.money.toFixed(2) }}元</div>
     <div class="qrcode-wrap">
       <img
         v-if="aliPayBase64 !== ''"
@@ -8,7 +8,12 @@
         :src="aliPayBase64"
         alt=""
       />
-      <template v-if="currentPayStatus !== PayStatusEnum.wait">
+      <template v-if="isExpired">
+        <div class="mask">
+          <div class="txt">二维码已过期</div>
+        </div>
+      </template>
+      <template v-else-if="currentPayStatus !== PayStatusEnum.wait">
         <div class="mask">
           <div class="txt">
             {{
@@ -23,11 +28,25 @@
     <div v-if="aliPayBase64 !== ''">
       <div class="bottom">
         <div class="sao">打开支付宝扫一扫</div>
-        <div class="expr">
+        <div
+          class="expr"
+          v-if="!isExpired"
+        >
           有效期5分钟({{
             formatDownTime({ endTime: downTimeEnd, startTime: downTimeStart })
           }})
         </div>
+        <div
+          class="expr"
+          v-else
+        >
+          <span
+            class="link"
+            @click="handleStartPay"
+          >
+            点击重新获取
+          </span>
+        </div>
       </div>
     </div>
 
@@ -37,13 +56,6 @@
     >
       ps:支付宝标题显示:东圃牛杂档,是正常的~
     </h3>
-
-    <div
-      v-if="payOk"
-      class="bottom"
-    >
-      <h2>支付成功!</h2>
-    </div>
   </div>
 </template>
 
@@ -54,21 +66,31 @@ import { onMounted, onUnmounted, ref } from 'vue';
 
 import { fetchAliPay, fetchAliPayStatus } from '@/api/order';
 import { PayStatusEnum } from '@/interface';
+import { useUserStore } from '@/store/user';
 import { formatDownTime } from '@/utils';
 
-const payOk = ref(false);
+const userStore = useUserStore();
 const aliPayBase64 = ref('');
 const payStatusTimer = ref();
 const downTimer = ref();
 const downTimeStart = ref();
 const downTimeEnd = ref();
+const isExpired = ref(false);
 
 const currentPayStatus = ref(PayStatusEnum.wait);
-const props = defineProps({
-  money: { type: String, default: '0.00' },
-  goodsId: { type: Number, default: -1 },
-  liveRoomId: { type: Number, default: -1 },
-});
+
+const props = withDefaults(
+  defineProps<{
+    money: number;
+    goodsId: number;
+    liveRoomId: number;
+  }>(),
+  {
+    money: 0,
+    goodsId: -1,
+    liveRoomId: -1,
+  }
+);
 
 onUnmounted(() => {
   clearInterval(payStatusTimer.value);
@@ -76,11 +98,7 @@ onUnmounted(() => {
 });
 
 onMounted(() => {
-  startPay({
-    goodsId: props.goodsId,
-    liveRoomId: props.liveRoomId,
-    money: props.money,
-  });
+  handleStartPay();
 });
 
 async function generateQR(text) {
@@ -97,27 +115,28 @@ async function generateQR(text) {
 
 function handleDownTime() {
   clearInterval(downTimer.value);
-  downTimeEnd.value = +new Date() + 1000 * 60 * 5;
-  downTimeStart.value = +new Date();
+  const nowTime = Math.floor(Date.now() / 1000) * 1000;
+  downTimeEnd.value = nowTime + 1000 * 60 * 5;
+  downTimeStart.value = nowTime;
   downTimer.value = setInterval(() => {
-    downTimeStart.value = +new Date();
+    if (downTimeEnd.value - downTimeStart.value <= 0) {
+      clearInterval(downTimer.value);
+      isExpired.value = true;
+    }
+    downTimeStart.value = Math.floor(Date.now() / 1000) * 1000;
   }, 1000);
 }
 
-async function startPay(data: {
-  goodsId: number;
-  liveRoomId: number;
-  money?: string;
-}) {
+async function handleStartPay() {
+  isExpired.value = false;
   currentPayStatus.value = PayStatusEnum.wait;
-  payOk.value = false;
   clearInterval(payStatusTimer.value);
   clearInterval(downTimer.value);
   try {
     const res = await fetchAliPay({
-      money: data.money,
-      goodsId: data.goodsId,
-      liveRoomId: data.liveRoomId,
+      money: props.money * 100,
+      goodsId: props.goodsId,
+      liveRoomId: props.liveRoomId,
     });
     if (res.code === 200) {
       if (isMobile()) {
@@ -149,8 +168,8 @@ function getPayStatus(outTradeNo: string) {
         currentPayStatus.value = PayStatusEnum.TRADE_SUCCESS;
         clearInterval(downTimer.value);
         clearInterval(payStatusTimer.value);
+        userStore.updateMyWallet();
         console.log('支付成功!');
-        payOk.value = true;
       }
     } catch (error) {
       console.log(error);
@@ -197,6 +216,10 @@ function getPayStatus(outTradeNo: string) {
     width: 100%;
     text-align: center;
     font-size: 14px;
+    .link {
+      cursor: pointer;
+      color: $theme-color-gold;
+    }
   }
 }
 </style>

+ 33 - 3
src/interface.ts

@@ -106,6 +106,36 @@ export interface IRedbagRecv {
   deleted_at?: string;
 }
 
+export enum GiftRecordIsRecvEnum {
+  yew,
+  no,
+}
+
+export enum GiftRecordStatusEnum {
+  ok,
+  balanceError,
+}
+
+export interface IGiftRecord {
+  id?: number;
+  is_recv?: GiftRecordIsRecvEnum;
+  goods_id?: number;
+  goods_nums?: number;
+  goods_snapshot?: string;
+  order_id?: number;
+  live_room_id?: number;
+  send_user_id?: number;
+  recv_user_id?: number;
+  status?: GiftRecordStatusEnum;
+  remark?: string;
+
+  goods?: IGoods;
+
+  created_at?: string;
+  updated_at?: string;
+  deleted_at?: string;
+}
+
 export enum LiveLineEnum {
   rtc = 'rtc',
   hls = 'hls',
@@ -166,7 +196,7 @@ export enum RankTypeEnum {
 export interface IWallet {
   id?: number;
   user_id?: number;
-  balance?: string;
+  balance?: number;
   created_at?: string;
   updated_at?: string;
   deleted_at?: string;
@@ -276,8 +306,8 @@ export interface IGoods {
   desc?: string;
   short_desc?: string;
   cover?: string;
-  price?: string;
-  original_price?: string;
+  price?: number;
+  original_price?: number;
   nums?: number;
   badge?: string;
   badge_bg?: string;

+ 9 - 0
src/store/user/index.ts

@@ -1,6 +1,7 @@
 import { defineStore } from 'pinia';
 
 import { fetchLogin, fetchUserInfo } from '@/api/user';
+import { fetchMyWallet } from '@/api/wallet';
 import { IAuth, IRole } from '@/interface';
 import { IUser } from '@/types/IUser';
 import cache from '@/utils/cache';
@@ -54,6 +55,14 @@ export const useUserStore = defineStore('user', {
         return null;
       }
     },
+    async updateMyWallet() {
+      const res = await fetchMyWallet();
+      if (res.code === 200) {
+        if (this.userInfo?.wallet?.balance) {
+          this.userInfo.wallet.balance = res.data.balance;
+        }
+      }
+    },
     async getUserInfo() {
       try {
         const { code, data } = await fetchUserInfo();

+ 7 - 0
src/utils/index.ts

@@ -2,6 +2,13 @@
 import { computeBox, getRangeRandom } from 'billd-utils';
 import sparkMD5 from 'spark-md5';
 
+export function formatMoney(money?: number) {
+  if (!money) {
+    return '0.00';
+  }
+  return (money / 100).toFixed(2);
+}
+
 export const formatTimeHour = (timestamp: number) => {
   function addZero(num: number) {
     return num < 10 ? `0${num}` : num;

+ 140 - 18
src/views/pull/index.vue

@@ -52,8 +52,9 @@
                     query: { id: appStore.liveRoomInfo?.areas?.[0].id },
                   })
                 "
-                >{{ appStore.liveRoomInfo?.areas?.[0].name }}</span
               >
+                {{ appStore.liveRoomInfo?.areas?.[0].name }}
+              </span>
             </div>
           </div>
         </div>
@@ -61,7 +62,33 @@
           class="other"
           @click="handlePk"
         >
-          在线人数:{{ liveUserList.length }}
+          <div class="top">在线人数:{{ liveUserList.length }}</div>
+          <div class="bottom">
+            <n-popover
+              placement="bottom"
+              trigger="hover"
+            >
+              <template #trigger>
+                <div class="tag">收到礼物</div>
+              </template>
+              <div class="popover-list">
+                <template v-if="giftGroupList.length">
+                  <div
+                    class="item"
+                    v-for="(item, index) in giftGroupList"
+                    :key="index"
+                  >
+                    <div
+                      class="ico"
+                      :style="{ backgroundImage: `url(${item.goods?.cover})` }"
+                    ></div>
+                    <div class="nums">x{{ item.nums }}</div>
+                  </div>
+                </template>
+                <span v-else>暂未收到礼物</span>
+              </div>
+            </n-popover>
+          </div>
         </div>
       </div>
       <div
@@ -108,7 +135,7 @@
           v-for="(item, index) in giftGoodsList"
           :key="index"
           class="item"
-          @click="handlePay()"
+          @click="handlePay({ goodsId: item.id })"
         >
           <div
             class="ico"
@@ -123,7 +150,7 @@
             </div>
           </div>
           <div class="name">{{ item.name }}</div>
-          <div class="price">¥{{ item.price }}</div>
+          <div class="price">¥{{ formatMoney(item.price) }}</div>
         </div>
         <div
           v-if="MODULE_CONFIG_SWITCH.pullGiftList"
@@ -131,7 +158,9 @@
           @click="handleRecharge"
         >
           <div class="ico wallet"></div>
-          <div class="name">余额:{{ userStore.userInfo?.wallet?.balance }}</div>
+          <div class="name">
+            余额:{{ formatMoney(userStore.userInfo?.wallet?.balance) }}元
+          </div>
           <div class="price">立即充值</div>
         </div>
       </div>
@@ -354,6 +383,11 @@ import { getRandomString, openToTarget } from 'billd-utils';
 import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
+import {
+  fetchGiftGroupList,
+  fetchGiftRecordCreate,
+  fetchGiftRecordList,
+} from '@/api/giftRecord';
 import { fetchGoodsList } from '@/api/goods';
 import { fetchGetWsMessageList } from '@/api/wsMessage';
 import { MODULE_CONFIG_SWITCH, QINIU_LIVE } from '@/constant';
@@ -363,7 +397,9 @@ import { usePull } from '@/hooks/use-pull';
 import { useUpload } from '@/hooks/use-upload';
 import {
   DanmuMsgTypeEnum,
+  GiftRecordStatusEnum,
   GoodsTypeEnum,
+  IGiftRecord,
   IGoods,
   WsMessageMsgIsFileEnum,
   WsMessageMsgIsShowEnum,
@@ -374,7 +410,7 @@ import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
 import { WsDisableSpeakingType, WsMsgTypeEnum } from '@/types/websocket';
-import { formatTimeHour } from '@/utils';
+import { formatMoney, formatTimeHour } from '@/utils';
 import { NODE_ENV } from 'script/constant';
 
 import RechargeCpt from './recharge/index.vue';
@@ -387,6 +423,8 @@ const roomId = ref(route.params.roomId as string);
 const configBg = ref();
 const configVideo = ref();
 const giftGoodsList = ref<IGoods[]>([]);
+const giftRecordList = ref<IGiftRecord[]>([]);
+const giftGroupList = ref<Array<IGiftRecord & { nums: number }>>([]);
 const height = ref(0);
 const giftLoading = ref(false);
 const showRecharge = ref(false);
@@ -426,9 +464,7 @@ onMounted(() => {
   }, 100);
   handleHistoryMsg();
   appStore.setPlay(true);
-  if (MODULE_CONFIG_SWITCH.pullGiftList) {
-    getGoodsList();
-  }
+  getGoodsList();
   if (topRef.value && bottomRef.value && containerRef.value) {
     const res =
       bottomRef.value.getBoundingClientRect().top -
@@ -438,6 +474,8 @@ onMounted(() => {
   }
   getBg();
   initPull();
+  getGiftRecord();
+  getGiftGroupList();
 });
 
 onUnmounted(() => {
@@ -445,6 +483,35 @@ onUnmounted(() => {
   closeRtc();
 });
 
+async function getGiftGroupList() {
+  const res = await fetchGiftGroupList({
+    live_room_id: Number(roomId.value),
+    status: GiftRecordStatusEnum.ok,
+  });
+  if (res.code === 200) {
+    // @ts-ignore
+    giftGroupList.value = res.data.rows.map((item) => {
+      try {
+        const json = JSON.parse(item.goods_snapshot!);
+        item.goods = json;
+      } catch (error) {
+        console.log(error);
+      }
+      return item;
+    });
+  }
+}
+
+async function getGiftRecord() {
+  const res = await fetchGiftRecordList({
+    live_room_id: Number(roomId.value),
+    status: GiftRecordStatusEnum.ok,
+  });
+  if (res.code === 200) {
+    giftRecordList.value = res.data.rows;
+  }
+}
+
 async function handleHistoryMsg() {
   try {
     const res = await fetchGetWsMessageList({
@@ -671,12 +738,17 @@ async function uploadChange() {
   }
 }
 
-function handlePay() {
-  if (!MODULE_CONFIG_SWITCH.pay) {
-    window.$message.info('敬请期待!');
-    return;
+async function handlePay({ goodsId }) {
+  const res = await fetchGiftRecordCreate({
+    goodsId,
+    goodsNums: 1,
+    liveRoomId: Number(roomId.value),
+  });
+  if (res.code === 200) {
+    window.$message.success('打赏成功!');
   }
-  window.$message.info('敬请期待!');
+  userStore.updateMyWallet();
+  getGiftGroupList();
 }
 
 function handleRefresh() {
@@ -720,6 +792,37 @@ function handleScrollTop() {
 </script>
 
 <style lang="scss" scoped>
+.popover-list {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  width: 140px;
+  .item {
+    margin-top: 10px;
+    margin-right: 10px;
+    text-align: center;
+    &:nth-child(1),
+    &:nth-child(2),
+    &:nth-child(3) {
+      margin-top: 5px;
+    }
+    &:nth-child(3n) {
+      margin-right: 0px;
+    }
+    .ico {
+      position: relative;
+      width: 40px;
+      height: 40px;
+      background-position: center center;
+      background-size: cover;
+      background-repeat: no-repeat;
+    }
+    .nums {
+      margin-top: 5px;
+      color: #18191c;
+    }
+  }
+}
 .pull-wrap {
   display: flex;
   justify-content: space-around;
@@ -773,7 +876,10 @@ function handleScrollTop() {
     .head {
       display: flex;
       justify-content: space-between;
+      box-sizing: border-box;
       padding: 10px 20px;
+      height: 70px;
+      color: #18191c;
 
       .info {
         display: flex;
@@ -792,7 +898,6 @@ function handleScrollTop() {
         .detail {
           .top {
             margin-bottom: 10px;
-            color: #18191c;
           }
           .bottom {
             font-size: 14px;
@@ -809,6 +914,22 @@ function handleScrollTop() {
         flex-direction: column;
         justify-content: center;
         font-size: 14px;
+        .top {
+          margin-bottom: 10px;
+        }
+        .bottom {
+          font-size: 12px;
+          .tag {
+            display: inline-block;
+            padding: 4px 10px;
+            border-radius: 10px;
+            background-color: $theme-color-gold;
+            color: white;
+            text-align: center;
+            line-height: 1;
+            cursor: pointer;
+          }
+        }
       }
     }
     .container {
@@ -1078,17 +1199,18 @@ function handleScrollTop() {
         cursor: no-drop;
 
         .bg {
-          @extend %maskBg;
           position: absolute !important;
+
+          @extend %maskBg;
         }
         .txt {
           position: absolute;
           top: 50%;
           left: 50%;
-          transform: translate(-50%, -50%);
-          font-size: 14px;
           width: 100%;
           text-align: center;
+          font-size: 14px;
+          transform: translate(-50%, -50%);
         }
       }
       .control {

+ 32 - 17
src/views/pull/recharge/index.vue

@@ -8,27 +8,32 @@
       class="container"
       @update:show="handleOnClose"
     >
-      <div>
-        充值金额(最低充值{{ minMoney }}元,最高充值{{ maxMoney }}元)
+      <n-input-group>
+        <n-input-group-label>
+          金额({{ minMoney }}-{{ maxMoney }}元)
+        </n-input-group-label>
         <n-input-number
           v-model:value="money"
           :precision="2"
           :min="minMoney"
           :max="maxMoney"
+          :placeholder="'请输入金额'"
           clearable
         >
           <template #prefix>¥</template>
+          <template #suffix>元</template>
         </n-input-number>
-      </div>
-      <n-button
-        type="primary"
-        @click="startPay"
-      >
-        确定充值
-      </n-button>
+        <n-button
+          type="primary"
+          @click="startPay"
+        >
+          充值
+        </n-button>
+      </n-input-group>
+
       <QrPayCpt
         v-if="showQrPay"
-        :money="goodsInfo.money"
+        :money="money"
         :goods-id="goodsInfo.goodsId"
         :live-room-id="goodsInfo.liveRoomId"
       ></QrPayCpt>
@@ -46,18 +51,22 @@ import { GoodsTypeEnum } from '@/interface';
 const showModal = ref(false);
 const maxMoney = 200;
 const minMoney = 0.1;
-const money = ref(minMoney);
+const money = ref(1);
 
 const showQrPay = ref(false);
 const goodsInfo = reactive({
-  money: '0.00',
   goodsId: -1,
   liveRoomId: -1,
 });
 
-const props = defineProps({
-  show: { type: Boolean, default: false },
-});
+const props = withDefaults(
+  defineProps<{
+    show: boolean;
+  }>(),
+  {
+    show: false,
+  }
+);
 
 const emits = defineEmits(['close']);
 
@@ -82,7 +91,6 @@ async function startPay() {
   if (res.code === 200) {
     showQrPay.value = false;
     nextTick(() => {
-      goodsInfo.money = `${money.value}`;
       goodsInfo.goodsId = res.data.id!;
       showQrPay.value = true;
     });
@@ -90,4 +98,11 @@ async function startPay() {
 }
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.recharge-wrap {
+  .title {
+    display: flex;
+    align-items: center;
+  }
+}
+</style>