Przeglądaj źródła

feat(callLogs): add new audio player component and update style.css

闪电 10 miesięcy temu
rodzic
commit
c45187881f

+ 40 - 0
src/assets/style.css

@@ -620,10 +620,18 @@ video {
620 620
   right: -0.25rem;
621 621
 }
622 622
 
623
+.-right-2 {
624
+  right: -0.5rem;
625
+}
626
+
623 627
 .-top-1 {
624 628
   top: -0.25rem;
625 629
 }
626 630
 
631
+.-top-2 {
632
+  top: -0.5rem;
633
+}
634
+
627 635
 .bottom-0 {
628 636
   bottom: 0px;
629 637
 }
@@ -797,6 +805,10 @@ video {
797 805
   display: flex;
798 806
 }
799 807
 
808
+.inline-flex {
809
+  display: inline-flex;
810
+}
811
+
800 812
 .table {
801 813
   display: table;
802 814
 }
@@ -917,6 +929,10 @@ video {
917 929
   width: 10rem;
918 930
 }
919 931
 
932
+.w-5 {
933
+  width: 1.25rem;
934
+}
935
+
920 936
 .w-5\/6 {
921 937
   width: 83.333333%;
922 938
 }
@@ -1047,6 +1063,10 @@ video {
1047 1063
   gap: 0.5rem;
1048 1064
 }
1049 1065
 
1066
+.gap-3 {
1067
+  gap: 0.75rem;
1068
+}
1069
+
1050 1070
 .gap-4 {
1051 1071
   gap: 1rem;
1052 1072
 }
@@ -1242,6 +1262,11 @@ video {
1242 1262
   background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
1243 1263
 }
1244 1264
 
1265
+.bg-red-500 {
1266
+  --tw-bg-opacity: 1;
1267
+  background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
1268
+}
1269
+
1245 1270
 .bg-white {
1246 1271
   --tw-bg-opacity: 1;
1247 1272
   background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
@@ -1334,6 +1359,11 @@ video {
1334 1359
   padding-right: 0.5rem;
1335 1360
 }
1336 1361
 
1362
+.px-3 {
1363
+  padding-left: 0.75rem;
1364
+  padding-right: 0.75rem;
1365
+}
1366
+
1337 1367
 .px-4 {
1338 1368
   padding-left: 1rem;
1339 1369
   padding-right: 1rem;
@@ -1417,6 +1447,11 @@ video {
1417 1447
   line-height: 2.25rem;
1418 1448
 }
1419 1449
 
1450
+.text-4xl {
1451
+  font-size: 2.25rem;
1452
+  line-height: 2.5rem;
1453
+}
1454
+
1420 1455
 .text-\[10px\] {
1421 1456
   font-size: 10px;
1422 1457
 }
@@ -1481,6 +1516,11 @@ video {
1481 1516
   color: rgb(37 99 235 / var(--tw-text-opacity, 1));
1482 1517
 }
1483 1518
 
1519
+.text-blue-800 {
1520
+  --tw-text-opacity: 1;
1521
+  color: rgb(30 64 175 / var(--tw-text-opacity, 1));
1522
+}
1523
+
1484 1524
 .text-gray-400 {
1485 1525
   --tw-text-opacity: 1;
1486 1526
   color: rgb(156 163 175 / var(--tw-text-opacity, 1));

+ 0 - 173
src/components/callLogs/audio.vue

@@ -1,173 +0,0 @@
1
-<template>
2
-    <div class="h-full bg-white p-6 flex flex-col">
3
-        <!-- 头部 -->
4
-        <div class="flex justify-between items-center mb-8">
5
-          <h2 class="text-xl font-medium">通话录音</h2>
6
-          <el-icon class="cursor-pointer text-gray-400 hover:text-gray-600" @click="drawerVisible = false">
7
-            <Close />
8
-          </el-icon>
9
-        </div>
10
-        <!-- 音频播放器 -->
11
-        <div class="bg-gray-50 rounded-lg p-6 mb-6">
12
-          <div class="flex justify-center mb-4">
13
-            <el-button class="w-16 h-16 !rounded-full flex items-center justify-center"
14
-              :type="isPlaying ? 'danger' : 'primary'" @click="togglePlay">
15
-              <el-icon class="text-2xl">
16
-                <component :is="isPlaying ? 'Pause' : 'VideoPlay'" />
17
-              </el-icon>
18
-            </el-button>
19
-          </div>
20
-          <!-- 进度条 -->
21
-          <div class="mb-2">
22
-            <el-slider v-model="progress" :max="100" @change="handleProgressChange" />
23
-          </div>
24
-          <!-- 时间显示 -->
25
-          <div class="flex justify-between text-sm text-gray-500">
26
-            <span>{{ formatTime(currentTime) }}</span>
27
-            <span>{{ formatTime(duration) }}</span>
28
-          </div>
29
-        </div>
30
-        <!-- 操作按钮 -->
31
-        <div class="mb-6">
32
-          <el-button type="primary" class="w-full !rounded-button whitespace-nowrap" @click="handleDownload">
33
-            <el-icon class="mr-2">
34
-              <Download />
35
-            </el-icon>
36
-            下载录音
37
-          </el-button>
38
-        </div>
39
-        <!-- 转写内容 -->
40
-        <div class="bg-white rounded-lg p-4 flex flex-col flex-grow overflow-hidden">
41
-          <h3 class="text-lg font-medium mb-4">语音转写内容</h3>
42
-          <div class="text-gray-600 leading-relaxed overflow-y-auto flex-grow space-y-2">
43
-            <div v-for="(message, index) in transcriptionMessages" :key="index" @click="jumpToTime(message.startTime)"
44
-              :class="[
45
-                'p-3 transition-colors rounded-lg cursor-pointer group',
46
-                message.role === 'agent' ? 'bg-gradient-to-r from-gray-100 to-gray-200 hover:from-gray-200 hover:to-gray-300' : 'bg-gradient-to-r from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200'
47
-              ]">
48
-              <div class="flex justify-between items-center mb-2">
49
-                <div class="flex items-center gap-2">
50
-                  <div class="text-xs text-gray-400">{{ formatTime(message.startTime) }}</div>
51
-                  <div :class="[
52
-                    'px-2 py-1 rounded-full text-xs font-medium whitespace-nowrap',
53
-                    message.role === 'agent' ? 'bg-gray-200 text-gray-700 border border-gray-300' : 'bg-blue-100 text-blue-600 border border-blue-200'
54
-                  ]">
55
-                    {{ message.role === 'agent' ? '客服' : '客户' }}
56
-                  </div>
57
-                </div>
58
-                <el-button class="!rounded-button opacity-0 group-hover:opacity-100 transition-opacity" size="small"
59
-                  type="text" @click.stop="playSegment(message.startTime, message.endTime)">
60
-                  <el-icon class="text-lg">
61
-                    <VideoPlay />
62
-                  </el-icon>
63
-                </el-button>
64
-              </div>
65
-              <div class="text-gray-700">{{ message.content }}</div>
66
-              <div class="text-xs text-gray-400 mt-1">置信度: {{ message.confidence }}%</div>
67
-            </div>
68
-          </div>
69
-        </div>
70
-      </div>
71
-</template>
72
-
73
-<script lang="ts" setup>
74
-import { ref } from 'vue';
75
-import { ElMessage } from 'element-plus';
76
-import { Close, VideoPlay, Pause, Download } from '@element-plus/icons-vue';
77
-const drawerVisible = ref(false);
78
-const deleteDialogVisible = ref(false);
79
-const isPlaying = ref(false);
80
-const progress = ref(0);
81
-const currentTime = ref(0);
82
-const duration = ref(180); // 示例时长3分钟
83
-const transcriptionMessages = ref([
84
-  { role: 'agent', content: '您好,这里是顾客服务中心,很高兴为您服务。', startTime: 0, endTime: 4, confidence: 98 },
85
-  { role: 'customer', content: '你好,我想咨询一下我前天购买的产品退货问题。', startTime: 5, endTime: 10, confidence: 96 },
86
-  { role: 'agent', content: '好的,请问您能提供一下订单号码吗?', startTime: 11, endTime: 15, confidence: 97 },
87
-  { role: 'customer', content: '是的,订单号是2023112509876。', startTime: 16, endTime: 20, confidence: 99 },
88
-  { role: 'agent', content: '好的,我已经查询到您的订单信息。请问您想退货的原因是什么呢?', startTime: 21, endTime: 27, confidence: 98 },
89
-  { role: 'customer', content: '产品规格和我预期的不太一样,我想换一个大一点的型号。', startTime: 28, endTime: 35, confidence: 95 },
90
-  { role: 'agent', content: '明白了。根据我们的退换货政策,您的商品符合七天无理由退换货条件。我这边帮您申请退货,您收到退货地址后,请在三天内寄出商品。', startTime: 36, endTime: 48, confidence: 97 },
91
-  { role: 'customer', content: '好的,麻烦你了。请问退款大概需要多久?', startTime: 49, endTime: 54, confidence: 98 },
92
-  { role: 'agent', content: '商品寄出后,我们收到并确认商品完好,会在1-3个工作日内为您办理退款。退款会原路返回至您的支付账户。', startTime: 55, endTime: 65, confidence: 96 },
93
-  { role: 'customer', content: '明白了,谢谢你的解释。', startTime: 66, endTime: 70, confidence: 99 },
94
-  { role: 'agent', content: '不客气,很高兴能帮到您。如果您还有其他问题,随时可以联系我们。祝您生活愉快!', startTime: 71, endTime: 80, confidence: 97 }
95
-]);
96
-const formatTime = (seconds: number): string => {
97
-  const minutes = Math.floor(seconds / 60);
98
-  const remainingSeconds = Math.floor(seconds % 60);
99
-  return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
100
-};
101
-const togglePlay = () => {
102
-  isPlaying.value = !isPlaying.value;
103
-  if (isPlaying.value) {
104
-    // 模拟播放进度
105
-    const timer = setInterval(() => {
106
-      if (progress.value >= 100) {
107
-        clearInterval(timer);
108
-        isPlaying.value = false;
109
-        progress.value = 0;
110
-        currentTime.value = 0;
111
-        return;
112
-      }
113
-      progress.value += 1;
114
-      currentTime.value = (progress.value / 100) * duration.value;
115
-    }, 1000);
116
-  }
117
-};
118
-const handleProgressChange = (value: number) => {
119
-  currentTime.value = (value / 100) * duration.value;
120
-};
121
-const jumpToTime = (time: number) => {
122
-  progress.value = (time / duration.value) * 100;
123
-  currentTime.value = time;
124
-};
125
-const playSegment = (startTime: number, endTime: number) => {
126
-  progress.value = (startTime / duration.value) * 100;
127
-  currentTime.value = startTime;
128
-  isPlaying.value = true;
129
-  // 模拟播放到指定片段结束
130
-  const timer = setInterval(() => {
131
-    if (currentTime.value >= endTime || !isPlaying.value) {
132
-      clearInterval(timer);
133
-      isPlaying.value = false;
134
-      return;
135
-    }
136
-    progress.value = (currentTime.value / duration.value) * 100;
137
-    currentTime.value += 1;
138
-  }, 1000);
139
-};
140
-const handleDownload = () => {
141
-  ElMessage.success('录音文件下载中...');
142
-};
143
-const handleDelete = () => {
144
-  deleteDialogVisible.value = true;
145
-};
146
-const confirmDelete = () => {
147
-  deleteDialogVisible.value = false;
148
-  drawerVisible.value = false;
149
-  ElMessage.success('录音已删除');
150
-};
151
-const openDrawer = () => {
152
-  drawerVisible.value = true;
153
-  progress.value = 0;
154
-  currentTime.value = 0;
155
-  isPlaying.value = false;
156
-};
157
-</script>
158
-<style scoped>
159
-.el-drawer__body {
160
-  padding: 0;
161
-  overflow: hidden;
162
-}
163
-
164
-/* 隐藏滚动条但保留滚动功能 */
165
-.overflow-y-auto::-webkit-scrollbar {
166
-  display: none;
167
-}
168
-
169
-.overflow-y-auto {
170
-  -ms-overflow-style: none;
171
-  scrollbar-width: none;
172
-}
173
-</style>

+ 340 - 0
src/components/callLogs/audioPlayer.vue

@@ -0,0 +1,340 @@
1
+<template>
2
+  <div class="h-full bg-white p-6 flex flex-col">
3
+    <!-- 头部 -->
4
+    <div class="flex justify-between items-center mb-8">
5
+      <h2 class="text-xl font-medium">通话录音</h2>
6
+      <el-icon class="cursor-pointer text-gray-400 hover:text-gray-600" @click="drawerVisible = false">
7
+        <Close />
8
+      </el-icon>
9
+    </div>
10
+    <!-- 音频播放器 -->
11
+    <div class="bg-gray-50 rounded-lg p-6 mb-6">
12
+      <div class="flex justify-center mb-4">
13
+        <audio ref="audioPlayer" :src="filePath" @timeupdate="updateTime"></audio>
14
+        <el-button class="w-16 h-16 !rounded-full flex items-center justify-center"
15
+          :type="isPlaying ? 'danger' : 'primary'" @click="togglePlay">
16
+          <el-icon class="text-4xl">
17
+            <component :is="isPlaying ? 'VideoPause' : 'VideoPlay'" />
18
+          </el-icon>
19
+        </el-button>
20
+      </div>
21
+      <!-- 进度条 -->
22
+      <div class="mb-2">
23
+        <el-slider v-model="progress" :max="100" @change="handleProgressChange" />
24
+      </div>
25
+      <!-- 时间显示 -->
26
+      <div class="flex justify-between text-sm text-gray-500">
27
+        <span>{{ formatTime(currentTime) }}</span>
28
+        <span>{{ formatTime(duration) }}</span>
29
+      </div>
30
+    </div>
31
+    <!-- 操作按钮 -->
32
+    <div class="mb-6">
33
+      <el-button type="primary" class="w-full !rounded-button whitespace-nowrap" @click="handleDownload">
34
+        <el-icon class="mr-2">
35
+          <Download />
36
+        </el-icon>
37
+        下载录音
38
+      </el-button>
39
+    </div>
40
+    <!-- 转写内容 -->
41
+    <div class="bg-white rounded-lg p-4 flex flex-col flex-grow overflow-hidden mb-6"
42
+      v-if="transcriptionMessages.length">
43
+      <h3 class="text-lg font-medium mb-4">语音转写内容</h3>
44
+      <div class="text-gray-600 leading-relaxed overflow-y-auto flex-grow space-y-2">
45
+        <div v-for="(message, index) in transcriptionMessages" :key="index" @click="jumpToTime(message.startTime)"
46
+          :class="[
47
+            'p-3 transition-colors rounded-lg cursor-pointer group',
48
+            message.direction === 1 ? 'bg-gradient-to-r from-gray-100 to-gray-200 hover:from-gray-200 hover:to-gray-300' : 'bg-gradient-to-r from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200'
49
+          ]">
50
+          <div class="flex justify-between items-center mb-2">
51
+            <div class="flex items-center gap-2">
52
+              <div class="text-xs text-gray-400">{{ message.timestamp }}</div>
53
+              <div :class="[
54
+                'px-2 py-1 rounded-full text-xs font-medium whitespace-nowrap',
55
+                message.direction === 1 ? 'bg-gray-200 text-gray-700 border border-gray-300' : 'bg-blue-100 text-blue-600 border border-blue-200'
56
+              ]">
57
+                {{ message.direction === 1 ? '客服' : '客户' }}
58
+              </div>
59
+            </div>
60
+            <el-button class="!rounded-button opacity-0 group-hover:opacity-100 transition-opacity" size="small"
61
+              type="text" @click.stop="playSegment(message.startTime, message.endTime)">
62
+              <el-icon class="text-lg">
63
+                <VideoPlay />
64
+              </el-icon>
65
+            </el-button>
66
+          </div>
67
+          <div class="text-gray-700">{{ message.page_content }}</div>
68
+          <!-- <div class="text-xs text-gray-400 mt-1">置信度: {{ message.confidence }}%</div> -->
69
+        </div>
70
+      </div>
71
+    </div>
72
+    <!-- 关键词模块 -->
73
+    <div class="mb-6" v-if="keywords.length">
74
+      <h2 class="text-lg font-semibold mb-4">关键词</h2>
75
+      <div class="flex flex-wrap gap-3">
76
+        <div v-for="(keyword, index) in keywords" :key="index"
77
+          class="relative inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
78
+          {{ keyword.text }}
79
+          <span
80
+            class="absolute -top-2 -right-2 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full">{{
81
+              keyword.count }}</span>
82
+        </div>
83
+      </div>
84
+    </div>
85
+  </div>
86
+</template>
87
+
88
+<script lang="ts" setup>
89
+import { ref, reactive, watch, onMounted } from 'vue';
90
+import { ElMessage } from 'element-plus';
91
+import { Close, VideoPlay, VideoPause, Download } from '@element-plus/icons-vue';
92
+import { flatten } from 'lodash';
93
+
94
+import { getPageListData } from '@/api/main/system/system';
95
+import { stripHtmlByRegex, formatMilliseconds } from '@/utils/tools';
96
+
97
+const drawerVisible = ref(false);
98
+const deleteDialogVisible = ref(false);
99
+const isPlaying = ref(false);
100
+const callId = ref(0);
101
+const filePath = ref('');
102
+const audioPlayer = ref(null);
103
+const props = defineProps({
104
+  callId: {
105
+    type: Number,
106
+  },
107
+  filePath: {
108
+    type: String,
109
+  },
110
+});
111
+watch(props, (newValue, oldValue) => {
112
+  if (newValue) {
113
+    callId.value = props.callId;
114
+    filePath.value = props.filePath;
115
+  }
116
+});
117
+
118
+const progress = ref(0);
119
+const currentTime = ref(0);
120
+const duration = ref(180); // 示例时长3分钟
121
+const transcriptionMessages: any = ref([]);
122
+const formatTime = (seconds: number): string => {
123
+  const minutes = Math.floor(seconds / 60);
124
+  const remainingSeconds = Math.floor(seconds % 60);
125
+  return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
126
+};
127
+const togglePlay = () => {
128
+  isPlaying.value = !isPlaying.value;
129
+  if (isPlaying.value) {
130
+    pause();
131
+    // 模拟播放进度
132
+    const timer = setInterval(() => {
133
+      if (progress.value >= 100) {
134
+        clearInterval(timer);
135
+        isPlaying.value = false;
136
+        progress.value = 0;
137
+        currentTime.value = 0;
138
+        return;
139
+      }
140
+      progress.value += 1;
141
+      currentTime.value = (progress.value / 100) * duration.value;
142
+    }, 1000);
143
+  }
144
+};
145
+const handleProgressChange = (value: number) => {
146
+  currentTime.value = (value / 100) * duration.value;
147
+};
148
+const jumpToTime = (time: number) => {
149
+  progress.value = (time / duration.value) * 100;
150
+  currentTime.value = time;
151
+};
152
+
153
+const handleDownload = () => {
154
+  ElMessage.success('录音文件下载中...');
155
+};
156
+const handleDelete = () => {
157
+  deleteDialogVisible.value = true;
158
+};
159
+const confirmDelete = () => {
160
+  deleteDialogVisible.value = false;
161
+  drawerVisible.value = false;
162
+  ElMessage.success('录音已删除');
163
+};
164
+const openDrawer = () => {
165
+  drawerVisible.value = true;
166
+  progress.value = 0;
167
+  currentTime.value = 0;
168
+  isPlaying.value = false;
169
+};
170
+let keywords = reactive([]);
171
+
172
+const getDetail = () => {
173
+  callId.value = 526;
174
+  getPageListData(`/call/translate/${callId.value}`).then(({ data }) => {
175
+    console.log(data, 'res')
176
+    if (data.translate) {
177
+      const translate = JSON.parse(data.translate);
178
+      transcriptionMessages.value = translate.map((msg: any) => {
179
+
180
+        const msgInfo: any = {
181
+          direction: msg.Number.toString().length > 4 ? 2 : 1,
182
+          page_content: msg.Speech || '',
183
+          startTime: 0,
184
+          endTime: 0,
185
+          timestamp: '',
186
+        };
187
+        // console.log(msg.Time, 'msgInfo');
188
+        if (msg.Time) {
189
+          const times = JSON.parse(msg.Time);
190
+          if (times?.length) {
191
+            const timeArray = flatten(times);
192
+            const time = timeArray?.[0];
193
+            if (time) {
194
+              msgInfo.timestamp = formatMilliseconds(time);
195
+              msgInfo.startTime = timeArray[0];
196
+              msgInfo.endTime = timeArray[timeArray.length - 1];
197
+            }
198
+          }
199
+        }
200
+
201
+        return msgInfo;
202
+
203
+      })
204
+
205
+    }
206
+
207
+    if (data.translateJson) {
208
+      keywords = JSON.parse(data.translateJson);
209
+
210
+    }
211
+  })
212
+}
213
+
214
+const startTime = ref(0);
215
+const endTime = ref(0);
216
+// const duration = ref(0);
217
+// const isPlaying = ref(false);
218
+// const currentTime = ref(0);
219
+
220
+const playSegment = (sTime: number, eTime: number) => {
221
+  progress.value = (sTime / duration.value) * 100;
222
+  currentTime.value = sTime;
223
+  endTime.value = eTime;
224
+  // isPlaying.value = true;
225
+  playVideoSegment();
226
+};
227
+
228
+// 播放指定区间
229
+const playVideoSegment = () => {
230
+  if (!audioPlayer.src) return;
231
+
232
+  audioPlayer.currentTime = startTime.value;
233
+  audioPlayer.play();
234
+  isPlaying.value = true;
235
+
236
+  // 模拟播放到指定片段结束
237
+  const timer = setInterval(() => {
238
+    if (currentTime.value >= endTime.value || !isPlaying.value) {
239
+      clearInterval(timer);
240
+      isPlaying.value = false;
241
+      return;
242
+    }
243
+    progress.value = (currentTime.value / duration.value) * 100;
244
+    currentTime.value += 1;
245
+  }, 1000);
246
+
247
+  // 设置区间结束监听
248
+  const checkEnd = () => {
249
+    if (audioPlayer.currentTime >= endTime.value) {
250
+      pause();
251
+      audioPlayer.currentTime = startTime.value; // 回到起点
252
+    } else if (isPlaying.value) {
253
+      requestAnimationFrame(checkEnd);
254
+    }
255
+  };
256
+  requestAnimationFrame(checkEnd);
257
+};
258
+
259
+// 暂停播放
260
+const pause = () => {
261
+  console.log(audioPlayer, 'audioPlayer');
262
+  audioPlayer.play();
263
+  isPlaying.value = false;
264
+}
265
+
266
+// 停止并重置
267
+const stop = () => {
268
+  pause();
269
+  audioPlayer.currentTime = startTime.value;
270
+  currentTime.value = startTime.value;
271
+}
272
+
273
+// 更新当前时间
274
+const updateTime = () => {
275
+  currentTime.value = audioPlayer.currentTime;
276
+}
277
+
278
+const loadRemoteAudio = async () => {
279
+  if (!filePath.value) return
280
+
281
+  try {
282
+
283
+
284
+    // 创建音频对象
285
+    const audio = audioPlayer.value
286
+    audio.src = filePath.value
287
+
288
+    // 等待元数据加载
289
+    await new Promise((resolve, reject) => {
290
+      audio.onloadedmetadata = () => {
291
+        duration.value = audio.duration
292
+        endTime.value = audio.duration
293
+        resolve(1)
294
+      }
295
+
296
+      audio.onerror = (e) => {
297
+        reject(new Error('音频加载失败'))
298
+      }
299
+
300
+      // 设置超时处理
301
+      setTimeout(() => {
302
+        reject(new Error('音频加载超时'))
303
+      }, 10000)
304
+    })
305
+  } catch (err) {
306
+    console.log(err)
307
+  } finally {
308
+  }
309
+}
310
+
311
+onMounted(() => {
312
+  callId.value = props.callId;
313
+  filePath.value = 'http://1.194.161.64:9000/files/luyin/20250228/1011/8a5d1910-a6cf-4674-acb9-61f29f00cb05.wav';
314
+  if (filePath.value) {
315
+    // audioPlayer.src = filePath.value;
316
+    // audioPlayer.onloadedmetadata = () => {
317
+    //   duration.value = audioPlayer.duration;
318
+    //   endTime.value = audioPlayer.duration;
319
+    // };
320
+    loadRemoteAudio();
321
+  } 
322
+  getDetail();
323
+});
324
+</script>
325
+<style scoped>
326
+.el-drawer__body {
327
+  padding: 0;
328
+  overflow: hidden;
329
+}
330
+
331
+/* 隐藏滚动条但保留滚动功能 */
332
+.overflow-y-auto::-webkit-scrollbar {
333
+  display: none;
334
+}
335
+
336
+.overflow-y-auto {
337
+  -ms-overflow-style: none;
338
+  scrollbar-width: none;
339
+}
340
+</style>

+ 8 - 0
src/utils/tools.js

@@ -27,3 +27,11 @@ export const hidePhone = (phone) => {
27 27
 export const stripHtmlByRegex = (html) => {
28 28
     return html.replace(/<[^>]*>/g, '').replace(/&[^;]+;/g, '');
29 29
 }
30
+
31
+export const formatMilliseconds = (milliseconds) => {
32
+    const totalSeconds = Math.floor(milliseconds / 1000);
33
+    const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
34
+    const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
35
+    const seconds = (totalSeconds % 60).toString().padStart(2, '0');
36
+    return `${minutes}:${seconds}`;
37
+}

+ 8 - 8
src/views/main/phone/index.vue

@@ -469,7 +469,7 @@ import { getPageListData, createPageData } from '@/api/main/system/system';
469 469
 import { userDecryptToAsterisk } from '@/utils/aes';
470 470
 import knowledgeList from "@/views/main/knowledgeBase/knowledgeList/cpns/konwlegelist/konwlegelist";
471 471
 
472
-import { max,flatten, keys } from 'lodash';
472
+import { flatten } from 'lodash';
473 473
 import { getOfffixNuber, getCallSate } from '@/utils/index';
474 474
 import { stripHtmlByRegex } from '@/utils/tools';
475 475
 
@@ -917,13 +917,13 @@ window.addEventListener('AsrMessageEvent', (msg: any) => {
917 917
     transcripts.value.push(msgInfo);
918 918
 })
919 919
 
920
-function formatMilliseconds(milliseconds) {
921
-    const totalSeconds = Math.floor(milliseconds / 1000);
922
-    const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
923
-    const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
924
-    const seconds = (totalSeconds % 60).toString().padStart(2, '0');
925
-    return `${minutes}:${seconds}`;
926
-}
920
+// function formatMilliseconds(milliseconds) {
921
+//     const totalSeconds = Math.floor(milliseconds / 1000);
922
+//     const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
923
+//     const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
924
+//     const seconds = (totalSeconds % 60).toString().padStart(2, '0');
925
+//     return `${minutes}:${seconds}`;
926
+// }
927 927
 
928 928
 const aiSubmit = async () => {
929 929
     aiLoading.value = true;

+ 9 - 10
src/views/main/telephone/callRecord/callRecord.vue

@@ -50,14 +50,10 @@
50 50
         >
51 51
       </template>
52 52
     </page-content>
53
-    <el-dialog title="录音" v-model="open" width="700px" append-to-body>
54
-      <audio-player ref="pageModalRef" :filePath="filePath"></audio-player>
55
-      <template #footer>
56
-        <div class="dialog-footer">
57
-          <el-button @click="open = false">关 闭</el-button>
58
-        </div>
59
-      </template>
60
-    </el-dialog>
53
+    <el-drawer title="通话录音" v-model="open" width="500px" :with-header="false" append-to-body direction="rtl">
54
+      <audio-player ref="pageModalRef" :callId="clickId" :filePath="filePath"/>
55
+    
56
+    </el-drawer>
61 57
     <el-dialog
62 58
       title="创建工单"
63 59
       v-model="openCreatOrder"
@@ -83,7 +79,8 @@
83 79
 
84 80
   import PageSearch from '@/components/page-search';
85 81
   import PageContent from '@/components/page-content';
86
-  import audioPlayer from './cpns/audioPlayer';
82
+  // import audioPlayer from './cpns/audioPlayer';
83
+  import audioPlayer from '@/components/callLogs/audioPlayer';
87 84
 
88 85
   import addOrder from '@/components/workOrder/add-order/add-order';
89 86
   import pageOrder from '@/components/page-order';
@@ -121,7 +118,7 @@
121 118
 
122 119
       const pageModalRef = ref(audioPlayer);
123 120
       const recordData = ref({});
124
-      const open = ref(false);
121
+      const open = ref(true);
125 122
       const openCreatOrder = ref(false);
126 123
       const openViewOrder = ref(false);
127 124
       const filePath = ref('');
@@ -139,10 +136,12 @@
139 136
           callPhoneFlag.value = false;
140 137
         }
141 138
       });
139
+      const clickId = ref(0);
142 140
       //查看详情
143 141
       const handleViewData = (data) => {
144 142
         console.log(data.recordPath);
145 143
         filePath.value = data.recordPath;
144
+        clickId.value = data.id;
146 145
         open.value = true;
147 146
       };
148 147
 

+ 10 - 0
tsconfig.json

@@ -0,0 +1,10 @@
1
+{
2
+  "compilerOptions": {
3
+    "baseUrl": ".",
4
+    "paths": {
5
+      "@/*": ["src/*"],
6
+      "@/api/*": ["api/*"]
7
+    }
8
+  },
9
+  "include": ["src", "api"]
10
+}