HomePage.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. <template>
  2. <div class="home-container">
  3. <div class="box-1">
  4. <div class="inner-box left-box">
  5. <span>{{ platformTitle }}</span>
  6. <div class="dropdown-box">
  7. <!-- 下拉菜单 -->
  8. <el-dropdown v-model="selectedGrade" @command="handleGradeSelect" @visible-change="handleVisibleChange" popper-class="no-arrow-dropdown">
  9. <el-button type="primary">
  10. {{ selectedGrade }}
  11. <!-- 根据下拉框状态显示不同的箭头图标 -->
  12. <el-icon class="el-icon--right" v-if="!dropdownVisible"><ArrowDownBold /></el-icon>
  13. <el-icon class="el-icon--right" v-else><ArrowUpBold /></el-icon>
  14. </el-button>
  15. <template #dropdown>
  16. <el-dropdown-menu class="dropdown-menu">
  17. <el-dropdown-item
  18. v-for="item in classData"
  19. :key="item.id"
  20. :command="item.ctType"
  21. >{{ item.ctType }}</el-dropdown-item
  22. >
  23. </el-dropdown-menu>
  24. </template>
  25. </el-dropdown>
  26. </div>
  27. </div>
  28. <div class="inner-box right-box">
  29. <div class="top-right-box">
  30. <!-- 动态渲染按钮 -->
  31. <el-button
  32. v-for="button in buttonConfigs"
  33. :key="button.name"
  34. round
  35. class="top-right-btn"
  36. :class="{ 'is-active': selectedButton === button.name }"
  37. @click="handleButtonClick(button)"
  38. >{{ button.name }}</el-button
  39. >
  40. <!-- 用户名显示 -->
  41. <UserInfoPopover />
  42. </div>
  43. </div>
  44. </div>
  45. <div class="box-2">
  46. <div
  47. class="left-box-in-box2"
  48. @click="goToAIGeneralCourse('AI智能课')"
  49. :style="{ backgroundImage: `url(${indexImages[0]})` }"
  50. >
  51. <span>智能课</span>
  52. </div>
  53. <div
  54. class="center-box-in-box2"
  55. @click="goToAILab()"
  56. :style="{ backgroundImage: `url(${indexImages[1]})` }"
  57. >
  58. <span>AI实验室</span>
  59. </div>
  60. <div class="right-box-in-box2">
  61. <div
  62. class="top-sub-box"
  63. :style="{ backgroundImage: `url(${indexImages[2]})` }"
  64. @click="goToEvaluation"
  65. >
  66. <span>能力测评</span>
  67. </div>
  68. <div
  69. class="bottom-sub-box"
  70. :style="{ backgroundImage: `url(${indexImages[3]})` }"
  71. @click="goToPersonalized"
  72. >
  73. <span>评估报告</span>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. </template>
  79. <script setup>
  80. import { ref, onMounted, watch } from 'vue'
  81. import { useRouter } from 'vue-router'
  82. import { ClassList } from '@/api/class.js'
  83. import {ArrowUpBold, ArrowDownBold} from '@element-plus/icons-vue'
  84. import { ElMessage } from 'element-plus'
  85. import UserInfoPopover from '@/components/user/UserInfoPopover.vue'
  86. // 导入图片
  87. import intelligenceImg from '@/assets/images/intelligence.png'
  88. import roomImg from '@/assets/images/room.png'
  89. import testImg from '@/assets/images/test.png'
  90. import studyImg from '@/assets/images/study.png'
  91. // 退出登录图标
  92. import logoutIcon from '@/assets/icon/logout.png'
  93. // 退出登录
  94. import {logoutLogic, removeLocalStorageKey} from '@/utils/loginUtils.js'
  95. import {aiCourseRoutes, blocklyRoutes, homeRoutes} from "@/router/index.js";
  96. // 平台标题响应式变量
  97. const platformTitle = ref(import.meta.env.VITE_APP_TITLE)
  98. // 更新平台标题
  99. const updatePlatformTitle = () => {
  100. platformTitle.value = localStorage.getItem('tenantName') || import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT
  101. }
  102. // 获取当前路由对象
  103. const router = useRouter()
  104. // 检查课程权限的通用函数
  105. const checkCoursePermission = (courseName) => {
  106. // 从本地存储获取课程权限配置
  107. const coursePermissionsStr = localStorage.getItem('coursePermissions');
  108. let hasPermission = true;
  109. if (coursePermissionsStr) {
  110. try {
  111. const coursePermissions = JSON.parse(coursePermissionsStr);
  112. // 获取对应课程的权限状态
  113. const permission = coursePermissions[courseName];
  114. // 检查是否无权限
  115. if (permission === '无权限') {
  116. hasPermission = false;
  117. }
  118. } catch (error) {
  119. console.error('解析课程权限配置失败:', error);
  120. }
  121. }
  122. return hasPermission;
  123. }
  124. // 处理按钮点击事件
  125. const handleButtonClick = (button) => {
  126. // 检查课程权限
  127. const hasPermission = checkCoursePermission(button.name);
  128. if (!hasPermission) {
  129. // 无权限时弹框提示
  130. ElMessage.warning('未激活课程,无法访问');
  131. return;
  132. }
  133. selectedButton.value = button.name
  134. router.push(button.route)
  135. }
  136. // 按钮配置数组
  137. const buttonConfigs = ref([
  138. { name: 'AI编程课', route: blocklyRoutes.home },
  139. { name: 'AI实验课', route: aiCourseRoutes.home }
  140. ])
  141. // 取消默认选中状态
  142. const selectedButton = ref('')
  143. // 图片路径
  144. const indexImages = ref([intelligenceImg, roomImg, testImg, studyImg])
  145. // 智能课
  146. const goToAIGeneralCourse = title => {
  147. // 检查课程权限
  148. const hasPermission = checkCoursePermission('课程权限');
  149. if (!hasPermission) {
  150. // 无权限时弹框提示
  151. ElMessage.warning('未激活课程,无法访问');
  152. return;
  153. }
  154. router.push({ path: '/ai-general-course', state: { title } })
  155. }
  156. //AI实验室
  157. const goToAILab = () => {
  158. router.push({
  159. path: '/ai-laboratory',
  160. // 跳转页面携带下拉菜单选中项的值
  161. state: { grade: selectedGrade.value }
  162. })
  163. }
  164. // 能力测评
  165. const goToEvaluation = () =>{
  166. router.push({
  167. path:'/evaluation'
  168. })
  169. }
  170. // 评估报告
  171. const goToPersonalized = () =>{
  172. router.push({
  173. path:'/personalized'
  174. })
  175. }
  176. // 下拉菜单选中项
  177. const selectedGrade = ref(localStorage.getItem('selectedGrade') || '')
  178. // 年级ID存储变量
  179. const selectedGradeId = ref(localStorage.getItem('selectedGradeId') || '')
  180. // 下拉框可见性状态
  181. const dropdownVisible = ref(false)
  182. // 获取年级
  183. const classData = ref([])
  184. // 处理下拉框显示/隐藏事件
  185. const handleVisibleChange = (visible) => {
  186. dropdownVisible.value = visible
  187. }
  188. const fetchCtTypes = async () => {
  189. try {
  190. const response = await ClassList()
  191. if (response.code === 0) {
  192. classData.value = response.data
  193. // 获取到数据,将第一个选项的值作为默认选中值
  194. if (classData.value.length > 0 && !selectedGrade.value) {
  195. selectedGrade.value = classData.value[0].ctType
  196. selectedGradeId.value = classData.value[0].id
  197. localStorage.setItem('selectedGrade', selectedGrade.value)
  198. localStorage.setItem('selectedGradeId', selectedGradeId.value)
  199. }
  200. }
  201. } catch (error) {
  202. console.error('获取 ctType 数据失败:', error)
  203. }
  204. }
  205. // 监听 selectedGrade 变化,保存到 localStorage
  206. watch(selectedGrade, (newValue) => {
  207. if (newValue) {
  208. localStorage.setItem('selectedGrade', newValue)
  209. // 当年级名称变化时,查找对应的ID
  210. const selectedItem = classData.value.find(item => item.ctType === newValue)
  211. if (selectedItem) {
  212. selectedGradeId.value = selectedItem.id
  213. localStorage.setItem('selectedGradeId', selectedGradeId.value)
  214. }
  215. }
  216. })
  217. // 监听 selectedGradeId 变化,保存到 localStorage
  218. watch(selectedGradeId, (newValue) => {
  219. if (newValue) {
  220. localStorage.setItem('selectedGradeId', newValue)
  221. }
  222. })
  223. // 处理下拉菜单选择
  224. const handleGradeSelect = (command) => {
  225. selectedGrade.value = command
  226. // 查找对应的ID
  227. const selectedItem = classData.value.find(item => item.ctType === command)
  228. if (selectedItem) {
  229. selectedGradeId.value = selectedItem.id
  230. }
  231. }
  232. onMounted(() => {
  233. fetchCtTypes()
  234. // 初始化平台标题
  235. updatePlatformTitle()
  236. // storage事件监听器,监听其他标签页对localStorage的修改
  237. window.addEventListener('storage', (e) => {
  238. if (e.key === 'tenantName') {
  239. updatePlatformTitle()
  240. }
  241. })
  242. //删除所有以token开头的键值对
  243. removeLocalStorageKey(localStorage.getItem("token") + "_ai_")//AI实验室
  244. removeLocalStorageKey(localStorage.getItem('token') + "_course_")//通识课、实操课
  245. removeLocalStorageKey(localStorage.getItem("token") + "_blockly_")//编程课
  246. removeLocalStorageKey(localStorage.getItem("token") + "_aiCourse_")//AI实验课
  247. })
  248. // 全局:更新租户名称
  249. window.updateTenantName = (newName) => {
  250. localStorage.setItem('tenantName', newName)
  251. updatePlatformTitle()
  252. }
  253. </script>
  254. <style scoped lang="scss">
  255. @use 'sass:math';
  256. // 定义rpx转换函数
  257. @function rpx($px) {
  258. @return math.div($px, 750) * 100vw;
  259. }
  260. .home-container {
  261. position: fixed;
  262. top: 0;
  263. left: 0;
  264. right: 0;
  265. bottom: 0;
  266. background: linear-gradient(to bottom, #001169, #8a78d0);
  267. display: flex;
  268. flex-direction: column;
  269. gap: rpx(0);
  270. }
  271. .box-1 {
  272. width: 100%;
  273. min-height: rpx(50);
  274. display: flex;
  275. justify-content: space-between;
  276. align-items: center;
  277. flex-wrap: wrap;
  278. box-sizing: border-box;
  279. font-size: rpx(16); // 默认字体大小
  280. }
  281. .box-2 {
  282. width: 90%;
  283. margin: auto;
  284. flex: 1;
  285. display: flex;
  286. justify-content: space-between;
  287. align-items: center;
  288. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
  289. box-sizing: border-box;
  290. // padding: 0 rpx(20); // 添加左右内边距
  291. cursor: pointer; // 添加鼠标指针样式
  292. }
  293. .box-2 span {
  294. // 添加 padding,使文字距上边和左边留有间距
  295. padding: rpx(10) 0 0 rpx(10);
  296. font-size: rpx(12); // 默认字体大小
  297. color: white;
  298. }
  299. .left-box-in-box2,
  300. .center-box-in-box2 {
  301. background-color: rgba(133, 135, 176, 0.5);
  302. border-radius: rpx(20);
  303. flex: 1; // 让三个盒子平均分配空间
  304. margin: 0 rpx(10); // 添加左右间距
  305. height: 85%; // 高度占满容器
  306. display: flex;
  307. justify-content: flex-start;
  308. align-items: flex-start;
  309. background-origin: border-box; // 确保背景图从边框开始显示
  310. background-clip: padding-box; // 确保背景图不会延伸到边框外
  311. }
  312. .left-box-in-box2:hover,
  313. .left-box-in-box2:active,
  314. .center-box-in-box2:hover,
  315. .center-box-in-box2:active {
  316. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
  317. }
  318. .left-box-in-box2,
  319. .center-box-in-box2,
  320. .top-sub-box,
  321. .bottom-sub-box {
  322. background-repeat: no-repeat;
  323. background-size: cover;
  324. background-position: center;
  325. }
  326. .right-box-in-box2 {
  327. flex: 1; // 让三个盒子平均分配空间
  328. margin: 0 rpx(10); // 添加左右间距
  329. height: 85%; // 高度占满容器
  330. display: flex;
  331. // 确保两个子盒子上下排列
  332. flex-direction: column;
  333. justify-content: flex-start;
  334. align-items: flex-start;
  335. gap: rpx(10);
  336. }
  337. .top-sub-box,
  338. .bottom-sub-box {
  339. background-color: rgba(133, 135, 176, 0.5);
  340. flex: 1; // 让两个子盒子平均分配空间;
  341. display: flex;
  342. border-radius: rpx(20);
  343. width: 100%; // 宽度占满容器;
  344. }
  345. .top-sub-box:hover,
  346. .top-sub-box:active,
  347. .bottom-sub-box:hover,
  348. .bottom-sub-box:active {
  349. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
  350. }
  351. .inner-box {
  352. height: 100%;
  353. display: flex;
  354. justify-content: center;
  355. align-items: center;
  356. font-size: rpx(16); // 默认字体大小
  357. }
  358. .left-box {
  359. position: relative;
  360. justify-content: flex-start;
  361. align-items: center;
  362. display: flex;
  363. flex: 1;
  364. padding-left: rpx(30);
  365. cursor: pointer;
  366. }
  367. .left-box span {
  368. position: static;
  369. margin-top: 0;
  370. margin-left: 0;
  371. margin-right: rpx(10); // 与下拉菜单之间的间距
  372. font-size: rpx(11);
  373. color: white;
  374. max-width: rpx(200); // 最大宽度限制
  375. white-space: normal; // 允许换行
  376. line-height: rpx(16); // 行高
  377. text-align: left;
  378. }
  379. .right-box {
  380. flex: 1;
  381. display: flex;
  382. justify-content: flex-end;
  383. align-items: center;
  384. margin-right: rpx(60);
  385. }
  386. .top-right-box {
  387. width: 100%;
  388. display: flex;
  389. justify-content: flex-end;
  390. align-items: center;
  391. flex-wrap: wrap;
  392. cursor: pointer; // 添加鼠标指针样式
  393. }
  394. .top-right-btn {
  395. width: rpx(50); // 使用 rpx 函数设置按钮宽度
  396. height: rpx(15); // 使用 rpx 函数设置按钮高度
  397. margin: rpx(10) rpx(5) 0 0; // 使用 rpx 函数设置外边距
  398. background-color: transparent;
  399. color: white;
  400. border: none; // 移除默认边框
  401. font-size: rpx(8); // 使用 rpx 函数设置字体大小
  402. outline: none; // 移除默认的外边框
  403. }
  404. .top-right-btn:hover {
  405. background-color: rgb(255, 255, 255, 0.7);
  406. border: 1px white solid;
  407. color: black;
  408. outline: none;
  409. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
  410. }
  411. .dropdown-box {
  412. align-items: center; // 垂直居中;
  413. margin-top: 0;
  414. display: flex;
  415. }
  416. .dropdown-box .el-button {
  417. width: rpx(60); // 设置按钮宽度;
  418. height: rpx(15); // 设置按钮高度;
  419. background-color: rgb(255, 255, 255, 0.7);
  420. border: 1px white solid;
  421. box-shadow: 0 4px 8px rgb(0, 0, 0, 0.3);
  422. color: black;
  423. border-radius: rpx(12);
  424. font-size: rpx(8); // 设置字体大小;
  425. }
  426. .dropdown-box .el-button:hover,
  427. .dropdown-box .el-button:focus,
  428. .dropdown-box .el-button:active {
  429. border: none; /* 移除悬停、聚焦、点击状态下的边框 */
  430. outline: none; /* 移除悬停、聚焦、点击状态下的轮廓线 el-scrollbar__view el-dropdown__list */
  431. }
  432. .dropdown-menu {
  433. width: rpx(100);
  434. // height: rpx(60);
  435. border-radius: rpx(5);
  436. border: 1px white solid;
  437. background-color: rgb(255, 255, 255,0.5);
  438. backdrop-filter: blur(rpx(5));
  439. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
  440. margin-left: rpx(40);
  441. }
  442. .el-scrollbar__view .el-dropdown__list{
  443. background-color: transparent;
  444. }
  445. .dropdown-menu ::v-deep(.el-dropdown-menu__item) {
  446. font-size: rpx(8);
  447. color: black;
  448. border-radius: rpx(5);
  449. width: rpx(78);
  450. height: rpx(20);
  451. margin-left: rpx(4);
  452. margin-bottom: rpx(8);
  453. }
  454. .dropdown-menu ::v-deep(.el-dropdown-menu__item:hover),
  455. .dropdown-menu ::v-deep(.el-dropdown-menu__item:focus),
  456. .dropdown-menu ::v-deep(.el-dropdown-menu__item:active) {
  457. background: linear-gradient(
  458. to bottom,
  459. #fee78a,
  460. #ffce1b
  461. );
  462. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
  463. }
  464. </style>
  465. <style lang="scss">
  466. /* 只消除非user-name-box的小三角 */
  467. .no-arrow-dropdown .el-popper__arrow{
  468. display: none;
  469. }
  470. .el-popper.is-light,
  471. .el-dropdown__popper.el-popper{
  472. background: transparent;
  473. border: none;
  474. box-shadow: none;
  475. }
  476. .el-dropdown__popper{
  477. --el-dropdown-menuItem-hover-color: none;
  478. }
  479. /* 移除用户名下拉菜单的焦点边框 */
  480. .user-name-box:focus,
  481. .user-name-box:focus-within,
  482. .user-name-box:hover{
  483. outline: none;
  484. box-shadow: none;
  485. }
  486. /* 确保Element Plus下拉菜单触发元素没有焦点边框 */
  487. .el-dropdown .el-dropdown__trigger:focus{
  488. outline: none;
  489. box-shadow: none;
  490. }
  491. </style>