AIDevelop.vue 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651
  1. <template>
  2. <!-- AI发展历程 -->
  3. <div class="home-container" @click="handlePageClick">
  4. <!-- 展开收起侧边栏 -->
  5. <div
  6. class="icon-expand"
  7. :style="{
  8. backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
  9. left: drawerVisible ? '18%' : '0',
  10. }"
  11. @click="toggleDrawer"
  12. >
  13. <span
  14. class="vertical-lines"
  15. :style="{
  16. color: drawerVisible ? '#8a78d0' : 'white'
  17. }"
  18. >||</span
  19. >
  20. </div>
  21. <el-drawer
  22. v-model="drawerVisible"
  23. direction="ltr"
  24. size="18%"
  25. :with-header="false"
  26. >
  27. <!-- 添加抽屉 -->
  28. <div class="drawer-box">
  29. <el-row class="tac">
  30. <el-col :span="12">
  31. <span class="mb-2">
  32. <img :src="classImages" alt="课程小节图标" />
  33. 课程小节
  34. </span>
  35. <el-menu
  36. :default-active="course.key"
  37. @open="handleOpen"
  38. @close="handleClose"
  39. @select="handleSelect"
  40. :default-openeds="['3','5']"
  41. >
  42. <template v-for="item in menuItems" :key="item.key">
  43. <el-menu-item v-if="!item.children" :index="item.key">{{
  44. item.title
  45. }}</el-menu-item>
  46. <el-sub-menu v-else :index="item.key">
  47. <template #title>
  48. <span>{{ item.title }}</span>
  49. </template>
  50. <el-menu-item-group v-if="item.children">
  51. <template v-for="child in item.children" :key="child.key">
  52. <el-menu-item :index="child.key"
  53. >•{{ child.title }}</el-menu-item
  54. >
  55. </template>
  56. </el-menu-item-group>
  57. </el-sub-menu>
  58. </template>
  59. </el-menu>
  60. </el-col>
  61. </el-row>
  62. </div>
  63. </el-drawer>
  64. <div class="content-box">
  65. <div class="box-1">
  66. <div class="inner-box left-box">
  67. <div class="box-icon" @click="goBack">
  68. <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
  69. {{ boxIconTitle }}
  70. </div>
  71. </div>
  72. <div class="inner-box right-box">
  73. <div class="top-right-box">
  74. <el-autocomplete
  75. v-model="SearchInput"
  76. :fetch-suggestions="querySearch"
  77. placeholder="搜索"
  78. @select="handleSearchSelect"
  79. class="search-input"
  80. value-key="title"
  81. :trigger-on-focus="false"
  82. >
  83. <template #prefix>
  84. <el-icon class="el-input__icon"><search /></el-icon>
  85. </template>
  86. <template #popper-append-to-body>
  87. <el-option
  88. class="scrollbar"
  89. v-for="item in filteredTitles"
  90. :key="item.key"
  91. :label="item.title"
  92. :value="item"
  93. ></el-option>
  94. </template>
  95. </el-autocomplete>
  96. </div>
  97. </div>
  98. </div>
  99. <div class="box-2">
  100. <!-- 课程标题 -->
  101. <div class="small-title">
  102. <span>{{ course.courseName }}</span>
  103. </div>
  104. <el-empty v-if="isDisabled"
  105. image-size="500"
  106. description="您无权查看该课程!"
  107. :image="isDisabledImage"
  108. />
  109. <template v-else>
  110. <!-- 视频组件 -->
  111. <VideoPlayer
  112. v-if="course.courseContentType === 'video'"
  113. :contentType="course.courseContentType"
  114. :videoPath="course.courseVideoPath"
  115. :courseId="course.id || ''"
  116. :typeId="typeId"
  117. :courseConfigList="course.courseConfigList || []"
  118. :allIndices="flattenMenuItems()"
  119. :currentIndex="course.key || ''"
  120. @timeUpdate="handleVideoTimeUpdate"
  121. @videoEnded="handleVideoEnded"
  122. @switchVideo="handleSelect"
  123. @saveProgress="handleSaveProgress"
  124. />
  125. <!-- 图片 -->
  126. <ImageView v-if="course.courseContentType === 'image'"
  127. :imagePath="course.courseImagePath"
  128. altText="课程图片"
  129. :courseId="course.id"
  130. @saveProgress="handleSaveProgress"
  131. ></ImageView>
  132. <!-- PPT -->
  133. <PptView v-if="course.courseContentType === 'ppt'"
  134. :pptPath="course.pptPath"
  135. :courseId="course.id"
  136. @saveProgress="handleSaveProgress"
  137. ref="pptRef"></PptView>
  138. <!--文生文-->
  139. <TextToText class="contentClass" v-if="course.courseContentType === 'aiTextToText'" ref="aiTextToText"
  140. :isCourse="true"
  141. :cacheDataKey="course.cacheDataKey"
  142. :cacheDataHistoryKey="course.cacheDataHistoryKey"
  143. @saveProgress="handleSaveProgress"></TextToText>
  144. <!--文生图-->
  145. <TextToImage class="contentClass" v-if="course.courseContentType === 'aiTextToImage'" ref="aiTextToImage"
  146. :isCourse="true"
  147. :cacheDataKey="course.cacheDataKey"
  148. :cacheDataHistoryKey="course.cacheDataHistoryKey"
  149. @saveProgress="handleSaveProgress"></TextToImage>
  150. <!--图生图-->
  151. <ImageToImage class="contentClass" v-if="course.courseContentType === 'aiImageToImage'" ref="aiImageToImage"
  152. :isCourse="true"
  153. :cacheDataKey="course.cacheDataKey"
  154. :cacheDataHistoryKey="course.cacheDataHistoryKey"
  155. @saveProgress="handleSaveProgress"></ImageToImage>
  156. <!--图生视频-->
  157. <ImageToVideo class="contentClass" v-if="course.courseContentType === 'aiImageToVideo'" ref="aiImageToVideo"
  158. :isCourse="true"
  159. :cacheDataKey="course.cacheDataKey"
  160. :cacheDataHistoryKey="course.cacheDataHistoryKey"
  161. @saveProgress="handleSaveProgress"></ImageToVideo>
  162. <!-- 对话界面(ailab课程类型) -->
  163. <DialogContent
  164. v-if="course.courseContentType === 'ailab'"
  165. :key="course.key"
  166. :scriptRoles="scriptRoles"
  167. :scriptData="course.courseContent"
  168. :back-text="boxIconTitle"
  169. :is-last-course="isLastCourse"
  170. @dialogue-ended="handleDialogueEnded"
  171. />
  172. </template>
  173. <!-- 视频切换按钮 - 始终显示 -->
  174. <div class="video-switch">
  175. <div class="caret-left" @click="playPreviousVideo">
  176. <el-button type="warning" round :disabled="flattenMenuItems().indexOf(course.key) === 0">
  177. <img :src="leftImg" alt="Left" />上一节</el-button
  178. >
  179. </div>
  180. <div class="caret-right" @click="playNextVideo">
  181. <el-button type="warning" round :disabled="flattenMenuItems().indexOf(course.key) === flattenMenuItems().length - 1"
  182. >下一节<img :src="rightImg" alt="Right" />
  183. </el-button>
  184. </div>
  185. </div>
  186. </div>
  187. </div>
  188. <!-- 弹框组件 -->
  189. <DialogComponents
  190. :questionDialogVisible="questionDialogVisible"
  191. :currentQuestion="courseConfig"
  192. :gradeId="gradeId"
  193. :typeId="typeId"
  194. :courseId="course.id || ''"
  195. @closeQuestionDialog="closeQuestionDialog"
  196. @submitAnswer="handleSubmitAnswer"
  197. @saveProgress="handleSaveProgress"
  198. />
  199. <!-- 提示弹窗组件 -->
  200. <PromptPopup
  201. :visible="promptPopupVisible"
  202. @confirm="handlePromptConfirm"
  203. @cancel="handlePromptCancel"
  204. />
  205. <!-- 播放提示组件 -->
  206. <PlayPrompt
  207. :visible="playPromptVisible"
  208. @countdownEnd="handlePlayPromptEnd"
  209. @close="handlePlayPromptClose"
  210. />
  211. </div>
  212. </template>
  213. <script setup>
  214. import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
  215. import { useRoute, useRouter } from 'vue-router'
  216. import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
  217. import isDisabledImage from '@/assets/images/permission/isDisabled.png'
  218. import classImages from '@/assets/icon/class.png'
  219. import { ClassType } from '@/api/class.js'
  220. import { Message } from '@/utils/message/Message.js'
  221. import { saveRecord } from '@/api/personalized/index.js'
  222. // 导入全局状态
  223. import { globalState } from '@/utils/globalState.js'
  224. // 导入图标
  225. import leftImg from '@/assets/icon/backward.png'
  226. import rightImg from '@/assets/icon/f-backward.png'
  227. // 导入新创建的组件
  228. import VideoPlayer from '@/components/videopage/VideoPlayer.vue'
  229. import DialogComponents from '@/components/videopage/DialogComponents.vue'
  230. import PptView from "@/components/PPT/PptView.vue";
  231. import ImageView from '@/components/Image/ImageView.vue'
  232. import PromptPopup from '@/components/popup/PromptPopup.vue'
  233. import PlayPrompt from '@/components/popup/PlayPrompt.vue'
  234. // AI实验室
  235. import TextToText from "@/components/ai/text/TextToText.vue";
  236. import TextToImage from "@/components/ai/image/TextToImage.vue";
  237. import ImageToImage from "@/components/ai/image/ImageToImage.vue";
  238. import ImageToVideo from "@/components/ai/video/ImageToVideo.vue";
  239. import DialogContent from '@/views/AIPage/aiGenerate/DialogContent.vue';
  240. import {DICT_TYPE} from "@/utils/dictUtils.js";
  241. import {teacherList} from "@/api/teachers.js";
  242. const router = useRouter() // 获取当前路由对象
  243. // 添加抽屉显示状态
  244. const drawerVisible = ref(false)
  245. // 渲染页面标题
  246. const boxIconTitle = ref('')
  247. // 课程集合数据
  248. const courseList = ref([])
  249. //当前课程
  250. const course = ref({})
  251. // 菜单数据
  252. const menuItems = ref([])
  253. // 课程集合数据
  254. const videoPathMap = ref({})
  255. // 已观看课程ID列表
  256. const watchedCourseIds = ref([])
  257. // 试题弹框显示状态
  258. const questionDialogVisible = ref(false)
  259. // 当前显示的试题
  260. const courseConfig = ref({})
  261. // 搜索框
  262. const SearchInput = ref('')
  263. // 年级id
  264. const gradeId = ref('')
  265. // 课程大纲id
  266. const typeId = ref('')
  267. // 课程小节id
  268. const courseId = ref('')
  269. // 课程排序
  270. const typeSort = ref('')
  271. // 测试账号禁用视频
  272. const isDisabled = ref(false)
  273. // 最后一节提示弹窗显示状态
  274. const promptPopupVisible = ref(false)
  275. // 即将播放下一节提示显示状态
  276. const playPromptVisible = ref(false)
  277. // 计算是否是最后一节课
  278. const isLastCourse = computed(() => {
  279. const allIndices = flattenMenuItems()
  280. const currentIndexInList = allIndices.indexOf(course.value.key)
  281. return currentIndexInList === allIndices.length - 1
  282. })
  283. // 处理 DialogContent 对话结束事件
  284. const handleDialogueEnded = (isLast) => {
  285. if (isLast) {
  286. // 最后一节课,检查课程类型
  287. if (course.value.courseContentType === 'ailab') {
  288. // ailab类型显示已经是最后一节课提示
  289. Message().notifyWarning('已经是最后一节课', true)
  290. return
  291. }
  292. // 其他类型显示返回提示弹窗
  293. promptPopupVisible.value = true
  294. } else {
  295. // 不是最后一节课,显示播放提示
  296. playPromptVisible.value = true
  297. }
  298. }
  299. // 脚本角色数据
  300. const scriptRoles = ref([])
  301. // 保存视频进度接口
  302. const handleSaveProgress = async (type, progress) => {
  303. if (!progress) {
  304. return;
  305. }
  306. let saveProgressData = {
  307. brpNjId: gradeId.value,
  308. brpType: type,
  309. brpProgress: progress
  310. }
  311. // aiCount类型不需要添加课程大纲id和课程小节id
  312. if (type !== "aiCount") {
  313. saveProgressData.brpCtId = typeId.value
  314. saveProgressData.brpCourseId = course.value.id
  315. }
  316. try {
  317. await saveRecord(saveProgressData)
  318. } catch (error) {
  319. console.error('保存视频进度失败:', error)
  320. }
  321. }
  322. //课程小节字典
  323. const menuDict = ref({
  324. 1: '课前回顾',
  325. 2: '课程引入',
  326. 3: '知识讲解',
  327. 4: '趣味实操',
  328. 5: '课程总结'
  329. })
  330. // 切换抽屉显示状态的函数
  331. const toggleDrawer = () => {
  332. drawerVisible.value = !drawerVisible.value
  333. }
  334. // 返回上一页
  335. const goBack = () => {
  336. router.push('/ai-general-course')
  337. }
  338. // 菜单打开和关闭的处理函数
  339. const handleOpen = () => {}
  340. const handleClose = () => {}
  341. // 菜单选择的处理函数
  342. const handleSelect = index => {
  343. // 根据索引切换视频
  344. if (videoPathMap.value[index]) {
  345. course.value = videoPathMap.value[index]
  346. setCacheDataKey()
  347. courseId.value = course.value.id
  348. // 切换标题后,关闭抽屉
  349. drawerVisible.value = false
  350. } else {
  351. //视频不存在
  352. Message().notifyWarning('视频不存在!', true)
  353. }
  354. //测试账号禁用视频
  355. if (disableMsg(index)) return
  356. }
  357. //填充缓存key
  358. const setCacheDataKey = () => {
  359. // 设置缓存key
  360. course.value.cacheDataKey = localStorage.getItem("token") + "_course_" + course.value.id
  361. course.value.cacheDataHistoryKey = course.value.cacheDataKey + "_history"
  362. }
  363. // 展平所有菜单项索引
  364. const flattenMenuItems = () => {
  365. const indices = []
  366. const traverse = items => {
  367. for (const item of items) {
  368. if (!item.children) {
  369. indices.push(item.key)
  370. } else {
  371. traverse(item.children)
  372. }
  373. }
  374. }
  375. traverse(menuItems.value)
  376. return indices
  377. }
  378. // 播放上一个视频
  379. const playPreviousVideo = () => {
  380. const allIndices = flattenMenuItems()
  381. const currentIndexInList = allIndices.indexOf(course.value.key)
  382. if (currentIndexInList > 0) {
  383. const previousIndex = allIndices[currentIndexInList - 1]
  384. handleSelect(previousIndex)
  385. }
  386. }
  387. // 播放下一个视频
  388. const playNextVideo = () => {
  389. const allIndices = flattenMenuItems()
  390. const currentIndexInList = allIndices.indexOf(course.value.key)
  391. if (currentIndexInList !== -1 && currentIndexInList < allIndices.length - 1) {
  392. const nextIndex = allIndices[currentIndexInList + 1]
  393. handleSelect(nextIndex)
  394. }
  395. }
  396. // 播放结束
  397. const handleVideoEnded = () => {
  398. console.log("播放结束")
  399. // 记录当前视频ID为已观看
  400. if (
  401. course.value &&
  402. course.value.id &&
  403. !watchedCourseIds.value.includes(course.value.id)
  404. ) {
  405. watchedCourseIds.value.push(course.value.id)
  406. localStorage.setItem(
  407. 'watchedCourseIds',
  408. JSON.stringify(watchedCourseIds.value)
  409. )
  410. }
  411. // 自动播放下一个
  412. // 检查是否是最后一节
  413. if (courseList.value && courseList.value.length > 0) {
  414. const currentIndex = courseList.value.findIndex(item => item.id === course.value.id)
  415. if (currentIndex === courseList.value.length - 1) {
  416. // 显示提示弹窗
  417. promptPopupVisible.value = true
  418. } else {
  419. // 显示播放提示
  420. playPromptVisible.value = true
  421. }
  422. }
  423. }
  424. // 禁用视频(提示语)
  425. const disableMsg = (index = course.value.key) => {
  426. //配置了课程权限,且视频id不在权限列表中
  427. isDisabled.value = videoPathMap.value[index].dataReadonly
  428. if (isDisabled.value) {
  429. Message().notifyWarning('您的账号并未开放此课程!', true)
  430. return isDisabled.value;
  431. }
  432. return isDisabled.value;
  433. }
  434. // 处理视频时间更新事件
  435. const handleVideoTimeUpdate = ({ currentTime, progressPercentage, courseConfig: config }) => {
  436. if (config) {
  437. questionDialogVisible.value = true
  438. courseConfig.value = config
  439. // 保存试题进度
  440. const saveQuestProgress = async () =>{
  441. try {
  442. // 确保courseId已经设置
  443. if (!courseId.value && typeId.value) {
  444. const courseRes = await ClassType(typeId.value)
  445. if (courseRes.data && courseRes.data.length > 0) {
  446. courseId.value = course.value && course.value.id ? course.value.id : courseRes.data[0].id
  447. }
  448. }
  449. if (config.id) {
  450. // 保存弹窗问题进度
  451. await saveRecord({
  452. brpNjId: gradeId.value,
  453. brpCtId: typeId.value,
  454. brpCourseConfigId: config.id,
  455. brpCourseId: courseId.value,
  456. brpType: 'courseQuest',
  457. brpProgress: progressPercentage
  458. })
  459. } else {
  460. console.error('无法保存试题进度: 试题id不存在')
  461. }
  462. }catch(error){
  463. console.error(`保存试题进度失败:`, error)
  464. }
  465. }
  466. // 调用异步函数
  467. saveQuestProgress()
  468. }
  469. }
  470. // 关闭试题弹框
  471. const closeQuestionDialog = () => {
  472. questionDialogVisible.value = false
  473. }
  474. // 提交答案
  475. const handleSubmitAnswer = (isCorrect) => {
  476. console.log("提交答案:", isCorrect)
  477. // 不直接关闭弹框,子组件自己控制关闭逻辑
  478. }
  479. // 处理提示弹窗确定按钮
  480. const handlePromptConfirm = () => {
  481. promptPopupVisible.value = false
  482. // 返回列表页
  483. goBack()
  484. }
  485. // 处理提示弹窗取消按钮
  486. const handlePromptCancel = () => {
  487. promptPopupVisible.value = false
  488. }
  489. // 处理播放提示倒计时结束
  490. const handlePlayPromptEnd = () => {
  491. playPromptVisible.value = false
  492. // 自动播放下一个视频
  493. playNextVideo();
  494. }
  495. // 处理播放提示关闭
  496. const handlePlayPromptClose = () => {
  497. playPromptVisible.value = false
  498. }
  499. // 处理页面点击事件,关闭提示弹窗
  500. const handlePageClick = (event) => {
  501. // 检查点击目标是否在弹窗内部
  502. const promptPopupElement = event.target.closest('.prompt-popup-content');
  503. const playPromptElement = event.target.closest('.play-prompt-container');
  504. // 如果点击目标不在弹窗内部,则关闭弹窗
  505. if (!promptPopupElement && promptPopupVisible.value) {
  506. promptPopupVisible.value = false;
  507. }
  508. if (!playPromptElement && playPromptVisible.value) {
  509. playPromptVisible.value = false;
  510. }
  511. }
  512. // 搜索
  513. const querySearch = (queryString, cb) => {
  514. const sections = getAllCourseSections()
  515. const results = queryString
  516. ? sections.filter(section =>
  517. section.title.toLowerCase().includes(queryString.toLowerCase())
  518. )
  519. : sections
  520. cb(results)
  521. }
  522. const filteredTitles = computed(() => {
  523. const sections = getAllCourseSections()
  524. if (!SearchInput.value) {
  525. return sections
  526. }
  527. return sections.filter(section =>
  528. section.title.toLowerCase().includes(SearchInput.value.toLowerCase())
  529. )
  530. })
  531. const handleSearchSelect = item => {
  532. handleSelect(item.key)
  533. // 清空输入框
  534. SearchInput.value = ''
  535. }
  536. // 课程小节数据提取
  537. const getAllCourseSections = () => {
  538. let sections = []
  539. const traverse = items => {
  540. items.forEach(item => {
  541. if (item.children) {
  542. traverse(item.children)
  543. } else {
  544. sections.push({ title: item.title, key: item.key })
  545. }
  546. })
  547. }
  548. traverse(menuItems.value)
  549. return sections
  550. }
  551. // 渲染 课程数据结构 以及 视频
  552. onMounted(async () => {
  553. const typeIdParam = history.state?.typeId || router.currentRoute.value.query.typeId
  554. if (typeIdParam) {
  555. typeId.value = typeIdParam
  556. try {
  557. // 取接口课程数据
  558. const res = await ClassType(typeIdParam)
  559. // 对返回的课程数据进行处理,确保ccTime为有效秒数
  560. const processedData = res.data.map(course => {
  561. // 检查并处理courseConfigList
  562. if (course.courseConfigList && Array.isArray(course.courseConfigList)) {
  563. // 过滤掉ccTime为0的配置项
  564. const validConfigList = course.courseConfigList.filter(config =>
  565. config.ccTime !== undefined && config.ccTime !== null && config.ccTime > 0
  566. )
  567. return {
  568. ...course,
  569. courseConfigList: validConfigList
  570. }
  571. }
  572. return course
  573. })
  574. courseList.value = processedData
  575. // 初始化已观看课程ID
  576. const savedWatchedIds = localStorage.getItem('watchedCourseIds')
  577. if (savedWatchedIds) {
  578. watchedCourseIds.value = JSON.parse(savedWatchedIds)
  579. }
  580. //课程数据
  581. let topName = '';
  582. courseList.value.forEach((courseTemp, index) => {
  583. let menuIndex = courseTemp.courseLabel + '-' + (index + 1)
  584. //大节
  585. let menu = {
  586. key: menuIndex,
  587. index: menuIndex,
  588. title: courseTemp.courseName
  589. }
  590. if (topName === courseTemp.courseLabel) {
  591. let topMenu = menuItems.value[menuItems.value.length - 1]
  592. let topMenuChildren = topMenu.children;
  593. if (topMenuChildren) {
  594. topMenuChildren.push(menu)
  595. } else {
  596. menu = {
  597. key: menuIndex,
  598. index: menuIndex,
  599. title: menuDict.value[courseTemp.courseLabel],
  600. children: [
  601. {
  602. key: topMenu.key,
  603. index: topMenu.index,
  604. title: topMenu.title,
  605. },
  606. {
  607. key: menuIndex,
  608. index: menuIndex,
  609. title: courseTemp.courseName
  610. }
  611. ]
  612. }
  613. menuItems.value[menuItems.value.length-1] = menu
  614. }
  615. }else{
  616. menuItems.value.push(menu)
  617. }
  618. topName = courseTemp.courseLabel
  619. courseTemp['key'] = menuIndex
  620. //解析AI生成课脚本json数据
  621. if (courseTemp.courseContentType === 'ailab') {
  622. try {
  623. courseTemp.courseContent = JSON.parse(courseTemp.courseContent)
  624. } catch (e) {
  625. console.error('解析courseContent失败:', e)
  626. }
  627. }
  628. videoPathMap.value[menuIndex] = courseTemp
  629. //确定默认课程
  630. if (index === 0) {
  631. courseId.value = courseTemp.id
  632. if(!disableMsg(menuIndex)){
  633. course.value = courseTemp
  634. setCacheDataKey()
  635. }else{
  636. course.value.key = courseTemp.key
  637. course.value.courseName = courseTemp.courseName
  638. }
  639. }
  640. })
  641. } catch (error) {
  642. console.error('获取课程数据失败:', error)
  643. }
  644. }
  645. const title = history.state?.typeName || router.currentRoute.value.query.typeName
  646. if (title) {
  647. boxIconTitle.value = String(title)
  648. }
  649. typeSort.value = history.state?.typeSort || router.currentRoute.value.query.typeSort
  650. // 初始化年级ID
  651. gradeId.value = globalState.initGradeId()
  652. //课程小节字典
  653. let menuDictStr = localStorage.getItem(DICT_TYPE.BJDX_COURSE_LABEL);
  654. let menuDictJson = menuDictStr ? JSON.parse(menuDictStr) : [];
  655. menuDict.value = menuDictJson.reduce((acc, item) => {
  656. acc[item.value] = item.label;
  657. return acc;
  658. }, {});
  659. // 初始化角色列表
  660. await getRoleList();
  661. })
  662. /**
  663. * 获取角色列表
  664. * @returns {Promise<void>}
  665. */
  666. const getRoleList = async () => {
  667. try {
  668. let grade = localStorage.getItem('selectedGrade')
  669. grade = grade ? grade : '小学低年级'
  670. // 获取小学低年级AI数据
  671. const listAiRes = await teacherList({ category: grade + 'AI' })
  672. const listRes = await teacherList({ category: grade })
  673. const listPoetRes = await teacherList({ category: "全部-Poet" })
  674. const listAi = listAiRes.data.list || []
  675. const list = listRes.data.list || []
  676. const listPoet = listPoetRes.data.list || []
  677. listAi.push(...list)
  678. listAi.push(...listPoet)
  679. scriptRoles.value = listAi
  680. } catch (error) {
  681. console.error('获取角色数据失败:', error)
  682. }
  683. }
  684. onBeforeUnmount(() => {
  685. // 组件卸载时的清理
  686. })
  687. </script>
  688. <style scoped lang="scss">
  689. @use 'sass:math';
  690. @use 'sass:color'; // 引入 color 模块
  691. // 定义rpx转换函数
  692. @function rpx($px) {
  693. @return math.div($px, 750) * 100vw;
  694. }
  695. // 定义儿童风格的蓝紫色调
  696. $primary-color: rgba(106, 90, 205, 0.52); // 主色调:蓝紫色
  697. $secondary-color: rgba(147, 112, 219, 0.66); // 辅助色:亮蓝紫色
  698. $accent-color: rgb(133, 89, 220); // 强调色:暗蓝紫色
  699. $light-color: #ffffff; // 浅色背景:淡紫色
  700. $text-color: #483d8b; // 文本颜色:靛蓝色
  701. // 视频切换按钮样式
  702. .video-switch {
  703. width: 100%;
  704. display: flex;
  705. margin-top: rpx(5);
  706. margin-bottom: rpx(15);
  707. justify-content: center;
  708. }
  709. .caret-right,
  710. .caret-left {
  711. width: rpx(50);
  712. margin: 0 rpx(20);
  713. margin: auto;
  714. display: flex;
  715. justify-content: center;
  716. }
  717. .caret-left ::v-deep(.el-button.is-round),
  718. .caret-right ::v-deep(.el-button.is-round) {
  719. width: rpx(50);
  720. height: rpx(15);
  721. color: white;
  722. font-size: rpx(7);
  723. border-radius: none;
  724. border: 1px white solid;
  725. background-color: rgb(255, 255, 255, 0.5);
  726. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
  727. }
  728. .caret-right img,
  729. .caret-left img {
  730. width: rpx(12);
  731. }
  732. .default-messages {
  733. margin-top: rpx(-10);
  734. margin-bottom: rpx(5);
  735. }
  736. /* 添加过渡样式 */
  737. .drawer-slide-enter-active,
  738. .drawer-slide-leave-active {
  739. transition: all 0.3s ease;
  740. }
  741. .drawer-slide-enter-from,
  742. .drawer-slide-leave-to {
  743. transform: translateX(-100%);
  744. opacity: 0;
  745. }
  746. .home-container ::v-deep(.el-drawer__body) {
  747. width: rpx(135);
  748. height: 100%;
  749. position: relative;
  750. background: linear-gradient(to bottom, #001169, #8a78d0);
  751. }
  752. .content-box {
  753. flex: 1;
  754. height: 100%;
  755. display: flex;
  756. flex-direction: column; /* 子元素上下排列 */
  757. background: linear-gradient(to bottom, #001169, #8a78d0);
  758. }
  759. .icon-expand {
  760. width: rpx(8);
  761. height: rpx(35);
  762. border-top-right-radius: rpx(5);
  763. border-bottom-right-radius: rpx(5);
  764. z-index: 9999;
  765. position: absolute;
  766. top: 50%;
  767. transform: translateY(-50%);
  768. cursor: pointer; // 添加鼠标指针样式
  769. clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
  770. display: flex;
  771. justify-content: center;
  772. align-items: center;
  773. transition: all 0.3s ease;
  774. }
  775. .icon-expand .vertical-lines {
  776. color: #8a78d0;
  777. font-size: rpx(10);
  778. }
  779. .home-container {
  780. position: fixed;
  781. top: 0;
  782. left: 0;
  783. right: 0;
  784. bottom: 0;
  785. background: linear-gradient(to bottom, #001169, #b4a8e1);
  786. display: flex;
  787. }
  788. .el-row {
  789. margin: auto;
  790. margin-top: rpx(20);
  791. }
  792. .tac ::v-deep(.el-menu) {
  793. background-color: transparent;
  794. border: none;
  795. width: 100%;
  796. margin-top: rpx(8);
  797. }
  798. /* 取消点击后的蓝色字体 el-sub-menu__title*/
  799. .tac ::v-deep(.el-menu-item.is-active),
  800. .tac ::v-deep(.el-sub-menu__title.is-active) {
  801. color: white;
  802. }
  803. // 取消鼠标悬浮颜色
  804. .tac ::v-deep(.el-sub-menu__title) {
  805. width: rpx(130);
  806. height: rpx(20);
  807. margin-bottom: rpx(5);
  808. border-radius: rpx(6);
  809. // 添加flex布局使标题和图标两端对齐
  810. display: flex;
  811. justify-content: space-between;
  812. align-items: center;
  813. }
  814. .el-menu ::v-deep(.el-sub-menu__title:hover),
  815. .el-menu ::v-deep(.el-sub-menu__title:focus),
  816. .el-menu ::v-deep(.el-sub-menu__title:active) {
  817. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  818. background-color: transparent;
  819. }
  820. // 添加二级标题折叠图标样式
  821. ::v-deep(.el-sub-menu__icon-arrow) {
  822. color: white;
  823. font-size: rpx(10); // 增大图标尺寸
  824. margin-left: auto; // 将图标推到右侧
  825. margin-top: rpx(-5);
  826. display: block;
  827. width: rpx(16); // 确保有明确宽度
  828. }
  829. // 鼠标悬停时的图标样式
  830. .el-menu ::v-deep(.el-sub-menu__title:hover .el-sub-menu__icon-arrow) {
  831. color: black; // 与悬停状态的文字颜色保持一致
  832. }
  833. .el-menu ::v-deep(.el-icon svg) {
  834. font-size: rpx(9);
  835. }
  836. .mb-2 {
  837. color: white;
  838. font-size: rpx(9);
  839. }
  840. .mb-2 img {
  841. width: rpx(10);
  842. height: rpx(10);
  843. vertical-align: middle;
  844. margin-top: rpx(-2);
  845. }
  846. .el-menu-item {
  847. width: rpx(100);
  848. height: rpx(20);
  849. margin-bottom: rpx(5);
  850. border-radius: rpx(6);
  851. color: white;
  852. font-size: rpx(8);
  853. }
  854. .el-menu ::v-deep(.el-sub-menu__title) {
  855. color: white;
  856. width: rpx(100);
  857. height: rpx(20);
  858. margin-bottom: rpx(5);
  859. font-size: rpx(8);
  860. }
  861. .el-menu ::v-deep(.el-sub-menu__title:hover),
  862. .el-menu ::v-deep(.el-sub-menu__title:focus),
  863. .el-menu ::v-deep(.el-sub-menu__title:active) {
  864. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  865. color: black;
  866. }
  867. .el-menu ::v-deep(.el-menu-item:hover),
  868. .el-menu ::v-deep(.el-menu-item:focus),
  869. .el-menu ::v-deep(.el-menu-item:active) {
  870. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  871. color: black;
  872. font-size: rpx(8);
  873. box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
  874. }
  875. .el-menu .el-menu-item.is-active {
  876. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  877. color: black;
  878. font-size: rpx(8);
  879. box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
  880. }
  881. .drawer-box {
  882. position: absolute;
  883. display: flex;
  884. align-items: center;
  885. height: 100%;
  886. width: 80%;
  887. }
  888. .drawer-box .toggle-button {
  889. width: rpx(10);
  890. height: rpx(50);
  891. font-size: rpx(7);
  892. background-color: rgba(17, 23, 29, 0.2);
  893. border: none;
  894. position: relative;
  895. writing-mode: vertical-lr; // 文字垂直排列,从左到右
  896. text-orientation: upright; // 文字保持正立
  897. }
  898. .toggle-button:hover {
  899. left: 0;
  900. }
  901. .toggle-button.is-active,
  902. .toggle-button:active,
  903. .toggle-button:focus {
  904. background-color: rgba(165, 209, 247, 0.8);
  905. color: white;
  906. border: none; // 移除点击时的边框
  907. outline: none; // 移除点击时的外边框
  908. }
  909. .box-1 {
  910. width: 100%;
  911. // height: rpx(50);
  912. margin-top: rpx(10);
  913. display: flex;
  914. justify-content: center;
  915. align-items: center;
  916. box-sizing: border-box;
  917. font-size: rpx(15); // 默认字体大小
  918. }
  919. .inner-box {
  920. height: 100%;
  921. display: flex;
  922. justify-content: center;
  923. align-items: center;
  924. font-size: rpx(16); // 默认字体大小
  925. }
  926. .left-box {
  927. position: relative;
  928. justify-content: flex-start;
  929. align-items: flex-start;
  930. flex: 1; // 设置左侧盒子占比为 2
  931. cursor: pointer; // 添加鼠标指针样式
  932. }
  933. .box-icon {
  934. display: flex;
  935. align-items: center;
  936. gap: rpx(5);
  937. padding: rpx(5) rpx(10);
  938. background-color: rgba(255, 255, 255, 80%);
  939. border-radius: rpx(30);
  940. backdrop-filter: blur(10px);
  941. cursor: pointer;
  942. transition: all 0.3s ease;
  943. font-size: rpx(8);
  944. color: #333;
  945. font-weight: 900;
  946. width: fit-content;
  947. margin-left: rpx(15);
  948. }
  949. .box-icon:hover {
  950. background-color: rgba(255, 255, 255, 90%);
  951. transform: translateX(-3px);
  952. }
  953. .left-icon {
  954. font-size: rpx(10);
  955. }
  956. // .box-icon .left-icon {
  957. // margin-left: rpx(10);
  958. // margin-right: rpx(5); // 设置图标和文字之间的间距 ;
  959. // }
  960. .left-box span {
  961. position: absolute;
  962. font-size: rpx(11); // 默认字体大小
  963. color: white;
  964. }
  965. .right-box {
  966. flex: 1;
  967. position: relative; // 添加相对定位;
  968. }
  969. .top-right-box {
  970. position: absolute; // 添加绝对定位
  971. // margin-top: rpx(9); // 调整上边距离
  972. margin-left: rpx(260); // 调整右边距离
  973. width: rpx(100); // 设置盒子宽度,可按需调整
  974. // height: 60px; // 设置盒子高度,可按需调整
  975. margin-right: rpx(20);
  976. display: flex;
  977. justify-content: flex-end;
  978. }
  979. .top-right-box {
  980. ::v-deep(.el-input__wrapper) {
  981. height: rpx(15);
  982. font-size: rpx(6);
  983. background-color: rgb(255, 255, 255, 0.5);
  984. border-radius: rpx(12);
  985. border: white 1px solid;
  986. color: white;
  987. }
  988. ::v-deep(.el-input__icon) {
  989. color: white; // 设置输入框图标颜色为白色
  990. }
  991. // 添加占位符样式
  992. ::v-deep(.el-input__inner::placeholder) {
  993. color: white;
  994. }
  995. // 添加输入框文字颜色样式
  996. ::v-deep(.el-input__inner) {
  997. color: black;
  998. }
  999. }
  1000. // 搜索框
  1001. .search-input {
  1002. width: rpx(100);
  1003. height: rpx(15);
  1004. font-size: rpx(7);
  1005. }
  1006. .box-2 {
  1007. width: 100%;
  1008. flex: 1;
  1009. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
  1010. box-sizing: border-box;
  1011. // display: flex; // 确保子元素水平排列
  1012. flex-wrap: wrap; // 允许子元素换行;
  1013. align-content: center; // 顶部对齐;
  1014. cursor: pointer; // 添加鼠标指针样式
  1015. }
  1016. .small-title {
  1017. width: 100%;
  1018. // margin-top: rpx(-20);
  1019. height: rpx(20);
  1020. color: white;
  1021. font-size: rpx(10);
  1022. justify-content: center; //使子元素水平居中
  1023. }
  1024. // 图片容器样式
  1025. .image-container {
  1026. width: 100%;
  1027. display: flex;
  1028. justify-content: center;
  1029. align-items: center;
  1030. // padding: rpx(20) 0;
  1031. }
  1032. // 图片样式
  1033. .course-image {
  1034. max-width: 70%;
  1035. max-height: rpx(400);
  1036. object-fit: contain;
  1037. border-radius: rpx(12);
  1038. box-shadow: 0 rpx(10) rpx(20) rgba(0, 0, 0, 0.1);
  1039. }
  1040. // 儿童风格试题弹框样式
  1041. .child-dialog {
  1042. .el-dialog__header {
  1043. display: none; // 隐藏原有的标题栏
  1044. }
  1045. .el-dialog__body {
  1046. padding: rpx(20);
  1047. position: relative;
  1048. }
  1049. .el-dialog__footer {
  1050. border-top: none;
  1051. padding: rpx(10) rpx(20);
  1052. text-align: center;
  1053. margin-top: auto; // 使底部按钮位于底部
  1054. }
  1055. .el-dialog__wrapper {
  1056. // 修改半透明背景色
  1057. background-color: rgba(0, 0, 0, 0.6);
  1058. }
  1059. .el-dialog {
  1060. border: none;
  1061. border-radius: rpx(20);
  1062. background: linear-gradient(
  1063. 135deg,
  1064. $light-color,
  1065. #d8bfd8
  1066. ); // 柔和的蓝紫色渐变
  1067. overflow: hidden;
  1068. display: flex; // 添加 flex 布局
  1069. flex-direction: column; // 设置垂直布局
  1070. min-height: 0; // 防止子元素溢出
  1071. // 添加装饰元素
  1072. &::before {
  1073. content: '';
  1074. position: absolute;
  1075. top: 0;
  1076. left: 0;
  1077. width: 100%;
  1078. height: rpx(10);
  1079. background: linear-gradient(90deg, $secondary-color, $accent-color);
  1080. }
  1081. }
  1082. }
  1083. // 问题标题样式
  1084. .question-title {
  1085. padding: rpx(15);
  1086. border-radius: rpx(12);
  1087. margin-bottom: rpx(20);
  1088. color: #483d8b;
  1089. font-weight: bold;
  1090. font-size: rpx(12);
  1091. position: relative;
  1092. display: flex;
  1093. // align-items: center;
  1094. .question-icon {
  1095. background-color: $accent-color;
  1096. color: white;
  1097. width: rpx(24);
  1098. height: rpx(24);
  1099. border-radius: 50%;
  1100. display: flex;
  1101. align-items: center;
  1102. justify-content: center;
  1103. margin-right: rpx(10);
  1104. font-weight: bold;
  1105. box-shadow: 0 rpx(2) rpx(5) rgba($accent-color, 0.3);
  1106. }
  1107. }
  1108. // 选项容器样式
  1109. .options-container {
  1110. margin-bottom: rpx(20);
  1111. }
  1112. // 问题选项样式
  1113. .question-option {
  1114. margin: rpx(8) 0;
  1115. padding: rpx(10) rpx(15);
  1116. border-radius: rpx(12);
  1117. border: rpx(1) solid rgba($primary-color, 0.3);
  1118. transition: all 0.3s ease;
  1119. display: flex;
  1120. align-items: center;
  1121. background-color: white;
  1122. box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.05);
  1123. ::v-deep(.el-radio__label) {
  1124. color: $text-color;
  1125. margin-left: rpx(8);
  1126. flex: 1;
  1127. text-align: left;
  1128. // 增大字体大小
  1129. font-size: rpx(12);
  1130. }
  1131. // 选中时的样式变化
  1132. .el-radio__input.is-checked + .el-radio__label {
  1133. font-weight: bold;
  1134. color: $accent-color;
  1135. }
  1136. &:hover {
  1137. background-color: rgba($primary-color, 0.05);
  1138. border-color: rgba($primary-color, 0.5);
  1139. transform: translateY(-rpx(1));
  1140. }
  1141. }
  1142. // 暂无选项样式
  1143. .no-options {
  1144. color: rgba($text-color, 0.7);
  1145. text-align: center;
  1146. padding: rpx(20);
  1147. font-size: rpx(12);
  1148. }
  1149. // 底部按钮样式
  1150. .child-button {
  1151. min-width: rpx(80);
  1152. height: rpx(30);
  1153. border-radius: rpx(8);
  1154. font-size: rpx(12);
  1155. font-weight: 500;
  1156. transition: all 0.3s ease;
  1157. box-shadow: 0 rpx(2) rpx(8) rgba(0, 0, 0, 0.1);
  1158. &.confirm {
  1159. background: linear-gradient(to bottom, #ab81ff, #8559dc);
  1160. border: none;
  1161. border-right: 15px;
  1162. color: white;
  1163. &:hover {
  1164. background: linear-gradient(
  1165. to bottom,
  1166. color.adjust(#ab81ff, $lightness: -5%),
  1167. color.adjust(#8559dc, $lightness: -5%)
  1168. );
  1169. transform: translateY(-rpx(1));
  1170. color: white;
  1171. }
  1172. }
  1173. &.cancel {
  1174. background: white;
  1175. border: rpx(1) solid rgba($primary-color, 0.3);
  1176. color: $text-color;
  1177. &:hover {
  1178. background: rgba($primary-color, 0.05);
  1179. border-color: rgba($primary-color, 0.5);
  1180. transform: translateY(-rpx(1));
  1181. }
  1182. }
  1183. }
  1184. // AI对话图标样式
  1185. .ai-icon-container {
  1186. position: absolute;
  1187. bottom: rpx(20);
  1188. right: rpx(20);
  1189. display: flex;
  1190. flex-direction: column;
  1191. align-items: center;
  1192. cursor: pointer;
  1193. transition: all 0.3s ease;
  1194. &:hover {
  1195. transform: translateY(-rpx(2));
  1196. }
  1197. .ai-icon {
  1198. width: rpx(30);
  1199. height: rpx(30);
  1200. margin-bottom: rpx(0);
  1201. filter: drop-shadow(0 rpx(2) rpx(4) rgba($primary-color, 0.3));
  1202. // 添加过渡动画
  1203. transition: transform 0.3s ease;
  1204. }
  1205. // 悬浮时放大效果
  1206. .ai-icon:hover {
  1207. transform: scale(1.5);
  1208. }
  1209. .ai-text {
  1210. color: $text-color;
  1211. font-size: rpx(8);
  1212. background-color: rgba(255, 255, 255, 0.7);
  1213. padding: rpx(2) rpx(5);
  1214. border-radius: rpx(5);
  1215. }
  1216. }
  1217. // AI消息样式
  1218. .ai-message {
  1219. display: flex;
  1220. align-items: flex-start;
  1221. margin-bottom: rpx(15);
  1222. .ai-avatar {
  1223. width: rpx(30);
  1224. height: rpx(30);
  1225. border-radius: 50%;
  1226. margin-right: rpx(10);
  1227. background-color: $primary-color;
  1228. padding: rpx(5);
  1229. // box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.2);
  1230. }
  1231. .ai-text-content {
  1232. background-color: $light-color;
  1233. padding: rpx(8) rpx(12);
  1234. border-radius: rpx(10);
  1235. font-size: rpx(10);
  1236. color: $text-color;
  1237. max-width: 80%;
  1238. // box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
  1239. }
  1240. }
  1241. // 用户输入框样式
  1242. .user-input {
  1243. ::v-deep(.el-input__wrapper) {
  1244. height: rpx(23);
  1245. border-top-left-radius: rpx(5);
  1246. border-bottom-left-radius: rpx(5);
  1247. border-color: rgba($primary-color, 0.3);
  1248. &:focus-within {
  1249. box-shadow: 0 0 0 rpx(1) rgba($primary-color, 0.5);
  1250. }
  1251. }
  1252. ::v-deep(.el-input__inner) {
  1253. font-size: rpx(10);
  1254. // color: $text-color;
  1255. text-indent: 1em;
  1256. }
  1257. ::v-deep(.el-input-group__append, .el-input-group__prepend) {
  1258. background: linear-gradient(to bottom, #ab81ff, #8559dc);
  1259. border-top-right-radius: rpx(5);
  1260. border-bottom-right-radius: rpx(5);
  1261. color: white;
  1262. font-size: rpx(9);
  1263. }
  1264. }
  1265. /* 定义淡入和缩放动画 */
  1266. .fade-scale-enter-active,
  1267. .fade-scale-leave-active {
  1268. transition: all 0.5s ease;
  1269. }
  1270. .fade-scale-enter-from,
  1271. .fade-scale-leave-to {
  1272. opacity: 0.1;
  1273. transform: scale(0.9);
  1274. }
  1275. // 自定义试题弹框背景
  1276. .child-dialog-wrapper {
  1277. position: fixed;
  1278. top: 0;
  1279. left: 0;
  1280. right: 0;
  1281. bottom: 0;
  1282. background-color: rgba(0, 0, 0, 0.6); // 半透明背景
  1283. display: flex;
  1284. justify-content: center;
  1285. align-items: center;
  1286. z-index: 1000;
  1287. }
  1288. .child-dialog {
  1289. border: none;
  1290. border-radius: rpx(15);
  1291. background: rgb(255, 255, 255, 0.8); // 柔和的蓝紫色渐变
  1292. overflow: hidden;
  1293. padding: rpx(5);
  1294. // width: 40%;
  1295. // height: 60%;
  1296. position: relative;
  1297. }
  1298. // AI对话弹框样式
  1299. .ai-dialog-wrapper {
  1300. position: fixed;
  1301. top: 0;
  1302. left: 0;
  1303. right: 0;
  1304. bottom: 0;
  1305. display: flex;
  1306. justify-content: flex-end;
  1307. align-items: center;
  1308. z-index: 1001;
  1309. pointer-events: none;
  1310. }
  1311. .ai-dialog {
  1312. border: none;
  1313. border-radius: rpx(15);
  1314. background: rgb(255, 255, 255, 0.8);
  1315. overflow: hidden;
  1316. padding: rpx(20);
  1317. width: 30%;
  1318. // 增加高度
  1319. height: 80%;
  1320. margin-right: rpx(50);
  1321. pointer-events: auto;
  1322. position: relative;
  1323. display: flex;
  1324. flex-direction: column;
  1325. &::before {
  1326. content: '';
  1327. position: absolute;
  1328. top: 0;
  1329. left: 0;
  1330. width: 100%;
  1331. height: rpx(10);
  1332. }
  1333. }
  1334. .ai-dialog-header {
  1335. position: relative;
  1336. display: flex;
  1337. justify-content: center;
  1338. align-items: center;
  1339. margin-bottom: rpx(15);
  1340. margin-top: rpx(-10);
  1341. img {
  1342. width: rpx(15);
  1343. }
  1344. h3 {
  1345. color: black;
  1346. font-size: rpx(12);
  1347. }
  1348. .close-btn {
  1349. padding: 0;
  1350. width: rpx(20);
  1351. height: rpx(20);
  1352. font-size: rpx(16);
  1353. line-height: 1;
  1354. position: absolute;
  1355. background-color: transparent;
  1356. border: none;
  1357. margin-left: rpx(200);
  1358. }
  1359. }
  1360. .ai-dialog-content {
  1361. flex: 1;
  1362. display: flex;
  1363. flex-direction: column;
  1364. }
  1365. .ai-message-history {
  1366. flex: 1;
  1367. // 当内容超出容器高度时,显示垂直滚动条
  1368. overflow-y: auto;
  1369. margin-bottom: rpx(15);
  1370. // 可以根据实际情况调整最大高度
  1371. font-size: rpx(5);
  1372. max-height: 50vh;
  1373. // padding: 5px 10px;
  1374. .message {
  1375. display: flex;
  1376. align-items: flex-start;
  1377. margin-bottom: rpx(10);
  1378. &.user {
  1379. flex-direction: row-reverse;
  1380. }
  1381. .avatar {
  1382. width: rpx(30);
  1383. height: rpx(30);
  1384. border-radius: 50%;
  1385. margin: 0 rpx(10);
  1386. }
  1387. .user {
  1388. width: 15px;
  1389. height: 15px;
  1390. }
  1391. .message-content {
  1392. background-color: #ffffff;
  1393. font-size: rpx(5);
  1394. max-width: 80%;
  1395. color: black;
  1396. box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
  1397. }
  1398. }
  1399. // 滚动条整体样式
  1400. &::-webkit-scrollbar {
  1401. width: rpx(4); // 滚动条宽度
  1402. }
  1403. // 滚动条滑块样式
  1404. &::-webkit-scrollbar-thumb {
  1405. background-color: $primary-color; // 滑块颜色
  1406. border-radius: rpx(4); // 滑块圆角
  1407. transition: background-color 0.3s ease; // 颜色过渡效果
  1408. }
  1409. // 滚动条轨道样式
  1410. &::-webkit-scrollbar-track {
  1411. background-color: rgba($primary-color, 0.2); // 轨道颜色
  1412. border-radius: rpx(4); // 轨道圆角
  1413. }
  1414. .message {
  1415. display: flex;
  1416. align-items: flex-start;
  1417. margin-bottom: rpx(10);
  1418. &.user {
  1419. flex-direction: row-reverse;
  1420. }
  1421. .avatar {
  1422. width: rpx(30);
  1423. height: rpx(30);
  1424. border-radius: 50%;
  1425. margin: 0 rpx(10);
  1426. }
  1427. .message-content {
  1428. background-color: white;
  1429. padding: rpx(8) rpx(12);
  1430. border-radius: rpx(5);
  1431. font-size: rpx(8);
  1432. color: black;
  1433. max-width: 80%;
  1434. box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
  1435. }
  1436. }
  1437. }
  1438. </style>
  1439. <style lang="scss">
  1440. // 搜索下拉框样式
  1441. @use 'sass:math';
  1442. // 定义rpx转换函数
  1443. @function rpx($px) {
  1444. @return math.div($px, 750) * 100vw;
  1445. }
  1446. /* 消除小三角 */
  1447. .el-popper__arrow {
  1448. display: none;
  1449. }
  1450. .el-popper.is-light,
  1451. .el-dropdown__popper.el-popper {
  1452. background: transparent;
  1453. border: none;
  1454. box-shadow: none;
  1455. }
  1456. .el-dropdown__popper {
  1457. --el-dropdown-menuItem-hover-color: none;
  1458. }
  1459. .el-autocomplete-suggestion .el-scrollbar__wrap {
  1460. margin: 0 auto;
  1461. background-color: rgba(255, 255, 255, 0.7);
  1462. border: 2px solid white;
  1463. border-radius: rpx(5);
  1464. backdrop-filter: blur(rpx(5));
  1465. }
  1466. .el-autocomplete-suggestion li {
  1467. color: black;
  1468. font-size: rpx(7);
  1469. padding: rpx(5) rpx(8); // 调整下拉项内边距
  1470. }
  1471. .el-autocomplete-suggestion li:hover {
  1472. background: linear-gradient(to bottom, #ffefb0, #ffcc00);
  1473. }
  1474. </style>
  1475. <style scoped lang="scss">
  1476. @use 'sass:math';
  1477. // 定义rpx转换函数
  1478. @function rpx($px) {
  1479. @return math.div($px, 750) * 100vw;
  1480. }
  1481. .default-messages {
  1482. margin-top: rpx(-10);
  1483. margin-bottom: rpx(5);
  1484. }
  1485. .contentClass{
  1486. width: 70%;
  1487. height: 80vh;
  1488. margin: 0 auto;
  1489. border-radius: rpx(15);
  1490. overflow: auto;
  1491. }
  1492. </style>