架构优化与文章页的设计(Day2)
前言
第一天的开发工作主要完成了项目的初始化、环境搭建、页面结构搭建和基础功能实现。第二天的开发工作将重点关注前端架构优化、路由系统配置和文章详情页面的开发,确保项目的可维护性和扩展性。
编辑
编辑
一、前端架构优化
1. 环境配置优化
问题分析
在第一天的开发中,API URL直接硬编码在代码中,存在以下问题:
- 安全性风险:暴露后端服务地址和端口
- 维护困难:不同环境需要修改代码
- 缺乏灵活性:无法统一管理API配置
解决方案
创建环境配置文件,实现配置与代码分离:
创建 .env.development 文件:
# 是否打开mock
VITE_USE_MOCK = false
# 发布路径
VITE_PUBLIC_PATH = /
# 跨域代理,您可以配置多个 ,请注意,没有换行符
VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"]]
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
#后台接口父地址(必填)
VITE_GLOB_API_URL=/jeecgboot
# 接口前缀
VITE_GLOB_API_URL_PREFIX=
配置Vite代理:
修改 vite.config.js 文件:
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
return {
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 3000,
proxy: {
'/jeecgboot': {
target: 'http://localhost:8080/jeecg-boot',
changeOrigin: true,
rewrite: (path) => path.replace(/^/jeecgboot/, '')
}
}
}
};
})
2. HTTP请求封装
安装依赖
npm install axios
创建HTTP请求封装
创建 src/utils/http/axios/index.ts 文件:
import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
interface RequestOptions {
joinPrefix?: boolean;
apiUrl?: string;
urlPrefix?: string;
}
interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string;
requestOptions?: RequestOptions;
}
// 全局配置
const globSetting = {
apiUrl: import.meta.env.VITE_GLOB_API_URL || '/jeecgboot',
urlPrefix: import.meta.env.VITE_GLOB_API_URL_PREFIX || ''
};
class VAxios {
private axiosInstance: AxiosInstance;
private options: CreateAxiosOptions;
constructor(options: CreateAxiosOptions) {
this.options = options;
this.axiosInstance = axios.create(options);
this.setupInterceptors();
}
/**
* 设置拦截器
*/
private setupInterceptors() {
// 请求拦截器
this.axiosInstance.interceptors.request.use(
(config) => {
const { url } = config;
if (url && !url.startsWith('http')) {
config.url = `${globSetting.apiUrl}${url}`;
}
// 可以在这里添加认证令牌等
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
this.axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('HTTP请求错误:', error);
return Promise.reject(error);
}
);
}
/**
* GET请求
*/
get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'GET' }, options);
}
/**
* POST请求
*/
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'POST' }, options);
}
/**
* 发送请求
*/
private request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<T>>(config)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
}
}
// 创建默认的HTTP实例
export const defHttp = new VAxios({
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
requestOptions: {
joinPrefix: true,
apiUrl: globSetting.apiUrl,
urlPrefix: globSetting.urlPrefix
}
});
3. API模块化管理
创建导航API模块
创建 src/api/nav.ts 文件:
import { defHttp } from '../utils/http/axios';
/**
* 导航相关API
*/
enum Api {
GetFullNavList = '/yucms/yucmsCategory/getFullNavList'
}
/**
* 获取完整导航数据(包含二级导航)
*/
export const getFullNavList = () => {
return defHttp.get({ url: Api.GetFullNavList });
};
创建文章API模块
创建 src/api/article.ts 文件:
import { defHttp } from '../utils/http/axios';
/**
* 文章相关API
*/
enum Api {
GetArticleDetail = '/yucms/yucmsArticle/getById',
GetArticleList = '/yucms/yucmsArticle/list',
GetRelatedArticles = '/yucms/yucmsArticle/getRelatedArticles',
GetHotArticles = '/yucms/yucmsArticle/getHotArticles'
}
/**
* 获取文章详情
*/
export const getArticleDetail = (id: string) => {
return defHttp.get({
url: Api.GetArticleDetail,
params: { id }
});
};
/**
* 获取文章列表
*/
export const getArticleList = (params?: any) => {
return defHttp.get({
url: Api.GetArticleList,
params
});
};
/**
* 获取相关文章
*/
export const getRelatedArticles = (id: string, limit: number = 4) => {
return defHttp.get({
url: Api.GetRelatedArticles,
params: { id, limit }
});
};
/**
* 获取热门文章
*/
export const getHotArticles = (limit: number = 5) => {
return defHttp.get({
url: Api.GetHotArticles,
params: { limit }
});
};
二、路由系统配置
1. 安装Vue Router
npm install vue-router@4
2. 创建路由配置
创建 src/router/index.js 文件:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../pages/Home.vue'
import ArticleDetail from '../pages/ArticleDetail.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/article/:id',
name: 'ArticleDetail',
component: ArticleDetail,
props: true
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
3. 注册路由
修改 src/main.js 文件:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
4. 重构项目结构
创建页面目录
src/pages/
├── Home.vue # 首页
└── ArticleDetail.vue # 文章详情页
修改App.vue
将App.vue改为根组件,只包含router-view:
<template>
<router-view />
</template>
<script setup>
</script>
<style>
</style>
创建Home.vue
将原App.vue的内容移动到 src/pages/Home.vue,并修改API导入路径:
<script setup>
import { ref, onMounted } from 'vue';
import { getFullNavList } from '../api/nav';
// ... 其他代码
</script>
三、文章详情页开发
1. 页面结构设计
文章详情页包含以下部分:
-
头部导航(与首页一致)
-
面包屑导航
-
文章内容区
- 文章标题和元信息
- 文章正文
- 文章操作(点赞、收藏、分享)
-
相关文章推荐
-
侧边栏
- 作者信息
- 热门文章
- 热门标签
-
页脚
2. 创建文章详情组件
创建 src/pages/ArticleDetail.vue 文件:
<template>
<div class="font-sans antialiased bg-slate-50 min-h-screen">
<!-- Header -->
<header class="sticky top-0 z-50 w-full border-b bg-white/95 backdrop-blur">
<!-- 导航内容与首页一致 -->
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- Article Content -->
<div class="lg:col-span-3">
<!-- Breadcrumb -->
<nav class="mb-6">
<ol class="flex items-center gap-2 text-sm text-slate-500">
<li><a href="/" class="hover:text-bank-primary">首页</a></li>
<li><i data-lucide="chevron-right" class="w-4 h-4"></i></li>
<li><a href="#" class="hover:text-bank-primary">技术干货</a></li>
<li><i data-lucide="chevron-right" class="w-4 h-4"></i></li>
<li class="text-slate-800 font-medium">文章详情</li>
</ol>
</nav>
<!-- Article Card -->
<article class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<!-- Article Header -->
<div class="p-6 border-b border-slate-200">
<div class="flex items-center gap-2 mb-3">
<span class="px-2 py-1 bg-bank-primary/10 text-bank-primary text-xs font-medium rounded">核心系统</span>
<span class="text-xs text-slate-400">原创</span>
</div>
<h1 class="text-2xl font-bold text-slate-800 mb-4">{{ article.title }}</h1>
<div class="flex flex-wrap items-center gap-4 text-sm text-slate-500">
<div class="flex items-center gap-2">
<img :src="article.authorAvatar" alt="作者头像" class="w-8 h-8 rounded-full">
<span>{{ article.author }}</span>
</div>
<div class="flex items-center gap-1">
<i data-lucide="calendar" class="w-4 h-4"></i>
<span>{{ article.publishTime }}</span>
</div>
<div class="flex items-center gap-1">
<i data-lucide="eye" class="w-4 h-4"></i>
<span>{{ article.views }} 阅读</span>
</div>
</div>
</div>
<!-- Article Content -->
<div class="p-6">
<div class="prose prose-slate max-w-none" v-html="article.content"></div>
</div>
<!-- Article Footer -->
<div class="p-6 border-t border-slate-200 bg-slate-50">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<button class="flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 hover:text-bank-primary hover:bg-white rounded-lg transition-colors">
<i data-lucide="heart" class="w-4 h-4"></i>
<span>点赞 {{ article.likes }}</span>
</button>
<button class="flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 hover:text-bank-primary hover:bg-white rounded-lg transition-colors">
<i data-lucide="bookmark" class="w-4 h-4"></i>
<span>收藏</span>
</button>
<button class="flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 hover:text-bank-primary hover:bg-white rounded-lg transition-colors">
<i data-lucide="share-2" class="w-4 h-4"></i>
<span>分享</span>
</button>
</div>
</div>
</div>
</article>
<!-- Related Articles -->
<section class="mt-8">
<h3 class="text-lg font-bold text-slate-800 mb-4">相关文章推荐</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<a v-for="related in relatedArticles" :key="related.id" :href="'/article/' + related.id" class="group flex gap-4 p-4 rounded-xl bg-white bank-card-shadow bank-hover-lift hover:bg-slate-50/80">
<!-- 相关文章卡片 -->
</a>
</div>
</section>
</div>
<!-- Sidebar -->
<div class="lg:col-span-1 space-y-6">
<!-- 作者信息 -->
<!-- 热门文章 -->
<!-- 热门标签 -->
</div>
</div>
</main>
<!-- Footer -->
<footer class="border-t bg-white mt-12">
<!-- 页脚内容 -->
</footer>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { getFullNavList } from '../api/nav';
// 导航数据
const navList = ref([]);
const mobileMenuOpen = ref(false);
// 文章数据
const article = ref({
id: '',
title: '银行核心系统分布式改造:技术选型与落地实践',
author: '张架构师',
publishTime: '2024-01-15 10:30',
views: 8562,
likes: 356,
content: `
<h2>一、背景与挑战</h2>
<p>随着银行业务的快速发展,传统的集中式核心系统面临着越来越多的挑战。</p>
<!-- 文章内容 -->
`
});
// 相关文章
const relatedArticles = ref([]);
// 获取导航数据
async function fetchNavList() {
try {
const data = await getFullNavList();
if (data.success) {
// 处理导航数据
}
} catch (error) {
console.error('获取导航数据失败:', error);
}
}
// 初始化
onMounted(() => {
fetchNavList();
initLucideIcons();
});
</script>
3. 修改首页文章链接
修改 src/pages/Home.vue 中的文章链接,支持跳转到详情页:
<!-- Article 1 -->
<a href="/article/1" class="group flex gap-4 p-4 rounded-xl bg-white bank-card-shadow bank-hover-lift hover:bg-slate-50/80">
<!-- 文章卡片内容 -->
</a>
<!-- Article 2 -->
<a href="/article/2" class="group flex gap-4 p-4 rounded-xl bg-white bank-card-shadow bank-hover-lift hover:bg-slate-50/80">
<!-- 文章卡片内容 -->
</a>
四、导航数据获取优化
1. 原始实现问题
第一天的实现中,导航数据直接硬编码在组件中,缺乏动态性和可维护性。
2. 优化方案
通过API动态获取导航数据,并构建父子关系:
// 获取完整导航数据(包含二级导航)
async function fetchNavList() {
try {
const data = await getFullNavList();
if (data.success) {
// 按sort字段从小到大排序,确保导航栏显示顺序正确
const sortedList = data.result.sort((a, b) => {
const sortA = a.sort || 0;
const sortB = b.sort || 0;
if (sortA !== sortB) {
return sortA - sortB;
}
return b.id.localeCompare(a.id);
});
// 分离一级和二级导航
const firstLevel = sortedList.filter(item => item.level === 1);
const secondLevel = sortedList.filter(item => item.level === 2);
// 构建父子关系
firstLevel.forEach(parent => {
parent.children = secondLevel.filter(child => child.pid === parent.id);
});
navList.value = firstLevel;
fullNavList.value = sortedList;
}
} catch (error) {
console.error('获取导航数据失败:', error);
}
}
五、问题解决
1. 路径别名配置问题
问题:使用 @ 路径别名时,Vite无法解析模块
解决方案:在 vite.config.js 中配置路径别名:
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
2. API导入路径问题
问题:将App.vue移动到pages目录后,API导入路径错误
解决方案:修改导入路径:
// 错误的导入路径
import { getFullNavList } from './api/nav';
// 正确的导入路径
import { getFullNavList } from '../api/nav';
3. 路由模式问题
问题:使用 createWebHistory 模式时,刷新页面出现404错误
解决方案:在开发环境中使用Vite代理,在生产环境中配置服务器重定向规则。
六、项目结构优化
优化后的项目结构:
fintech-vue3/
├── public/
├── src/
│ ├── api/ # API模块
│ │ ├── nav.ts # 导航API
│ │ └── article.ts # 文章API
│ ├── pages/ # 页面组件
│ │ ├── Home.vue # 首页
│ │ └── ArticleDetail.vue # 文章详情页
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── utils/ # 工具函数
│ │ └── http/
│ │ └── axios/
│ │ └── index.ts # HTTP请求封装
│ ├── App.vue # 根组件
│ ├── main.js # 入口文件
│ └── style.css # 全局样式
├── .env.development # 开发环境配置
├── index.html
├── package.json
├── postcss.config.js
├── tailwind.config.js
└── vite.config.js
七、开发成果
-
✅ 前端架构优化
- 环境配置与代码分离
- HTTP请求统一封装
- API模块化管理
-
✅ 路由系统配置
- Vue Router集成
- 路由配置文件创建
- 页面组件重构
-
✅ 文章详情页开发
- 完整的文章展示功能
- 相关文章推荐
- 侧边栏功能
-
✅ 导航数据动态获取
- API集成
- 数据排序和父子关系构建
八、后续计划
1. 功能完善
- 实现文章详情的真实数据获取
- 添加文章评论功能
- 实现文章搜索功能
- 添加用户登录功能
2. 性能优化
- 图片懒加载
- 路由懒加载
- 代码分割
- 缓存策略
3. 用户体验优化
- 添加页面加载动画
- 优化移动端体验
- 添加返回顶部功能
- 实现文章目录导航
4. 后端集成
- 对接真实的后端API
- 实现数据持久化
- 添加用户认证
- 实现权限管理
九、总结
第二天的开发工作主要完成了前端架构优化、路由系统配置和文章详情页面的开发。通过环境配置、HTTP请求封装和API模块化管理,提高了项目的可维护性和扩展性。路由系统的引入使得页面之间的跳转更加灵活,文章详情页的开发丰富了网站的内容展示功能。
在开发过程中,我们遵循了以下原则:
- 配置与代码分离:通过环境变量管理配置,提高灵活性
- 模块化设计:将功能模块化,便于维护和扩展
- 统一封装:统一HTTP请求处理,减少代码重复
- 响应式设计:确保在不同设备上都有良好的用户体验
通过这两天的开发,我们已经搭建了一个基本的企业级门户网站框架,后续将继续完善功能和优化性能,打造一个功能完善、性能优异的企业级门户网站。