AIDevelop.vue 41 KB

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