gemercheung 2 năm trước cách đây
mục cha
commit
e95993b46c

+ 2 - 2
.env

@@ -5,8 +5,8 @@ VITE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
 VITE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.5.0
 # VITE_APP_SOCKET_URL=wss://ws.gemer.xyz
-# VITE_APP_SOCKET_URL=wss://testeurws.4dkankan.com
-VITE_APP_SOCKET_URL=wss://127.0.0.1:8889
+VITE_APP_SOCKET_URL=wss://testeurws.4dkankan.com
+# VITE_APP_SOCKET_URL=wss://127.0.0.1:8889
 # 静态资源目录
 VITE_APP_STATIC_DIR=viewer
 

+ 2 - 2
.env.development

@@ -4,8 +4,8 @@ VITE_APP_RESOURCE_URL=https://4dkk.4dage.com/
 VITE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
 VITE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.5.0
-VITE_APP_SOCKET_URL=ws://127.0.0.1:8889
-# VITE_APP_SOCKET_URL=wss://testeurws.4dkankan.com
+# VITE_APP_SOCKET_URL=ws://127.0.0.1:8889
+VITE_APP_SOCKET_URL=wss://testws.4dkankan.com
 # VITE_APP_SOCKET_URL=wss://221.4.210.172:16666
 # 静态资源目录
 VITE_APP_STATIC_DIR=viewer

+ 1 - 0
.npmrc

@@ -0,0 +1 @@
+registry=https://mirrors.cloud.tencent.com/npm/

+ 2 - 1
package.json

@@ -29,7 +29,8 @@
     "uuid": "^9.0.0",
     "vue": "^3.2.41",
     "vue-i18n": "^9.2.2",
-    "vue-types": "^4.2.1"
+    "vue-types": "^4.2.1",
+    "vue3-otp-input": "^0.3.6"
   },
   "devDependencies": {
     "@types/node": "^18.11.7",

+ 11 - 0
pnpm-lock.yaml

@@ -43,6 +43,7 @@ specifiers:
   vue-i18n: ^9.2.2
   vue-tsc: ^1.0.9
   vue-types: ^4.2.1
+  vue3-otp-input: ^0.3.6
   windicss: ^3.5.6
 
 dependencies:
@@ -58,6 +59,7 @@ dependencies:
   vue: 3.2.41
   vue-i18n: 9.2.2_vue@3.2.41
   vue-types: 4.2.1_vue@3.2.41
+  vue3-otp-input: 0.3.6_vue@3.2.41
 
 devDependencies:
   '@types/node': 18.11.7
@@ -5860,6 +5862,15 @@ packages:
       '@vue/server-renderer': 3.2.41_vue@3.2.41
       '@vue/shared': 3.2.41
 
+  /vue3-otp-input/0.3.6_vue@3.2.41:
+    resolution: {integrity: sha512-08A/33P3dvk8DBQ6ACJ6ajNCNdkJL2Di5CVVLxcYK7+izM4KFMeWsFnZsx2fD0VfLE7t6jpi+cNgNU83nP7/TQ==}
+    engines: {node: '>=14.0.0', npm: '>=7.0.0'}
+    peerDependencies:
+      vue: ^3.0.*
+    dependencies:
+      vue: 3.2.41
+    dev: false
+
   /w3c-hr-time/1.0.2:
     resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
     dependencies:

+ 6 - 3
src/App.vue

@@ -38,8 +38,8 @@
   } else {
     changeLocale('zh');
   }
-  const maxNumber = import.meta.env.VITE_ROOM_MEMBER || 10;
-  const test_version = computed(() => import.meta.env.VITE_TEST_VERSION || '');
+  // const maxNumber = import.meta.env.VITE_ROOM_MEMBER || 10;
+  // const test_version = computed(() => import.meta.env.VITE_TEST_VERSION || '');
   const sceneStore = useSceneStore();
   const appStore = useAppStore();
   const rtcStore = useRtcStore();
@@ -273,9 +273,12 @@
 
 <template>
   <div class="debug flex justify-between px-1" v-if="showDebug">
-    <span>
+    <!-- <span>
       {{ t('base.debuginfo') }}:公告,当前测试最新: {{ test_version }}, 当前满员条件:
       {{ maxNumber }}
+    </span> -->
+    <span>
+      {{ t('base.debuginfo') }}:公告,当前测试1.1.0正在开发测试中,如要1.0.0请联系开发!
     </span>
     <span class="close" @click.stop="showDebug = false">X</span>
   </div>

+ 41 - 1
src/components/chatRoom/controls/actions.ts

@@ -57,6 +57,9 @@ export function handleActions({
     case 'user-leave':
       handleUserLeave(user, members);
       break;
+    case 'user-exit':
+      handleUserExit(user, members);
+      break;
 
     case 'users-muted':
       handleUserMuted(userId, muted);
@@ -100,6 +103,9 @@ export function handleActions({
       handleRoomStatus(membersStatus);
     default:
       break;
+    case 'user-be-kicked':
+      handleError();
+      break;
   }
   if (members?.length) {
     console.log('服务器的', members);
@@ -323,7 +329,7 @@ const handleUserLeave = (user?: UserInfoType, members?: UserInfoType[]) => {
       mode: '',
       Nickname: name,
       UserId: user.UserId,
-      text: t('action.exitRoom'),
+      text: t('action.leaveRoom'),
     };
 
     console.log('members', user, members);
@@ -340,6 +346,40 @@ const handleUserLeave = (user?: UserInfoType, members?: UserInfoType[]) => {
     members && rtcStore.setMemberList(members);
   }
 };
+
+const handleUserExit = (user?: UserInfoType, members?: UserInfoType[]) => {
+  const { t } = useI18n();
+  const rtcStore = useRtcStore();
+  const isMaxAlert = members && members.length > Number(import.meta.env.VITE_ROOM_MEMBER);
+  console.log('有人退出了', user?.UserId, isMaxAlert);
+  if (user && !isMaxAlert) {
+    let name = user.Nickname;
+    if (user.Role == 'leader') {
+      name = t('action.hoster');
+      Dialog.toast({ content: t('action.hostExitRoom') });
+      rtcStore.setHosterOnline(false);
+    }
+    const data = {
+      role: user.Role,
+      mode: '',
+      Nickname: name,
+      UserId: user.UserId,
+      text: t('action.exitRoom'),
+    };
+
+    if (rtcStore.isLeader) {
+      console.log('主持人看到', data);
+      rtcStore.addToChatList(data);
+      const { muteVideoLeader } = useRtcSdk();
+      muteVideoLeader.value = false;
+    } else {
+      console.log('参与者看到', data);
+      user.Role == 'leader' && rtcStore.addToChatList(data);
+    }
+
+    members && rtcStore.setMemberList(members);
+  }
+};
 //被动处理用离开 全员解散
 const handleLeaderDismiss = () => {
   const { closeSocket } = useSocket();

+ 298 - 0
src/components/chatRoom/dialog/password.vue

@@ -0,0 +1,298 @@
+<template>
+  <div id="opt-password" @click.stop v-if="ifShow">
+    <div class="created_dialog">
+      <div class="blurBox"></div>
+
+      <div class="content">
+        <div class="dialog_title">请输入房间密码</div>
+        <div class="user_name">
+          <v-otp-input
+            class="otp-input-container"
+            ref="otpInput"
+            input-classes="otp-input"
+            separator="-"
+            :num-inputs="4"
+            :should-auto-focus="true"
+            :is-input-num="true"
+            :conditionalClass="['one', 'two', 'three', 'four']"
+            :placeholder="['*', '*', '*', '*']"
+            @on-change="handleOnChange"
+            @on-complete="handleOnComplete"
+          />
+        </div>
+
+        <div class="created_btn">
+          <div class="created_cancel" @click="closeCreated">{{ t('base.cancel') }}</div>
+          <div class="created_confirm" @click="createdConfirm">{{ t('base.confirm') }}</div>
+        </div>
+      </div>
+    </div>
+    <!-- <Cropper v-bind="option" v-if="showCrop" @close="closeCrop" @ok="confirmCrop" /> -->
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, watchEffect } from 'vue';
+
+  // import Dialog from '/@/components/basic/dialog';
+  import { useI18n } from '/@/hooks/useI18n';
+  // import { useSocket } from '/@/hooks/userSocket';
+  import VOtpInput from 'vue3-otp-input';
+  const emit = defineEmits(['closeDialog', 'confirmDialog']);
+  const { t } = useI18n();
+  // const regex = new RegExp('^([\u4E00-\uFA29]|[\uE7C7-\uE7F3]|[a-zA-Z0-9_]){1,15}$');
+
+  const password = ref('');
+  // const rtcStore = useRtcStore();
+  const ifShow = ref(false);
+  const props = defineProps({
+    show: {
+      type: Boolean,
+      default: false,
+    },
+  });
+  watchEffect(() => {
+    if (props.show) {
+      ifShow.value = props.show;
+    }
+  });
+
+  const closeCreated = () => {
+    ifShow.value = false;
+    // const { closeSocket } = useSocket();
+    // emit('closeDialog');
+    // closeSocket();
+    emit('closeDialog');
+  };
+  const createdConfirm = () => {
+    emit('confirmDialog');
+  };
+  const handleOnChange = () => {};
+
+  const handleOnComplete = (value: string) => {
+    console.log('OTP completed: ', value);
+    password.value = value;
+  };
+</script>
+
+<style lang="scss">
+  #opt-password {
+    width: 100vw;
+    height: 100%;
+    // background: rgba(0, 0, 0, 0.5);
+    background: transparent;
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 1000000;
+    // pointer-events: none;
+    .created_dialog {
+      width: 8.64rem;
+      min-height: 5rem;
+      // background: #ffffff;
+      pointer-events: auto;
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      // overflow: hidden;
+      border: 1px solid rgba(255, 255, 255, 0.1);
+      border-radius: 4px;
+
+      .blurBox {
+        position: absolute;
+        z-index: 1;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: rgba(0, 0, 0, 0.7);
+        filter: blur(1px);
+      }
+      .content {
+        position: relative;
+        z-index: 2;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+      }
+      .avatar-box {
+        width: 1.7067rem;
+        height: 1.7067rem;
+        margin: 0.56rem auto 0;
+        border: 1px #fff solid;
+        border-radius: 50%;
+        // background-image: url('@/assets/images/avatar_default.jpg');
+        background-size: 100%;
+        background-repeat: no-repeat;
+        position: relative;
+        overflow: hidden;
+        cursor: pointer;
+        &:hover {
+          border: 1px #ed5d18 solid;
+          .tips {
+            color: #ed5d18;
+          }
+        }
+        .tips {
+          width: 100%;
+          height: 0.5rem;
+          position: absolute;
+          background: rgba(0, 0, 0, 0.5);
+          bottom: 0;
+          left: 0;
+          text-align: center;
+          line-height: 0.5rem;
+          font-size: 0.22rem;
+        }
+        input {
+          width: 100%;
+          height: 100%;
+          opacity: 0;
+          position: relative;
+          z-index: 10;
+          cursor: pointer;
+        }
+      }
+      .dialog_title {
+        font-size: 0.39rem;
+        width: 100%;
+        height: 1.39rem;
+        padding: 0 0.56rem;
+        box-sizing: border-box;
+        font-size: 0.39rem;
+        color: #fff;
+        line-height: 1.39rem;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        border-bottom-style: solid;
+        border-bottom-width: 1px;
+        border-bottom-color: rgba(255, 255, 255, 0.1);
+      }
+      .user_name {
+        width: 100%;
+        height: 1.11rem;
+        padding: 0 0.56rem;
+        box-sizing: border-box;
+        font-size: 0.39rem;
+        line-height: 1.11rem;
+        margin: 0.56rem 0;
+        position: relative;
+        .limitNum {
+          position: absolute;
+          right: 0.64rem;
+          top: 50%;
+          transform: translateY(-50%);
+          font-size: 0.33rem;
+          color: #b9bdbc;
+        }
+        .input_name {
+          font-size: 0.39rem;
+          width: 100%;
+          height: 100%;
+          line-height: 1.11rem;
+          padding: 0 1.066667rem 0 0.28rem;
+          box-sizing: border-box;
+          background: rgba(0, 0, 0, 0.5);
+          border-radius: 4px;
+          color: #fff;
+          border: none;
+          outline: none;
+          &::placeholder {
+            color: rgba(255, 255, 255, 0.3);
+          }
+        }
+      }
+      .mode_btn {
+        width: 100%;
+        height: 1.11rem;
+        padding: 0 0.56rem;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 0.56rem;
+        > div.mode {
+          width: 3.61rem;
+          height: 100%;
+          border-radius: 0.65rem;
+          border: 0.03rem solid #fff;
+          color: #fff;
+          font-size: 0.39rem;
+          line-height: 1.11rem;
+          text-align: center;
+          box-sizing: border-box;
+          &.active {
+            color: #ed5d18;
+            border: 0.03rem solid #ed5d18;
+          }
+        }
+      }
+      .created_btn {
+        width: 100%;
+        height: 1.36rem;
+        border-top-style: solid;
+        border-top-width: 1px;
+        border-top-color: rgba(255, 255, 255, 0.1);
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 0.39rem;
+        > div {
+          width: 50%;
+          height: 1.36rem;
+          text-align: center;
+          line-height: 1.36rem;
+          font-size: 0.39rem;
+          box-sizing: border-box;
+          &.created_cancel {
+            color: #fff;
+            border-right-style: solid;
+            border-right-width: 1px;
+            border-right-color: rgba(255, 255, 255, 0.1);
+          }
+          &.created_confirm {
+            color: #ed5d18;
+          }
+        }
+      }
+    }
+    .otp-input-container {
+      display: flex;
+      width: 100%;
+      flex-direction: row;
+      > div {
+        flex: 1;
+      }
+    }
+    .otp-input {
+      width: 80px;
+      height: 60px;
+      padding: 5px;
+      margin: 0 10px;
+      font-size: 22px;
+      border-radius: 4px;
+      background: rgba(46, 46, 46, 0.9);
+      text-align: center;
+      caret-color: #ed5d18;
+    }
+    /* Background colour of an input field with value */
+    .otp-input.is-complete {
+      // background-color: #f6eaea;
+      background: rgba(46, 46, 46, 0.9);
+    }
+    .otp-input::-webkit-inner-spin-button,
+    .otp-input::-webkit-outer-spin-button {
+      -webkit-appearance: none;
+      margin: 0;
+    }
+    input::placeholder {
+      font-size: 15px;
+      text-align: center;
+      font-weight: 600;
+    }
+  }
+</style>

+ 2 - 0
src/components/chatRoom/index.vue

@@ -144,6 +144,7 @@
     @confirm-dialog="handleCloseRoom"
   />
   <CreatedName :show="showCreateNameDialog" @confirm-dialog="handleNameConfirm" />
+  <PasswordDialog :show="false" />
 </template>
 
 <script lang="ts" setup>
@@ -174,6 +175,7 @@
   import { useRoom, SceneItemType } from '/@/hooks/useRoom';
   import consola from 'consola';
   import CloseDialog from './dialog/close.vue';
+  import PasswordDialog from './dialog/password.vue';
   import ShareContainer from './share.vue';
   import CreatedName from './dialog/createdName.vue';
 

+ 12 - 0
src/components/chatRoom/roomControl.ts

@@ -3,6 +3,7 @@ import consola from 'consola';
 import type { ConsolaLogObject } from 'consola';
 import { handleActions, handleSync, handleReceivePaint, handleJoin } from './controls';
 import type { SocketParams } from '/@/store/modules/rtc';
+import Dialog from '/@/components/basic/dialog';
 // 所有socket业务事件集中点
 
 export function initSocketEvent(socket: SocketIOClient.Socket): void {
@@ -44,6 +45,17 @@ export function initSocketEvent(socket: SocketIOClient.Socket): void {
     socket.on('onAny', (event: any) => {
       console.error('onAny:-->', event);
     });
+    socket.on('manager-error', ({ type, code }) => {
+      switch (type) {
+        case 'repeat-login':
+          Dialog.toast({ content: '你已有正在连接的页面,请勿重复登录!' });
+          break;
+
+        default:
+          Dialog.toast({ content: `错误代码:${code},类型:${type}` });
+          break;
+      }
+    });
     socket.on('error', (error: any) => {
       const actionLog: ConsolaLogObject = {
         message: error,

+ 1 - 0
src/locales/lang/en/action.ts

@@ -8,6 +8,7 @@ export default {
   hostAllowInput: 'The host is unmuted you',
   hostBanInput: 'The host is muted you',
   enterRoom: 'Enter the studio',
+  leaveRoom: 'Leave the studio',
   exitRoom: 'Exit the studio',
   endUpRoom: 'The Livestream is ended',
 };

+ 4 - 2
src/locales/lang/zh/action.ts

@@ -4,10 +4,12 @@ export default {
   hosterCloseDraw: '主持人关闭画笔',
   hosterEnterRoom: '主持人进入房间',
   hosterDismissRoom: '主持人已解散房间',
-  hostExitRoom: '主持人离开了房间',
+  hostLeaveRoom: '主持人离开了房间',
+  hostExitRoom: '主持人退出了房间',
   hostAllowInput: '主持人已解除禁言',
   hostBanInput: '主持人设置了禁言',
   enterRoom: '进入房间',
-  exitRoom: '离开房间',
+  leaveRoom: '离开房间',
+  exitRoom: '退出房间',
   endUpRoom: '带看已结束',
 };