2025-11-18 11:48:01 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="search-view">
|
|
|
|
|
<!-- 页面头部 -->
|
|
|
|
|
<div class="search-header">
|
|
|
|
|
<div class="header-content">
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
<button class="back-button" @click="goBack">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<ArrowLeft />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>返回</span>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="header-info">
|
|
|
|
|
<h1 class="page-title">
|
|
|
|
|
<el-icon class="title-icon">
|
|
|
|
|
<Search />
|
|
|
|
|
</el-icon>
|
|
|
|
|
搜索结果
|
|
|
|
|
</h1>
|
|
|
|
|
<p class="page-desc" v-if="searchKeyword">
|
|
|
|
|
搜索关键词: <span class="keyword-text">{{ searchKeyword }}</span>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索框 -->
|
|
|
|
|
<div class="header-search">
|
2025-12-24 12:06:59 +08:00
|
|
|
<div class="search-box">
|
|
|
|
|
<input
|
|
|
|
|
v-model="localSearchKeyword"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="搜索文章和课程内容"
|
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
/>
|
|
|
|
|
<div class="search-button" @click="handleSearch">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Search />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-11-18 11:48:01 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 统计信息 -->
|
|
|
|
|
<div class="stats-bar" v-if="!loading && (articles.length > 0 || courses.length > 0)">
|
|
|
|
|
<div class="stat-item">
|
|
|
|
|
<span class="stat-label">共找到</span>
|
|
|
|
|
<span class="stat-value">{{ total }}</span>
|
|
|
|
|
<span class="stat-label">条结果</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-divider"></div>
|
|
|
|
|
<div class="stat-item">
|
|
|
|
|
<span class="stat-label">文章</span>
|
|
|
|
|
<span class="stat-value">{{ articles.length }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-divider"></div>
|
|
|
|
|
<div class="stat-item">
|
|
|
|
|
<span class="stat-label">课程</span>
|
|
|
|
|
<span class="stat-value">{{ courses.length }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索结果 -->
|
|
|
|
|
<div class="search-results" v-loading="loading">
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
<div v-if="!loading && total === 0" class="empty-state">
|
|
|
|
|
<el-icon class="empty-icon">
|
|
|
|
|
<DocumentDelete />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<h3>未找到相关内容</h3>
|
|
|
|
|
<p>换个关键词试试吧</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 文章结果 -->
|
|
|
|
|
<div v-if="articles.length > 0" class="result-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2 class="section-title">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Document />
|
|
|
|
|
</el-icon>
|
|
|
|
|
文章 ({{ articles.length }})
|
|
|
|
|
</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="articles-grid">
|
|
|
|
|
<div
|
|
|
|
|
v-for="article in articles"
|
|
|
|
|
:key="article.resourceID"
|
|
|
|
|
class="article-card"
|
|
|
|
|
@click="handleArticleClick(article)"
|
|
|
|
|
>
|
|
|
|
|
<!-- 文章封面 -->
|
|
|
|
|
<div class="article-cover">
|
|
|
|
|
<img
|
|
|
|
|
v-if="article.coverImage"
|
|
|
|
|
:src="FILE_DOWNLOAD_URL + article.coverImage"
|
|
|
|
|
:alt="article.resourceName"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="cover-placeholder">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Document />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cover-overlay">
|
|
|
|
|
<span class="view-button">查看详情</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 文章信息 -->
|
|
|
|
|
<div class="article-info">
|
|
|
|
|
<h3 class="article-title" :title="article.resourceName">
|
|
|
|
|
{{ article.resourceName }}
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<!-- 简介 -->
|
|
|
|
|
<p class="article-summary">{{ article.summary || '暂无简介' }}</p>
|
|
|
|
|
|
|
|
|
|
<!-- 底部元信息 -->
|
|
|
|
|
<div class="article-meta">
|
|
|
|
|
<div class="meta-item">
|
|
|
|
|
<el-icon class="meta-icon">
|
|
|
|
|
<User />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>{{ article.author || '未知' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="meta-item">
|
|
|
|
|
<el-icon class="meta-icon">
|
|
|
|
|
<View />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>{{ formatNumber(article.viewCount) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="meta-item" v-if="article.publishTime">
|
|
|
|
|
<el-icon class="meta-icon">
|
|
|
|
|
<Clock />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>{{ formatDate(article.publishTime) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 课程结果 -->
|
|
|
|
|
<div v-if="courses.length > 0" class="result-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2 class="section-title">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Reading />
|
|
|
|
|
</el-icon>
|
|
|
|
|
课程 ({{ courses.length }})
|
|
|
|
|
</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="courses-grid">
|
|
|
|
|
<div
|
|
|
|
|
v-for="course in courses"
|
|
|
|
|
:key="course.courseID"
|
|
|
|
|
class="course-card"
|
|
|
|
|
@click="handleCourseClick(course)"
|
|
|
|
|
>
|
|
|
|
|
<!-- 课程封面 -->
|
|
|
|
|
<div class="course-cover">
|
|
|
|
|
<img
|
|
|
|
|
v-if="course.coverImage"
|
|
|
|
|
:src="FILE_DOWNLOAD_URL + course.coverImage"
|
|
|
|
|
:alt="course.courseName"
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="cover-placeholder">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Reading />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cover-overlay">
|
|
|
|
|
<span class="view-button">查看课程</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 课程信息 -->
|
|
|
|
|
<div class="course-info">
|
|
|
|
|
<h3 class="course-title" :title="course.courseName">
|
|
|
|
|
{{ course.courseName }}
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<!-- 简介 -->
|
|
|
|
|
<p class="course-summary">{{ course.summary || '暂无简介' }}</p>
|
|
|
|
|
|
|
|
|
|
<!-- 底部元信息 -->
|
|
|
|
|
<div class="course-meta">
|
|
|
|
|
<div class="meta-item">
|
|
|
|
|
<el-icon class="meta-icon">
|
|
|
|
|
<User />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>{{ course.author || '未知' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="meta-item">
|
|
|
|
|
<el-icon class="meta-icon">
|
|
|
|
|
<View />
|
|
|
|
|
</el-icon>
|
|
|
|
|
<span>{{ formatNumber(course.viewCount) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div v-if="total > pageSize" class="pagination">
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="currentPage"
|
|
|
|
|
v-model:page-size="pageSize"
|
|
|
|
|
:total="total"
|
|
|
|
|
:page-sizes="[10, 20, 30, 50]"
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
@current-change="handlePageChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, onMounted, computed, watch } from 'vue';
|
|
|
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
import {
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
Search,
|
|
|
|
|
Document,
|
|
|
|
|
Reading,
|
|
|
|
|
User,
|
|
|
|
|
View,
|
|
|
|
|
Clock,
|
|
|
|
|
DocumentDelete,
|
|
|
|
|
} from '@element-plus/icons-vue';
|
|
|
|
|
import { resourceApi } from '@/apis/resource';
|
|
|
|
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
|
|
|
|
import type { TaskItemVO } from '@/types/study';
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const searchKeyword = ref('');
|
|
|
|
|
const localSearchKeyword = ref('');
|
|
|
|
|
const currentPage = ref(1);
|
|
|
|
|
const pageSize = ref(20);
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
const searchResults = ref<TaskItemVO[]>([]);
|
|
|
|
|
|
|
|
|
|
// 计算属性:分离文章和课程
|
|
|
|
|
const articles = computed(() => {
|
|
|
|
|
return searchResults.value.filter(item => item.itemType === 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const courses = computed(() => {
|
|
|
|
|
return searchResults.value.filter(item => item.itemType === 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 加载搜索结果
|
|
|
|
|
*/
|
|
|
|
|
async function loadSearchResults() {
|
|
|
|
|
if (!searchKeyword.value.trim()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const result = await resourceApi.searchItems({
|
|
|
|
|
pageParam: {
|
|
|
|
|
pageNumber: currentPage.value,
|
|
|
|
|
pageSize: pageSize.value,
|
|
|
|
|
},
|
|
|
|
|
filter: {
|
|
|
|
|
title: searchKeyword.value.trim(),
|
|
|
|
|
status: 1, // 只查询已发布的内容
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.success && result.dataList) {
|
|
|
|
|
searchResults.value = result.dataList;
|
|
|
|
|
total.value = result.dataList.length;
|
|
|
|
|
} else {
|
|
|
|
|
searchResults.value = [];
|
|
|
|
|
total.value = 0;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('搜索失败:', error);
|
|
|
|
|
ElMessage.error('搜索失败,请稍后重试');
|
|
|
|
|
searchResults.value = [];
|
|
|
|
|
total.value = 0;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行搜索
|
|
|
|
|
*/
|
|
|
|
|
function handleSearch() {
|
|
|
|
|
if (!localSearchKeyword.value.trim()) {
|
|
|
|
|
ElMessage.warning('请输入搜索关键词');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
searchKeyword.value = localSearchKeyword.value;
|
|
|
|
|
currentPage.value = 1;
|
|
|
|
|
|
|
|
|
|
// 更新URL
|
|
|
|
|
router.push({
|
|
|
|
|
path: '/search',
|
|
|
|
|
query: { keyword: searchKeyword.value },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loadSearchResults();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 页码变化
|
|
|
|
|
*/
|
|
|
|
|
function handlePageChange(page: number) {
|
|
|
|
|
currentPage.value = page;
|
|
|
|
|
loadSearchResults();
|
|
|
|
|
// 滚动到顶部
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 每页数量变化
|
|
|
|
|
*/
|
|
|
|
|
function handleSizeChange(size: number) {
|
|
|
|
|
pageSize.value = size;
|
|
|
|
|
currentPage.value = 1;
|
|
|
|
|
loadSearchResults();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 点击文章
|
|
|
|
|
*/
|
|
|
|
|
function handleArticleClick(article: TaskItemVO) {
|
|
|
|
|
router.push(`/article/show?articleId=${article.resourceID}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 点击课程
|
|
|
|
|
*/
|
|
|
|
|
function handleCourseClick(course: TaskItemVO) {
|
|
|
|
|
router.push(`/study-plan/course-detail?courseId=${course.courseID}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回上一页
|
|
|
|
|
*/
|
|
|
|
|
function goBack() {
|
|
|
|
|
router.back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 格式化数字
|
|
|
|
|
*/
|
|
|
|
|
function formatNumber(num?: number): string {
|
|
|
|
|
if (!num) return '0';
|
|
|
|
|
if (num >= 10000) {
|
|
|
|
|
return (num / 10000).toFixed(1) + 'w';
|
|
|
|
|
}
|
|
|
|
|
return num.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 格式化日期
|
|
|
|
|
*/
|
|
|
|
|
function formatDate(date?: Date | string): string {
|
|
|
|
|
if (!date) return '';
|
|
|
|
|
const d = new Date(date);
|
|
|
|
|
const year = d.getFullYear();
|
|
|
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
|
|
|
return `${year}-${month}-${day}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 监听路由变化
|
|
|
|
|
watch(
|
|
|
|
|
() => route.query.keyword,
|
|
|
|
|
(newKeyword) => {
|
|
|
|
|
if (newKeyword && typeof newKeyword === 'string') {
|
|
|
|
|
searchKeyword.value = newKeyword;
|
|
|
|
|
localSearchKeyword.value = newKeyword;
|
|
|
|
|
loadSearchResults();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 组件挂载
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
const keyword = route.query.keyword;
|
|
|
|
|
if (keyword && typeof keyword === 'string') {
|
|
|
|
|
searchKeyword.value = keyword;
|
|
|
|
|
localSearchKeyword.value = keyword;
|
|
|
|
|
loadSearchResults();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.search-view {
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-header {
|
|
|
|
|
background: white;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-content {
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-button {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
2025-12-24 12:06:59 +08:00
|
|
|
gap: 6px;
|
2025-11-18 11:48:01 +08:00
|
|
|
padding: 8px 16px;
|
2025-12-24 12:06:59 +08:00
|
|
|
background: #F9FAFB;
|
|
|
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
|
|
|
border-radius: 8px;
|
2025-11-18 11:48:01 +08:00
|
|
|
font-size: 14px;
|
2025-12-24 12:06:59 +08:00
|
|
|
color: #4A5565;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
2025-11-18 11:48:01 +08:00
|
|
|
|
|
|
|
|
&:hover {
|
2025-12-24 12:06:59 +08:00
|
|
|
background: #F3F4F6;
|
|
|
|
|
color: #E7000B;
|
|
|
|
|
border-color: #E7000B;
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-info {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
|
|
|
|
.title-icon {
|
|
|
|
|
font-size: 28px;
|
2025-12-24 14:12:44 +08:00
|
|
|
color: #C62828;
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-desc {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
|
|
.keyword-text {
|
2025-12-24 14:12:44 +08:00
|
|
|
color: #C62828;
|
2025-11-18 11:48:01 +08:00
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-search {
|
|
|
|
|
width: 400px;
|
2025-12-24 12:06:59 +08:00
|
|
|
}
|
2025-11-18 11:48:01 +08:00
|
|
|
|
2025-12-24 12:06:59 +08:00
|
|
|
.search-box {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 40px;
|
|
|
|
|
background: #FFFFFF;
|
|
|
|
|
border: 1px solid rgba(186, 192, 204, 0.5);
|
|
|
|
|
border-radius: 30px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
input {
|
|
|
|
|
flex: 1;
|
|
|
|
|
height: 100%;
|
|
|
|
|
padding: 0 90px 0 20px;
|
|
|
|
|
border: none;
|
|
|
|
|
outline: none;
|
|
|
|
|
font-family: "Source Han Sans SC";
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #141F38;
|
2025-11-18 11:48:01 +08:00
|
|
|
|
2025-12-24 12:06:59 +08:00
|
|
|
&::placeholder {
|
|
|
|
|
color: rgba(0, 0, 0, 0.3);
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-button {
|
2025-12-24 12:06:59 +08:00
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
width: 60px;
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: #C62828;
|
|
|
|
|
border-radius: 0 30px 30px 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background 0.3s;
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
background: #B71C1C;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stats-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
padding: 16px 32px;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
border-top: 1px solid #e4e7ed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
2025-12-24 14:12:44 +08:00
|
|
|
color: #C62828;
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-divider {
|
|
|
|
|
width: 1px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
background: #dcdfe6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-results {
|
|
|
|
|
padding: 0 32px 32px;
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 80px 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
.empty-icon {
|
|
|
|
|
font-size: 80px;
|
|
|
|
|
color: #dcdfe6;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
color: #606266;
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.result-section {
|
|
|
|
|
margin-bottom: 48px;
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
font-size: 24px;
|
2025-12-24 14:12:44 +08:00
|
|
|
color: #C62828;
|
2025-11-18 11:48:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.articles-grid,
|
|
|
|
|
.courses-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
|
|
gap: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-card,
|
|
|
|
|
.course-card {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
transform: translateY(-4px);
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 21, 41, 0.12);
|
|
|
|
|
|
|
|
|
|
.cover-overlay {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-cover,
|
|
|
|
|
.course-cover {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 180px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cover-placeholder {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
font-size: 64px;
|
|
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cover-overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transition: opacity 0.3s;
|
|
|
|
|
|
|
|
|
|
.view-button {
|
|
|
|
|
padding: 8px 24px;
|
|
|
|
|
background: white;
|
2025-12-24 14:12:44 +08:00
|
|
|
color: #C62828;
|
2025-11-18 11:48:01 +08:00
|
|
|
border-radius: 20px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-info,
|
|
|
|
|
.course-info {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-title,
|
|
|
|
|
.course-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
min-height: 44.8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-summary,
|
|
|
|
|
.course-summary {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
min-height: 42px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.article-meta,
|
|
|
|
|
.course-meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
|
|
|
|
.meta-icon {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-top: 32px;
|
|
|
|
|
padding: 24px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
.header-content {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-search {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.articles-grid,
|
|
|
|
|
.courses-grid {
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.header-content {
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-results {
|
|
|
|
|
padding: 0 20px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.articles-grid,
|
|
|
|
|
.courses-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|