Parcourir la source

feat(lang): 动态设置页面标题并优化多语言支持

- 在 `langStore` 中新增 `updateDynamicTitle` 方法,根据当前语言动态设置页面标题
- 从语言包中读取网站标题,支持中英文切换时自动更新
- 移除了首页中的静态标题内容,改为通过多语言管理

fix(breadcrumb): 修复面包屑导航重复项及路径参数问题

- 使用 `Set` 去重避免相同路径多次添加
- 正确处理带参数的动态路由路径替换
- 过滤出含有 `meta.title` 的路由记录用于生成面包屑

refactor(menu): 调整菜单项文字并优化路由匹配逻辑

- 将“工作流交易”更正为“我的学习”
- 简化主菜单选中状态
zhangningning il y a 2 semaines
Parent
commit
1ad94613bd

+ 1 - 1
index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>video-learning-demo</title>
+    <title></title>
   </head>
   <body>
     <div id="app"></div>

+ 8 - 8
src/App.vue

@@ -8,7 +8,7 @@
           <el-menu :default-active="activeIndex" mode="horizontal" :ellipsis="false" class="meauList">
             <el-menu-item index="1" @click="$router.push('/')">AI工作流</el-menu-item>
             <!-- <el-menu-item index="2" @click="$router.push('/my-learning')">工作流交易</el-menu-item> -->
-            <el-menu-item index="2" @click="goMyLearning">工作流交易</el-menu-item>
+            <el-menu-item index="2" @click="goMyLearning">我的学习</el-menu-item>
             <el-menu-item index="3" @click="$router.push('/my-learning')">学习教程系统</el-menu-item>
             <el-menu-item index="4" @click="$router.push('/my-learning')">学习笔记</el-menu-item>
             <el-menu-item index="5" @click="$router.push('/my-learning')">积分商城</el-menu-item>
@@ -44,6 +44,8 @@ import { useRoute, useRouter } from 'vue-router'
 import { useLangStore } from '@/pinia/langStore'
 const langStore = useLangStore()
 $i18n.global.locale.value = langStore.currentLang
+// 动态更新页面标题
+langStore.updateDynamicTitle()
 
 const route = useRoute()
 const router = useRouter()
@@ -54,14 +56,12 @@ function goMyLearning() {
 };
 // 将 activeIndex 改为响应式,并根据当前路由动态计算
 const activeIndex = computed(() => {
-  switch (route.path) {
-    case '/':
-      return '1'
-    case '/my-learning':
-      return '2'
-    default:
-      return '1' // 默认返回首页
+  console.log(route)
+  if (route.path === '/') return '1'
+  if (route.path.startsWith('/my-learning')) {
+    return '2'
   }
+  return '1' // 默认返回首页
 });
 </script>
 

+ 28 - 16
src/components/Breadcrumb.vue

@@ -5,7 +5,7 @@
     <el-breadcrumb-item
       v-for="item in breadcrumbItems"
       :key="item.path"
-      :to="item.path === route.path ? undefined : { path: item.path }"
+      :to="item.path === $route.path ? undefined : { path: item.path }"
     >
       {{ item.name }}
     </el-breadcrumb-item>
@@ -20,26 +20,38 @@ const route = useRoute()
 
 const breadcrumbItems = computed(() => {
   const items = []
+  const processedPaths = new Set() // 用于跟踪已处理的路径
   
-  // 根据当前路由动态生成面包屑项
-  if (route.path.startsWith('/course/')) {
+  // 获取匹配的路由记录
+  const matchedRoutes = route.matched.filter(item => item.meta && item.meta.title)
+  
+  // 遍历匹配的路由,生成面包屑项
+  matchedRoutes.forEach((matchedRoute) => {
+    // 跳过首页 '/'
+    if (matchedRoute.path === '/') return
+    
+    // 获取路由标题
+    const title = matchedRoute.meta.title
+    
+    // 构造完整路径
+    let path = matchedRoute.path
+    
+    // 如果路径中有参数,需要替换参数
+    if (route.params) {
+      Object.keys(route.params).forEach(param => {
+        path = path.replace(`:${param}`, route.params[param])
+      })
+    }
     
-    // 如果有课程ID,可以从 store 或 API 获取课程名称
-    console.log('Generating breadcrumb for course detail',route)
-    if (route.params.id) {
+    // 避免重复添加相同路径的项目
+    if (!processedPaths.has(path)) {
       items.push({
-        name: `课程详情-${route.query.title}`,
-        path: route.path
+        name: title,
+        path: path
       })
+      processedPaths.add(path)
     }
-  } else {
-    // 从路由元信息获取标题
-    const title = route.meta?.title || route.name || '未知页面'
-    items.push({
-      name: title,
-      path: route.path
-    })
-  }
+  })
   
   return items
 })

+ 4 - 97
src/pages/Home.vue

@@ -1,31 +1,10 @@
 <template>
   <div class="home-page container">
     <!-- 使用 BlockNote 编辑器 -->
-    <BlockNoteEditor v-model="editorContent" @getHtml="getHtml" :editable="true"/>
-    <!-- <div v-html="editorContent_html"></div> -->
     <div class="banner">
-      <h1>欢迎来到视频学习平台</h1>
-      <p>发现优质课程,提升你的技能</p>
+     
     </div>
     
-    <div class="course-list">
-      <h2>热门课程</h2>
-      <div class="course-grid">
-        <div 
-          v-for="course in courseList" 
-          :key="course.id" 
-          class="course-card"
-          @click="goToCourseDetail(course.id, course.title)"
-        >
-          <img :src="course.cover" :alt="course.title" class="course-cover">
-          <div class="course-info">
-            <h3>{{ course.title }}</h3>
-            <p>讲师:{{ course.teacher }}</p>
-            <div class="course-price">¥{{ course.price }}</div>
-          </div>
-        </div>
-      </div>
-    </div>
   </div>
 </template>
 
@@ -70,13 +49,9 @@ const courseList = ref([])
 
 onMounted(() => {
   console.log('Home mounted')
-  courseStore.fetchCourseList()
-  courseList.value = courseStore.courseList;
 })
 
-const getHtml = (html) => {
-  editorContent_html.value = html
-}
+
 
 const goToCourseDetail = (id,title) => {
   //增加参数名称
@@ -86,81 +61,13 @@ const goToCourseDetail = (id,title) => {
       title: title
     }
   })
-}
+};
 
 </script>
 
 <style scoped lang="scss">
 
 .banner {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: white;
-  padding: 60px 20px;
-  text-align: center;
-  border-radius: 8px;
-  margin-bottom: 40px;
-  
-  h1 {
-    font-size: 2.5rem;
-    margin-bottom: 10px;
-  }
-  
-  p {
-    font-size: 1.2rem;
-    opacity: 0.9;
-  }
-}
-
-.course-list {
-  h2 {
-    font-size: 1.8rem;
-    margin-bottom: 20px;
-    border-bottom: 2px solid #eee;
-    padding-bottom: 10px;
-  }
-  
-  .course-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-    gap: 20px;
-  }
-  
-  .course-card {
-    border: 1px solid #eee;
-    border-radius: 8px;
-    overflow: hidden;
-    cursor: pointer;
-    transition: transform 0.3s;
-    
-    &:hover {
-      transform: translateY(-5px);
-      box-shadow: 0 5px 15px rgba(0,0,0,0.1);
-    }
-    
-    .course-cover {
-      width: 100%;
-      height: 180px;
-      object-fit: cover;
-    }
-    
-    .course-info {
-      padding: 15px;
-      
-      h3 {
-        margin-bottom: 8px;
-        font-size: 1.1rem;
-      }
-      
-      p {
-        color: #666;
-        margin-bottom: 10px;
-      }
-      
-      .course-price {
-        color: #e63946;
-        font-weight: bold;
-      }
-    }
-  }
+ 
 }
 </style>

+ 166 - 0
src/pages/HomeOld.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="home-page container">
+    <!-- 使用 BlockNote 编辑器 -->
+    <BlockNoteEditor v-model="editorContent" @getHtml="getHtml" :editable="true"/>
+    <!-- <div v-html="editorContent_html"></div> -->
+    <div class="banner">
+      <h1>欢迎来到视频学习平台</h1>
+      <p>发现优质课程,提升你的技能</p>
+    </div>
+    
+    <div class="course-list">
+      <h2>热门课程</h2>
+      <div class="course-grid">
+        <div 
+          v-for="course in courseList" 
+          :key="course.id" 
+          class="course-card"
+          @click="goToCourseDetail(course.id, course.title)"
+        >
+          <img :src="course.cover" :alt="course.title" class="course-cover">
+          <div class="course-info">
+            <h3>{{ course.title }}</h3>
+            <p>讲师:{{ course.teacher }}</p>
+            <div class="course-price">¥{{ course.price }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted,ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { useCourseStore } from '../pinia/courseStore'
+
+// 导入封装好的 BlockNote 组件
+import BlockNoteEditor from '@/components/BlockNoteEditor.vue';
+
+// 绑定编辑器内容
+// [
+//   {
+//     type: "paragraph",
+//     content: [{ type: "text", text: "只读模式" }]
+//   }
+// ]
+
+const editorContent = ref(
+  [
+    {
+      type: "paragraph",
+      content: [{ type: "text", text: "只读模式" }]
+    },
+    {
+    "id": "378ce968-02c2-4856-888b-c35a355aa84b",
+    "type": "codeBlock",
+    "props": {
+        "language": "text"
+    },
+    "content": [],
+    "children": []
+    }
+  ]
+);
+const editorContent_html = ref();
+
+const router = useRouter()
+const courseStore = useCourseStore()
+const courseList = ref([])
+
+onMounted(() => {
+  console.log('Home mounted')
+  courseStore.fetchCourseList()
+  courseList.value = courseStore.courseList;
+})
+
+const getHtml = (html) => {
+  editorContent_html.value = html
+}
+
+const goToCourseDetail = (id,title) => {
+  //增加参数名称
+  router.push({
+    path: `/course/${id}`,
+    query: {
+      title: title
+    }
+  })
+}
+
+</script>
+
+<style scoped lang="scss">
+
+.banner {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 60px 20px;
+  text-align: center;
+  border-radius: 8px;
+  margin-bottom: 40px;
+  
+  h1 {
+    font-size: 2.5rem;
+    margin-bottom: 10px;
+  }
+  
+  p {
+    font-size: 1.2rem;
+    opacity: 0.9;
+  }
+}
+
+.course-list {
+  h2 {
+    font-size: 1.8rem;
+    margin-bottom: 20px;
+    border-bottom: 2px solid #eee;
+    padding-bottom: 10px;
+  }
+  
+  .course-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+    gap: 20px;
+  }
+  
+  .course-card {
+    border: 1px solid #eee;
+    border-radius: 8px;
+    overflow: hidden;
+    cursor: pointer;
+    transition: transform 0.3s;
+    
+    &:hover {
+      transform: translateY(-5px);
+      box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+    }
+    
+    .course-cover {
+      width: 100%;
+      height: 180px;
+      object-fit: cover;
+    }
+    
+    .course-info {
+      padding: 15px;
+      
+      h3 {
+        margin-bottom: 8px;
+        font-size: 1.1rem;
+      }
+      
+      p {
+        color: #666;
+        margin-bottom: 10px;
+      }
+      
+      .course-price {
+        color: #e63946;
+        font-weight: bold;
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
src/pages/MyLearning.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="my-learning-page container">
     <!-- <el-page-header content="我的学习"></el-page-header> -->
-    <Breadcrumb />
+    <!-- <Breadcrumb /> -->
     
     <!-- 学习统计卡片 -->
     <div class="learning-stats">
@@ -167,7 +167,7 @@ const completedCourses = computed(() => {
 
 // 继续学习(跳转到课程详情页)
 const continueLearning = (courseId) => {
-  router.push(`/course/${courseId}`)
+  router.push(`/my-learning/course/${courseId}`)
 }
 
 // 收藏/取消收藏课程

+ 220 - 0
src/pages/MyLearningHome.vue

@@ -0,0 +1,220 @@
+<template>
+  <div class="my-learning-page container">
+    <router-view />
+  </div>
+</template>
+
+<script setup>
+// import { VideoPlay, Clock, Check,StarFilled } from '@element-plus/icons-vue'
+// import { ElProgress } from 'element-plus'
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+// 模拟已购课程数据(实际项目中从 Pinia 或接口获取)
+const myCourses = ref([
+  {
+    id: 1,
+    title: 'Vue 3 从入门到精通',
+    cover: 'https://picsum.photos/400/225?random=1',
+    teacher: '张三',
+    progress: 65, // 学习进度(百分比)
+    isCollected: true // 是否收藏
+  },
+  {
+    id: 2,
+    title: 'JavaScript 高级编程',
+    cover: 'https://picsum.photos/400/225?random=2',
+    teacher: '李四',
+    progress: 30,
+    isCollected: false
+  }
+])
+
+// 模拟最近学习记录
+const recentRecords = ref([
+  {
+    courseId: 1,
+    courseTitle: 'Vue 3 从入门到精通',
+    videoTitle: 'Composition API 基础',
+    studyTime: '2025-01-15 19:30'
+  },
+  {
+    courseId: 2,
+    courseTitle: 'JavaScript 高级编程',
+    videoTitle: '闭包与作用域',
+    studyTime: '2025-01-14 16:45'
+  }
+])
+
+// 计算属性:学习时长(模拟数据,实际可累加视频观看时长)
+const studyTime = computed(() => {
+  // 简单模拟:每个课程进度每10%对应1小时学习时长
+  return myCourses.value.reduce((total, course) => {
+    return total + Math.floor(course.progress / 10)
+  }, 0)
+})
+
+// 计算属性:已完成课程数(进度100%)
+const completedCourses = computed(() => {
+  return myCourses.value.filter(course => course.progress === 100).length
+})
+
+// 继续学习(跳转到课程详情页)
+const continueLearning = (courseId) => {
+  router.push(`/my-learning/course/${courseId}`)
+}
+
+// 收藏/取消收藏课程
+const toggleCollect = (courseId) => {
+  const course = myCourses.value.find(item => item.id === courseId)
+  if (course) {
+    course.isCollected = !course.isCollected
+    // 实际项目中这里需要调用接口保存状态到后端
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+// 学习统计卡片
+.learning-stats {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 30px;
+  flex-wrap: wrap;
+  
+  .stat-card {
+    flex: 1;
+    min-width: 200px;
+    
+    .stat-item {
+      display: flex;
+      align-items: center;
+      padding: 15px 0;
+      
+      i {
+        font-size: 2rem;
+        color: $primary-color;
+        margin-right: 15px;
+      }
+      
+      .stat-label {
+        color: $text-light-color;
+        font-size: 0.9rem;
+        margin-bottom: 5px;
+      }
+      
+      .stat-value {
+        font-size: 1.8rem;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+// 我的课程列表
+.my-courses {
+  h2 {
+    font-size: 1.5rem;
+    margin-bottom: 15px;
+    border-bottom: 2px solid $border-color;
+    padding-bottom: 10px;
+  }
+  
+  .no-courses {
+    text-align: center;
+    padding: 50px 0;
+    
+    .el-empty {
+      margin-bottom: 20px;
+    }
+  }
+  
+  .course-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+    gap: 20px;
+  }
+  
+  .my-course-card {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    
+    .course-header {
+      display: flex;
+      gap: 15px;
+      margin-bottom: 15px;
+      
+      .course-cover {
+        width: 120px;
+        height: 80px;
+        object-fit: cover;
+        border-radius: 4px;
+      }
+      
+      .course-info {
+        flex: 1;
+        
+        h3 {
+          font-size: 1.1rem;
+          margin-bottom: 8px;
+          display: -webkit-box;
+          -webkit-line-clamp: 1;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+        }
+        
+        p {
+          color: $text-light-color;
+          font-size: 0.9rem;
+          margin-bottom: 8px;
+        }
+        
+        .progress-container {
+          display: flex;
+          align-items: center;
+          // gap: 10px;
+          
+          .progress-text {
+            font-size: 0.8rem;
+            color: $text-light-color;
+          }
+        }
+      }
+    }
+    
+    .course-actions {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-top: auto;
+    }
+  }
+}
+
+// 最近学习记录
+.recent-learning {
+  margin-top: 40px;
+  
+  h2 {
+    font-size: 1.5rem;
+    margin-bottom: 15px;
+    border-bottom: 2px solid $border-color;
+    padding-bottom: 10px;
+  }
+}
+
+// 响应式调整
+@media (max-width: 768px) {
+  .learning-stats {
+    flex-direction: column;
+  }
+  
+  .my-courses .course-list {
+    grid-template-columns: 1fr;
+  }
+}
+</style>

+ 15 - 1
src/pinia/langStore.js

@@ -1,6 +1,8 @@
 import { defineStore } from 'pinia'
 import zhCn from 'element-plus/es/locale/lang/zh-cn'
 import en from 'element-plus/es/locale/lang/en'
+import zhMessages from '@/locales/zh-CN.js' // 中文语言包
+import enMessages from '@/locales/en.js'
 
 // 定义localStorage的key
 const LANG_KEY = 'APP_CURRENT_LANG'
@@ -32,6 +34,18 @@ export const useLangStore = defineStore('lang', {
       }
       window.$i18n.locale = lang
       console.log('切换到语言:', lang, $i18n)
-    }
+      // 动态设置页面标题
+      this.updateDynamicTitle()
+    },
+    // 动态更新页面标题
+    updateDynamicTitle() {
+      // 从语言包获取网站标题
+      const siteTitle = this.currentLang === 'en' ? enMessages.common.title : zhMessages.common.title;
+      if (siteTitle) {
+        document.title = siteTitle
+      }
+    },
+    
+   
   }
 })