From 78e93a3e3185bb2658f0371b2be9d34d9c7a06a0 Mon Sep 17 00:00:00 2001
From: "SWX\\10484" <1048449493@qq.com>
Date: Fri, 12 Jun 2026 14:52:37 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/layout/components/SystemSettingDialog.vue | 104 ++++++++++++++-
.../realTimeConsultation.vue | 118 +++++++++++++++---
2 files changed, 198 insertions(+), 24 deletions(-)
diff --git a/src/layout/components/SystemSettingDialog.vue b/src/layout/components/SystemSettingDialog.vue
index ebd6e43..76b2411 100644
--- a/src/layout/components/SystemSettingDialog.vue
+++ b/src/layout/components/SystemSettingDialog.vue
@@ -37,19 +37,21 @@
+
+
+
+
@@ -122,6 +124,7 @@ export default {
visible(newVal) {
if (newVal) {
this.activeTab = "basic";
+ this.refreshAuthStatus();
}
},
// 监听基本设置变化,自动保存到本地
@@ -166,6 +169,8 @@ export default {
return {
visible: false,
activeTab: "basic",
+ videoAuthorized: false,
+ cacheAuthorized: false,
basicForm: {
notification: true,
sendKey: "enter",
@@ -268,6 +273,84 @@ export default {
// 用户取消操作
});
},
+ // ==================== IndexedDB 目录句柄(与 realTimeConsultation 共享 HiUTalkStoreDB) ====================
+ _openIDB() {
+ return new Promise((resolve, reject) => {
+ const req = indexedDB.open('HiUTalkStoreDB', 1);
+ req.onupgradeneeded = () => { req.result.createObjectStore('handles'); };
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ },
+ async _getStoredDirHandle(key) {
+ const db = await this._openIDB();
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction('handles', 'readonly');
+ const req = tx.objectStore('handles').get(key);
+ req.onsuccess = () => { db.close(); resolve(req.result); };
+ req.onerror = () => { db.close(); reject(req.error); };
+ });
+ },
+ async _storeDirHandle(key, handle) {
+ const db = await this._openIDB();
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction('handles', 'readwrite');
+ const req = tx.objectStore('handles').put(handle, key);
+ req.onsuccess = () => { db.close(); resolve(); };
+ req.onerror = () => { db.close(); reject(req.error); };
+ });
+ },
+ // 刷新授权状态
+ async refreshAuthStatus() {
+ this.videoAuthorized = await this._checkAuthorized('screenshot_dir');
+ this.cacheAuthorized = await this._checkAuthorized('cache_dir');
+ },
+ async _checkAuthorized(key) {
+ const handle = await this._getStoredDirHandle(key);
+ if (!handle) return false;
+ const perm = await handle.queryPermission({ mode: 'readwrite' });
+ return perm === 'granted';
+ },
+ // 视讯存储:修改保存文件夹
+ async modifyVideoPath() {
+ await this._selectAndStorePath('screenshot_dir', 'videoPath', '视讯存储');
+ },
+ // 缓存存储:修改保存文件夹
+ async modifyCachePath() {
+ await this._selectAndStorePath('cache_dir', 'cachePath', '缓存存储');
+ },
+ // 通用:弹出目录选择器 → 创建 HiUTalkStore 子文件夹 → 存储句柄 → 填充路径到输入框
+ async _selectAndStorePath(handleKey, pathKey, label) {
+ if (!window.showDirectoryPicker) {
+ this.$message.error('当前浏览器不支持,请使用 Chrome 或 Edge');
+ return;
+ }
+ try {
+ let dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
+ // 在选中目录下创建/获取 HiUTalkStore 子文件夹
+ if (dirHandle.name !== 'HiUTalkStore') {
+ dirHandle = await dirHandle.getDirectoryHandle('HiUTalkStore', { create: true });
+ }
+ await this._storeDirHandle(handleKey, dirHandle);
+ if (pathKey === 'videoPath') this.videoAuthorized = true;
+ if (pathKey === 'cachePath') this.cacheAuthorized = true;
+ // 提取当前路径的盘符(如 D:),拼接选中的文件夹名填充到输入框
+ const drive = this._extractDrive(this.basicForm[pathKey]);
+ this.basicForm[pathKey] = `${drive}\\...\\${dirHandle.name}`;
+ this.$message.success(`${label}路径已更新`);
+ } catch (err) {
+ if (err.name !== 'AbortError') {
+ console.error(`${label}路径修改失败`, err);
+ this.$message.error('修改失败');
+ }
+ }
+ },
+ // 从路径中提取盘符,例如 "D:\RUS\HiUTalkStore" → "D:"
+ _extractDrive(path) {
+ if (!path) return 'D:';
+ const match = path.match(/^([A-Za-z]:)/);
+ return match ? match[1] : 'D:';
+ },
restoreDefault() {
this.$confirm("确定要恢复默认设置吗?", "提示", {
confirmButtonText: "确定",
@@ -276,6 +359,8 @@ export default {
}).then(() => {
const version = this.$store.getters.loginInfo?.upgrade_data?.version || "V01.01.16";
const defaultPath = `D:\\RUS_${version}\\HiUTalkStore`;
+ this.videoAuthorized = false;
+ this.cacheAuthorized = false;
this.basicForm = {
notification: true,
sendKey: "enter",
@@ -335,4 +420,15 @@ export default {
border-color: #ccc;
color: #666;
}
+.auth-tag {
+ display: inline-block;
+ margin-top: 4px;
+ font-size: 12px;
+ &.authorized {
+ color: #67c23a;
+ }
+ &.unauthorized {
+ color: #e6a23c;
+ }
+}
diff --git a/src/views/videoCommunication/realTimeConsultation.vue b/src/views/videoCommunication/realTimeConsultation.vue
index f1e26e5..ac58d90 100644
--- a/src/views/videoCommunication/realTimeConsultation.vue
+++ b/src/views/videoCommunication/realTimeConsultation.vue
@@ -458,7 +458,7 @@ export default {
window.hirtcwebsdk.init({
serviceID: '56da5fd8921f4f7093a42e2a',
serviceKey: '2c17c6393771ee3048ae34d6b965sdew',
- Services: { BasicRoomServiceToken: "https://192.168.69.174:3001/v1/auth/token" },
+ Services: { BasicRoomServiceToken: "https://wjw-ultrasoundappl/v1/auth/token" },
cameraLayers: [
{
width: 320,
@@ -902,18 +902,12 @@ export default {
mimeType: 'video/webm;codecs=vp8,opus',
});
this.recordedChunks = [];
+ const fileName = `${this.meetingTitle}录制_${new Date().toLocaleString().replace(/[/:\\s]/g, '_')}.webm`;
this.mediaRecorder.ondataavailable = e => e.data.size && this.recordedChunks.push(e.data);
- this.mediaRecorder.onstop = () => {
+ this.mediaRecorder.onstop = async () => {
const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `${this.meetingTitle}录制_${new Date().toLocaleString()}.webm`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- this.$message.success('录制完成');
+ // 优先保存到系统设置的视讯存储路径,降级下载
+ await this._saveBlobToVideoPath(blob, fileName, '录制');
};
this.mediaRecorder.start();
this.isRecording = true;
@@ -921,6 +915,7 @@ export default {
this.startRecordingTimer();
this.$message.warning('录制中');
} catch (e) {
+ console.error('录制失败', e);
this.$message.error('录制失败');
}
},
@@ -957,8 +952,44 @@ export default {
this.saveMeetingSnapshot();
},
- // 保存会议界面截图
+ // ==================== 截图保存辅助:IndexedDB 存储目录句柄 ====================
+ _openIDB() {
+ return new Promise((resolve, reject) => {
+ const req = indexedDB.open('HiUTalkStoreDB', 1);
+ req.onupgradeneeded = () => { req.result.createObjectStore('handles'); };
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ },
+ async _getStoredDirHandle() {
+ const db = await this._openIDB();
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction('handles', 'readonly');
+ const req = tx.objectStore('handles').get('screenshot_dir');
+ req.onsuccess = () => { db.close(); resolve(req.result); };
+ req.onerror = () => { db.close(); reject(req.error); };
+ });
+ },
+ // 获取已授权的目录句柄(页面刷新后自动重授权,不弹目录选择器)
+ async _getAuthorizedHandle() {
+ const dirHandle = await this._getStoredDirHandle();
+ if (!dirHandle) return null;
+ let perm = await dirHandle.queryPermission({ mode: 'readwrite' });
+ // 页面刷新后权限变为 'prompt',需重新请求(saveMeetingSnapshot 由用户点击触发,具备手势上下文)
+ if (perm !== 'granted') {
+ try {
+ perm = await dirHandle.requestPermission({ mode: 'readwrite' });
+ } catch (e) {
+ console.error('重新请求目录权限失败', e);
+ }
+ }
+ return perm === 'granted' ? dirHandle : null;
+ },
+
+ // 保存会议界面截图(保存到系统设置中的视讯存储路径,无弹窗)
async saveMeetingSnapshot() {
+ // 1. 截图生成 canvas 和 blob
+ let blob, fileName;
try {
const html2canvas = (await import('html2canvas')).default;
const container = document.querySelector('.meeting-container');
@@ -972,14 +1003,61 @@ export default {
logging: false,
allowTaint: false,
});
- const link = document.createElement('a');
- link.download = `会诊截图_${new Date().toLocaleString()}.png`;
- link.href = canvas.toDataURL('image/png');
- link.click();
- this.$message.success('截图已保存');
- } catch (err) {
- console.error('截图失败', err);
- this.$message.error('截图失败');
+ fileName = `会诊截图_${new Date().toLocaleString().replace(/[/:\\s]/g, '_')}.png`;
+ blob = await new Promise((resolve, reject) => {
+ canvas.toBlob(b => { if (b) resolve(b); else reject(new Error('toBlob 失败')); }, 'image/png');
+ });
+ } catch (e) {
+ console.error('截图生成失败', e);
+ this.$message.error('截图生成失败');
+ return;
+ }
+
+ // 2. 优先保存到系统设置的视讯存储路径,降级下载
+ await this._saveBlobToVideoPath(blob, fileName, '截图');
+ },
+
+ // 将 blob 保存到系统设置的视讯存储路径(优先写入授权目录,降级下载)
+ async _saveBlobToVideoPath(blob, fileName, label) {
+ const videoPath = this._getSystemVideoPath();
+
+ // 1. 尝试写入已授权的目录
+ const dirHandle = await this._getAuthorizedHandle();
+ if (dirHandle) {
+ try {
+ const fileHandle = await dirHandle.getFileHandle(fileName, { create: true });
+ const writable = await fileHandle.createWritable();
+ await writable.write(blob);
+ await writable.close();
+ this.$message.success(`${label}已保存到 ${videoPath || dirHandle.name}`);
+ return;
+ } catch (err) {
+ console.error(`${label}写入失败,降级为下载`, err);
+ }
+ }
+
+ // 2. 降级方案:浏览器下载
+ try {
+ const { saveAs } = await import('file-saver');
+ saveAs(blob, fileName);
+ this.$message.success(videoPath
+ ? `${label}已下载(请在系统设置中授权"${videoPath}"以自动保存)`
+ : `${label}已保存`);
+ } catch (e) {
+ console.error(`${label}保存失败`, e);
+ this.$message.error(`${label}保存失败`);
+ }
+ },
+
+ // 从系统设置中读取视讯存储路径
+ _getSystemVideoPath() {
+ try {
+ const raw = localStorage.getItem('systemSettings');
+ if (!raw) return '';
+ const settings = JSON.parse(raw);
+ return settings.basicForm?.videoPath || '';
+ } catch (e) {
+ return '';
}
},