SanHQin 1 日 前
コミット
a301925865

+ 1 - 1
dist/index.html

@@ -37,7 +37,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.9da04959e695ba26355b9e8d46d67dc2.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.2c492f3d5a39df08f4a3.js></script><script type=text/javascript src=./static/js/app.8b652defbb7f2b1f812c.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.dec1ff17944e764bbd96dbb66cfb544f.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.2c492f3d5a39df08f4a3.js></script><script type=text/javascript src=./static/js/app.28a61ec657138cd713da.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/app.dec1ff17944e764bbd96dbb66cfb544f.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/app.dec1ff17944e764bbd96dbb66cfb544f.css.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/workPage.092a2b6b85fcea9b6c61502e86fb2750.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/workPage.092a2b6b85fcea9b6c61502e86fb2750.css.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/app.28a61ec657138cd713da.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/app.28a61ec657138cd713da.js.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/manifest.23ea04dc469b57e2b4f8.js.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/workPage-manifest.2ece51fa34be51c8610a.js.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/workPage.d41c86de27c1cca942a0.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/workPage.d41c86de27c1cca942a0.js.map


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/workPage.e04327f29aab77cb1437.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/workPage.e04327f29aab77cb1437.js.map


+ 1 - 1
dist/workPage.html

@@ -1,4 +1,4 @@
-<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.8f80ba644312281cacfc720d32708c43.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.7b6275e4cf468b42a92b.js></script><script type=text/javascript src=./static/js/workPage.e04327f29aab77cb1437.js></script></body></html><script>function stopSafari() {
+<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.092a2b6b85fcea9b6c61502e86fb2750.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.7b6275e4cf468b42a92b.js></script><script type=text/javascript src=./static/js/workPage.d41c86de27c1cca942a0.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

+ 78 - 27
src/components/pages/workPage/components/sixHatPage.vue

@@ -156,7 +156,8 @@
               <button v-for="(opt, idx) in timerPresets" :key="idx" class="shTimerOpt"
                 :class="{ shTimerOptActive: work.timerDuration === opt.value }"
                 @click="work.timerDuration = opt.value">{{ opt.label }}</button>
-              <button class="shTimerOpt" :class="{ shTimerOptActive: timerCustomFocused }" @click="focusTimerCustom">{{ lang.ssSHTimerCustom }}</button>
+              <button class="shTimerOpt" :class="{ shTimerOptActive: timerCustomFocused }" @click="focusTimerCustom">{{
+                lang.ssSHTimerCustom }}</button>
               <input v-if="timerCustomFocused" type="number" min="1" class="shTimerCustomInput"
                 v-model.number="work.timerCustomValue" @blur="onTimerCustomBlur" @keyup.enter="onTimerCustomBlur" />
             </div>
@@ -206,7 +207,13 @@
                   d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
                 </path>
               </svg>
-              <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
+              <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="28" height="28"
+                viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"
+                stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true">
+                <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path>
+                <path d="M12 9v4"></path>
+                <path d="M12 17h.01"></path>
+              </svg>
               <svg v-else-if="hat.key === 'yellow'" viewBox="0 0 24 24" width="28" height="28" fill="none"
                 stroke="#ca8a04" stroke-width="2">
                 <polygon
@@ -282,8 +289,11 @@
       <!-- 任务说明:有讨论主题时显示 -->
       <div class="shTaskDescCard" v-if="work.stage === 'activity' && work.scenario && work.scenario.trim()">
         <div class="shTaskDescIcon">
-          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="#F5A623" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-            <path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"></path>
+          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="#F5A623" stroke-width="2"
+            stroke-linecap="round" stroke-linejoin="round">
+            <path
+              d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z">
+            </path>
           </svg>
         </div>
         <div class="shTaskDescBody">
@@ -320,7 +330,13 @@
                 d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
               </path>
             </svg>
-            <svg v-else-if="currentHat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
+            <svg v-else-if="currentHat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="44" height="44"
+              viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"
+              stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true">
+              <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path>
+              <path d="M12 9v4"></path>
+              <path d="M12 17h.01"></path>
+            </svg>
             <svg v-else-if="currentHat.key === 'yellow'" viewBox="0 0 24 24" width="44" height="44" fill="none"
               stroke="#ca8a04" stroke-width="2">
               <polygon
@@ -345,7 +361,7 @@
           <div class="shActivityHatDesc">{{ currentHat.desc }}</div>
           <div class="shActivityHatProgress">{{ lang.ssSHHatProgress.replace('{n}', currentHatIdx +
             1).replace('{total}',
-            selectedHats.length) }}</div>
+              selectedHats.length) }}</div>
           <!-- 引导问题 -->
           <div class="shActivityHatPrompt">{{ currentHatPrompt }}</div>
         </div>
@@ -419,7 +435,7 @@
         <button class="shSecondaryBtn" :class="{ shSecondaryBtnDisabled: currentHatIdx === 0 }"
           :disabled="currentHatIdx === 0" @click="prevHat">{{ lang.ssSHPrevHat }}</button>
         <button class="shPrimaryBtn" @click="nextOrFinish">{{ isLastHat ? lang.ssSHFinish : lang.ssSHNextHat
-          }}</button>
+        }}</button>
       </div>
     </div>
 
@@ -446,7 +462,13 @@
                 d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
               </path>
             </svg>
-            <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
+            <svg v-else-if="hat.key === 'black'" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
+              viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"
+              stroke-linejoin="round" class="lucide lucide-triangle-alert w-4 h-4 text-white" aria-hidden="true">
+              <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path>
+              <path d="M12 9v4"></path>
+              <path d="M12 17h.01"></path>
+            </svg>
             <svg v-else-if="hat.key === 'yellow'" viewBox="0 0 24 24" width="22" height="22" fill="none"
               stroke="#ca8a04" stroke-width="2">
               <polygon
@@ -776,6 +798,11 @@ export default {
     },
   },
   methods: {
+    // 清理 iframe 录音会话(统一清理入口)
+    _cleanupTranscriber(iframe) {
+      iframe.contentWindow.onSessionStopped = null;
+      iframe.contentWindow.window.onRecognizedResult = null;
+    },
     // —— 加载班级学生(接口无数据时保持 mock)——
     async loadClassStudents() {
       try {
@@ -960,7 +987,8 @@ export default {
         this.$message.error(this.lang.ssSHRecServiceNotReady);
         return;
       }
-      try {
+      // try {
+      const startTranscriber = () => {
         if (this.lang.lang === 'com') iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 8;
         else if (this.lang.lang === 'hk') iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 4;
         else iframe.contentWindow.window.document.getElementById('languageOptions').selectedIndex = 2;
@@ -977,23 +1005,45 @@ export default {
         iframe.contentWindow.ConversationTranscriber();
         this.recordObj.status = '1';
         this.recordObj.startTime = new Date().getTime();
-        setTimeout(() => {
-          if (!navigator.permissions) return;
-          navigator.permissions.query({ name: 'microphone' }).then(ps => {
-            if (ps.state !== 'granted') {
-              this.recordObj.status = '0';
-              this.recordObj.loading = false;
-              iframe.contentWindow.onSessionStopped = null;
-              iframe.contentWindow.window.onRecognizedResult = null;
-              this.$message.warning(this.lang.ssSHNoPermStop);
-            }
-          }).catch(() => { });
-        }, 2000);
-      } catch (err) {
-        console.error('startRecord error:', err);
-        this.$message.error(this.lang.ssSHRecStartFail);
-        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(() => {
+        if (!navigator.permissions) return;
+        navigator.permissions.query({ name: 'microphone' }).then(ps => {
+          if (ps.state !== 'granted') {
+            this.recordObj.status = '0';
+            this.recordObj.loading = false;
+            this._cleanupTranscriber(iframe);
+            // iframe.contentWindow.onSessionStopped = null;
+            // iframe.contentWindow.window.onRecognizedResult = null;
+            this.$message.warning(this.lang.ssSHNoPermStop);
+          }
+        }).catch(() => { });
+      }, 2000);
+      // } catch (err) {
+      //   console.error('startRecord error:', err);
+      //   this.$message.error(this.lang.ssSHRecStartFail);
+      //   this.recordObj.status = '0';
+      // }
     },
     stopRecord() {
       if (new Date().getTime() - this.recordObj.startTime < 2000) {
@@ -1019,8 +1069,9 @@ export default {
         this.recordObj.status = '0';
         this.recordObj.loading = false;
         this.$message.success(this.lang.ssSHRecStopped);
-        iframe.contentWindow.onSessionStopped = null;
-        iframe.contentWindow.window.onRecognizedResult = null;
+        this._cleanupTranscriber(iframe);
+        // iframe.contentWindow.onSessionStopped = null;
+        // iframe.contentWindow.window.onRecognizedResult = null;
       };
     },
   },

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません