info.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <template>
  2. <div class="appNew-container">
  3. <el-card shadow="never" class="" style="">
  4. <div style="overflow: hidden">
  5. <div style="font-weight: bolder;float: left">
  6. {{ form.title }}
  7. </div>
  8. <div style="float: right">
  9. 请选择会议设备:
  10. <el-select v-model="deviceId" placeholder="请选择会议设备" style="">
  11. <el-option v-for="device in deviceList" :key="device.id" :label="device.deviceName"
  12. :value="device.id" />
  13. </el-select>
  14. </div>
  15. </div>
  16. </el-card>
  17. <el-card shadow="never" class="" style="">
  18. <el-tabs type="border-card" style="height: calc(100vh - 150px)">
  19. <el-tab-pane label="会议文件">
  20. <div style="display: flex;justify-content: center">
  21. <div style="width: 30%;">
  22. <el-table v-loading="loading" :data="fileList" height="calc(100vh - 220px)" @selection-change="handleSelectionChange">
  23. <el-table-column type="selection" width="55" align="center" />
  24. <el-table-column prop="fileName" label="文件名" :show-overflow-tooltip="true"/>
  25. </el-table>
  26. </div>
  27. <div style="width: 70%;border-left: 1px solid #e6e6e6;">
  28. <div v-if="null == pageSrc" style="height: calc(100vh - 300px);">
  29. <el-empty description=""></el-empty>
  30. </div>
  31. <div v-else style="height: calc(100vh - 300px);display: flex;justify-content: center;align-items: center;" >
  32. <el-image :src="pageSrc.fileUrl" style="height: 100%"/>
  33. </div>
  34. <div style="height: 10%;display: flex;justify-content: center;align-items: center;">
  35. <el-button type="primary" :disabled="single" @click="showFile">立即播放</el-button>
  36. <el-button type="primary" @click="lastPage">上一页</el-button>
  37. <el-button type="primary" @click="nextPage">下一页</el-button>
  38. <el-button type="warning" :disabled="single" @click="endShowFile">结束播放</el-button>
  39. </div>
  40. </div>
  41. </div>
  42. </el-tab-pane>
  43. <el-tab-pane label="资料链接">
  44. <div style="display: flex;justify-content: center">
  45. <div style="width: 30%;height: calc(100vh - 220px)">
  46. <div v-for="(item,index) in materialOptions">
  47. <div style="display: flex;flex-direction: column;justify-content: center;">
  48. <span>
  49. <span style="font-weight: bolder;margin: 10px;">链接地址:</span>
  50. <span>{{ item }}</span>
  51. </span>
  52. <span style="margin: 10px;">
  53. <el-button type="primary" @click="showUrl(item)">开始播放</el-button>
  54. <el-button type="warning" @click="endShowUrl(item)">结束播放</el-button>
  55. </span>
  56. </div>
  57. </div>
  58. </div>
  59. <div v-if="material == null" style="width: 70%;border-left: 1px solid #e6e6e6;">
  60. <el-empty description=""></el-empty>
  61. </div>
  62. <div v-else style="width: 70%;border-left: 1px solid #e6e6e6;">
  63. <iframe :src="material" style="width: 100%;height: calc(100vh - 220px);border: 0;"></iframe>
  64. </div>
  65. </div>
  66. </el-tab-pane>
  67. <el-tab-pane label="图片抓拍">
  68. <div style="height: calc(100vh - 260px);display: flex;flex-wrap: wrap;overflow: auto">
  69. <div v-for="(item, index) in this.photoList" :key="index" class="imageDiv">
  70. <!-- <el-card shadow="never" class="centerCard">
  71. <el-image style="width: 320px;height: 180px;margin: 10px" :src="item"/>
  72. </el-card>-->
  73. <div>
  74. <el-image style="width: 320px;height: 180px;margin: 5px" :src="item"/>
  75. </div>
  76. <div style="margin: 5px;display: flex;justify-content: center;align-items: center;">
  77. <el-button type="warning" @click="deletePhoto(index)">删除图片</el-button>
  78. </div>
  79. </div>
  80. </div>
  81. <div style="margin-top: 5px;display: flex;justify-content: center;align-items: center;">
  82. <el-button @click="refreshPhoto" type="primary" >刷新</el-button>
  83. <el-button @click="takePhoto" type="primary" >拍照</el-button>
  84. <el-button @click="savePhoto" type="primary" >保存</el-button>
  85. </div>
  86. </el-tab-pane>
  87. <el-tab-pane label="会议纪要">
  88. <div style="display: flex;justify-content: center">
  89. <!-- 计时区域 -->
  90. <div style="width: 30%;height: calc(100vh - 220px)">
  91. <div style="font-size: medium;margin-bottom: 10px">会议录音</div>
  92. <div style="height: 85%;">
  93. <el-table v-loading="loading" :data="audioList" height="100%" row-key="id" @row-click="showAudioTxt" @selection-change="handleSelectionChange">
  94. <el-table-column label="序号" align="center" prop="sort" width="50" />
  95. <el-table-column label="文件名称" align="left" prop="fileName" :show-overflow-tooltip="true" />
  96. <el-table-column label="文本准换" align="center" prop="isTranslate" width="80" >
  97. <template slot-scope="scope">
  98. <dict-tag :options="dict.type.sys_asr_status" :value="scope.row.isTranslate"/>
  99. </template>
  100. </el-table-column>
  101. <el-table-column label="创建时间" align="left" prop="createTime" width="150" />
  102. </el-table>
  103. </div>
  104. <div style="height: 5%;display: flex;justify-content: center;align-items: center;font-weight: bolder;font-size: large">
  105. {{ formatTime }}
  106. </div>
  107. <!-- <el-divider></el-divider>-->
  108. <div style="margin-top: 5px;display: flex;justify-content: center;align-items: center;">
  109. <el-button @click="startTimer" type="primary" :disabled="timerRunning">开始录音</el-button>
  110. <el-button @click="pauseTimer" type="warning" :disabled="!timerRunning">暂停录音</el-button>
  111. <el-button @click="refreshAudio" type="primary" >刷新列表</el-button>
  112. <el-button @click="summaryGenerate" type="primary" >一键生成</el-button>
  113. </div>
  114. </div>
  115. <!-- 录音文件区域 -->
  116. <div v-if="audioTxt == null" style="width: 70%;border-left: 1px solid #e6e6e6;">
  117. <div style="font-size: medium;margin-bottom: 10px;margin-left: 20px">录音内容</div>
  118. <el-empty description=""></el-empty>
  119. </div>
  120. <div v-else style="width: 70%;border-left: 1px solid #e6e6e6;">
  121. <div style="font-size: medium;margin-bottom: 10px;margin-left: 20px">录音内容</div>
  122. <el-input resize="none" v-model="audioTxt" style="margin-left: 10px" type="textarea" :readonly="audioReadOnly" :rows = "29" placeholder="请输入内容" />
  123. <div style="margin-top: 35px;display: flex;justify-content: center;align-items: center;">
  124. <!-- <el-button type="primary" @click="copyWord">文本复制</el-button>-->
  125. <el-button type="primary" @click="audioTxtEdit">文本编辑</el-button>
  126. <el-button type="primary" @click="audioTxtSave">文本保存</el-button>
  127. </div>
  128. </div>
  129. </div>
  130. </el-tab-pane>
  131. <el-tab-pane label="AI问答">
  132. <div style="display: flex;flex-direction: column; height: calc(100vh - 150px)">
  133. <el-input v-model="sourceWord" resize="none" type="textarea" :rows = "12" placeholder="请输入内容" />
  134. <div style="margin-top: 20px;display: flex;justify-content: center;align-items: center;">
  135. <el-button type="primary" @click="rsWord">提交问题</el-button>
  136. <el-button type="waring" @click="resetWord">文本重置</el-button>
  137. </div>
  138. <el-divider style="margin-top: 10px;margin-bottom: 10px"></el-divider>
  139. <el-input id="targetWord" resize="none" v-model="targetWord" type="textarea" :readonly="true" :rows = "14" placeholder="请输入内容" />
  140. <div style="margin-top: 20px;display: flex;flex-direction: column;justify-content: center;align-items: center;">
  141. <el-button type="primary" @click="copyWord">文本复制</el-button>
  142. </div>
  143. </div>
  144. </el-tab-pane>
  145. </el-tabs>
  146. </el-card>
  147. </div>
  148. </template>
  149. <script>
  150. import {getMeeting, getMeetingFileImage, updateMeeting} from "@/api/partywork/meeting";
  151. import {endAudio, endFilePlay, filePlay, startAudio, takePhoto, urlControl} from "@/api/partywork/remoteControl";
  152. import {getToken} from "@/utils/auth";
  153. import {getAudio, listAudio, summaryMeeting, updateAudioTxt} from "@/api/partywork/meeting_audio";
  154. import {listDevice} from "@/api/pc/device";
  155. export default {
  156. dicts: ['sys_asr_status'],
  157. data() {
  158. return {
  159. meetingId: null,
  160. form: {},
  161. fileList: [],
  162. imageList: [],
  163. // 遮罩层
  164. loading: true,
  165. ids: [],
  166. // 非单个禁用
  167. single: true,
  168. // 非多个禁用
  169. multiple: true,
  170. totalPage: null,
  171. currentPage: 1,
  172. pageSrc: null,
  173. // 学习资料网站
  174. materialOptions: [],
  175. sourceWord: null,
  176. targetWord: null,
  177. websocket: null,
  178. material: null,
  179. timer: null, // 记录经过的时间秒数
  180. intervalId: null, // 定时器的ID
  181. timerRunning: false, // 状态标示,是否正在计时
  182. elapsed: 0, // 经过时间(毫秒)
  183. audioList: [],
  184. audioTxt: null,
  185. photoList:[],
  186. deviceList: [],
  187. deviceId: null,
  188. audioReadOnly: true,
  189. audioId: null,
  190. }
  191. },
  192. computed: {
  193. formatTime() {
  194. let remaining = this.elapsed;
  195. let milliseconds = remaining % 1000;
  196. remaining = Math.floor(remaining / 1000);
  197. let seconds = remaining % 60;
  198. remaining = Math.floor(remaining / 60);
  199. let minutes = remaining % 60;
  200. let hours = Math.floor(remaining / 60);
  201. return `${hours.toString().padStart(2, '0')}:${minutes
  202. .toString()
  203. .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(3, '0')}`;
  204. }
  205. },
  206. created() {
  207. this.getRouterParam();
  208. this.getMeetingInfo();
  209. this.socketConnect();
  210. this.refreshAudio();
  211. this.getDeviceList();
  212. },
  213. destroyed() {
  214. this.websocket.close();
  215. if (this.timerRunning) {
  216. this.pauseTimer();
  217. clearInterval(this.timer);
  218. }
  219. },
  220. methods: {
  221. audioTxtEdit() {
  222. this.audioReadOnly = false;
  223. this.$message({
  224. message: '您现在可以对录音内容进行编辑',
  225. type: 'success'
  226. });
  227. },
  228. validDeviceId() {
  229. if (this.deviceId == null || this.deviceId === '') {
  230. this.$message({
  231. message: '请先选择会议设备',
  232. type: 'error'
  233. });
  234. return false;
  235. }
  236. return true;
  237. },
  238. getDeviceList() {
  239. let param = {
  240. pageSize: 100
  241. }
  242. listDevice(param).then(response => {
  243. this.deviceList = response.rows;
  244. })
  245. },
  246. summaryGenerate() {
  247. if (this.timerRunning) {
  248. this.$message({
  249. message: '请先暂停录音',
  250. type: 'warning'
  251. });
  252. }else {
  253. summaryMeeting(this.meetingId).then(response => {
  254. this.$message({
  255. message: response.msg,
  256. type: 'success'
  257. });
  258. });
  259. /*this.$modal.confirm('确认通过AI一键生成会议纪要?').then(function() {
  260. return summaryMeeting(this.meetingId);
  261. }).then(() => {
  262. this.$modal.msgSuccess("任务提交成功,请稍后刷新列表!");
  263. }).catch(() => {});*/
  264. }
  265. },
  266. savePhoto() {
  267. this.form.picsPath = this.photoList.join(",");
  268. updateMeeting(this.form).then(response => {
  269. this.$message({
  270. message: '保存成功',
  271. type: 'success'
  272. });
  273. });
  274. },
  275. refreshPhoto() {
  276. const id = this.meetingId;
  277. getMeeting(id).then(response => {
  278. // 会议照片集合
  279. if (response.data.picsPath != null && response.data.picsPath !== '') {
  280. this.photoList = response.data.pics_path;
  281. console.log(this.photoList)
  282. }
  283. });
  284. },
  285. deletePhoto(index) {
  286. this.photoList.splice(index, 1);
  287. },
  288. takePhoto() {
  289. if (!this.validDeviceId()) {
  290. return;
  291. }
  292. let id = this.meetingId;
  293. takePhoto(id,this.deviceId).then(response => {
  294. this.$message({
  295. message: '发送拍照指令成功',
  296. type: 'success'
  297. });
  298. });
  299. },
  300. showAudioTxt(row) {
  301. if (row.isTranslate == 0 || row.isTranslate == 2 || row.isTranslate == 3) {
  302. this.$message({
  303. message: '当前无文本内容可供查看,请稍后!',
  304. type: 'warning'
  305. });
  306. return;
  307. }
  308. let id = row.id;
  309. this.audioId = id;
  310. getAudio(id).then(response => {
  311. this.audioTxt = response.data.audioTxt;
  312. });
  313. },
  314. audioTxtSave() {
  315. this.audioReadOnly = true;
  316. let id = this.audioId;
  317. let audioTxt = this.audioTxt;
  318. let param = {
  319. id: id,
  320. audioTxt: audioTxt
  321. }
  322. updateAudioTxt(param).then(response => {
  323. this.$message({
  324. message: '文本内容保存成功!',
  325. type: 'success'
  326. });
  327. this.showAudioTxt(param);
  328. });
  329. },
  330. refreshAudio() {
  331. let queryParam = {
  332. meetingId: this.meetingId,
  333. pageNum: 1,
  334. pageSize: 100
  335. };
  336. this.loading = true;
  337. listAudio(queryParam).then(response => {
  338. this.audioList = response.rows;
  339. this.loading = false;
  340. });
  341. },
  342. // 开始计时
  343. startTimer() {
  344. if (!this.validDeviceId()) {
  345. return;
  346. }
  347. if (!this.timer) {
  348. this.timerRunning = true;
  349. this.timer = setInterval(() => {
  350. this.elapsed += 10; // 我们每10毫秒更新一次时钟
  351. }, 10);
  352. startAudio(this.meetingId,this.deviceId).then(response => {
  353. this.$message({
  354. message: '开始录制成功',
  355. type: 'success'
  356. });
  357. });
  358. }
  359. },
  360. // 暂停计时
  361. pauseTimer() {
  362. if (!this.validDeviceId()) {
  363. return;
  364. }
  365. this.timerRunning = false;
  366. clearInterval(this.timer);
  367. this.timer = null;
  368. endAudio(this.meetingId,this.deviceId).then(response => {
  369. this.$message({
  370. message: '暂停录制成功',
  371. type: 'success'
  372. });
  373. });
  374. },
  375. showUrl(url) {
  376. if (!this.validDeviceId()) {
  377. return;
  378. }
  379. this.material = url;
  380. let param = {
  381. material : url,
  382. type : 1,
  383. deviceId: this.deviceId,
  384. meetingId: this.meetingId
  385. };
  386. urlControl(param).then(response => {
  387. this.$message({
  388. message: '播放成功',
  389. type: 'success'
  390. });
  391. });
  392. },
  393. endShowUrl(url) {
  394. if (!this.validDeviceId()) {
  395. return;
  396. }
  397. this.material = null;
  398. let param = {
  399. material : url,
  400. type : 2,
  401. deviceId: this.deviceId
  402. };
  403. urlControl(param).then(response => {
  404. this.$message({
  405. message: '结束成功',
  406. type: 'success'
  407. });
  408. });
  409. },
  410. copyWord() {
  411. let textToCopy = document.getElementById("targetWord").value;
  412. navigator.clipboard.writeText(textToCopy).then(function() {
  413. }, function(err) {
  414. console.error('无法复制文本:', err);
  415. });
  416. this.$message({
  417. message: '文本复制成功',
  418. type: 'success'
  419. });
  420. },
  421. resetWord() {
  422. this.sourceWord = "";
  423. let inputElement = document.getElementById("targetWord");
  424. inputElement.value = "";
  425. },
  426. getRouterParam() {
  427. this.meetingId = this.$route.query.meetingId;
  428. console.log(this.meetingId);
  429. },
  430. getMeetingInfo() {
  431. const id = this.meetingId;
  432. this.loading = true;
  433. getMeeting(id).then(response => {
  434. this.form = response.data;
  435. this.fileList = this.form.followList;
  436. this.materialOptions = this.form.materialOptions;
  437. // console.log(this.form);
  438. this.loading = false;
  439. // 会议照片集合
  440. if (this.form.picsPath != null && this.form.picsPath !== "") {
  441. this.photoList = this.form.picsPath.split(",");
  442. }
  443. });
  444. },
  445. // 多选框选中数据
  446. handleSelectionChange(selection) {
  447. this.ids = selection.map(item => item.id)
  448. this.single = selection.length!==1
  449. this.multiple = !selection.length
  450. },
  451. // 开始播放会议文件
  452. showFile() {
  453. if (!this.validDeviceId()) {
  454. return;
  455. }
  456. const id = this.ids[0];
  457. console.log(id);
  458. this.currentPage = 1;
  459. getMeetingFileImage(id).then(response => {
  460. this.imageList = response.data;
  461. this.totalPage = this.imageList.length;
  462. this.getPageSrc();
  463. });
  464. this.controlFilePlay();
  465. },
  466. getPageSrc () {
  467. this.imageList.forEach((item, index) => {
  468. if (index === this.currentPage - 1) {
  469. this.pageSrc = item;
  470. }
  471. })
  472. },
  473. // 上一页
  474. lastPage() {
  475. if (!this.validDeviceId()) {
  476. return;
  477. }
  478. if (this.currentPage === 1) {
  479. this.$message({
  480. message: '已经是第一页',
  481. type: 'warning'
  482. });
  483. } else {
  484. this.currentPage--;
  485. this.getPageSrc();
  486. this.controlFilePlay();
  487. }
  488. },
  489. controlFilePlay() {
  490. const id = this.ids[0];
  491. filePlay(id,this.currentPage,this.deviceId).then(response => {
  492. this.$message({
  493. message: response.msg,
  494. type: 'success'
  495. });
  496. });
  497. },
  498. endFilePlay() {
  499. const id = this.ids[0];
  500. endFilePlay(id,this.deviceId).then(response => {
  501. this.$message({
  502. message: response.msg,
  503. type: 'success'
  504. });
  505. });
  506. },
  507. // 下一页
  508. nextPage() {
  509. if (!this.validDeviceId()) {
  510. return;
  511. }
  512. if (this.totalPage == null) {
  513. this.$message({
  514. message: '请先选择文件开始文件播放',
  515. type: 'warning'
  516. });
  517. return;
  518. }
  519. if (this.currentPage === this.totalPage) {
  520. this.$message({
  521. message: '已经是最后一页',
  522. type: 'warning'
  523. });
  524. } else {
  525. this.currentPage++;
  526. this.getPageSrc();
  527. this.controlFilePlay();
  528. }
  529. },
  530. // 结束播放
  531. endShowFile() {
  532. if (!this.validDeviceId()) {
  533. return;
  534. }
  535. this.endFilePlay();
  536. this.currentPage = null;
  537. this.pageSrc = null;
  538. this.imageList = [];
  539. // this.fileList = [];
  540. this.totalPage = null;
  541. this.currentPage = 1;
  542. },
  543. rsWord() {
  544. if (this.sourceWord === null || this.sourceWord === '') {
  545. this.$message({
  546. message: '文本内容不能为空',
  547. type: 'warning'
  548. });
  549. }else {
  550. // 调用接口
  551. /*let word = {
  552. message : this.sourceWord
  553. };
  554. rsWord(word).then(response => {
  555. this.targetWord = response.data.result;
  556. });*/
  557. let inputElement = document.getElementById("targetWord");
  558. inputElement.value = "";
  559. this.socketSend()
  560. }
  561. },
  562. socketSend() {
  563. let message = this.sourceWord;
  564. this.websocket.send(message);
  565. },
  566. socketConnect() {
  567. let socketUrl = "ws://localhost:8080/ws/server/" + getToken();
  568. this.websocket = new WebSocket(socketUrl);
  569. this.websocket.onopen = function (event) {
  570. console.log("连接成功");
  571. };
  572. this.websocket.onmessage = function (event) {
  573. let message = event.data;
  574. let decodedMessage = decodeURIComponent(message); // 将消息转换为UTF-8编码
  575. let inputElement = document.getElementById("targetWord");
  576. inputElement.value = inputElement.value + decodedMessage;
  577. };
  578. this.websocket.onclose = function (event) {
  579. console.log("连接关闭");
  580. };
  581. this.websocket.onerror = function (event) {
  582. console.log("链接错误");
  583. };
  584. },
  585. }
  586. }
  587. </script>
  588. <style>
  589. .imageDiv{
  590. border: 1px solid #e6e6e6;
  591. height: 250px;
  592. margin-left: 40px;
  593. margin-right: 30px;
  594. margin-bottom: 10px;
  595. }
  596. </style>