index.vue 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804
  1. <template>
  2. <div class="common-preview">
  3. <div class="common-preview__header">
  4. <div class="menu-container">
  5. {{ courseware_info.book_name }}
  6. </div>
  7. <div class="courseware">
  8. <span class="name-path">{{ courseware_info.name_path }}</span>
  9. <span class="flow-nodename">{{ courseware_info.cur_audit_flow_node_name }}</span>
  10. <slot name="middle" :courseware="courseware_info"></slot>
  11. <div class="group">
  12. <el-checkbox v-model="isShowGroup">显示分组</el-checkbox>
  13. <el-checkbox v-model="groupShowAll">分组显示全部</el-checkbox>
  14. <el-checkbox v-model="isJudgeCorrect">判断对错</el-checkbox>
  15. <el-checkbox v-model="isShowAnswer" :disabled="!isJudgeCorrect">显示答案</el-checkbox>
  16. </div>
  17. <span class="link">
  18. <el-select v-model="lang" placeholder="请选择语言" size="mini" class="lang-select">
  19. <el-option v-for="item in langList" :key="item.type" :label="item.name" :value="item.type" />
  20. </el-select>
  21. </span>
  22. <div class="operator">
  23. <slot name="operator" :courseware="courseware_info"></slot>
  24. </div>
  25. </div>
  26. </div>
  27. <div class="audit-content">
  28. <!-- 左侧菜单栏 -->
  29. <aside v-if="navigationShow" class="left-menu">
  30. <div class="courseware-info">
  31. <div class="cover-image">
  32. <img v-if="project.cover_image_file_url.length > 0" :src="project.cover_image_file_url" alt="cover-image" />
  33. </div>
  34. <div class="info-content">
  35. <div class="catalogue-icon" @click="toggleNavigationShow">
  36. <SvgIcon icon-class="catalogue" size="54" />
  37. </div>
  38. <div class="courseware">
  39. <div class="name nowrap-ellipsis" :title="courseware_info.book_name">
  40. {{ courseware_info.book_name }}
  41. </div>
  42. <div class="editor" :title="project.editor">
  43. {{ project.editor }}
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. <!-- 教材章节树 -->
  49. <div class="courseware-tree">
  50. <div
  51. v-for="{ id: nodeId, name, deep, is_leaf_chapter } in node_list"
  52. :key="nodeId"
  53. :class="['menu-item', { active: curSelectId === nodeId }, { courseware: isTrue(is_leaf_chapter) }]"
  54. :style="computedNameStyle(deep, isTrue(is_leaf_chapter))"
  55. @click="selectChapterNode(nodeId, isTrue(is_leaf_chapter))"
  56. >
  57. <span class="name nowrap-ellipsis" :title="name">
  58. {{ name }}
  59. </span>
  60. </div>
  61. </div>
  62. </aside>
  63. <div
  64. ref="previewMain"
  65. class="main-container"
  66. :style="{ paddingLeft: navigationShow ? '15px' : '315px', paddingRight: sidebarShow ? '15px' : '315px' }"
  67. >
  68. <!-- 左侧菜单栏 - 收缩 -->
  69. <div v-if="!navigationShow && !isFullScreen" class="catalogue-bar" @click="toggleNavigationShow">
  70. <SvgIcon icon-class="catalogue" size="54" />
  71. </div>
  72. <main :class="['preview-main']">
  73. <div class="preview-left"></div>
  74. <CoursewarePreview
  75. v-if="courseware_info.book_name"
  76. ref="courserware"
  77. :is-show-group="isShowGroup"
  78. :group-show-all="groupShowAll"
  79. :group-row-list="content_group_row_list"
  80. :data="data"
  81. :courseware-id="curSelectId"
  82. :component-list="component_list"
  83. :background="background"
  84. :can-remark="isTrue(courseware_info.is_my_audit_task) && isTrue(courseware_info.is_can_add_audit_remark)"
  85. :show-remark="false"
  86. :project="project"
  87. :component-remark-obj="remark_list_obj"
  88. @computeScroll="computeScroll"
  89. @editNote="handEditNote"
  90. @saveCollect="saveCollect"
  91. />
  92. <div class="preview-right"></div>
  93. </main>
  94. <!-- 右侧菜单栏 - 收缩 -->
  95. <aside v-if="!sidebarShow && !isFullScreen" class="sidebar-bar">
  96. <aside class="toolbar">
  97. <div class="toolbar-special">
  98. <img :src="require('@/assets/icon/sidebar-fullscreen.png')" alt="全屏" @click="fullScreen" />
  99. <img :src="require('@/assets/icon/sidebar-toolkit.png')" alt="工具箱" />
  100. <img :src="require(`@/assets/icon/arrow-down.png`)" alt="伸缩" @click="toggleSidebarShow" />
  101. </div>
  102. </aside>
  103. </aside>
  104. </div>
  105. <div v-if="!sidebarShow" class="back-top" @click="backTop">
  106. <img :src="require(`@/assets/icon/back-top.png`)" alt="返回顶部" />
  107. </div>
  108. <!-- 右侧工具栏 -->
  109. <aside v-if="sidebarShow" ref="sidebarMenu" class="sidebar">
  110. <aside class="toolbar">
  111. <div class="toolbar-special">
  112. <img :src="require('@/assets/icon/sidebar-fullscreen.png')" alt="全屏" @click="fullScreen" />
  113. <img :src="require('@/assets/icon/sidebar-toolkit.png')" alt="工具箱" />
  114. </div>
  115. <div v-if="sidebarShow" class="toolbar-list">
  116. <div
  117. v-for="{ icon, title, handle, param, children } in sidebarIconList"
  118. :key="icon"
  119. :class="['sidebar-item', { active: curToolbarIcon === icon }]"
  120. :title="title"
  121. @click="handleSidebarClick(handle, param, icon, children)"
  122. >
  123. <div
  124. class="sidebar-icon icon-mask"
  125. :style="{
  126. backgroundColor: curToolbarIcon === icon ? '#fff' : '#1E2129',
  127. maskImage: `url(${require(`@/assets/icon/sidebar-${icon}.svg`)})`,
  128. }"
  129. ></div>
  130. </div>
  131. </div>
  132. <div class="adjustable" @click="toggleSidebarShow">
  133. <img :src="require(`@/assets/icon/arrow-up.png`)" alt="伸缩" />
  134. </div>
  135. </aside>
  136. <div class="content">
  137. <div v-if="curToolbarIcon === 'search'" class="resource_box">
  138. <h5>{{ drawerTitle }}</h5>
  139. <div style="height: 40px"></div>
  140. <el-row :gutter="10" style="margin: 5px">
  141. <el-col :span="16">
  142. <el-input v-model="searchContent" placeholder="请输入文本内容" clearable />
  143. </el-col>
  144. <el-col :span="4">
  145. <el-button type="primary" @click="querySearchList"> 查询 </el-button>
  146. </el-col>
  147. </el-row>
  148. <div>
  149. <el-table :data="searchList" :show-header="false">
  150. <!-- <el-table-column prop="courseware_name" label="课件" />
  151. <el-table-column prop="component_type" label="组件" /> -->
  152. <el-table-column>
  153. <template #default="{ row }">
  154. {{ row.courseware_name + ' / ' + row.component_type_name }}
  155. </template>
  156. </el-table-column>
  157. <el-table-column label="" width="50">
  158. <template #default="{ row }">
  159. <el-link type="primary" @click="handleLocation(row, 3)">定位</el-link>
  160. </template>
  161. </el-table-column>
  162. </el-table>
  163. </div>
  164. </div>
  165. <!-- <div v-if="curToolbarIcon === 'totalResources'" class="resource_box"></div> -->
  166. <div v-if="['image', 'audio', 'video'].includes(twoCurToolbarIcon)" class="resource_box">
  167. <div class="source-toolbar-list">
  168. <div
  169. v-for="{ icon, title, handle, param } in twoSidebarList"
  170. :key="icon"
  171. :class="['sidebar-item', { active: twoCurToolbarIcon === icon }]"
  172. :title="title"
  173. @click="handleSidebarClick(handle, param, icon, '', 2)"
  174. >
  175. <div
  176. class="sidebar-icon icon-mask"
  177. :style="{
  178. backgroundColor: twoCurToolbarIcon === icon ? '#fff' : '#1E2129',
  179. maskImage: `url(${require(`@/assets/icon/sidebar-${icon}.svg`)})`,
  180. }"
  181. ></div>
  182. </div>
  183. </div>
  184. <div style="height: 40px"></div>
  185. <el-collapse v-model="activeBookChapterId" accordion @change="multimediaHandleChange">
  186. <el-collapse-item
  187. v-for="chapter in bookChapterList"
  188. :key="chapter.id"
  189. :name="chapter.id"
  190. :title="chapter.name"
  191. >
  192. <!-- 加载状态 -->
  193. <div v-if="multimediaLoadingStates" class="loading-text">加载中...</div>
  194. <!-- 加载完成显示数据 -->
  195. <div v-else-if="chapter.data">
  196. <ul class="scroll-container" infinite-scroll-disabled="disabled" :infinite-scroll-immediate="false">
  197. <li
  198. v-for="(item, index) in chapter.data"
  199. :key="`${chapter.id}-${index}`"
  200. class="list-item"
  201. @click="handleFileClick(item?.courseware_id, item?.component_id)"
  202. >
  203. <template v-if="parseInt(drawerType) === 0">
  204. <el-image v-if="shouldShowItem(chapter, item)" :src="item.file_url" fit="contain" />
  205. <div class="mark">
  206. <span class="word">{{ item.file_name }}</span>
  207. <el-link type="primary" class="el-icon-place linkLocation" @click="handleLocation(item, 3)" />
  208. </div>
  209. </template>
  210. <template v-else-if="parseInt(drawerType) === 1">
  211. <AudioPlay
  212. v-if="shouldShowItem(chapter, item)"
  213. view-size="middle"
  214. :file-id="item.file_id"
  215. :file-name="item.file_name.slice(0, item.file_name.lastIndexOf('.'))"
  216. :show-slider="true"
  217. :audio-index="index"
  218. />
  219. <div class="mark">
  220. <span class="word"></span>
  221. <el-link type="primary" class="el-icon-place linkLocation" @click="handleLocation(item, 3)" />
  222. </div>
  223. </template>
  224. <template v-else-if="parseInt(drawerType) === 2">
  225. <VideoPlay
  226. v-if="shouldShowItem(chapter, item)"
  227. view-size="small"
  228. :file-id="item.file_id"
  229. :video-index="index"
  230. />
  231. <div class="mark">
  232. <span class="word">{{ item.file_name }}</span>
  233. <el-link type="primary" class="el-icon-place linkLocation" @click="handleLocation(item, 3)" />
  234. </div>
  235. </template>
  236. </li>
  237. </ul>
  238. </div>
  239. <!-- 加载失败或未加载 -->
  240. <div v-else class="error-text">没有资源</div>
  241. </el-collapse-item>
  242. </el-collapse>
  243. </div>
  244. <div v-if="curToolbarIcon === 'collect'" class="resource_box">
  245. <h5>{{ drawerTitle }}</h5>
  246. <div style="height: 40px"></div>
  247. <ul v-if="allCottectList.length > 0" class="card-box">
  248. <li v-for="item in allCottectList" :key="item.id">
  249. <span class="el-icon-notebook-2"> 原文</span>
  250. <span>{{ item.text }}</span>
  251. <div>
  252. <el-button type="text" class="el-icon-delete" @click="handDelCollect(item.id)"> 删除</el-button>
  253. <el-divider direction="vertical" />
  254. <el-button type="text" class="el-icon-place" @click="handleLocation(item, 2)"> 定位</el-button>
  255. </div>
  256. </li>
  257. </ul>
  258. </div>
  259. <div v-if="curToolbarIcon === 'note'" class="resource_box">
  260. <h5>{{ drawerTitle }}</h5>
  261. <div style="height: 40px"></div>
  262. <ul v-if="allNoteList.length > 0" class="card-box">
  263. <li v-for="item in allNoteList" :key="item.id">
  264. <span class="el-icon-notebook-2"> 原文</span>
  265. <span>{{ item.text }}</span>
  266. <el-divider class="mt10" />
  267. <span v-html="item.note"></span>
  268. <div>
  269. <el-button type="text" class="el-icon-edit" @click="handEditNote(item)"> 编辑</el-button>
  270. <el-divider direction="vertical" />
  271. <el-button type="text" class="el-icon-delete" @click="handDelNote(item.id)"> 删除</el-button>
  272. <el-divider direction="vertical" />
  273. <el-button type="text" class="el-icon-place" @click="handleLocation(item, 1)"> 定位</el-button>
  274. </div>
  275. </li>
  276. </ul>
  277. </div>
  278. <template v-if="curToolbarIcon === 'audit'">
  279. <AuditRemark :remark-list="remark_list" :is-audit="isShowAudit" @deleteRemarks="deleteRemarks" />
  280. </template>
  281. </div>
  282. <div class="back-top" @click="backTop">
  283. <img :src="require(`@/assets/icon/back-top.png`)" alt="返回顶部" />
  284. </div>
  285. </aside>
  286. </div>
  287. <el-dialog title="" :visible="visibleMindMap" width="1100px" class="audit-dialog" @close="dialogClose('MindMap')">
  288. <MindMap
  289. v-if="isChildDataLoad"
  290. ref="mindMapRef"
  291. :project-id="projectId"
  292. :mind-map-json-data="mindMapJsonData"
  293. @child-click="handleNodeClick"
  294. />
  295. </el-dialog>
  296. <el-dialog
  297. title=""
  298. :visible="visibleVisNetwork"
  299. width="1100px"
  300. class="audit-dialog"
  301. :close-on-click-modal="false"
  302. @close="dialogClose('VisNetwork')"
  303. >
  304. <VisNetwork ref="visNetworkRef" :book-id="projectId" @child-click="handleNodeClick" />
  305. </el-dialog>
  306. <ExplanatoryNoteDialog
  307. ref="explanatoryNote"
  308. :open.sync="editDialogOpen"
  309. :init-data="oldRichData"
  310. title-text="笔记"
  311. @confirm="saveNote"
  312. @cancel="delNote"
  313. />
  314. </div>
  315. </template>
  316. <script>
  317. import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
  318. import { isTrue } from '@/utils/validate';
  319. import MindMap from '@/components/MindMap.vue';
  320. import VideoPlay from '@/views/book/courseware/preview/components/common/VideoPlay.vue';
  321. import AudioPlay from '@/views/book/courseware/preview/components/common/AudioPlay.vue';
  322. import ExplanatoryNoteDialog from '@/components/ExplanatoryNoteDialog.vue';
  323. import VisNetwork from '@/components/VisNetwork.vue';
  324. import * as OpenCC from 'opencc-js';
  325. import { GetBookCoursewareInfo, GetCoursewareAuditRemarkList, GetProjectInfo } from '@/api/project';
  326. import {
  327. ContentGetCoursewareContent_View,
  328. ChapterGetBookChapterStructExpandList,
  329. GetBookBaseInfo,
  330. MangerGetBookMindMap,
  331. GetBookChapterStructExpandList,
  332. PageQueryBookResourceList,
  333. GetLanguageTypeList,
  334. GetBookUnifiedAttrib,
  335. GetMyNoteList,
  336. DeleteMyNote,
  337. AddMyNote,
  338. UpdateMyNote,
  339. AddMyCollect,
  340. GetMyCollectList,
  341. DeleteMyCollect,
  342. SearchBookContentText,
  343. } from '@/api/book';
  344. import { getLocalStore } from '@/utils/auth';
  345. import { toggleFullScreen } from '@/utils/common';
  346. export default {
  347. name: 'CommonPreview',
  348. components: {
  349. CoursewarePreview,
  350. MindMap,
  351. VideoPlay,
  352. AudioPlay,
  353. ExplanatoryNoteDialog,
  354. VisNetwork,
  355. },
  356. provide() {
  357. return {
  358. getLang: () => this.lang,
  359. getChinese: () => this.chinese,
  360. getLangList: () => this.langList,
  361. convertText: this.convertText,
  362. getProjectId: () => this.projectId,
  363. getSelectId: () => this.select_node,
  364. };
  365. },
  366. data() {
  367. const sidebarIconList = [
  368. { icon: 'search', title: '搜索', handle: 'getSearch', param: { type: '5' } },
  369. { icon: 'mindmap', title: '思维导图', handle: 'openMindMap', param: {} },
  370. { icon: 'knowledge', title: '知识图谱', handle: 'openVisNetwork', param: {} },
  371. {
  372. icon: 'totalResources',
  373. title: '总资源',
  374. handle: '',
  375. param: {},
  376. children: [
  377. { icon: 'audio', title: '音频', handle: 'openDrawer', param: { type: '1' } },
  378. { icon: 'image', title: '图片', handle: 'openDrawer', param: { type: '0' } },
  379. { icon: 'video', title: '视频', handle: 'openDrawer', param: { type: '2' } },
  380. ],
  381. },
  382. { icon: 'collect', title: '收藏', handle: 'getCollect', param: { type: '3' } },
  383. { icon: 'note', title: '笔记', handle: 'getNote', param: { type: '4' } },
  384. // { icon: 'translate', title: '翻译', handle: '', param: {} },
  385. // { icon: 'setting', title: '设置', handle: '', param: {} },
  386. ];
  387. const book_id = getLocalStore('book_id') || '';
  388. return {
  389. projectId: book_id,
  390. select_node: this.id,
  391. courseware_info: {
  392. book_name: '',
  393. is_can_start_edit: 'false',
  394. is_can_submit_audit: 'false',
  395. is_can_audit_pass: 'false',
  396. is_can_audit_reject: 'false',
  397. is_can_add_audit_remark: 'false',
  398. is_can_finish_audit: 'false',
  399. is_can_request_shangjia_book: 'false',
  400. is_can_request_rollback_project: 'false',
  401. is_can_shangjia_book: 'false',
  402. is_can_rollback_project: 'false',
  403. },
  404. background: {
  405. background_image_url: '',
  406. background_position: {
  407. left: 0,
  408. top: 0,
  409. },
  410. },
  411. node_list: [],
  412. data: { row_list: [] },
  413. component_list: [],
  414. content_group_row_list: [],
  415. remark_list: [],
  416. remark_list_obj: {}, // 存放以组件为对象的数组
  417. searchList: [],
  418. searchContent: '',
  419. visible: false,
  420. remark_content: '',
  421. submit_loading: false,
  422. isTrue,
  423. menuPosition: {
  424. x: -1,
  425. y: -1,
  426. componentId: 'WHOLE',
  427. },
  428. curToolbarIcon: this.isShowAudit ? 'audit' : '',
  429. sidebarIconList,
  430. twoSidebarList: [],
  431. twoCurToolbarIcon: '',
  432. visibleMindMap: false,
  433. visibleVisNetwork: false,
  434. isChildDataLoad: false,
  435. mindMapJsonData: {}, // 思维导图json数据
  436. drawerType: '', // 抽屉类型
  437. drawerStyle: {
  438. top: '0',
  439. height: '0',
  440. right: '0',
  441. },
  442. page_capacity: 10,
  443. cur_page: 1,
  444. file_list: [],
  445. total_count: 0,
  446. loading: false,
  447. lastLoadTime: 0,
  448. minLoadInterval: 3 * 1000,
  449. isShowGroup: false,
  450. groupShowAll: true,
  451. opencc: OpenCC.Converter({ from: 'cn', to: 'tw' }),
  452. langList: [],
  453. lang: 'ZH',
  454. chinese: 'zh-Hans',
  455. isJudgeCorrect: false,
  456. isShowAnswer: false,
  457. unified_attrib: {},
  458. curSelectId: this.id,
  459. navigationShow: true,
  460. sidebarShow: true,
  461. project: {
  462. editor: '', // 作者
  463. cover_image_file_id: null, // 封面图片ID
  464. cover_image_file_url: '', // 封面图片URL
  465. },
  466. allNoteList: [],
  467. editDialogOpen: false,
  468. oldRichData: {},
  469. newSelectedInfo: null,
  470. allCottectList: [],
  471. bookChapterList: [],
  472. book_id: '',
  473. activeBookChapterId: '',
  474. multimediaLoadingStates: true,
  475. isFullScreen: false, // 是否全屏状态
  476. };
  477. },
  478. computed: {
  479. disabled() {
  480. const result = this.loading || this.noMore;
  481. return result;
  482. },
  483. noMore() {
  484. const result = this.file_list.length >= this.total_count;
  485. return result;
  486. },
  487. drawerTitle() {
  488. const titleMap = {
  489. 0: '图片资源',
  490. 1: '音频资源',
  491. 2: '视频资源',
  492. 3: '收藏列表',
  493. 4: '笔记列表',
  494. 5: '搜索结果',
  495. };
  496. return titleMap[this.drawerType] || '资源列表';
  497. },
  498. shouldShowItem() {
  499. return (chapter, item) => {
  500. return this.activeBookChapterId === chapter.id && item && item.file_id;
  501. };
  502. },
  503. },
  504. watch: {
  505. isJudgeCorrect(newVal) {
  506. if (!newVal) {
  507. this.isShowAnswer = false;
  508. }
  509. this.simulateAnswer(newVal);
  510. },
  511. isShowAnswer() {
  512. this.simulateAnswer();
  513. },
  514. curSelectId() {
  515. if (this.curToolbarIcon === 'note') {
  516. this.getNote();
  517. } else if (this.curToolbarIcon === 'collect') {
  518. this.getCollect();
  519. }
  520. },
  521. },
  522. mounted() {
  523. this.calcDrawerPosition();
  524. },
  525. created() {
  526. this.getBookBaseInfo();
  527. this.getBookChapterStructExpandList();
  528. this.getBookUnifiedAttr();
  529. this.getProjectInfo();
  530. // 监听全屏事件
  531. document.addEventListener('fullscreenchange', () => {
  532. if (document.fullscreenElement) {
  533. this.isFullScreen = true;
  534. } else {
  535. this.isFullScreen = false;
  536. }
  537. });
  538. },
  539. beforeDestroy() {
  540. document.removeEventListener('fullscreenchange', () => {});
  541. },
  542. methods: {
  543. selectFirstLeafNode() {
  544. if (!this.node_list || this.node_list.length === 0) return;
  545. let node = this.node_list.find((x) => this.isTrue(x.is_leaf_chapter));
  546. if (!node) return;
  547. this.selectChapterNode(node.id, true);
  548. },
  549. getBookBaseInfo() {
  550. GetBookBaseInfo({ id: this.projectId }).then(({ book_info }) => {
  551. this.courseware_info = { ...this.courseware_info, ...book_info, book_name: book_info.name };
  552. if (book_info.cover_image_file_id) {
  553. this.project.cover_image_file_url = book_info.cover_image_file_url;
  554. }
  555. if (book_info.editor) {
  556. this.project.editor = book_info.editor;
  557. }
  558. if (book_info.cover_image_file_url) {
  559. this.project.cover_image_file_url = book_info.cover_image_file_url;
  560. }
  561. });
  562. },
  563. getProjectInfo() {
  564. GetProjectInfo({ id: this.projectId }).then(({ project_info }) => {
  565. if (project_info.cover_image_file_url) {
  566. this.project = project_info;
  567. }
  568. });
  569. },
  570. /**
  571. * 得到教材课件信息
  572. * @param {string} id - 课件ID
  573. */
  574. getBookCoursewareInfo(id) {
  575. GetBookCoursewareInfo({ id, is_contain_producer: 'true', is_contain_auditor: 'true' }).then(
  576. ({ courseware_info }) => {
  577. this.courseware_info = { ...this.courseware_info, ...courseware_info };
  578. this.getLangList();
  579. }
  580. );
  581. },
  582. /**
  583. * 得到课件内容(展示内容)
  584. * @param {string} id - 课件ID
  585. */
  586. getCoursewareComponentContent_View(id) {
  587. ContentGetCoursewareContent_View({ id }).then(
  588. ({ content, component_list, content_group_row_list, title_list }) => {
  589. title_list = title_list || [];
  590. if (content) {
  591. const _content = JSON.parse(content);
  592. this.data = _content;
  593. this.background = {
  594. background_image_url: _content.background_image_url,
  595. background_position: _content.background_position,
  596. };
  597. } else {
  598. this.data = { row_list: [] };
  599. }
  600. if (component_list) this.component_list = component_list;
  601. this.component_list.forEach((x) => {
  602. if (x.component_type === 'audio') {
  603. let _c = JSON.parse(x.content);
  604. let p = _c.property || {};
  605. if (!p.file_name_display_mode) p.file_name_display_mode = 'true';
  606. if (p.view_method === 'independent' && !p.style_mode) {
  607. p.style_mode = 'middle';
  608. }
  609. if (!p.style_mode) p.style_mode = 'big';
  610. if (p.view_method === 'icon') {
  611. p.file_name_display_mode = 'false';
  612. p.view_method = 'independent';
  613. p.style_mode = 'small';
  614. }
  615. x.content = JSON.stringify(_c);
  616. } else if (x.component_type === 'richtext') {
  617. let _c = JSON.parse(x.content);
  618. let p = _c.property || {};
  619. let lev = Number(p.title_style_level);
  620. if (p.is_title !== 'true' || lev < 1 || !_c.content) return;
  621. let style = title_list.find((y) => y.level === lev) || {};
  622. if (style && style.style) {
  623. style = JSON.parse(style.style);
  624. let c_text = _c.content;
  625. const parser = new DOMParser();
  626. const doc = parser.parseFromString(c_text, 'text/html');
  627. const body = doc.body;
  628. const pElements = body.querySelectorAll('p');
  629. this.processHtmlString(pElements, style);
  630. _c.content = body.innerHTML;
  631. x.content = JSON.stringify(_c);
  632. }
  633. }
  634. });
  635. if (content_group_row_list) this.content_group_row_list = JSON.parse(content_group_row_list) || [];
  636. }
  637. );
  638. },
  639. processHtmlString(pElements, styleConfig, isClear) {
  640. pElements.forEach((pElement) => {
  641. if (isClear) {
  642. pElement.removeAttribute('style');
  643. } else {
  644. const style = pElement.style;
  645. if (styleConfig.font) style.fontFamily = styleConfig.font;
  646. if (styleConfig.font_size) style.fontSize = styleConfig.font_size;
  647. if (styleConfig.text_color) style.color = styleConfig.text_color;
  648. style.fontWeight = styleConfig.bold === 'true' ? 'bold' : 'normal';
  649. style.fontStyle = styleConfig.italic === 'true' ? 'italic' : 'normal';
  650. if (styleConfig.indent || styleConfig.indent == 0) style.marginLeft = styleConfig.indent + 'px';
  651. }
  652. });
  653. },
  654. getLangList() {
  655. GetLanguageTypeList({ book_id: this.courseware_info.book_id, is_contain_zh: 'true' }).then(
  656. ({ language_type_list }) => {
  657. this.langList = language_type_list;
  658. }
  659. );
  660. },
  661. /**
  662. * 得到教材章节结构展开列表
  663. */
  664. getBookChapterStructExpandList() {
  665. ChapterGetBookChapterStructExpandList({
  666. book_id: this.projectId,
  667. node_deep_mode: 0,
  668. is_contain_producer: 'true',
  669. is_contain_auditor: 'true',
  670. }).then(({ node_list }) => {
  671. this.node_list = node_list;
  672. });
  673. },
  674. getBookUnifiedAttr() {
  675. GetBookUnifiedAttrib({ book_id: this.projectId }).then(({ content }) => {
  676. if (content) {
  677. this.unified_attrib = JSON.parse(content);
  678. }
  679. });
  680. },
  681. /**
  682. * 选择节点
  683. * @param {string} nodeId - 节点ID
  684. */
  685. selectNode(nodeId) {
  686. this.getCoursewareComponentContent_View(nodeId);
  687. this.getBookCoursewareInfo(nodeId);
  688. this.getCoursewareAuditRemarkList(nodeId);
  689. this.select_node = nodeId;
  690. },
  691. // 审校批注列表
  692. getCoursewareAuditRemarkList(id) {
  693. this.remark_list = [];
  694. let remarkListObj = {};
  695. GetCoursewareAuditRemarkList({
  696. courseware_id: id,
  697. }).then(({ remark_list }) => {
  698. this.remark_list = remark_list;
  699. if (!remark_list) return;
  700. remarkListObj = remark_list.reduce((acc, item) => {
  701. if (!acc[item.component_id]) {
  702. acc[item.component_id] = [];
  703. }
  704. acc[item.component_id].push(item);
  705. return acc;
  706. }, {});
  707. this.remark_list_obj = remarkListObj;
  708. });
  709. },
  710. dialogClose(type) {
  711. this[`visible${type}`] = false;
  712. },
  713. // 计算previewMain滑动距离
  714. computeScroll() {
  715. this.$refs.courserware.handleResult(
  716. this.$refs.previewMain.scrollTop,
  717. this.$refs.previewMain.scrollLeft,
  718. this.select_node
  719. );
  720. },
  721. /**
  722. * 处理侧边栏图标点击事件
  723. * @param {string} handle - 处理函数名
  724. * @param {any} param - 处理函数参数
  725. * @param {string} icon - 图标名称
  726. */
  727. handleSidebarClick(handle, param, icon, children, barLevel) {
  728. if (typeof handle === 'string' && handle && typeof this[handle] === 'function') {
  729. this[handle](param);
  730. }
  731. if (barLevel === 2) {
  732. this.twoCurToolbarIcon = icon;
  733. } else {
  734. this.curToolbarIcon = icon;
  735. this.twoCurToolbarIcon = '';
  736. }
  737. if (children && children.length > 0 && Array.isArray(children)) {
  738. this.twoSidebarList = children;
  739. this.twoCurToolbarIcon = children[0].icon;
  740. this.openDrawer(children[0].param);
  741. }
  742. },
  743. /**
  744. * 打开知识图谱
  745. */
  746. openMindMap() {
  747. MangerGetBookMindMap({ book_id: this.projectId }).then(({ content }) => {
  748. if (content) {
  749. this.mindMapJsonData = JSON.parse(content);
  750. this.isChildDataLoad = true;
  751. }
  752. });
  753. this.visibleMindMap = true;
  754. },
  755. async handleNodeClick(data) {
  756. let [nodeId, componentId] = data.split('#');
  757. if (nodeId) this.selectNode(nodeId);
  758. if (componentId) {
  759. let node = await this.$refs.courserware.findChildComponentByKey(componentId);
  760. if (node) {
  761. await this.$nextTick();
  762. this.$refs.previewMain.scrollTo({
  763. top: node.$el.offsetTop - 50,
  764. left: node.$el.offsetLeft - 50,
  765. behavior: 'smooth',
  766. });
  767. }
  768. }
  769. this.visibleMindMap = false;
  770. this.visibleVisNetwork = false;
  771. },
  772. async openVisNetwork() {
  773. this.visibleVisNetwork = true;
  774. },
  775. // 计算抽屉滑出位置
  776. calcDrawerPosition() {
  777. const menu = this.$refs.sidebarMenu;
  778. if (menu) {
  779. const rect = menu.getBoundingClientRect();
  780. this.drawerStyle = {
  781. top: `${rect.top}px`,
  782. height: `${rect.height}px`,
  783. left: `${rect.right - 240}px`,
  784. };
  785. }
  786. },
  787. /**
  788. * 打开抽屉并初始化加载
  789. * @param {Object} param - 抽屉参数
  790. * @param {string} param.type - 抽屉类型(0: 图片, 1: 音频, 2: 视频)
  791. */
  792. openDrawer({ type }) {
  793. if (this.drawerType === type) {
  794. this.drawerType = '';
  795. return;
  796. }
  797. // 重置所有加载状态
  798. this.activeBookChapterId = '';
  799. this.resetLoadState();
  800. this.drawerType = type;
  801. this.$nextTick(() => {
  802. // 确保DOM更新后触发加载
  803. // this.loadMore();
  804. this.loadBookChapterStructExpandList();
  805. });
  806. },
  807. openAudit() {
  808. this.drawerType = '';
  809. },
  810. resetLoadState() {
  811. this.cur_page = 1;
  812. this.file_list = [];
  813. this.total_count = 0;
  814. this.loading = false;
  815. this.lastLoadTime = 0; // 重置时间戳,允许立即加载
  816. this.loadCount = 0;
  817. },
  818. /**
  819. * 加载章节
  820. */
  821. async loadBookChapterStructExpandList() {
  822. const params = {
  823. book_id: this.projectId,
  824. node_deep_mode: 3, // 节点深度模式 0【全部】,1【只查询章节】2【只查询非叶子章节】3【只查询第一层】
  825. is_contain_root_node: 'false', // 是否包含根节点(把教材作为根节点)
  826. is_contain_producer: 'false', // 是否包含制作人信息
  827. is_contain_auditor: 'false', // 是否包含审核人信息
  828. };
  829. await GetBookChapterStructExpandList(params).then(({ node_list }) => {
  830. this.bookChapterList = node_list || [];
  831. });
  832. },
  833. /**
  834. * 加载章节下的资源
  835. */
  836. async loadmultimediaList() {
  837. const params = {
  838. page_capacity: this.page_capacity,
  839. cur_page: this.cur_page,
  840. book_id: this.projectId,
  841. book_chapter_node_id: this.activeBookChapterId,
  842. type: parseInt(this.drawerType),
  843. };
  844. await PageQueryBookResourceList(params)
  845. .then(({ total_count, resource_list }) => {
  846. this.total_count = total_count;
  847. this.file_list = resource_list || [];
  848. })
  849. .finally(() => {
  850. this.loading = false;
  851. });
  852. },
  853. /**
  854. * 点击章节,切换数据
  855. */
  856. async multimediaHandleChange() {
  857. const item = this.bookChapterList.find((item) => item.id === this.activeBookChapterId);
  858. if (item) {
  859. this.multimediaLoadingStates = true;
  860. if (!item.data && !item.error) {
  861. await this.loadmultimediaList();
  862. let tmpList = this.file_list && this.file_list.length > 0 ? [...this.file_list] : null;
  863. this.$set(item, 'data', tmpList);
  864. this.multimediaLoadingStates = false;
  865. } else {
  866. this.multimediaLoadingStates = false;
  867. }
  868. }
  869. },
  870. // 加载更多数据
  871. async loadMore() {
  872. const now = Date.now();
  873. // 只有当lastLoadTime不为0(不是第一次)且时间间隔太短时才return
  874. if (this.lastLoadTime > 0 && now - this.lastLoadTime < this.minLoadInterval) {
  875. return;
  876. }
  877. if (this.disabled || this.loading) {
  878. if (this.lastLoadTime > 0) {
  879. return;
  880. }
  881. }
  882. this.loading = true;
  883. const params = {
  884. page_capacity: this.page_capacity,
  885. cur_page: this.cur_page,
  886. book_id: this.projectId,
  887. book_chapter_node_id: this.activeBookChapterId,
  888. type: parseInt(this.drawerType),
  889. };
  890. await PageQueryBookResourceList(params)
  891. .then(({ total_count, resource_list }) => {
  892. this.total_count = total_count;
  893. // 记录加载前的滚动高度
  894. const scrollContainer = this.$el.querySelector('.scroll-container');
  895. const isAtBottom = this.isScrollAtBottom(scrollContainer);
  896. this.file_list = this.cur_page === 1 ? resource_list : [...this.file_list, ...resource_list];
  897. if (!resource_list || resource_list.length === 0) {
  898. return;
  899. }
  900. this.cur_page += 1;
  901. // 只有当前已经在底部时才微调滚动位置
  902. if (isAtBottom) {
  903. this.$nextTick(() => {
  904. // 轻微向上滚动,创造滚动空间
  905. scrollContainer.scrollTop -= 5;
  906. });
  907. }
  908. })
  909. .finally(() => {
  910. this.loading = false;
  911. this.lastLoadTime = now;
  912. });
  913. },
  914. isScrollAtBottom(container) {
  915. if (!container) return false;
  916. return container.scrollHeight - container.scrollTop <= container.clientHeight + 5;
  917. },
  918. async handleFileClick(courseware_id, component_id) {
  919. if (courseware_id) this.selectNode(courseware_id);
  920. if (component_id) {
  921. let node = await this.$refs.courserware.findChildComponentByKey(component_id);
  922. if (node) {
  923. await this.$nextTick();
  924. this.$refs.previewMain.scrollTo({
  925. top: node.offsetTop - 50,
  926. left: node.offsetLeft - 50,
  927. behavior: 'smooth',
  928. });
  929. }
  930. }
  931. },
  932. /**
  933. * 文本转换
  934. * @param {string} text - 要转换的文本
  935. * @returns {string} - 转换后的文本
  936. */
  937. convertText(text) {
  938. if (this.chinese === 'zh-Hant' && this.opencc) {
  939. return this.opencc(text);
  940. }
  941. return text;
  942. },
  943. simulateAnswer(disabled = true) {
  944. this.$refs.courserware.simulateAnswer(this.isJudgeCorrect, this.isShowAnswer, disabled);
  945. },
  946. /**
  947. * 选择节点
  948. * @param {string} nodeId - 节点ID
  949. * @param {boolean} isLeaf - 是否是叶子节点
  950. */
  951. selectChapterNode(nodeId, isLeaf) {
  952. if (!isLeaf) return;
  953. if (this.curSelectId === nodeId) return;
  954. this.curSelectId = nodeId;
  955. this.selectNode(nodeId);
  956. },
  957. /**
  958. * 计算章节名称样式
  959. * @param {number} deep - 节点深度
  960. * @param {boolean} isLeaf - 是否是叶子节点
  961. * @returns {Object} - 样式对象
  962. */
  963. computedNameStyle(deep, isLeaf) {
  964. return {
  965. 'padding-left': `${(deep - 1) * 8}px`,
  966. cursor: isLeaf ? 'pointer' : 'auto',
  967. };
  968. },
  969. /**
  970. * 切换左侧导航栏显示与隐藏
  971. */
  972. toggleNavigationShow() {
  973. this.navigationShow = !this.navigationShow;
  974. },
  975. /**
  976. * 切换右侧工具栏显示与隐藏
  977. */
  978. toggleSidebarShow() {
  979. this.sidebarShow = !this.sidebarShow;
  980. },
  981. backTop() {
  982. this.$refs.previewMain.scrollTo({
  983. top: 0,
  984. left: 0,
  985. behavior: 'smooth',
  986. });
  987. },
  988. /**
  989. * 定位到对应位置
  990. * @param {Object} item - 位置对象
  991. * @param {number} type - 定位类型(1: 笔记定位, 2: 收藏定位, 3: 资源定位)
  992. */
  993. handleLocation(item, type) {
  994. if (type === 3) {
  995. let did = `${item.courseware_id}#${item.component_id}`;
  996. this.handleNodeClick(did);
  997. this.curSelectId = item.courseware_id;
  998. return;
  999. }
  1000. if (this.$refs.courserware && this.$refs.courserware.handleLocation) {
  1001. item.type = type;
  1002. this.$refs.courserware.handleLocation(item);
  1003. }
  1004. },
  1005. async getNote(params) {
  1006. if (params && params.type) this.drawerType = Number(params.type);
  1007. this.allNoteList = [];
  1008. await GetMyNoteList({ courseware_id: this.curSelectId }).then((res) => {
  1009. if (res.status === 1) {
  1010. res.note_list.forEach((x) => {
  1011. if (x.note_desc) {
  1012. let n = JSON.parse(x.note_desc);
  1013. let obj = {
  1014. coursewareId: x.courseware_id,
  1015. id: x.id,
  1016. blockId: n.blockId,
  1017. startIndex: n.startIndex,
  1018. endIndex: n.endIndex,
  1019. text: n.text,
  1020. note: n.note,
  1021. };
  1022. this.allNoteList.push(obj);
  1023. }
  1024. });
  1025. }
  1026. });
  1027. },
  1028. async handEditNote(note) {
  1029. this.oldRichData = {};
  1030. if (this.allNoteList.length === 0) {
  1031. await this.getNote();
  1032. }
  1033. let old = this.allNoteList.find(
  1034. (x) =>
  1035. x.coursewareId === note.coursewareId &&
  1036. x.blockId === note.blockId &&
  1037. x.startIndex === note.startIndex &&
  1038. x.endIndex === note.endIndex
  1039. );
  1040. if (old) {
  1041. this.oldRichData = old;
  1042. }
  1043. this.newSelectedInfo = note;
  1044. this.editDialogOpen = true;
  1045. },
  1046. saveNote(note) {
  1047. let noteInfo = {
  1048. blockId: this.newSelectedInfo.blockId,
  1049. startIndex: this.newSelectedInfo.startIndex,
  1050. endIndex: this.newSelectedInfo.endIndex,
  1051. note: note.note,
  1052. text: this.newSelectedInfo.text,
  1053. };
  1054. let reqData = {
  1055. courseware_id: this.newSelectedInfo.coursewareId, // 课件 ID
  1056. component_id: 'WHOLE',
  1057. note_desc: JSON.stringify(noteInfo), // 位置描述
  1058. };
  1059. if (note.id) {
  1060. if (!noteInfo.note) {
  1061. this.delNote(note.id);
  1062. return;
  1063. }
  1064. let upDate = {
  1065. id: note.id,
  1066. note_desc: reqData.note_desc,
  1067. };
  1068. UpdateMyNote(upDate).then(() => {
  1069. this.getNote();
  1070. });
  1071. } else {
  1072. AddMyNote(reqData).then(() => {
  1073. this.getNote();
  1074. });
  1075. }
  1076. this.editDialogOpen = false;
  1077. this.newSelectedInfo = null;
  1078. this.selectedInfo = null;
  1079. },
  1080. handDelNote(id) {
  1081. this.$confirm('确定要删除此条笔记吗?', '提示', {
  1082. confirmButtonText: '确定',
  1083. cancelButtonText: '取消',
  1084. type: 'warning',
  1085. })
  1086. .then(() => {
  1087. this.delNote(id);
  1088. })
  1089. .catch(() => {});
  1090. },
  1091. delNote(id) {
  1092. const noteId = id || (this.oldRichData && this.oldRichData.id);
  1093. if (!noteId) return;
  1094. DeleteMyNote({ id: noteId }).then(() => {
  1095. this.allNoteList = this.allNoteList.filter((x) => x.id !== noteId);
  1096. });
  1097. },
  1098. async getCollect(params) {
  1099. if (params && params.type) this.drawerType = Number(params.type);
  1100. this.allCottectList = [];
  1101. await GetMyCollectList({ courseware_id: this.curSelectId }).then((res) => {
  1102. if (res.status === 1) {
  1103. res.collect_list.forEach((x) => {
  1104. if (x.collect_desc) {
  1105. let n = JSON.parse(x.collect_desc);
  1106. let obj = {
  1107. coursewareId: x.courseware_id,
  1108. id: x.id,
  1109. blockId: n.blockId,
  1110. startIndex: n.startIndex,
  1111. endIndex: n.endIndex,
  1112. text: n.text,
  1113. };
  1114. this.allCottectList.push(obj);
  1115. }
  1116. });
  1117. }
  1118. });
  1119. },
  1120. async saveCollect(collect) {
  1121. if (this.allCottectList.length === 0) {
  1122. await this.getCollect();
  1123. }
  1124. let old = this.allCottectList.find(
  1125. (x) =>
  1126. x.coursewareId === collect.coursewareId &&
  1127. x.blockId === collect.blockId &&
  1128. x.startIndex === collect.startIndex &&
  1129. x.endIndex === collect.endIndex
  1130. );
  1131. if (old) {
  1132. this.$message({
  1133. dangerouslyUseHTMLString: true,
  1134. message: "<i class='el-icon-check' />已收藏",
  1135. });
  1136. return;
  1137. }
  1138. this.newSelectedInfo = collect;
  1139. let collectInfo = {
  1140. blockId: this.newSelectedInfo.blockId,
  1141. startIndex: this.newSelectedInfo.startIndex,
  1142. endIndex: this.newSelectedInfo.endIndex,
  1143. text: this.newSelectedInfo.text,
  1144. };
  1145. let reqData = {
  1146. courseware_id: this.newSelectedInfo.coursewareId, // 课件 ID
  1147. component_id: 'WHOLE',
  1148. collect_desc: JSON.stringify(collectInfo), // 位置描述
  1149. };
  1150. AddMyCollect(reqData)
  1151. .then(() => {
  1152. this.getCollect();
  1153. })
  1154. .then(() => {
  1155. this.$message({
  1156. dangerouslyUseHTMLString: true,
  1157. message: "<i class='el-icon-check' />已收藏",
  1158. });
  1159. });
  1160. },
  1161. handDelCollect(id) {
  1162. this.$confirm('确定要删除此条收藏吗?', '提示', {
  1163. confirmButtonText: '确定',
  1164. cancelButtonText: '取消',
  1165. type: 'warning',
  1166. })
  1167. .then(() => {
  1168. DeleteMyCollect({ id }).then(() => {
  1169. this.allCottectList = this.allCottectList.filter((x) => x.id !== id);
  1170. });
  1171. })
  1172. .catch(() => {});
  1173. },
  1174. getSearch(params) {
  1175. if (params && params.type) this.drawerType = Number(params.type);
  1176. },
  1177. async querySearchList() {
  1178. this.searchList = [];
  1179. if (!this.searchContent) return;
  1180. await SearchBookContentText({ book_id: this.projectId, text: this.searchContent }).then((res) => {
  1181. if (res.status === 1) {
  1182. this.searchList = res.courseware_component_list;
  1183. }
  1184. });
  1185. },
  1186. fullScreen() {
  1187. toggleFullScreen(this.$refs.previewMain);
  1188. },
  1189. },
  1190. };
  1191. </script>
  1192. <style lang="scss" scoped>
  1193. @use '@/styles/mixin.scss' as *;
  1194. $total-width: $courseware-width + $courseware-left-margin + $courseware-right-margin;
  1195. .common-preview {
  1196. &__header {
  1197. position: sticky;
  1198. top: 0;
  1199. left: 0;
  1200. z-index: 9;
  1201. display: flex;
  1202. align-items: center;
  1203. height: 40px;
  1204. padding: 6px 4px;
  1205. margin-bottom: 5px;
  1206. background-color: #fff;
  1207. border-top: $border;
  1208. border-bottom: $border;
  1209. > .menu-container {
  1210. display: flex;
  1211. justify-content: space-between;
  1212. width: 360px;
  1213. padding: 4px 8px;
  1214. border-right: $border;
  1215. }
  1216. > .courseware {
  1217. display: flex;
  1218. flex-grow: 1;
  1219. column-gap: 16px;
  1220. align-items: center;
  1221. justify-content: space-between;
  1222. height: 40px;
  1223. .name-path {
  1224. min-width: 200px;
  1225. height: 40px;
  1226. padding: 4px 8px;
  1227. font-size: 14px;
  1228. line-height: 32px;
  1229. border-right: $border;
  1230. }
  1231. .lang-select {
  1232. :deep .el-input {
  1233. width: 100px;
  1234. }
  1235. :deep .el-input__inner {
  1236. height: 24px;
  1237. line-height: 24px;
  1238. background-color: #fff;
  1239. }
  1240. :deep .el-input__icon {
  1241. line-height: 24px;
  1242. }
  1243. }
  1244. .flow-nodename {
  1245. flex: 1;
  1246. }
  1247. .group {
  1248. display: flex;
  1249. align-items: center;
  1250. }
  1251. .operator {
  1252. display: flex;
  1253. column-gap: 8px;
  1254. align-items: center;
  1255. .link {
  1256. + .link {
  1257. margin-left: 0;
  1258. &::before {
  1259. margin-right: 8px;
  1260. color: #999;
  1261. content: '|';
  1262. }
  1263. }
  1264. }
  1265. }
  1266. }
  1267. }
  1268. .main-container {
  1269. position: relative;
  1270. flex: 1;
  1271. min-width: 1110px;
  1272. padding: 15px 0;
  1273. overflow: auto;
  1274. background-color: #ececec;
  1275. .catalogue-bar {
  1276. position: absolute;
  1277. top: 15px;
  1278. left: 0;
  1279. display: flex;
  1280. align-items: center;
  1281. justify-content: center;
  1282. width: 54px;
  1283. height: 54px;
  1284. margin: -9px 6px 0 240px;
  1285. cursor: pointer;
  1286. background-color: #fff;
  1287. border-radius: 2px;
  1288. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 40%);
  1289. }
  1290. .sidebar-bar {
  1291. position: absolute;
  1292. top: 0;
  1293. right: 240px;
  1294. display: flex;
  1295. width: 60px;
  1296. height: calc(100vh - 166px);
  1297. .toolbar {
  1298. display: flex;
  1299. flex-direction: column;
  1300. align-items: center;
  1301. width: 60px;
  1302. height: 100%;
  1303. img {
  1304. cursor: pointer;
  1305. }
  1306. &-special {
  1307. display: flex;
  1308. flex-direction: column;
  1309. row-gap: 16px;
  1310. align-items: center;
  1311. width: 100%;
  1312. margin-bottom: 24px;
  1313. background-color: #fff;
  1314. img {
  1315. width: 36px;
  1316. height: 36px;
  1317. }
  1318. }
  1319. }
  1320. }
  1321. }
  1322. .back-top {
  1323. position: absolute;
  1324. right: 240px;
  1325. bottom: 0;
  1326. display: flex;
  1327. place-content: center center;
  1328. align-items: center;
  1329. width: 60px;
  1330. height: 60px;
  1331. cursor: pointer;
  1332. background-color: #fff;
  1333. }
  1334. main.preview-main {
  1335. display: flex;
  1336. flex: 1;
  1337. width: calc($total-width);
  1338. min-width: calc($total-width);
  1339. max-width: calc($total-width);
  1340. min-height: 100%;
  1341. margin: 0 auto;
  1342. background-color: #fff;
  1343. border-radius: 4px;
  1344. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 40%);
  1345. .preview-left {
  1346. width: $courseware-left-margin;
  1347. min-width: $courseware-left-margin;
  1348. max-width: $courseware-left-margin;
  1349. background-color: $courseware-bgColor;
  1350. }
  1351. .preview-right {
  1352. width: $courseware-right-margin;
  1353. min-width: $courseware-right-margin;
  1354. max-width: $courseware-right-margin;
  1355. background-color: $courseware-bgColor;
  1356. }
  1357. &.no-audit {
  1358. margin: 0 auto;
  1359. }
  1360. }
  1361. .audit-content {
  1362. display: flex;
  1363. min-width: 1810px;
  1364. height: calc(100vh - 46px);
  1365. .left-menu {
  1366. display: flex;
  1367. flex-direction: column;
  1368. width: $catalogue-width;
  1369. font-family: 'Microsoft YaHei', 'Arial', sans-serif;
  1370. background-color: #fff;
  1371. .courseware-info {
  1372. display: flex;
  1373. column-gap: 18px;
  1374. width: 100%;
  1375. height: 186px;
  1376. padding: 6px 6px 24px;
  1377. border-bottom: $border;
  1378. .cover-image {
  1379. display: flex;
  1380. align-items: center;
  1381. justify-content: center;
  1382. width: 111px;
  1383. height: 157px;
  1384. background-color: rgba(229, 229, 229, 100%);
  1385. img {
  1386. max-width: 111px;
  1387. max-height: 157px;
  1388. }
  1389. }
  1390. .info-content {
  1391. display: flex;
  1392. flex-direction: column;
  1393. justify-content: space-between;
  1394. .catalogue-icon {
  1395. text-align: right;
  1396. .svg-icon {
  1397. cursor: pointer;
  1398. }
  1399. }
  1400. .courseware {
  1401. width: 159px;
  1402. height: 64px;
  1403. font-size: 16px;
  1404. .name {
  1405. font-weight: bold;
  1406. }
  1407. .editor {
  1408. display: -webkit-box;
  1409. overflow: hidden;
  1410. text-overflow: ellipsis;
  1411. word-break: break-word;
  1412. white-space: normal;
  1413. -webkit-line-clamp: 2; /* 多行省略行数,按需调整 */
  1414. -webkit-box-orient: vertical;
  1415. }
  1416. }
  1417. }
  1418. }
  1419. .courseware-tree {
  1420. display: flex;
  1421. flex: 1;
  1422. flex-direction: column;
  1423. row-gap: 8px;
  1424. padding: 12px;
  1425. margin-top: 12px;
  1426. overflow: auto;
  1427. .menu-item {
  1428. display: flex;
  1429. align-items: center;
  1430. &:not(.courseware) {
  1431. font-weight: bold;
  1432. }
  1433. &.courseware {
  1434. &:hover {
  1435. .name {
  1436. background-color: #f3f3f3;
  1437. }
  1438. }
  1439. }
  1440. .svg-icon {
  1441. margin-left: 4px;
  1442. &.my-edit-task {
  1443. color: $right-color;
  1444. }
  1445. }
  1446. .name {
  1447. flex: 1;
  1448. padding: 4px 8px 4px 4px;
  1449. border-radius: 4px;
  1450. }
  1451. &.active {
  1452. .name {
  1453. font-weight: bold;
  1454. color: #4095e5;
  1455. }
  1456. }
  1457. }
  1458. }
  1459. }
  1460. .sidebar {
  1461. position: relative;
  1462. display: flex;
  1463. width: $sidebar-width;
  1464. .toolbar {
  1465. display: flex;
  1466. flex-direction: column;
  1467. align-items: center;
  1468. width: 60px;
  1469. height: 100%;
  1470. background-color: rgba(247, 248, 250, 100%);
  1471. img {
  1472. cursor: pointer;
  1473. }
  1474. &-special {
  1475. display: flex;
  1476. flex-direction: column;
  1477. row-gap: 16px;
  1478. margin-bottom: 24px;
  1479. }
  1480. &-list {
  1481. display: flex;
  1482. flex-direction: column;
  1483. row-gap: 16px;
  1484. align-items: center;
  1485. width: 100%;
  1486. .sidebar-item {
  1487. width: 100%;
  1488. padding-top: 2px;
  1489. text-align: center;
  1490. .sidebar-icon {
  1491. width: 36px;
  1492. height: 36px;
  1493. cursor: pointer;
  1494. }
  1495. &.active {
  1496. background-color: #4095e5;
  1497. }
  1498. }
  1499. }
  1500. }
  1501. .content {
  1502. flex: 1;
  1503. background-color: #fff;
  1504. .resource_box {
  1505. height: 100%;
  1506. overflow-y: auto;
  1507. border: 1px solid #e5e5e5;
  1508. .source-toolbar-list {
  1509. position: fixed;
  1510. z-index: 999;
  1511. display: flex;
  1512. width: 240px;
  1513. background: #f2f3f5;
  1514. .sidebar-item {
  1515. width: 100%;
  1516. padding-top: 5px;
  1517. text-align: center;
  1518. border-right: 1px solid #ccc;
  1519. .sidebar-icon {
  1520. width: 26px;
  1521. height: 26px;
  1522. cursor: pointer;
  1523. }
  1524. &.active {
  1525. background-color: #4095e5;
  1526. }
  1527. }
  1528. }
  1529. h5 {
  1530. position: fixed;
  1531. z-index: 999;
  1532. width: 240px;
  1533. padding: 0 5px;
  1534. margin: 0;
  1535. font-size: 18px;
  1536. line-height: 40px;
  1537. background: #f2f3f5;
  1538. }
  1539. .scroll-container {
  1540. display: flex;
  1541. flex-direction: column;
  1542. row-gap: 8px;
  1543. margin: 6px;
  1544. .list-item {
  1545. // display: flex;
  1546. align-items: center;
  1547. cursor: pointer;
  1548. border: 1px solid #ccc;
  1549. border-radius: 8px;
  1550. :deep .el-slider {
  1551. .el-slider__runway {
  1552. background-color: #eee;
  1553. }
  1554. }
  1555. .el-image {
  1556. display: flex;
  1557. width: 100%;
  1558. min-width: 100%;
  1559. height: 90px;
  1560. background-color: #ccc;
  1561. border-radius: 8px;
  1562. }
  1563. .video-play {
  1564. width: 100%;
  1565. min-width: 100%;
  1566. }
  1567. .text-box {
  1568. word-break: break-word;
  1569. }
  1570. }
  1571. }
  1572. p {
  1573. color: #999;
  1574. text-align: center;
  1575. }
  1576. .card-box li {
  1577. padding: 10px;
  1578. border-bottom: 1px solid #ccc;
  1579. .el-icon-notebook-2 {
  1580. display: block;
  1581. margin-bottom: 4px;
  1582. font-size: 12px;
  1583. color: grey;
  1584. }
  1585. }
  1586. .mark {
  1587. display: flex;
  1588. align-items: center;
  1589. justify-content: space-between;
  1590. padding: 3px;
  1591. .word {
  1592. flex: 1;
  1593. margin-right: 10px;
  1594. word-break: break-all;
  1595. }
  1596. .linkLocation {
  1597. cursor: pointer;
  1598. }
  1599. }
  1600. ::v-deep .el-collapse-item__header {
  1601. margin-left: 5px;
  1602. }
  1603. .loading-text {
  1604. padding: 5px;
  1605. color: #999;
  1606. text-align: center;
  1607. }
  1608. .error-text {
  1609. padding: 5px;
  1610. color: #999;
  1611. text-align: center;
  1612. }
  1613. }
  1614. }
  1615. .back-top {
  1616. position: absolute;
  1617. bottom: 0;
  1618. left: 0;
  1619. display: flex;
  1620. place-content: center center;
  1621. align-items: center;
  1622. width: 60px;
  1623. height: 60px;
  1624. cursor: pointer;
  1625. }
  1626. }
  1627. }
  1628. }
  1629. :deep .scroll-container .audio-wrapper {
  1630. width: 100% !important;
  1631. .audio-middle {
  1632. width: 100% !important;
  1633. padding: 6px 8px !important;
  1634. margin-left: 0;
  1635. border: none;
  1636. border-radius: 8px;
  1637. .audio-name {
  1638. text-align: left;
  1639. }
  1640. .slider-area {
  1641. column-gap: 8px !important;
  1642. }
  1643. :deep .remark-dialog {
  1644. .el-dialog__body {
  1645. padding: 5px 20px;
  1646. }
  1647. }
  1648. :deep .audit-dialog {
  1649. .el-dialog__body {
  1650. height: calc(100vh - 260px);
  1651. padding: 5px 20px;
  1652. }
  1653. .mind-map-container .mind-map {
  1654. height: calc(100vh - 310px);
  1655. }
  1656. }
  1657. }
  1658. }
  1659. .mt10 {
  1660. margin: 10px 0 0 !important;
  1661. background-color: #eee;
  1662. }
  1663. </style>
  1664. <style lang="scss">
  1665. .tox-tinymce-aux {
  1666. z-index: 9999 !important;
  1667. }
  1668. </style>