当前位置: 首页 > news >正文

【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');

实现说明

  1. 路由设置:使用Vue Router实现路由跳转,文章详情页使用动态路由参数来传递文章ID
  2. API封装:将API请求封装在单独的模块中,便于维护
  3. 异步请求:使用axios进行HTTP请求,使用async/await处理异步操作
  4. 响应式设计:使用Vue3的reactive和ref实现响应式数据
  5. 代码组织:将组件分为视图组件和基础组件,便于复用
  6. 用户体验:添加加载状态显示和错误处理

运行项目步骤

  1. 创建Vue项目:vue create article-app
  2. 安装依赖:npm install axios vue-router@next
  3. 创建上述目录结构并添加文件
  4. 配置环境变量(可选):
    在项目根目录创建.env文件:
    VUE_APP_API_BASE_URL=http://your-flask-api.com/api

注意事项

  1. 后端API应返回以下结构的数据:
    • 文章列表:文章ID、标题、摘要、作者、发布日期等
    • 文章详情:标题、作者、内容、封面图片、发布日期等
  2. 如果使用v-html渲染内容,需要确保内容安全,防止XSS攻击
  3. 根据实际需求调整样式
  4. 在实际项目中添加错误处理和加载状态管理
  5. 确保跨域问题已经解决(在Flask中配置CORS)
http://www.jsqmd.com/news/1148778/

相关文章:

  • NBM7100A与PIC18F2458的低功耗电源管理方案
  • CTF 信息泄露与 RCE 防御:从 NewStarCTF 2023 案例看 4 类常见漏洞与加固方案
  • IP地址查询(街道级)接口调用限制与用量边界详解
  • C++ 从10万行hbcore实战总结AI代码3条工程纪律
  • Unity中手搓A*寻路算法:从原理到C#实现与性能优化
  • 电池二阶等效电路模型
  • 长期语言暴力对认知能力的损伤机制与干预策略研究
  • 线性回归最小二乘法:3种解法对比与5个关键假设验证
  • CTF Pwn实战入门:栈溢出原理与ROP链构造详解
  • 深入解析Windows虚拟磁盘API:从virtdisk.h原理到VC++实战开发
  • 华为云Flexus+DeepSeek征文|从零搭建企业级AI知识库:DeepSeek + Dify + Flexus X 实战指南
  • 工业信号干扰防护与光耦选型实战指南
  • RealBasicVSR视频超分辨率终极实战指南:从模糊到4K高清的AI魔法
  • 当 AI 开始“胡说八道”,谁来买单?——从德国法院裁决看生成式搜索的法律边界
  • CVE-2026-43456实战:Linux bonding驱动19年类型混淆0day挖掘、复现、提权与加固完整手册
  • 创业团队的技术文档管理:从零搭建知识库与新人onboarding体系
  • 反物质引力实验:容度原理解码——当反氢原子与普通氢原子同时下落,宇宙的不对称之谜依然等待答案
  • 微服务调用链的超时、重试与熔断——Resilience4j 生产配置指南
  • Adobe-GenP 3.0:深入解析Adobe Creative Cloud通用补丁技术实现
  • 数学建模国赛C题 2023:Python 实现 3 大问题完整求解流程与代码解析
  • 终极热键侦探:3分钟快速定位Windows热键冲突的免费高效工具
  • TLA2518与PIC18F26K22构建高精度数据采集系统
  • 终极指南:3分钟让PS3蓝牙手柄在Windows上完美运行
  • SpringCloud + React19 集成Scalar的API文档
  • YOLOv12交通标识检测实战:从训练到部署全流程
  • MySQL 8.0的自增主键持久化特性
  • 网易云热门乐评 API 调用限制与用量边界详解
  • MATLAB自适应滤波实战包:LMS基础版+AdaGrad/RMSProp/Adam四种学习率策略一键对比
  • Cursor AI助手试用限制的工程化解决方案:从技术架构到生产部署
  • PPO 算法 PyTorch 2.1 实战:CartPole-v1 环境 500 步稳定训练 3 大调参技巧