From fee33a68c6e04c7703bff24ad2c4726541664a0d Mon Sep 17 00:00:00 2001 From: fengerli <528575642@qq.com> Date: Thu, 19 Mar 2026 11:42:26 +0800 Subject: [PATCH] =?UTF-8?q?pc=E7=AB=AF100j=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- src/assets/images/hb.png | Bin 0 -> 1133 bytes src/assets/images/hbAc.png | Bin 0 -> 1199 bytes src/assets/images/rg1.png | Bin 0 -> 304 bytes src/assets/images/rg1Ac.png | Bin 0 -> 1467 bytes src/assets/images/组 62.png | Bin 0 -> 992 bytes src/views/controlCenter/100J/index.vue | 284 +++++++++++++++---------- 7 files changed, 174 insertions(+), 114 deletions(-) create mode 100644 src/assets/images/hb.png create mode 100644 src/assets/images/hbAc.png create mode 100644 src/assets/images/rg1.png create mode 100644 src/assets/images/rg1Ac.png create mode 100644 src/assets/images/组 62.png diff --git a/package.json b/package.json index ca5fd88..46f5773 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,13 @@ "image-conversion": "2.1.1", "js-cookie": "3.0.5", "jsencrypt": "3.3.2", + "lamejs": "^1.2.1", "mitt": "^3.0.1", "nprogress": "0.2.0", "paho-mqtt": "^1.1.0", "pinia": "3.0.2", "qrcode-vue3": "^1.7.1", + "recorder-core": "^1.3.25011100", "screenfull": "6.0.2", "vue": "3.5.13", "vue-cropper": "1.1.1", @@ -72,7 +74,7 @@ "sass": "1.87.0", "terser": "^5.43.1", "typescript": "~5.8.3", - "unocss": "^66.0.0", + "unocss": "^0.58.0", "unplugin-auto-import": "19.1.2", "unplugin-icons": "22.1.0", "unplugin-vue-components": "28.5.0", diff --git a/src/assets/images/hb.png b/src/assets/images/hb.png new file mode 100644 index 0000000000000000000000000000000000000000..e619cde1d08f803fd3e1f28411b94419bcb185bc GIT binary patch literal 1133 zcmV-z1d{uSP)Nyn-3bUmh3_$(8WDiOU0%C-OPA{2bY(iC!@|DLLkbt=E3Iwml#Q+l=-24aq zM1X4UZIEIAT>tpN#4f(EAH!kWnp?;sM+vyzodF;|6y(_+$9L>MG~8$KwIJ?DXymQt zVqBEF-*Rt3uyVo=#jTs$_*)cTrj7UI$fI!QmD+o@aPOPn|YvB&?9ZZR_q-xqozp*Xr34U3oa-x)`)s_2h>JgC+rfZ$i zX{j?9)>Aml1CfNTi>Q;q45=|wCvXwD!6|I(4>+t>HZYg+4CB&xU}SKFA4;?$K_alQ z&k&f0Awxy0l~BkYj1Gp%3~8UBb}|z(^bydB;jleCU^_U$K70(Ba~)$~bTEbvG9ecs zN=qSs+~;FWT|rgeg)#cOY;-WaKLU7Y;aBE&MN}?W@-__p>k5bO@JMyc)-+GMmY+SJ zhM@~9V>}?_hf^~)s|KAd2~ArRE6W- z{aKj$y9a%TS5d)-@k3u7Q{ECirqudk7(a#S7|bRxh55_th(qdET3#=O*ej3Y7zd!* z1eZy=+&H#&{s#a6|No1{^DO`X00v1!K~w_(wUR&-i25QR00000NkvXXu0mjf4xkw3 literal 0 HcmV?d00001 diff --git a/src/assets/images/hbAc.png b/src/assets/images/hbAc.png new file mode 100644 index 0000000000000000000000000000000000000000..84e9e3488b2de71492948f0418a20cffefbf5e47 GIT binary patch literal 1199 zcmV;g1W@~lP)RSxQM8z7^ndk6_g^nDOIdcK~ylJV#=F9784Td{o#Rg30-Mr&>U*C*(CiC7nd1)pJ}87f_BuaD`+!Z>pYoxDr2fc}Ztc);o>FI3*(Y3$Tmt ztRmKa{(k|*as0CnPz2xkawh+>_DN*F;Yxhf~TXyzdwkEXA(e%w8z)CA2@|GBy!p)>IqK3c~h>QG+{K2&_3O=qT|{_AP@P0 z6j)KkFoDcDy>U%KlfhgHXf=_OM$RN%t>FqldF>S@zhFvO;NS{G4hgUikY^!y(}>Q= zcKbWR=osGkdjPd(TvnNUp(c`dKpw|L*J4e_cr})N0w}&W2FfwaOB$38Pj?Nx}>Sv`+vzo8DF!)GTz$wA_@8Z3imlK`WtOw&66V|N&*aZgO zk;ofpSk3qS3T1_83r4!TXNmx+B^J@yhB;I-czr&Yv1l)WWUnYMP&<}!`q9%H=K1A< z5tWg=!JXPT=MJ}A_GzGOFv)SCT(YU>CUn9ZA#SZcmK7=+%+6?#gWI1^X=LRF#)I>@ zRLty*F%M=Lv%#qD$r{wGCyXl(k_5S8md9T3PQ_6hxIh9F=W^@FysDB2-{odcfqcvZaMlrSyWH21aV&a7V3+8ZIZ+$@J;8W<$1JOK2}f_aR38}H{h~NS zAdLX)4*5(9Cf_N?`rp?;XbnQj5!IBO*`3(fdLA=mgXv&X2n*@2K=1hr^h_$A5)IBPDV~#3t$2~ zTF;T@;~x*-gSSe9p5C_w*hk7dz^(--KceK@{oCO|{#S9GG z!XV7ZFl&wkQ1Gg!i(^Q|oVV8k`I;33SORWqf7RLBYyRFn+Ro>S$c(MVuV)x=#=rRF za?Qf`$(`rdn`0--Ri`lOJHEAPQ1GPW?h%ub>{x=-wZA)G8!!mdXPHdzth)UZsP3=?pv7%Zw}P3p2Nv?_rA{ZA?~5kh&uG@}>sEhW7V&wO#YnWdEI+_~?b7=YoubFJ3Qky6EJ7 yF6Pzt;=KvK+O4Ik6HYp8GMaGa2D7)w3H}|asRnPw+j4*&WbkzLb6Mw<&;$Um=63Y} literal 0 HcmV?d00001 diff --git a/src/assets/images/rg1Ac.png b/src/assets/images/rg1Ac.png new file mode 100644 index 0000000000000000000000000000000000000000..448f71d856000566d5880128f1b3124ae189e3e3 GIT binary patch literal 1467 zcmV;s1w{IZP)ii z@0sBlWZm-oP}Y!{lQgJqn%X2c^9%;Bf{a-b%{G%A!6G7f@PdU?mn5UMAD#M`5z|aF zNx&n@t1+saMbDI^5DG~GR3DK^%Z_A`=g5BSQdR_>jXZa&oJDUT^Q#ZX zY-pSwTdXUJtN3+wvY|D}^V2UO@DHK4vS2ch+C0||O-%uq zB;TVERB11{^fmJSAYSI1Cc+wjSme>iNjIE|+@Uxj9@AbZK9XB&lIp+1`-(yD7{co` zGdL|lmg;n~uVe>=@tIzTBFFLb{!;S&(~2WS{{RHHiIE`0K@tq{x6{kaze$Gfl0IY# zmHk4ZGjy5n-zS-z5z6(O=$)uk?hVx?@?{MfZ9(iNm^5-g>qnw7jy#Hx z=<|Do!{bmss5OIal|`ly9HJoj%NX}_VLd>^D?_X!S>t)vK9mRq1#_Rq(%=PulMYGV-?be@?$()iW}%R@f>xz#AsNg-V~ zEYfAEUqdK)gIw}faM_z}{a~JDT4f2lnDHe8sS9u&b;3b$q<>)W%mv)x!qiL zpVj#2pEd;LgIXISS<~b9!D(kvc8_4Yw}s2z68_1ZX)Q0X;EfE(X^opFY4Rg|%VvWm z_d&(slD8CPZwpH92a>JdlYEm&j`uu5RrKdhp=vJ3#&7+N8S$4r?!od!Y^NWfIFU5~ ziCTLk6{?4CCWrT_*ls5ye&XM*U)v;&Tiq>L?1~0r#%NS;$g(9W4OvIYW|wjO-72@! z+ap=zUczVtEbwx|T* zPf2~t^l?M}FfqQsh$(D@fEEE8 zyA&2$q_BvU7Ksr_Atqvo8r zSp-hNgmT8&1wWcZd+ZbLtsBQnN=d9dr)sIDYCGOIJf{vaa|R8Q8>Hdk4h@%wWC@k~ zYrY}HDP=NN(uKhHMtWR>p?RgvISX~Um94#J4@`nCBkt6;u)(KUoRTHlMBUc`oY1j z|5{JPPX#8tP*8njxC>V}zA#p7KR8&d>ZAbF@t4p+MF?# zY#L5A$R_DtGAT#U8Z5^cd0jqClEE?A2#a=Z-l9yv8jOJv5!>VY{y=7o{1?w10P{|* z$)A)vVhtw4^vMJDpj9>V1vFFwKFxMDJ%55F;nXj_rZqpbfC!212 z4Gxx7y1RM^t)a&报警模式
-
-
+
+
{{ mode.name }}
@@ -40,9 +40,9 @@

警示灯爆闪

-
-
+
+
{{ mode.name }}
@@ -52,7 +52,7 @@
-
+

语音播报

@@ -81,19 +81,19 @@
- + 上传语音
- + 文字转语音
- + 所有语音
@@ -114,19 +114,24 @@
📍 - 经纬度 {{ deviceDetail && deviceDetail.longitude ? - Number(deviceDetail.longitude).toFixed(4) : '无' }} - {{ deviceDetail && deviceDetail.latitude ? Number(deviceDetail.latitude).toFixed(4) - : '无' }} +
经纬度 +
{{ deviceDetail && deviceDetail.longitude ? + Number(deviceDetail.longitude).toFixed(4) : '0.00' }} + {{ deviceDetail && deviceDetail.latitude ? + Number(deviceDetail.latitude).toFixed(4) + : '0.00' }}
+
+
-
-
地址 {{ deviceDetail.address || "未获取到地址" }}
+
+
地址
查看
+
{{ deviceDetail.address || "未获取到地址" }}
-
+

调节

@@ -135,7 +140,7 @@
%
- 保存
@@ -145,7 +150,7 @@
%
- 保存 + 保存
@@ -155,7 +160,7 @@
%
- 保存 + 保存
@@ -322,22 +327,16 @@ import { useRoute, useRouter } from 'vue-router'; import { useMqtt } from '@/utils/mqtt'; import api from '@/api/controlCenter/controlPanel/100J'; import { DeviceDetail, LightMode } from '@/api/controlCenter/controlPanel/types'; -import { getDeviceStatus } from '@/utils/function'; // 路由和实例 const route = useRoute(); const router = useRouter(); const { proxy } = getCurrentInstance() as ComponentInternalInstance; -import request from '@/utils/request'; // 导入图片资源 -import closeDefault from '@/assets/images/close.png'; -import closeActive from '@/assets/images/close_HL.png'; -import rb from '@/assets/images/rb.png'; -import rbAc from '@/assets/images/rbAc.png'; -import sg from '@/assets/images/sg.png'; -import sgAc from '@/assets/images/sgAc.png'; -import { object } from 'vue-types'; - +import rb from '@/assets/images/rg1.png'; +import rbAc from '@/assets/images/rg1Ac.png'; +import sg from '@/assets/images/hb.png'; +import sgAc from '@/assets/images/hbAc.png'; // 基础状态 const forceAlarmLoading = ref(false); const lightModesLoading = ref(false); @@ -345,26 +344,17 @@ const centerDialogVisible = ref(false); const currentVoiceId = ref(''); // 当前选中的语音ID const voiceList = ref([]); // 语音列表 -// MQTT相关 -const { - connected, - connect, - subscribe, - onConnect, - onError, - onMessage, - disconnect -} = useMqtt(); - // ====================== 录制语音 ====================== +import Recorder from 'recorder-core'; +// 引入 MP3 编码器(核心:生成标准 MP3) +import 'recorder-core/src/engine/mp3'; +import 'recorder-core/src/engine/mp3-engine'; const recordVoiceDialog = ref(false); const isRecording = ref(false); const recordDuration = ref(0); -const recordedBlob = ref(null); -const audioChunks = ref([]); -const mediaRecorder = ref(null); +const recorderIns = ref(null); // Recorder 实例 let recordTimer: NodeJS.Timeout | null = null; -let audioStream: MediaStream | null = null; +const recordedBlob = ref() const formatTime = (seconds: number) => { const min = String(Math.floor(seconds / 60)).padStart(2, '0'); @@ -372,80 +362,118 @@ const formatTime = (seconds: number) => { return `${min}:${sec}`; }; +// ------------- 开始录制语音上传 ------------- const startRecordVoice = async () => { try { - if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { - proxy?.$modal.msgError('当前浏览器不支持麦克风录制,请使用Chrome/Edge/Firefox'); - return; - } - audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }); - mediaRecorder.value = new MediaRecorder(audioStream); - audioChunks.value = []; - mediaRecorder.value.ondataavailable = (e) => audioChunks.value.push(e.data); - mediaRecorder.value.onstop = () => { - recordedBlob.value = new Blob(audioChunks.value, { type: 'audio/mp3' }); - if (audioStream) { - audioStream.getTracks().forEach(track => track.stop()); - audioStream = null; + // 1. 重置状态 + resetRecord(); + recorderIns.value = Recorder({ + type: 'mp3', // 直接录制为 MP3 + sampleRate: 44100, // 标准采样率 + bitRate: 128, // 128kbps(微信兼容) + onProcess: (buffers: any, power: number, bufferDuration: number, bufferSampleRate: number) => { } - }; - mediaRecorder.value.start(); - isRecording.value = true; - recordDuration.value = 0; - recordTimer = setInterval(() => recordDuration.value++, 1000); + }); + recorderIns.value.open(() => { + // 权限申请成功,开始录制 + recorderIns.value.start(); + isRecording.value = true; + recordDuration.value = 0; + recordTimer = setInterval(() => recordDuration.value++, 1000); + // proxy?.$modal.msgSuccess('开始录制语音...'); + }, (err: any) => { + // 权限申请失败 + proxy?.$modal.msgError(`麦克风权限申请失败`); + resetRecord(); + }); } catch (err) { - proxy?.$modal.msgError('麦克风权限申请失败'); - if (audioStream) { - audioStream.getTracks().forEach(track => track.stop()); - audioStream = null; - } + proxy?.$modal.msgError('录制异常,请重试'); + resetRecord(); } }; +// ------------- 停止录制 ------------- const stopRecordVoice = () => { - if (!mediaRecorder.value || !isRecording.value) return; - mediaRecorder.value.stop(); - isRecording.value = false; - if (recordTimer) { - clearInterval(recordTimer); - recordTimer = null; + if (!recorderIns.value || !isRecording.value) { + proxy?.$modal.msgWarning('未在录制状态'); + return; + } + + try { + // 停止录制并获取 MP3 Blob + recorderIns.value.stop((blob: Blob, duration: number) => { + // blob 是标准 MP3 格式(微信100%兼容) + recordedBlob.value = blob; + console.log('标准 MP3 生成成功:', blob); + + isRecording.value = false; + if (recordTimer) clearInterval(recordTimer); + proxy?.$modal.msgSuccess(`录制完成,时长:${Math.round(duration)}秒`); + + // 释放资源 + recorderIns.value.close(); + }, (err: any) => { + // proxy?.$modal.msgError(`停止录制失败:${err.msg}`); + resetRecord(); + }); + } catch (err) { + proxy?.$modal.msgError('停止录制异常'); + resetRecord(); } }; +// ------------- 重置录制状态 ------------- const resetRecord = () => { isRecording.value = false; recordDuration.value = 0; recordedBlob.value = null; - audioChunks.value = []; if (recordTimer) { clearInterval(recordTimer); recordTimer = null; } + // 释放 Recorder 资源 + if (recorderIns.value) { + recorderIns.value.close(); + recorderIns.value = null; + } }; - +// ------------- 保存/上传标准 MP3 文件 ------------- const saveRecordVoice = () => { - if (!recordedBlob.value) { - proxy?.$modal.msgWarning('暂无录制的语音文件'); + // 1. 校验 MP3 Blob 是否有效 + if (!recordedBlob.value || recordedBlob.value.size === 0) { + proxy?.$modal.msgWarning('暂无有效录制的 MP3 文件'); return; } - const formData = new FormData(); - formData.append('file', recordedBlob.value, `record_${new Date().getTime()}.mp3`); - formData.append('deviceId', route.params.deviceId as string); - proxy?.$modal.loading('保存中...'); - api.uploadAudioToOss(formData).then(res => { - proxy?.$modal.closeLoading(); - if (res.code === 200) { - proxy?.$modal.msgSuccess('保存成功'); - recordVoiceDialog.value = false; - queryAudioFileInfo(); - resetRecord(); - } else { - proxy?.$modal.msgError('保存语音失败:' + res.msg); - } - }).catch(err => { - proxy?.$modal.closeLoading(); - proxy?.$modal.msgError('保存语音失败'); - }); + // 2. 校验 deviceId + const deviceId = route?.params?.deviceId; + if (!deviceId || typeof deviceId !== 'string') { + return; + } + try { + // 3. 构建 FormData(上传标准 MP3) + const formData = new FormData(); + formData.append('deviceId', deviceId); + const fileName = `${new Date().getTime()}.mp3`; + formData.append('file', recordedBlob.value, fileName); + // 4. 上传接口 + proxy?.$modal.loading('保存中...'); + api.uploadAudioToOss(formData).then(res => { + proxy?.$modal.closeLoading(); + if (res.code === 200) { + proxy?.$modal.msgSuccess('保存成功'); + recordVoiceDialog.value = false; + queryAudioFileInfo(); + resetRecord(); + } else { + proxy?.$modal.msgError('保存语音失败'); + } + }).catch(err => { + proxy?.$modal.closeLoading(); + proxy?.$modal.msgError('保存语音失败'); + }); + } catch (err) { + + } }; const handleRecordVoice = () => { @@ -638,7 +666,7 @@ const deleteVoiceById = async (fileId: string) => { const renameVoice = async (item: any) => { const { value } = await ElMessageBox.prompt('请输入新名称', '重命名', { inputPlaceholder: '请输入新名称' }); if (value) { - const res = await api.videRenameAudioFile({ deviceId: route.params.deviceId, fileId:item.fileId, fileName: value }); + const res = await api.videRenameAudioFile({ deviceId: route.params.deviceId, fileId: item.fileId, fileName: value }); if (res.code === 200) { proxy?.$modal.msgSuccess(res.msg); queryAudioFileInfo(); @@ -884,7 +912,7 @@ const showClose = async () => { if (res.code === 200) { deviceDetail.value.voiceStrobeAlarm = 0; proxy?.$modal.msgSuccess(res.msg); - await getList(); + //await getList(); } } catch (error: any) { proxy?.$modal.msgError(error.msg); @@ -908,7 +936,7 @@ const forceAlarm = async () => { deviceDetail.value.voiceStrobeAlarm = 1; proxy?.$modal.msgSuccess(res.msg); - await getList(); + // await getList(); } } catch (error: any) { proxy?.$modal.msgError(error.msg); @@ -939,7 +967,7 @@ const playCurrentVoice = async () => { proxy?.$modal.msgError(res.msg); } } catch (err: any) { - proxy?.$modal.msgError(err.msg); + // proxy?.$modal.msgError(err.msg); } } // 2. 非报警中场景:单纯播放/暂停语音 @@ -958,7 +986,7 @@ const playCurrentVoice = async () => { proxy?.$modal.msgError(res.msg); } } catch (err: any) { - proxy?.$modal.msgError(err.msg); + // proxy?.$modal.msgError(err.msg); } } }; @@ -1018,18 +1046,27 @@ onUnmounted(() => { border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1); background: white; - padding: 20px; + padding: 1px 20px; border: 1px solid #ebeef5; - min-height: 730px; + min-height: 700px; } .content-card1 { border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1); background: white; - padding: 20px; + padding: 1px 20px; border: 1px solid #ebeef5; - min-height: 250px; + height: 440px; + } + + .content-card2 { + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1); + background: white; + padding: 1px 20px; + border: 1px solid #ebeef5; + height: 250px; } .content-card_gps { @@ -1056,11 +1093,12 @@ onUnmounted(() => { .lacatin_gps { height: 70px; - border-radius: 4px; - background: rgba(247, 248, 252, 1); + background: #F7F8FC; + border-radius: 4px 4px 4px 4px; width: 100%; padding: 10px; word-break: break-all; + margin-top: 15px; } .mode_2 { @@ -1069,8 +1107,17 @@ onUnmounted(() => { border-radius: 8px; width: 105px; text-align: center; + + } + .active { + border-radius: 4px 4px 4px 4px; + border: 1px solid #027CFB; + background: rgba(2, 124, 251, 0.06); + } + + .mode-card { display: flex; flex-direction: column; @@ -1142,7 +1189,7 @@ onUnmounted(() => { .save-btn { padding: 6px 20px; border-radius: 29px; - background: rgba(2, 124, 251, 1); + // background: rgba(2, 124, 251, 1); border: none; } @@ -1168,6 +1215,12 @@ onUnmounted(() => { } .location-info { + .location-item1 { + display: flex; + justify-content: space-between; + color: #38404F; + } + .location-item { display: flex; align-items: center; @@ -1235,7 +1288,7 @@ onUnmounted(() => { flex-direction: column; align-items: center; padding: 10px; - border: 1px solid #dcdfe6; + // border: 1px solid #dcdfe6; border-radius: 8px; cursor: pointer; transition: all 0.3s; @@ -1246,14 +1299,19 @@ onUnmounted(() => { } .el-icon { - font-size: 20px; + font-size: 24px; margin-bottom: 5px; - color: #409eff; + color: #027CFB; + border: 1px solid rgba(2, 124, 251, 0.2); + border-radius: 6px 6px 6px 6px; + width: 37px; + height: 37px; } span { - font-size: 12px; - color: #606266; + font-size: 14px; + color: #027CFB; + margin-top: 5px; } } }