Browse Source

update 赠送礼物

warrior 3 months ago
parent
commit
8732abde97

BIN
projects/live-app/public/img/credit.png


+ 1 - 2
projects/live-app/src/app/components/live/live.component.ts

@@ -59,8 +59,7 @@ export class LiveComponent implements OnInit {
     // queryProfile.notEqualTo('isDeleted', true);
     // this.profile = await queryProfile.first();
     if (this.room?.id) {
-      await this.liveService.getToken(this.room);
-      // this.liveService.join();
+      // await this.liveService.getToken(this.room);
     } else {
       const alert = await this.alertController.create({
         header: '提示',

+ 90 - 3
projects/live-app/src/moduls/live/link-page/link-page.component.html

@@ -29,12 +29,18 @@
     (click)="showTool = !showTool"
     [style.visibility]="showTool ? 'visible' : 'hidden'"
   >
-    <div class="row" style="justify-content: center;">
-      <div class="tips">剩余通话时长 <span>{{ liveService.countdown | secondsToTime }}</span></div>
+    <div class="row" style="justify-content: center">
+      <div class="tips">
+        剩余通话时长
+        <span
+          [style.color]="liveService.countdown > 120 ? '#ffc409' : '#ff2636'"
+          >{{ liveService.countdown | secondsToTime }}</span
+        >
+      </div>
     </div>
     @if (!liveService.isAnchor) {
     <div class="row">
-      <div class="row-li">
+      <div class="row-li" (click)="onChangeLiveStatus($event, 'gift')">
         <div class="icon-box">
           <ion-icon class="icon" name="gift"></ion-icon>
         </div>
@@ -97,4 +103,85 @@
       </div>
     </div>
   </div>
+  <ion-modal
+    #modal
+    [isOpen]="showGiftModal"
+    [initialBreakpoint]="1"
+    [breakpoints]="[0, 1]"
+    (didDismiss)="showGiftModal = false"
+    class="gift-modal"
+  >
+    <ng-template>
+      <div class="gift-region">
+        <ion-toolbar>
+          <div class="gift-tabs">
+            @for (item of tabs; track $index) {
+            <div
+              [style.color]="activeTab == item.val ? '#000' : '#6b6b6b'"
+              class="tab"
+              (click)="selectTab(item.val)"
+            >
+              {{ item.name }}
+            </div>
+            }
+          </div>
+        </ion-toolbar>
+        <div class="gift-content">
+          <div class="gift-list">
+            @for (item of giftList; track $index) {
+            <div
+              [ngClass]="{
+                'gift-item-acitve': currentGift?.id == item?.id,
+                'gift-item': true
+              }"
+              (click)="currentGift = item"
+            >
+              <div class="img">
+                <img [src]="item.imgUrl" alt="" />
+              </div>
+              <div class="info">
+                <div class="name">{{ item.name }}</div>
+                <div class="price">
+                  <img class="credit" src="img/credit.png" alt="" />{{
+                    item.price
+                  }}
+                </div>
+              </div>
+            </div>
+            }
+          </div>
+        </div>
+        <div class="gift-footer">
+          <div class="left">
+            <img src="img/credit.png" alt="" class="credit" />
+            <span class="credit-num">{{ wallet.balance }}</span>
+          </div>
+          <div class="btns">
+            @if (currentGift?.id) {
+            <div class="chang-gift">
+              {{ currentGift?.name }} {{ currentGift?.price }}/个
+            </div>
+            }
+            <input
+              [(ngModel)]="giftCount" 
+              min="1"
+              type="number"
+              class="input-num"
+              (blur)="onChange()"
+              [disabled]="!currentGift?.id"
+            />
+            <div class="btn-item" (click)="sendGift()">
+              <span>赠送</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </ng-template>
+  </ion-modal>
 </ion-content>
+@if (isShowGiftModal) {
+  <div class="modal-gift-img">
+    <img [src]="currentGift?.imgUrl" alt="" />
+  </div>
+}
+

+ 172 - 22
projects/live-app/src/moduls/live/link-page/link-page.component.scss

@@ -1,7 +1,7 @@
 .content {
   // --padding-bottom: 50px;
   --background: #fff;
-  .nav-data{
+  .nav-data {
     position: fixed;
     top: 20px;
     display: flex;
@@ -11,7 +11,7 @@
     padding: 10px;
     color: white;
     z-index: 1;
-    .room-data{
+    .room-data {
       background-color: rgb(0 0 0 / 40%);
       display: flex;
       width: 160px;
@@ -20,21 +20,21 @@
       border-radius: 40px;
       padding: 4px;
       border: 1px solid rgb(255 255 255 / 30%);
-      .avatar{
+      .avatar {
         width: 36px;
         height: 36px;
       }
-      .profile-title{
+      .profile-title {
         display: flex;
         flex-direction: column;
         align-items: start;
         justify-content: space-around;
         margin-left: 4px;
         flex: 1;
-        .profile-name{
+        .profile-name {
           font-size: 14px;
         }
-        .level{
+        .level {
           background-color: #cc59de;
           text-align: center;
           font-size: 12px;
@@ -42,12 +42,13 @@
           padding: 0px 6px;
         }
       }
-      ion-icon{
+      ion-icon {
         margin-left: 10px;
         font-size: 30px;
       }
     }
-    .report,.exit{
+    .report,
+    .exit {
       background-color: rgb(0 0 0 / 40%);
       color: white;
       border-radius: 50%;
@@ -57,22 +58,22 @@
       align-items: center;
       justify-content: center;
       border: 1px solid rgb(255 255 255 / 30%);
-      ion-icon{
+      ion-icon {
         font-size: 26px;
       }
     }
   }
-  .tools{
+  .tools {
     position: fixed;
     bottom: 0;
     left: 0;
     width: 100%;
     // height: 260px;
     padding: 10px;
-    .row{
+    .row {
       display: flex;
       justify-content: space-between;
-      .row-li{
+      .row-li {
         display: flex;
         justify-content: center;
         align-items: center;
@@ -81,11 +82,11 @@
         color: #fff;
         width: 100px;
         margin-bottom: 6px;
-        .label{
+        .label {
           margin-top: 6px;
           font-size: 14px;
         }
-        .icon-box{
+        .icon-box {
           background-color: rgba(0, 0, 0, 0.4);
           // border: 1px solid rgba(255, 255, 255, 0.3);
           border-radius: 50%;
@@ -93,15 +94,15 @@
           display: flex;
           align-items: center;
           justify-content: center;
-          ion-icon{
+          ion-icon {
             color: white;
             font-size: 30px;
           }
-          .icon-tool{
+          .icon-tool {
             width: 30px;
             height: 30px;
           }
-          img{
+          img {
             width: 50px;
             height: 50px;
             background: white;
@@ -109,23 +110,172 @@
             overflow: hidden;
           }
         }
-        .action-icon{
+        .action-icon {
           background-color: #ffffff;
-          ion-icon{
+          ion-icon {
             color: black !important;
           }
         }
       }
-      .tips{
+      .tips {
         background-color: rgba(0, 0, 0, 0.4);
         border-radius: 20px;
         color: white;
         padding: 6px;
         margin-bottom: 10px;
-        span{
+        span {
           color: #ffc409;
         }
       }
     }
   }
-}
+}
+.gift-modal {
+  --height: 400px;
+}
+.gift-region {
+  position: relative;
+  .gift-tabs {
+    display: flex;
+    font-size: 14px;
+    font-weight: bold;
+    padding: 0 10px;
+    .tab {
+      margin-right: 10px;
+    }
+  }
+  .gift-content {
+    overflow-y: scroll;
+    padding-bottom: 100px;
+    height: 356px;
+    .gift-list {
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      grid-gap: 10px;
+      padding: 6px;
+      .gift-item {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        border-radius: 10px;
+        border: 1px solid #fff;
+        font-size: 14px;
+        .img {
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+        }
+        .info {
+          text-align: center;
+          .name {
+            margin: 6px 0;
+          }
+          .price {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: #6b6b6b;
+            font-family: PingFang SC;
+            font-size: 12px;
+            .credit {
+              width: 10px;
+              height: 10px;
+            }
+          }
+        }
+      }
+      .gift-item-acitve {
+        border: 1px solid #fc3651 !important;
+      }
+    }
+  }
+  .gift-footer {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 80px;
+    padding: 20px;
+    padding-bottom: 20px;
+    background: linear-gradient(
+      rgb(255 255 255 / 31%),
+      rgb(255 255 255 / 86%),
+      #ffffff,
+      #fdfdfd
+    );
+    align-items: flex-end;
+    .left {
+      display: flex;
+      align-items: center;
+      font-size: 12px;
+      .credit {
+        width: 10px;
+        height: 10px;
+        margin-right: 6px;
+      }
+    }
+    .btns {
+      display: flex;
+      align-items: center;
+      .chang-gift {
+        font-size: 14px;
+      }
+      .input-num {
+        width: 50px;
+        height: 24px;
+        margin-right: 10px;
+        border-radius: 6px;
+        border: 1px solid #dcdcdc;
+      }
+      .btn-item {
+        background-color: #fc3651;
+        color: white;
+        padding: 4px 16px;
+        border-radius: 16px;
+        font-size: 14px;
+      }
+    }
+  }
+}
+.modal-gift-img {
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #000000b0;
+  img{
+    animation: scaleUp 5s ease-in-out forwards; /* 使用scaleUp动画,持续3秒 */
+    position: absolute;
+    bottom: 0;
+  }
+}
+@keyframes scaleUp {
+  0% {
+    transform: scale(0.1); /* 从0.1倍大小开始 */
+  }
+  50%{
+    transform: scale(0.1);
+    bottom: 400px;
+  }
+  60% {
+    transform: scale(0.8);
+    bottom: 400px;
+  }
+  70% {
+    transform: scale(0.2);
+    bottom: 400px;
+  }
+  90%{
+    transform: scale(1.2); /* 放大到原始大小 */
+    bottom: 400px;
+  }
+  100% {
+    transform: scale(1.2); /* 放大到原始大小 */
+    bottom: 400px;
+  }
+}

+ 75 - 5
projects/live-app/src/moduls/live/link-page/link-page.component.ts

@@ -13,12 +13,19 @@ import { ActivatedRoute } from '@angular/router';
 import { LiveComponent } from '../../../app/components/live/live.component';
 import { LiveService } from '../../../services/live.service';
 import { SharedModule } from '../../shared.module';
+import { AiChatService } from '../../../services/aichart.service';
 @Component({
   selector: 'app-link-page',
   templateUrl: './link-page.component.html',
   styleUrls: ['./link-page.component.scss'],
   standalone: true,
-  imports: [IonicModule, FormsModule, LiveComponent, CommonModule,SharedModule],
+  imports: [
+    IonicModule,
+    FormsModule,
+    LiveComponent,
+    CommonModule,
+    SharedModule,
+  ],
 })
 export class LinkPageComponent implements OnInit {
   @ViewChild('live') live!: LiveComponent;
@@ -27,11 +34,33 @@ export class LinkPageComponent implements OnInit {
   room?: Parse.Object;
   isFollow: boolean = false;
   showTool: boolean = true; // 是否显示工具栏
+  showGiftModal: boolean = false; // 是否显示礼物弹窗
+  giftList: Array<any> = [];
+  tabs: Array<any> = [
+    {
+      name: '礼物',
+      val: 'all',
+    },
+    {
+      name: '专属礼物',
+      val: 'vip',
+    },
+    {
+      name: '守护',
+      val: 'guard',
+    },
+  ];
+  activeTab: string = 'all';
+  currentGift: any; //当前礼物
+  giftCount: number = 1;
+  wallet: any = { balance: 0 };
+  isShowGiftModal: boolean = false; // 是否显示礼物动效弹窗
   constructor(
     public toastController: ToastController,
     private loadingCtrl: LoadingController,
     private alertController: AlertController,
     private activateRoute: ActivatedRoute,
+    private aiServ: AiChatService,
     public liveService: LiveService
   ) {}
 
@@ -51,6 +80,8 @@ export class LinkPageComponent implements OnInit {
       message: '加载中',
     });
     this.getRoom();
+    this.giftList = await this.aiServ.getGift();
+    console.log(this.giftList);
     loading.present();
     loading.dismiss();
   }
@@ -72,6 +103,12 @@ export class LinkPageComponent implements OnInit {
     let r = await query.first();
     this.isFollow = r?.id ? true : false;
   }
+  async selectTab(val: string) {
+    if (this.activeTab === val) return;
+    this.giftList = await this.aiServ.getGift(val == 'all' ? '' : val);
+    this.activeTab = val;
+    this.currentGift = null;
+  }
   /* 关注 */
   async onCollection() {
     let query = new Parse.Query('ProfileRadar');
@@ -101,7 +138,7 @@ export class LinkPageComponent implements OnInit {
     this.isFollow = !this.isFollow;
   }
   /* 直播状态 */
-  onChangeLiveStatus(e: any, type: string) {
+  async onChangeLiveStatus(e: any, type: string) {
     e.cancelBubble = true;
     switch (type) {
       case 'audio':
@@ -113,10 +150,43 @@ export class LinkPageComponent implements OnInit {
       case 'mute':
         this.liveService.muteAudio();
         break;
+      case 'gift':
+        let uid: any = Parse.User.current()?.id;
+        this.wallet = await this.aiServ.getWallet(uid);
+        this.showGiftModal = true;
+        break;
     }
-    // if (type == 'audio' || type == 'camera' || type == 'mute') {
-    //   this.liveService.tools[type] = !this.liveService.tools[type];
-    // }
+  }
+  async sendGift() {
+    if (!this.currentGift?.id || this.giftCount < 1) return;
+    const alert = await this.alertController.create({
+      cssClass: 'my-custom-class',
+      header: '送出礼物',
+      message: `确定送出${this.giftCount}件${this.currentGift.name}吗`,
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel',
+          cssClass: 'secondary',
+          handler: (blah) => {},
+        },
+        {
+          text: '确定',
+          handler: () => {
+            this.showGiftModal = false;
+            this.isShowGiftModal = true;
+            setTimeout(() => {
+              this.isShowGiftModal = false;
+            }, 5000);
+          },
+        },
+      ],
+    });
+    await alert.present();
+  }
+  onChange() {
+    this.giftCount = this.giftCount < 1 ? 1 : Math.floor(this.giftCount);
+    console.log(this.giftCount);
   }
   /* 结束直播 */
   async endCall(e: any) {

+ 1 - 1
projects/live-app/src/moduls/user/profile/profile.component.ts

@@ -75,7 +75,7 @@ export class ProfileComponent implements OnInit {
     let res = await this.aiChatServ.getFansAndFollow(this.uid);
     this.numsObject.fans = res.data[0].fans;
     this.numsObject.follow = res.data[0].follow;
-    let res1 = await this.aiChatServ.getGift(this.uid);
+    let res1 = await this.aiChatServ.getGiftLog(this.uid);
     this.numsObject.gift = res1.data[0].gift ?? 0;
     await this.getLoveRender();
     loading.dismiss();

+ 20 - 5
projects/live-app/src/services/aichart.service.ts

@@ -8,17 +8,32 @@ import { HttpService } from './http.service';
 export class AiChatService {
   isLoggedIn = false;
   company: string = 'Qje9D4bqol';
-  constructor(
-    private router: Router,
-    private http: HttpService,
-  ) {}
+  constructor(private router: Router, private http: HttpService) {}
+  async getWallet(uid: string):Promise<any> {
+    const data = await this.http.httpRequst(
+      'https://server.fmode.cn/api/ailiao/wallet',
+      { uid: uid },
+      'POST'
+    );
+    return data['data']
+  }
   getFansAndFollow(uid: string): Promise<any> {
     let sql = `SELECT 
     (SELECT COUNT(*) FROM "ProfileRadar" WHERE "fromUser" = '${uid}' AND "name" = '关注' AND "isDeleted" IS NOT TRUE) AS follow,
     (SELECT COUNT(*) FROM "ProfileRadar" WHERE "toUser" = '${uid}' AND "name" = '关注' AND "isDeleted" IS NOT TRUE) AS fans;`;
     return this.http.customSQL(sql);
   }
-  getGift(uid: string): Promise<any> {
+  async getGift(type?: string): Promise<any> {
+    let where = type ? `AND "type" = '${type}'` : '';
+    let sql = `SELECT "objectId" AS "id","name","price","order","imgUrl","type","rightsMap"
+    FROM "GiftModule"
+    WHERE "company" = '${this.company}'
+    ${where}
+    ORDER BY "price"`;
+    const data: any = await this.http.customSQL(sql);
+    return data?.data;
+  }
+  getGiftLog(uid: string): Promise<any> {
     let sql = `SELECT SUM("index")
     FROM "LoveRender"
     WHERE "fromUser" ='${uid}'`;

+ 16 - 11
src/index.html

@@ -1,13 +1,18 @@
-<!doctype html>
+<!DOCTYPE html>
 <html lang="en">
-<head>
-  <meta charset="utf-8">
-  <title>NovaLive</title>
-  <base href="/">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <link rel="icon" type="image/x-icon" href="favicon.ico">
-</head>
-<body>
-  <app-root></app-root>
-</body>
+  <head>
+    <meta charset="utf-8" />
+    <title>NovaLive</title>
+    <base href="/" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <link rel="icon" type="image/x-icon" href="favicon.ico" />
+  </head>
+  <style>
+    * {
+      font-family: PingFang SC;
+    }
+  </style>
+  <body>
+    <app-root></app-root>
+  </body>
 </html>