Przeglądaj źródła

master: Added 添加喊话

gitboyzcf 5 miesięcy temu
rodzic
commit
bf52001a7a

+ 18 - 0
src/views/preview-list/children/preview-info.vue

@@ -50,6 +50,22 @@
                   </template>
                 </a-button>
               </a-tooltip>
+              <!-- 喊话 -->
+              <a-popover
+                title=""
+                trigger="click"
+                position="top"
+                :content-style="{ padding: '0 15px' }"
+              >
+                <a-button @click="speechClick">
+                  <template #icon>
+                    <Icon icon="ri:speak-line" width="22" height="22" />
+                  </template>
+                </a-button>
+                <template #content>
+                  <Speech />
+                </template>
+              </a-popover>
               <!-- 对讲 -->
               <a-tooltip :content="$t('previewList.dj')">
                 <a-button @click="intercomClick">
@@ -292,6 +308,7 @@
   import { useAppStore } from '@/store';
   import dayjs from 'dayjs';
   import { Message } from '@arco-design/web-vue';
+  import Speech from '../components/speech.vue';
   // import useWheel from '@/assets/js/useWheel';
 
   const { t } = useI18n();
@@ -493,6 +510,7 @@
     // (app.ip as string).split('//')[1]
     IntercomFn(intercomDisabled, '39.107.83.190');
   };
+  const speechClick = () => {};
   watch(
     radioActive,
     (newV) => {

+ 12 - 0
src/views/preview-list/components/alarm-all.vue

@@ -0,0 +1,12 @@
+<template>
+  <div class="alarm-all"> 测试 </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+</script>
+
+<style lang="less" scoped>
+  .alarm-all {
+  }
+</style>

+ 63 - 0
src/views/preview-list/components/pub-modal.vue

@@ -0,0 +1,63 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    :title="title"
+    :unmount-on-close="true"
+    :mask="mask"
+    :closable="closable"
+    :ok-loading="okLoading"
+    :draggable="draggable"
+    :fullscreen="fullscreen"
+    :hide-title="hideTitle"
+    :footer="footer"
+    @ok="handleOk"
+    @cancel="handleCancel"
+  >
+    <div class="modal-content">
+      <slot></slot>
+    </div>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+
+  withDefaults(
+    defineProps<{
+      title?: string;
+      mask?: boolean;
+      closable?: boolean;
+      okLoading?: boolean;
+      hideTitle?: boolean;
+      draggable?: boolean;
+      fullscreen?: boolean;
+      footer?: boolean;
+    }>(),
+    {
+      title: '提示',
+    }
+  );
+
+  const visible = ref(false);
+
+  const emits = defineEmits(['ok', 'cancel']);
+
+  const handleOk = () => emits('ok');
+  const handleCancel = () => emits('cancel');
+
+  const open = () => {
+    visible.value = true;
+  };
+  const close = () => {
+    visible.value = false;
+  };
+
+  defineExpose({ open, close });
+</script>
+
+<style lang="less" scoped>
+  .modal-content {
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 192 - 0
src/views/preview-list/components/speech.vue

@@ -0,0 +1,192 @@
+<template>
+  <div class="speech">
+    <svg
+      v-if="!isSpeech"
+      xmlns="http://www.w3.org/2000/svg"
+      width="24"
+      height="24"
+      viewBox="0 0 24 24"
+    >
+      <rect width="2.8" height="12" x="1" y="6" fill="#8ac5ff" />
+      <rect width="2.8" height="12" x="5.8" y="6" fill="#8ac5ff" />
+      <rect width="2.8" height="12" x="10.6" y="6" fill="#8ac5ff" />
+      <rect width="2.8" height="12" x="15.4" y="6" fill="#8ac5ff" />
+      <rect width="2.8" height="12" x="20.2" y="6" fill="#8ac5ff" />
+    </svg>
+    <Icon
+      v-else
+      class="icon"
+      icon="svg-spinners:bars-scale-middle"
+      width="24"
+      height="24"
+      animation="0.7"
+      color="#8ac5ff"
+    />
+    <!-- <div class="second">{{ second }}s</div> -->
+    <a-tooltip :content="$t('previewList.hh')">
+      <a-button
+        @mousedown="speechDown"
+        @mouseup="speechUp"
+        @mouseleave="speechUp"
+      >
+        <template #icon>
+          <Icon
+            class="icon"
+            icon="tabler:microphone"
+            width="24"
+            height="24"
+            color="#8ac5ff"
+          />
+        </template>
+      </a-button>
+    </a-tooltip>
+    <a-tooltip :content="$t('previewList.bt')">
+      <a-button
+        v-if="!isPlay"
+        :disabled="audioChunks.length === 0"
+        @click="speechPlay"
+      >
+        <template #icon>
+          <Icon
+            class="icon"
+            icon="majesticons:play-circle-line"
+            width="24"
+            height="24"
+            color="#8ac5ff"
+          />
+        </template>
+      </a-button>
+      <a-button v-else @click="speechStop">
+        <template #icon>
+          <Icon
+            class="icon"
+            icon="gg:play-stop-o"
+            width="24"
+            height="24"
+            color="#8ac5ff"
+          />
+        </template>
+      </a-button>
+    </a-tooltip>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { Icon } from '@iconify/vue';
+  import { Message } from '@arco-design/web-vue';
+  import { useAppStore } from '@/store';
+
+  const app = useAppStore();
+  const isSpeech = ref(false);
+  const isPlay = ref(false);
+  const audioChunks = ref<any[]>([]);
+  let mediaRecorder: MediaRecorder;
+  let audioUrl;
+  let audioBlob: any;
+  let audio: any;
+  const second = ref(0);
+
+  // 获取用户的麦克风权限并开始录音
+  const getMicrophone = async () => {
+    let previousTime = Date.now();
+    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+
+    mediaRecorder = new MediaRecorder(stream);
+    mediaRecorder.ondataavailable = (event) => {
+      second.value += 1;
+      const timeNow = Date.now();
+      // eslint-disable-next-line no-console
+      console.log(`Interval: ${(timeNow - previousTime) / 1000}s`);
+      previousTime = timeNow;
+      audioChunks.value.push(event.data);
+    };
+
+    mediaRecorder.onstop = () => {
+      audio = document.createElement('audio');
+      audio.controls = true;
+      audioBlob = new Blob(audioChunks.value, { type: 'audio/wav' });
+      audioUrl = URL.createObjectURL(audioBlob);
+      audio.src = audioUrl;
+    };
+
+    if (audio) {
+      audio = null;
+      audioChunks.value = [];
+    }
+    mediaRecorder.start();
+    Message.info('开始录音');
+  };
+
+  // 停止录音
+  const stopMicrophone = async () => {
+    if (!isSpeech.value) return;
+    mediaRecorder.stop();
+    Message.info('录音结束');
+  };
+
+  // 播放录音
+  const playMicrophone = async () => {
+    audio.play();
+  };
+
+  // 将录音发送到服务器
+  const sendMicrophone = async () => {
+    const formData = new FormData();
+    formData.append('audio', audioBlob, 'recording.wav');
+    try {
+      const response = await fetch(`${app.ip}/IM/AudioFile`, {
+        method: 'PUT',
+        body: formData,
+      });
+
+      if (response.ok) {
+        Message.success('音频上传成功');
+        // alert('Audio uploaded successfully');
+      } else {
+        // alert('Failed to upload audio');
+        Message.error('上传音频失败');
+      }
+    } catch (error) {
+      Message.error('上传音频失败');
+      // eslint-disable-next-line no-console
+      console.error('Error uploading audio:', error);
+      // alert('Error uploading audio');
+    }
+  };
+
+  const speechDown = () => {
+    isSpeech.value = true;
+    getMicrophone();
+  };
+  const speechUp = () => {
+    stopMicrophone();
+    isSpeech.value = false;
+    sendMicrophone();
+  };
+
+  const speechStop = () => {
+    isPlay.value = false;
+  };
+  const speechPlay = () => {
+    // eslint-disable-next-line no-console
+    console.log('播放开始');
+    isPlay.value = true;
+    playMicrophone();
+    audio.onended = () => {
+      // eslint-disable-next-line no-console
+      console.log('播放结束');
+      speechStop();
+    };
+  };
+</script>
+
+<style scoped lang="less">
+  .speech {
+    height: 40px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 15px;
+  }
+</style>

+ 30 - 8
src/views/preview-list/index.vue

@@ -351,6 +351,22 @@
                           </template>
                           <template #title>
                             <span>{{ t('previewList.cz') }}</span>
+                            <a-tooltip :content="$t('previewList.tip1')">
+                              <a-button
+                                type="text"
+                                size="mini"
+                                style="position: absolute; right: 8px"
+                                @click="modalRef.open()"
+                              >
+                                <template #icon>
+                                  <Icon
+                                    icon="gg:view-list"
+                                    width="20"
+                                    height="20"
+                                  />
+                                </template>
+                              </a-button>
+                            </a-tooltip>
                             <a-tooltip :content="$t('previewList.tip1')">
                               <a-button
                                 type="text"
@@ -395,6 +411,9 @@
         <PreviewInfo :data="comData" @handle-back="handleBack" />
       </template>
     </Transition>
+    <PubModal ref="modalRef" :fullscreen="true">
+      <AlarmAll />
+    </PubModal>
   </div>
 </template>
 
@@ -403,7 +422,6 @@
   // import { useFullscreen } from '@vueuse/core';
   import { useAppStore } from '@/store';
   import useWorker from '@/assets/js/video-lib/omnimatrix-video-player';
-  import { TreeNodeData } from '@arco-design/web-vue/es/tree/interface';
   import { Icon } from '@iconify/vue'; // https://iconify.design/docs/icon-components/vue/#iconify-for-vue
   import {
     ref,
@@ -419,6 +437,10 @@
   import { Message } from '@arco-design/web-vue';
   import { useDebounceFn } from '@vueuse/core';
   import dayjs from 'dayjs';
+  import AlarmAll from './components/alarm-all.vue';
+  import PubModal from './components/pub-modal.vue';
+
+  const modalRef = ref();
 
   const resizeArr: any[] = [];
   const { loading, setLoading } = useLoading(true);
@@ -545,7 +567,7 @@
     },
   ]);
 
-  const originTreeData = ref<TreeNodeData[]>([
+  const originTreeData = ref<any[]>([
     {
       title: 'camera 0-0',
       key: '0-0',
@@ -578,7 +600,7 @@
 
   const searchKey = ref<string>('');
 
-  function searchData(keyword: string): TreeNodeData[] {
+  function searchData(keyword: string): any[] {
     function loop<T extends { [k: string]: any }>(data: T[]): T[] {
       const result: T[] = [];
       data.forEach((item) => {
@@ -597,7 +619,7 @@
       return result;
     }
 
-    return loop<TreeNodeData>(originTreeData.value);
+    return loop<any>(originTreeData.value);
   }
   const fetchData = async (
     params: PolicyParams = { current: 1, pageSize: 20, Status: 'All' }
@@ -606,7 +628,7 @@
     try {
       const { data } = await queryPolicyList(params);
       const res = Array.isArray(data) ? data : [];
-      originTreeData.value = res.map((item: TreeNodeData & PolicyRecord) => ({
+      originTreeData.value = res.map((item: any & PolicyRecord) => ({
         key: item.id,
         id: item.id,
         title: item.alias,
@@ -621,7 +643,7 @@
     }
   };
 
-  const treeData = computed<TreeNodeData[]>(() => {
+  const treeData = computed<any[]>(() => {
     if (!searchKey.value) return originTreeData.value;
     return searchData(searchKey.value);
   });
@@ -656,8 +678,8 @@
     selectedKeys: Array<string | number>,
     data: {
       selected?: boolean;
-      selectedNodes: TreeNodeData[];
-      node?: TreeNodeData;
+      selectedNodes: any[];
+      node?: any;
       e?: Event;
     }
   ): void => {