فهرست منبع

feat:ui尝试

BaiQi 1 سال پیش
والد
کامیت
e2e81d3322
3فایلهای تغییر یافته به همراه1142 افزوده شده و 0 حذف شده
  1. 338 0
      src/components/GlobalHeader/NewGlobalHeader.vue
  2. 145 0
      src/views/PortalPage/NewPortalPage.css
  3. 659 0
      src/views/PortalPage/NewPortalPage.vue

+ 338 - 0
src/components/GlobalHeader/NewGlobalHeader.vue

@@ -0,0 +1,338 @@
+<template>
+  <div class="global-header-body">
+    <el-backtop/>
+    <img id="global-header-logo" alt="err" src="@/assets/seec-logo--full.png" @click="toPortal">
+    <el-menu :default-active="activeMenu" mode="horizontal" @select="handleMenuSelect" id="menu">
+      <el-menu-item index="1">
+        首页
+      </el-menu-item>
+      <!--      <el-menu-item v-if="isStudent" index="2">-->
+      <!--        任务-->
+      <!--      </el-menu-item>-->
+      <el-menu-item index="3">
+        课程
+      </el-menu-item>
+      <el-menu-item index="5">
+        任务
+      </el-menu-item>
+      <!--<el-menu-item index="4">-->
+      <!--  项目-->
+      <!--</el-menu-item>-->
+      <el-menu-item v-if="isTeacher" index="7">
+        管理
+      </el-menu-item>
+        <el-menu-item v-if="isTeacher" index="10">
+            交流
+        </el-menu-item>
+      <el-menu-item index="6">
+        个人中心
+      </el-menu-item>
+    </el-menu>
+    <div id="global-header-icon">
+      <el-tooltip content="前往通知页" placement="bottom" effect="light" :show-after="1000">
+        <div class="global-header-icon-content" @click="toMessage" style="position: relative">
+          <sup v-if="unreadNum !== '0'" class="reminder">
+            {{ unreadNum }}
+          </sup>
+          <svg height="32" viewBox="0 0 1024 1024" width="32" xmlns="http://www.w3.org/2000/svg">
+            <path
+              d="M849.6 713.4c8.4-0.4 16.7 1.8 23.8 6.4-28.9-18.5-46.4-50.4-46.3-84.8V436c0-145.9-105.7-267.6-246.9-297.5v-7.2c0-37.1-30.1-67.1-67.2-67.1h-0.1c-37.1 0-67.3 30.1-67.3 67.2v7C304.3 168.4 198.7 290 198.7 436v199.1c0 35.7-18.6 66.9-46.4 84.8 7-4.6 15.4-6.9 23.8-6.4-24.7-0.9-45.5 18.4-46.4 43.1-0.9 24.7 18.4 45.5 43.1 46.4h676.6c24.7 0.1 44.8-20 44.9-44.7 0.1-24.8-19.9-44.9-44.7-44.9zM513 959.8c62 0 112.2-50.2 112.2-111.9H400.7c0 61.7 50.2 111.9 112.3 111.9z"
+              fill="#E8AA5B"></path>
+          </svg>
+        </div>
+      </el-tooltip>
+      <el-tooltip content="前往个人中心页" placement="right" effect="light" :show-after="1000">
+        <div class="global-header-icon-content" @click="toHome">
+          <ElPopover trigger="hover">
+            <template #reference>
+              <div v-if="isLogin" id="info-circle">
+              <span>
+                {{ singleName }}
+              </span>
+              </div>
+              <div v-else>
+                <svg height="32" viewBox="0 0 1025 1024" width="32" xmlns="http://www.w3.org/2000/svg">
+                  <path
+                    d="M511.999723 331.913794m-117.748854 0a117.748854 117.748854 0 1 0 235.497708 0 117.748854 117.748854 0 1 0-235.497708 0Z"
+                    fill="#82BAD8"></path>
+                  <path
+                    d="M511.999723 0.000554A511.999723 511.999723 0 1 0 1025.10767 512.000277 512.276779 512.276779 0 0 0 511.999723 0.000554zM792.380524 779.636496a24.935051 24.935051 0 0 1-24.935052-24.935052
+                  255.445749 255.445749 0 0 0-510.614442 0 24.935051 24.935051 0 0 1-49.870103 0 304.76174 304.76174 0 0 1 205.29859-288.138372 167.896013 167.896013 0 1 1 199.480412 0 304.76174 304.76174 0 0 1 205.575646 288.138372 24.935051 24.935051 0 0 1-24.935051 24.935052z"
+                    fill="#82BAD8"></path>
+                </svg>
+              </div>
+            </template>
+            <InfoCard/>
+          </ElPopover>
+        </div>
+      </el-tooltip>
+    </div>
+  </div>
+</template>
+
+<script>
+import InfoCard from "@/components/GlobalHeader/InfoCard.vue";
+
+export default {
+  name: "GlobalHeader",
+  components: {InfoCard},
+  data() {
+    return {
+      menuIndex: [
+        ["1", "Portal"],
+        ["2", "Task"],
+        ["3", "Course"],
+        ["4", "Project"],
+        ["5", "Test"],
+        ["6", "Home"],
+        ["7", "Manage"],
+        ["8", "Problem"],
+        ["9", "CourseDetail"],
+        ["10", "Communication"]
+      ]
+    };
+  },
+  computed: {
+    isLogin() {
+      return this.$store.getters.isLogin;
+    },
+    name() {
+      return this.$store.state.user.name || "无名氏";
+    },
+    singleName() {
+      return this.name.substring(0, 1);
+    },
+    isTeacher() {
+      return this.$store.getters.isTeacher;
+    },
+    isStudent() {
+      return this.$store.getters.isStudent;
+    },
+    activeMenu() {
+      let out = "1";
+      this.menuIndex.forEach(i => {
+        if (i[1] === this.$route.name) {
+          out = i[0];
+        }
+      });
+      return out;
+    },
+    unReadMessages() {
+      return this.$store.getters.unReadMessages;
+    },
+    unReadAnnouncements() {
+      return this.$store.getters.unReadAnnouncements;
+    },
+    unReadInvitations() {
+      return this.$store.getters.unReadInvitations;
+    },
+    unreadNum() {
+      const x = this.unReadAnnouncements === undefined ? 0 : this.unReadAnnouncements.length;
+      const y = this.unReadMessages === undefined ? 0 : this.unReadMessages.length;
+      const z = this.unReadInvitations === undefined ? 0 : this.unReadInvitations.length;
+      return x + y + z > 99 ? "99+" : x + y + z + "";
+    },
+  },
+
+  watch: {
+    isLogin(newData) {
+      if (newData) {
+        this.$store.dispatch("fetchMessages", this);
+      }
+    },
+    $route() {
+      if (this.isLogin) {
+        this.$store.dispatch("fetchMessages", this);
+      }
+    }
+  },
+
+  created() {
+    if (this.isLogin) {
+      this.$store.dispatch("fetchMessages", this);
+    }
+  },
+
+  methods: {
+    toPortal() {
+      this.$router.push("/portal");
+    },
+    toHome() {
+      this.$router.push("/home");
+    },
+    toMessage() {
+      this.$router.push("/message");
+    },
+    handleMenuSelect(key) {
+      let target = "/portal";
+      this.menuIndex.forEach(i => {
+        if (i[0] === key) {
+          target = i[1];
+        }
+      });
+      this.$router.push({name: target});
+    },
+  },
+};
+</script>
+
+<style scoped>
+/* 自定义赛博朋克边框动画 */
+.cyber-border {
+  position: relative;
+  overflow: hidden;
+}
+
+.cyber-border::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: -100%;
+  width: 100%;
+  height: 2px;
+  background: linear-gradient(
+      90deg,
+      transparent,
+      #71bbdb,
+      #f3a64a,
+      #50a879,
+      transparent
+  );
+  animation: borderFlow 3s linear infinite;
+}
+
+@keyframes borderFlow {
+  0% { left: -100%; }
+  100% { left: 100%; }
+}
+
+/* 重新实现导航栏背景颜色以及动画 */
+@keyframes GradientFlow {
+  0% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0% 50%;
+  }
+}
+
+.global-header-body {
+  width: calc(100% - 2px);
+  height: 80px;
+  position: sticky;
+  top: 0;
+  z-index: 999;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  border: 1px solid #e4e7ed;
+  background: linear-gradient(
+      135deg,
+      #f3a64a,  /* 橙色 */
+      #3a85bb,  /* 天蓝色 */
+      #50a879,  /* 绿色 */
+      #70bbdb   /* 海蓝色 */
+  );
+  background-size: 400% 400%;
+  animation: GradientFlow 8s ease infinite;
+  transition: all 0.5s ease;
+}
+
+#global-header-logo {
+  height: 42px;
+  margin-left: 150px;
+  cursor: pointer;
+}
+
+:deep(.el-menu--horizontal) {
+  height: 80px;
+}
+
+:deep(.el-menu--horizontal>.el-submenu .el-submenu__title) {
+  height: 80px;
+  position: relative;
+  /*bottom: 2px;*/
+}
+
+:deep(.el-menu--horizontal>.el-menu-item) {
+  height: 80px;
+  position: relative;
+  line-height: 80px;
+  /*bottom: 2px;*/
+}
+
+#menu{
+  background-color: rgba(0, 0, 0, 0%);
+}
+
+#menu li {
+  color: white;
+  border-bottom: none;
+  font-size: 20px;
+}
+
+#menu li:hover {
+  background: rgba(113, 187, 219, 0.15);
+  /*color: white;*/
+}
+
+#menu li:visited {
+  background: rgba(113, 187, 219, 0.15);
+  /*color: white;*/
+}
+
+
+#global-header-icon {
+  width: 120px;
+  margin-right: 150px;
+  display: flex;
+  justify-content: space-around;
+}
+
+.global-header-icon-content {
+  cursor: pointer;
+}
+
+#info-circle {
+  height: 32px;
+  width: 32px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  background: #619dc9;
+}
+
+#info-circle span {
+  color: white;
+  font-weight: bold;
+}
+
+.reminder {
+  position: absolute;
+  top: 0;
+  right: 0;
+  transform: translateY(-25%) translateX(35%);
+  background-color: #f56c6c;
+  border-radius: 10px;
+  color: #fff;
+  display: inline-block;
+  font-size: 12px;
+  height: 18px;
+  line-height: 18px;
+  padding: 0 6px;
+  text-align: center;
+  white-space: nowrap;
+  border: 1px solid #fff;
+}
+
+/* 使用深度选择器穿透scoped限制 */
+::v-deep(.el-menu--horizontal > .el-menu-item:not(.is-disabled):focus),
+::v-deep(.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover) {
+  background-color: rgba(113, 187, 219, 0.15) !important;
+}
+</style>

+ 145 - 0
src/views/PortalPage/NewPortalPage.css

@@ -0,0 +1,145 @@
+#portal-body {
+    height: 100%;
+    display: grid;
+    grid-template-columns: 1fr 2.5fr;
+    grid-template-rows: 210px auto auto;
+    grid-gap: 12px 12px;
+    padding: 10px 10vw 80px 10vw;
+}
+
+.portal-card-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 40px;
+}
+
+.portal-card-title-main {
+    font-weight: bold;
+    font-size: larger;
+}
+
+:deep(.el-card__body) {
+    padding-top: 8px;
+    padding-bottom: 8px;
+}
+
+:deep(.portal-card-divider) {
+    margin: 8px 0 8px 0;
+}
+
+.portal-card-main {
+    display: flex;
+    flex-direction: column;
+    overflow: scroll;
+    align-items: center;
+}
+
+.portal-card-main::-webkit-scrollbar {
+    display: none;
+}
+
+.portal-my-card-main {
+    display: grid;
+    grid-template-columns: 1fr;
+    min-width: calc(max(100%, 250px));
+}
+
+.portal-card-content {
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr;
+    grid-gap: 20px;
+}
+
+.tip {
+    font-size: small;
+    font-weight: bold;
+    color: #666666;
+}
+
+/* 容器样式 */
+.good-course-title {
+    display: flex;
+    flex-direction: column;
+    align-items: center;  /* 关键居中属性 */
+    width: 100%;
+}
+
+/* 动态标题容器 */
+.dynamic-header {
+    margin-bottom: 4rem;
+    position: relative;
+}
+
+
+/* 渐变标题样式 */
+.gradient-heading {
+    font-size: 3rem;
+    font-weight: 800;
+    color: #0a1929;
+    margin-bottom: 1rem;
+    position: relative;
+    z-index: 10;
+}
+
+.gradient-text {
+    background: linear-gradient(to right, #f3a64a, #71bbdb);
+    -webkit-background-clip: text;
+    background-clip: text;
+    color: transparent;
+    text-align: center;
+}
+
+/* 标题下划线 */
+.title-underline {
+    position: absolute;
+    bottom: -1rem;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 12rem;
+    height: 2px;
+    background: linear-gradient(to right, #f3a64a, #71bbdb);
+    border-radius: 999px;
+}
+
+/* 动态副标题 */
+.animated-subtitle {
+    margin-top: 2rem;
+    font-size: 1.5rem;
+    color: #71bbdb;
+}
+
+.subtitle-gradient {
+    background: linear-gradient(to right, #71bbdb, #50a879);
+    -webkit-background-clip: text;
+    background-clip: text;
+    color: transparent;
+    font-weight: 500;
+    animation: pulse 2s infinite;
+    text-align: center;
+}
+
+/* 脉冲动画 */
+@keyframes pulse {
+    0%, 100% {
+        opacity: 1;
+    }
+    50% {
+        opacity: 0.5;
+    }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+    .gradient-heading {
+        font-size: 2rem;
+    }
+
+    .title-underline {
+        width: 8rem;
+    }
+
+    .animated-subtitle {
+        font-size: 1rem;
+    }
+}

+ 659 - 0
src/views/PortalPage/NewPortalPage.vue

@@ -0,0 +1,659 @@
+<template>
+  <!-- 新增固定背景层 -->
+  <div class="fixed-grid-bg"></div>
+
+  <div
+      style="height: calc(100vh - 80px); color: #71bbdb; margin-bottom: 0"
+      class="content-wrapper"
+  >
+    <PhotoChange></PhotoChange>
+    <div class="good-course-title">
+      <el-row class="dynamic-header">
+        <!-- 背景渐变层 -->
+        <div class="header-backdrop"></div>
+
+        <!-- 主标题 -->
+        <el-col :span="24" class="text-center">
+          <h2 class="gradient-heading">
+            <span class="gradient-text">SEE精选课程</span>
+            <div class="title-underline"></div>
+          </h2>
+
+          <!-- 动态副标题 -->
+          <div class="animated-subtitle">
+            <div class="subtitle-gradient">▼ 探索知识领域 ▼</div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <div class="course-container">
+      <!-- 课程列表容器 使用 Element Plus 的布局组件 -->
+      <el-row :gutter="20" >
+        <!-- 循环渲染课程卡片 -->
+        <el-col
+            v-for="(course, index) in courses"
+            :key="index"
+            :xs="24"
+            :sm="12"
+            :lg="8"
+        >
+          <!-- 课程卡片容器 -->
+          <div
+              class="course-card"
+              :style="{
+          '--main-color': course.color,
+          '--secondary-color': course.secondaryColor
+        }"
+          >
+            <!-- 动态光效层 -->
+            <div class="hover-glow"></div>
+
+            <!-- 右上角科技图标 -->
+            <div class="tech-icon">
+              <!--<el-icon :size="30" :color="course.color">-->
+                <!--<component :is="course.icon" />-->
+              <!--</el-icon>-->
+            </div>
+
+            <!-- 课程封面 -->
+            <div class="course-cover">
+              <div class="image-container">
+                <img
+                    :src="course.cover"
+                    alt="课程封面"
+                    class="cover-image"
+                    @load="handleImageLoad"
+                >
+                <div v-show="!isLoaded" class="image-slot">
+                  Loading<span class="dot">...</span>
+                </div>
+              </div>
+              <!--<el-image-->
+              <!--    :src="course.cover"-->
+              <!--    fit="cover"-->
+              <!--    class="cover-image"-->
+              <!--&gt;-->
+              <!--  <template #placeholder>-->
+              <!--    <div class="image-slot">Loading<span class="dot">...</span></div>-->
+              <!--  </template>-->
+              <!--</el-image>-->
+              <div class="cover-overlay"></div>
+            </div>
+
+            <!-- 课程内容 -->
+            <div class="course-content">
+              <!-- 类别标签 -->
+              <div class="flex-between">
+                <el-tag
+                    effect="dark"
+                    :color="hexToRgba(course.color, 0.1)"
+                    :style="{ color: course.color }"
+                >
+                  {{ course.category }}
+                </el-tag>
+                <!-- 评分 -->
+                <div class="rating">
+                  <el-rate
+                      v-model="course.rating"
+                      disabled
+                      :colors="[course.color]"
+                  />
+                  <span class="rating-text">{{ course.rating.toFixed(1) }}</span>
+                </div>
+              </div>
+
+              <!-- 课程标题 -->
+              <h3 class="course-title">{{ course.title }}</h3>
+
+              <!-- 课程描述 -->
+              <p class="course-desc">{{ course.description }}</p>
+
+              <!-- 底部信息 -->
+              <div class="flex-between footer">
+                <!-- 讲师信息 -->
+                <div class="lecturer">
+                  <el-avatar :size="36" :src="course.lecturer.avatar" />
+                  <span class="lecturer-name">{{ course.lecturer.name }}</span>
+                </div>
+
+                <!-- 开始学习按钮 -->
+                <el-button
+                    type="primary"
+                    class="study-btn"
+                    @click="showCourseDetail(course)"
+                >
+                  开始学习 →
+                  <template #icon>
+                    <el-icon><ArrowRight /></el-icon>
+                  </template>
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+// import CarouselCard from "@/views/PortalPage/components/CarouselCard";
+import CourseCard from "@/components/CourseCard";
+import MyCourseCard from "@/views/PortalPage/components/MyCourseCard";
+import MyTestCard from "@/views/PortalPage/components/MyTestCard";
+import {ArrowRight, FolderOpened, Lock, MagicStick, SetUp, ShoppingCart} from "@element-plus/icons-vue";
+import TestCard from "@/components/TestCard";
+import PhotoChange from "@/views/PortalPage/components/PhotoChange";
+import {ref} from "vue";
+
+export default {
+  //数据
+  name: "PortalPage",
+  components: {
+    ArrowRight,
+  },
+  data() {
+    return {
+      isExpanding: false,
+      activeTestPaneName: "eval",
+      exampleEvalTestList: [],
+      exampleCourseList: [],
+      showCourseList_current: 1,
+      exampleCoderTestList: [],
+      myCourseList: [],
+      myEvalExamList: [],
+      myCoderExamList: [],
+      courseRowHeight: 360,
+      testRowHeight: 300,
+    };
+  },
+  computed: {
+    isLogin() {
+      return this.$store.getters.isLogin;
+    },
+    isTeacher() {
+      return this.$store.getters.isTeacher;
+    },
+    isStudent() {
+      return this.$store.getters.isStudent;
+    },
+    isAdmin(){
+      return this.$store.state.user.role==="ADMIN";
+    },
+    myTestList() {
+      return []
+        .concat(this.myEvalExamList)
+        .concat(this.myCoderExamList)
+        .sort((a, b) => {
+          let a_time = a["startAt"] * 1000 || new Date(a.startTime).getTime();
+          let b_time = b["startAt"] * 1000 || new Date(b.startTime).getTime();
+          return a_time - b_time;
+        });
+    },
+    showExampleCourseList() {
+      return this.exampleCourseList.slice((this.showCourseList_current - 1) * 6, this.showCourseList_current * 6);
+    },
+    showExampleCoderTestList() {
+      return this.exampleCoderTestList
+        // .filter(i => this.state(i) === "RUNNING")
+        .slice(0, 6);
+    },
+    showExampleEvalTestList() {
+      return this.exampleEvalTestList
+        // .filter(i => this.state(i) === "RUNNING")
+        .slice(0, 6);
+    },
+  },
+
+  //生命周期函数
+  created() {
+    window.addEventListener("scroll", this.handleResize, true);
+  },
+  beforeUnmount() {
+    window.removeEventListener("scroll", this.handleResize,);
+  },
+  async mounted() {
+    const that = this;
+
+    this.exampleCourseList = (await this.$apis.getExampleCourses()).data.data;
+    this.exampleEvalTestList = (await this.$apis.getRecommendEvalExams()).data.data;
+    this.exampleCoderTestList = (await this.$apis.getRecommendCoderExams({})).data.data;
+
+    if (this.isLogin && this.isStudent) {
+      const res = await this.$apis.getStudentCourses();
+      console.log(res);
+      this.myCourseList = (await this.$apis.getStudentCourses()).data.data;
+      this.myEvalExamList = (await this.$apis.getStudentEvalExams()).data.data.filter(i => this.state(i) === "RUNNING");
+      this.myCoderExamList = (await this.$apis.getStudentCoderExams({})).data.data.res.filter(i => this.state(i) === "RUNNING");
+    }
+
+    await this.$nextTick(() => that.handleResize());
+  },
+
+  //其他函数
+  methods: {
+    toAuth() {
+      this.$router.push("/auth");
+    },
+    toCourse() {
+      this.$router.push("/course");
+    },
+    toManage() {
+      this.$router.push("/manage");
+    },
+    toTest() {
+      this.$router.push("/test");
+    },
+    handleResize() {
+      if (this.$route.path !== "/portal") return;
+      let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
+      if (scrollTop > 100) {
+        this.courseRowHeight = 640;
+        this.isExpanding = true;
+      }
+      if (this.$refs.portalTestCardBodyMain) {
+        this.testRowHeight = Math.max(this.$refs.portalTestCardBodyMain.$el.clientHeight, 300);
+      }
+    },
+    changeTestPane() {
+      const that = this;
+      this.$nextTick(() => that.handleResize());
+    },
+    state(testVO) {
+      const now = new Date();
+      if (testVO["examId"]) {
+        if (new Date(testVO.endTime) < now) {
+          return "FINISHED";
+        } else if (new Date(testVO.startTime) > now) {
+          return "NOT_STARTED";
+        } else {
+          return "RUNNING";
+        }
+      } else {
+        if (testVO.status === "CLOSED") {
+          return "CLOSED";
+        } else if (testVO["startAt"] * 1000 > now) {
+          return "NOT_STARTED";
+        } else if (testVO["endAt"] * 1000 < now) {
+          return "FINISHED";
+        } else {
+          return "RUNNING";
+        }
+      }
+    },
+    handleCourseCurrentChange(index) {
+      this.showCourseList_current = index;
+    }
+  }
+};
+</script>
+
+<script setup>
+// 课程数据
+import {Monitor, TrendCharts} from "@element-plus/icons-vue";
+import {ElMessage} from "element-plus";
+import {onMounted} from "vue";
+import {useApis} from "../../api";
+
+const apis = useApis();
+
+const courses = ref([
+  {
+    title: "全栈工程师实战",
+    category: "🚩 编程开发",
+    color: "#f3a64a",
+    secondaryColor: "#71bbdb",
+    icon: MagicStick,
+    cover: "https://picsum.photos/800/450?random=1&coding,tech,neon",
+    rating: 4.9,
+    description: "Node.js + React 全栈开发技术栈,构建企业级应用,掌握前后端分离开发模式,学习数据库设计与优化。",
+    lecturer: {
+      name: "王涛导师",
+      avatar: "https://i.pravatar.cc/40?img=1",
+      bio: "资深全栈工程师,10年开发经验,专注于Web开发与架构设计。"
+    },
+    duration: "36小时",
+    lessons: 120,
+    level: "中级",
+    price: 299,
+    isFeatured: true
+  },
+  {
+    title: "深度学习实战",
+    category: "🚀 人工智能",
+    color: "#71bbdb",
+    secondaryColor: "#50a879",
+    icon: Monitor,
+    cover: "https://picsum.photos/800/450?random=2&ai,tech,neon",
+    rating: 4.8,
+    description: "TensorFlow与PyTorch核心开发,构建智能推荐系统,学习神经网络、卷积神经网络和循环神经网络。",
+    lecturer: {
+      name: "李娜教授",
+      avatar: "https://i.pravatar.cc/40?img=2",
+      bio: "人工智能领域专家,专注于深度学习与计算机视觉。"
+    },
+    duration: "48小时",
+    lessons: 150,
+    level: "高级",
+    price: 399,
+    isFeatured: true
+  },
+  {
+    title: "大数据分析",
+    category: "📊 数据科学",
+    color: "#50a879",
+    secondaryColor: "#f3a64a",
+    icon: TrendCharts,
+    cover: "https://picsum.photos/800/450?random=3&data,tech,neon",
+    rating: 4.7,
+    description: "使用Python进行数据清洗、分析和可视化,学习Pandas、NumPy、Matplotlib等工具,掌握大数据处理技巧。",
+    lecturer: {
+      name: "陈伟分析师",
+      avatar: "https://i.pravatar.cc/40?img=3",
+      bio: "数据科学专家,擅长数据分析与机器学习。"
+    },
+    duration: "30小时",
+    lessons: 100,
+    level: "中级",
+    price: 349,
+    isFeatured: false
+  },
+  {
+    title: "前端开发进阶",
+    category: "🚩 编程开发",
+    color: "#f3a64a",
+    secondaryColor: "#71bbdb",
+    icon: MagicStick,
+    cover: "https://picsum.photos/800/450?random=4&frontend,tech,neon",
+    rating: 4.6,
+    description: "深入学习Vue.js和React框架,掌握组件化开发、状态管理和性能优化。",
+    lecturer: {
+      name: "张敏导师",
+      avatar: "https://i.pravatar.cc/40?img=4",
+      bio: "前端开发专家,专注于现代Web开发技术。"
+    },
+    duration: "24小时",
+    lessons: 80,
+    level: "中级",
+    price: 249,
+    isFeatured: false
+  },
+  {
+    title: "机器学习入门",
+    category: "🚀 人工智能",
+    color: "#71bbdb",
+    secondaryColor: "#50a879",
+    icon: Monitor,
+    cover: "https://picsum.photos/800/450?random=5&ml,tech,neon",
+    rating: 4.5,
+    description: "从零开始学习机器学习基础,掌握常用算法和模型评估方法。",
+    lecturer: {
+      name: "刘强博士",
+      avatar: "https://i.pravatar.cc/40?img=5",
+      bio: "机器学习研究员,专注于算法研究与工程实践。"
+    },
+    duration: "20小时",
+    lessons: 60,
+    level: "初级",
+    price: 199,
+    isFeatured: true
+  },
+  {
+    title: "数据可视化",
+    category: "📊 数据科学",
+    color: "#50a879",
+    secondaryColor: "#f3a64a",
+    icon: TrendCharts,
+    cover: "https://picsum.photos/800/450?random=6&dataviz,tech,neon",
+    rating: 4.7,
+    description: "学习使用D3.js和ECharts创建交互式数据可视化图表,提升数据展示效果。",
+    lecturer: {
+      name: "赵琳分析师",
+      avatar: "https://i.pravatar.cc/40?img=6",
+      bio: "数据可视化专家,擅长将复杂数据转化为直观图表。"
+    },
+    duration: "18小时",
+    lessons: 50,
+    level: "初级",
+    price: 179,
+    isFeatured: false
+  }
+]);
+
+// 显示课程详情
+const showCourseDetail = (course) => {
+  // 使用 Element Plus 的消息框示例
+  ElMessage.success(`进入课程: ${course.title}`);
+};
+
+// 工具函数:HEX转RGBA
+const hexToRgba = (hex, alpha) => {
+  const r = parseInt(hex.slice(1, 3), 16);
+  const g = parseInt(hex.slice(3, 5), 16);
+  const b = parseInt(hex.slice(5, 7), 16);
+  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+};
+
+let isLoaded = ref(false);
+
+const handleImageLoad = () => {
+  isLoaded.value = true;
+};
+
+const fetchData = () => {
+  apis.getAllCourses()
+      .then(res => {
+        console.log("---------");
+        console.log(res);
+      });
+};
+
+onMounted(() => {
+  fetchData();
+});
+</script>
+
+<style scoped src="./NewPortalPage.css"/>
+<style scoped>
+@import "https://unpkg.com/pattern.css";
+
+/* 新增固定背景样式 */
+.fixed-grid-bg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: -1; /* 确保内容在上层 */
+  background-image:
+      linear-gradient(rgba(58, 55, 55, 0.3) 1px, transparent 1px),
+      linear-gradient(90deg, rgba(58, 55, 55, 0.3) 1px, transparent 1px);
+  background-size: 30px 30px; /* 网格尺寸 */
+  background-color: #f1f5f9;
+}
+
+/* 内容容器样式 */
+.content-wrapper {
+  position: relative;
+  z-index: 1; /* 确保内容在背景上层 */
+  background: rgba(255, 255, 255, 0); /* 半透明白色背景 */
+  /*backdrop-filter: blur(2px); 可选模糊效果 */
+}
+</style>
+
+<style scoped>
+/* 课程容器 */
+.course-container {
+  padding: 20px;
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+/* 课程卡片 */
+.course-card {
+  position: relative;
+  background: white;
+  border-radius: 1.5rem;
+  border: 2px solid rgba(var(--main-color), 0.1);
+  transition: all 0.3s ease;
+  overflow: hidden;
+  margin-bottom: 20px;
+}
+
+.course-card:hover {
+  border-color: rgba(var(--main-color), 0.3);
+  box-shadow: 0 10px 30px rgba(var(--main-color), 0.1);
+}
+
+/* 动态光效 */
+.hover-glow {
+  /* ...原有光效实现... */
+}
+
+/* 科技图标 */
+.tech-icon {
+  /* ...原有图标动画实现... */
+}
+
+/* 课程封面 */
+.course-cover {
+  position: relative;
+  height: 240px;
+  overflow: hidden;
+  border-radius: 1.5rem 1.5rem 0 0;
+}
+
+.cover-image {
+  width: 100%;
+  height: 100%;
+  transition: transform 0.5s ease;
+}
+
+.course-card:hover .cover-image {
+  transform: scale(1.1);
+}
+
+.cover-overlay {
+  /* ...原有渐变覆盖层实现... */
+}
+
+/* 课程内容 */
+.course-content {
+  padding: 1.5rem;
+}
+
+.flex-between {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+/* 课程标题 */
+.course-title {
+  font-size: 1.25rem;
+  font-weight: 700;
+  margin: 1rem 0;
+  color: #0a1929;
+  transition: color 0.3s ease;
+}
+
+.course-card:hover .course-title {
+  color: var(--main-color);
+}
+
+/* 课程描述 */
+.course-desc {
+  color: #71bbdb;
+  line-height: 1.5;
+  margin: 0.75rem 0;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+
+/* 底部信息 */
+.footer {
+  margin-top: 1.5rem;
+}
+
+.lecturer {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+}
+
+.lecturer-name {
+  font-size: 0.875rem;
+  color: #71bbdb;
+}
+
+/* 自定义按钮样式 */
+.study-btn {
+  background: linear-gradient(
+      to right,
+      var(--main-color),
+      var(--secondary-color)
+  );
+  border: 2px solid rgba(255, 255, 255, 0.2);
+  border-radius: 0.75rem;
+  color: white;
+  transition: all 0.3s ease;
+}
+
+.study-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 5px 15px rgba(var(--main-color), 0.3);
+}
+
+/* 图片容器 */
+.image-container {
+  position: relative;
+  width: 100%;
+  height: 200px; /* 根据实际情况调整 */
+  overflow: hidden;
+  background-color: #f5f7fa; /* 保持与Element Plus一致的背景色 */
+  border-radius: 4px; /* 可选圆角 */
+}
+
+/* 图片样式 */
+.cover-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover; /* 保持原有fit="cover"效果 */
+  //transition: opacity 0.3s;
+}
+
+/* 加载占位符 */
+.image-slot {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #909399; /* Element Plus的次要文字颜色 */
+  font-size: 14px;
+  background: repeating-linear-gradient(
+      -45deg,
+      #f5f7fa,
+      #f5f7fa 10px,
+      #e9e9eb 10px,
+      #e9e9eb 20px
+  ); /* 添加加载条纹效果 */
+}
+
+/* 加载动画 */
+.dot {
+  display: inline-block;
+  animation: dot-pulse 5s infinite;
+}
+
+@keyframes dot-pulse {
+  0%, 100% { opacity: 0.3; }
+  50% { opacity: 1; }
+}
+</style>