index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. <template>
  2. <div class="ch_box" ref="ch_box">
  3. <div class="ch_content_box" v-show="type == 1">
  4. <searchArea
  5. :courseDetail="courseDetail"
  6. ref="searchAreaRef"
  7. :navList="navList"
  8. :tcid="tcid"
  9. :openMegaphone="openMegaphone"
  10. :fileId="fileId"
  11. :recordType="recordType"
  12. v-if="itemType == 1"
  13. />
  14. <taskArea
  15. :courseDetail="courseDetail"
  16. :navList="navList"
  17. :courseType="courseType"
  18. :taskCount="taskCount"
  19. :worksStudent="worksStudent"
  20. :openMegaphone="openMegaphone"
  21. ref="taskAreaRef"
  22. :fileId="fileId"
  23. v-if="itemType == 2"
  24. />
  25. <countdown
  26. ref="countdownRef"
  27. :fileId="fileId"
  28. :courseDetail="courseDetail"
  29. v-show="itemType == 3"
  30. />
  31. <languageAssistant ref="languageAssistantRef" v-if="itemType == 4" />
  32. <!-- <dialogArea
  33. :courseDetail="courseDetail"
  34. :openMegaphone="openMegaphone"
  35. ref="dialogAreaRef"
  36. :fileId="fileId"
  37. v-if="itemType == 3"
  38. /> -->
  39. </div>
  40. <div class="ch_nav_box">
  41. <div class="ch_nav_box_top">
  42. <div @click="changeFold(!fold)" ref="foldBtnRef">
  43. <el-tooltip
  44. class="item"
  45. effect="dark"
  46. :content="fold ? '折叠' : '展开'"
  47. placement="top"
  48. >
  49. <img
  50. :src="require('../../assets/icon/course/foldIcon.svg')"
  51. alt=""
  52. :style="`${fold ? 'transform: rotate(90deg);' : ''}`"
  53. />
  54. </el-tooltip>
  55. </div>
  56. <div @click="$emit('review')" v-if="tType == 1">
  57. <el-tooltip class="item" effect="dark" content="评论" placement="top">
  58. <img
  59. :src="require('../../assets/icon/course/comment2.svg')"
  60. alt=""
  61. style="width: 22px;height: 22px;"
  62. />
  63. </el-tooltip>
  64. </div>
  65. <div
  66. @click="startRecording()"
  67. v-if="!videoStart && (tType == 1 || tType == 4)"
  68. >
  69. <el-tooltip class="item" effect="dark" content="录制" placement="top">
  70. <img
  71. :src="require('../../assets/icon/course/record3.svg')"
  72. alt=""
  73. style="width: 22px;height: 22px;"
  74. />
  75. </el-tooltip>
  76. </div>
  77. <div
  78. @click="$emit('stopRecording')"
  79. v-else-if="tType == 1 || tType == 4"
  80. style="background:#f63564"
  81. >
  82. <el-tooltip class="item" effect="dark" content="下载" placement="top">
  83. <img
  84. :src="require('../../assets/icon/course/record4.svg')"
  85. alt=""
  86. style="width: 22px;height: 22px;"
  87. />
  88. </el-tooltip>
  89. </div>
  90. </div>
  91. <div class="ch_nav_box_middle">
  92. <div
  93. :class="[
  94. 'ch_nav_box_middle_item',
  95. itemType == 2 ? 'ch_nav_box_middle_item_active' : ''
  96. ]"
  97. @click.stop="changeItemType(2)"
  98. >
  99. <img
  100. v-if="itemType == 2"
  101. :src="require('../../assets/icon/course/task_active.png')"
  102. />
  103. <img
  104. v-if="itemType != 2"
  105. :src="require('../../assets/icon/course/task.png')"
  106. />
  107. <!-- <span :style="`background:url(${itemType==2?require('../../assets/icon/course/task_active.png'):require('../../assets/icon/course/task.png')});`"></span> -->
  108. <div>任务</div>
  109. </div>
  110. <div
  111. :class="[
  112. 'ch_nav_box_middle_item',
  113. itemType == 1 ? 'ch_nav_box_middle_item_active' : ''
  114. ]"
  115. @click.stop="changeItemType(1)"
  116. >
  117. <img
  118. v-if="itemType == 1"
  119. :src="require('../../assets/icon/course/up_active.png')"
  120. />
  121. <img
  122. v-if="itemType != 1"
  123. :src="require('../../assets/icon/course/up.png')"
  124. />
  125. <!-- <span :style="`background:url(${itemType==1?require('../../assets/icon/course/up_active.png'):require('../../assets/icon/course/up.png')});`"></span> -->
  126. <div>对话</div>
  127. </div>
  128. <div
  129. :class="[
  130. 'ch_nav_box_middle_item',
  131. itemType == 3 ? 'ch_nav_box_middle_item_active' : ''
  132. ]"
  133. @click.stop="changeItemType(3)"
  134. >
  135. <img
  136. v-if="itemType == 3"
  137. :src="require('../../assets/icon/course/Countdown2.svg')"
  138. />
  139. <img
  140. v-if="itemType != 3"
  141. :src="require('../../assets/icon/course/Countdown.svg')"
  142. />
  143. <div>倒计时</div>
  144. </div>
  145. </div>
  146. <div class="ch_nav_box_bottom">
  147. <div @click.stop="commentAndAnnotate()">
  148. <el-tooltip class="item" effect="dark" content="批注" placement="top">
  149. <img
  150. :src="require('../../assets/icon/course/edit2.svg')"
  151. v-if="!AnnotationCanvasShow"
  152. />
  153. <img :src="require('../../assets/icon/course/edit3.svg')" v-else />
  154. </el-tooltip>
  155. </div>
  156. <!-- <div @click.stop="changeItemType(4)" :class="[itemType == 4?'ch_nav_box_middle_item_active':'']">
  157. <el-tooltip
  158. class="item"
  159. effect="dark"
  160. :content="itemType != 4?'开启语音助手':'关闭语音助手'"
  161. placement="top"
  162. >
  163. <img v-if="itemType != 4" :src="require('../../assets/icon/course/robot.svg')" />
  164. <img v-else :src="require('../../assets/icon/course/robot2.svg')" />
  165. </el-tooltip>
  166. </div> -->
  167. <div
  168. @click.stop="startAssistant()"
  169. :class="[recordType == 1 ? 'ch_nav_box_middle_item_active' : '']"
  170. >
  171. <el-tooltip
  172. class="item"
  173. effect="dark"
  174. :content="recordType == 0 ? '开启语音助手' : '关闭语音助手'"
  175. placement="top"
  176. >
  177. <img
  178. v-if="recordType != 1"
  179. :src="require('../../assets/icon/course/robot3.svg')"
  180. />
  181. <img v-else :src="require('../../assets/icon/course/robot3.svg')" />
  182. </el-tooltip>
  183. </div>
  184. <div @click.stop="$emit('goStep', 0)">
  185. <el-tooltip
  186. class="item"
  187. effect="dark"
  188. content="上一步"
  189. placement="top"
  190. >
  191. <img :src="require('../../assets/icon/course/last.png')" />
  192. </el-tooltip>
  193. </div>
  194. <div @click.stop="$emit('goStep', 1)">
  195. <el-tooltip
  196. class="item"
  197. effect="dark"
  198. content="下一步"
  199. placement="top"
  200. >
  201. <img :src="require('../../assets/icon/course/next.png')" />
  202. </el-tooltip>
  203. </div>
  204. <div @click="openSetting">
  205. <el-tooltip
  206. class="item"
  207. effect="dark"
  208. :content="type == 0 ? '展开' : '折叠'"
  209. placement="top"
  210. >
  211. <img :src="require('../../assets/icon/course/menu.png')" />
  212. </el-tooltip>
  213. </div>
  214. </div>
  215. </div>
  216. <div v-show="fold" class="itemFold" ref="itemFoldRef">
  217. <div @click="$emit('backPage')">
  218. <img :src="require('../../assets/icon/course/return.png')" alt="" />
  219. <span>返回</span>
  220. </div>
  221. <div @click="$emit('refresh')">
  222. <img :src="require('../../assets/icon/course/refresh.png')" alt="" />
  223. <span>刷新</span>
  224. </div>
  225. <div @click="$emit('authority')" v-if="tType == 1 || tType == 4">
  226. <img :src="require('../../assets/icon/course/setting.png')" alt="" />
  227. <span>权限</span>
  228. </div>
  229. </div>
  230. <levitatedSphere ref="levitatedSphereRef" @startTime="startTime" />
  231. <timepiece ref="timepieceRef" />
  232. <AnnotationCanvas
  233. ref="AnnotationCanvasRef"
  234. @close="endCommentAndAnnotate"
  235. @changeStatus="changeAnnotationCanvasShow"
  236. />
  237. </div>
  238. </template>
  239. <script>
  240. import searchArea from "./component/searchArea.vue";
  241. import taskArea from "./component/taskArea.vue";
  242. // import dialogArea from "./component/dialogArea.vue";
  243. import levitatedSphere from "./component/levitatedSphere.vue";
  244. import timepiece from "./component/timepiece.vue";
  245. import countdown from "./component/countdown.vue";
  246. import AnnotationCanvas from "./component/AnnotationCanvas.vue";
  247. import languageAssistant from "./component/languageAssistant.vue";
  248. export default {
  249. emits: [
  250. "refresh",
  251. "goStep",
  252. "backPage",
  253. "authority",
  254. "review",
  255. "stopRecording",
  256. "startRecording"
  257. ],
  258. components: {
  259. searchArea,
  260. taskArea,
  261. // dialogArea,
  262. levitatedSphere,
  263. timepiece,
  264. countdown,
  265. AnnotationCanvas,
  266. languageAssistant
  267. },
  268. props: {
  269. courseDetail: {
  270. type: Object,
  271. default: () => {}
  272. },
  273. tType: {
  274. type: Number,
  275. default: 0
  276. },
  277. navList: {
  278. type: Array,
  279. default: () => []
  280. },
  281. tcid: {
  282. type: String,
  283. default: ""
  284. },
  285. courseType: {
  286. type: Number,
  287. default: 0
  288. },
  289. taskCount: {
  290. type: Number,
  291. default: 0
  292. },
  293. worksStudent: {
  294. type: Array,
  295. default: () => []
  296. },
  297. fileList: {
  298. type: Array,
  299. default: () => []
  300. },
  301. videoStart: {
  302. type: Boolean,
  303. default: false
  304. }
  305. },
  306. data() {
  307. return {
  308. userid: this.$route.query.userid,
  309. courseId: this.$route.query.courseId,
  310. type: 0,
  311. itemType: 0, //0--无 1-搜索 2-任务 3-对话
  312. fileId: [],
  313. recordType: 0,
  314. recordLoading: false,
  315. fold: false,
  316. openMegaphone: false, //是否打开喇叭
  317. getFileIdLoading: false,
  318. AnnotationCanvasShow: false
  319. };
  320. },
  321. mounted() {
  322. this.setWidth();
  323. this.getFileId();
  324. },
  325. methods: {
  326. changeAnnotationCanvasShow(newValue) {
  327. this.AnnotationCanvasShow = newValue;
  328. },
  329. startRecording() {
  330. this.$emit("startRecording");
  331. this.insertMemorandum(`使用<span class="btn">录制</span>功能,录制课堂`);
  332. },
  333. insertMemorandum(_html) {
  334. //保存行为操作
  335. //variable
  336. //btn
  337. return;
  338. let params = [
  339. {
  340. uid: this.userid,
  341. courseId: this.courseId,
  342. content: _html
  343. }
  344. ];
  345. this.ajax
  346. .post(
  347. this.$store.state.api + "insert_systemOperation_countdownBehavior",
  348. params
  349. )
  350. .then(res => {
  351. if (res.data == 1) {
  352. console.log("保存操作成功");
  353. } else {
  354. console.log("保存操作失败");
  355. }
  356. })
  357. .catch(e => {
  358. console.log("保存操作失败");
  359. console.log(e);
  360. });
  361. },
  362. setWidth() {
  363. let w = this.$refs.ch_box;
  364. let w2 = w.offsetWidth + 30 + "px";
  365. this.$emit("setWidth", w2);
  366. },
  367. openSetting() {
  368. this.type = this.type == 1 ? 0 : 1;
  369. this.$nextTick(() => {
  370. if(this.type==1){
  371. this.$parent.mlDialog = false
  372. }
  373. this.setWidth();
  374. });
  375. },
  376. changeItemType(type) {
  377. this.type = 0;
  378. this.openSetting();
  379. // this.$message.info("切换到"+type)
  380. this.$nextTick(() => {
  381. // if (this.itemType == 1 && type != 1) {
  382. // this.$refs.searchAreaRef.scrollBottom();
  383. // this.$refs.searchAreaRef.getWantSearch();
  384. // } else if (this.itemType == 2) {
  385. // this.$refs.taskAreaRef.scrollBottom();
  386. // } else if (this.itemType == 3) {
  387. // this.$refs.dialogAreaRef.scrollBottom();
  388. // }
  389. this.itemType = type;
  390. if (this.itemType == 4 && this.recordType == 1) {
  391. //关闭悬浮语音助手
  392. this.$refs.levitatedSphereRef.stopRecord();
  393. }
  394. if (this.itemType == 3) {
  395. this.insertMemorandum(`打开<span class="btn">倒计时</span>面板`);
  396. }
  397. });
  398. },
  399. //计时
  400. startTime(time) {
  401. this.$refs.timepieceRef.startTime(time);
  402. },
  403. getFileId() {
  404. if (this.getFileIdLoading) return;
  405. this.getFileIdLoading = true;
  406. this.fileId = [];
  407. let _this = this;
  408. let _successFileUrl = [];
  409. if (this.fileList.length <= 0) retrun;
  410. let addType = ["DOCX", "DOC", "PPT", "PPTX", "MD", "TXT", "PDF"];
  411. this.fileList.forEach(i => {
  412. if (
  413. addType.includes(
  414. i.url.split(".")[i.url.split(".").length - 1].toLocaleUpperCase()
  415. )
  416. ) {
  417. _successFileUrl.push(i.url);
  418. }
  419. });
  420. let promiseList = [];
  421. _successFileUrl.forEach(i => {
  422. promiseList.push(
  423. new Promise((resolve, reject) => {
  424. _this.ajax
  425. .put("https://gpt4.cocorobo.cn/upload_file_knowledge", {
  426. url: i
  427. })
  428. .then(res => {
  429. let _data = res.data.FunctionResponse;
  430. if (_data.result && _data.result.id) {
  431. this.fileId.push(_data.result.id);
  432. }
  433. resolve();
  434. });
  435. })
  436. );
  437. });
  438. Promise.all(promiseList).then(res => {
  439. this.getFileIdLoading = false;
  440. });
  441. },
  442. startAssistant() {
  443. if (this.recordLoading) return this.$message.info("请稍等...");
  444. this.recordLoading = true;
  445. if (this.recordType == 0) {
  446. if (this.itemType == 4) {
  447. this.itemType = 0;
  448. this.type = 0;
  449. }
  450. // this.$message.info("开启")
  451. // this.changeRecordType(1)
  452. this.$refs.levitatedSphereRef.recordStart();
  453. } else if (this.recordType == 1) {
  454. // this.$message.info("关闭")
  455. // this.changeRecordType(0)
  456. this.$refs.levitatedSphereRef.stopRecord();
  457. }
  458. },
  459. changeMegaphone() {
  460. this.openMegaphone = !this.openMegaphone;
  461. if (this.openMegaphone) {
  462. this.$message.success("已开启AI语音");
  463. } else {
  464. this.$message.success("已关闭AI语音");
  465. }
  466. },
  467. // 展开
  468. changeFold(newValue) {
  469. // this.$message.info("展开");
  470. this.fold = newValue;
  471. console.log(this.$refs.foldBtnRef)
  472. let e1 = this.$refs.foldBtnRef.getBoundingClientRect();
  473. let e2 = this.$refs.ch_box.getBoundingClientRect();
  474. console.log('👇👇')
  475. console.log(e1.top - e2.top)
  476. this.$refs.itemFoldRef.style.top = e1.top - e2.top + "px";
  477. },
  478. // 收起
  479. changeUnfold(newValue) {
  480. // this.$message.info("收起");
  481. this.fold = newValue;
  482. },
  483. // 语音识别
  484. startRecord() {
  485. this.$refs.levitatedSphereRef.startRecord();
  486. },
  487. stopRecord() {
  488. this.$refs.levitatedSphereRef.stopRecord();
  489. },
  490. // 语音合成
  491. startSpeak() {
  492. },
  493. changeRecordType(type) {
  494. this.recordLoading = false;
  495. this.recordType = type;
  496. },
  497. commentAndAnnotate() {
  498. this.$refs.AnnotationCanvasRef.open();
  499. this.insertMemorandum(`开始使用<span class="btn">批注</span>功能`);
  500. },
  501. endCommentAndAnnotate() {
  502. this.insertMemorandum(`结束使用<span class="btn">批注</span>功能`);
  503. }
  504. }
  505. };
  506. </script>
  507. <style scoped>
  508. .ch_box {
  509. width: auto;
  510. background: rgb(255, 255, 255);
  511. position: fixed;
  512. height: calc(100% - 40px);
  513. border-radius: 10px;
  514. box-sizing: border-box;
  515. right: 20px;
  516. display: flex;
  517. top: 20px;
  518. z-index: 1000;
  519. }
  520. .ch_nav_box {
  521. height: 100%;
  522. width: 65px;
  523. display: flex;
  524. flex-direction: column;
  525. align-items: center;
  526. overflow-y: auto !important; /* 上下溢出显示滚动条 */
  527. overflow-x: hidden !important; /* 左右溢出正常溢出 */
  528. position: relative;
  529. }
  530. .ch_content_box {
  531. width: 400px;
  532. height: 100%;
  533. border-right: 2px solid #e7e7e7;
  534. }
  535. .ch_nav_box_bottom {
  536. width: 100%;
  537. box-sizing: border-box;
  538. border-top: solid 1px #eaeaea;
  539. display: flex;
  540. flex-direction: column;
  541. }
  542. .ch_nav_box_middle {
  543. width: 100%;
  544. box-sizing: border-box;
  545. border-top: solid 1px #eaeaea;
  546. flex-direction: column;
  547. justify-content: space-between;
  548. }
  549. .ch_nav_box_middle_item {
  550. width: 100%;
  551. height: 80px;
  552. display: flex;
  553. flex-direction: column;
  554. justify-content: center;
  555. align-items: center;
  556. cursor: pointer;
  557. transition: 0.3s;
  558. font-size: 14px;
  559. }
  560. .ch_nav_box_middle_item_active {
  561. background-color: #3681fc;
  562. color: white;
  563. }
  564. .ch_nav_box_middle_item > img {
  565. width: 24px;
  566. height: 24px;
  567. margin-bottom: 5px;
  568. }
  569. .ch_nav_box_bottom > div {
  570. width: 100%;
  571. height: 65px;
  572. display: flex;
  573. flex-direction: column;
  574. justify-content: center;
  575. align-items: center;
  576. cursor: pointer;
  577. }
  578. .ch_nav_box_bottom > div > img {
  579. width: 24px;
  580. height: 24px;
  581. }
  582. .ch_nav_box_top {
  583. width: 100%;
  584. height: auto;
  585. margin-top: auto;
  586. }
  587. .ch_nav_box_top > div {
  588. width: 100%;
  589. height: 65px;
  590. display: flex;
  591. justify-content: center;
  592. align-items: center;
  593. cursor: pointer;
  594. position: relative;
  595. }
  596. .ch_nav_box_top > div > img {
  597. width: 24px;
  598. height: 24px;
  599. transition: 0.3s;
  600. }
  601. .itemFold {
  602. position: absolute;
  603. width: 130px;
  604. right: 65px;
  605. top: 0;
  606. background: rgb(255, 255, 255);
  607. box-sizing: border-box;
  608. border: solid 1px #eaeaea;
  609. /* border-top: solid 1px #eaeaea; */
  610. z-index: 1000; /* 确保二级菜单在主菜单上层 */
  611. border-radius: 10px;
  612. box-sizing: border-box;
  613. padding:10px;
  614. display:flex;
  615. flex-direction: column;
  616. justify-content: center;
  617. align-items: center;
  618. }
  619. .itemFold > div {
  620. width: 100%;
  621. height: 45px;
  622. display: flex;
  623. justify-content: center;
  624. align-items: center;
  625. cursor: pointer;
  626. margin: 5px 0;
  627. border-radius: 10px;
  628. }
  629. .itemFold > div:hover{
  630. background:#F3F7FD;
  631. }
  632. .itemFold > div > img {
  633. width: 24px;
  634. height: 24px;
  635. }
  636. .itemFold > div > span{
  637. margin-left:10px;
  638. }
  639. @media screen and (max-height: 820px) {
  640. .ch_nav_box_bottom > div {
  641. height: auto;
  642. padding: 8px 0;
  643. }
  644. .ch_nav_box_middle_item{
  645. height: auto;
  646. padding: 8px 0;
  647. }
  648. .ch_nav_box_top > div{
  649. height: auto;
  650. padding: 8px 0;
  651. }
  652. }
  653. </style>