PageRtcLive.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. <template>
  2. <div id="PageRtcLive">
  3. <div class="member_number">
  4. <div class="members"></div>
  5. <span>{{ user_list.length }}觀看</span>
  6. </div>
  7. <chat v-show="chatShow" :chatList="chatList" :user_info="user_info"></chat>
  8. <Trtccom :audioMuted="audioMuted" :videoMuted="videoMuted" v-if="isJoined" />
  9. <div class="contorlBar" v-if="!showInput">
  10. <div v-if="connectStatus == 1" :class="{ disabled: !user_info.IsWords }" class="saySomething" @click="onFocus">
  11. <!-- <i class="speakIcon"
  12. :class="{'dis':!user_info.IsWords}"></i> -->
  13. <span v-if="user_info.IsWords">說點什麼</span>
  14. <span v-if="!user_info.IsWords">已被禁言</span>
  15. <div class="disSpeakBtn" @click.stop="chatShow = !chatShow" :class="{ dis: !chatShow }"></div>
  16. </div>
  17. <div style="text-align: right; width: 100%" v-if="connectStatus == 0">連接中...</div>
  18. <div v-if="connectStatus == 1" class="contorl_btn">
  19. <div v-if="isBrushes && user_info.Role == 'leader'" @click="onDrawUndo" class="brushesBack" :class="{ disabled: !canUndo }"></div>
  20. <div v-if="user_info.Role == 'leader'" @click="onDraw(!isBrushes)" :class="{ brushesed: isBrushes }" class="brushes"></div>
  21. <div v-if="user_list.length < 5" class="invitation" @click="onClickShare"></div>
  22. <div v-if="role == 'leader'" class="members" @click="openMember"></div>
  23. <div v-if="!disableMic" @click="handleMuteAduio" :class="{ mic_off: audioMuted, disabled: !audioDeviceId }" class="mic_on"></div>
  24. <div v-if="disableMic" class="mic_no" :class="{ disabled: !audioDeviceId }"></div>
  25. <div v-if="role == 'leader'" @click="handleMuteVideo" :class="[videoMuted ? 'video_no' : 'video_on', videoDeviceId ? '' : 'disabled']"></div>
  26. <div class="exit" @click="openDialog('dialogIndex')"></div>
  27. </div>
  28. </div>
  29. <div class="layer" v-if="showInput" @click="closeInput">
  30. <div class="inputBox" @click.stop>
  31. <div class="msgBox">
  32. <input id="input_msg" type="text" maxlength="200" v-model.trim="text" :placeholder="`說點什麼~`" />
  33. <span class="iconsend_icon" :class="{ disable: text == '' }" @click.stop="sendText">發送</span>
  34. </div>
  35. </div>
  36. </div>
  37. <!-- 成員 -->
  38. <div class="layer" v-if="showMember" @click.self="closeMember">
  39. <div class="memberContent animated" :class="animateActive ? 'fadeInUpBig' : 'fadeOutDownBig'">
  40. <div class="blurBox"></div>
  41. <div class="content">
  42. <div class="memberHeader">
  43. <span> 成員管理({{ user_list.length }})</span>
  44. <i class="iconfont"></i>
  45. </div>
  46. <div class="memberList">
  47. <div class="memberItem">
  48. <div class="userMsg">
  49. <div class="avatar">
  50. <img :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
  51. </div>
  52. <div class="name" v-if="user_info.Role == 'leader'">{{ user_info.Nickname }} (主持人,我)</div>
  53. <div class="name" v-else>{{ user_info.Nickname }} (我)</div>
  54. </div>
  55. <div class="button" v-if="user_info.Role == 'leader'">
  56. <!-- <div class="outBtn iconfont iconremove"></div> -->
  57. <!-- <div v-if="audioDevices.length>0" class="micBtn iconfont" @click="changeMedia('audio',hideMic)" :class="user_info.muted ?'iconmic_off':'iconmic_on'"></div>
  58. <div v-else class="micBtn iconfont iconmic_off"></div> -->
  59. <div @click="setAllMuted(!all_mute_mic)" class="micBtn mute_all_mic" :class="{ open_all_mic: all_mute_mic }"></div>
  60. </div>
  61. </div>
  62. <div v-show="user_info.UserId != i.UserId && i.Role != 'leader'" class="memberItem" v-for="(i, idx) in user_list" :key="idx">
  63. <div class="userMsg">
  64. <div class="avatar">
  65. <img :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
  66. </div>
  67. <div class="name">{{ i.Nickname }}</div>
  68. </div>
  69. <div class="button" v-if="user_info.Role == 'leader'">
  70. <div class="micBtn" :class="i.IsWords ? 'ban_speak_on' : 'ban_speak_off'" @click="userCanSpeak(i)"></div>
  71. <!-- <div class="outBtn icon_remove" @click="userGetOut(i, idx)"></div> -->
  72. <div class="micBtn" :class="i.IsMuted ? 'mute_one_mic_off' : 'mute_one_mic_on'" @click="onMemberMuted(i)"></div>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. </div>
  80. <teleport :to="`#app`">
  81. <div v-if="showShare" @click="showShare = false" class="sharetip">
  82. <img @click.stop :style="`right:${isMP ? '16%' : '6%'}`" :src="require('@/assets/images/icon/img_scene_share.png')" alt="" />
  83. </div>
  84. </teleport>
  85. </template>
  86. <script setup>
  87. import { onMounted, onUnmounted, watch, defineEmits, ref, reactive, computed, nextTick } from "vue";
  88. import { useApp, getApp } from "@/app";
  89. import { useStore } from "vuex";
  90. import { Dialog } from "@/global_components/";
  91. import common from "@/utils/common";
  92. import { mapGetters } from "vuex";
  93. import chat from "./chat/chat.vue";
  94. import Trtccom from "./Trtccom.vue";
  95. import browser from "@/utils/browser";
  96. import wxShare from "@/utils/wxshare";
  97. const emit = defineEmits(["openDialog", "closeSocket"]);
  98. const store = useStore();
  99. let jumpNewScene = (sceneFirstView) => {
  100. let url = window.location.href;
  101. if (!browser.hasURLParam("pose")) {
  102. url += `&${sceneFirstView.sceneview}`;
  103. } else {
  104. url = browser.replaceQueryString(url, "pose", sceneFirstView.sceneview.replace("pose=", ""));
  105. }
  106. url = browser.replaceQueryString(url, "m", sceneFirstView.num);
  107. return url;
  108. };
  109. let chatAutoScroll = () => {
  110. let el = document.getElementById("chat");
  111. let client_h = document.getElementById("chat").clientHeight;
  112. let all = document.getElementById("contents").clientHeight;
  113. el.scrollTo(0, client_h + all);
  114. };
  115. let createSocket = (config) => {
  116. console.log(process.env.VUE_APP_SOCKET_URL, "process.env.VUE_APP_SOCKET_URL");
  117. var socket = io(process.env.VUE_APP_SOCKET_URL, {
  118. path: "/ws-sync",
  119. transports: ["websocket"],
  120. });
  121. return socket;
  122. };
  123. store.commit("rtc/setSocket", createSocket());
  124. let getUrl = (href, queryArr) => {
  125. queryArr.forEach((item) => {
  126. if (!browser.hasURLParam(item.key)) {
  127. let ttt = href.split("index.html?");
  128. href = `${ttt[0]}index.html?${item.key}=${item.val}&${ttt[1]}`;
  129. console.log(href, "------index.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.htmlindex.html----------");
  130. } else {
  131. href = browser.replaceQueryString(href, item.key, item.val);
  132. }
  133. });
  134. return href;
  135. };
  136. const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
  137. const audioDeviceId = computed(() => store.getters["rtc/audioDeviceId"]);
  138. const connectStatus = ref(0);
  139. const isBrushes = ref(false);
  140. const showInput = ref(false);
  141. const showMember = ref(false);
  142. const animateActive = ref(false);
  143. const showShare = ref(false);
  144. const isMP = ref(false);
  145. const audioMuted = ref(false);
  146. const videoMuted = ref(false);
  147. const socket = computed(() => store.getters["rtc/socket"]);
  148. const myVideoShow = ref(false);
  149. const userVideoShow = ref(false);
  150. const user_info = ref({});
  151. const user_list = ref([]);
  152. const mode = ref(browser.getURLParam("mode"));
  153. const role = ref(browser.getURLParam("role"));
  154. const userId = computed(() => store.getters["rtc/userId"]);
  155. const roomId = computed(() => store.getters["rtc/roomId"]);
  156. const isJoined = ref(false);
  157. const paint = reactive({});
  158. const chatList = ref([]);
  159. const text = ref("");
  160. const shareLink = ref("");
  161. const canUndo = ref(false);
  162. const audioDevices = ref([1]);
  163. const videoDevices = ref([1]);
  164. const disableMic = ref(false);
  165. const chatShow = ref(true);
  166. const all_mute_mic = ref(false);
  167. const tags = computed(() => {
  168. return store.getters["tag/tags"] || [];
  169. });
  170. const onClickShare = () => {
  171. if (browser.detectWeixin()) {
  172. //ios的ua中无miniProgram,但都有MicroMessenger(表示是微信浏览器)
  173. wx.miniProgram.getEnv((res) => {
  174. showShare.value = true;
  175. if (res.miniprogram) {
  176. isMP.value = true;
  177. //在小程序里
  178. } else {
  179. // 不在小程序里
  180. isMP.value = false;
  181. }
  182. });
  183. } else {
  184. openDialog("dialogShare", shareLink.value);
  185. }
  186. };
  187. const userGetOut = (item, i) => {
  188. socket.value &&
  189. socket.value.emit("action", {
  190. type: "getout",
  191. data: {
  192. id: item.UserId,
  193. },
  194. });
  195. user_list.value = user_list.value.splice(i, 1);
  196. };
  197. const setUserWords = (res) => {
  198. if (res.userId == user_info.value.UserId) {
  199. user_info.value.IsWords = res.words;
  200. Dialog.toast({ content: !user_info.value.IsWords ? `主持人設置了禁言` : `主持人已解除禁言` });
  201. }
  202. };
  203. const handleMuteVideo = () => {
  204. videoMuted.value = !videoMuted.value;
  205. };
  206. const handleMuteAduio = () => {
  207. audioMuted.value = !audioMuted.value;
  208. if (role.value == "leader") {
  209. socket.value.emit("action", { type: "users-muted", muted: audioMuted.value, userId: user_info.value.UserId });
  210. }
  211. };
  212. const setUserMuted = (res) => {
  213. if (res.userId) {
  214. if (res.userId == user_info.value.UserId && role.value == "customer") {
  215. user_info.value.IsMuted = res.muted;
  216. Dialog.toast({ content: !user_info.value.IsMuted ? `主持人設置了開麥` : `主持人設置了靜音` });
  217. disableMic.value = res.muted;
  218. audioMuted.value = res.muted;
  219. }
  220. } else {
  221. onAllMuted(res);
  222. }
  223. };
  224. const setAllMuted = (data) => {
  225. console.log(data, "IsMuted");
  226. socket.value.emit("action", { type: "users-muted", muted: data });
  227. };
  228. const onAllMuted = (res) => {
  229. user_list.value = res.members.reduce(function (tempArr, item) {
  230. if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
  231. tempArr.push(item);
  232. }
  233. return tempArr;
  234. }, []);
  235. if (role.value == "leader") {
  236. all_mute_mic.value = res.muted;
  237. }
  238. user_list.value.forEach((item) => {
  239. user_info.value.IsMuted = res.muted;
  240. item.IsMuted = res.muted;
  241. if (role.value == "customer") {
  242. Dialog.toast({ content: !user_info.value.IsMuted ? `主持人設置了開麥` : `主持人設置了靜音` });
  243. disableMic.value = res.muted;
  244. audioMuted.value = res.muted;
  245. }
  246. });
  247. };
  248. //用戶加入
  249. const setUserJoin = async (res) => {
  250. console.log("有人進來了", res);
  251. // self.user_info = res.user;
  252. user_list.value = res.members.reduce(function (tempArr, item) {
  253. if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
  254. tempArr.push(item);
  255. }
  256. return tempArr;
  257. }, []);
  258. // self.chekcLeaderInfo();
  259. let name = res.user.Nickname;
  260. if (res.user.Role == "leader") {
  261. name = "主持人";
  262. Dialog.toast({ content: `主持人進入房間` });
  263. socket.value.emit("action", { type: "user-init" });
  264. }
  265. let data = {
  266. role: res.user.Role,
  267. mode: mode.value,
  268. Nickname: name,
  269. UserId: res.user.UserId,
  270. text: "進入房間",
  271. };
  272. chatList.value.push(data);
  273. await nextTick();
  274. try {
  275. chatAutoScroll();
  276. } catch (error) {}
  277. };
  278. const onDrawUndo = async () => {
  279. let app = await getApp();
  280. app.Connect.paint.undo();
  281. canUndo.value = app.Connect.paint.records.length > 0;
  282. console.log(app.Connect.paint.records, "app.Connect.paint.records");
  283. };
  284. // 畫筆開啟
  285. const onDraw = async (status) => {
  286. isBrushes.value = status;
  287. if (isBrushes.value) {
  288. await getApp().Connect.paint.show({ role: role.value, paint: role.value == "leader" ? true : false });
  289. if (role.value == "leader") {
  290. socket.value.emit("action", { type: "user-paint", open: true });
  291. }
  292. } else {
  293. await getApp().Connect.paint.hide();
  294. if (role.value == "leader") {
  295. socket.value.emit("action", { type: "user-paint", open: false });
  296. }
  297. }
  298. };
  299. //打開輸入框
  300. const onFocus = () => {
  301. showInput.value = true;
  302. nextTick(() => {
  303. let input_msg = document.getElementById("input_msg");
  304. input_msg.focus();
  305. });
  306. };
  307. //發彈幕
  308. const sendText = async () => {
  309. if (text.value == "") {
  310. return;
  311. }
  312. let data = {
  313. role: role.value,
  314. mode: mode.value,
  315. Nickname: user_info.value.Nickname,
  316. UserId: user_info.value.UserId,
  317. text: text.value,
  318. };
  319. socket.value &&
  320. socket.value.emit("action", {
  321. type: "danmumsg",
  322. data,
  323. });
  324. chatList.value.push(data);
  325. await nextTick();
  326. try {
  327. chatAutoScroll();
  328. let input_msg = document.getElementById("input_msg");
  329. input_msg.blur();
  330. } catch (error) {}
  331. closeInput();
  332. };
  333. //接收消息
  334. const setReceiveMsg = async (res) => {
  335. console.log(res);
  336. if (res.role == "leader") {
  337. res.Nickname = "主持人";
  338. }
  339. chatList.value.push(res);
  340. await nextTick();
  341. try {
  342. chatAutoScroll();
  343. } catch (error) {}
  344. };
  345. // 關閉輸入框
  346. const closeInput = () => {
  347. showInput.value = false;
  348. text.value = "";
  349. };
  350. // 開啟成員
  351. const openMember = () => {
  352. showMember.value = true;
  353. animateActive.value = true;
  354. };
  355. // 關閉成員
  356. const closeMember = () => {
  357. animateActive.value = false;
  358. let t = setTimeout(() => {
  359. clearTimeout(t);
  360. showMember.value = false;
  361. }, 200);
  362. };
  363. const openDialog = (str, link) => {
  364. emit("openDialog", str, link);
  365. };
  366. const onMemberMuted = (item) => {
  367. item.IsMuted = !item.IsMuted;
  368. socket.value.emit("action", { type: "users-muted", muted: item.IsMuted, userId: item.UserId });
  369. };
  370. const onMemberLeave = async (res) => {
  371. console.log("有人離開了", res);
  372. user_list.value = res.members.reduce(function (tempArr, item) {
  373. if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
  374. tempArr.push(item);
  375. }
  376. return tempArr;
  377. }, []);
  378. let name = res.user.Nickname;
  379. if (res.user.Role == "leader") {
  380. name = "主持人";
  381. Dialog.toast({ content: `主持人離開了房間` });
  382. }
  383. let data = {
  384. role: res.user.Role,
  385. mode: mode.value,
  386. Nickname: name,
  387. UserId: res.user.UserId,
  388. text: "離開房間",
  389. };
  390. chatList.value.push(data);
  391. await nextTick();
  392. try {
  393. chatAutoScroll();
  394. } catch (error) {}
  395. };
  396. const userCanSpeak = (item) => {
  397. item.IsWords = !item.IsWords;
  398. socket.value.emit("action", { type: "users-words", words: item.IsWords, userId: item.UserId });
  399. };
  400. const onGetOuT = (data) => {
  401. if (data.id == user_info.value.UserId) {
  402. emit("closeSocket");
  403. Dialog.toast({ content: `您已被移除` });
  404. }
  405. };
  406. watch(
  407. user_list,
  408. () => {
  409. if (role.value == "leader") {
  410. all_mute_mic.value = !user_list.value.some((item) => !item.IsMuted && item.Role == "customer");
  411. }
  412. },
  413. {
  414. deep: true,
  415. }
  416. );
  417. const startFollow = (app) => {
  418. app.Connect.follow.start({ follow: role.value == "customer" });
  419. store.commit("rtc/setUserId", browser.getURLParam("vruserId") || `user_${role.value}${Math.floor(Math.random() * 100000000)}`);
  420. store.commit("rtc/setRoomId", browser.getURLParam("roomId") || `room_${Math.floor(Math.random() * 100000000)}`);
  421. socket.value.on("connect", (e) => {
  422. socket.value.emit("join", {
  423. userId: userId.value,
  424. roomId: roomId.value,
  425. role: role.value || "leader",
  426. nickname: browser.getURLParam("name"),
  427. });
  428. });
  429. // 加入房間成功
  430. socket.value.on("join", (data) => {
  431. let meblist = data.members.reduce(function (tempArr, item) {
  432. if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
  433. tempArr.push(item);
  434. }
  435. return tempArr;
  436. }, []);
  437. if (meblist.length > 5 && role.value == "customer") {
  438. Dialog.toast({ content: `房間已滿員` });
  439. emit("closeSocket");
  440. return;
  441. }
  442. connectStatus.value = 1;
  443. if (role.value == "customer") {
  444. socket.value.emit("action", { type: "user-init" });
  445. if (data.user.IsMuted) {
  446. disableMic.value = true;
  447. audioMuted.value = true;
  448. }
  449. } else {
  450. if (data.user.IsMuted) {
  451. audioMuted.value = true;
  452. }
  453. }
  454. isJoined.value = true;
  455. user_info.value = data.user;
  456. user_list.value = meblist;
  457. //更新分享鏈接
  458. shareLink.value = getUrl(window.location.href, [
  459. {
  460. key: "mode",
  461. val: mode.value,
  462. },
  463. {
  464. key: "name",
  465. val: "",
  466. },
  467. {
  468. key: "vruserId",
  469. val: "",
  470. },
  471. {
  472. key: "role",
  473. val: "customer",
  474. },
  475. {
  476. key: "roomId",
  477. val: user_info.value.RoomId,
  478. },
  479. ]);
  480. wxShare({
  481. donotconfig: true,
  482. title: `【好友推薦】一起雲逛店吧~`,
  483. desc: "【好友推薦】一起雲逛店吧~",
  484. link: shareLink.value,
  485. imgUrl: "https://glp-vr.cdfmembers.com/cdf/file/91dd5305525f463286f03a31abd1c154.jpg",
  486. });
  487. if (browser.detectWeixin()) {
  488. //ios的ua中无miniProgram,但都有MicroMessenger(表示是微信浏览器)
  489. wx.miniProgram.getEnv((res) => {
  490. if (res.miniprogram) {
  491. wx.miniProgram.postMessage({
  492. data: {
  493. title: "【好友推薦】一起雲逛店吧~",
  494. imageUrl: "https://glp-vr.cdfmembers.com/cdf/file/91dd5305525f463286f03a31abd1c154.jpg",
  495. h5Url: shareLink.value,
  496. },
  497. });
  498. //在小程序里
  499. }
  500. });
  501. }
  502. console.log("分享", shareLink.value);
  503. let tmp = "";
  504. if (user_info.value.Role == "leader") {
  505. tmp = getUrl(window.location.href, [
  506. {
  507. key: "roomId",
  508. val: user_info.value.RoomId,
  509. },
  510. {
  511. key: "vruserId",
  512. val: user_info.value.UserId,
  513. },
  514. ]);
  515. } else {
  516. tmp = getUrl(window.location.href, [
  517. {
  518. key: "vruserId",
  519. val: user_info.value.UserId,
  520. },
  521. ]);
  522. }
  523. console.log("創建房間後更新url", tmp);
  524. store.commit("rtc/setRole", user_info.value.Role);
  525. history.replaceState(null, null, tmp);
  526. });
  527. socket.value.on("action", (data) => {
  528. if (data.type == "error") {
  529. Dialog.toast({ content: `房間未找到`, type: "error" });
  530. emit("closeSocket");
  531. } else if (data.type == "danmumsg") {
  532. setReceiveMsg(data.data);
  533. } else if (data.type == "getout") {
  534. onGetOuT(data.data);
  535. } else if (data.type == "user-init") {
  536. app.Connect.follow.sync();
  537. } else if (data.type == "user-paint") {
  538. onDraw(data.open);
  539. if (role.value == "customer") {
  540. if (data.open) {
  541. Dialog.toast({ content: `主持人開啟畫筆` });
  542. } else {
  543. Dialog.toast({ content: `主持人關閉畫筆` });
  544. }
  545. }
  546. } else if (data.type == "user-join") {
  547. setUserJoin(data);
  548. } else if (data.type == "users-muted") {
  549. setUserMuted(data);
  550. //閉麥
  551. } else if (data.type == "users-words") {
  552. setUserWords(data);
  553. //禁言
  554. } else if (data.type == "user-leave") {
  555. // 房間解散
  556. onMemberLeave(data);
  557. } else if (data.type == "leader-dismiss") {
  558. emit("closeSocket");
  559. Dialog.toast({ content: `主持人已解散房間` });
  560. } else if (data.type == "tagclick") {
  561. if (role.value == "customer") {
  562. let item = tags.value.find((item) => item.sid == data.data.sid);
  563. if (item.type == "commodity") {
  564. store.commit("tag/setTagClickType", {
  565. type: "goodlist",
  566. data: item,
  567. });
  568. } else if (item.type == "waterfall") {
  569. store.commit("tag/setTagClickType", {
  570. type: "waterfall",
  571. data: item,
  572. });
  573. } else if (item.type == "link_scene") {
  574. let sceneFirstView = item.hotContent.sceneFirstView;
  575. window.location.href = jumpNewScene(sceneFirstView);
  576. }
  577. // document.querySelector(`[data-tag-id="${data.data.sid}"]`).click();
  578. // console.log(data.data.sid);
  579. }
  580. } else if (data.type == "tagclose") {
  581. if (role.value == "customer") {
  582. store.commit("tag/setTagClickType", {
  583. type: "",
  584. data: {},
  585. });
  586. }
  587. }
  588. });
  589. // 同屏帶看
  590. socket.value.on("sync", (data) => {
  591. if (role.value == "customer") {
  592. app.Connect.follow.receive(data);
  593. }
  594. });
  595. // 畫筆
  596. socket.value.on("paint", (data) => {
  597. if (role.value == "customer") {
  598. app.Connect.paint.receive(data);
  599. }
  600. });
  601. };
  602. let onfollowData = (data) => {
  603. if (isJoined.value) {
  604. socket.value.emit("sync", data);
  605. }
  606. };
  607. let onfollowPaint = async (data) => {
  608. canUndo.value = (await getApp().Connect.paint.records.length) > 0;
  609. socket.value.emit("paint", data);
  610. };
  611. onMounted(async () => {
  612. let app = await getApp();
  613. startFollow(app);
  614. app.Connect.follow.on("data", onfollowData);
  615. app.Connect.paint.on("data", onfollowPaint);
  616. });
  617. onUnmounted(async () => {
  618. let app = await getApp();
  619. app.Connect.follow.off("data", onfollowData);
  620. app.Connect.follow.off("data", onfollowData);
  621. });
  622. </script>
  623. <style scoped lang="scss">
  624. #PageRtcLive {
  625. position: absolute;
  626. left: 0;
  627. bottom: 0;
  628. // height: 7.31rem;
  629. width: 100%;
  630. z-index: 999;
  631. // background: rgba(0, 0, 0, 0.1);
  632. padding: 0 0.44rem;
  633. box-sizing: border-box;
  634. // pointer-events: none;
  635. .member_number {
  636. // width: 1.67rem;
  637. height: 0.5rem;
  638. background: rgba(0, 0, 0, 0.3);
  639. border-radius: 0.25rem;
  640. position: fixed;
  641. top: 0.56rem;
  642. right: 0.44rem;
  643. padding: 0.07rem 0.17rem;
  644. display: flex;
  645. align-items: center;
  646. justify-content: center;
  647. .members {
  648. width: 0.42rem;
  649. height: 0.42rem;
  650. background: url(~@/assets/images/rtcLive/members@2x.png);
  651. background-size: 100% 100%;
  652. margin-right: 0.07rem;
  653. }
  654. span {
  655. font-size: 0.24rem;
  656. color: #fff;
  657. }
  658. }
  659. .contorlBar {
  660. width: 9.51rem;
  661. height: 1.173333rem;
  662. background: rgba(0, 0, 0, 0.5);
  663. border-radius: 0.67rem;
  664. border: 0.03rem solid rgba(255, 255, 255, 0.1);
  665. margin: 0.67rem auto 0;
  666. // position: absolute;
  667. position: fixed;
  668. z-index: 9999;
  669. left: 50%;
  670. transform: translateX(-50%);
  671. padding: 0 0.266667rem;
  672. box-sizing: border-box;
  673. bottom: 0.94rem;
  674. display: flex;
  675. align-items: center;
  676. justify-content: space-between;
  677. .saySomething {
  678. color: #fff;
  679. // font-size: 0.42rem;
  680. height: 0.8rem;
  681. position: relative;
  682. display: flex;
  683. align-items: center;
  684. justify-content: center;
  685. background: rgba(0, 0, 0, 0.5);
  686. padding: 0 0.2rem 0 0.2rem;
  687. border-radius: 0.64rem;
  688. width: 100%;
  689. display: flex;
  690. align-items: center;
  691. justify-content: space-between;
  692. .disSpeakBtn {
  693. width: 0.533333rem;
  694. height: 0.533333rem;
  695. background: url(~@/assets/images/rtcLive/pop-up_screen_on@2x.png) no-repeat;
  696. background-size: 100% 100%;
  697. cursor: pointer;
  698. &.dis {
  699. background: url(~@/assets/images/rtcLive/pop-up_screen_off@2x.png) no-repeat;
  700. background-size: 100% 100%;
  701. }
  702. }
  703. .speakIcon {
  704. width: 0.32rem;
  705. height: 0.32rem;
  706. background: url(~@/assets/images/rtcLive/Input_norma@2x.png);
  707. background-size: 100% 100%;
  708. display: block;
  709. margin-right: 0.1rem;
  710. &.dis {
  711. background: url(~@/assets/images/rtcLive/Input_disabled@2x.png);
  712. background-size: 100% 100%;
  713. }
  714. }
  715. span {
  716. font-size: 0.266667rem;
  717. }
  718. // &::after {
  719. // content: "";
  720. // position: absolute;
  721. // width: 0.04rem;
  722. // height: 0.44rem;
  723. // background: #ffffff;
  724. // border-radius: 0.03rem;
  725. // top: 50%;
  726. // transform: translateY(-50%);
  727. // right: -0.28rem;
  728. // }
  729. }
  730. .contorl_btn {
  731. display: flex;
  732. align-items: center;
  733. justify-content: space-between;
  734. margin-left: 0.2rem;
  735. > div {
  736. // width: 0.56rem;
  737. // height: 0.56rem;
  738. width: 0.65rem;
  739. height: 0.65rem;
  740. // font-size: 0.56rem;
  741. // background: #FD5151;
  742. margin-right: 0.33rem;
  743. // &.iconexit {
  744. // // width: 0.56rem;
  745. // // height: 0.56rem;
  746. // color: #fd5151;
  747. // // background: #fff;
  748. // // border-radius: 50%;
  749. // // overflow: hidden;
  750. // }
  751. &.brushesBack {
  752. background: url(~@/assets/images/rtcLive/revocation@2x.png);
  753. background-size: 100% 100%;
  754. }
  755. &.brushes {
  756. // background: url(~@/assets/images/rtcLive/brushes@2x.png);
  757. background: url(~@/assets/images/rtcLive/brushes@2x.png);
  758. background-size: 100% 100%;
  759. }
  760. &.brushesed {
  761. background: url(~@/assets/images/rtcLive/brushes_selected@2_1.png);
  762. background-size: 100% 100%;
  763. }
  764. &.invitation {
  765. background: url(~@/assets/images/rtcLive/invitation@2x.png);
  766. background-size: 100% 100%;
  767. }
  768. &.members {
  769. background: url(~@/assets/images/rtcLive/members@2x.png);
  770. background-size: 100% 100%;
  771. }
  772. &.mic_on {
  773. background: url(~@/assets/images/rtcLive/mic_on@2x.png);
  774. background-size: 100% 100%;
  775. }
  776. &.mic_no {
  777. background: url(~@/assets/images/rtcLive/mic_off_50@2x.png);
  778. background-size: 100% 100%;
  779. }
  780. &.mic_off {
  781. background: url(~@/assets/images/rtcLive/mic_off@2x.png);
  782. background-size: 100% 100%;
  783. }
  784. &.video_on {
  785. background: url(~@/assets/images/rtcLive/video_on@2x.png);
  786. background-size: 100% 100%;
  787. }
  788. &.video_no {
  789. background: url(~@/assets/images/rtcLive/video_off_50@2x.png);
  790. background-size: 100% 100%;
  791. }
  792. &.video_off {
  793. background: url(~@/assets/images/rtcLive/video_off@2x.png);
  794. background-size: 100% 100%;
  795. }
  796. &.exit {
  797. background: url(~@/assets/images/rtcLive/exit@2x.png);
  798. background-size: 100% 100%;
  799. }
  800. &:last-child {
  801. margin-right: 0;
  802. }
  803. }
  804. }
  805. }
  806. .layer {
  807. width: 100vw;
  808. height: 100vh;
  809. background: rgba(0, 0, 0, 0.1);
  810. z-index: 10000;
  811. position: fixed;
  812. bottom: 0;
  813. left: 0;
  814. // right: unset;
  815. // top: unset;
  816. .inputBox {
  817. width: 100vw;
  818. height: 1.39rem;
  819. // background: #f2f2f2;
  820. border: 1px solid rgba(255, 255, 255, 0.1);
  821. background: rgba(0, 0, 0, 0.5);
  822. position: absolute;
  823. bottom: 0;
  824. left: 0;
  825. padding: 0 0.44rem;
  826. box-sizing: border-box;
  827. display: flex;
  828. align-items: center;
  829. justify-content: center;
  830. .msgBox {
  831. width: 9.53rem;
  832. height: 0.94rem;
  833. position: absolute;
  834. > input {
  835. width: 9.53rem;
  836. height: 0.94rem;
  837. background: rgba(0, 0, 0, 0.5);
  838. border-radius: 0.56rem;
  839. font-size: 0.42rem;
  840. padding: 0 1.6rem 0 0.44rem;
  841. box-sizing: border-box;
  842. border: none;
  843. outline: none;
  844. color: #fff;
  845. resize: none;
  846. line &::placeholder {
  847. color: rgba(255, 255, 255, 0.5);
  848. }
  849. }
  850. .iconfont {
  851. // width: 0.56rem;
  852. // height: 0.56rem;
  853. // background: #ed5d18;
  854. font-size: 0.56rem;
  855. position: absolute;
  856. top: 50%;
  857. transform: translateY(-50%);
  858. right: 0.78rem;
  859. color: #fff;
  860. &.active {
  861. color: #ed5d18;
  862. }
  863. }
  864. .iconsend_icon {
  865. position: absolute;
  866. top: 50%;
  867. transform: translateY(-50%);
  868. // right: 0.78rem;
  869. right: 0.106667rem;
  870. width: 1.28rem;
  871. height: 0.693333rem;
  872. background: #ed5d18;
  873. color: #fff;
  874. font-size: 0.32rem;
  875. text-align: center;
  876. line-height: 0.693333rem;
  877. border-radius: 0.533333rem;
  878. // width: 0.67rem;
  879. // height: 0.67rem;
  880. // background-image: url(~@/assets/images/rtcLive/send_selected@2x.png);
  881. // background-size: 100% 100%;
  882. }
  883. }
  884. }
  885. .memberContent {
  886. height: auto;
  887. width: 100%;
  888. // background: #ffffff;
  889. border-radius: 0.28rem 0.28rem 0px 0px;
  890. position: absolute;
  891. left: 0;
  892. bottom: 0;
  893. &.animated {
  894. animation-duration: 0.3s;
  895. }
  896. .blurBox {
  897. background: rgba(0, 0, 0, 0.9);
  898. border-radius: 0.14rem 0.14rem 0px 0px;
  899. filter: blur(1px);
  900. position: absolute;
  901. width: 100%;
  902. height: 100%;
  903. z-index: 1;
  904. top: 0;
  905. left: 0;
  906. }
  907. .content {
  908. position: relative;
  909. width: 100%;
  910. height: 100%;
  911. z-index: 2;
  912. top: 0;
  913. left: 0;
  914. }
  915. .memberHeader {
  916. width: 100%;
  917. height: 1.28rem;
  918. text-align: center;
  919. line-height: 1.28rem;
  920. > span {
  921. font-size: 0.42rem;
  922. color: #fff;
  923. }
  924. }
  925. .memberList {
  926. width: 100%;
  927. height: 8.33rem;
  928. border-top-style: solid;
  929. border-top-color: rgba(0, 0, 0, 0.1);
  930. border-top-width: 1px;
  931. border-bottom-style: solid;
  932. border-bottom-color: rgba(0, 0, 0, 0.1);
  933. border-bottom-width: 1px;
  934. box-sizing: border-box;
  935. overflow-y: auto;
  936. &::-webkit-scrollbar {
  937. display: none;
  938. }
  939. .memberItem {
  940. width: 100%;
  941. height: 1.39rem;
  942. display: flex;
  943. align-items: center;
  944. justify-content: space-between;
  945. padding: 0.19rem 0.44rem;
  946. .userMsg {
  947. display: flex;
  948. align-items: center;
  949. justify-content: center;
  950. .avatar {
  951. width: 1rem;
  952. height: 1rem;
  953. border-radius: 50%;
  954. // overflow: hidden;
  955. margin-right: 0.28rem;
  956. > img {
  957. // width: 100%;
  958. // height: 100%;
  959. width: 1rem;
  960. height: 1rem;
  961. }
  962. }
  963. .name {
  964. font-size: 0.39rem;
  965. color: #fff;
  966. }
  967. }
  968. .button {
  969. display: flex;
  970. align-items: center;
  971. justify-content: center;
  972. > div {
  973. // width: 0.56rem;
  974. // height: 0.56rem;
  975. // background: #fc6970;
  976. font-size: 0.65rem;
  977. color: #fff;
  978. &.micBtn {
  979. width: 0.65rem;
  980. height: 0.65rem;
  981. margin-right: 0.3rem;
  982. &.mute_all_mic {
  983. background-image: url(~@/assets/images/rtcLive/mic_all_on@2x.png);
  984. background-size: 100% 100%;
  985. }
  986. &.open_all_mic {
  987. background-image: url(~@/assets/images/rtcLive/mic_all_off@2x.png);
  988. background-size: 100% 100%;
  989. }
  990. &.mute_one_mic_on {
  991. background-image: url(~@/assets/images/rtcLive/mic_on@2x.png);
  992. background-size: 100% 100%;
  993. }
  994. &.mute_one_mic_off {
  995. background-image: url(~@/assets/images/rtcLive/mic_off@2x.png);
  996. background-size: 100% 100%;
  997. }
  998. &.ban_speak_on {
  999. background-image: url(~@/assets/images/rtcLive/chat_on@2x.png);
  1000. background-size: 100% 100%;
  1001. }
  1002. &.ban_speak_off {
  1003. background-image: url(~@/assets/images/rtcLive/chat_off@2x.png);
  1004. background-size: 100% 100%;
  1005. }
  1006. }
  1007. &.outBtn {
  1008. margin-right: 0.3rem;
  1009. width: 0.65rem;
  1010. height: 0.65rem;
  1011. &.icon_remove {
  1012. background-image: url(~@/assets/images/rtcLive/remove@2x.png);
  1013. background-size: 100% 100%;
  1014. }
  1015. }
  1016. }
  1017. }
  1018. }
  1019. }
  1020. .memberBottom {
  1021. width: 100%;
  1022. padding: 0 0.44rem;
  1023. box-sizing: border-box;
  1024. display: flex;
  1025. align-items: center;
  1026. justify-content: center;
  1027. padding: 0.28rem 0;
  1028. box-sizing: border-box;
  1029. > div {
  1030. width: 4.61rem;
  1031. height: 1.11rem;
  1032. color: #ed5d18;
  1033. border-radius: 0.11rem;
  1034. border: 0.03rem solid #ed5d18;
  1035. display: flex;
  1036. align-items: center;
  1037. justify-content: center;
  1038. font-size: 0.39rem;
  1039. &.mute_all {
  1040. margin-right: 0.31rem;
  1041. }
  1042. }
  1043. }
  1044. }
  1045. }
  1046. .chat__area-layoutBox {
  1047. display: block;
  1048. width: 100%;
  1049. height: auto;
  1050. position: fixed;
  1051. bottom: 0;
  1052. left: 0;
  1053. right: unset;
  1054. top: unset;
  1055. background: #fff;
  1056. // padding-bottom: 0.821256rem;
  1057. .chat__area-layoutBox_bg {
  1058. width: 100vw;
  1059. height: 100vh;
  1060. position: fixed;
  1061. z-index: 1;
  1062. top: 0;
  1063. left: 0;
  1064. }
  1065. .chat__area-l-content {
  1066. display: -webkit-box;
  1067. display: -ms-flexbox;
  1068. display: flex;
  1069. -webkit-box-orient: horizontal;
  1070. -webkit-box-direction: normal;
  1071. -ms-flex-direction: row;
  1072. flex-direction: row;
  1073. -webkit-box-align: center;
  1074. -ms-flex-align: center;
  1075. align-items: center;
  1076. -webkit-box-pack: center;
  1077. -ms-flex-pack: center;
  1078. justify-content: center;
  1079. min-height: 0.821256rem;
  1080. padding: 0.241546rem 0;
  1081. width: 100%;
  1082. position: relative;
  1083. z-index: 100;
  1084. textarea {
  1085. width: calc(100% - 0.96618rem);
  1086. -ms-flex-preferred-size: calc(100% - 0.96618rem);
  1087. flex-basis: calc(100% - 0.96618rem);
  1088. background: none;
  1089. border: none;
  1090. border-radius: 0;
  1091. color: #000;
  1092. font-size: 0.386473rem;
  1093. line-height: 0.483092rem;
  1094. padding: 0 0.241546rem;
  1095. resize: none;
  1096. -webkit-user-select: text;
  1097. caret-color: #ff4500;
  1098. overflow: hidden !important;
  1099. }
  1100. }
  1101. }
  1102. @keyframes myAnimation {
  1103. 0% {
  1104. background-position: 0px 0px;
  1105. background-size: 13.3344rem auto;
  1106. }
  1107. 100% {
  1108. background-position: -13.1104rem 0px;
  1109. background-size: 13.3344rem auto;
  1110. }
  1111. }
  1112. }
  1113. .sharetip {
  1114. position: fixed;
  1115. z-index: 99999;
  1116. width: 100%;
  1117. height: 100%;
  1118. left: 0;
  1119. right: 0;
  1120. bottom: 0;
  1121. top: 0;
  1122. background-color: rgba($color: #000000, $alpha: 0.66);
  1123. > img {
  1124. position: absolute;
  1125. right: 16%;
  1126. top: 0;
  1127. max-width: 70vw;
  1128. }
  1129. }
  1130. </style>