活动和精选资源以及师资力量页面的静态渲染
BIN
public/images/Featured_resources/全部图片1.png
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
public/images/Featured_resources/全部图片2.png
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
public/images/Featured_resources/全部图片3.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
public/images/Featured_resources/全部图片4.png
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
public/images/Featured_resources/全部图片5.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
public/images/Featured_resources/全部图片6.png
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
public/images/Featured_resources/全部图片7.png
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
public/images/Featured_resources/全部图片8.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
public/images/Featured_resources/全部视频1.png
Normal file
After Width: | Height: | Size: 196 KiB |
BIN
public/images/Featured_resources/全部视频2.png
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
public/images/Featured_resources/全部视频3.png
Normal file
After Width: | Height: | Size: 192 KiB |
BIN
public/images/Featured_resources/全部视频4.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
public/images/Featured_resources/全部视频5.png
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
public/images/Featured_resources/全部视频6.png
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
public/images/Featured_resources/全部视频7.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
public/images/Featured_resources/全部视频8.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
public/images/Featured_resources/精品视频3.png
Normal file
After Width: | Height: | Size: 177 KiB |
BIN
public/images/Featured_resources/精选视频1.png
Normal file
After Width: | Height: | Size: 201 KiB |
BIN
public/images/Featured_resources/精选视频2.png
Normal file
After Width: | Height: | Size: 258 KiB |
BIN
public/images/Featured_resources/精选资源轮播.png
Normal file
After Width: | Height: | Size: 2.1 MiB |
BIN
public/images/Teachers/师资力量1.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
public/images/Teachers/师资力量2.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
public/images/Teachers/师资力量3.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
public/images/Teachers/师资力量4.png
Normal file
After Width: | Height: | Size: 126 KiB |
BIN
public/images/Teachers/师资力量5.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
public/images/Teachers/师资力量6.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
public/images/Teachers/师资力量7.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
public/images/Teachers/师资力量8.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
public/images/Teachers/师资力量切图-轮播区.png
Normal file
After Width: | Height: | Size: 153 KiB |
BIN
public/images/activity/活动切图-轮播区.png
Normal file
After Width: | Height: | Size: 432 KiB |
BIN
public/images/activity/活动图1.png
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
public/images/activity/活动图2.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/activity/活动图3.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
@ -73,13 +73,13 @@ export const getCoursesExample = async () => {
|
||||
const response = await CourseApi.getCourses({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
category: '前端开发',
|
||||
level: 'intermediate',
|
||||
sortBy: 'rating'
|
||||
categoryId: 1,
|
||||
difficulty: 1,
|
||||
sortBy: 'createdAt'
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
const { list, total, page, pageSize } = response.data
|
||||
const { list, total } = response.data
|
||||
console.log('课程列表:', list)
|
||||
console.log('总数:', total)
|
||||
return response.data
|
||||
@ -98,7 +98,7 @@ export const searchCoursesExample = async () => {
|
||||
level: 'intermediate',
|
||||
price: 'paid',
|
||||
rating: 4,
|
||||
sortBy: 'rating',
|
||||
sortBy: 'newest',
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
@ -33,6 +33,7 @@ export class AuthApi {
|
||||
nickname: '用户',
|
||||
avatar: '',
|
||||
role: 'student',
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
|
@ -13,7 +13,6 @@ import type {
|
||||
BackendCourseSection,
|
||||
BackendCourseSectionListResponse,
|
||||
Quiz,
|
||||
QuizQuestion,
|
||||
LearningProgress,
|
||||
SearchRequest,
|
||||
Instructor,
|
||||
@ -30,12 +29,23 @@ export class CourseApi {
|
||||
/**
|
||||
* 格式化时间戳为ISO字符串
|
||||
*/
|
||||
private static formatTimestamp(timestamp: number | null | undefined): string {
|
||||
if (!timestamp || timestamp <= 0) {
|
||||
private static formatTimestamp(timestamp: number | string | null | undefined): string {
|
||||
if (!timestamp) {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果是字符串,尝试解析
|
||||
if (typeof timestamp === 'string') {
|
||||
const date = new Date(timestamp)
|
||||
return isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString()
|
||||
}
|
||||
|
||||
// 如果是数字时间戳
|
||||
if (timestamp <= 0) {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
|
||||
// 如果时间戳是秒级的,转换为毫秒级
|
||||
const ms = timestamp < 10000000000 ? timestamp * 1000 : timestamp
|
||||
const date = new Date(ms)
|
||||
@ -181,8 +191,8 @@ export class CourseApi {
|
||||
content: response.data.outline, // 使用 outline 作为课程内容
|
||||
thumbnail: response.data.cover,
|
||||
coverImage: response.data.cover,
|
||||
price: parseFloat(response.data.price),
|
||||
originalPrice: parseFloat(response.data.price),
|
||||
price: parseFloat(response.data.price || '0'),
|
||||
originalPrice: parseFloat(response.data.price || '0'),
|
||||
currency: 'CNY',
|
||||
rating: 4.5,
|
||||
ratingCount: 0,
|
||||
@ -214,8 +224,8 @@ export class CourseApi {
|
||||
certifications: []
|
||||
},
|
||||
status: 'published' as const,
|
||||
createdAt: this.formatTimestamp(response.data.createdAt),
|
||||
updatedAt: this.formatTimestamp(response.data.updatedAt),
|
||||
createdAt: this.formatTimestamp(response.data.createdTime),
|
||||
updatedAt: this.formatTimestamp(response.data.updatedTime),
|
||||
publishedAt: response.data.startTime
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import type {
|
||||
ApiResponse,
|
||||
PaginationResponse,
|
||||
Order,
|
||||
OrderItem,
|
||||
|
||||
} from '../types'
|
||||
|
||||
/**
|
||||
|
@ -53,7 +53,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
@ -90,7 +90,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
@ -130,7 +130,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
@ -155,7 +155,7 @@ export class UploadApi {
|
||||
error?: string
|
||||
}>>> {
|
||||
const formData = new FormData()
|
||||
files.forEach((file, index) => {
|
||||
files.forEach((file) => {
|
||||
formData.append(`files`, file)
|
||||
})
|
||||
|
||||
@ -163,7 +163,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
@ -255,7 +255,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
@ -291,7 +291,7 @@ export class UploadApi {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (onProgress && progressEvent.total) {
|
||||
const progress = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
|
@ -1,7 +1,7 @@
|
||||
// HTTP 请求封装文件
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import router from '@/router'
|
||||
// import router from '@/router'
|
||||
import type { ApiResponse } from './types'
|
||||
|
||||
// 消息提示函数 - 使用window.alert作为fallback,实际项目中应该使用UI库的消息组件
|
||||
@ -27,21 +27,15 @@ const request: AxiosInstance = axios.create({
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// 添加认证token
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
Authorization: `Bearer ${userStore.token}`,
|
||||
}
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
}
|
||||
|
||||
// 添加请求时间戳
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
'X-Request-Time': Date.now().toString(),
|
||||
}
|
||||
config.headers['X-Request-Time'] = Date.now().toString()
|
||||
|
||||
// 开发环境下打印请求信息
|
||||
if (import.meta.env.DEV) {
|
||||
@ -77,7 +71,7 @@ request.interceptors.response.use(
|
||||
|
||||
// 检查业务状态码
|
||||
if (data.code === 200 || data.code === 0) {
|
||||
return data
|
||||
return response
|
||||
}
|
||||
|
||||
// 处理业务错误
|
||||
|
@ -23,6 +23,7 @@ export interface User {
|
||||
username: string
|
||||
email: string
|
||||
phone?: string
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
role: 'student' | 'teacher' | 'admin'
|
||||
status: 'active' | 'inactive' | 'banned'
|
||||
|
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 自定义控制栏 -->
|
||||
<div v-if="showControls && !error" class="video-controls" :class="{ 'controls-visible': controlsVisible }">
|
||||
<div v-if="controlsVisible && !error" class="video-controls" :class="{ 'controls-visible': controlsVisible }">
|
||||
<!-- 进度条 -->
|
||||
<div class="progress-container" @click="seekTo" @mousemove="showProgressPreview" @mouseleave="hideProgressPreview">
|
||||
<div class="progress-track">
|
||||
@ -336,7 +336,7 @@ const loadVideo = () => {
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
hls.on(Hls.Events.ERROR, (_event, data) => {
|
||||
console.error('HLS error:', data)
|
||||
if (data.fatal) {
|
||||
error.value = true
|
||||
|
@ -1,27 +1,9 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { CourseApi } from '@/api/modules/course'
|
||||
import type { Course as ApiCourse } from '@/api/types'
|
||||
|
||||
export interface Course {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
instructor: string
|
||||
instructorAvatar?: string
|
||||
thumbnail: string
|
||||
price: number
|
||||
originalPrice?: number
|
||||
rating: number
|
||||
studentsCount: number
|
||||
duration: string
|
||||
level: 'beginner' | 'intermediate' | 'advanced'
|
||||
category: string
|
||||
tags: string[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
isEnrolled?: boolean
|
||||
progress?: number
|
||||
}
|
||||
export type Course = ApiCourse
|
||||
|
||||
export interface Lesson {
|
||||
id: number
|
||||
@ -54,12 +36,12 @@ export const useCourseStore = defineStore('course', () => {
|
||||
filtered = filtered.filter(course =>
|
||||
course.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
course.description.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
course.instructor.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
course.instructor.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (selectedCategory.value) {
|
||||
filtered = filtered.filter(course => course.category === selectedCategory.value)
|
||||
filtered = filtered.filter(course => course.category.name === selectedCategory.value)
|
||||
}
|
||||
|
||||
if (selectedLevel.value) {
|
||||
@ -90,53 +72,136 @@ export const useCourseStore = defineStore('course', () => {
|
||||
id: 1,
|
||||
title: 'Vue.js 3 完整教程',
|
||||
description: '从零开始学习Vue.js 3,包括Composition API、TypeScript集成等现代开发技术',
|
||||
instructor: '李老师',
|
||||
instructorAvatar: 'https://via.placeholder.com/50',
|
||||
content: '详细的Vue.js 3课程内容',
|
||||
instructor: {
|
||||
id: 1,
|
||||
name: '李老师',
|
||||
title: '前端开发专家',
|
||||
bio: '资深前端开发工程师',
|
||||
avatar: 'https://via.placeholder.com/50',
|
||||
rating: 4.8,
|
||||
studentsCount: 1234,
|
||||
coursesCount: 5,
|
||||
experience: '5年前端开发经验',
|
||||
education: ['计算机科学学士'],
|
||||
certifications: ['Vue.js认证']
|
||||
},
|
||||
thumbnail: 'https://via.placeholder.com/300x200',
|
||||
coverImage: 'https://via.placeholder.com/300x200',
|
||||
price: 299,
|
||||
originalPrice: 399,
|
||||
currency: 'CNY',
|
||||
rating: 4.8,
|
||||
ratingCount: 100,
|
||||
studentsCount: 1234,
|
||||
duration: '12小时',
|
||||
level: 'intermediate',
|
||||
category: '前端开发',
|
||||
category: {
|
||||
id: 1,
|
||||
name: '前端开发',
|
||||
slug: 'frontend',
|
||||
description: '前端开发相关课程'
|
||||
},
|
||||
tags: ['Vue.js', 'JavaScript', 'TypeScript'],
|
||||
totalLessons: 20,
|
||||
language: 'zh-CN',
|
||||
skills: ['Vue.js', 'TypeScript', 'Composition API'],
|
||||
requirements: ['JavaScript基础', 'HTML/CSS基础'],
|
||||
objectives: ['掌握Vue.js 3核心概念', '学会使用Composition API', '理解TypeScript集成'],
|
||||
status: 'published',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-15'
|
||||
updatedAt: '2024-01-15',
|
||||
publishedAt: '2024-01-01'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'React 18 实战开发',
|
||||
description: '掌握React 18的新特性,包括并发渲染、Suspense等高级功能',
|
||||
instructor: '王老师',
|
||||
instructorAvatar: 'https://via.placeholder.com/50',
|
||||
content: '详细的React 18课程内容',
|
||||
instructor: {
|
||||
id: 2,
|
||||
name: '王老师',
|
||||
title: 'React专家',
|
||||
bio: '资深React开发工程师',
|
||||
avatar: 'https://via.placeholder.com/50',
|
||||
rating: 4.9,
|
||||
studentsCount: 2156,
|
||||
coursesCount: 8,
|
||||
experience: '6年React开发经验',
|
||||
education: ['软件工程硕士'],
|
||||
certifications: ['React认证']
|
||||
},
|
||||
thumbnail: 'https://via.placeholder.com/300x200',
|
||||
coverImage: 'https://via.placeholder.com/300x200',
|
||||
price: 399,
|
||||
originalPrice: 499,
|
||||
currency: 'CNY',
|
||||
rating: 4.9,
|
||||
ratingCount: 200,
|
||||
studentsCount: 2156,
|
||||
duration: '15小时',
|
||||
level: 'advanced',
|
||||
category: '前端开发',
|
||||
category: {
|
||||
id: 1,
|
||||
name: '前端开发',
|
||||
slug: 'frontend',
|
||||
description: '前端开发相关课程'
|
||||
},
|
||||
tags: ['React', 'JavaScript', 'Hooks'],
|
||||
totalLessons: 25,
|
||||
language: 'zh-CN',
|
||||
skills: ['React 18', 'Hooks', '并发渲染'],
|
||||
requirements: ['JavaScript基础', 'React基础'],
|
||||
objectives: ['掌握React 18新特性', '学会并发渲染', '理解Suspense'],
|
||||
status: 'published',
|
||||
createdAt: '2024-01-05',
|
||||
updatedAt: '2024-01-20'
|
||||
updatedAt: '2024-01-20',
|
||||
publishedAt: '2024-01-05'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Node.js 后端开发',
|
||||
description: '学习Node.js后端开发,包括Express、数据库操作、API设计等',
|
||||
instructor: '张老师',
|
||||
instructorAvatar: 'https://via.placeholder.com/50',
|
||||
content: '详细的Node.js课程内容',
|
||||
instructor: {
|
||||
id: 3,
|
||||
name: '张老师',
|
||||
title: 'Node.js专家',
|
||||
bio: '资深后端开发工程师',
|
||||
avatar: 'https://via.placeholder.com/50',
|
||||
rating: 4.7,
|
||||
studentsCount: 987,
|
||||
coursesCount: 6,
|
||||
experience: '7年后端开发经验',
|
||||
education: ['计算机科学硕士'],
|
||||
certifications: ['Node.js认证']
|
||||
},
|
||||
thumbnail: 'https://via.placeholder.com/300x200',
|
||||
coverImage: 'https://via.placeholder.com/300x200',
|
||||
price: 349,
|
||||
originalPrice: 449,
|
||||
currency: 'CNY',
|
||||
rating: 4.7,
|
||||
ratingCount: 150,
|
||||
studentsCount: 987,
|
||||
duration: '18小时',
|
||||
level: 'intermediate',
|
||||
category: '后端开发',
|
||||
category: {
|
||||
id: 2,
|
||||
name: '后端开发',
|
||||
slug: 'backend',
|
||||
description: '后端开发相关课程'
|
||||
},
|
||||
tags: ['Node.js', 'Express', 'MongoDB'],
|
||||
totalLessons: 30,
|
||||
language: 'zh-CN',
|
||||
skills: ['Node.js', 'Express', 'MongoDB', 'API设计'],
|
||||
requirements: ['JavaScript基础', '编程基础'],
|
||||
objectives: ['掌握Node.js后端开发', '学会Express框架', '理解数据库操作'],
|
||||
status: 'published',
|
||||
createdAt: '2024-01-10',
|
||||
updatedAt: '2024-01-25'
|
||||
updatedAt: '2024-01-25',
|
||||
publishedAt: '2024-01-10'
|
||||
}
|
||||
]
|
||||
courses.value = mockCourses
|
||||
|
@ -18,12 +18,12 @@ export const useUserStore = defineStore('user', () => {
|
||||
const isAdmin = computed(() => user.value?.role === 'admin')
|
||||
|
||||
// 方法 - 简化版本,主要用于状态管理
|
||||
const login = async (credentials: { email: string; password: string }) => {
|
||||
const login = async (_credentials: { email: string; password: string }) => {
|
||||
// 这个方法现在主要用于兼容性,实际登录逻辑在组件中处理
|
||||
return { success: true, message: '请使用登录模态框进行登录' }
|
||||
}
|
||||
|
||||
const register = async (userData: any) => {
|
||||
const register = async (_userData: any) => {
|
||||
// 这个方法现在主要用于兼容性,实际注册逻辑在组件中处理
|
||||
return { success: true, message: '请使用注册模态框进行注册' }
|
||||
}
|
||||
|
@ -42,49 +42,47 @@
|
||||
class="activity-card"
|
||||
>
|
||||
<div class="card-header">
|
||||
<div class="card-background">
|
||||
<div class="year-badge">2025</div>
|
||||
<div class="course-title">{{ activity.title }}</div>
|
||||
<div class="course-subtitle">{{ activity.subtitle }}</div>
|
||||
<div class="card-image">
|
||||
<img
|
||||
src="/images/activity/活动图1.png"
|
||||
alt="活动图片"
|
||||
class="activity-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card-body">
|
||||
<!-- 特色标签 -->
|
||||
<div class="feature-tags">
|
||||
<span
|
||||
v-for="tag in activity.tags"
|
||||
:key="tag"
|
||||
class="feature-tag"
|
||||
>
|
||||
<i class="tag-icon">✓</i>
|
||||
{{ tag }}
|
||||
</span>
|
||||
<!-- 状态标签 -->
|
||||
<div class="status-badge">
|
||||
<span class="status-text">进行中</span>
|
||||
</div>
|
||||
|
||||
<!-- 课程信息 -->
|
||||
<div class="course-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">{{ activity.courseTitle }}</span>
|
||||
|
||||
<!-- 课程标题 -->
|
||||
<h3 class="course-main-title">{{ activity.courseTitle }}</h3>
|
||||
|
||||
<!-- 课程副标题 -->
|
||||
<p class="course-subtitle-text">{{ activity.subtitle }}</p>
|
||||
|
||||
<!-- 课程详细信息 -->
|
||||
<div class="course-details">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">活动时间:</span>
|
||||
<span class="detail-value">{{ activity.schedule.replace('开课时间:', '') }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-text">{{ activity.schedule }}</span>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">活动类型:</span>
|
||||
<span class="detail-value">激励类活动</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-text">{{ activity.duration }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-text">{{ activity.students }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="price">{{ activity.price }}</span>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">已报名:</span>
|
||||
<span class="detail-value">{{ activity.students.replace('已报名:', '') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 查看详情按钮 -->
|
||||
<div class="card-footer">
|
||||
<button class="detail-btn" @click="viewDetail(activity.id)">
|
||||
查看详情
|
||||
查看活动
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -103,7 +101,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
const loading = ref(true)
|
||||
|
||||
// 横幅图片路径 - 您可以在这里设置图片路径
|
||||
const bannerImageSrc = ref('')
|
||||
const bannerImageSrc = ref('/images/activity/活动切图-轮播区.png')
|
||||
|
||||
// 检查是否有横幅图片
|
||||
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
|
||||
@ -114,7 +112,6 @@ const activities = ref([
|
||||
id: 1,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -125,7 +122,6 @@ const activities = ref([
|
||||
id: 2,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -136,7 +132,6 @@ const activities = ref([
|
||||
id: 3,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -147,7 +142,6 @@ const activities = ref([
|
||||
id: 4,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -158,7 +152,6 @@ const activities = ref([
|
||||
id: 5,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -169,7 +162,6 @@ const activities = ref([
|
||||
id: 6,
|
||||
title: '计算机二级',
|
||||
subtitle: 'C语言讲练综合班',
|
||||
tags: ['系统备考', '考点详解', '题考刷题'],
|
||||
courseTitle: '计算机二级C语言程序设计证书',
|
||||
schedule: '开课时间:2025.07.26-2025.09.28',
|
||||
duration: '适合年级:高校本科生',
|
||||
@ -186,9 +178,9 @@ const viewDetail = (id: number) => {
|
||||
}
|
||||
|
||||
// 设置横幅图片的方法(供后续使用)
|
||||
const setBannerImage = (imagePath: string) => {
|
||||
bannerImageSrc.value = imagePath
|
||||
}
|
||||
// const setBannerImage = (imagePath: string) => {
|
||||
// bannerImageSrc.value = imagePath
|
||||
// }
|
||||
|
||||
// 模拟数据加载
|
||||
onMounted(() => {
|
||||
@ -318,51 +310,44 @@ onMounted(() => {
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.card-background {
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.activity-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-background::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
transform: translate(30px, -30px);
|
||||
}
|
||||
|
||||
.card-background::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 50%;
|
||||
transform: translate(-20px, 20px);
|
||||
}
|
||||
|
||||
.year-badge {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #44A08D;
|
||||
background: #9ACD32;
|
||||
color: #333;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
@ -385,64 +370,62 @@ onMounted(() => {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.feature-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
/* 状态标签 */
|
||||
.status-badge {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: #FFF2E6;
|
||||
color: #FF6B35;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
.status-text {
|
||||
display: inline-block;
|
||||
background: #FF6B35;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag-icon {
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
/* 课程标题 */
|
||||
.course-main-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.info-row:first-child {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
/* 课程副标题 */
|
||||
.course-subtitle-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #FF6B35;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin: 0 0 16px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 课程详细信息 */
|
||||
.course-details {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
@ -452,9 +435,9 @@ onMounted(() => {
|
||||
|
||||
.detail-btn {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
background: #4A90E2;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
@ -464,7 +447,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.detail-btn:hover {
|
||||
background: #357ABD;
|
||||
background: #4A90E2;
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@ -573,6 +557,10 @@ onMounted(() => {
|
||||
.placeholder-text {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@ -604,6 +592,10 @@ onMounted(() => {
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
@ -87,6 +87,7 @@
|
||||
:size="60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="instructor-info">
|
||||
<div class="instructor-name">{{ course.instructor.name }}</div>
|
||||
<div class="instructor-title">{{ course.instructor.title }}</div>
|
||||
@ -258,7 +259,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="chapter.expanded" class="chapter-lessons">
|
||||
<div v-for="(section, sectionIndex) in chapter.sections" :key="section.id" class="lesson-item">
|
||||
<div v-for="section in chapter.sections" :key="section.id" class="lesson-item">
|
||||
<div class="lesson-info" @click="handleSectionClick(section)">
|
||||
<span class="lesson-type" :class="getLessonTypeClass(section)">
|
||||
{{ getLessonTypeText(section) }}
|
||||
@ -458,17 +459,17 @@ const generateChapterGroups = () => {
|
||||
}
|
||||
|
||||
// 获取章节标题
|
||||
const getChapterTitle = (chapterIndex: number): string => {
|
||||
const titles = [
|
||||
'课前准备',
|
||||
'程序设计基础知识',
|
||||
'程序的控制结构',
|
||||
'大话吉模型介绍',
|
||||
'DeepSeek实际应用',
|
||||
'DeepSeek实际应用'
|
||||
]
|
||||
return titles[chapterIndex - 1] || '课程内容'
|
||||
}
|
||||
// const getChapterTitle = (chapterIndex: number): string => {
|
||||
// const titles = [
|
||||
// '课前准备',
|
||||
// '程序设计基础知识',
|
||||
// '程序的控制结构',
|
||||
// '大话吉模型介绍',
|
||||
// 'DeepSeek实际应用',
|
||||
// 'DeepSeek实际应用'
|
||||
// ]
|
||||
// return titles[chapterIndex - 1] || '课程内容'
|
||||
// }
|
||||
|
||||
// 预览模态框相关数据
|
||||
const previewModalVisible = ref(false)
|
||||
@ -585,13 +586,13 @@ const toggleChapter = (chapterIndex: number) => {
|
||||
}
|
||||
|
||||
// 格式化时长
|
||||
const formatDuration = (sortOrder: number): string => {
|
||||
// 根据章节序号模拟时长
|
||||
const baseDuration = 5 + (sortOrder * 3) // 基础5分钟 + 序号*3分钟
|
||||
const minutes = baseDuration % 60
|
||||
const seconds = (sortOrder * 17) % 60 // 模拟秒数
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
// const formatDuration = (sortOrder: number): string => {
|
||||
// // 根据章节序号模拟时长
|
||||
// const baseDuration = 5 + (sortOrder * 3) // 基础5分钟 + 序号*3分钟
|
||||
// const minutes = baseDuration % 60
|
||||
// const seconds = (sortOrder * 17) % 60 // 模拟秒数
|
||||
// return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
// }
|
||||
|
||||
// 获取课时类型样式类
|
||||
const getLessonTypeClass = (section: CourseSection): string => {
|
||||
@ -696,18 +697,18 @@ const navigateToEnrolledArea = (videoUrl: string, sectionName: string) => {
|
||||
}
|
||||
|
||||
// 更新视频播放器(备用方案)
|
||||
const updateVideoPlayer = (videoUrl: string, sectionName: string) => {
|
||||
console.log('更新视频播放器:', { videoUrl, sectionName })
|
||||
// const updateVideoPlayer = (videoUrl: string, sectionName: string) => {
|
||||
// console.log('更新视频播放器:', { videoUrl, sectionName })
|
||||
|
||||
// 如果在同一页面内更新视频播放器
|
||||
// 可以通过事件总线或状态管理来实现
|
||||
// // 如果在同一页面内更新视频播放器
|
||||
// // 可以通过事件总线或状态管理来实现
|
||||
|
||||
// 这里先显示确认信息
|
||||
const confirmed = confirm(`即将播放视频: ${sectionName}\n是否继续?`)
|
||||
if (confirmed) {
|
||||
navigateToEnrolledArea(videoUrl, sectionName)
|
||||
}
|
||||
}
|
||||
// // 这里先显示确认信息
|
||||
// const confirmed = confirm(`即将播放视频: ${sectionName}\n是否继续?`)
|
||||
// if (confirmed) {
|
||||
// navigateToEnrolledArea(videoUrl, sectionName)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 预览章节(非视频内容)
|
||||
const previewSection = (section: CourseSection) => {
|
||||
|
@ -597,21 +597,46 @@ const setDefaultCourseInfo = () => {
|
||||
id: courseId.value,
|
||||
title: 'C语言程序设计',
|
||||
description: '<p>本课程将带你从零开始学习C语言程序设计,掌握编程基础知识。</p>',
|
||||
category: '信息技术',
|
||||
instructor: '教师',
|
||||
duration: 3600,
|
||||
content: '详细的C语言课程内容',
|
||||
category: {
|
||||
id: 3,
|
||||
name: '信息技术',
|
||||
slug: 'it',
|
||||
description: '信息技术相关课程'
|
||||
},
|
||||
instructor: {
|
||||
id: 4,
|
||||
name: '教师',
|
||||
title: 'C语言专家',
|
||||
bio: '资深C语言开发工程师',
|
||||
avatar: '',
|
||||
rating: 4.5,
|
||||
studentsCount: 1000,
|
||||
coursesCount: 3,
|
||||
experience: '10年C语言教学经验',
|
||||
education: ['计算机科学博士'],
|
||||
certifications: ['C语言认证']
|
||||
},
|
||||
thumbnail: '',
|
||||
coverImage: '',
|
||||
duration: '60小时',
|
||||
level: 'beginner',
|
||||
price: 0,
|
||||
originalPrice: 0,
|
||||
currency: 'CNY',
|
||||
rating: 4.5,
|
||||
ratingCount: 100,
|
||||
studentsCount: 1000,
|
||||
lessonsCount: 20,
|
||||
thumbnail: '',
|
||||
totalLessons: 20,
|
||||
language: 'zh-CN',
|
||||
skills: ['C语言', '程序设计', '算法'],
|
||||
requirements: ['计算机基础'],
|
||||
objectives: ['掌握C语言语法', '学会程序设计', '理解算法思维'],
|
||||
tags: ['编程', 'C语言'],
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
isEnrolled: true,
|
||||
progress: 0
|
||||
status: 'published',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
publishedAt: '2024-01-01'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -633,7 +658,7 @@ const onVideoEnded = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onVideoTimeUpdate = (time: number) => {
|
||||
const onVideoTimeUpdate = (_time: number) => {
|
||||
// 记录学习时长
|
||||
totalStudyTime.value += 1
|
||||
}
|
||||
@ -764,7 +789,7 @@ const replyToComment = (commentId: number) => {
|
||||
const likeReply = (commentId: number, replyId: number) => {
|
||||
const comment = comments.value.find(c => c.id === commentId)
|
||||
if (comment && comment.replies) {
|
||||
const reply = comment.replies.find(r => r.id === replyId)
|
||||
const reply = comment.replies.find((r: any) => r.id === replyId)
|
||||
if (reply) {
|
||||
if (reply.liked) {
|
||||
reply.likes = (reply.likes || 0) - 1
|
||||
|
@ -45,8 +45,12 @@
|
||||
>
|
||||
<div class="card-header">
|
||||
<div class="avatar-container">
|
||||
<!-- 头像占位 -->
|
||||
<div class="avatar-placeholder"></div>
|
||||
<!-- 师资头像 -->
|
||||
<img
|
||||
:src="teacher.avatar"
|
||||
:alt="teacher.name"
|
||||
class="teacher-avatar"
|
||||
/>
|
||||
<div v-if="teacher.featured" class="featured-badge">金牌讲师</div>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
@ -57,11 +61,9 @@
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3 class="teacher-name">{{ teacher.name }}</h3>
|
||||
<p class="teacher-title">{{ teacher.title }}</p>
|
||||
<p class="teacher-position">{{ teacher.position }}</p>
|
||||
<p class="teacher-description">{{ teacher.description }}</p>
|
||||
<div class="teacher-tags">
|
||||
<span v-for="tag in teacher.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div class="teacher-specialty">{{ teacher.specialty }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,13 +96,13 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// 横幅图片相关
|
||||
const bannerImageSrc = ref('')
|
||||
const bannerImageSrc = ref('/images/Teachers/师资力量切图-轮播区.png')
|
||||
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
|
||||
|
||||
// 设置横幅图片的方法(供后续使用)
|
||||
const setBannerImage = (imagePath: string) => {
|
||||
bannerImageSrc.value = imagePath
|
||||
}
|
||||
// const setBannerImage = (imagePath: string) => {
|
||||
// bannerImageSrc.value = imagePath
|
||||
// }
|
||||
|
||||
// 筛选标签数据
|
||||
const filterTabs = ref([
|
||||
@ -121,59 +123,67 @@ const teachers = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '黄天羽',
|
||||
title: '注册国际人才测评师资格认证',
|
||||
description: '注册国际企业学习设计师 认证',
|
||||
tags: ['主讲', '资深人才测评师'],
|
||||
featured: true
|
||||
position: '北京理工大学计算机学院教授',
|
||||
description: '北京市高等学校育年教学名师,博导',
|
||||
specialty: '主讲 - 软件工程基础训练',
|
||||
featured: true,
|
||||
avatar: '/images/Teachers/师资力量1.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '蓝天',
|
||||
title: '北京理工大学MBA企业文化专家顾问',
|
||||
position: '北京理工大学MBA企业文化专家顾问',
|
||||
description: '多家知名上市企业高管',
|
||||
tags: ['主讲', 'MBA企业文化专家']
|
||||
specialty: '主讲 - MBA企业文化专家',
|
||||
avatar: '/images/Teachers/师资力量2.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '万精云',
|
||||
title: '中国人事科学',
|
||||
position: '中国人事科学研究院研究员',
|
||||
description: '中国科学院博士',
|
||||
tags: ['主讲', '人事专家']
|
||||
specialty: '主讲 - 人事管理专家',
|
||||
avatar: '/images/Teachers/师资力量3.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '张庆勋',
|
||||
title: '北京大学博士',
|
||||
description: '内蒙古财经大学',
|
||||
tags: ['主讲', '金牌讲师']
|
||||
position: '北京大学博士',
|
||||
description: '内蒙古财经大学教授',
|
||||
specialty: '主讲 - 金融学专家',
|
||||
avatar: '/images/Teachers/师资力量4.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '程毅',
|
||||
title: '中国科技大学博士研究生',
|
||||
description: '',
|
||||
tags: ['主讲', '科技专家']
|
||||
position: '中国科技大学博士研究生导师',
|
||||
description: '计算机科学与技术专业带头人',
|
||||
specialty: '主讲 - 计算机科学专家',
|
||||
avatar: '/images/Teachers/师资力量5.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '王德华',
|
||||
title: '数字经济与金融研究中心专家',
|
||||
position: '数字经济与金融研究中心专家',
|
||||
description: '多家知名上市企业高级管理顾问',
|
||||
tags: ['主讲', '数字经济专家']
|
||||
specialty: '主讲 - 数字经济专家',
|
||||
avatar: '/images/Teachers/师资力量6.png'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '马前程',
|
||||
title: '清华大学管理学院',
|
||||
position: '清华大学管理学院教授',
|
||||
description: '多家一线互联网企业高级管理顾问',
|
||||
tags: ['主讲', '清华大学管理专家']
|
||||
specialty: '主讲 - 企业管理专家',
|
||||
avatar: '/images/Teachers/师资力量7.png'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '陈宇',
|
||||
title: '知名上市企业高级管理顾问专家',
|
||||
position: '知名上市企业高级管理顾问专家',
|
||||
description: '多家一线互联网企业高级管理顾问',
|
||||
tags: ['主讲', '企业管理专家']
|
||||
specialty: '主讲 - 企业战略专家',
|
||||
avatar: '/images/Teachers/师资力量8.png'
|
||||
}
|
||||
])
|
||||
|
||||
@ -246,18 +256,15 @@ const goToPage = (page: number) => {
|
||||
|
||||
.banner-image-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.banner-placeholder {
|
||||
@ -364,6 +371,7 @@ const goToPage = (page: number) => {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
aspect-ratio: 3/4;
|
||||
}
|
||||
|
||||
.faculty-card:hover {
|
||||
@ -373,7 +381,7 @@ const goToPage = (page: number) => {
|
||||
|
||||
.card-header {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
height: 70%;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -386,26 +394,18 @@ const goToPage = (page: number) => {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
.teacher-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #e0e0e0 0%, #f0f0f0 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-placeholder::after {
|
||||
content: '头像占位';
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.featured-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
background: #FF6B35;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
background: #E53E3E;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
@ -415,59 +415,60 @@ const goToPage = (page: number) => {
|
||||
|
||||
.card-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
transform: translateY(-50%);
|
||||
color: #4A90E2;
|
||||
background: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: white;
|
||||
background: #4A90E2;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.card-arrow:hover {
|
||||
background: #357ABD;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
height: 30%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.teacher-name {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0 0 6px 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.teacher-title {
|
||||
font-size: 14px;
|
||||
.teacher-position {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.teacher-description {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin: 0 0 12px 0;
|
||||
line-height: 1.4;
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
.teacher-tags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: #f0f8ff;
|
||||
color: #4A90E2;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
border: 1px solid #e6f3ff;
|
||||
color: #666;
|
||||
margin: 0 0 6px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.teacher-specialty {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 分页组件 */
|
||||
@ -528,10 +529,6 @@ const goToPage = (page: number) => {
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.banner-image-container {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
@ -557,10 +554,6 @@ const goToPage = (page: number) => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.banner-image-container {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 36px;
|
||||
}
|
||||
@ -587,10 +580,6 @@ const goToPage = (page: number) => {
|
||||
padding: 20px 0 40px;
|
||||
}
|
||||
|
||||
.banner-image-container {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ import RegisterModal from '@/components/auth/RegisterModal.vue'
|
||||
const { t, locale } = useI18n()
|
||||
const router = useRouter()
|
||||
const courseStore = useCourseStore()
|
||||
const { loginModalVisible, registerModalVisible, enrollCourse, handleAuthSuccess } = useAuth()
|
||||
const { loginModalVisible, registerModalVisible, handleAuthSuccess } = useAuth()
|
||||
|
||||
// 轮播图根据语言动态切换
|
||||
const bannerImage = computed(() => {
|
||||
|
@ -1,23 +1,13 @@
|
||||
<template>
|
||||
<div class="resources-page">
|
||||
<!-- 轮播图区域 -->
|
||||
<!-- 横幅图区域 -->
|
||||
<div class="banner-section">
|
||||
<div class="banner-container">
|
||||
<div class="banner-slide active">
|
||||
<div class="banner-image">
|
||||
<!-- 轮播图占位 -->
|
||||
<div class="banner-placeholder"></div>
|
||||
</div>
|
||||
<div class="banner-content">
|
||||
<h2 class="banner-title">海量资源聚合,您的一站式数字资源库</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 轮播指示器 -->
|
||||
<div class="banner-indicators">
|
||||
<span class="indicator active"></span>
|
||||
<span class="indicator"></span>
|
||||
<span class="indicator"></span>
|
||||
</div>
|
||||
<img
|
||||
src="/images/Featured_resources/精选资源轮播.png"
|
||||
alt="精选资源横幅"
|
||||
class="banner-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -34,7 +24,11 @@
|
||||
class="featured-card"
|
||||
>
|
||||
<div class="card-image">
|
||||
<div class="image-placeholder"></div>
|
||||
<img
|
||||
:src="video.image"
|
||||
:alt="video.title"
|
||||
class="video-thumbnail"
|
||||
/>
|
||||
<div class="play-button">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
@ -70,7 +64,11 @@
|
||||
class="video-card"
|
||||
>
|
||||
<div class="card-image">
|
||||
<div class="image-placeholder"></div>
|
||||
<img
|
||||
:src="video.image"
|
||||
:alt="video.title"
|
||||
class="video-thumbnail"
|
||||
/>
|
||||
<div class="play-button">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
@ -109,7 +107,11 @@
|
||||
class="image-card"
|
||||
>
|
||||
<div class="card-image">
|
||||
<div class="image-placeholder"></div>
|
||||
<img
|
||||
:src="image.image"
|
||||
:alt="image.title"
|
||||
class="image-thumbnail"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">{{ image.title }}</h3>
|
||||
@ -130,9 +132,21 @@ import { ref } from 'vue'
|
||||
|
||||
// 精选视频数据
|
||||
const featuredVideos = ref([
|
||||
{ id: 1, title: '西安工业大学内部资源之一' },
|
||||
{ id: 2, title: '华南工业大学内部资源之一' },
|
||||
{ id: 3, title: '西安工业大学内部资源之一' }
|
||||
{
|
||||
id: 1,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精选视频1.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '华南工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精选视频2.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精品视频3.png'
|
||||
}
|
||||
])
|
||||
|
||||
// 视频筛选标签
|
||||
@ -148,14 +162,46 @@ const activeVideoTab = ref('all')
|
||||
|
||||
// 全部视频数据
|
||||
const allVideos = ref([
|
||||
{ id: 1, title: '北京工业大学内部资源之一' },
|
||||
{ id: 2, title: '北京工业大学内部资源之一' },
|
||||
{ id: 3, title: '西安工业大学内部资源之一' },
|
||||
{ id: 4, title: '北京工业大学内部资源之一' },
|
||||
{ id: 5, title: '中国工业大学内部资源之一' },
|
||||
{ id: 6, title: '西安工业大学内部资源之一' },
|
||||
{ id: 7, title: '西安工业大学内部资源之一' },
|
||||
{ id: 8, title: '内蒙古工业大学内部资源之一' }
|
||||
{
|
||||
id: 1,
|
||||
title: '北京工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频1.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '北京工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频2.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频3.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '北京工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频4.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '中国工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频5.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频6.png'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频7.png'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '内蒙古工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部视频8.png'
|
||||
}
|
||||
])
|
||||
|
||||
// 图片筛选标签
|
||||
@ -171,14 +217,46 @@ const activeImageTab = ref('all')
|
||||
|
||||
// 全部图片数据
|
||||
const allImages = ref([
|
||||
{ id: 1, title: '中国工业大学内部资源之一' },
|
||||
{ id: 2, title: '西安工业大学内部资源之一' },
|
||||
{ id: 3, title: '西安工业大学内部资源之一' },
|
||||
{ id: 4, title: '内蒙古工业大学内部资源之一' },
|
||||
{ id: 5, title: '北京工业大学内部资源之一' },
|
||||
{ id: 6, title: '北京工业大学内部资源之一' },
|
||||
{ id: 7, title: '西安工业大学内部资源之一' },
|
||||
{ id: 8, title: '内蒙古工业大学内部资源之一' }
|
||||
{
|
||||
id: 1,
|
||||
title: '中国工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片1.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片2.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片3.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '内蒙古工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片4.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '北京工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片5.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '北京工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片6.png'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片7.png'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '内蒙古工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/全部图片8.png'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
@ -188,89 +266,24 @@ const allImages = ref([
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 轮播图区域 */
|
||||
/* 横幅图区域 */
|
||||
.banner-section {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-slide {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.banner-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-placeholder::after {
|
||||
content: '轮播图占位';
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
color: white;
|
||||
max-width: 800px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.banner-indicators {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.indicator.active {
|
||||
background: white;
|
||||
height: auto;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
@ -289,6 +302,7 @@ const allImages = ref([
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 精选视频区域 */
|
||||
@ -323,6 +337,20 @@ const allImages = ref([
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -378,35 +406,34 @@ const allImages = ref([
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 30px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
gap: 20px;
|
||||
margin: 0 auto 30px auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
padding: 8px 20px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.filter-tab:hover {
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
color: #4A90E2;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
background: #4A90E2;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 全部视频区域 */
|
||||
@ -545,12 +572,14 @@ const allImages = ref([
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
overflow-x: auto;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
gap: 16px;
|
||||
justify-content: flex-start;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
@ -566,13 +595,9 @@ const allImages = ref([
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.banner-section {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 20px;
|
||||
padding: 0 16px;
|
||||
.banner-image {
|
||||
max-height: 250px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.container {
|
||||
@ -596,10 +621,12 @@ const allImages = ref([
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
margin-bottom: 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
|
@ -25,10 +25,10 @@
|
||||
<div v-for="section in sections" :key="section.id" class="section-item">
|
||||
<div class="section-info">
|
||||
<strong>ID:</strong> {{ section.id }} <br>
|
||||
<strong>标题:</strong> {{ section.title }} <br>
|
||||
<strong>标题:</strong> {{ section.name }} <br>
|
||||
<strong>课程ID:</strong> {{ section.lessonId }} <br>
|
||||
<strong>排序:</strong> {{ section.sortOrder }} <br>
|
||||
<strong>链接:</strong> {{ section.sectionId }} <br>
|
||||
<strong>排序:</strong> {{ section.sort }} <br>
|
||||
<strong>ID:</strong> {{ section.id }} <br>
|
||||
<strong>层级:</strong> {{ section.level }}
|
||||
</div>
|
||||
</div>
|
||||
|