jinx пре 3 година
родитељ
комит
4a580c13e4

+ 12 - 0
package-lock.json

@@ -15,6 +15,7 @@
         "socket.io": "^4.5.1",
         "trtc-js-sdk": "^4.13.0",
         "vue": "^3.2.36",
+        "vue-cropper": "^1.0.5",
         "vuex": "^4.0.2"
       },
       "devDependencies": {
@@ -9230,6 +9231,12 @@
         "@vue/shared": "3.2.36"
       }
     },
+    "node_modules/vue-cropper": {
+      "version": "1.0.5",
+      "resolved": "http://192.168.0.47:4873/vue-cropper/-/vue-cropper-1.0.5.tgz",
+      "integrity": "sha512-D4XXdqWmMWRLOIV9LIh7/mkH6OBOMQDFbRjwntkxmAtxOtwpC9U5ZZ6lSXw5F5cbd4g8znDjk6MuCwIL+fZSrA==",
+      "license": "ISC"
+    },
     "node_modules/vue-hot-reload-api": {
       "version": "2.3.4",
       "resolved": "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
@@ -17165,6 +17172,11 @@
         "@vue/shared": "3.2.36"
       }
     },
+    "vue-cropper": {
+      "version": "1.0.5",
+      "resolved": "http://192.168.0.47:4873/vue-cropper/-/vue-cropper-1.0.5.tgz",
+      "integrity": "sha512-D4XXdqWmMWRLOIV9LIh7/mkH6OBOMQDFbRjwntkxmAtxOtwpC9U5ZZ6lSXw5F5cbd4g8znDjk6MuCwIL+fZSrA=="
+    },
     "vue-hot-reload-api": {
       "version": "2.3.4",
       "resolved": "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "socket.io": "^4.5.1",
     "trtc-js-sdk": "^4.13.0",
     "vue": "^3.2.36",
+    "vue-cropper": "^1.0.5",
     "vuex": "^4.0.2"
   },
   "devDependencies": {

BIN
src/assets/images/avatar_default.png


+ 210 - 285
src/components/RTC/PageRtcLive.vue

@@ -1,4 +1,6 @@
 <template>
+  <div v-if="leaderAvatar && showAvatar" class="avatar-box" :style="`background-image:url(${leaderAvatar});`"></div>
+
   <div id="PageRtcLive">
     <div class="member_number">
       <div class="members"></div>
@@ -6,65 +8,27 @@
     </div>
     <chat v-show="chatShow" :chatList="chatList" :user_info="user_info"></chat>
 
-    <Trtccom
-      :audioMuted="audioMuted"
-      :videoMuted="videoMuted"
-      v-if="isRunRTC"
-    />
+    <Trtccom :audioMuted="audioMuted" :videoMuted="videoMuted" v-if="isRunRTC" />
 
     <div class="contorlBar" v-if="!showInput">
-      <div
-        v-if="connectStatus == 1"
-        :class="{ disabled: !user_info.IsWords }"
-        class="saySomething"
-        @click="onFocus"
-      >
+      <div v-if="connectStatus == 1" :class="{ disabled: !user_info.IsWords }" class="saySomething" @click="onFocus">
         <!-- <i class="speakIcon"
            :class="{'dis':!user_info.IsWords}"></i> -->
         <span v-if="user_info.IsWords">说点什么</span>
         <span v-if="!user_info.IsWords">已被禁言</span>
 
-        <div
-          class="disSpeakBtn"
-          @click.stop="chatShow = !chatShow"
-          :class="{ dis: !chatShow }"
-        ></div>
-      </div>
-      <div style="text-align: right; width: 100%" v-if="connectStatus == 0">
-        连接中...
+        <div class="disSpeakBtn" @click.stop="chatShow = !chatShow" :class="{ dis: !chatShow }"></div>
       </div>
+      <div style="text-align: right; width: 100%" v-if="connectStatus == 0">连接中...</div>
       <div v-if="connectStatus == 1" class="contorl_btn">
-        <div
-          v-if="isBrushes && user_info.Role == 'leader'"
-          @click="onDrawUndo"
-          class="brushesBack"
-          :class="{ disabled: !canUndo }"
-        ></div>
-        <div
-          v-if="user_info.Role == 'leader'"
-          @click="onDraw(!isBrushes)"
-          :class="{ brushesed: isBrushes }"
-          class="brushes"
-        ></div>
-
-        <div
-          v-if="user_list.length < 50"
-          class="invitation"
-          @click="onClickShare"
-        ></div>
+        <div v-if="isBrushes && user_info.Role == 'leader'" @click="onDrawUndo" class="brushesBack" :class="{ disabled: !canUndo }"></div>
+        <div v-if="user_info.Role == 'leader'" @click="onDraw(!isBrushes)" :class="{ brushesed: isBrushes }" class="brushes"></div>
+
+        <div v-if="user_list.length < 50" class="invitation" @click="onClickShare"></div>
         <div v-if="role == 'leader'" class="members" @click="openMember"></div>
         <template v-if="role == 'leader'">
-          <div
-            v-if="!disableMic"
-            @click="handleMuteAduio"
-            :class="{ mic_off: audioMuted, disabled: !audioDeviceId }"
-            class="mic_on"
-          ></div>
-          <div
-            v-if="disableMic"
-            class="mic_no"
-            :class="{ disabled: !audioDeviceId }"
-          ></div>
+          <div v-if="!disableMic" @click="handleMuteAduio" :class="{ mic_off: audioMuted, disabled: !audioDeviceId }" class="mic_on"></div>
+          <div v-if="disableMic" class="mic_no" :class="{ disabled: !audioDeviceId }"></div>
         </template>
         <!-- <div
           v-if="role == 'leader'"
@@ -80,29 +44,15 @@
     <div class="layer" v-if="showInput" @click="closeInput">
       <div class="inputBox" @click.stop>
         <div class="msgBox">
-          <input
-            id="input_msg"
-            type="text"
-            maxlength="200"
-            v-model.trim="text"
-            :placeholder="`说点什么~`"
-          />
-          <span
-            class="iconsend_icon"
-            :class="{ disable: text == '' }"
-            @click.stop="sendText"
-            >发送</span
-          >
+          <input id="input_msg" type="text" maxlength="200" v-model.trim="text" :placeholder="`说点什么~`" />
+          <span class="iconsend_icon" :class="{ disable: text == '' }" @click.stop="sendText">发送</span>
         </div>
       </div>
     </div>
 
     <!-- 成員 -->
     <div class="layer" v-if="showMember" @click.self="closeMember">
-      <div
-        class="memberContent animated"
-        :class="animateActive ? 'fadeInUpBig' : 'fadeOutDownBig'"
-      >
+      <div class="memberContent animated" :class="animateActive ? 'fadeInUpBig' : 'fadeOutDownBig'">
         <div class="blurBox"></div>
         <div class="content">
           <div class="memberHeader">
@@ -113,16 +63,9 @@
             <div class="memberItem">
               <div class="userMsg">
                 <div class="avatar">
-                  <img
-                    :src="
-                      require('@/assets/images/rtcLive/avatar_small@2x.png')
-                    "
-                    alt=""
-                  />
-                </div>
-                <div class="name" v-if="user_info.Role == 'leader'">
-                  {{ user_info.Nickname }} (主持人,我)
+                  <img :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
                 </div>
+                <div class="name" v-if="user_info.Role == 'leader'">{{ user_info.Nickname }} (主持人,我)</div>
                 <div class="name" v-else>{{ user_info.Nickname }} (我)</div>
               </div>
 
@@ -135,30 +78,15 @@
               </div> -->
             </div>
 
-            <div
-              v-show="user_info.UserId != i.UserId && i.Role != 'leader'"
-              class="memberItem"
-              v-for="(i, idx) in user_list"
-              :key="idx"
-            >
+            <div v-show="user_info.UserId != i.UserId && i.Role != 'leader'" class="memberItem" v-for="(i, idx) in user_list" :key="idx">
               <div class="userMsg">
                 <div class="avatar">
-                  <img
-                    :src="
-                      require('@/assets/images/rtcLive/avatar_small@2x.png')
-                    "
-                    alt=""
-                  />
+                  <img :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
                 </div>
                 <div class="name">{{ i.Nickname }}</div>
               </div>
               <div class="button" v-if="user_info.Role == 'leader'">
-                <div
-                  class="micBtn"
-                  :class="i.IsWords ? 'ban_speak_on' : 'ban_speak_off'"
-                  :wo="i.IsWords"
-                  @click="userCanSpeak(i)"
-                ></div>
+                <div class="micBtn" :class="i.IsWords ? 'ban_speak_on' : 'ban_speak_off'" :wo="i.IsWords" @click="userCanSpeak(i)"></div>
                 <!-- <div
                   class="micBtn"
                   :class="i.IsMuted ? 'mute_one_mic_off' : 'mute_one_mic_on'"
@@ -174,92 +102,74 @@
 
   <teleport :to="`#app`">
     <div v-if="showShare" @click="showShare = false" class="sharetip">
-      <img
-        @click.stop
-        :style="`right:${isMP ? '16%' : '6%'}`"
-        :src="require('@/assets/images/icon/img_scene_share.png')"
-        alt=""
-      />
+      <img @click.stop :style="`right:${isMP ? '16%' : '6%'}`" :src="require('@/assets/images/icon/img_scene_share.png')" alt="" />
     </div>
   </teleport>
 </template>
 
 <script setup>
-import {
-  onMounted,
-  onUnmounted,
-  watch,
-  defineEmits,
-  ref,
-  reactive,
-  computed,
-  nextTick,
-} from "vue";
-import { useApp, getApp } from "@/app";
-import { useStore } from "vuex";
-import { Dialog } from "@/global_components/";
-import common from "@/utils/common";
-
-import { mapGetters } from "vuex";
-import chat from "./chat/chat.vue";
-import Trtccom from "./Trtccom.vue";
-import browser from "@/utils/browser";
-import wxShare from "@/utils/wxshare";
-
-const emit = defineEmits(["openDialog", "closeSocket"]);
+import { onMounted, onUnmounted, watch, defineEmits, ref, reactive, computed, nextTick } from 'vue';
+import { useApp, getApp } from '@/app';
+import { useStore } from 'vuex';
+import { Dialog } from '@/global_components/';
+import common from '@/utils/common';
+
+import { mapGetters } from 'vuex';
+import chat from './chat/chat.vue';
+import Trtccom from './Trtccom.vue';
+import browser from '@/utils/browser';
+import wxShare from '@/utils/wxshare';
+import defaultAvatar from '@/assets/images/avatar_default.png';
+const emit = defineEmits(['openDialog', 'closeSocket']);
 
 const store = useStore();
+const leaderAvatar = computed(() => {
+  return store.getters['rtc/avatar'] ? store.getters['rtc/avatar'] : localStorage.getItem('leaderAvatar') ? localStorage.getItem('leaderAvatar') : defaultAvatar;
+});
 
 let jumpNewScene = (sceneFirstView) => {
   let url = window.location.href;
 
-  if (!browser.hasURLParam("pose")) {
+  if (!browser.hasURLParam('pose')) {
     url += `&${sceneFirstView.sceneview}`;
   } else {
-    url = browser.replaceQueryString(
-      url,
-      "pose",
-      sceneFirstView.sceneview.replace("pose=", "")
-    );
+    url = browser.replaceQueryString(url, 'pose', sceneFirstView.sceneview.replace('pose=', ''));
   }
 
-  url = browser.replaceQueryString(url, "m", sceneFirstView.num);
+  url = browser.replaceQueryString(url, 'm', sceneFirstView.num);
   return url;
 };
 
 let chatAutoScroll = () => {
-  let el = document.getElementById("chat");
-  let client_h = document.getElementById("chat").clientHeight;
-  let all = document.getElementById("contents").clientHeight;
+  let el = document.getElementById('chat');
+  let client_h = document.getElementById('chat').clientHeight;
+  let all = document.getElementById('contents').clientHeight;
   el.scrollTo(0, client_h + all);
 };
 
 let createSocket = (config) => {
   var socket = io(process.env.VUE_APP_SOCKET_URL, {
-    path: "/ws-sync",
-    transports: ["websocket"],
+    path: '/ws-sync',
+    transports: ['websocket'],
   });
   return socket;
 };
 
-store.commit("rtc/setSocket", createSocket());
+store.commit('rtc/setSocket', createSocket());
 
 let getUrl = (href, queryArr) => {
   queryArr.forEach((item) => {
     if (!browser.hasURLParam(item.key)) {
-      let ttt = href.split("index.html?");
+      let ttt = href.split('index.html?');
       href = `${ttt[0]}index.html?${item.key}=${item.val}&${ttt[1]}`;
-      console.log(
-        href,
-        "------index.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.html----------"
-      );
+      console.log(href, '------index.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.html----------');
     } else {
       href = browser.replaceQueryString(href, item.key, item.val);
     }
   });
 
-  if (href.indexOf("customer") != -1) {
-    return href.replace("&sync=1", "");
+  if (href.indexOf('customer') != -1) {
+    return href.replace('&sync=1', '');
   }
 
   return href;
@@ -282,17 +192,17 @@ const isMP = ref(false);
 const audioMuted = ref(false);
 const videoMuted = ref(false);
 
-const socket = computed(() => store.getters["rtc/socket"]);
+const socket = computed(() => store.getters['rtc/socket']);
 
 const myVideoShow = ref(false);
 const userVideoShow = ref(false);
 const user_info = ref({});
 const user_list = ref([]);
-const mode = ref(browser.getURLParam("mode"));
-const role = ref(browser.getURLParam("role"));
+const mode = ref(browser.getURLParam('mode'));
+const role = ref(browser.getURLParam('role'));
 
-const userId = computed(() => store.getters["rtc/userId"]);
-const roomId = computed(() => store.getters["rtc/roomId"]);
+const userId = computed(() => store.getters['rtc/userId']);
+const roomId = computed(() => store.getters['rtc/roomId']);
 
 const isJoined = ref(false);
 
@@ -301,9 +211,9 @@ const isRunRTC = ref(false);
 const paint = reactive({});
 
 const chatList = ref([]);
-const text = ref("");
+const text = ref('');
 
-const shareLink = ref("");
+const shareLink = ref('');
 const canUndo = ref(false);
 const audioDevices = ref([1]);
 const videoDevices = ref([1]);
@@ -312,17 +222,17 @@ const chatShow = ref(true);
 const all_mute_mic = ref(false);
 
 const tags = computed(() => {
-  return store.getters["tag/tags"] || [];
+  return store.getters['tag/tags'] || [];
 });
 
 const onClickShare = () => {
-  openDialog("dialogShare", shareLink.value);
+  openDialog('dialogShare', shareLink.value);
 };
 
 const userGetOut = (item, i) => {
   socket.value &&
-    socket.value.emit("action", {
-      type: "getout",
+    socket.value.emit('action', {
+      type: 'getout',
       data: {
         id: item.UserId,
       },
@@ -335,9 +245,7 @@ const setUserWords = (res) => {
   if (res.userId == user_info.value.UserId) {
     user_info.value.IsWords = res.words;
     Dialog.toast({
-      content: !user_info.value.IsWords
-        ? `主持人设置了禁言`
-        : `主持人已解除禁言`,
+      content: !user_info.value.IsWords ? `主持人设置了禁言` : `主持人已解除禁言`,
     });
   }
 };
@@ -348,9 +256,9 @@ const handleMuteVideo = () => {
 
 const handleMuteAduio = () => {
   audioMuted.value = !audioMuted.value;
-  if (role.value == "leader") {
-    socket.value.emit("action", {
-      type: "users-muted",
+  if (role.value == 'leader') {
+    socket.value.emit('action', {
+      type: 'users-muted',
       muted: audioMuted.value,
       userId: user_info.value.UserId,
     });
@@ -359,12 +267,10 @@ const handleMuteAduio = () => {
 
 const setUserMuted = (res) => {
   if (res.userId) {
-    if (res.userId == user_info.value.UserId && role.value == "customer") {
+    if (res.userId == user_info.value.UserId && role.value == 'customer') {
       user_info.value.IsMuted = res.muted;
       Dialog.toast({
-        content: !user_info.value.IsMuted
-          ? `主持人设置了开麦`
-          : `主持人设置了静音`,
+        content: !user_info.value.IsMuted ? `主持人设置了开麦` : `主持人设置了静音`,
       });
       disableMic.value = res.muted;
       audioMuted.value = res.muted;
@@ -375,8 +281,8 @@ const setUserMuted = (res) => {
 };
 
 const setAllMuted = (data) => {
-  console.log(data, "IsMuted");
-  socket.value.emit("action", { type: "users-muted", muted: data });
+  console.log(data, 'IsMuted');
+  socket.value.emit('action', { type: 'users-muted', muted: data });
 };
 
 const onAllMuted = (res) => {
@@ -387,18 +293,16 @@ const onAllMuted = (res) => {
     return tempArr;
   }, []);
 
-  if (role.value == "leader") {
+  if (role.value == 'leader') {
     all_mute_mic.value = res.muted;
   }
 
   user_list.value.forEach((item) => {
     user_info.value.IsMuted = res.muted;
     item.IsMuted = res.muted;
-    if (role.value == "customer") {
+    if (role.value == 'customer') {
       Dialog.toast({
-        content: !user_info.value.IsMuted
-          ? `主持人设置了开麦`
-          : `主持人设置了静音`,
+        content: !user_info.value.IsMuted ? `主持人设置了开麦` : `主持人设置了静音`,
       });
       disableMic.value = res.muted;
       audioMuted.value = res.muted;
@@ -408,7 +312,7 @@ const onAllMuted = (res) => {
 
 //用戶加入
 const setUserJoin = async (res) => {
-  console.log("有人进来了", res);
+  console.log('有人进来了', res);
   // self.user_info = res.user;
   user_list.value = res.members.reduce(function (tempArr, item) {
     if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
@@ -418,17 +322,18 @@ const setUserJoin = async (res) => {
   }, []);
   // self.chekcLeaderInfo();
   let name = res.user.Nickname;
-  if (res.user.Role == "leader") {
-    name = "主持人";
+  if (res.user.Role == 'leader') {
+    name = '主持人';
     Dialog.toast({ content: `主持人进入房间` });
-    socket.value.emit("action", { type: "user-init" });
+    socket.value.emit('action', { type: 'user-init' });
+    store.commit('rtc/setAvatar',res.user.Avatar)
   }
   let data = {
     role: res.user.Role,
     mode: mode.value,
     Nickname: name,
     UserId: res.user.UserId,
-    text: "进入房间",
+    text: '进入房间',
   };
   if (role.value == "leader") {
     chatList.value.push(data);
@@ -445,7 +350,7 @@ const onDrawUndo = async () => {
   app.Connect.paint.undo();
   canUndo.value = app.Connect.paint.records.length > 0;
 
-  console.log(app.Connect.paint.records, "app.Connect.paint.records");
+  console.log(app.Connect.paint.records, 'app.Connect.paint.records');
 };
 
 // 畫筆開啟
@@ -454,15 +359,15 @@ const onDraw = async (status) => {
   if (isBrushes.value) {
     await getApp().Connect.paint.show({
       role: role.value,
-      paint: role.value == "leader" ? true : false,
+      paint: role.value == 'leader' ? true : false,
     });
-    if (role.value == "leader") {
-      socket.value.emit("action", { type: "user-paint", open: true });
+    if (role.value == 'leader') {
+      socket.value.emit('action', { type: 'user-paint', open: true });
     }
   } else {
     await getApp().Connect.paint.hide();
-    if (role.value == "leader") {
-      socket.value.emit("action", { type: "user-paint", open: false });
+    if (role.value == 'leader') {
+      socket.value.emit('action', { type: 'user-paint', open: false });
     }
   }
 };
@@ -471,14 +376,14 @@ const onDraw = async (status) => {
 const onFocus = () => {
   showInput.value = true;
   nextTick(() => {
-    let input_msg = document.getElementById("input_msg");
+    let input_msg = document.getElementById('input_msg');
     input_msg.focus();
   });
 };
 
 //發彈幕
 const sendText = async () => {
-  if (text.value == "") {
+  if (text.value == '') {
     return;
   }
   let data = {
@@ -489,8 +394,8 @@ const sendText = async () => {
     text: text.value,
   };
   socket.value &&
-    socket.value.emit("action", {
-      type: "danmumsg",
+    socket.value.emit('action', {
+      type: 'danmumsg',
       data,
     });
 
@@ -498,7 +403,7 @@ const sendText = async () => {
   await nextTick();
   try {
     chatAutoScroll();
-    let input_msg = document.getElementById("input_msg");
+    let input_msg = document.getElementById('input_msg');
     input_msg.blur();
   } catch (error) {}
   closeInput();
@@ -507,8 +412,8 @@ const sendText = async () => {
 //接收消息
 const setReceiveMsg = async (res) => {
   console.log(res);
-  if (res.role == "leader") {
-    res.Nickname = "主持人";
+  if (res.role == 'leader') {
+    res.Nickname = '主持人';
   }
   chatList.value.push(res);
   await nextTick();
@@ -520,7 +425,7 @@ const setReceiveMsg = async (res) => {
 // 關閉輸入框
 const closeInput = () => {
   showInput.value = false;
-  text.value = "";
+  text.value = '';
 };
 
 // 開啟成員
@@ -538,20 +443,20 @@ const closeMember = () => {
 };
 
 const openDialog = (str, link) => {
-  emit("openDialog", str, link);
+  emit('openDialog', str, link);
 };
 
 const onMemberMuted = (item) => {
   item.IsMuted = !item.IsMuted;
-  socket.value.emit("action", {
-    type: "users-muted",
+  socket.value.emit('action', {
+    type: 'users-muted',
     muted: item.IsMuted,
     userId: item.UserId,
   });
 };
 
 const onMemberLeave = async (res) => {
-  console.log("有人离开了", res);
+  console.log('有人离开了', res);
   user_list.value = res.members.reduce(function (tempArr, item) {
     if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
       tempArr.push(item);
@@ -559,8 +464,8 @@ const onMemberLeave = async (res) => {
     return tempArr;
   }, []);
   let name = res.user.Nickname;
-  if (res.user.Role == "leader") {
-    name = "主持人";
+  if (res.user.Role == 'leader') {
+    name = '主持人';
     Dialog.toast({ content: `主持人离开了房间` });
   }
   let data = {
@@ -568,7 +473,7 @@ const onMemberLeave = async (res) => {
     mode: mode.value,
     Nickname: name,
     UserId: res.user.UserId,
-    text: "离开房间",
+    text: '离开房间',
   };
   if (role.value == "leader") {
     chatList.value.push(data);
@@ -582,8 +487,8 @@ const onMemberLeave = async (res) => {
 
 const userCanSpeak = (item) => {
   item.IsWords = !item.IsWords;
-  socket.value.emit("action", {
-    type: "users-words",
+  socket.value.emit('action', {
+    type: 'users-words',
     words: item.IsWords,
     userId: item.UserId,
   });
@@ -591,7 +496,7 @@ const userCanSpeak = (item) => {
 
 const onGetOuT = (data) => {
   if (data.id == user_info.value.UserId) {
-    emit("closeSocket");
+    emit('closeSocket');
     Dialog.toast({ content: `您已被移除` });
   }
 };
@@ -599,42 +504,54 @@ const onGetOuT = (data) => {
 watch(
   user_list,
   () => {
-    if (role.value == "leader") {
-      all_mute_mic.value = !user_list.value.some(
-        (item) => !item.IsMuted && item.Role == "customer"
-      );
+    if (role.value == 'leader') {
+      all_mute_mic.value = !user_list.value.some((item) => !item.IsMuted && item.Role == 'customer');
     }
   },
   {
     deep: true,
   }
 );
+const showAvatar = ref(false);
 
 const startFollow = (app) => {
-  app.Connect.follow.start({ follow: role.value == "customer" });
-
-  store.commit(
-    "rtc/setUserId",
-    browser.getURLParam("vruserId") ||
-      `user_${role.value}${Math.floor(Math.random() * 100000000)}`
-  );
-  store.commit(
-    "rtc/setRoomId",
-    browser.getURLParam("roomId") ||
-      `room_${Math.floor(Math.random() * 100000000)}`
-  );
-
-  socket.value.on("connect", (e) => {
-    socket.value.emit("join", {
+  app.Connect.follow.start({ follow: role.value == 'customer' });
+
+  store.commit('rtc/setUserId', browser.getURLParam('vruserId') || `user_${role.value}${Math.floor(Math.random() * 100000000)}`);
+  store.commit('rtc/setRoomId', browser.getURLParam('roomId') || `room_${Math.floor(Math.random() * 100000000)}`);
+
+  socket.value.on('connect', (e) => {
+    let params = {
       userId: userId.value,
       roomId: roomId.value,
-      role: role.value || "leader",
-      nickname: browser.getURLParam("name"),
-    });
+      role: role.value || 'leader',
+      nickname: browser.getURLParam('name'),
+      // avatar: leaderAvatar.value,
+    };
+    if (role.value == 'leader') {
+      params.avatar = leaderAvatar.value;
+    }
+    socket.value.emit('join', params);
   });
 
+  const leaderInfo = ref({});
   // 加入房間成功
-  socket.value.on("join", (data) => {
+  socket.value.on('join', (data) => {
+    // leaderInfo.value = data.members.filter((item) => item.Role == 'leader');
+
+    if (role.value == 'customer') {
+      data.members.forEach((item) => {
+        if (item.Role == 'leader') {
+          leaderInfo.value = item;
+          showAvatar.value = true;
+          store.commit('rtc/setAvatar', leaderInfo.value.Avatar);
+        }
+      });
+    } else {
+      showAvatar.value = true;
+      console.error(showAvatar.value);
+    }
+
     let meblist = data.members.reduce(function (tempArr, item) {
       if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
         tempArr.push(item);
@@ -642,17 +559,17 @@ const startFollow = (app) => {
       return tempArr;
     }, []);
 
-    if (meblist.length > 50 && role.value == "customer") {
+    if (meblist.length > 50 && role.value == 'customer') {
       Dialog.toast({ content: `房间已满员` });
-      emit("closeSocket");
+      emit('closeSocket');
       return;
     }
 
     connectStatus.value = 1;
-    if (role.value == "customer") {
-      socket.value.emit("action", { type: "ask-currentscene" });
+    if (role.value == 'customer') {
+      socket.value.emit('action', { type: 'ask-currentscene' });
       setTimeout(() => {
-        socket.value.emit("action", { type: "user-init" });
+        socket.value.emit('action', { type: 'user-init' });
       }, 1500);
 
       if (data.user.IsMuted) {
@@ -677,63 +594,63 @@ const startFollow = (app) => {
     //更新分享鏈接
     shareLink.value = getUrl(window.location.href, [
       {
-        key: "mode",
+        key: 'mode',
         val: mode.value,
       },
       {
-        key: "name",
-        val: "",
+        key: 'name',
+        val: '',
       },
       {
-        key: "vruserId",
-        val: "",
+        key: 'vruserId',
+        val: '',
       },
       {
-        key: "role",
-        val: "customer",
+        key: 'role',
+        val: 'customer',
       },
       {
-        key: "roomId",
+        key: 'roomId',
         val: user_info.value.RoomId,
       },
     ]);
 
-    let tmp = "";
+    let tmp = '';
 
-    if (user_info.value.Role == "leader") {
+    if (user_info.value.Role == 'leader') {
       tmp = getUrl(window.location.href, [
         {
-          key: "roomId",
+          key: 'roomId',
           val: user_info.value.RoomId,
         },
         {
-          key: "vruserId",
+          key: 'vruserId',
           val: user_info.value.UserId,
         },
       ]);
     } else {
       tmp = getUrl(window.location.href, [
         {
-          key: "vruserId",
+          key: 'vruserId',
           val: user_info.value.UserId,
         },
       ]);
     }
 
-    store.commit("rtc/setRole", user_info.value.Role);
+    store.commit('rtc/setRole', user_info.value.Role);
     history.replaceState(null, null, tmp);
   });
 
-  socket.value.on("action", (data) => {
-    console.log(data, "=============");
-    if (data.type == "error") {
-      Dialog.toast({ content: `房间未找到`, type: "error" });
-      emit("closeSocket");
-    } else if (data.type == "danmumsg") {
+  socket.value.on('action', (data) => {
+    console.log(data, '=============');
+    if (data.type == 'error') {
+      Dialog.toast({ content: `房间未找到`, type: 'error' });
+      emit('closeSocket');
+    } else if (data.type == 'danmumsg') {
       setReceiveMsg(data.data);
-    } else if (data.type == "getout") {
+    } else if (data.type == 'getout') {
       onGetOuT(data.data);
-    } else if (data.type == "user-init") {
+    } else if (data.type == 'user-init') {
       app.Connect.follow.sync();
       if (role.value == "leader") {
         setTimeout(() => {
@@ -759,39 +676,39 @@ const startFollow = (app) => {
       }
     } else if (data.type == "user-paint") {
       onDraw(data.open);
-      if (role.value == "customer") {
+      if (role.value == 'customer') {
         if (data.open) {
           Dialog.toast({ content: `主持人开启画笔` });
         } else {
           Dialog.toast({ content: `主持人关闭画笔` });
         }
       }
-    } else if (data.type == "user-join") {
+    } else if (data.type == 'user-join') {
       setUserJoin(data);
-    } else if (data.type == "users-muted") {
+    } else if (data.type == 'users-muted') {
       setUserMuted(data);
       //閉麥
-    } else if (data.type == "users-words") {
+    } else if (data.type == 'users-words') {
       setUserWords(data);
       //禁言
-    } else if (data.type == "user-leave") {
+    } else if (data.type == 'user-leave') {
       // 房間解散
       onMemberLeave(data);
-    } else if (data.type == "leader-dismiss") {
-      emit("closeSocket");
+    } else if (data.type == 'leader-dismiss') {
+      emit('closeSocket');
       Dialog.toast({ content: `主持人已解散房间` });
-    } else if (data.type == "tagclick") {
-      if (role.value == "customer") {
+    } else if (data.type == 'tagclick') {
+      if (role.value == 'customer') {
         let item = tags.value.find((item) => item.sid == data.data.sid);
         store.commit("tag/setTagClickType", {
           type: "goodlist",
           data: item,
         });
       }
-    } else if (data.type == "tagclose") {
-      if (role.value == "customer") {
-        store.commit("tag/setTagClickType", {
-          type: "",
+    } else if (data.type == 'tagclose') {
+      if (role.value == 'customer') {
+        store.commit('tag/setTagClickType', {
+          type: '',
           data: {},
         });
       }
@@ -804,20 +721,16 @@ const startFollow = (app) => {
         socket.value.emit("action", {
           type: "answer-currentscene",
           data: {
-            scene: browser.getURLParam("m"),
-            pose: browser.getURLParam("pose"),
+            scene: browser.getURLParam('m'),
+            pose: browser.getURLParam('pose'),
           },
         });
       }
-    } else if (data.type == "answer-currentscene") {
-      if (role.value == "customer") {
-        if (data.data.scene != browser.getURLParam("m")) {
-          let url1 = browser.replaceQueryString(
-            window.location.href,
-            "m",
-            data.data.scene
-          );
-          let url = browser.replaceQueryString(url1, "pose", data.data.pose);
+    } else if (data.type == 'answer-currentscene') {
+      if (role.value == 'customer') {
+        if (data.data.scene != browser.getURLParam('m')) {
+          let url1 = browser.replaceQueryString(window.location.href, 'm', data.data.scene);
+          let url = browser.replaceQueryString(url1, 'pose', data.data.pose);
           location.replace(url);
         }
       }
@@ -825,45 +738,59 @@ const startFollow = (app) => {
   });
 
   // 同屏帶看
-  socket.value.on("sync", (data) => {
-    if (role.value == "customer") {
+  socket.value.on('sync', (data) => {
+    if (role.value == 'customer') {
       app.Connect.follow.receive(data);
     }
   });
 
   // 畫筆
-  socket.value.on("paint", (data) => {
-    if (role.value == "customer") {
+  socket.value.on('paint', (data) => {
+    if (role.value == 'customer') {
       app.Connect.paint.receive(data);
     }
   });
 };
 let onfollowData = (data) => {
   if (isJoined.value) {
-    socket.value.emit("sync", data);
+    socket.value.emit('sync', data);
   }
 };
 
 let onfollowPaint = async (data) => {
   canUndo.value = (await getApp().Connect.paint.records.length) > 0;
-  socket.value.emit("paint", data);
+  socket.value.emit('paint', data);
 };
 
 onMounted(async () => {
   let app = await getApp();
   startFollow(app);
-  app.Connect.follow.on("data", onfollowData);
-  app.Connect.paint.on("data", onfollowPaint);
+  app.Connect.follow.on('data', onfollowData);
+  app.Connect.paint.on('data', onfollowPaint);
 });
 
 onUnmounted(async () => {
   let app = await getApp();
-  app.Connect.follow.off("data", onfollowData);
-  app.Connect.follow.off("data", onfollowData);
+  app.Connect.follow.off('data', onfollowData);
+  app.Connect.follow.off('data', onfollowData);
 });
 </script>
 
 <style scoped lang="scss">
+.avatar-box {
+  width: 1.7067rem;
+  height: 1.7067rem;
+  margin: 0.56rem auto 0;
+  border: 1px #ed5d18 solid;
+  border-radius: 50%;
+  // background-image: url('@/assets/images/avatar_default.jpg');
+  background-size: 100%;
+  background-repeat: no-repeat;
+  position: fixed;
+  top: 0.2667rem;
+  left: 0.2667rem;
+  z-index: 1000;
+}
 #PageRtcLive {
   position: absolute;
   left: 0;
@@ -935,13 +862,11 @@ onUnmounted(async () => {
       .disSpeakBtn {
         width: 0.533333rem;
         height: 0.533333rem;
-        background: url(~@/assets/images/rtcLive/pop-up_screen_on@2x.png)
-          no-repeat;
+        background: url(~@/assets/images/rtcLive/pop-up_screen_on@2x.png) no-repeat;
         background-size: 100% 100%;
         cursor: pointer;
         &.dis {
-          background: url(~@/assets/images/rtcLive/pop-up_screen_off@2x.png)
-            no-repeat;
+          background: url(~@/assets/images/rtcLive/pop-up_screen_off@2x.png) no-repeat;
           background-size: 100% 100%;
         }
       }

+ 108 - 36
src/components/RTC/dialog/createdRoom.vue

@@ -5,14 +5,12 @@
       <div class="content">
         <div class="dialog_title" v-if="role == 'leader'">创建一起逛</div>
         <div class="dialog_title" v-else>进入一起逛</div>
+        <div class="avatar-box" v-if="role == 'leader'" :style="`background-image:url(${avatar});`">
+          <input type="file" @change="changeFile($event)" accept=".jpg,.png" />
+          <div class="tips">更换</div>
+        </div>
         <div class="user_name">
-          <input
-            class="input_name"
-            maxlength="20"
-            v-model.trim="userName"
-            type="text"
-            :placeholder="role == 'leader' ? ' 请输入发起人昵称' : '请输入您的昵称'"
-          />
+          <input class="input_name" maxlength="20" v-model.trim="userName" type="text" :placeholder="role == 'leader' ? ' 请输入发起人昵称' : '请输入您的昵称'" />
           <span class="limitNum">{{ userName.length }}/20</span>
         </div>
         <!-- <div v-if="role!='customer'" class="mode_btn">
@@ -24,44 +22,80 @@
         </div>
       </div>
     </div>
+    <Cropper v-bind="option" v-if="showCrop" @close="closeCrop" @ok="confirmCrop" />
   </div>
 </template>
 
 <script>
-import { Dialog } from "@/global_components/";
-import browser from "@/utils/browser";
-import { useStore } from "vuex";
-
+import { Dialog } from '@/global_components/';
+import browser from '@/utils/browser';
+import { useStore } from 'vuex';
+import Cropper from '@/components/cropper/cropper.vue';
+// import defaultAvatar from '@/assets/images/avatar_default.jpg';
 export default {
   data() {
     return {
-      role: browser.getURLParam("role") || "leader",
-      mode: browser.getURLParam("mode") || 2,
+      role: browser.getURLParam('role') || 'leader',
+      mode: browser.getURLParam('mode') || 2,
       modeList: [
         {
           mode: 1,
-          title: "1V1",
+          title: '1V1',
         },
         {
           mode: 2,
-          title: "多人模式",
+          title: '多人模式',
         },
       ],
-      store:useStore(),
-      userName: "",
-      roomId: browser.getURLParam("roomId"),
+      store: useStore(),
+      userName: '',
+      roomId: browser.getURLParam('roomId'),
+      showCrop: false,
+      // base64: null,
+      defaultAvatar: require('@/assets/images/avatar_default.png'),
+      // avatar: null,
+      option: {
+        // img: 'https://4dkk.4dage.com/scene_edit_data/KK-t-SfG2Xcb8QX/user/thumb-1k.jpg?_=1661768330305',
+        img: '',
+      },
     };
   },
 
-  mounted() {},
-  components: {},
+  mounted() {
+    // this.avatar = this.base64 || this.defaultAvatar;
+  },
+  computed: {
+    avatar: function () {
+      return this.$store.getters['rtc/avatar'] ? this.$store.getters['rtc/avatar'] : localStorage.getItem('leaderAvatar') ? localStorage.getItem('leaderAvatar') : this.defaultAvatar;
+    },
+  },
+  components: { Cropper },
   // created: {},
   // mounted:{},
   methods: {
+    changeFile(e) {
+      let file = e.target.files[0];
+
+      let blob = window.URL.createObjectURL(file);
+      console.log(blob);
+      this.option.img = blob;
+      this.openCrop();
+      e.target.value = '';
+    },
+    confirmCrop(base64) {
+      this.$store.commit('rtc/setAvatar', base64);
+      localStorage.setItem('leaderAvatar', base64);
+    },
+    openCrop() {
+      this.showCrop = true;
+    },
+    closeCrop() {
+      this.showCrop = false;
+    },
     getUrl(href, queryArr) {
       queryArr.forEach((item) => {
         if (!browser.hasURLParam(item.key)) {
-          let ttt = href.split("index.html?");
+          let ttt = href.split('index.html?');
           href = `${ttt[0]}index.html?${item.key}=${item.val}&${ttt[1]}`;
         } else {
           href = browser.replaceQueryString(href, item.key, item.val);
@@ -75,32 +109,32 @@ export default {
       this.mode = mode;
     },
     closeCreated() {
-      this.$emit("closeCreated");
+      this.$emit('closeCreated');
     },
     createdConfirm() {
-      if (this.userName == "") {
-        Dialog.toast({ content: "请输入入您的昵称", type: "error" });
+      if (this.userName == '') {
+        Dialog.toast({ content: '请输入入您的昵称', type: 'error' });
         return;
       }
       let name = encodeURIComponent(this.userName);
       let hh = window.location.href;
 
-      if (this.role == "customer") {
+      if (this.role == 'customer') {
         let tempUrl = this.getUrl(hh, [
           {
-            key: "mode",
+            key: 'mode',
             val: this.mode,
           },
           {
-            key: "name",
+            key: 'name',
             val: name,
           },
           {
-            key: "role",
-            val: "customer",
+            key: 'role',
+            val: 'customer',
           },
           {
-            key: "roomId",
+            key: 'roomId',
             val: this.roomId,
           },
         ]);
@@ -109,16 +143,16 @@ export default {
       } else {
         let tempUrl = this.getUrl(hh, [
           {
-            key: "mode",
+            key: 'mode',
             val: this.mode,
           },
           {
-            key: "name",
+            key: 'name',
             val: name,
           },
           {
-            key: "role",
-            val: "leader",
+            key: 'role',
+            val: 'leader',
           },
         ]);
 
@@ -126,10 +160,10 @@ export default {
         history.replaceState(null, null, tempUrl);
         console.log(tempUrl);
       }
-      this.store.commit("rtc/setRole", this.role);
+      this.store.commit('rtc/setRole', this.role);
 
       this.$nextTick(() => {
-        this.$emit("createdConfirm");
+        this.$emit('createdConfirm');
       });
     },
   },
@@ -178,6 +212,44 @@ export default {
       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%;

+ 42 - 39
src/components/RTC/index.vue

@@ -1,6 +1,7 @@
 <template>
   <transition mode="out-in">
     <div>
+    
       <dialogIndex @closeDialog="closeDialog" @confirmDialog="confirmDialog" v-if="dialog == 'dialogIndex'"></dialogIndex>
       <dialogShare :shareLink="shareLink" @closeDialog="closeDialog" v-if="dialog == 'dialogShare'"></dialogShare>
       <createdRoom v-if="showCreated" @closeCreated="closeCreated" @createdConfirm="createdConfirm()"></createdRoom>
@@ -10,66 +11,66 @@
 </template>
 
 <script setup>
-import PageRtcLive from "./PageRtcLive";
+import PageRtcLive from './PageRtcLive';
 // import Draw from "./paint/Draw";
-import createdRoom from "./dialog/createdRoom";
-import dialogIndex from "./dialog/index.vue";
-import dialogShare from "./dialog/share.vue";
-import { Dialog } from "@/global_components/";
-import browser from "@/utils/browser";
-import { onMounted, watch, computed, ref, nextTick } from "vue";
-import { useStore } from "vuex";
-import { useApp, getApp } from "@/app";
+import createdRoom from './dialog/createdRoom';
+import dialogIndex from './dialog/index.vue';
+import dialogShare from './dialog/share.vue';
+import { Dialog } from '@/global_components/';
+import browser from '@/utils/browser';
+import { onMounted, watch, computed, ref, nextTick } from 'vue';
+import { useStore } from 'vuex';
+import { useApp, getApp } from '@/app';
 
 const store = useStore();
 
-const shareLink = ref("");
-const dialog = ref("");
+const shareLink = ref('');
+const dialog = ref('');
 const show = ref(false);
 const showPaint = ref(true);
 const showCreated = ref(false);
-const roomId = ref(browser.getURLParam("roomId"));
-const role = ref(browser.getURLParam("role"));
-const userName = ref(browser.getURLParam("name"));
-const socket = computed(() => store.getters["rtc/socket"]);
+const roomId = ref(browser.getURLParam('roomId'));
+const role = ref(browser.getURLParam('role'));
+const userName = ref(browser.getURLParam('name'));
+const socket = computed(() => store.getters['rtc/socket']);
 
 const openDialog = (str, link) => {
   shareLink.value = link;
   dialog.value = str;
 };
 const closeDialog = (str, link) => {
-  dialog.value = "";
+  dialog.value = '';
   dialog.value = str;
 };
 
 const confirmDialog = async () => {
   await getApp().Connect.follow.exit();
   if (socket.value) {
-    if (browser.getURLParam("role") == "leader") {
-      socket.value.emit("action", { type: "leader-dismiss" });
+    if (browser.getURLParam('role') == 'leader') {
+      socket.value.emit('action', { type: 'leader-dismiss' });
     }
 
     setTimeout(() => {
       socket.value.close();
-      store.commit("rtc/setSocket", null);
+      store.commit('rtc/setSocket', null);
     }, 0);
   }
 
-  store.commit("rtc/setIsJoined", false);
+  store.commit('rtc/setIsJoined', false);
 
-  store.commit("rtc/setVideoDeviceId", "");
-  store.commit("rtc/setAudioDeviceId", "");
+  store.commit('rtc/setVideoDeviceId', '');
+  store.commit('rtc/setAudioDeviceId', '');
 
   let tempUrl = window.location.href;
-  ["mode", "name", "role", "roomId", "vruserId"].forEach((item) => {
-    tempUrl = browser.replaceQueryString(tempUrl, item, "");
+  ['mode', 'name', 'role', 'roomId', 'vruserId'].forEach((item) => {
+    tempUrl = browser.replaceQueryString(tempUrl, item, '');
   });
 
   history.replaceState(null, null, tempUrl);
-  store.commit("rtc/setRole", "");
+  store.commit('rtc/setRole', '');
 
-  store.commit("showShoppingguide", false);
-  dialog.value = "";
+  store.commit('showShoppingguide', false);
+  dialog.value = '';
 
   setTimeout(() => {
     location.reload();
@@ -77,13 +78,14 @@ const confirmDialog = async () => {
 };
 
 const closeCreated = (str, link) => {
-  store.commit("showShoppingguide", false);
+  store.commit('rtc/setAvatar', null);
+  store.commit('showShoppingguide', false);
   showCreated.value = false;
   //showguide();
-  if (browser.getURLParam("role")) {
+  if (browser.getURLParam('role')) {
     let tempUrl = window.location.href;
-    ["mode", "name", "role", "roomId", "vruserId"].forEach((item) => {
-      tempUrl = browser.replaceQueryString(tempUrl, item, "");
+    ['mode', 'name', 'role', 'roomId', 'vruserId'].forEach((item) => {
+      tempUrl = browser.replaceQueryString(tempUrl, item, '');
     });
     history.replaceState(null, null, tempUrl);
   }
@@ -96,17 +98,16 @@ const createdConfirm = (str, link) => {
 };
 
 const showguide = () => {
-  if (!localStorage.getItem("user_guide")) {
+  if (!localStorage.getItem('user_guide')) {
     Dialog.confirm({
       showCloseIcon: false,
-      okText: "我知道了",
-      content:
-        "<span style='font-size: 16px; line-height: 1.5;'>開發者已遵守收集、使用最終用戶個人信息有關的所有可適用法律、政策和法規,保護用戶個人信息安全。<span/>",
-      title: "隱私條款:",
+      okText: '我知道了',
+      content: "<span style='font-size: 16px; line-height: 1.5;'>開發者已遵守收集、使用最終用戶個人信息有關的所有可適用法律、政策和法規,保護用戶個人信息安全。<span/>",
+      title: '隱私條款:',
       single: true,
       func: (state) => {
-        if (state == "ok") {
-          localStorage.setItem("user_guide", Date.now());
+        if (state == 'ok') {
+          localStorage.setItem('user_guide', Date.now());
         }
       },
     });
@@ -127,4 +128,6 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+
+</style>

+ 197 - 0
src/components/cropper/cropper.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="cropper-box">
+    <!-- <VueCropper v-if="show" ref="vmRef" v-bind="option" v-on="on" /> -->
+    <div class="layout">
+      <p class="title">{{ title }}</p>
+      <div class="content">
+        <VueCropper v-if="show" ref="vmRef" v-bind="option" v-on="on" />
+      </div>
+      <div class="footer">
+        <div class="cancel" @click="cancelCrop()">{{ noText }}</div>
+        <div class="confirm" @click="confirmCrop()">{{ okText }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { VueCropper } from 'vue-cropper';
+import 'vue-cropper/dist/index.css';
+import { computed, defineProps, ref, nextTick, defineEmits } from 'vue';
+// import 'vue-cropper/dist/index.css'
+// import { useI18n } from '@/i18n'
+// const { t } = useI18n({ useScope: 'global' })
+const sizeType = ref(1);
+const layerWidth = 500;
+const emit = defineEmits(['close', 'ok']);
+const props = defineProps({
+  fixedNumber: {
+    type: Array,
+    default: () => [1, 1],
+  },
+  img: { type: String },
+  noText: {
+    type: String,
+    default: '取消',
+  },
+  okText: {
+    type: String,
+    default: '确认',
+  },
+  title: {
+    type: String,
+    default: '裁剪',
+  },
+});
+const show = ref(true);
+const fixedNumber = props.fixedNumber;
+const getHeight = (width) => (fixedNumber[1] / fixedNumber[0]) * width;
+
+const option = {
+  outputSize: 1,
+  outputType: 'png',
+  info: false,
+  full: true,
+  fixed: true,
+  canScale: true,
+  fixedNumber: fixedNumber,
+  canMove: true,
+  canMoveBox: true,
+  fixedBox: false,
+  original: false,
+  autoCrop: true,
+  autoCropWidth: layerWidth / 2,
+  autoCropHeight: getHeight(layerWidth / 2),
+  centerBox: false,
+  mode: 'contain',
+  maxImgSize: 400,
+  ...props,
+};
+
+const vmRef = ref(null);
+const on = {
+  imgLoad(status) {
+    if (status !== 'success') {
+      // props.cb('图片加载失败');
+    }
+  },
+};
+
+const clickHandler = async (status) => {
+  if (status === 'ok') {
+    let data = await Promise.all([new Promise((resolve) => vmRef.value.getCropBlob(resolve)), new Promise((resolve) => vmRef.value.getCropData(resolve))]);
+    if (props.showSize) {
+      data.push(sizeType.value);
+    }
+    props.cb(null, data);
+  } else {
+    props.cb();
+  }
+};
+const confirmCrop = () => {
+  vmRef.value.getCropData((data) => {
+    // do something
+    emit('ok', data);
+    emit('close');
+  });
+};
+const cancelCrop = () => {
+  emit('close');
+};
+</script>
+
+<style lang="scss" socped>
+.vue-cropper {
+  background-repeat: repeat;
+}
+.cropper-box {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+  background: rgba(0, 0, 0, 0.6);
+  .layout {
+    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;
+    background: rgba(0, 0, 0, 0.7);
+    .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);
+    }
+    .content {
+      width: 100%;
+      height: 3.4133rem;
+      margin: 0.56rem 0;
+    }
+    .footer {
+      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;
+        &.cancel {
+          color: #fff;
+          border-right-style: solid;
+          border-right-width: 1px;
+          border-right-color: rgba(255, 255, 255, 0.1);
+        }
+        &.confirm {
+          color: #ed5d18;
+        }
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+.cropper-view-box {
+  outline-color: var(--color-main-normal) !important;
+}
+.crop-point {
+  background-color: var(--color-main-normal) !important;
+}
+.size {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 20px;
+}
+</style>
+<script>
+export default { name: 'ui-cropper' };
+</script>

+ 7 - 0
src/store/modules/rtc.js

@@ -24,9 +24,13 @@ export default {
             isShared: false,
             remoteStreams: [],
             invitedRemoteStreams: [],
+            avatar:null,
         }
     },
     getters: {
+        avatar: state => {
+            return state.avatar
+        },
         socket: state => {
             return state.socket
         },
@@ -58,6 +62,9 @@ export default {
         invitedRemoteStreams: state => state.invitedRemoteStreams
     },
     mutations: {
+        setAvatar(state, payload) {
+            state.avatar = payload
+        },
         setSocket(state, payload) {
             state.socket = payload
         },