企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 2)

2 阅读8分钟

架构优化与文章页的设计(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模块化管理,提高了项目的可维护性和扩展性。路由系统的引入使得页面之间的跳转更加灵活,文章详情页的开发丰富了网站的内容展示功能。

在开发过程中,我们遵循了以下原则:

  1. 配置与代码分离:通过环境变量管理配置,提高灵活性
  2. 模块化设计:将功能模块化,便于维护和扩展
  3. 统一封装:统一HTTP请求处理,减少代码重复
  4. 响应式设计:确保在不同设备上都有良好的用户体验

通过这两天的开发,我们已经搭建了一个基本的企业级门户网站框架,后续将继续完善功能和优化性能,打造一个功能完善、性能优异的企业级门户网站。