yunzer_go/frontend/src/views/technicalArticles/index.vue
2025-12-25 22:21:12 +08:00

437 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<Header />
<div class="articles-page">
<div class="container">
<div class="top-content">
<h1 class="page-title">技术文章</h1>
<div class="content flex flex-column align-items-center">
<p>探索知识与洞见</p>
</div>
</div>
<div class="main-content">
<!-- 左侧分类导航 -->
<div class="left-menu">
<div class="menu-title">分类导航</div>
<ul class="category-list">
<li
v-for="category in categories"
:key="category.id"
class="category-item"
:class="{ active: selectedCategory === category.id }"
@click="selectCategory(category.id)"
>
{{ category.name }}
</li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="contents">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="articles.length === 0" class="no-data">暂无内容</div>
<div v-else class="articles-grid">
<div
v-for="article in articles"
:key="article.id"
class="article-card"
>
<div class="article-image">
<img :src="getImageUrl(article.image)" :alt="article.title" />
</div>
<div class="card-header">
<router-link
:to="`/article?id=${article.id}&source=technicalArticles`"
class="article-title-link"
>
<h3 class="article-title">{{ article.title }}</h3>
</router-link>
<div class="article-meta">
<span class="article-view">
<i class="fa-solid fa-eye"></i>
{{ article.view || 0 }}
</span>
<span class="article-likes">
<i class="fa-solid fa-heart"></i>
{{ article.likes || 0 }}
</span>
<span class="article-date">{{
formatDate(article.publishdate)
}}</span>
</div>
</div>
</div>
</div>
<!-- 分页组件 -->
<div v-if="total > 0" class="pagination-wrapper">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import Header from "@/views/components/header.vue";
import Footer from "@/views/components/footer.vue";
import { technicalArticles } from "@/api/technicalArticles";
// 响应式数据
const categories = ref<any[]>([]); // 分类列表
const articles = ref<any[]>([]); // 文章列表
const selectedCategory = ref<string>(""); // 选中的分类
const loading = ref(false); // 加载状态
const currentPage = ref(1); // 当前页码
const pageSize = ref(10); // 每页条数
const total = ref(0); // 总条数
// 获取图片地址
const getImageUrl = (imagePath: string) => {
if (!imagePath) return "/src/assets/imgs/default.png";
return import.meta.env.VITE_API_DOMAIN + imagePath;
};
// 获取分类列表
const fetchCategories = async () => {
try {
const response: any = await technicalArticles.getTechnicalArticlesCategory();
if (
response.data &&
response.data.data &&
Array.isArray(response.data.data)
) {
categories.value = response.data.data;
// 默认选择第一个分类
if (categories.value.length > 0) {
selectedCategory.value = categories.value[0].id;
fetchArticles();
}
} else {
categories.value = [];
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
}
} catch (error) {
console.error("获取分类失败:", error);
categories.value = [];
ElMessage.warning("获取分类失败");
}
};
// 获取文章列表
const fetchArticles = async (_page: number = currentPage.value) => {
loading.value = true;
try {
const response: any = await technicalArticles.getTechnicalArticlesLists(
selectedCategory.value
);
const data = response.data?.data;
if (data?.articles && Array.isArray(data.articles)) {
articles.value = data.articles;
total.value = data.total || 0;
currentPage.value = data.page || 1;
pageSize.value = data.limit || 10;
} else {
articles.value = [];
total.value = 0;
ElMessage.warning(response.data?.msg || "获取文章失败");
}
} catch (error) {
console.error("获取文章失败:", error);
articles.value = [];
total.value = 0;
ElMessage.warning("获取文章失败");
} finally {
loading.value = false;
}
};
// 选择分类
const selectCategory = (categoryId: string) => {
selectedCategory.value = categoryId;
currentPage.value = 1; // 切换分类时重置到第一页
fetchArticles(1);
};
// 分页处理
const handlePageChange = (page: number) => {
currentPage.value = page;
fetchArticles(page);
};
// 每页条数变化
const handlePageSizeChange = (size: number) => {
pageSize.value = size;
currentPage.value = 1; // 重置到第一页
fetchArticles(1);
};
// 格式化日期
const formatDate = (timestamp: string | number) => {
if (!timestamp) return "";
// 如果是字符串数字,转换为数字
const numTimestamp =
typeof timestamp === "string" ? parseInt(timestamp) : timestamp;
// 如果时间戳是秒格式10位转换为毫秒
const date = new Date(
numTimestamp < 1e10 ? numTimestamp * 1000 : numTimestamp
);
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
};
// 组件挂载时获取数据
onMounted(() => {
fetchCategories();
});
</script>
<style scoped lang="less">
.articles-page {
padding-top: 100px;
min-height: 100vh;
background: #f9fafc;
.top-content {
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
padding: 80px 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--white);
.page-title {
font-size: 28px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.content {
line-height: 1.6;
p {
margin-bottom: 16px;
}
}
}
.main-content {
width: 1200px;
margin: 0 auto;
display: flex;
gap: 30px;
padding: 40px 0;
align-items: flex-start;
.left-menu {
background-color: var(--white);
padding: 20px;
border-radius: 8px;
width: 250px;
flex-shrink: 0;
align-self: flex-start;
.menu-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #1e9fff;
}
.category-list {
list-style: none;
padding: 0;
margin: 0;
.category-item {
padding: 12px 16px;
margin-bottom: 8px;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s ease;
border-left: 3px solid transparent;
&:hover {
background: #f0f8ff;
border-left-color: #1e9fff;
}
&.active {
background: #e6f7ff;
border-left-color: #1e9fff;
color: #1e9fff;
font-weight: 500;
}
}
}
}
.contents {
background-color: var(--white);
// min-height: 400px;
border-radius: 8px;
padding: 20px;
flex: 1;
.pagination-wrapper {
margin-top: 30px;
display: flex;
justify-content: center;
padding: 20px 0;
border-top: 1px solid #e8e8e8;
.el-pagination {
--el-pagination-font-size: 14px;
--el-pagination-button-width: 40px;
--el-pagination-button-height: 40px;
}
}
.loading,
.no-data {
text-align: center;
padding: 60px 20px;
color: #666;
font-size: 16px;
}
.articles-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
.article-card {
// min-width: 250px;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.article-image {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 140px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.card-header {
padding: 16px 16px 12px;
.article-title-link {
text-decoration: none;
display: block;
&:hover .article-title {
color: #007bff;
}
}
.article-title {
font-size: 14px;
font-weight: 600;
height: 40px;
color: #333;
margin: 0 0 8px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
transition: color 0.3s ease;
cursor: pointer;
}
.article-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #999;
.article-view,
.article-likes,
.article-date {
display: flex;
align-items: center;
gap: 4px;
}
}
}
.card-body {
padding: 0 16px;
.article-summary {
font-size: 12px;
color: #666;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
margin: 0;
}
}
.card-footer {
padding: 12px 16px 16px;
text-align: right;
.read-more {
color: #1e9fff;
text-decoration: none;
font-size: 12px;
font-weight: 500;
transition: color 0.3s ease;
&:hover {
color: #0d8aff;
text-decoration: underline;
}
}
}
}
}
}
}
}
</style>