活动和精选资源以及师资力量页面的静态渲染

This commit is contained in:
username 2025-07-29 00:06:54 +08:00
parent 5bb1bbac26
commit c7453da854
50 changed files with 562 additions and 457 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -73,13 +73,13 @@ export const getCoursesExample = async () => {
const response = await CourseApi.getCourses({ const response = await CourseApi.getCourses({
page: 1, page: 1,
pageSize: 20, pageSize: 20,
category: '前端开发', categoryId: 1,
level: 'intermediate', difficulty: 1,
sortBy: 'rating' sortBy: 'createdAt'
}) })
if (response.code === 200) { if (response.code === 200) {
const { list, total, page, pageSize } = response.data const { list, total } = response.data
console.log('课程列表:', list) console.log('课程列表:', list)
console.log('总数:', total) console.log('总数:', total)
return response.data return response.data
@ -98,7 +98,7 @@ export const searchCoursesExample = async () => {
level: 'intermediate', level: 'intermediate',
price: 'paid', price: 'paid',
rating: 4, rating: 4,
sortBy: 'rating', sortBy: 'newest',
page: 1, page: 1,
pageSize: 10 pageSize: 10
}) })

View File

@ -33,6 +33,7 @@ export class AuthApi {
nickname: '用户', nickname: '用户',
avatar: '', avatar: '',
role: 'student', role: 'student',
status: 'active',
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
}, },

View File

@ -13,7 +13,6 @@ import type {
BackendCourseSection, BackendCourseSection,
BackendCourseSectionListResponse, BackendCourseSectionListResponse,
Quiz, Quiz,
QuizQuestion,
LearningProgress, LearningProgress,
SearchRequest, SearchRequest,
Instructor, Instructor,
@ -30,12 +29,23 @@ export class CourseApi {
/** /**
* ISO字符串 * ISO字符串
*/ */
private static formatTimestamp(timestamp: number | null | undefined): string { private static formatTimestamp(timestamp: number | string | null | undefined): string {
if (!timestamp || timestamp <= 0) { if (!timestamp) {
return new Date().toISOString() return new Date().toISOString()
} }
try { 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 ms = timestamp < 10000000000 ? timestamp * 1000 : timestamp
const date = new Date(ms) const date = new Date(ms)
@ -181,8 +191,8 @@ export class CourseApi {
content: response.data.outline, // 使用 outline 作为课程内容 content: response.data.outline, // 使用 outline 作为课程内容
thumbnail: response.data.cover, thumbnail: response.data.cover,
coverImage: response.data.cover, coverImage: response.data.cover,
price: parseFloat(response.data.price), price: parseFloat(response.data.price || '0'),
originalPrice: parseFloat(response.data.price), originalPrice: parseFloat(response.data.price || '0'),
currency: 'CNY', currency: 'CNY',
rating: 4.5, rating: 4.5,
ratingCount: 0, ratingCount: 0,
@ -214,8 +224,8 @@ export class CourseApi {
certifications: [] certifications: []
}, },
status: 'published' as const, status: 'published' as const,
createdAt: this.formatTimestamp(response.data.createdAt), createdAt: this.formatTimestamp(response.data.createdTime),
updatedAt: this.formatTimestamp(response.data.updatedAt), updatedAt: this.formatTimestamp(response.data.updatedTime),
publishedAt: response.data.startTime publishedAt: response.data.startTime
} }

View File

@ -4,7 +4,7 @@ import type {
ApiResponse, ApiResponse,
PaginationResponse, PaginationResponse,
Order, Order,
OrderItem,
} from '../types' } from '../types'
/** /**

View File

@ -53,7 +53,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total
@ -90,7 +90,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total
@ -130,7 +130,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total
@ -155,7 +155,7 @@ export class UploadApi {
error?: string error?: string
}>>> { }>>> {
const formData = new FormData() const formData = new FormData()
files.forEach((file, index) => { files.forEach((file) => {
formData.append(`files`, file) formData.append(`files`, file)
}) })
@ -163,7 +163,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total
@ -255,7 +255,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total
@ -291,7 +291,7 @@ export class UploadApi {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {
const progress = Math.round( const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total

View File

@ -1,7 +1,7 @@
// HTTP 请求封装文件 // HTTP 请求封装文件
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import router from '@/router' // import router from '@/router'
import type { ApiResponse } from './types' import type { ApiResponse } from './types'
// 消息提示函数 - 使用window.alert作为fallback实际项目中应该使用UI库的消息组件 // 消息提示函数 - 使用window.alert作为fallback实际项目中应该使用UI库的消息组件
@ -27,21 +27,15 @@ const request: AxiosInstance = axios.create({
// 请求拦截器 // 请求拦截器
request.interceptors.request.use( request.interceptors.request.use(
(config: AxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
// 添加认证token // 添加认证token
const userStore = useUserStore() const userStore = useUserStore()
if (userStore.token) { 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) { if (import.meta.env.DEV) {
@ -77,7 +71,7 @@ request.interceptors.response.use(
// 检查业务状态码 // 检查业务状态码
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
return data return response
} }
// 处理业务错误 // 处理业务错误

View File

@ -23,6 +23,7 @@ export interface User {
username: string username: string
email: string email: string
phone?: string phone?: string
nickname?: string
avatar?: string avatar?: string
role: 'student' | 'teacher' | 'admin' role: 'student' | 'teacher' | 'admin'
status: 'active' | 'inactive' | 'banned' status: 'active' | 'inactive' | 'banned'

View File

@ -57,7 +57,7 @@
</div> </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-container" @click="seekTo" @mousemove="showProgressPreview" @mouseleave="hideProgressPreview">
<div class="progress-track"> <div class="progress-track">
@ -336,7 +336,7 @@ const loadVideo = () => {
loading.value = false loading.value = false
}) })
hls.on(Hls.Events.ERROR, (event, data) => { hls.on(Hls.Events.ERROR, (_event, data) => {
console.error('HLS error:', data) console.error('HLS error:', data)
if (data.fatal) { if (data.fatal) {
error.value = true error.value = true

View File

@ -1,27 +1,9 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { CourseApi } from '@/api/modules/course' import { CourseApi } from '@/api/modules/course'
import type { Course as ApiCourse } from '@/api/types'
export interface Course { export type Course = ApiCourse
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 interface Lesson { export interface Lesson {
id: number id: number
@ -54,12 +36,12 @@ export const useCourseStore = defineStore('course', () => {
filtered = filtered.filter(course => filtered = filtered.filter(course =>
course.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || course.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
course.description.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) { if (selectedCategory.value) {
filtered = filtered.filter(course => course.category === selectedCategory.value) filtered = filtered.filter(course => course.category.name === selectedCategory.value)
} }
if (selectedLevel.value) { if (selectedLevel.value) {
@ -90,53 +72,136 @@ export const useCourseStore = defineStore('course', () => {
id: 1, id: 1,
title: 'Vue.js 3 完整教程', title: 'Vue.js 3 完整教程',
description: '从零开始学习Vue.js 3包括Composition API、TypeScript集成等现代开发技术', description: '从零开始学习Vue.js 3包括Composition API、TypeScript集成等现代开发技术',
instructor: '李老师', content: '详细的Vue.js 3课程内容',
instructorAvatar: 'https://via.placeholder.com/50', 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', thumbnail: 'https://via.placeholder.com/300x200',
coverImage: 'https://via.placeholder.com/300x200',
price: 299, price: 299,
originalPrice: 399, originalPrice: 399,
currency: 'CNY',
rating: 4.8, rating: 4.8,
ratingCount: 100,
studentsCount: 1234, studentsCount: 1234,
duration: '12小时', duration: '12小时',
level: 'intermediate', level: 'intermediate',
category: '前端开发', category: {
id: 1,
name: '前端开发',
slug: 'frontend',
description: '前端开发相关课程'
},
tags: ['Vue.js', 'JavaScript', 'TypeScript'], 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', createdAt: '2024-01-01',
updatedAt: '2024-01-15' updatedAt: '2024-01-15',
publishedAt: '2024-01-01'
}, },
{ {
id: 2, id: 2,
title: 'React 18 实战开发', title: 'React 18 实战开发',
description: '掌握React 18的新特性包括并发渲染、Suspense等高级功能', description: '掌握React 18的新特性包括并发渲染、Suspense等高级功能',
instructor: '王老师', content: '详细的React 18课程内容',
instructorAvatar: 'https://via.placeholder.com/50', 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', thumbnail: 'https://via.placeholder.com/300x200',
coverImage: 'https://via.placeholder.com/300x200',
price: 399, price: 399,
originalPrice: 499,
currency: 'CNY',
rating: 4.9, rating: 4.9,
ratingCount: 200,
studentsCount: 2156, studentsCount: 2156,
duration: '15小时', duration: '15小时',
level: 'advanced', level: 'advanced',
category: '前端开发', category: {
id: 1,
name: '前端开发',
slug: 'frontend',
description: '前端开发相关课程'
},
tags: ['React', 'JavaScript', 'Hooks'], 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', createdAt: '2024-01-05',
updatedAt: '2024-01-20' updatedAt: '2024-01-20',
publishedAt: '2024-01-05'
}, },
{ {
id: 3, id: 3,
title: 'Node.js 后端开发', title: 'Node.js 后端开发',
description: '学习Node.js后端开发包括Express、数据库操作、API设计等', description: '学习Node.js后端开发包括Express、数据库操作、API设计等',
instructor: '张老师', content: '详细的Node.js课程内容',
instructorAvatar: 'https://via.placeholder.com/50', 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', thumbnail: 'https://via.placeholder.com/300x200',
coverImage: 'https://via.placeholder.com/300x200',
price: 349, price: 349,
originalPrice: 449,
currency: 'CNY',
rating: 4.7, rating: 4.7,
ratingCount: 150,
studentsCount: 987, studentsCount: 987,
duration: '18小时', duration: '18小时',
level: 'intermediate', level: 'intermediate',
category: '后端开发', category: {
id: 2,
name: '后端开发',
slug: 'backend',
description: '后端开发相关课程'
},
tags: ['Node.js', 'Express', 'MongoDB'], 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', createdAt: '2024-01-10',
updatedAt: '2024-01-25' updatedAt: '2024-01-25',
publishedAt: '2024-01-10'
} }
] ]
courses.value = mockCourses courses.value = mockCourses

View File

@ -18,12 +18,12 @@ export const useUserStore = defineStore('user', () => {
const isAdmin = computed(() => user.value?.role === 'admin') 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: '请使用登录模态框进行登录' } return { success: true, message: '请使用登录模态框进行登录' }
} }
const register = async (userData: any) => { const register = async (_userData: any) => {
// 这个方法现在主要用于兼容性,实际注册逻辑在组件中处理 // 这个方法现在主要用于兼容性,实际注册逻辑在组件中处理
return { success: true, message: '请使用注册模态框进行注册' } return { success: true, message: '请使用注册模态框进行注册' }
} }

View File

@ -42,49 +42,47 @@
class="activity-card" class="activity-card"
> >
<div class="card-header"> <div class="card-header">
<div class="card-background"> <div class="card-image">
<div class="year-badge">2025</div> <img
<div class="course-title">{{ activity.title }}</div> src="/images/activity/活动图1.png"
<div class="course-subtitle">{{ activity.subtitle }}</div> alt="活动图片"
class="activity-image"
/>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<!-- 特色标签 --> <!-- 状态标签 -->
<div class="feature-tags"> <div class="status-badge">
<span <span class="status-text">进行中</span>
v-for="tag in activity.tags"
:key="tag"
class="feature-tag"
>
<i class="tag-icon"></i>
{{ tag }}
</span>
</div> </div>
<!-- 课程信息 --> <!-- 课程标题 -->
<div class="course-info"> <h3 class="course-main-title">{{ activity.courseTitle }}</h3>
<div class="info-row">
<span class="info-label">{{ activity.courseTitle }}</span> <!-- 课程副标题 -->
<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>
<div class="info-row"> <div class="detail-item">
<span class="info-text">{{ activity.schedule }}</span> <span class="detail-label">活动类型</span>
<span class="detail-value">激励类活动</span>
</div> </div>
<div class="info-row"> <div class="detail-item">
<span class="info-text">{{ activity.duration }}</span> <span class="detail-label">已报名</span>
</div> <span class="detail-value">{{ activity.students.replace('已报名:', '') }}</span>
<div class="info-row">
<span class="info-text">{{ activity.students }}</span>
</div>
<div class="info-row">
<span class="price">{{ activity.price }}</span>
</div> </div>
</div> </div>
<!-- 查看详情按钮 --> <!-- 查看详情按钮 -->
<div class="card-footer"> <div class="card-footer">
<button class="detail-btn" @click="viewDetail(activity.id)"> <button class="detail-btn" @click="viewDetail(activity.id)">
查看详情 查看活动
</button> </button>
</div> </div>
</div> </div>
@ -103,7 +101,7 @@ import { ref, computed, onMounted } from 'vue'
const loading = ref(true) const loading = ref(true)
// - // -
const bannerImageSrc = ref('') const bannerImageSrc = ref('/images/activity/活动切图-轮播区.png')
// //
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '') const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
@ -114,7 +112,6 @@ const activities = ref([
id: 1, id: 1,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -125,7 +122,6 @@ const activities = ref([
id: 2, id: 2,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -136,7 +132,6 @@ const activities = ref([
id: 3, id: 3,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -147,7 +142,6 @@ const activities = ref([
id: 4, id: 4,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -158,7 +152,6 @@ const activities = ref([
id: 5, id: 5,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -169,7 +162,6 @@ const activities = ref([
id: 6, id: 6,
title: '计算机二级', title: '计算机二级',
subtitle: 'C语言讲练综合班', subtitle: 'C语言讲练综合班',
tags: ['系统备考', '考点详解', '题考刷题'],
courseTitle: '计算机二级C语言程序设计证书', courseTitle: '计算机二级C语言程序设计证书',
schedule: '开课时间2025.07.26-2025.09.28', schedule: '开课时间2025.07.26-2025.09.28',
duration: '适合年级:高校本科生', duration: '适合年级:高校本科生',
@ -186,9 +178,9 @@ const viewDetail = (id: number) => {
} }
// 使 // 使
const setBannerImage = (imagePath: string) => { // const setBannerImage = (imagePath: string) => {
bannerImageSrc.value = imagePath // bannerImageSrc.value = imagePath
} // }
// //
onMounted(() => { onMounted(() => {
@ -318,51 +310,44 @@ onMounted(() => {
/* 卡片头部 */ /* 卡片头部 */
.card-header { .card-header {
position: relative; position: relative;
height: 120px; height: 180px;
overflow: hidden; overflow: hidden;
border-radius: 12px 12px 0 0;
} }
.card-background { .card-image {
width: 100%; width: 100%;
height: 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; padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; 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 { .year-badge {
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 20px; left: 20px;
background: rgba(255, 255, 255, 0.9); background: #9ACD32;
color: #44A08D; color: #333;
padding: 4px 8px; padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
@ -385,64 +370,62 @@ onMounted(() => {
z-index: 1; z-index: 1;
} }
/* 卡片主体 */ /* 卡片主体 */
.card-body { .card-body {
padding: 20px; padding: 20px;
} }
.feature-tags { /* 状态标签 */
display: flex; .status-badge {
gap: 8px; margin-bottom: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
} }
.feature-tag { .status-text {
display: flex; display: inline-block;
align-items: center; background: #FF6B35;
gap: 4px; color: white;
background: #FFF2E6; padding: 4px 12px;
color: #FF6B35; border-radius: 12px;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
} }
.tag-icon { /* 课程标题 */
font-size: 10px; .course-main-title {
font-style: normal; font-size: 16px;
} font-weight: 600;
color: #333;
.course-info { margin: 0 0 8px 0;
margin-bottom: 20px;
}
.info-row {
margin-bottom: 8px;
font-size: 13px;
color: #666;
line-height: 1.4; line-height: 1.4;
} }
.info-row:first-child { /* 课程副标题 */
margin-bottom: 12px; .course-subtitle-text {
}
.info-label {
font-weight: 600;
color: #333;
font-size: 14px; font-size: 14px;
}
.info-text {
color: #666;
}
.price {
color: #FF6B35; color: #FF6B35;
font-weight: 600; margin: 0 0 16px 0;
font-size: 14px; 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 { .detail-btn {
width: 100%; width: 100%;
padding: 10px 20px; padding: 12px 20px;
background: #4A90E2; background: #f5f5f5;
color: white; color: #666;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
@ -464,7 +447,8 @@ onMounted(() => {
} }
.detail-btn:hover { .detail-btn:hover {
background: #357ABD; background: #4A90E2;
color: white;
transform: translateY(-1px); transform: translateY(-1px);
} }
@ -573,6 +557,10 @@ onMounted(() => {
.placeholder-text { .placeholder-text {
font-size: 20px; font-size: 20px;
} }
.card-header {
height: 160px;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -604,6 +592,10 @@ onMounted(() => {
.container { .container {
padding: 0 16px; padding: 0 16px;
} }
.card-header {
height: 140px;
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {

View File

@ -87,6 +87,7 @@
:size="60" :size="60"
/> />
</div> </div>
<div class="instructor-info"> <div class="instructor-info">
<div class="instructor-name">{{ course.instructor.name }}</div> <div class="instructor-name">{{ course.instructor.name }}</div>
<div class="instructor-title">{{ course.instructor.title }}</div> <div class="instructor-title">{{ course.instructor.title }}</div>
@ -258,7 +259,7 @@
</span> </span>
</div> </div>
<div v-if="chapter.expanded" class="chapter-lessons"> <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)"> <div class="lesson-info" @click="handleSectionClick(section)">
<span class="lesson-type" :class="getLessonTypeClass(section)"> <span class="lesson-type" :class="getLessonTypeClass(section)">
{{ getLessonTypeText(section) }} {{ getLessonTypeText(section) }}
@ -458,17 +459,17 @@ const generateChapterGroups = () => {
} }
// //
const getChapterTitle = (chapterIndex: number): string => { // const getChapterTitle = (chapterIndex: number): string => {
const titles = [ // const titles = [
'课前准备', // '',
'程序设计基础知识', // '',
'程序的控制结构', // '',
'大话吉模型介绍', // '',
'DeepSeek实际应用', // 'DeepSeek',
'DeepSeek实际应用' // 'DeepSeek'
] // ]
return titles[chapterIndex - 1] || '课程内容' // return titles[chapterIndex - 1] || ''
} // }
// //
const previewModalVisible = ref(false) const previewModalVisible = ref(false)
@ -585,13 +586,13 @@ const toggleChapter = (chapterIndex: number) => {
} }
// //
const formatDuration = (sortOrder: number): string => { // const formatDuration = (sortOrder: number): string => {
// // //
const baseDuration = 5 + (sortOrder * 3) // 5 + *3 // const baseDuration = 5 + (sortOrder * 3) // 5 + *3
const minutes = baseDuration % 60 // const minutes = baseDuration % 60
const seconds = (sortOrder * 17) % 60 // // const seconds = (sortOrder * 17) % 60 //
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` // return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
} // }
// //
const getLessonTypeClass = (section: CourseSection): string => { const getLessonTypeClass = (section: CourseSection): string => {
@ -696,18 +697,18 @@ const navigateToEnrolledArea = (videoUrl: string, sectionName: string) => {
} }
// //
const updateVideoPlayer = (videoUrl: string, sectionName: string) => { // const updateVideoPlayer = (videoUrl: string, sectionName: string) => {
console.log('更新视频播放器:', { videoUrl, sectionName }) // console.log(':', { videoUrl, sectionName })
// // //
// 线 // // 线
// // //
const confirmed = confirm(`即将播放视频: ${sectionName}\n是否继续`) // const confirmed = confirm(`: ${sectionName}\n`)
if (confirmed) { // if (confirmed) {
navigateToEnrolledArea(videoUrl, sectionName) // navigateToEnrolledArea(videoUrl, sectionName)
} // }
} // }
// //
const previewSection = (section: CourseSection) => { const previewSection = (section: CourseSection) => {

View File

@ -597,21 +597,46 @@ const setDefaultCourseInfo = () => {
id: courseId.value, id: courseId.value,
title: 'C语言程序设计', title: 'C语言程序设计',
description: '<p>本课程将带你从零开始学习C语言程序设计掌握编程基础知识。</p>', description: '<p>本课程将带你从零开始学习C语言程序设计掌握编程基础知识。</p>',
category: '信息技术', content: '详细的C语言课程内容',
instructor: '教师', category: {
duration: 3600, 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', level: 'beginner',
price: 0, price: 0,
originalPrice: 0, originalPrice: 0,
currency: 'CNY',
rating: 4.5, rating: 4.5,
ratingCount: 100,
studentsCount: 1000, studentsCount: 1000,
lessonsCount: 20, totalLessons: 20,
thumbnail: '', language: 'zh-CN',
skills: ['C语言', '程序设计', '算法'],
requirements: ['计算机基础'],
objectives: ['掌握C语言语法', '学会程序设计', '理解算法思维'],
tags: ['编程', 'C语言'], tags: ['编程', 'C语言'],
createdAt: Date.now(), status: 'published',
updatedAt: Date.now(), createdAt: '2024-01-01',
isEnrolled: true, updatedAt: '2024-01-01',
progress: 0 publishedAt: '2024-01-01'
} }
} }
} }
@ -633,7 +658,7 @@ const onVideoEnded = () => {
} }
} }
const onVideoTimeUpdate = (time: number) => { const onVideoTimeUpdate = (_time: number) => {
// //
totalStudyTime.value += 1 totalStudyTime.value += 1
} }
@ -764,7 +789,7 @@ const replyToComment = (commentId: number) => {
const likeReply = (commentId: number, replyId: number) => { const likeReply = (commentId: number, replyId: number) => {
const comment = comments.value.find(c => c.id === commentId) const comment = comments.value.find(c => c.id === commentId)
if (comment && comment.replies) { 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) {
if (reply.liked) { if (reply.liked) {
reply.likes = (reply.likes || 0) - 1 reply.likes = (reply.likes || 0) - 1

View File

@ -45,8 +45,12 @@
> >
<div class="card-header"> <div class="card-header">
<div class="avatar-container"> <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 v-if="teacher.featured" class="featured-badge">金牌讲师</div>
</div> </div>
<div class="card-arrow"> <div class="card-arrow">
@ -57,11 +61,9 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<h3 class="teacher-name">{{ teacher.name }}</h3> <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> <p class="teacher-description">{{ teacher.description }}</p>
<div class="teacher-tags"> <div class="teacher-specialty">{{ teacher.specialty }}</div>
<span v-for="tag in teacher.tags" :key="tag" class="tag">{{ tag }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -94,13 +96,13 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
// //
const bannerImageSrc = ref('') const bannerImageSrc = ref('/images/Teachers/师资力量切图-轮播区.png')
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '') const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
// 使 // 使
const setBannerImage = (imagePath: string) => { // const setBannerImage = (imagePath: string) => {
bannerImageSrc.value = imagePath // bannerImageSrc.value = imagePath
} // }
// //
const filterTabs = ref([ const filterTabs = ref([
@ -121,59 +123,67 @@ const teachers = ref([
{ {
id: 1, id: 1,
name: '黄天羽', name: '黄天羽',
title: '注册国际人才测评师资格认证', position: '北京理工大学计算机学院教授',
description: '注册国际企业学习设计师 认证', description: '北京市高等学校育年教学名师,博导',
tags: ['主讲', '资深人才测评师'], specialty: '主讲 - 软件工程基础训练',
featured: true featured: true,
avatar: '/images/Teachers/师资力量1.png'
}, },
{ {
id: 2, id: 2,
name: '蓝天', name: '蓝天',
title: '北京理工大学MBA企业文化专家顾问', position: '北京理工大学MBA企业文化专家顾问',
description: '多家知名上市企业高管', description: '多家知名上市企业高管',
tags: ['主讲', 'MBA企业文化专家'] specialty: '主讲 - MBA企业文化专家',
avatar: '/images/Teachers/师资力量2.png'
}, },
{ {
id: 3, id: 3,
name: '万精云', name: '万精云',
title: '中国人事科学', position: '中国人事科学研究院研究员',
description: '中国科学院博士', description: '中国科学院博士',
tags: ['主讲', '人事专家'] specialty: '主讲 - 人事管理专家',
avatar: '/images/Teachers/师资力量3.png'
}, },
{ {
id: 4, id: 4,
name: '张庆勋', name: '张庆勋',
title: '北京大学博士', position: '北京大学博士',
description: '内蒙古财经大学', description: '内蒙古财经大学教授',
tags: ['主讲', '金牌讲师'] specialty: '主讲 - 金融学专家',
avatar: '/images/Teachers/师资力量4.png'
}, },
{ {
id: 5, id: 5,
name: '程毅', name: '程毅',
title: '中国科技大学博士研究生', position: '中国科技大学博士研究生导师',
description: '', description: '计算机科学与技术专业带头人',
tags: ['主讲', '科技专家'] specialty: '主讲 - 计算机科学专家',
avatar: '/images/Teachers/师资力量5.png'
}, },
{ {
id: 6, id: 6,
name: '王德华', name: '王德华',
title: '数字经济与金融研究中心专家', position: '数字经济与金融研究中心专家',
description: '多家知名上市企业高级管理顾问', description: '多家知名上市企业高级管理顾问',
tags: ['主讲', '数字经济专家'] specialty: '主讲 - 数字经济专家',
avatar: '/images/Teachers/师资力量6.png'
}, },
{ {
id: 7, id: 7,
name: '马前程', name: '马前程',
title: '清华大学管理学院', position: '清华大学管理学院教授',
description: '多家一线互联网企业高级管理顾问', description: '多家一线互联网企业高级管理顾问',
tags: ['主讲', '清华大学管理专家'] specialty: '主讲 - 企业管理专家',
avatar: '/images/Teachers/师资力量7.png'
}, },
{ {
id: 8, id: 8,
name: '陈宇', name: '陈宇',
title: '知名上市企业高级管理顾问专家', position: '知名上市企业高级管理顾问专家',
description: '多家一线互联网企业高级管理顾问', description: '多家一线互联网企业高级管理顾问',
tags: ['主讲', '企业管理专家'] specialty: '主讲 - 企业战略专家',
avatar: '/images/Teachers/师资力量8.png'
} }
]) ])
@ -246,18 +256,15 @@ const goToPage = (page: number) => {
.banner-image-container { .banner-image-container {
width: 100%; width: 100%;
height: 400px; height: auto;
position: relative; position: relative;
display: flex;
align-items: center;
justify-content: center;
} }
.banner-image { .banner-image {
width: 100%; width: 100%;
height: 100%; height: auto;
display: block;
object-fit: cover; object-fit: cover;
object-position: center;
} }
.banner-placeholder { .banner-placeholder {
@ -364,6 +371,7 @@ const goToPage = (page: number) => {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
aspect-ratio: 3/4;
} }
.faculty-card:hover { .faculty-card:hover {
@ -373,7 +381,7 @@ const goToPage = (page: number) => {
.card-header { .card-header {
position: relative; position: relative;
height: 200px; height: 70%;
background: #f5f5f5; background: #f5f5f5;
display: flex; display: flex;
align-items: center; align-items: center;
@ -386,26 +394,18 @@ const goToPage = (page: number) => {
height: 100%; height: 100%;
} }
.avatar-placeholder { .teacher-avatar {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(135deg, #e0e0e0 0%, #f0f0f0 100%); object-fit: cover;
display: flex; object-position: center;
align-items: center;
justify-content: center;
}
.avatar-placeholder::after {
content: '头像占位';
color: #999;
font-size: 14px;
} }
.featured-badge { .featured-badge {
position: absolute; position: absolute;
top: 12px; top: 8px;
left: 12px; left: 8px;
background: #FF6B35; background: #E53E3E;
color: white; color: white;
padding: 4px 8px; padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
@ -415,59 +415,60 @@ const goToPage = (page: number) => {
.card-arrow { .card-arrow {
position: absolute; position: absolute;
top: 50%; bottom: 16px;
right: 16px; right: 16px;
transform: translateY(-50%); color: white;
color: #4A90E2; background: #4A90E2;
background: white; width: 36px;
width: 32px; height: 36px;
height: 32px;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { .card-content {
padding: 20px; padding: 16px;
height: 30%;
display: flex;
flex-direction: column;
justify-content: flex-start;
text-align: left;
} }
.teacher-name { .teacher-name {
font-size: 18px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
margin: 0 0 8px 0; margin: 0 0 6px 0;
line-height: 1.2;
} }
.teacher-title { .teacher-position {
font-size: 14px; font-size: 12px;
color: #666; color: #666;
margin: 0 0 8px 0; margin: 0 0 4px 0;
line-height: 1.4; line-height: 1.3;
} }
.teacher-description { .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; 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) { @media (max-width: 1024px) {
.banner-image-container {
height: 350px;
}
.placeholder-icon { .placeholder-icon {
font-size: 40px; font-size: 40px;
} }
@ -557,10 +554,6 @@ const goToPage = (page: number) => {
font-size: 13px; font-size: 13px;
} }
.banner-image-container {
height: 300px;
}
.placeholder-icon { .placeholder-icon {
font-size: 36px; font-size: 36px;
} }
@ -587,10 +580,6 @@ const goToPage = (page: number) => {
padding: 20px 0 40px; padding: 20px 0 40px;
} }
.banner-image-container {
height: 250px;
}
.placeholder-icon { .placeholder-icon {
font-size: 32px; font-size: 32px;
} }

View File

@ -230,7 +230,7 @@ import RegisterModal from '@/components/auth/RegisterModal.vue'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const router = useRouter() const router = useRouter()
const courseStore = useCourseStore() const courseStore = useCourseStore()
const { loginModalVisible, registerModalVisible, enrollCourse, handleAuthSuccess } = useAuth() const { loginModalVisible, registerModalVisible, handleAuthSuccess } = useAuth()
// //
const bannerImage = computed(() => { const bannerImage = computed(() => {

View File

@ -1,23 +1,13 @@
<template> <template>
<div class="resources-page"> <div class="resources-page">
<!-- 轮播图区域 --> <!-- 横幅图区域 -->
<div class="banner-section"> <div class="banner-section">
<div class="banner-container"> <div class="banner-container">
<div class="banner-slide active"> <img
<div class="banner-image"> src="/images/Featured_resources/精选资源轮播.png"
<!-- 轮播图占位 --> alt="精选资源横幅"
<div class="banner-placeholder"></div> class="banner-image"
</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>
</div> </div>
</div> </div>
@ -34,7 +24,11 @@
class="featured-card" class="featured-card"
> >
<div class="card-image"> <div class="card-image">
<div class="image-placeholder"></div> <img
:src="video.image"
:alt="video.title"
class="video-thumbnail"
/>
<div class="play-button"> <div class="play-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M8 5V19L19 12L8 5Z" fill="white"/> <path d="M8 5V19L19 12L8 5Z" fill="white"/>
@ -70,7 +64,11 @@
class="video-card" class="video-card"
> >
<div class="card-image"> <div class="card-image">
<div class="image-placeholder"></div> <img
:src="video.image"
:alt="video.title"
class="video-thumbnail"
/>
<div class="play-button"> <div class="play-button">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M8 5V19L19 12L8 5Z" fill="white"/> <path d="M8 5V19L19 12L8 5Z" fill="white"/>
@ -109,7 +107,11 @@
class="image-card" class="image-card"
> >
<div class="card-image"> <div class="card-image">
<div class="image-placeholder"></div> <img
:src="image.image"
:alt="image.title"
class="image-thumbnail"
/>
</div> </div>
<div class="card-content"> <div class="card-content">
<h3 class="card-title">{{ image.title }}</h3> <h3 class="card-title">{{ image.title }}</h3>
@ -130,9 +132,21 @@ import { ref } from 'vue'
// //
const featuredVideos = ref([ const featuredVideos = ref([
{ id: 1, title: '西安工业大学内部资源之一' }, {
{ id: 2, title: '华南工业大学内部资源之一' }, id: 1,
{ id: 3, title: '西安工业大学内部资源之一' } 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([ const allVideos = ref([
{ id: 1, title: '北京工业大学内部资源之一' }, {
{ id: 2, title: '北京工业大学内部资源之一' }, id: 1,
{ id: 3, title: '西安工业大学内部资源之一' }, title: '北京工业大学内部资源之一',
{ id: 4, title: '北京工业大学内部资源之一' }, image: '/images/Featured_resources/全部视频1.png'
{ id: 5, title: '中国工业大学内部资源之一' }, },
{ id: 6, title: '西安工业大学内部资源之一' }, {
{ id: 7, title: '西安工业大学内部资源之一' }, id: 2,
{ id: 8, title: '内蒙古工业大学内部资源之一' } 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([ const allImages = ref([
{ id: 1, title: '中国工业大学内部资源之一' }, {
{ id: 2, title: '西安工业大学内部资源之一' }, id: 1,
{ id: 3, title: '西安工业大学内部资源之一' }, title: '中国工业大学内部资源之一',
{ id: 4, title: '内蒙古工业大学内部资源之一' }, image: '/images/Featured_resources/全部图片1.png'
{ id: 5, title: '北京工业大学内部资源之一' }, },
{ id: 6, title: '北京工业大学内部资源之一' }, {
{ id: 7, title: '西安工业大学内部资源之一' }, id: 2,
{ id: 8, title: '内蒙古工业大学内部资源之一' } 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> </script>
@ -188,89 +266,24 @@ const allImages = ref([
background: #f8f9fa; background: #f8f9fa;
} }
/* 轮播图区域 */ /* 横幅图区域 */
.banner-section { .banner-section {
position: relative; position: relative;
height: 400px; width: 100%;
overflow: hidden; overflow: hidden;
} }
.banner-container { .banner-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: auto;
}
.banner-slide {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
} }
.banner-image { .banner-image {
position: absolute;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; height: auto;
z-index: 1; display: block;
} object-fit: cover;
.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;
} }
/* 主要内容区域 */ /* 主要内容区域 */
@ -289,6 +302,7 @@ const allImages = ref([
font-weight: 600; font-weight: 600;
color: #333; color: #333;
margin: 0 0 30px 0; margin: 0 0 30px 0;
text-align: center;
} }
/* 精选视频区域 */ /* 精选视频区域 */
@ -323,6 +337,20 @@ const allImages = ref([
overflow: hidden; 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 { .image-placeholder {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -378,35 +406,34 @@ const allImages = ref([
/* 筛选标签 */ /* 筛选标签 */
.filter-tabs { .filter-tabs {
display: flex; display: flex;
gap: 0; gap: 20px;
margin-bottom: 30px; margin: 0 auto 30px auto;
background: white; justify-content: center;
border-radius: 8px; align-items: center;
padding: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
width: fit-content; width: fit-content;
} }
.filter-tab { .filter-tab {
padding: 8px 20px; padding: 8px 16px;
border: none; border: none;
background: transparent; background: transparent;
color: #666; color: #999;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
border-radius: 6px; border-radius: 4px;
transition: all 0.3s; transition: all 0.3s;
white-space: nowrap; white-space: nowrap;
font-weight: 400;
} }
.filter-tab:hover { .filter-tab:hover {
background: #f8f9fa; color: #4A90E2;
color: #333;
} }
.filter-tab.active { .filter-tab.active {
background: #4A90E2; background: #4A90E2;
color: white; color: white;
font-weight: 500;
} }
/* 全部视频区域 */ /* 全部视频区域 */
@ -545,12 +572,14 @@ const allImages = ref([
.section-title { .section-title {
font-size: 20px; font-size: 20px;
margin-bottom: 20px; margin-bottom: 20px;
text-align: center;
} }
.filter-tabs { .filter-tabs {
overflow-x: auto; overflow-x: auto;
padding: 4px; gap: 16px;
gap: 4px; justify-content: flex-start;
padding: 0 20px;
} }
.filter-tab { .filter-tab {
@ -566,13 +595,9 @@ const allImages = ref([
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.banner-section { .banner-image {
height: 250px; max-height: 250px;
} object-fit: cover;
.banner-title {
font-size: 20px;
padding: 0 16px;
} }
.container { .container {
@ -596,10 +621,12 @@ const allImages = ref([
.section-title { .section-title {
font-size: 18px; font-size: 18px;
text-align: center;
} }
.filter-tabs { .filter-tabs {
margin-bottom: 20px; margin-bottom: 20px;
gap: 12px;
} }
.filter-tab { .filter-tab {

View File

@ -25,10 +25,10 @@
<div v-for="section in sections" :key="section.id" class="section-item"> <div v-for="section in sections" :key="section.id" class="section-item">
<div class="section-info"> <div class="section-info">
<strong>ID:</strong> {{ section.id }} <br> <strong>ID:</strong> {{ section.id }} <br>
<strong>标题:</strong> {{ section.title }} <br> <strong>标题:</strong> {{ section.name }} <br>
<strong>课程ID:</strong> {{ section.lessonId }} <br> <strong>课程ID:</strong> {{ section.lessonId }} <br>
<strong>排序:</strong> {{ section.sortOrder }} <br> <strong>排序:</strong> {{ section.sort }} <br>
<strong>链接:</strong> {{ section.sectionId }} <br> <strong>ID:</strong> {{ section.id }} <br>
<strong>层级:</strong> {{ section.level }} <strong>层级:</strong> {{ section.level }}
</div> </div>
</div> </div>