feat: 重构前端项目结构并添加新功能
重构项目目录结构,将组件和服务模块化 添加Element Plus UI库并集成到项目中 实现文章、留言和分类的类型定义 新增工具函数模块包括日期格式化和字符串处理 重写路由配置并添加全局路由守卫 优化页面布局和响应式设计 新增服务层封装API请求 完善文章详情页和相关文章推荐功能
This commit is contained in:
@@ -1,61 +1,145 @@
|
||||
<!-- 文章模板 -->
|
||||
<!-- 文章列表组件 -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="article-card"
|
||||
v-for="item in datas"
|
||||
:key="item.title + item.publishedAt"
|
||||
@click="aericleClick(item.articleid)"
|
||||
>
|
||||
<h2>{{ item.title }}</h2>
|
||||
<el-text class="mx-1">{{ item.author }}</el-text>
|
||||
<div v-if="item.mg">mg</div>
|
||||
<p>{{ item.publishedAt }}</p>
|
||||
<div class="article-list-container">
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-skeleton :count="5" />
|
||||
</div>
|
||||
|
||||
<!-- 文章列表 -->
|
||||
<transition-group name="article-item" tag="div" v-else>
|
||||
<div
|
||||
class="article-card"
|
||||
v-for="item in datas"
|
||||
:key="item.id || (item.title + item.publishedAt)"
|
||||
@click="handleArticleClick(item.articleid)"
|
||||
>
|
||||
<h2 class="article-title">{{ item.title }}</h2>
|
||||
<div class="article-meta">
|
||||
<span class="article-author">{{ item.author || '清疯不颠' }}</span>
|
||||
<span class="article-date">{{ formatDateDisplay(item.publishedAt || item.createTime) }}</span>
|
||||
<span v-if="item.categoryName" class="article-category">{{ item.categoryName }}</span>
|
||||
<span v-if="item.views" class="article-views">{{ item.views }} 阅读</span>
|
||||
<span v-if="item.commentCount" class="article-comments">{{ item.commentCount }} 评论</span>
|
||||
</div>
|
||||
<div v-if="item.mg" class="article-tag">mg</div>
|
||||
<p class="article-preview">{{ formatContentPreview(item.content, 150) }}</p>
|
||||
</div>
|
||||
</transition-group>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && datas.length === 0" class="empty-container">
|
||||
<el-empty description="暂无文章" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { articleAPI } from '@/axios/api'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { articleService } from '@/services'
|
||||
import { formatDate, formatRelativeTime } from '@/utils/dateUtils'
|
||||
import { formatContentPreview } from '@/utils/stringUtils'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// 路由实例
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// 响应式状态
|
||||
const datas = ref([])
|
||||
console.log("获取文章列表")
|
||||
onMounted(() => {
|
||||
articleAPI.getAllArticles().then(res => {
|
||||
datas.value = res.data
|
||||
console.log(res.data)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
}).finally(() => {
|
||||
console.log("finally")
|
||||
})
|
||||
})
|
||||
// 跳转到文章详情
|
||||
const aericleClick = (aur) => {
|
||||
router.push({
|
||||
path: '/articlecontents/:url',
|
||||
query: { url: aur }
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
const fetchArticles = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await articleService.getAllArticles()
|
||||
datas.value = res.data || []
|
||||
console.log('获取文章列表成功:', datas.value)
|
||||
} catch (error) {
|
||||
console.error('获取文章列表失败:', error)
|
||||
ElMessage.error('获取文章列表失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
console.log('文章列表加载完成')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文章点击事件
|
||||
* @param {Object} article - 文章对象
|
||||
*/
|
||||
const handleArticleClick = (article) => {
|
||||
console.log('文章点击:', article)
|
||||
router.push({
|
||||
path: '/article/:url',
|
||||
query: { url: article }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期显示
|
||||
* @param {string} dateString - 日期字符串
|
||||
* @returns {string} 格式化后的日期
|
||||
*/
|
||||
const formatDateDisplay = (dateString) => {
|
||||
if (!dateString) return ''
|
||||
|
||||
try {
|
||||
// 如果是今天或昨天的文章,显示相对时间
|
||||
const date = new Date(dateString)
|
||||
const now = new Date()
|
||||
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (diffDays < 2) {
|
||||
return formatRelativeTime(dateString)
|
||||
}
|
||||
|
||||
// 否则显示具体日期
|
||||
return formatDate(dateString, 'YYYY-MM-DD')
|
||||
} catch (error) {
|
||||
console.error('日期格式化错误:', error)
|
||||
return dateString
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchArticles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.article-list-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 分类筛选区域 */
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 文章卡片 */
|
||||
.article-card {
|
||||
border-radius: 10px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
gap: 0.8rem;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
transition: all 0.4s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -71,12 +155,99 @@ const aericleClick = (aur) => {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.article-card:hover {
|
||||
transform: translateY(-5px) perspective(2000px) rotateX(0);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25),
|
||||
0 0 50px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.article-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 文章标题 */
|
||||
.article-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.article-card:hover .article-title {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* 文章元信息 */
|
||||
.article-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
font-size: 0.875rem;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 文章分类 */
|
||||
.article-category {
|
||||
padding: 2px 8px;
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: #3498db;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* 文章标签 */
|
||||
.article-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: #3498db;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 文章预览 */
|
||||
.article-preview {
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.article-item-enter-active,
|
||||
.article-item-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.article-item-enter-from,
|
||||
.article-item-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.article-card {
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user