浏览代码

fix: 优化

shuisheng 2 年之前
父节点
当前提交
692c5ad0d8

+ 1 - 1
README.md

@@ -2,7 +2,7 @@
   <a href="https://live.hsslive.cn" target="_blank">
     <img
       width="200"
-      src="https://github.com/galaxy-s10/billd-live/blob/master/src/assets/img/logo-txt.svg"
+      src="https://resource.hsslive.cn/billd-live/image/240160ddbc14367f7e0126c1f5b09b69.svg"
       alt="Billd-Live logo"
     />
   </a>

+ 8 - 2
src/components/LoginModal/index.vue

@@ -75,6 +75,7 @@
               </n-button>
             </n-tab-pane>
             <n-tab-pane
+              v-if="MODULE_CONFIG_SWITCH.wechatLogin"
               name="qrcodelogin"
               tab="微信登录"
             >
@@ -87,9 +88,13 @@
             </n-tab-pane>
           </n-tabs>
         </n-card>
-        <div class="other-login">
+        <div
+          class="other-login"
+          v-if="MODULE_CONFIG_SWITCH.thirdLogin"
+        >
           <span>第三方登录:</span>
           <div
+            v-if="MODULE_CONFIG_SWITCH.qqLogin"
             class="logo-wrap"
             @click="handleQQLogin()"
           >
@@ -108,6 +113,7 @@
             />
           </div> -->
           <div
+            v-if="MODULE_CONFIG_SWITCH.githubLogin"
             class="logo-wrap"
             @click="handleGithubLogin"
           >
@@ -128,7 +134,7 @@ import QRCode from 'qrcode';
 import { onUnmounted, reactive, ref } from 'vue';
 
 import { fetchQrcodeLogin, fetchQrcodeLoginStatus } from '@/api/user';
-import { QRCODE_LOGIN_URI } from '@/constant';
+import { MODULE_CONFIG_SWITCH, QRCODE_LOGIN_URI } from '@/constant';
 import { useQQLogin } from '@/hooks/use-login';
 import { useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';

+ 0 - 1
src/components/Slider/index.vue

@@ -140,7 +140,6 @@ onMounted(() => {
 }
 .slider-cpt-wrap {
   overflow: hidden;
-  padding: 2px 0;
   .slider {
     position: relative;
     overflow: scroll;

+ 49 - 0
src/constant.ts

@@ -69,6 +69,55 @@ export const COOKIE_KEY = {
 
 export const lsKeyPrefix = 'billd_live___';
 
+export const MODULE_CONFIG_SWITCH = {
+  // 后台入口
+  admin: true,
+  // app下载入口
+  appdownload: true,
+  // 文档
+  doc: true,
+  // 生态系统
+  ecosystem: true,
+  // 关于
+  about: true,
+  // 赞助
+  sponsors: true,
+  // 私有化部署
+  privatizationDeployment: true,
+  // github
+  github: true,
+  // 轮播
+  slider: true,
+  // 底部版权提示
+  copyrightNotice: true,
+  // 侧边栏
+  sidebar: true,
+  // 侧边栏排行榜
+  sidebarRank: true,
+  // 侧边栏商店
+  sidebarShop: true,
+  // 侧边栏订单
+  sidebarOrder: true,
+  // 支付
+  pay: true,
+  // 第三方登录
+  thirdLogin: true,
+  // qq登录
+  qqLogin: true,
+  // 微信扫码登录
+  wechatLogin: true,
+  // github登录
+  githubLogin: true,
+  // 我要开播按钮
+  startLive: true,
+  // srs开播
+  startLiveSRS: true,
+  // webrtc开播
+  startLiveWebRTC: true,
+  // msr开播
+  startLiveWebMSR: true,
+};
+
 // 全局的localStorage的key
 export const lsKey = {
   lastBuildDate: 'lastBuildDate',

+ 1 - 2
src/hooks/modal/index.vue

@@ -89,8 +89,7 @@ export default defineComponent({
         font-size: 14px;
       }
       &.next {
-        background-color: white;
-        background-image: $theme-color-gold;
+        background: $theme-color-gold;
         color: white;
         font-weight: 700;
         font-size: 16px;

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

@@ -31,6 +31,7 @@ export function usePush() {
   const liveRoomInfo = ref<ILiveRoom>();
   const localStream = ref<MediaStream>();
   const videoElArr = ref<HTMLVideoElement[]>([]);
+  const msgIsFile = ref(false);
 
   const {
     roomLiving,
@@ -328,7 +329,7 @@ export function usePush() {
         msg: danmuStr.value,
         msgType: DanmuMsgTypeEnum.danmu,
         live_room_id: Number(roomId.value),
-        msgIsFile: false,
+        msgIsFile: msgIsFile.value,
       },
     });
     damuList.value.push({
@@ -336,7 +337,7 @@ export function usePush() {
       msgType: DanmuMsgTypeEnum.danmu,
       msg: danmuStr.value,
       userInfo: userStore.userInfo!,
-      msgIsFile: false,
+      msgIsFile: msgIsFile.value,
     });
     danmuStr.value = '';
   }
@@ -348,6 +349,7 @@ export function usePush() {
     sendDanmu,
     keydownDanmu,
     sendBlob,
+    msgIsFile,
     mySocketId,
     lastCoverImg,
     localStream,

+ 8 - 2
src/interface.ts

@@ -202,6 +202,13 @@ export enum LiveRoomPullIsShouldAuthEnum {
   /** 不需要鉴权 */
   no,
 }
+/** 是否使用cdn */
+export enum LiveRoomUseCDNEnum {
+  /** 使用cdn */
+  yes = 1,
+  /** 不使用cdn */
+  no = 2,
+}
 
 export interface ILiveRoom {
   id?: number;
@@ -218,8 +225,7 @@ export interface ILiveRoom {
   user_live_room?: IUserLiveRoom & { user: IUser };
   name?: string;
   desc?: string;
-  /** 1:使用cdn;2:不使用cdn */
-  cdn?: number;
+  cdn?: LiveRoomUseCDNEnum;
   /** 权重 */
   weight?: number;
   /** 推流秘钥 */

+ 16 - 2
src/layout/pc/head/index.vue

@@ -42,6 +42,7 @@
             class="item"
             :href="COMMON_URL.admin"
             @click.prevent="openToTarget(COMMON_URL.admin)"
+            v-if="MODULE_CONFIG_SWITCH.admin"
           >
             直播后台
           </a>
@@ -49,6 +50,7 @@
             class="item"
             :href="COMMON_URL.mobileApk"
             @click.prevent="openToTarget(COMMON_URL.mobileApk)"
+            v-if="MODULE_CONFIG_SWITCH.appdownload"
           >
             App下载
             <div class="badge">
@@ -71,6 +73,7 @@
         <Dropdown
           v-model="dropdownDoc"
           class="doc"
+          v-if="MODULE_CONFIG_SWITCH.appdownload"
         >
           <template #btn>
             <div class="btn">
@@ -118,6 +121,7 @@
         <Dropdown
           v-model="dropdownSys"
           class="ecosystem"
+          v-if="MODULE_CONFIG_SWITCH.ecosystem"
         >
           <template #btn>
             <div class="btn">
@@ -162,6 +166,7 @@
         <Dropdown
           v-model="dropdownAbout"
           class="about"
+          v-if="MODULE_CONFIG_SWITCH.about"
         >
           <template #btn>
             <div class="btn">
@@ -192,6 +197,7 @@
         </Dropdown>
 
         <a
+          v-if="MODULE_CONFIG_SWITCH.sponsors"
           class="sponsors"
           :class="{
             active: router.currentRoute.value.name === routerName.sponsors,
@@ -202,6 +208,7 @@
           赞助
         </a>
         <a
+          v-if="MODULE_CONFIG_SWITCH.privatizationDeployment"
           class="privatizationDeployment"
           :class="{
             active:
@@ -220,6 +227,7 @@
         </a>
 
         <a
+          v-if="MODULE_CONFIG_SWITCH.github"
           class="github"
           target="_blank"
           href="https://github.com/galaxy-s10/billd-live"
@@ -230,25 +238,31 @@
           />
         </a>
 
-        <Dropdown class="start-live">
+        <Dropdown
+          class="start-live"
+          v-if="MODULE_CONFIG_SWITCH.startLive"
+        >
           <template #btn>
             <div class="btn">我要开播</div>
           </template>
           <template #list>
             <div class="list">
               <a
+                v-if="MODULE_CONFIG_SWITCH.startLiveSRS"
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_srs)"
               >
                 <div class="txt">srs开播</div>
               </a>
               <a
+                v-if="MODULE_CONFIG_SWITCH.startLiveWebRTC"
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_wertc)"
               >
                 <div class="txt">webrtc开播</div>
               </a>
               <a
+                v-if="MODULE_CONFIG_SWITCH.startLiveWebMSR"
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_msr)"
               >
@@ -305,7 +319,7 @@ import { useRouter } from 'vue-router';
 import Dropdown from '@/components/Dropdown/index.vue';
 import VPIconChevronDown from '@/components/icons/VPIconChevronDown.vue';
 import VPIconExternalLink from '@/components/icons/VPIconExternalLink.vue';
-import { COMMON_URL } from '@/constant';
+import { COMMON_URL, MODULE_CONFIG_SWITCH } from '@/constant';
 import { loginTip } from '@/hooks/use-login';
 import { LiveRoomTypeEnum } from '@/interface';
 import { routerName } from '@/router';

+ 2 - 1
src/layout/pc/index.vue

@@ -6,12 +6,13 @@
       <component :is="Component"></component>
     </router-view>
     <ModalCpt></ModalCpt>
-    <SidebarCpt></SidebarCpt>
+    <SidebarCpt v-if="MODULE_CONFIG_SWITCH.sidebar"></SidebarCpt>
     <LoginModal v-if="appStore.showLoginModal"></LoginModal>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { MODULE_CONFIG_SWITCH } from '@/constant';
 import { useAppStore } from '@/store/app';
 
 import HeadCpt from './head/index.vue';

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

@@ -1,6 +1,7 @@
 <template>
   <aside class="sidebar-wrap">
     <div
+      v-if="MODULE_CONFIG_SWITCH.sidebarRank"
       class="item"
       @click="router.push({ name: routerName.rank })"
     >
@@ -8,6 +9,7 @@
       <div class="txt">排行榜</div>
     </div>
     <div
+      v-if="MODULE_CONFIG_SWITCH.sidebarShop"
       class="item"
       @click="router.push({ name: routerName.shop })"
     >
@@ -15,6 +17,7 @@
       <div class="txt">商店</div>
     </div>
     <div
+      v-if="MODULE_CONFIG_SWITCH.sidebarOrder"
       class="item"
       @click="router.push({ name: routerName.order })"
     >
@@ -25,6 +28,7 @@
 </template>
 
 <script lang="ts" setup>
+import { MODULE_CONFIG_SWITCH } from '@/constant';
 import router, { routerName } from '@/router';
 </script>
 

+ 147 - 42
src/views/h5/room/index.vue

@@ -59,39 +59,104 @@
         @refresh="handleRefresh"
       ></VideoControls>
     </div>
-    <div class="danmu-list">
-      <div class="title">弹幕专区</div>
-      <div
-        ref="containerRef"
-        class="list"
-        :style="{ height: containerHeight + 'px' }"
+    <div class="n-tab-wrap">
+      <n-tabs
+        type="line"
+        animated
       >
-        <div
-          v-for="(item, index) in damuList"
-          :key="index"
-          class="item"
+        <n-tab-pane
+          name="danmu"
+          tab="聊天"
         >
-          <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
-            <span class="name">
-              {{ item.userInfo?.username || item.socket_id }}:
-            </span>
-            <span class="msg">{{ item.msg }}</span>
-          </template>
-          <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
-            <span class="name system">系统通知:</span>
-            <span class="msg">
-              {{ item.userInfo?.username || item.socket_id }}进入直播!
-            </span>
-          </template>
-          <template v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved">
-            <span class="name system">系统通知:</span>
-            <span class="msg">
-              {{ item.userInfo?.username || item.socket_id }}离开直播!
-            </span>
-          </template>
-        </div>
-      </div>
+          <div class="danmu-list">
+            <div
+              ref="danmuListRef"
+              class="list"
+              :style="{ height: containerHeight + 'px' }"
+            >
+              <div
+                v-for="(item, index) in damuList"
+                :key="index"
+                class="item"
+              >
+                <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
+                  <span class="name">
+                    <span v-if="item.userInfo">
+                      {{ item.userInfo.username }}[{{
+                        item.userInfo.roles?.map((v) => v.role_name).join()
+                      }}]
+                    </span>
+                    <span v-else>{{ item.socket_id }}[游客]</span>
+                  </span>
+                  <span>:</span>
+                  <span
+                    class="msg"
+                    v-if="!item.msgIsFile"
+                  >
+                    {{ item.msg }}
+                  </span>
+                  <div
+                    class="msg img"
+                    v-else
+                  >
+                    <img
+                      :src="item.msg"
+                      alt=""
+                      @load="handleScrollTop"
+                    />
+                  </div>
+                </template>
+                <template
+                  v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin"
+                >
+                  <span class="name system">系统通知:</span>
+                  <span class="msg">
+                    {{ item.userInfo?.username || item.socket_id }}进入直播!
+                  </span>
+                </template>
+                <template
+                  v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved"
+                >
+                  <span class="name system">系统通知:</span>
+                  <span class="msg">
+                    {{ item.userInfo?.username || item.socket_id }}离开直播!
+                  </span>
+                </template>
+              </div>
+            </div>
+          </div>
+        </n-tab-pane>
+        <n-tab-pane
+          name="customerService"
+          tab="客服"
+        >
+          <div
+            class="customerService-wrap"
+            :style="{ height: containerHeight + 'px' }"
+          >
+            敬请期待!
+          </div>
+        </n-tab-pane>
+        <n-tab-pane
+          name="liveRoomInfo"
+          tab="直播间信息"
+        >
+          <div
+            class="liveRoomInfo-wrap"
+            :style="{ height: containerHeight + 'px' }"
+          >
+            <div>直播间名称:{{ appStore.liveRoomInfo?.name }}</div>
+            <div>直播间简介:{{ appStore.liveRoomInfo?.desc }}</div>
+            <div>
+              直播间分区:{{
+                appStore.liveRoomInfo?.areas?.[0].name || '暂无分区'
+              }}
+            </div>
+          </div>
+        </n-tab-pane>
+      </n-tabs>
     </div>
+
     <div
       ref="bottomRef"
       class="send-msg"
@@ -99,6 +164,7 @@
       <input
         v-model="danmuStr"
         class="ipt"
+        placeholder="发个弹幕吧~"
         @keydown="keydownDanmu"
       />
       <n-button
@@ -129,7 +195,7 @@ const cacheStore = usePiniaCacheStore();
 const appStore = useAppStore();
 
 const bottomRef = ref<HTMLDivElement>();
-const containerRef = ref<HTMLDivElement>();
+const danmuListRef = ref<HTMLDivElement>();
 const showPlayBtn = ref(false);
 const containerHeight = ref(0);
 const videoWrapHeight = ref(0);
@@ -165,13 +231,17 @@ watch(
   }
 );
 
+function handleScrollTop() {
+  if (danmuListRef.value) {
+    danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight + 10000;
+  }
+}
+
 watch(
   () => damuList.value.length,
   () => {
     setTimeout(() => {
-      if (containerRef.value) {
-        containerRef.value.scrollTop = containerRef.value.scrollHeight;
-      }
+      handleScrollTop();
     }, 0);
   }
 );
@@ -220,10 +290,10 @@ onMounted(() => {
   videoWrapHeight.value =
     document.documentElement.clientWidth / appStore.videoRatio;
   nextTick(() => {
-    if (containerRef.value && bottomRef.value) {
+    if (danmuListRef.value && bottomRef.value) {
       const res =
         bottomRef.value.getBoundingClientRect().top -
-        containerRef.value.getBoundingClientRect().top;
+        danmuListRef.value.getBoundingClientRect().top;
       containerHeight.value = res;
     }
   });
@@ -233,6 +303,8 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 .h5-room-wrap {
+  height: 100vh;
+  background-color: #0c1622;
   .head {
     display: flex;
     align-items: center;
@@ -324,9 +396,24 @@ onMounted(() => {
     }
   }
 
+  .n-tab-wrap {
+    padding-left: 10px;
+    background: #0c1622;
+    color: white;
+    :deep(.n-tabs-tab) {
+      --n-tab-text-color: white;
+    }
+    :deep(.n-tabs-nav-scroll-content) {
+      border-bottom: 0 !important;
+    }
+
+    // :deep(.n-tabs-pane-wrapper) {
+    //   --n-pane-text-color: white;
+    // }
+  }
   .danmu-list {
     box-sizing: border-box;
-    padding: 0 15px;
+    padding: 0;
     background-color: #0c1622;
     text-align: initial;
     .title {
@@ -341,19 +428,37 @@ onMounted(() => {
       @extend %hideScrollbar;
     }
     .item {
-      margin-bottom: 10px;
-      font-size: 12px;
+      box-sizing: border-box;
+      margin-bottom: 4px;
+      padding: 2px;
+      white-space: normal;
+      word-wrap: break-word;
+      font-size: 13px;
+
       .name {
-        color: #ccc;
+        color: #9499a0;
+        cursor: pointer;
         &.system {
           color: red;
         }
       }
       .msg {
-        color: #fff;
+        margin-top: 4px;
+        color: #61666d;
+        &.img {
+          img {
+            width: 80%;
+          }
+        }
       }
     }
   }
+  .customerService-wrap,
+  .liveRoomInfo-wrap {
+    height: 100%;
+    height: 300px;
+    color: white;
+  }
   .send-msg {
     position: fixed;
     bottom: 0;
@@ -362,7 +467,7 @@ onMounted(() => {
     align-items: center;
     justify-content: space-evenly;
     box-sizing: border-box;
-    padding: 0 10px;
+    padding: 0;
     width: 100%;
     height: 40px;
     background-color: #0c1622;

+ 27 - 13
src/views/home/index.vue

@@ -15,18 +15,27 @@
         loop
       ></video>
       <div
-        v-for="(item, index) in interactionList"
-        :key="index"
+        v-if="MODULE_CONFIG_SWITCH.slider"
+        class="slider-wrap"
       >
-        <Slider
-          v-if="item.length"
-          :list="item"
-          :width="docW"
-          :speed="120"
-          :direction="index % 2 === 0 ? 'l-r' : 'r-l'"
-          :customStyle="{ margin: '0 auto' }"
-        ></Slider>
+        <div
+          v-for="(item, index) in interactionList"
+          :key="index"
+        >
+          <Slider
+            v-if="item.length"
+            :list="item"
+            :width="docW"
+            :speed="120"
+            :direction="index % 2 === 0 ? 'l-r' : 'r-l'"
+            :customStyle="{ margin: '0 auto' }"
+          ></Slider>
+        </div>
       </div>
+      <div
+        v-else
+        class="slider-wrap"
+      ></div>
 
       <div class="container">
         <div
@@ -178,7 +187,12 @@
       </div>
     </div>
 
-    <div class="foot">*部分内容来源网络,如有侵权,请联系我删除~</div>
+    <div
+      class="foot"
+      v-if="MODULE_CONFIG_SWITCH.copyrightNotice"
+    >
+      *部分内容来源网络,如有侵权,请联系我删除~
+    </div>
   </div>
 </template>
 
@@ -188,7 +202,7 @@ import { useRouter } from 'vue-router';
 
 import { fetchLiveList } from '@/api/live';
 import { fetchFindLiveConfigByKey } from '@/api/liveConfig';
-import { sliderList } from '@/constant';
+import { MODULE_CONFIG_SWITCH, sliderList } from '@/constant';
 import { usePull } from '@/hooks/use-pull';
 import {
   ILive,
@@ -353,7 +367,7 @@ function joinRoom(data: { roomId: number }) {
       // object-fit: fill;
     }
     .slider-wrap {
-      margin: 0 auto;
+      padding: 4px 0;
     }
     .container {
       display: flex;

+ 10 - 1
src/views/pull/index.vue

@@ -234,6 +234,7 @@
             @click="mockClick"
           >
             <input
+              placeholder="发个弹幕吧~"
               ref="uploadRef"
               type="file"
               class="input-upload"
@@ -266,7 +267,7 @@
 import { onMounted, onUnmounted, ref, watch } from 'vue';
 
 import { fetchGoodsList } from '@/api/goods';
-import { QINIU_LIVE } from '@/constant';
+import { MODULE_CONFIG_SWITCH, QINIU_LIVE } from '@/constant';
 import { loginTip } from '@/hooks/use-login';
 import { usePull } from '@/hooks/use-pull';
 import { useUpload } from '@/hooks/use-upload';
@@ -416,6 +417,10 @@ async function uploadChange() {
 }
 
 function handlePay() {
+  if (!MODULE_CONFIG_SWITCH.pay) {
+    window.$message.info('敬请期待!');
+    return;
+  }
   window.$message.info('敬请期待!');
 }
 
@@ -444,6 +449,10 @@ async function getGoodsList() {
 }
 
 function handleRecharge() {
+  if (!MODULE_CONFIG_SWITCH.pay) {
+    window.$message.info('敬请期待!');
+    return;
+  }
   if (!loginTip()) return;
   showRecharge.value = true;
 }

+ 195 - 50
src/views/push/index.vue

@@ -211,9 +211,30 @@
             >
               <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
                 <span class="name">
-                  {{ item.userInfo?.username || item.socket_id }}:
+                  <span v-if="item.userInfo">
+                    {{ item.userInfo.username }}[{{
+                      item.userInfo.roles?.map((v) => v.role_name).join()
+                    }}]
+                  </span>
+                  <span v-else>{{ item.socket_id }}[游客]</span>
                 </span>
-                <span class="msg">{{ item.msg }}</span>
+                <span>:</span>
+                <span
+                  class="msg"
+                  v-if="!item.msgIsFile"
+                >
+                  {{ item.msg }}
+                </span>
+                <div
+                  class="msg img"
+                  v-else
+                >
+                  <img
+                    :src="item.msg"
+                    alt=""
+                    @load="handleScrollTop"
+                  />
+                </div>
               </template>
               <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
                 <span class="name system">系统通知:</span>
@@ -234,7 +255,43 @@
             </div>
           </div>
         </div>
-        <div class="send-msg">
+        <div
+          class="send-msg"
+          v-loading="msgLoading"
+        >
+          <div class="control">
+            <div
+              class="ico face"
+              title="表情"
+              @click="handleWait"
+            ></div>
+            <div
+              class="ico img"
+              title="图片"
+              @click="mockClick"
+            >
+              <input
+                ref="uploadRef"
+                type="file"
+                class="input-upload"
+                accept=".webp,.png,.jpg,.jpeg,.gif"
+                @change="uploadChange"
+              />
+            </div>
+          </div>
+          <textarea
+            v-model="danmuStr"
+            class="ipt"
+            @keydown="keydownDanmu"
+          ></textarea>
+          <div
+            class="btn"
+            @click="sendDanmu"
+          >
+            发送
+          </div>
+        </div>
+        <!-- <div class="send-msg">
           <input
             v-model="danmuStr"
             class="ipt"
@@ -247,7 +304,7 @@
           >
             发送
           </n-button>
-        </div>
+        </div> -->
       </div>
     </div>
 
@@ -300,9 +357,10 @@ import {
 import { useRoute } from 'vue-router';
 import * as workerTimers from 'worker-timers';
 
-import { mediaTypeEnumMap } from '@/constant';
+import { QINIU_LIVE, mediaTypeEnumMap } from '@/constant';
 import { usePush } from '@/hooks/use-push';
 import { useRTCParams } from '@/hooks/use-rtcParams';
+import { useUpload } from '@/hooks/use-upload';
 import { DanmuMsgTypeEnum, LiveRoomTypeEnum, MediaTypeEnum } from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
 import { usePiniaCacheStore } from '@/store/cache';
@@ -338,6 +396,7 @@ const {
   sendDanmu,
   keydownDanmu,
   sendBlob,
+  msgIsFile,
   mySocketId,
   lastCoverImg,
   canvasVideoStream,
@@ -367,6 +426,8 @@ const webaudioVideo = ref<HTMLVideoElement>();
 const fabricCanvas = ref<fabric.Canvas>();
 const startTime = ref(+new Date());
 // const startTime = ref(1692807352565); // 1693027352565
+const msgLoading = ref(false);
+const uploadRef = ref<HTMLInputElement>();
 
 const timeCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const stopwatchCanvasDom = ref<Raw<fabric.Text>[]>([]);
@@ -418,13 +479,17 @@ watch(
   () => damuList.value.length,
   () => {
     setTimeout(() => {
-      if (danmuListRef.value) {
-        danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight;
-      }
+      handleScrollTop();
     }, 0);
   }
 );
 
+function handleScrollTop() {
+  if (danmuListRef.value) {
+    danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight + 10000;
+  }
+}
+
 function handleSendBlob(event: BlobEvent) {
   bolbId.value += 1;
   sendBlob({
@@ -434,6 +499,40 @@ function handleSendBlob(event: BlobEvent) {
   });
 }
 
+function mockClick() {
+  uploadRef.value?.click();
+}
+
+function handleWait() {
+  window.$message.warning('敬请期待!');
+}
+
+async function uploadChange() {
+  const fileList = uploadRef.value?.files;
+  if (fileList?.length) {
+    try {
+      msgLoading.value = true;
+      msgIsFile.value = true;
+      const res = await useUpload({
+        prefix: QINIU_LIVE.prefix['billd-live/msg-image/'],
+        file: fileList[0],
+      });
+      if (res?.resultUrl) {
+        danmuStr.value = res.resultUrl || '错误图片';
+        sendDanmu();
+      }
+    } catch (error) {
+      console.log(error);
+    } finally {
+      msgIsFile.value = false;
+      msgLoading.value = false;
+      if (uploadRef.value) {
+        uploadRef.value.value = '';
+      }
+    }
+  }
+}
+
 function handleAllType() {
   const types = [
     'video/webm',
@@ -508,32 +607,14 @@ onUnmounted(() => {
   clearFrame();
 });
 
-function initUserMedia() {
-  navigator.mediaDevices
-    .getUserMedia({
-      video: true,
-      audio: false,
-    })
-    .then(() => {
-      console.log('初始化获取摄像头成功');
-    })
-    .catch(() => {
-      console.log('初始化获取摄像头失败');
-    })
-    .finally(() => {
-      navigator.mediaDevices
-        .getUserMedia({
-          video: false,
-          audio: true,
-        })
-        .then(() => {
-          console.log('初始化获取麦克风成功');
-        })
-        .catch(() => {
-          console.log('初始化获取麦克风失败');
-          showOpenMicophoneTipCpt.value = true;
-        });
-    });
+async function initUserMedia() {
+  const res1 = await handleUserMedia({ video: true, audio: false });
+  console.log('初始化获取摄像头成功');
+  const res2 = await handleUserMedia({ video: false, audio: true });
+  console.log('初始化获取麦克风成功');
+  if (!res1 || !res2) {
+    showOpenMicophoneTipCpt.value = true;
+  }
 }
 
 function renderAll() {
@@ -1919,7 +2000,7 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
       position: relative;
       flex: 1;
       box-sizing: border-box;
-      padding: 10px;
+      padding: 10px 10px 0px;
       width: 100%;
       border-radius: 6px;
       background-color: papayawhip;
@@ -1929,46 +2010,110 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
       }
       .list {
         overflow: scroll;
-        height: 360px;
+        height: 300px;
 
-        @extend %hideScrollbar;
+        @extend %customScrollbar;
 
         .item {
-          margin-bottom: 10px;
-          font-size: 12px;
+          box-sizing: border-box;
+          margin-bottom: 4px;
+          padding: 2px 0;
+          white-space: normal;
+          word-wrap: break-word;
+          font-size: 13px;
+
           .name {
             color: #9499a0;
+            cursor: pointer;
+            &.system {
+              color: red;
+            }
           }
           .msg {
+            margin-top: 4px;
             color: #61666d;
+            &.img {
+              img {
+                width: 80%;
+              }
+            }
           }
         }
       }
-
       .send-msg {
-        position: absolute;
-        bottom: 10px;
-        left: 50%;
-        display: flex;
-        align-items: center;
+        position: relative;
         box-sizing: border-box;
-        width: calc(100% - 20px);
-        transform: translateX(-50%);
+        padding: 4px 0;
+        width: 100%;
+        .control {
+          display: flex;
+          margin: 4px 0;
+          .ico {
+            margin-right: 6px;
+            width: 24px;
+            height: 24px;
+            cursor: pointer;
+            .input-upload {
+              width: 0;
+              height: 0;
+              opacity: 0;
+            }
+            &.face {
+              @include setBackground('@/assets/img/msg-face.webp');
+            }
+            &.img {
+              @include setBackground('@/assets/img/msg-img.webp');
+            }
+          }
+        }
         .ipt {
           display: block;
           box-sizing: border-box;
           margin: 0 auto;
-          margin-right: 10px;
           padding: 10px;
-          width: 80%;
-          height: 30px;
+          width: 100%;
+          height: 60px;
           outline: none;
           border: 1px solid hsla(0, 0%, 60%, 0.2);
           border-radius: 4px;
           background-color: #f1f2f3;
           font-size: 14px;
         }
+        .btn {
+          box-sizing: border-box;
+          margin-top: 10px;
+          margin-left: auto;
+          padding: 4px;
+          width: 70px;
+          border-radius: 4px;
+          background-color: $theme-color-gold;
+          color: white;
+          text-align: center;
+          font-size: 12px;
+          cursor: pointer;
+        }
       }
+
+      // .send-msg {
+      //   display: flex;
+      //   align-items: center;
+      //   box-sizing: border-box;
+      //   width: calc(100% - 20px);
+      //   .ipt {
+      //     display: block;
+      //     box-sizing: border-box;
+      //     margin: 0 auto;
+      //     margin-right: 10px;
+      //     padding: 10px;
+      //     width: 80%;
+      //     height: 30px;
+      //     outline: none;
+      //     border: 1px solid hsla(0, 0%, 60%, 0.2);
+      //     border-radius: 4px;
+      //     background-color: #f1f2f3;
+      //     font-size: 14px;
+      //   }
+      // }
     }
   }
 }

+ 5 - 0
src/views/shop/index.vue

@@ -64,6 +64,7 @@ import { useRoute } from 'vue-router';
 
 import { fetchGoodsList } from '@/api/goods';
 import QrPayCpt from '@/components/QrPay/index.vue';
+import { MODULE_CONFIG_SWITCH } from '@/constant';
 import { GoodsTypeEnum, IGoods } from '@/interface';
 import router from '@/router';
 
@@ -120,6 +121,10 @@ function changeTab(type: GoodsTypeEnum) {
 }
 
 function startPay(item: IGoods) {
+  if (!MODULE_CONFIG_SWITCH.pay) {
+    window.$message.info('敬请期待!');
+    return;
+  }
   if (Number(item.price) === 0) {
     window.$message.info('该商品是免费的,不需要购买!');
     return;