Browse Source

添加v4看店核心代码

bill 2 years ago
parent
commit
660bd3cc20
39 changed files with 7020 additions and 22 deletions
  1. 3 3
      .env
  2. 1 1
      .env.development
  3. 6558 0
      pnpm-lock.yaml
  4. 2 0
      src/app.js
  5. 4 1
      src/app.vue
  6. BIN
      src/assets/icon.png
  7. 16 9
      src/components/Controls/Control.Mobile.vue
  8. 94 0
      src/components/Controls/Panel/main-shop.vue
  9. 106 0
      src/components/Controls/Panel/scene-list.vue
  10. 65 7
      src/components/RTC/PageRtcLive.vue
  11. 1 0
      src/components/RTC/Trtccom.vue
  12. 10 1
      src/components/RTC/index.vue
  13. 79 0
      src/components/RTC/mini-platform.js
  14. BIN
      src/components/icon/images/arrow@2x.png
  15. BIN
      src/components/icon/images/arrows@2x.png
  16. BIN
      src/components/icon/images/brushes@2x.png
  17. BIN
      src/components/icon/images/brushes_selected@2x.png
  18. BIN
      src/components/icon/images/chat_off@2x.png
  19. BIN
      src/components/icon/images/chat_on@2x.png
  20. BIN
      src/components/icon/images/cross@2x.png
  21. BIN
      src/components/icon/images/exit@2x.png
  22. BIN
      src/components/icon/images/guided@2x.png
  23. BIN
      src/components/icon/images/invitation@2x.png
  24. BIN
      src/components/icon/images/members@2x.png
  25. BIN
      src/components/icon/images/mic_off@2x.png
  26. BIN
      src/components/icon/images/mic_off_50@2x.png
  27. BIN
      src/components/icon/images/mic_on@2x.png
  28. BIN
      src/components/icon/images/play.png
  29. BIN
      src/components/icon/images/pop-up_screen_off@2x.png
  30. BIN
      src/components/icon/images/pop-up_screen_on@2x.png
  31. BIN
      src/components/icon/images/revocation@2x.png
  32. BIN
      src/components/icon/images/revocation_50%@2x.png
  33. BIN
      src/components/icon/images/scene.png
  34. BIN
      src/components/icon/images/show@2x.png
  35. BIN
      src/components/icon/images/编组@2x.png
  36. 24 0
      src/components/icon/index.vue
  37. 5 0
      src/env.js
  38. 48 0
      src/store/room.js
  39. 4 0
      vue.config.js

+ 3 - 3
.env

@@ -4,11 +4,11 @@ VUE_APP_RESOURCE_URL=https://4dkk.4dage.com/
 # 静态资源地址
 VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
-VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.2.0
+VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.2.1
 # socket地址
-VUE_APP_SOCKET_URL=https://ws.4dkankan.com/
+VUE_APP_SOCKET_URL=wss://ws.4dkankan.com
 # 静态资源目录
 VUE_APP_STATIC_DIR=viewer
 # 接口请求地址
-VUE_APP_APIS_URL=https://www.4dkankan.com/
+VUE_APP_APIS_URL=https://test.4dkankan.com/
 

+ 1 - 1
.env.development

@@ -1 +1 @@
-VUE_APP_SDK_DIR=http://127.0.0.1:3099/dist/sdk
+VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.2.1

File diff suppressed because it is too large
+ 6558 - 0
pnpm-lock.yaml


+ 2 - 0
src/app.js

@@ -8,6 +8,8 @@ export function createApp(opitons = {}) {
     }
     opitons.region = process.env.VUE_APP_REGION_URL
     opitons.resource = process.env.VUE_APP_RESOURCE_URL
+    opitons.server = '/'
+    
     _num = opitons.num
     _app = new KanKan(opitons)
     deferred.resolve(_app)

+ 4 - 1
src/app.vue

@@ -109,6 +109,7 @@
 </template>
 
 <script setup>
+import { initialRoom } from '@/store/room.js'
 import { useMusicPlayer } from "@/utils/sound";
 // import UiTags from "@/components/Tags";
 import GoodsList from "@/components/Tags/goods-list.vue";
@@ -429,7 +430,7 @@ onMounted(async () => {
     store.commit("setFloorId", pano.floorIndex);
     store.commit("rtc/setShowdaogou", true);
 
-    if (browser.getURLParam("roomId")) {
+    if (browser.getURLParam("roomId") && browser.getURLParam("vruserId") && browser.getURLParam("name")) {
       store.commit("showShoppingguide", true);
     }
     useMusicPlayer();
@@ -516,6 +517,8 @@ onMounted(async () => {
     });
   }
 });
+
+initialRoom()
 </script>
 <style lang="scss">
 .tab-layer {

BIN
src/assets/icon.png


+ 16 - 9
src/components/Controls/Control.Mobile.vue

@@ -1,20 +1,19 @@
 <template>
   <FloorSwitch />
-  <transition mode="out-in">
-      <component class="limitwidth" :is="panelPage"></component>
-  </transition>
-
-
+  <component class="limitwidth" :is="panelPage"></component>
 </template>
 
 <script setup>
 
 import { useStore } from "vuex";
-import { onMounted, watch, computed, ref, shallowRef, nextTick } from "vue";
+import { onMounted, watch, computed, ref, shallowRef, nextTick, watchEffect } from "vue";
 import { useApp, getApp } from "@/app";
 import FloorSwitch from "./FloorSwitch";
+import { room } from '@/store/room'
+import browser from "@/utils/browser";
 
-import vMain from "./Panel/Main";
+// import vMain from "./Panel/Main";
+import vMain from "./Panel/main-shop.vue";
 import Guide from "./Panel/Guide";
 
 import guideShop from "../RTC/index";
@@ -37,8 +36,16 @@ const mode = computed(() => store.getters["mode"]);
 let timer = null;
 
 const panelPage = computed(() => {
-  let status = store.getters["tour/isPlay"] ? Guide :  vMain;
-  return store.getters["shoppingguide"] ? guideShop : status;
+  // let status = store.getters["tour/isPlay"] ? Guide :  vMain;
+  // console.log('=====>', store.getters["shoppingguide"])
+  // return store.getters["shoppingguide"] ? guideShop : status;
+  
+  if (room.value && (browser.getURLParam("role") === 'leader' || room.value.roomStatus === 1) && store.getters["shoppingguide"]) {
+    return guideShop
+  } else {
+    return vMain
+  }
+  // return store.getters["shoppingguide"] ? guideShop : vMain
 });
 
 

+ 94 - 0
src/components/Controls/Panel/main-shop.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="panel" :class="{show}">
+    <span class="icon" @click="playing ? pause() : play()">
+      <Icon type="play" />
+    </span>
+    <span class="icon" @click="showScenes = !showScenes">
+      <Icon type="scene" />
+    </span>
+    <span class="ctrl" @click="show = !show">
+      <Icon type="arrows@2x" />
+    </span>
+  </div>
+  <SceneList 
+    v-if="showScenes" 
+    @close="showScenes = false" 
+    @changeScene="changeScene"
+  />
+</template>
+
+<script setup>
+import SceneList from './scene-list.vue'
+import { changeScene } from '@/store/room'
+import Icon from '@/components/icon/index.vue'
+import { ref } from 'vue'
+import { getApp } from '@/app'
+
+const show = ref(false)
+const app = getApp()
+const playing = ref(false)
+const showScenes = ref(false)
+
+app.use('TourPlayer').then(player => {
+  player.on('play', ({ partId, frameId }) => (playing.value = true))
+  player.on('pause', ({ partId, frameId }) => (playing.value = false))
+  player.on('end', () => {
+    playing.value = false
+    // 兼容最后一个画面没有进度的问题
+    this.progressPart = 100
+  })
+})
+
+const play = async () => {
+  const player = await app.TourManager.player
+  player.play()
+}
+
+const pause = async () => {
+  const player = await app.TourManager.player
+  player.pause()
+}
+
+</script>
+
+<style lang="scss" scoped>
+.panel {
+  position: fixed;
+  top: calc(100% - 90px);
+  left: 0;
+  z-index: 22;
+  height: 44px;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 0px 24px 24px 0px;
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  padding-right: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: space-evenly;
+  width: 110px;
+  transform: translateX(-80px);
+
+
+  &.show {
+    transform: translateX(0);
+  }
+}
+
+.icon {
+  font-size: 24px;
+  height: 1em;
+  color: #fff;
+
+  &.active {
+    color: #ED5D18;
+  }
+}
+
+.ctrl {
+  position: absolute;
+  right: 10px;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 12px;
+}
+</style>

+ 106 - 0
src/components/Controls/Panel/scene-list.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="scene-list">
+    <div class="header">
+      <p>场景列表({{ sceneList.length }})</p>
+      <span class="icon" @click="emit('close')">
+        <Icon type="cross@2x" />
+      </span>
+    </div>
+    <div class="content">
+      <div class="sign" v-for="scene in sceneList" :key="scene.id" @click="emit('changeScene', scene)">
+        <div class="info">
+          <img :src="scene.thumb">
+          <p>{{ scene.sceneName }}</p>
+        </div>
+        <span class="icon">
+          <Icon type="arrow@2x" />
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { sceneList } from '@/store/room'
+import Icon from '@/components/icon/index.vue'
+
+const emit = defineEmits(['close', 'changeScene']);
+</script>
+
+<style scoped lang="scss">
+.scene-list {
+  height: 80%;
+  background: rgba(0,0,0,0.8);
+  border-radius: 10px 10px 0px 0px;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  display: flex;
+  flex-direction: column;
+  z-index: 99999;
+  position: absolute;
+}
+
+.header {
+  flex: none;
+  text-align: center;
+  height: 46px;
+  line-height: 46px;
+  border-bottom: 1px solid rgba(255,255,255,.1);
+
+  p {
+    font-size: 16px;
+    font-weight: 500;
+    color: #FFFFFF;
+  }
+
+  .icon {
+    position: absolute;
+    right: 0;
+    top: 0;
+    font-size: 14px;
+    padding: 0 20px;
+  }
+}
+
+.content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 11px;
+
+  .sign {
+    height: 60px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 18px;
+
+    .info {
+      flex: 1;
+      display: flex;
+      align-items: center;
+
+      img {
+        flex: none;
+        width: 60px;
+        height: 60px;
+        border-radius: 4px;
+      }
+
+      p {
+        font-size: 14px;
+        font-weight: 400;
+        color: #FFFFFF;
+        line-height: 20px;
+        margin-left: 10px;
+      }
+    }
+
+    .icon {
+      flex: none;
+      font-size: 10px;
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 65 - 7
src/components/RTC/PageRtcLive.vue

@@ -35,6 +35,7 @@
         class="saySomething"
         @click="onFocus"
       >
+
         <!-- <i class="speakIcon"
            :class="{'dis':!user_info.IsWords}"></i> -->
         <span v-if="user_info.IsWords">说点什么</span>
@@ -73,13 +74,13 @@
           <div
             v-if="!disableMic"
             @click="handleMuteAduio"
-            :class="{ mic_off: audioMuted, disabled: !audioDeviceId }"
+            :class="{ mic_off: audioMuted, disabled: disabledAudio }"
             class="mic_on"
           ></div>
           <div
             v-if="disableMic"
             class="mic_no"
-            :class="{ disabled: !audioDeviceId }"
+            :class="{ disabled: disabledAudio }"
           ></div>
         </template>
         <!-- <div
@@ -90,6 +91,15 @@
             videoDeviceId ? '' : 'disabled',
           ]"
         ></div> -->
+        
+        
+        <div
+          v-if="user_info.Role == 'leader'"
+          @click="showScenes = !showScenes"
+          style="font-size: 0.65rem"
+        >
+          <Icon type="scene" />
+        </div>
         <div class="exit" @click="openDialog('dialogIndex')"></div>
       </div>
     </div>
@@ -198,6 +208,12 @@
       />
     </div>
   </teleport>
+
+  <SceneList 
+    v-if="showScenes" 
+    @close="showScenes = false" 
+    @changeScene="changeScene"
+  />
 </template>
 
 <script setup>
@@ -210,7 +226,10 @@ import {
   reactive,
   computed,
   nextTick,
+  watchEffect,
 } from "vue";
+import SceneList from '../Controls/Panel/scene-list.vue'
+import { enterRoom, changeScene as changeSceneRaw, currentScene } from '@/store/room'
 import { useApp, getApp } from "@/app";
 import { useStore } from "vuex";
 import { Dialog } from "@/global_components/";
@@ -222,11 +241,14 @@ import Trtccom from "./Trtccom.vue";
 import browser from "@/utils/browser";
 import wxShare from "@/utils/wxshare";
 import defaultAvatar from "@/assets/images/avatar_default.png";
+import { isMiniApp } from '@/env'
+import { initialMini } from './mini-platform'
+import Icon from '@/components/icon/index.vue'
+
 const emit = defineEmits(["openDialog", "closeSocket"]);
 
 const store = useStore();
 const leaderAvatar = computed(() => {
-  console.log(typeof store.getters["rtc/avatar"]);
   return store.getters["rtc/avatar"];
 });
 
@@ -291,6 +313,16 @@ const tagImageIndex = computed(() => store.getters["tag/tagImageIndex"]);
 const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
 const audioDeviceId = computed(() => store.getters["rtc/audioDeviceId"]);
 
+const disabledAudio = computed(() => {
+  if (isMiniApp) {
+    return false
+  } else {
+    return audioDeviceId.value
+  }
+})
+
+watchEffect(() => console.error(disabledAudio.value))
+
 const connectStatus = ref(0);
 const isBrushes = ref(false);
 const showInput = ref(false);
@@ -339,7 +371,11 @@ const tags = computed(() => {
 });
 
 const onClickShare = () => {
-  openDialog("dialogShare", shareLink.value);
+  if (isMiniApp) {
+    mini.enterShare()
+  } else {
+    openDialog("dialogShare", shareLink.value);
+  }
 };
 
 const userGetOut = (item, i) => {
@@ -455,12 +491,15 @@ const setUserJoin = async (res) => {
   };
   if (role.value == "leader") {
     chatList.value.push(data);
+    socket.value.emit('action', { type: 'changeScene', data: currentScene.value })
   }
 
   await nextTick();
   try {
     chatAutoScroll();
   } catch (error) {}
+
+
 };
 const changeFile = (e) => {
   let file = e.target.files[0];
@@ -681,8 +720,9 @@ const startFollow = (app) => {
   const leaderInfo = ref({});
   // 加入房間成功
   socket.value.on("join", (data) => {
-    showAvatar.value = true;
-
+    console.log(data)
+    // showAvatar.value = true;
+    enterRoom()
     let meblist = data.members.reduce(function (tempArr, item) {
       if (tempArr.findIndex((ele) => ele.UserId === item.UserId) === -1) {
         tempArr.push(item);
@@ -716,8 +756,11 @@ const startFollow = (app) => {
 
     isJoined.value = true;
 
+    
     setTimeout(() => {
-      isRunRTC.value = true;
+      if (!isMiniApp) {
+        isRunRTC.value = true;
+      }
     }, 3000);
 
     user_info.value = data.user;
@@ -885,6 +928,8 @@ const startFollow = (app) => {
           location.replace(url);
         }
       }
+    } else if (data.type === 'changeScene') {
+      changeSceneRaw(data.data)
     }
   });
 
@@ -913,6 +958,7 @@ let onfollowPaint = async (data) => {
   socket.value.emit("paint", data);
 };
 
+
 onMounted(async () => {
   let app = await getApp();
   startFollow(app);
@@ -925,6 +971,18 @@ onUnmounted(async () => {
   app.Connect.follow.off("data", onfollowData);
   app.Connect.follow.off("data", onfollowData);
 });
+
+const mini = isMiniApp && initialMini(audioMuted)
+const showScenes = ref(false)
+const changeScene = (scene) => {
+  socket.value.emit("action", {
+    type: "changeScene",
+    data: scene,
+  });
+  setTimeout(() => {
+    changeSceneRaw(scene)
+  }, 500)
+}
 </script>
 
 <style scoped lang="scss">

+ 1 - 0
src/components/RTC/Trtccom.vue

@@ -478,6 +478,7 @@ function handleStreamUpdated(event) {
 }
 
 let switchDevice = async ({ videoId, audioId }) => {
+  console.log()
   if (!isJoined.value) {
     return;
   }

+ 10 - 1
src/components/RTC/index.vue

@@ -21,7 +21,11 @@ import browser from '@/utils/browser';
 import { onMounted, watch, computed, ref, nextTick } from 'vue';
 import { useStore } from 'vuex';
 import { useApp, getApp } from '@/app';
+import { useCurrentMini } from './mini-platform'
+import { isMiniApp } from '@/env'
+import { leaveRoom } from '@/store/room'
 
+console.error('RTC create')
 const store = useStore();
 
 const shareLink = ref('');
@@ -44,7 +48,11 @@ const closeDialog = (str, link) => {
 };
 
 const confirmDialog = async () => {
+  if (isMiniApp) {
+    useCurrentMini()?.leave()
+  }
   await getApp().Connect.follow.exit();
+  await leaveRoom()
   if (socket.value) {
     if (browser.getURLParam('role') == 'leader') {
       socket.value.emit('action', { type: 'leader-dismiss' });
@@ -62,9 +70,10 @@ const confirmDialog = async () => {
   store.commit('rtc/setAudioDeviceId', '');
 
   let tempUrl = window.location.href;
-  ['mode', 'name', 'role', 'roomId', 'vruserId'].forEach((item) => {
+  ['mode', 'name', 'role','vruserId'].forEach((item) => {
     tempUrl = browser.replaceQueryString(tempUrl, item, '');
   });
+  // tempUrl = browser.replaceQueryString(tempUrl, 'role', 'customer')
 
   history.replaceState(null, null, tempUrl);
   store.commit('rtc/setRole', '');

+ 79 - 0
src/components/RTC/mini-platform.js

@@ -0,0 +1,79 @@
+import { useStore } from "vuex";
+import { watchEffect, watch, computed, nextTick } from 'vue'
+import browser from "@/utils/browser";
+
+let mini
+
+export const useCurrentMini = () => mini
+
+export const initialMini = (audioMuted) => {
+  const store = useStore();
+  const socket = computed(() => store.getters["rtc/socket"]);
+
+  let isStop = false
+  const stopUpdate = (cb) => {
+    isStop = true
+    cb()
+    nextTick(() => isStop = false)
+  }
+
+
+  watch(socket, (news, olds, onCleatup) => {
+    if (!socket.value) {
+      return;
+    }
+
+    const signalActions = {
+      openAudioCallback(isOpen) {
+        stopUpdate(() => {
+          audioMuted.value = !isOpen
+          console.log('更改')
+        })
+      }
+    }
+    
+    let stopMutedWatch
+    const joinCallback = data => {
+      console.log('join room')
+      store.commit("rtc/setAvatar", browser.getURLParam("avatar"));
+      stopMutedWatch && stopMutedWatch()
+      console.log('进入监听')
+      stopMutedWatch = watch(audioMuted, (audioMuted, oldAudioMuted) => {
+        if (!isStop && audioMuted !== oldAudioMuted) {
+          console.error(audioMuted, oldAudioMuted)
+          socket.value.emit('signal', { type: 'openAudio', payload: !audioMuted })
+          if (audioMuted) {
+            console.log('静音请求')
+          } else {
+            console.log('开启声音请求')
+          }
+        }
+      }, { immediate: true, flush: 'pre' })
+    }
+    const signalCallback = data => {
+      if (data.type in signalActions) {
+        signalActions[data.type](data.payload)
+      }
+    }
+
+    socket.value.on("join", joinCallback)
+    socket.value.on('signal', signalCallback)
+
+    onCleatup(() => {
+      stopMutedWatch && stopMutedWatch()
+      socket.value.off('join', joinCallback)
+      socket.value.on('signal', signalCallback)
+    })
+  }, { immediate: true })
+
+  mini = {
+    enterShare() {
+      socket.value.emit('signal', { type: 'enterShareMode' })
+    },
+    leave() {
+      socket.value.emit('signal', { type: 'onUnload' })
+    }
+  }
+
+  return mini
+}

BIN
src/components/icon/images/arrow@2x.png


BIN
src/components/icon/images/arrows@2x.png


BIN
src/components/icon/images/brushes@2x.png


BIN
src/components/icon/images/brushes_selected@2x.png


BIN
src/components/icon/images/chat_off@2x.png


BIN
src/components/icon/images/chat_on@2x.png


BIN
src/components/icon/images/cross@2x.png


BIN
src/components/icon/images/exit@2x.png


BIN
src/components/icon/images/guided@2x.png


BIN
src/components/icon/images/invitation@2x.png


BIN
src/components/icon/images/members@2x.png


BIN
src/components/icon/images/mic_off@2x.png


BIN
src/components/icon/images/mic_off_50@2x.png


BIN
src/components/icon/images/mic_on@2x.png


BIN
src/components/icon/images/play.png


BIN
src/components/icon/images/pop-up_screen_off@2x.png


BIN
src/components/icon/images/pop-up_screen_on@2x.png


BIN
src/components/icon/images/revocation@2x.png


BIN
src/components/icon/images/revocation_50%@2x.png


BIN
src/components/icon/images/scene.png


BIN
src/components/icon/images/show@2x.png


BIN
src/components/icon/images/编组@2x.png


+ 24 - 0
src/components/icon/index.vue

@@ -0,0 +1,24 @@
+<template>
+  <span class="iii-icon">
+    <img :src="require(`./images/${type}.png`)" alt="">
+  </span>
+</template>
+
+<script setup>
+defineProps({ type: { default: '' } })
+</script>
+
+<style scoped lang="scss">
+.iii-icon {
+  width: 1em;
+  height: 1em;
+  display: inline-block;
+
+  img {
+    width: 100%;
+    height: 100%;
+    display: block;
+    object-fit: cover;
+  }
+}
+</style>

+ 5 - 0
src/env.js

@@ -0,0 +1,5 @@
+import browser from "@/utils/browser";
+
+
+export const isMiniApp = browser.getURLParam("isMiniApp") === '1'
+

+ 48 - 0
src/store/room.js

@@ -0,0 +1,48 @@
+import browser from "@/utils/browser";
+import axios from "axios";
+import { ref, computed } from 'vue'
+
+const roomParam = browser.getURLParam("roomId")
+export const room = ref(null)
+export const roomId = roomParam && roomParam.substr(7)
+export const sceneList = computed(() => room.value?.sceneData || [])
+export const isLeader = browser.getURLParam("role") === 'leader'
+export const currentScene = computed(() => {
+  const num = browser.getURLParam("m")
+  return sceneList.value.find(scene => scene.num === num)
+})
+
+export const changeScene = (scene) => {
+  if (currentScene.value?.num !== scene.num) {
+    console.log(scene, currentScene.value)
+    const params = new URLSearchParams(location.search)
+    params.set('m', scene.num)
+    const url = new URL(location.href)
+    url.search = `?` + params.toString()
+    location.replace(url)
+  }
+}
+
+export const initialRoom = async () => {
+  const res = await axios.get('/takelook/roomInfo', { params: { roomId } })
+  room.value = res.data.data
+  console.log(room.value)
+}
+
+export const enterRoom = async () => {
+  if (!isLeader) return;
+  await axios.get('/takelook/inOrOutRoom', { params: {
+    type: 0,
+    role: 'leader',
+    roomId
+  } })
+}
+
+export const leaveRoom = async () => {
+  if (!isLeader) return;
+  await axios.get('/takelook/inOrOutRoom', { params: {
+    type: 1,
+    role: 'leader',
+    roomId
+  } })
+}

+ 4 - 0
vue.config.js

@@ -21,6 +21,10 @@ module.exports = defineConfig({
         target: process.env.VUE_APP_APIS_URL,
         changeOrigin: true,
       },
+      "/takelook": {
+        target: 'http://v4-test.4dkankan.com/',
+        changeOrigin: true,
+      },
     },
   },
 });