news 2026/10/15 4:21:04

企业门户网站开发实战:Django + Vue 前后端分离与部署指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
企业门户网站开发实战:Django + Vue 前后端分离与部署指南

说起来,企业门户网站算是 Web 开发里“看着简单、做起来碎活儿一堆”的项目类型。无非是首页、新闻、产品、关于我们、联系页面,再加一个能维护内容的后台。可真等你去设计数据库表、处理图片上传、调跨域、考虑部署环境的时候,就会发现每一环都有细节要抠。我这次用基于 Python 和 Vue 的技术组合从头做了一套企业门户,后端 Django 提供 API,前端 Vue 3 做页面展示和后台管理,整个项目从前期的需求拆解到最终的服务器部署,花了大概两周时间。这篇文章把我整个过程中的设计思路、关键代码、以及踩过的坑完整记录下来,如果你正好准备做同类型项目,可以直接拿这套方案当参照。

1. 项目整体设计与技术选型思路

1.1 企业门户网站的隐性需求

第一件事不是装环境,是花半天时间把需求剥开。企业门户网站表面上是“信息公开”,但真实项目里往往藏着三四层隐性需求。

首先是内容维护。公司内部没有专职前端,运营编辑需要自己发布新闻、更新产品、调整轮播图。这意味着网站必须有一套后台管理系统,而且后台操作越简单越好,最好能做到“打开网页就能改”。

其次是展示效果。门户网站是企业的门面,首页要大气、专业,同时适配 PC 和手机,加载速度不能慢。这个需求直接决定了前端不能堆太多重型库,图片也得做按需加载和压缩处理。

第三是可扩展性。今天做的是官网,明天可能加一个手机 App、小程序,或者行业解决方案专题页。如果后端把页面渲染和业务逻辑耦合在一起,后续扩展会非常痛苦。所以从第一天起就应该把后端定位成纯 API 服务。

第四是安全合规。企业网站需要处理用户留言、联系方式、后台登录等信息,防 SQL 注入、防 XSS、密码加密存储、接口限流,都是不可回避的问题。很多赶工期的门户站在这些地方几乎是裸奔状态,后期一旦被爬虫抓爆或者被注入,补救成本很高。

把这些隐性需求理清之后,技术选型的方向基本就明确了。

1.2 技术组合的选型逻辑

后端我选择 Django,原因有三点。

第一,Django 自带 ORM 和迁移机制。企业门户的数据模型无非是新闻、产品、分类、轮播图这些,用 Django 的模型类定义之后,一条 migrate 命令就能同步数据库表结构,比手写 SQL 高效太多,后续改字段也方便。

第二,Django Admin 在项目初期能帮大忙。虽然最终我要用 Vue 做定制后台,但在开发阶段,Admin 开箱即用的内容增删改查功能可以让前端还没搭起来的时候就先录入测试数据,后端联调效率高很多。

第三,Django 的用户认证体系成熟。Session、Token、权限组、密码哈希,这些都是经过了大量生产环境验证的轮子,不需要自己造。

前端选择 Vue 3 而不是 jQuery 或者 React,理由也很实际。Vue 的渐进式特性对后端开发者非常友好,只学一个核心框架加 Vue Router、Pinia 就能应付门户站这种规模;组件化能力足够应付后台管理页面这种表单密集型场景;生态里的 Element Plus 提供了现成的后台 UI 组件,表格、弹窗、表单校验都可以直接拿来用,能省掉大量写样式的日子。

前后端分离是刻意做出的选择。Django 不做模板渲染,只返回 JSON;Vue 通过 axios 请求接口,自己管理页面状态。这样做的直接好处是,未来的小程序端和移动端可以直接复用同一套 API,后端不需要为了新的客户端重新开发一套接口。坏处也很明显,跨域、部署、认证都会比单体多一层配置,这部分我放到后面的章节专门讲。

1.3 整体架构与数据库规划

整个系统按三层结构来组织:

展示层是 Vue 单页应用,构建产物部署到 Nginx;接口层是 Django + Django REST Framework,通过 Gunicorn 托管;数据层用 MySQL 8.0,业务数据统一存放。

选择 MySQL 而不是 SQLite,是因为门户网站的新闻和产品数据会持续增长,而且部署环境往往是云服务器上的独立数据库服务,MySQL 在这种场景下最成熟、文档最多,排坑时能找到大量现成参考。开发环境用 SQLite、生产环境切换 MySQL 的做法也很常见,用 Django 的配置分离可以做到一键切换,我后面会给出配置文件示例。

数据库表设计上,我把核心表分成五类:

  • 用户表,存放后台管理员账号,包括用户名、密码哈希、角色、状态;
  • 分类表,区分新闻分类和产品分类两种用途,用 type 字段做区分;
  • 内容表,包括新闻表和产品表,里面冗余了一个分类外键;
  • 展示配置表,包括轮播图表、站点信息表,用于控制首页轮播图和底栏信息;
  • 操作日志表,记录后台的关键操作,方便问题排查。

字段设计上我坚持几个原则:能用整型表示的绝不用字符串,比如分类的 type 字段直接用 SmallIntegerField;时间字段统一用 DateTimeField 且带时区,避免前端展示时出现时差偏差;小状态值用 BooleanField 或 SmallIntegerField,避免后续扩展枚举值时修改字段类型。

2. 后端设计与核心实现(Django + DRF)

2.1 环境准备与项目初始化

开发环境我用的是 Python 3.10,虚拟环境推荐 venv,轻量且干净。依赖安装命令如下:

python -m venv venv source venv/bin/activate pip install django djangorestframework django-cors-headers pillow mysqlclient

django-cors-headers 一开始就装上,后面联调见到 CORS 报错时会感谢这个决定。pillow 是 Django ImageField 上传图片时的依赖,漏掉的话同步模型后跑迁移不会报错,但一传图片就会直接炸。

django-admin startproject portal_backend cd portal_backend python manage.py startapp contents python manage.py startapp users

我把应用分成 contents 和 users 两个模块,contents 管资讯和产品,users 管管理员账号。项目规模不大,两个 app 足够清晰,再多就过度设计了。

这里提醒一个版本兼容问题。新版 Django 对 MySQL 的默认连接驱动还是 mysqlclient,如果在 Windows 上直接 pip 安装容易碰到编译错误,最好是提前装好 C++ Build Tools,或者换用 pymysql 并在项目init.py 里做适配。我在 Linux 云服务器上开发,一条命令就装完了,省了不少麻烦。

2.2 数据模型设计:字段与关系的处理技巧

contents 应用里最核心的模型是新闻模型和产品模型。以新闻模型为例:

from django.db import models from django.contrib.auth.models import User class News(models.Model): title = models.CharField(max_length=200, verbose_name='标题') category = models.ForeignKey('Category', on_delete=models.PROTECT, verbose_name='分类') cover = models.ImageField(upload_to='news/covers/', blank=True, null=True, verbose_name='封面图') content = models.TextField(verbose_name='正文内容') author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='发布人') is_published = models.BooleanField(default=False, verbose_name='是否发布') view_count = models.IntegerField(default=0, verbose_name='浏览数') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'news' ordering = ['-created_at']

几个字段设计上的关键点。

外键约束用 on_delete=models.PROTECT 而不是 CASCADE。企业门户的分类删除应该被阻止,防止管理员误删某个分类导致一批新闻一起消失。PROTECT 会让 Django 在删除分类时抛出 ProtectedError,配合前端提示“该分类下存在内容,无法删除”,这个交互逻辑是必要的。

发布人字段用 SET_NULL 而不是级联删除。如果用户被删除,新闻不能跟着被删,但也不能保留一个失效外键,SET_NULL 是最合理的处理。

布尔字段 is_published 默认设为 False 是我特意选的。如果默认 True,开发阶段想测试草稿功能就很麻烦。列表接口默认只返回已发布的数据,管理端则可以通过专属接口查看全部数据。

产品模型和新闻模型结构类似,但额外增加了 price 字段和 stock 字段。这些字段不是必须,但企业和客户沟通时经常需要展示价格区间,预先设计好很有用。价格用 DecimalField(max_digits=10, decimal_places=2),不用 FloatField,因为数据库浮点数做精确比较会出问题。

分类表我用了自关联结构:

class Category(models.Model): TYPE_CHOICES = ( (1, '新闻分类'), (2, '产品分类'), ) name = models.CharField(max_length=50) type = models.SmallIntegerField(choices=TYPE_CHOICES, default=1) parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)

分类支持二级层级,虽然当前场景用不到太深,但预留 parent 字段成本极低,后期扩展专题页时可以少一次数据库迁移。

2.3 API 接口:从 ViewSet 到 Serializer 的落地

Django REST Framework 是后端 API 的核心,我按资源划分接口,用 ModelViewSet 生成标准 CRUD。以新闻接口为例:

class NewsViewSet(viewsets.ModelViewSet): queryset = News.objects.filter(is_published=True) serializer_class = NewsSerializer permission_classes = [IsAuthenticatedOrReadOnly]

filter(is_published=True) 直接约束了查询集,匿名用户只能看到已发布内容。但管理员登录后如果需要看草稿,就得重写 get_queryset 或者加一个管理专用接口。我是加了一个自定义 action:

@action(detail=False, methods=['get'], permission_classes=[IsAdminUser]) def admin_list(self, request): queryset = News.objects.all() page = self.paginate_queryset(queryset) serializer = NewsSerializer(page, many=True) return self.get_paginated_response(serializer.data)

序列化器里处理外键嵌套也是个常见的坑。如果直接序列化 category 外键,前端拿到的是分类的 id,展示时还得再查一次分类表。更实用的做法是写一个嵌套序列化器,或者在序列化器里加自定义字段:

class NewsSerializer(serializers.ModelSerializer): category_name = serializers.CharField(source='category.name', read_only=True) author_name = serializers.CharField(source='author.username', read_only=True) class Meta: model = News fields = ['id', 'title', 'category', 'category_name', 'cover', 'content', 'is_published', 'view_count', 'created_at', 'author_name']

前端一次请求就能拿到展示所需的全部字段,减少一次网络往返。

ModelViewSet 虽然省事,但不能把标准 CRUD 接口全部暴露给公网。我的处理方式是:门户前台只开放列表和详情接口,后台管理单独用 admin_ 前缀的接口,并且全部加上 IsAdminUser 权限。路由分发也要跟着调整,让对外接口只映射到 list 和 retrieve 两个行为。

2.4 认证方案:JWT 还是 Session

后台登录认证我选了 JWT,用的是 SimpleJWT 库。企业门户的运营人员通常在一个固定的后台页面上登录,传统 Session 方案也够用,但 JWT 的优势在于:前端拿到 token 后可以存到 localStorage,请求接口时放在 Authorization 头里,后端不依赖服务端会话存储,以后做水平扩展时不需要共享 Session,以后要加用户端登录也方便。

配置如下:

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], }

开发时容易踩的坑是:如果 DEFAULT_PERMISSION_CLASSES 全局配了 IsAuthenticated,匿名访问新闻列表也会被拦截。要在具体的 ViewSet 里覆盖 permission_classes,比如对外展示接口用 IsAuthenticatedOrReadOnly,后台接口用 IsAdminUser。

JWT 的过期时间我也调过。SimpleJWT 默认的 ACCESS_TOKEN_LIFETIME 是 5 分钟,对门户后台来说太短,运营人员可能正在编辑一篇长文章,突然就 401 了。我把访问 token 改成了 2 小时,刷新 token 有效期 7 天,兼顾安全和体验。

密码加密这块直接用 Django 自带的 make_password 和 check_password,不重复造轮子。创建管理员账号时走 createsuperuser 或者代码里调 create_user 方法,确保密码经过 PBKDF2 哈希。

3. 前端设计与核心实现(Vue 3 + Element Plus)

3.1 工程搭建与目录规范

前端我用 Vite 初始化 Vue 3 项目。Vite 的启动速度和热更新体验比老一代的 webpack 构建方案好非常多,尤其对门户这种页面组件多的项目,调试效率提升明显。

npm create vite@latest portal_frontend -- --template vue cd portal_frontend npm install vue-router@4 pinia axios element-plus

目录规划上,我按功能分包而不是按文件类型分包:

  • src/api:存放所有接口请求模块,对应后端每个资源一个文件;
  • src/router:路由配置文件,管理页面路径和导航守卫;
  • src/stores:Pinia 状态管理,目前主要管用户登录状态;
  • src/views:页面组件,按 route 一一对应;
  • src/components:通用组件,比如分页组件、上传组件、富文本组件;
  • src/utils:工具函数,包括封装的 axios 实例。

这样分的好处是,后端加一个接口时,改动点集中在 src/api/ 对应的文件里,不会到处找请求代码。

3.2 路由设计与导航守卫

门户网站的路由分成两个区域:访客可以访问的前台页面,以及需要登录的后台页面。

{ path: '/', component: HomeView, meta: { title: '首页' } }, { path: '/news/:id', component: NewsDetailView, meta: { title: '新闻详情' } }, { path: '/products', component: ProductsView, meta: { title: '产品中心' } }, { path: '/admin', component: AdminLayout, meta: { requiresAuth: true }, children: [ { path: 'dashboard', component: DashboardView }, { path: 'news', component: NewsManageView }, { path: 'banner', component: BannerManageView } ] }

导航守卫是后台权限控制的第一道关口:

router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !authStore.isLoggedIn) { next('/login') } else { next() } })

这里要说明一点:前端导航守卫只是用户体验层面的拦截,真正的安全校验在后端接口权限上。前端即使绕过守卫拿到了页面,没有 token 也调不到数据。

3.3 axios 封装与拦截器:统一处理 token 和错误

axios 实例的封装是整个前端的数据通道。我封装了一个 request 模块:

import axios from 'axios' import { ElMessage } from 'element-plus' import { useAuthStore } from '@/stores/auth' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) request.interceptors.request.use(config => { const authStore = useAuthStore() if (authStore.token) { config.headers.Authorization = `Bearer ${authStore.token}` } return config }) request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { const authStore = useAuthStore() authStore.logout() router.push('/login') } ElMessage.error(error.response?.data?.message || '请求失败,请稍后重试') return Promise.reject(error) } )

这个封装把 token 注入、401 自动退出、错误提示统一收敛到一处,后面写具体接口时只需要关心数据返回。

需要注意一个细节:在拦截器里使用 Pinia store 时,不要在模块顶层直接获取 store 实例,因为那一刻还没创建 pinia 实例。要等到请求真正发生时再调用 useAuthStore(),像我代码里这样写在拦截器回调内部就可以了。这是不少新手会遇到的运行时错误。

3.4 核心页面实现:门户展示与后台管理

门户首页是整个项目投入精力最大的页面,要兼顾视觉效果和加载性能。我把它拆成了轮播区、新闻列表区、产品精选区和联系我们区四个模块。

轮播图组件用 Element Plus 的 el-carousel 就可以,数据从 /api/banners/ 拉取。这里有个优化细节:轮播图是首屏资源,不能懒加载,否则会出现明显的加载闪烁。我在 img 标签上直接设置了加载优先级。

产品列表页实现了分类筛选和分页。分类筛选用 tab 切换,内部用 activeCategory 的 ref 记录当前选择,点击时重新调用列表接口:

const loadProducts = async () => { loading.value = true try { const res = await productApi.getList({ category: activeCategory.value, page: currentPage.value, page_size: pageSize.value }) products.value = res.results total.value = res.count } finally { loading.value = false } }

后台管理页面是表单密集型场景。我用 Element Plus 的 el-table 展示数据列表,搭配 el-dialog 弹窗做新增和编辑表单。整个后台管理的核心逻辑都围绕“表单校验 + CRUD 请求”来组织。

后台新闻管理页包含这些功能:按关键词和状态筛选列表、新增/编辑新闻弹窗、上传封面图片、富文本编辑正文、一键发布/下线、删除确认。表单校验在 Element Plus 里直接声明 rules 即可,最常见的坑是富文本内容校验时要拿到真实值,而不是拿包装后的响应式对象去校验。

关于富文本编辑器,企业门户的运营人员经常要排版图文,我试过多个方案,最终用的是一个基于 Quill 的轻量级组件。不需要很重型的编辑器,够用、能粘贴图片就行。在 Vue 3 + Vite 下有些富文本组件会碰到兼容性问题,遇到报错时优先查包的 exports 字段是否兼容浏览器构建。

3.5 状态管理:用户登录状态的持久化

Pinia 在项目里的职责很轻,主要是管理登录状态。用户登录后把 token 和用户信息写入 store,同时持久化到 localStorage:

export const useAuthStore = defineStore('auth', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null') }), actions: { login(data) { this.token = data.access this.userInfo = data.user localStorage.setItem('token', data.access) localStorage.setItem('userInfo', JSON.stringify(data.user)) }, logout() { this.token = '' this.userInfo = null localStorage.removeItem('token') localStorage.removeItem('userInfo') } } })

初始化时从 localStorage 恢复状态,避免页面刷新后登录态丢失。这个方案对门户后台已经够用,不需要额外引入持久化插件。

4. 前后端联调与部署实践

4.1 CORS 跨域:一次配置两个面

前后端分离项目联调时第一个遇到的就是跨域问题。浏览器会拦截跨域请求,Django 后端需要明确告诉浏览器允许哪些来源跨域。

在 Django 侧,安装 django-cors-headers 之后要在 settings.py 里做三件事:

INSTALLED_APPS = [ ... 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ... ] CORS_ALLOWED_ORIGINS = [ 'http://localhost:5173', 'http://example.com', ]

CORS_ALLOWED_ORIGINS 里开发环境要放 Vue 项目的地址,也就是 Vite 默认的 localhost:5173,生产环境要放你的域名。如果图省事直接 CORS_ALLOW_ALL_ORIGINS=True,开发阶段影响不大,生产环境非常不建议。一旦日志里出现可疑来源的请求,排查成本会很高。

还要留意预检请求。POST 请求带 JSON 和 Authorization 头时,浏览器会先发一个 OPTIONS 预检,Django 要能响应这个 OPTIONS。django-cors-headers 的中间件默认会处理,但如果路由里自己写了 OPTIONS 相关逻辑,可能会互相干扰。我的建议是在 APIRouter 里保持 DRF 自带的路由分发,不要手动添加 OPTIONS 兼容代码。

前端侧也有一个细节:axios 默认不会携带跨域 cookie,如果认证选择的是 Session 方案,还得设置 withCredentials。但 JWT 方案把 token 放在 header 里,不涉及这个配置,这也是我选 JWT 的原因之一。

4.2 图片上传与静态文件访问

门户网站涉及的图片分两类:后台运营上传的封面图,以及前端 build 产物里的静态资源。

运营上传的图片通过 Django 的 Media 机制处理:

MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'

开发环境下,在 urls.py 里额外配置:

from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

这里有个容易踩的坑:前端展示图片时,接口返回的 cover 字段是相对路径,比如 /media/news/covers/xxx.jpg,前端要手动拼接上后端的域名或反代地址。我的做法是在 axios baseURL 之外单独提取一个图片基址工具函数,只要环境变量里的 API 地址是完整的部署前缀,图片地址就能正确拼出来。

生产环境的 media 目录牵涉到权限和容量。部署时我把 media 目录放在源码目录外面,用绝对路径配置 MEDIA_ROOT,后面用 Nginx 直接 alias 到对应目录,这样就不会在每次重新部署代码时把用户上传的图片一起覆盖掉。很多项目上线一段时间后改版发布会发现之前的图片全没了,往往就是这个原因。

4.3 前端构建与 Nginx 反向代理

前端开发完成后执行构建命令:

npm run build

Vite 会在 dist 目录生成静态资源。部署层我用 Nginx 做统一入口,同时处理前端静态资源和后端 API 请求。

一个经过真实验证的 Nginx 站点配置:

server { listen 80; server_name example.com; client_max_body_size 20m; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /media/ { alias /var/www/portal/media/; expires 7d; } location /static/ { alias /var/www/portal/static_collected/; expires 7d; } location / { root /var/www/portal/dist; try_files $uri $uri/ /index.html; } }

location /api/ 把 API 请求反向代理到 Django 服务,客户端看到的还是同一个域名,生产环境自然没有跨域问题。这也意味着生产环境的 CORS_ALLOWED_ORIGINS 可以只保留同源配置。

location / 这里有一个门户 SPA 部署的关键点:try_files $uri $uri/ /index.html。如果不加这个配置,用户访问 /news/3 并刷新页面时,Nginx 找不到对应的物理文件会返回 404。单页应用的 history 路由模式必须处理这个 fallback。

Django 侧的 collectstatic 也得做。Django Admin 和 DRF 自带页面的样式文件要收集到一个目录,便于 Nginx 引用。在 settings.py 里设置 STATIC_ROOT 后执行:

python manage.py collectstatic

4.4 Django 服务的进程托管

生产环境不能直接用 runserver 跑,我用 Gunicorn 启动:

gunicorn portal_backend.wsgi:application -w 4 -b 127.0.0.1:8000

4 个 worker 对中小型门户网站足够。如果服务器 CPU 核心数多,可以用 2 倍核心数加 1 的公式估算,但企业门户的数据库访问和图片读取占大头,4 个进程在 2 核 4G 的云服务器上表现很稳定。

进程守护我用 Supervisor,把 Gunicorn 托管为服务,崩溃后自动拉起:

[program:portal] command=/var/www/portal/venv/bin/gunicorn portal_backend.wsgi:application -w 4 -b 127.0.0.1:8000 directory=/var/www/portal/backend autostart=true autorestart=true user=www-data

这条部署链路的完整逻辑是:用户访问域名,Nginx 处理静态文件或反向代理,Gunicorn 接收请求,Django 处理业务,MySQL 读写数据。任一环节出问题,都能顺着这条链路排查。

5. 常见问题与排查技巧实录

项目开发过程中踩过的比较有代表性的问题,我整理成速查表,方便你直接对号入座。

现象原因解决方案
前端请求接口报 CORS 错误Django 未配置 CORS_ALLOWED_ORIGINS 或中间件顺序不对安装 django-cors-headers,确保 CorsMiddleware 放在其他中间件前面
上传图片出现 500 错误缺少 pillow 依赖或 media 目录不可写安装 pillow,检查 media 目录属性和权限
刷新前端页面 404Nginx 没有配置 try_files 回退在 location / 中添加 try_files $uri $uri/ /index.html
登录后访问后台提示 401JWT 过期时间过短,或 token 未放入请求头调大 ACCESS_TOKEN_LIFETIME,检查 axios 拦截器是否正确注入 Authorization
数据库迁移报错mysqlclient 未安装或版本不兼容安装匹配 MySQL 8 的驱动,必要时用 pymysql 适配
序列化器返回的外键只有 id未处理外键嵌套展示用 source 参数或嵌套序列化器补充展示字段
部署后 Admin 样式丢失未执行 collectstatic执行 python manage.py collectstatic,并配置 STATIC_ROOT
富文本内容保存后样式错乱编辑器生成的 HTML 被自动转义根据编辑器类型正确处理安全过滤或 HTML 渲染

除了表格里的问题,我再分享几个文档里很少写到的技巧。

第一个是关于查询性能。新闻列表接口如果直接查全表,数据过百条后分页和排序会明显变慢。我给 News 表在 category 和 created_at 字段上加了数据库索引,具体做法是在模型 Meta 里的 indexes 属性中声明。企业门户虽然数据量不大,但响应速度快对用户体验是有实际价值的。

第二个是关于后端接口的缓存策略。门户网站的内容变化频率低但访问量高,我在新闻详情接口上做了一层简单的 Redis 缓存:查询时先查缓存,没有就查数据库再写入缓存,内容更新后主动删除对应缓存的 key。这个方案改动很小,对列表页和详情页的响应时间提升非常明显。

第三是关于 Vue 打包体积优化。Element Plus 默认全量引入会导致首屏 JS 很大。我用自动按需引入的方式,只打包用到的组件和样式。具体做法是安装 unplugin-auto-import 和 unplugin-vue-components 两个插件,并在 vite.config.js 里配置 ElementPlusResolver。打包完成后,dist 目录体积从 2MB 左右降到 800KB 左右,首屏加载速度提升显著。

第四是关于前后端环境变量。Vite 支持通过 .env.development 和 .env.production 区分开发环境和生产环境的 API 地址。开发环境指到 http://localhost:8000,生产环境指到 https://example.com/api,这样切换环境完全不用改业务代码,这是前后端分离项目的标配。

第五是关于接口分页方案。DRF 默认的分页返回结构是 {count, next, previous, results},我在前端 axios 封装里直接解构出 results 和 count,没有做二次转换。前后端约定好这一层数据结构,后面所有列表页的写法都是同一套模式。

这套 Python + Vue 的企业门户从零搭完,我的最大体会是:前后端分离的门户项目,前端难点不在页面长什么样,而在请求层的统一封装、路由权限的流转、各种组件的兼容适配;后端的难点也不在业务逻辑本身,而在模型字段设计的取舍、权限边界的划分、部署链路的每一环是否都跑通。

如果你正准备做同类型项目,建议先把 Admin 后台和基础 CRUD 接口跑通,再动手做前端页面,这个顺序能让你在联调时少走很多弯路。我在实际项目里最后悔的一件事,是前期为了省事没有写接口文档,后期前后端联调时全靠口头同步,一旦接口字段改了就得挨个页面排查。如果你参考这套方案,强烈建议从一开始就用 DRF 的自动文档功能,哪怕粗糙一点,整个合作过程也会顺畅很多。

这个项目后续如果想扩展,方向其实很清晰:给新闻详情页加阅读量统计和评论模块,给门户加站内搜索接口,接入对象存储来管理越来越大的图片资源,都是水到渠成的事。不过那是下一阶段的故事了,眼下这套门户已经稳定跑起来。上面的代码细节和坑位记录,希望能帮你少踩几个我踩过的雷。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 4:19:24

可信计算3.0实战:从信任根到动态度量的部署与避坑指南

简介:这份PDF资料聚焦可信计算3.0技术及其应用实践,面向网络安全从业者、等级保护测评人员及信息安全专业师生,帮助读者理解可信计算从被动防御向主动免疫演进的技术脉络与落地路径。资源为单个PDF文件,压缩包约4.3MB,…

作者头像 李华
网站建设 2026/10/15 4:19:23

Vue3+Node.js全栈实战:登录/权限/文件上传闭环项目

简介:这是一套基于 Vue 3 与 Node.js 构建的全栈实战项目源码,面向前端进阶开发者、全栈学习者及中小型系统原型开发者,聚焦 Web 终端交互与命令式操作场景的工程化实现。项目从前端终端界面、命令解析执行到后端服务响应形成完整闭环&#x…

作者头像 李华
网站建设 2026/10/15 4:18:43

FNF剧情型模组全解析:安装、排错与“争抢BF”的原理

如果你经常逛节奏游戏社区,大概见过这类描述:Pico 和岳父在争夺 BF。第一次看到这种说法的人,很可能会被绕晕——这不是同一个世界观下的角色吗?怎么突然变成了三角关系?这到底是在玩梗,还是模组里真实发生…

作者头像 李华
网站建设 2026/10/15 4:18:42

降AI率实战指南:从检测原理到工具选型的完整方案

写论文翻车名场面:洋洋洒洒两千字初稿,导师看完就一句话:"这段是你让AI写的吧?"你还没来得及否认,导师已经把屏幕转过来,指着第三段说:"别的不说,这一段四句话全是同…

作者头像 李华