Ver código fonte

feat: emoji表情

shuisheng 2 anos atrás
pai
commit
e9e694de92

+ 6 - 0
src/constant.ts

@@ -120,6 +120,12 @@ export const COOKIE_KEY = {
 export const lsKeyPrefix = 'billd_live___';
 
 export const MODULE_CONFIG_SWITCH = {
+  logo: true,
+  home: true,
+  area: true,
+  shop: true,
+  pullGiftList: true,
+  pullShowAuth: false,
   // 后台入口
   admin: true,
   // app下载入口

+ 251 - 0
src/emoji.ts

@@ -0,0 +1,251 @@
+export const emojiArray = [
+  '🤣',
+  '😂',
+  '🙂',
+  '🙃',
+  '🫠',
+  '😉',
+  '😊',
+  '😇',
+  '🥰',
+  '😍',
+  '🤩',
+  '😘',
+  '😗',
+  '☺️',
+  '😚',
+  '😙',
+  '🥲',
+  '😋',
+  '😛',
+  '😜',
+  '🤪',
+  '😝',
+  '🤑',
+  '🤗',
+  '🤭',
+  '🫢',
+  '🫣',
+  '🤫',
+  '🤔',
+  '🫡',
+  '🤐',
+  '🤨',
+  '😐',
+  '😑',
+  '😶',
+  '🫥',
+  '😶‍🌫️',
+  '😏',
+  '😒',
+  '🙄',
+  '😬',
+  '😮‍💨',
+  '🤥',
+  '🫨',
+  '😌',
+  '😔',
+  '😪',
+  '🤤',
+  '😴',
+  '😷',
+  '🤒',
+  '🤕',
+  '🤢',
+  '🤮',
+  '🤧',
+  '🥵',
+  '🥶',
+  '🥴',
+  '😵',
+  '😵‍💫',
+  '🤯',
+  '🤠',
+  '🥳',
+  '🥸',
+  '😎',
+  '🤓',
+  '🧐',
+  '😕',
+  '🫤',
+  '😟',
+  '🙁',
+  '☹️',
+  '😮',
+  '😯',
+  '😲',
+  '😳',
+  '🥺',
+  '🥹',
+  '😦',
+  '😧',
+  '😨',
+  '😰',
+  '😥',
+  '😢',
+  '😭',
+  '😱',
+  '😖',
+  '😣',
+  '😞',
+  '😓',
+  '😩',
+  '😫',
+  '🥱',
+  '😤',
+  '😡',
+  '😠',
+  '🤬',
+  '😈',
+  '👿',
+  '💀',
+  '☠️',
+  '💩',
+  '🤡',
+  '👹',
+  '👺',
+  '👻',
+  '👽',
+  '👾',
+  '🤖',
+  '😺',
+  '😸',
+  '😹',
+  '😻',
+  '😼',
+  '😽',
+  '🙀',
+  '😿',
+  '😾',
+  '🙈',
+  '🙉',
+  '🙊',
+  '💋',
+  '💯',
+  '💢',
+  '💥',
+  '💫',
+  '💦',
+  '💨',
+  '🕳️',
+  '💤',
+  '👋',
+  '🤚',
+  '🖐️',
+  '✋',
+  '🖖',
+  '🫱',
+  '🫲',
+  '🫳',
+  '🫴',
+  '🫷',
+  '🫸',
+  '👌',
+  '🤌',
+  '🤏',
+  '✌️',
+  '🤞',
+  '🫰',
+  '🤟',
+  '🤘',
+  '🤙',
+  '👈',
+  '👉',
+  '👆',
+  '🖕',
+  '👇',
+  '☝️',
+  '🫵',
+  '👍',
+  '👎',
+  '✊',
+  '👊',
+  '🤛',
+  '🤜',
+  '👏',
+  '🙌',
+  '🫶',
+  '👐',
+  '🤲',
+  '🤝',
+  '🙏',
+  '✍️',
+  '💅',
+  '🤳',
+  '💪',
+  '🦾',
+  '🦿',
+  '🦵',
+  '🦶',
+  '👂',
+  '🦻',
+  '👃',
+  '🧠',
+  '🫀',
+  '🫁',
+  '🦷',
+  '🦴',
+  '👀',
+  '👁️',
+  '👅',
+  '👄',
+  '🫦',
+  '👶',
+  '🧒',
+  '👦',
+  '👧',
+  '🧑',
+  '👱',
+  '👨',
+  '🧔',
+  '🧔‍♂️',
+  '🧔‍♀️',
+  '👨‍🦰',
+  '👨‍🦱',
+  '👨‍🦳',
+  '👨‍🦲',
+  '👩',
+  '👩‍🦰',
+  '🧑‍🦰',
+  '👩‍🦱',
+  '🧑‍🦱',
+  '👩‍🦳',
+  '🧑‍🦳',
+  '👩‍🦲',
+  '🧑‍🦲',
+  '👱‍♀️',
+  '👱‍♂️',
+  '🧓',
+  '👴',
+  '👵',
+  '🙍',
+  '🙍‍♂️',
+  '🙍‍♀️',
+  '🙎',
+  '🙎‍♂️',
+  '🙎‍♀️',
+  '🙅',
+  '🙅‍♂️',
+  '🙅‍♀️',
+  '🙆',
+  '🙆‍♂️',
+  '🙆‍♀️',
+  '💁',
+  '💁‍♂️',
+  '💁‍♀️',
+  '💘',
+  '💝',
+  '💖',
+  '💗',
+  '💞',
+  '💕',
+  '❣️',
+  '💔',
+  '❤️‍🔥',
+  '❤️‍🩹',
+  '❤️',
+  '🩷',
+  '🧡',
+  '💚',
+  '💙',
+];

+ 2 - 0
src/hooks/use-pull.ts

@@ -389,12 +389,14 @@ export function usePull(roomId: string) {
       msgType: DanmuMsgTypeEnum.danmu,
       msg: danmuStr.value,
       msgIsFile: msgIsFile.value,
+      sendMsgTime: +new Date(),
     };
     const messageData: WsMessageType['data'] = {
       msg: danmuStr.value,
       msgType: DanmuMsgTypeEnum.danmu,
       live_room_id: Number(roomId),
       msgIsFile: msgIsFile.value,
+      sendMsgTime: +new Date(),
     };
     instance.send({
       requestId,

+ 2 - 0
src/hooks/use-push.ts

@@ -339,6 +339,7 @@ export function usePush() {
         msgType: DanmuMsgTypeEnum.danmu,
         live_room_id: Number(roomId.value),
         msgIsFile: msgIsFile.value,
+        sendMsgTime: +new Date(),
       },
     });
     damuList.value.push({
@@ -347,6 +348,7 @@ export function usePush() {
       msg: danmuStr.value,
       userInfo: userStore.userInfo!,
       msgIsFile: msgIsFile.value,
+      sendMsgTime: +new Date(),
     });
     danmuStr.value = '';
   }

+ 2 - 0
src/hooks/use-srs-ws.ts

@@ -338,6 +338,7 @@ export const useSrsWs = () => {
         msg: data.data.msg,
         userInfo: data.user_info,
         msgIsFile: data.data.msgIsFile,
+        sendMsgTime: data.data.sendMsgTime,
       });
     });
 
@@ -428,6 +429,7 @@ export const useSrsWs = () => {
         userInfo: data.join_user_info,
         msgIsFile: false,
         msg: '',
+        sendMsgTime: +new Date(),
       };
       damuList.value.push(danmu);
       ws.send<WsGetLiveUserType['data']>({

+ 1 - 0
src/interface-ws.ts

@@ -107,6 +107,7 @@ export type WsMessageType = IWsFormat<{
   msgType: DanmuMsgTypeEnum;
   msgIsFile: boolean;
   msg: string;
+  sendMsgTime: number;
   live_room_id: number;
 }>;
 

+ 1 - 0
src/interface.ts

@@ -506,4 +506,5 @@ export interface IDanmu {
   request_id?: string;
   userInfo?: IUser;
   msgIsFile: boolean;
+  sendMsgTime: number;
 }

+ 4 - 0
src/layout/pc/head/index.vue

@@ -3,6 +3,7 @@
     <div class="head">
       <div class="left">
         <div
+          v-if="MODULE_CONFIG_SWITCH.logo"
           class="logo-wrap"
           @click="router.push('/')"
         >
@@ -12,6 +13,7 @@
         <div class="nav">
           <a
             class="item"
+            v-if="MODULE_CONFIG_SWITCH.home"
             :class="{
               active: router.currentRoute.value.path === '/',
             }"
@@ -21,6 +23,7 @@
             首页
           </a>
           <a
+            v-if="MODULE_CONFIG_SWITCH.area"
             class="item"
             :class="{
               active: router.currentRoute.value.name === routerName.area,
@@ -30,6 +33,7 @@
             分区
           </a>
           <a
+            v-if="MODULE_CONFIG_SWITCH.shop"
             class="item"
             :class="{
               active: router.currentRoute.value.name === routerName.shop,

+ 46 - 0
src/utils/index.ts

@@ -2,6 +2,52 @@
 import { getRangeRandom } from 'billd-utils';
 import sparkMD5 from 'spark-md5';
 
+export const formatTimeHour = (timestamp: number) => {
+  function addZero(num: number) {
+    return num < 10 ? `0${num}` : num;
+  }
+  const date = new Date(timestamp);
+
+  // 获取小时
+  const hours = date.getHours();
+
+  // 获取分钟
+  const minutes = date.getMinutes();
+
+  // 打印结果
+  return `${addZero(hours)}:${addZero(minutes)}`;
+};
+
+export const formatTime = (timestamp: number) => {
+  function addZero(num: number) {
+    return num < 10 ? `0${num}` : num;
+  }
+  const date = new Date(timestamp);
+
+  // 获取年份
+  const year = date.getFullYear();
+
+  // 获取月份(注意月份是从0开始的,所以要加1)
+  const month = date.getMonth() + 1;
+
+  // 获取日期
+  const day = date.getDate();
+
+  // 获取小时
+  const hours = date.getHours();
+
+  // 获取分钟
+  const minutes = date.getMinutes();
+
+  // 获取秒数
+  const seconds = date.getSeconds();
+
+  // 打印结果
+  return `${year}-${addZero(month)}-${addZero(day)} ${addZero(hours)}:${addZero(
+    minutes
+  )}:${addZero(seconds)}`;
+};
+
 export const getLiveRoomPageUrl = (liveRoomId: number) => {
   return `${getHostnameUrl()}/pull/${liveRoomId}`;
 };

+ 76 - 6
src/views/h5/room/index.vue

@@ -80,13 +80,24 @@
                 class="item"
               >
                 <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
+                  <span class="time"
+                    >[{{ formatTimeHour(item.sendMsgTime) }}]</span
+                  >
                   <span class="name">
                     <span v-if="item.userInfo">
-                      {{ item.userInfo.username }}[{{
-                        item.userInfo.roles?.map((v) => v.role_name).join()
-                      }}]
+                      <span>{{ item.userInfo.username }}</span>
+                      <span v-if="MODULE_CONFIG_SWITCH.pullShowAuth">
+                        [{{
+                          item.userInfo.roles?.map((v) => v.role_name).join()
+                        }}]
+                      </span>
+                    </span>
+                    <span v-else>
+                      <span>{{ item.socket_id }}</span>
+                      <span v-if="MODULE_CONFIG_SWITCH.pullShowAuth">
+                        [游客]
+                      </span>
                     </span>
-                    <span v-else>{{ item.socket_id }}[游客]</span>
                     <span>:</span>
                   </span>
                   <span
@@ -167,6 +178,23 @@
       ref="bottomRef"
       class="send-msg"
     >
+      <div
+        class="emoji-list"
+        v-if="showEmoji"
+      >
+        <div
+          class="item"
+          v-for="(item, index) in emojiArray"
+          :key="index"
+          @click="handlePushStr(item)"
+        >
+          {{ item }}
+        </div>
+      </div>
+      <div
+        class="face"
+        @click="showEmoji = !showEmoji"
+      ></div>
       <input
         v-model="danmuStr"
         class="ipt"
@@ -192,11 +220,14 @@ import { useRoute } from 'vue-router';
 
 import { fetchFindLiveConfigByKey } from '@/api/liveConfig';
 import { fetchFindLiveRoom } from '@/api/liveRoom';
+import { MODULE_CONFIG_SWITCH } from '@/constant';
+import { emojiArray } from '@/emoji';
 import { usePull } from '@/hooks/use-pull';
 import { DanmuMsgTypeEnum, LiveRoomTypeEnum } from '@/interface';
 import router, { mobileRouterName } from '@/router';
 import { useAppStore } from '@/store/app';
 import { usePiniaCacheStore } from '@/store/cache';
+import { formatTimeHour } from '@/utils';
 
 const route = useRoute();
 const cacheStore = usePiniaCacheStore();
@@ -205,6 +236,8 @@ const appStore = useAppStore();
 const bottomRef = ref<HTMLDivElement>();
 const danmuListRef = ref<HTMLDivElement>();
 const showPlayBtn = ref(false);
+const showEmoji = ref(false);
+
 const containerHeight = ref(0);
 const videoWrapHeight = ref(0);
 const frontendWechatQrcode = ref('');
@@ -252,6 +285,11 @@ onMounted(() => {
   getLiveRoomInfo();
 });
 
+function handlePushStr(str) {
+  danmuStr.value += str;
+  showEmoji.value = false;
+}
+
 watch(
   () => remoteVideo.value,
   (newVal) => {
@@ -467,7 +505,8 @@ async function getWechatQrcode() {
       word-wrap: break-word;
       font-size: 13px;
 
-      .name {
+      .name,
+      .time {
         color: white;
         opacity: 0.8;
         cursor: pointer;
@@ -513,7 +552,38 @@ async function getWechatQrcode() {
     padding: 0;
     width: 100%;
     height: 40px;
-    background-color: #0c1622;
+    background-color: white;
+    .emoji-list {
+      position: absolute;
+      top: 0;
+      right: 0;
+      left: 0;
+      overflow: scroll;
+      box-sizing: border-box;
+      padding-top: 5px;
+      padding-left: 5px;
+      height: 160px;
+      background-color: #fff;
+      transform: translateY(-100%);
+
+      @extend %customScrollbar;
+      .item {
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        box-sizing: border-box;
+        width: 8vw;
+        height: 8vw;
+        border: 1px solid #f8f8f8;
+        font-size: 20px;
+      }
+    }
+    .face {
+      width: 20px;
+      height: 20px;
+
+      @include setBackground('@/assets/img/msg-face.webp');
+    }
     .ipt {
       display: block;
       box-sizing: border-box;

+ 72 - 12
src/views/pull/index.vue

@@ -121,6 +121,7 @@
           <div class="price">¥{{ item.price }}</div>
         </div>
         <div
+          v-if="MODULE_CONFIG_SWITCH.pullGiftList"
           class="item"
           @click="handleRecharge"
         >
@@ -178,15 +179,17 @@
           class="item"
         >
           <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
+            <span class="time">[{{ formatTimeHour(item.sendMsgTime) }}]</span>
             <span class="name">
               <span
                 v-if="
                   item.userInfo && userStore.userInfo?.id === item.userInfo.id
                 "
               >
-                {{ item.userInfo.username }}[{{
-                  item.userInfo.roles?.map((v) => v.role_name).join()
-                }}]
+                <span>{{ item.userInfo.username }}</span>
+                <span v-if="MODULE_CONFIG_SWITCH.pullShowAuth">
+                  [{{ item.userInfo.roles?.map((v) => v.role_name).join() }}]
+                </span>
               </span>
               <Dropdown
                 trigger="click"
@@ -194,9 +197,10 @@
                 v-else-if="item.userInfo"
               >
                 <template #btn>
-                  {{ item.userInfo.username }}[{{
-                    item.userInfo.roles?.map((v) => v.role_name).join()
-                  }}]
+                  <span>{{ item.userInfo.username }}</span>
+                  <span v-if="MODULE_CONFIG_SWITCH.pullShowAuth">
+                    [{{ item.userInfo.roles?.map((v) => v.role_name).join() }}]
+                  </span>
                 </template>
                 <template #list>
                   <div class="list">
@@ -232,7 +236,10 @@
                   </div>
                 </template>
               </Dropdown>
-              <span v-else>{{ item.socket_id }}[游客]</span>
+              <span v-else>
+                <span>{{ item.socket_id }}</span>
+                <span v-if="MODULE_CONFIG_SWITCH.pullShowAuth">[游客]</span>
+              </span>
             </span>
             <span>:</span>
             <span
@@ -283,10 +290,23 @@
           </span>
         </div>
         <div class="control">
+          <div
+            class="emoji-list"
+            v-if="showEmoji"
+          >
+            <div
+              class="item"
+              v-for="(item, index) in emojiArray"
+              :key="index"
+              @click="handlePushStr(item)"
+            >
+              {{ item }}
+            </div>
+          </div>
           <div
             class="ico face"
             title="表情"
-            @click="handleWait"
+            @click="handleEmoji"
           ></div>
           <div
             class="ico img"
@@ -303,6 +323,7 @@
           </div>
         </div>
         <textarea
+          ref="danmuIptRef"
           :placeholder="'发个弹幕吧~'"
           v-model="danmuStr"
           class="ipt"
@@ -330,6 +351,7 @@ import { useRoute } from 'vue-router';
 
 import { fetchGoodsList } from '@/api/goods';
 import { MODULE_CONFIG_SWITCH, QINIU_LIVE } from '@/constant';
+import { emojiArray } from '@/emoji';
 import { commentAuthTip, loginTip } from '@/hooks/use-login';
 import { usePull } from '@/hooks/use-pull';
 import { useUpload } from '@/hooks/use-upload';
@@ -339,6 +361,7 @@ import router, { routerName } from '@/router';
 import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
+import { formatTimeHour } from '@/utils';
 import { NODE_ENV } from 'script/constant';
 
 import RechargeCpt from './recharge/index.vue';
@@ -354,6 +377,7 @@ const giftGoodsList = ref<IGoods[]>([]);
 const height = ref(0);
 const giftLoading = ref(false);
 const showRecharge = ref(false);
+const showEmoji = ref(false);
 const msgLoading = ref(false);
 const topRef = ref<HTMLDivElement>();
 const bottomRef = ref<HTMLDivElement>();
@@ -361,6 +385,7 @@ const danmuListRef = ref<HTMLDivElement>();
 const remoteVideoRef = ref<HTMLDivElement>();
 const containerRef = ref<HTMLDivElement>();
 const uploadRef = ref<HTMLInputElement>();
+const danmuIptRef = ref<HTMLTextAreaElement>();
 const {
   initPull,
   closeWs,
@@ -385,7 +410,9 @@ onMounted(() => {
     scrollTo(0, 0);
   }, 100);
   appStore.setPlay(true);
-  getGoodsList();
+  if (MODULE_CONFIG_SWITCH.pullGiftList) {
+    getGoodsList();
+  }
   if (topRef.value && bottomRef.value && containerRef.value) {
     const res =
       bottomRef.value.getBoundingClientRect().top -
@@ -500,14 +527,20 @@ function getBg() {
   }
 }
 
-function handleWait() {
+function handlePushStr(str) {
+  danmuStr.value += str;
+  showEmoji.value = false;
+  danmuIptRef.value?.focus();
+}
+
+function handleEmoji() {
   if (!loginTip()) {
     return;
   }
   if (!commentAuthTip()) {
     return;
   }
-  window.$message.warning('敬请期待!');
+  showEmoji.value = !showEmoji.value;
 }
 
 function mockClick() {
@@ -911,7 +944,8 @@ function handleScrollTop() {
         word-wrap: break-word;
         font-size: 13px;
 
-        .name {
+        .name,
+        .time {
           color: #9499a0;
           &.system {
             color: red;
@@ -968,6 +1002,32 @@ function handleScrollTop() {
       .control {
         display: flex;
         margin: 4px 0;
+        .emoji-list {
+          position: absolute;
+          top: 0;
+          right: 0;
+          left: 0;
+          overflow: scroll;
+          box-sizing: border-box;
+          padding-top: 5px;
+          padding-left: 5px;
+          height: 160px;
+          background-color: #fff;
+          transform: translateY(-100%);
+
+          @extend %customScrollbar;
+          .item {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            box-sizing: border-box;
+            width: 14%;
+            height: 18%;
+            border: 1px solid #f8f8f8;
+            font-size: 20px;
+            cursor: pointer;
+          }
+        }
         .ico {
           margin-right: 6px;
           width: 24px;