【VUE】Vue3.0实现文章详情页功能
【VUE】Vue3.0实现文章详情页功能
- 项目目录结构
- 核心代码实现
- 1. API模块 (src/api/article.js)
- 2. 路由配置 (src/router/index.js)
- 3. 文章列表页面 (src/views/ArticleListView.vue)
- 4. 文章卡片组件 (src/components/ArticleCard.vue)
- 5. 文章详情页面 (src/views/ArticleView.vue)
- 6. App.vue (src/App.vue)
- 7. main.js (src/main.js)
- 实现说明
- 运行项目步骤
- 注意事项
下面是一个完整的Vue3项目实现,包括目录结构、路由配置、API调用和页面渲染。
项目目录结构
src/ ├── api/ │ └── article.js # API请求模块 ├── components/ │ ├── ArticleCard.vue # 文章卡片组件 │ └── ArticleDetail.vue # 文章详情组件 ├── router/ │ └── index.js # 路由配置 ├── views/ │ ├── ArticleListView.vue # 文章列表页面 │ └── ArticleView.vue # 文章详情页面 ├── App.vue └── main.js核心代码实现
1. API模块 (src/api/article.js)
importaxiosfrom'axios';constapiClient=axios.create({baseURL:'http://your-flask-api.com/api',// 替换为你的Flask API地址timeout:5000});exportdefault{getArticleList(){returnapiClient.get('/articles');},getArticleDetail(id){returnapiClient.get(`/articles/${id}`);}}2. 路由配置 (src/router/index.js)
import{createRouter,createWebHistory}from'vue-router';importArticleListViewfrom'@/views/ArticleListView.vue';importArticleViewfrom'@/views/ArticleView.vue';constroutes=[{path:'/',name:'ArticleList',component:ArticleListView},{path:'/article/:id',name:'ArticleDetail',component:ArticleView,props:true}];constrouter=createRouter({history:createWebHistory(),routes});exportdefaultrouter;3. 文章列表页面 (src/views/ArticleListView.vue)
<template> <div class="article-list"> <h1>文章列表</h1> <div v-if="loading">加载中...</div> <div v-else> <ArticleCard v-for="article in articles" :key="article.id" :article="article" @click="goToArticle(article.id)" /> </div> </div> </template> <script> import { ref, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import ArticleCard from '@/components/ArticleCard.vue'; import articleApi from '@/api/article'; export default { components: { ArticleCard }, setup() { const router = useRouter(); const articles = ref([]); const loading = ref(true); const fetchArticles = async () => { try { const response = await articleApi.getArticleList(); articles.value = response.data; } catch (error) { console.error('获取文章列表失败:', error); } finally { loading.value = false; } }; const goToArticle = (id) => { router.push({ name: 'ArticleDetail', params: { id } }); }; onMounted(() => { fetchArticles(); }); return { articles, loading, goToArticle }; } } </script> <style> .article-list { max-width: 800px; margin: 0 auto; padding: 20px; } </style>4. 文章卡片组件 (src/components/ArticleCard.vue)
<template> <div class="article-card"> <div class="content"> <h3>{{ article.title }}</h3> <p>{{ article.excerpt }}</p> <small>作者: {{ article.author }} | 发布日期: {{ formatDate(article.publishDate) }}</small> </div> </div> </template> <script> export default { props: { article: { type: Object, required: true } }, methods: { formatDate(dateString) { return new Date(dateString).toLocaleDateString(); } } } </script> <style scoped> .article-card { border: 1px solid #eee; border-radius: 8px; padding: 15px; margin-bottom: 15px; transition: box-shadow 0.3s; cursor: pointer; } .article-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .content h3 { margin-top: 0; } </style>5. 文章详情页面 (src/views/ArticleView.vue)
<template> <div class="article-detail"> <div v-if="loading" class="loading">加载中...</div> <div v-else-if="article"> <button @click="goBack" class="back-button">返回列表</button> <div class="detail-content"> <h1>{{ article.title }}</h1> <div class="meta"> <span>作者: {{ article.author }}</span> <span>发布日期: {{ formatDate(article.publishDate) }}</span> <span>阅读量: {{ article.readCount }}</span> </div> <div v-if="article.coverImage" class="cover-image"> <img :src="article.coverImage" alt="封面图片"> </div> <div class="content" v-html="article.content"></div> </div> </div> <div v-else class="error"> <p>无法加载文章内容</p> <button @click="goBack">返回列表</button> </div> </div> </template> <script> import { ref, onMounted, watch } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import articleApi from '@/api/article'; export default { props: { id: { type: String, required: true } }, setup(props) { const route = useRoute(); const router = useRouter(); const article = ref(null); const loading = ref(true); const fetchArticle = async () => { try { loading.value = true; const response = await articleApi.getArticleDetail(props.id); article.value = response.data; } catch (error) { console.error('获取文章详情失败:', error); article.value = null; } finally { loading.value = false; } }; const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString(); }; const goBack = () => { router.push({ name: 'ArticleList' }); }; onMounted(() => { fetchArticle(); }); // 监听props变化,当在同一组件中切换文章时重新获取数据 watch(() => props.id, (newId) => { if (newId) fetchArticle(); }); return { article, loading, formatDate, goBack }; } } </script> <style scoped> .article-detail { max-width: 800px; margin: 0 auto; padding: 20px; } .back-button { padding: 8px 16px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } .back-button:hover { background-color: #e9e9e9; } .detail-content h1 { margin-top: 0; } .meta { display: flex; justify-content: space-between; margin: 10px 0 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; font-size: 0.9em; color: #666; } .cover-image { margin: 20px 0; } .cover-image img { width: 100%; border-radius: 4px; } .content { line-height: 1.6; } .loading { text-align: center; padding: 40px; } .error { text-align: center; padding: 40px; color: #f56c6c; } </style>6. App.vue (src/App.vue)
<template> <router-view /> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } </style>7. main.js (src/main.js)
import{createApp}from'vue';importAppfrom'./App.vue';importrouterfrom'./router';importaxiosfrom'axios';constapp=createApp(App);// 全局配置axiosaxios.defaults.baseURL=process.env.VUE_APP_API_BASE_URL||'http://your-flask-api.com/api';app.use(router);app.mount('#app');实现说明
- 路由设置:使用Vue Router实现路由跳转,文章详情页使用动态路由参数来传递文章ID
- API封装:将API请求封装在单独的模块中,便于维护
- 异步请求:使用axios进行HTTP请求,使用async/await处理异步操作
- 响应式设计:使用Vue3的reactive和ref实现响应式数据
- 代码组织:将组件分为视图组件和基础组件,便于复用
- 用户体验:添加加载状态显示和错误处理
运行项目步骤
- 创建Vue项目:
vue create article-app - 安装依赖:
npm install axios vue-router@next - 创建上述目录结构并添加文件
- 配置环境变量(可选):
在项目根目录创建.env文件:VUE_APP_API_BASE_URL=http://your-flask-api.com/api
注意事项
- 后端API应返回以下结构的数据:
- 文章列表:文章ID、标题、摘要、作者、发布日期等
- 文章详情:标题、作者、内容、封面图片、发布日期等
- 如果使用v-html渲染内容,需要确保内容安全,防止XSS攻击
- 根据实际需求调整样式
- 在实际项目中添加错误处理和加载状态管理
- 确保跨域问题已经解决(在Flask中配置CORS)
