|
|
@@ -10,7 +10,10 @@
|
|
|
<div class="kwlHeaderRight" @click="showHowTo = !showHowTo">
|
|
|
<span>{{ lang.ssKWLHowTo }}</span>
|
|
|
<i :class="['kwlArrow', { 'kwlArrowUp': showHowTo }]">
|
|
|
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4 transition-transform "><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
+ class="w-4 h-4 transition-transform ">
|
|
|
+ <polyline points="6 9 12 15 18 9"></polyline>
|
|
|
+ </svg>
|
|
|
</i>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -50,20 +53,14 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 视频全屏播放弹窗 -->
|
|
|
- <el-dialog
|
|
|
- :visible.sync="videoDialogVisible"
|
|
|
- :append-to-body="true"
|
|
|
- :show-close="true"
|
|
|
- custom-class="kwlVideoDialog"
|
|
|
- width="80%"
|
|
|
- @closed="onVideoDialogClosed"
|
|
|
- >
|
|
|
+ <el-dialog :visible.sync="videoDialogVisible" :append-to-body="true" :show-close="true"
|
|
|
+ custom-class="kwlVideoDialog" width="80%" @closed="onVideoDialogClosed">
|
|
|
<div class="kwlVideoDialogBody" v-if="currentVideoUrl">
|
|
|
<video :src="currentVideoUrl" controls autoplay class="kwlVideoFull"></video>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- KWL 三列
|
|
|
+ <!-- KWL 三列
|
|
|
@keyup.enter="addItem('know')"
|
|
|
-->
|
|
|
<div class="kwlColumns">
|
|
|
@@ -80,11 +77,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="kwlAddRow">
|
|
|
- <input
|
|
|
- v-model="newKnow"
|
|
|
- class="kwlInput"
|
|
|
- :placeholder="work.kplaceholder ? work.kplaceholder : lang.ssKWLAddPlaceholder"
|
|
|
- />
|
|
|
+ <input v-model="newKnow" class="kwlInput"
|
|
|
+ :placeholder="work.kplaceholder ? work.kplaceholder : lang.ssKWLAddPlaceholder" />
|
|
|
<button class="kwlAddBtn kwlAddBtnKnow" @click="addItem('know')">+</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -104,11 +98,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="kwlAddRow">
|
|
|
- <input
|
|
|
- v-model="newWant"
|
|
|
- class="kwlInput"
|
|
|
- :placeholder="work.wplaceholder ? work.wplaceholder : lang.ssKWLAddPlaceholder"
|
|
|
- />
|
|
|
+ <input v-model="newWant" class="kwlInput"
|
|
|
+ :placeholder="work.wplaceholder ? work.wplaceholder : lang.ssKWLAddPlaceholder" />
|
|
|
<button class="kwlAddBtn kwlAddBtnWant" @click="addItem('want')">+</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -128,11 +119,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="kwlAddRow">
|
|
|
- <input
|
|
|
- v-model="newLearned"
|
|
|
- class="kwlInput"
|
|
|
- :placeholder="work.lplaceholder ? work.lplaceholder : lang.ssKWLAddPlaceholder"
|
|
|
- />
|
|
|
+ <input v-model="newLearned" class="kwlInput"
|
|
|
+ :placeholder="work.lplaceholder ? work.lplaceholder : lang.ssKWLAddPlaceholder" />
|
|
|
<button class="kwlAddBtn kwlAddBtnLearn" @click="addItem('learned')">+</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -142,18 +130,11 @@
|
|
|
<div class="kwlCard kwlReflection" v-if="isShowReflection">
|
|
|
<div class="kwlRefTitle">{{ lang.ssKWLReflection }}</div>
|
|
|
<div class="kwlRefAreaWrap">
|
|
|
- <textarea
|
|
|
- v-model="work.reflection"
|
|
|
- class="kwlRefTextarea"
|
|
|
- :placeholder="lang.ssKWLReflectionPH"
|
|
|
- ></textarea>
|
|
|
+ <textarea v-model="work.reflection" class="kwlRefTextarea" :placeholder="lang.ssKWLReflectionPH"></textarea>
|
|
|
<!-- 右下角麦克风按钮:点击开始/停止录音 -->
|
|
|
- <div
|
|
|
- class="kwlMicBtn"
|
|
|
- :class="{ kwlMicBtnActive: recordObj.status == '1' }"
|
|
|
+ <div class="kwlMicBtn" :class="{ kwlMicBtnActive: recordObj.status == '1' }"
|
|
|
:title="recordObj.status == '1' ? lang.ssKWLStopRec : lang.ssKWLVoiceInput"
|
|
|
- @click="recordObj.status == '1' ? stopRecord() : startRecord()"
|
|
|
- >
|
|
|
+ @click="recordObj.status == '1' ? stopRecord() : startRecord()">
|
|
|
<!-- 录音中:声波动画 -->
|
|
|
<svg v-if="recordObj.status == '1'" width="18" height="18" viewBox="0 0 120 20">
|
|
|
<rect x="5" y="5" width="8" :height="10" rx="3" fill="currentColor">
|
|
|
@@ -214,9 +195,9 @@ export default {
|
|
|
taskDescription: "",
|
|
|
taskUrl: [],
|
|
|
showHowTo: false,
|
|
|
- kplaceholder:"",
|
|
|
- wplaceholder:"",
|
|
|
- lplaceholder:"",
|
|
|
+ kplaceholder: "",
|
|
|
+ wplaceholder: "",
|
|
|
+ lplaceholder: "",
|
|
|
isShowReflection: true
|
|
|
},
|
|
|
newKnow: "",
|
|
|
@@ -272,9 +253,9 @@ export default {
|
|
|
taskDescription: "",
|
|
|
taskUrl: [],
|
|
|
showHowTo: false,
|
|
|
- kplaceholder:"",
|
|
|
- wplaceholder:"",
|
|
|
- lplaceholder:"",
|
|
|
+ kplaceholder: "",
|
|
|
+ wplaceholder: "",
|
|
|
+ lplaceholder: "",
|
|
|
isShowReflection: true
|
|
|
}, _data);
|
|
|
|
|
|
@@ -287,6 +268,11 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
+ // 清理 iframe 录音会话(统一清理入口)
|
|
|
+ _cleanupTranscriber(iframe) {
|
|
|
+ iframe.contentWindow.onSessionStopped = null;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ },
|
|
|
changeWorkData(newValue) {
|
|
|
const str = JSON.stringify(newValue);
|
|
|
this.$emit("changeWorkData", str);
|
|
|
@@ -328,16 +314,18 @@ export default {
|
|
|
this.$message.error(this.lang.ssKWLRecServiceNotReady);
|
|
|
return;
|
|
|
}
|
|
|
- try {
|
|
|
- if(this.lang.lang == 'com'){
|
|
|
+
|
|
|
+ const startTranscriber = () => {
|
|
|
+ // try {
|
|
|
+ if (this.lang.lang == 'com') {
|
|
|
iframe.contentWindow.window.document.getElementById(
|
|
|
"languageOptions"
|
|
|
).selectedIndex = 8;
|
|
|
- }else if(this.lang.lang == 'hk'){
|
|
|
+ } else if (this.lang.lang == 'hk') {
|
|
|
iframe.contentWindow.window.document.getElementById(
|
|
|
"languageOptions"
|
|
|
).selectedIndex = 4;
|
|
|
- }else{
|
|
|
+ } else {
|
|
|
iframe.contentWindow.window.document.getElementById(
|
|
|
"languageOptions"
|
|
|
).selectedIndex = 2;
|
|
|
@@ -358,23 +346,44 @@ export default {
|
|
|
iframe.contentWindow.ConversationTranscriber();
|
|
|
this.recordObj.status = "1";
|
|
|
this.recordObj.startTime = new Date().getTime();
|
|
|
- setTimeout(() => {
|
|
|
- navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
|
|
|
- if (permissionStatus.state !== "granted") {
|
|
|
- this.recordObj.status = "0";
|
|
|
- this.recordObj.loading = false;
|
|
|
- iframe.contentWindow.onSessionStopped = null;
|
|
|
- iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
- this.$message.warning(this.lang.ssKWLNoPermStop);
|
|
|
- return;
|
|
|
- }
|
|
|
- }).catch(() => {});
|
|
|
- }, 2000);
|
|
|
- } catch (err) {
|
|
|
- console.error("startRecord error:", err);
|
|
|
- this.$message.error(this.lang.ssKWLRecStartFail);
|
|
|
- this.recordObj.status = "0";
|
|
|
+
|
|
|
+ }
|
|
|
+ // 先通过 getUserMedia 主动请求麦克风权限,避免 ConversationTranscriber
|
|
|
+ // 在浏览器权限弹窗期间被中断导致内部状态异常
|
|
|
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
|
+ navigator.mediaDevices.getUserMedia({ audio: true })
|
|
|
+ .then(stream => {
|
|
|
+ // 权限已获取,立即释放 stream,让 ConversationTranscriber 自己接管麦克风
|
|
|
+ stream.getTracks().forEach(t => t.stop());
|
|
|
+ startTranscriber();
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户拒绝权限
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
+ this.$message.success(this.lang.ssNoPermStop);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 降级:不支持 getUserMedia 的浏览器直接启动
|
|
|
+ startTranscriber();
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
|
|
|
+ if (permissionStatus.state !== "granted") {
|
|
|
+ this.recordObj.status = "0";
|
|
|
+ this.recordObj.loading = false;
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
+ this.$message.warning(this.lang.ssKWLNoPermStop);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }).catch(() => { });
|
|
|
+ }, 2000);
|
|
|
+ // } catch (err) {
|
|
|
+ // console.error("startRecord error:", err);
|
|
|
+ // this.$message.error(this.lang.ssKWLRecStartFail);
|
|
|
+ // this.recordObj.status = "0";
|
|
|
+ // }
|
|
|
},
|
|
|
// 停止录音
|
|
|
stopRecord() {
|
|
|
@@ -382,20 +391,19 @@ export default {
|
|
|
this.$message.info(this.lang.ssKWLRecordShort);
|
|
|
return;
|
|
|
}
|
|
|
+ const iframe = this.$refs["iframeRef"];
|
|
|
navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
|
|
|
if (permissionStatus.state !== "granted") {
|
|
|
this.recordObj.status = "0";
|
|
|
this.recordObj.loading = false;
|
|
|
- let iframe = this.$refs["iframeRef"];
|
|
|
if (iframe && iframe.contentWindow) {
|
|
|
- iframe.contentWindow.onSessionStopped = null;
|
|
|
- iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
}
|
|
|
this.$message.warning(this.lang.ssKWLNoMicPerm);
|
|
|
return;
|
|
|
}
|
|
|
if (this.recordObj.status == '1' && !this.recordObj.loading) {
|
|
|
- let iframe = this.$refs["iframeRef"];
|
|
|
+ // let iframe = this.$refs["iframeRef"];
|
|
|
if (!iframe || !iframe.contentWindow) {
|
|
|
this.recordObj.status = "0";
|
|
|
this.recordObj.loading = false;
|
|
|
@@ -416,16 +424,14 @@ export default {
|
|
|
this.recordObj.status = "0";
|
|
|
this.recordObj.loading = false;
|
|
|
this.$message.success(this.lang.ssKWLRecStopped);
|
|
|
- iframe.contentWindow.onSessionStopped = null;
|
|
|
- iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
};
|
|
|
} else {
|
|
|
this.recordObj.status = "0";
|
|
|
this.recordObj.loading = false;
|
|
|
let iframe = this.$refs["iframeRef"];
|
|
|
if (iframe && iframe.contentWindow) {
|
|
|
- iframe.contentWindow.onSessionStopped = null;
|
|
|
- iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
}
|
|
|
}
|
|
|
}).catch(() => {
|
|
|
@@ -433,15 +439,14 @@ export default {
|
|
|
this.recordObj.loading = false;
|
|
|
let iframe = this.$refs["iframeRef"];
|
|
|
if (iframe && iframe.contentWindow) {
|
|
|
- iframe.contentWindow.onSessionStopped = null;
|
|
|
- iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this._cleanupTranscriber(iframe);
|
|
|
}
|
|
|
this.$message.success(this.lang.ssKWLRecStopped);
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
- mounted() {},
|
|
|
- beforeUnmount() {}
|
|
|
+ mounted() { },
|
|
|
+ beforeUnmount() { }
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
@@ -658,17 +663,17 @@ export default {
|
|
|
}
|
|
|
|
|
|
/* 视频全屏弹窗 */
|
|
|
-.kwlVideoDialog >>> .el-dialog {
|
|
|
+.kwlVideoDialog>>>.el-dialog {
|
|
|
background: transparent;
|
|
|
box-shadow: none;
|
|
|
}
|
|
|
|
|
|
-.kwlVideoDialog >>> .el-dialog__header {
|
|
|
+.kwlVideoDialog>>>.el-dialog__header {
|
|
|
padding: 0;
|
|
|
margin: 0;
|
|
|
}
|
|
|
|
|
|
-.kwlVideoDialog >>> .el-dialog__headerbtn {
|
|
|
+.kwlVideoDialog>>>.el-dialog__headerbtn {
|
|
|
top: -2.5rem;
|
|
|
right: -2.5rem;
|
|
|
width: 2.5rem;
|
|
|
@@ -678,17 +683,17 @@ export default {
|
|
|
z-index: 10;
|
|
|
}
|
|
|
|
|
|
-.kwlVideoDialog >>> .el-dialog__close {
|
|
|
+.kwlVideoDialog>>>.el-dialog__close {
|
|
|
color: #fff;
|
|
|
font-size: 1.5rem;
|
|
|
}
|
|
|
|
|
|
-.kwlVideoDialog >>> .el-dialog__body {
|
|
|
+.kwlVideoDialog>>>.el-dialog__body {
|
|
|
padding: 0;
|
|
|
background: transparent;
|
|
|
}
|
|
|
|
|
|
-.kwlVideoDialog >>> .el-dialog__headerbtn:hover .el-dialog__close {
|
|
|
+.kwlVideoDialog>>>.el-dialog__headerbtn:hover .el-dialog__close {
|
|
|
color: #fff;
|
|
|
}
|
|
|
|