
1. 先想清楚“高校社团管理系统”要管什么再决定用 Django 还是 Flask每年到这个时间都会有学生把同样一句话发给我我想做一个 Python Vue 的高校社团管理系统后端用 Django 还是 Flask如果只看 PyCharm 里的报错和 npm 里的依赖这个问题确实会让人纠结。但以我做过几个类似系统的经验来说更常见的问题是项目做到一半发现“社团”到底要经历哪些流程、谁有权审核、活动报名之后状态怎么变这些需求根本没想清楚。这个系统的本质不是写出一组增删改查页面而是把“学校社团运行规则”变成一套稳定的业务模型。你面对的主要用户不是一类人而是至少三类普通学生、社团负责人、校级管理端用户。三类人对系统的诉求差异很大。普通学生最关心的是能看到哪些社团、怎么加入、活动怎么报名社团负责人关心的是成员怎么管理、活动怎么发起、经费怎么记录校级管理端用户关心的是新社团能不能成立、活动是否合规、各项数据是否能快速看到。如果只做一个列表式页面满足不了任何一方最后展示的时候功能很单薄。1.1 系统里不是只有“学生”和“社团”两种角色我看到很多初期设计都会把角色简化成“普通用户”和“管理员”把社团负责人当成普通用户的一种高级状态然后靠一个布尔字段去判断。这种设计在一两个模块时还能撑住一旦加入“审批”和“状态流转”代码会越来越拧巴。实际项目里角色至少要拆成四类student学生注册、浏览社团、申请加入、报名活动club_manager社团负责人管理自己名下社团的资料、成员、活动、经费supervisor/admin社联或团委管理端审核社团成立申请、审核活动、发布校级通知、查看统计superuser系统管理员处理运维问题日常用得少角色拆分不只是为了点击不同菜单而是为了后端权限判断和 Vue 前端路由守卫都能围绕“一个用户能做什么”去设计。比如社团负责人只能管理自己创建的社团不能把别人社团的活动删掉管理端可以审核活动但不能代替负责人填写活动内容。1.2 第一个版本只需跑通这几条核心链路很多初学者总想把界面做得特别丰富甚至每个模块都来一张表。实际上评审老师或者项目经理最关心的是几条关键业务链路是否走通。我做这个系统时会把 MVP 砍成下面几条学生注册、登录后能浏览“已通过审核”的社团列表学生能提交入社申请社团负责人能在后台同意或拒绝社团负责人能创建社团但状态默认是“待审核”管理端审核通过后才会公开社团负责人能发布活动学生能报名活动报名人数不能超过容量管理端能看到所有业务数据并执行审核操作与其一开始就做社团留言板、附件上传、消息通知、数据大屏不如先把这几条链路跑通。因为这些链路牵涉到用户、社团、成员关系、活动、报名、审批状态和数据统计能把这些做完就已经覆盖了系统的大部分“硬骨头”。1.3 为什么 Python Vue 适合这种课设或毕设系统抛开个人偏好Python Vue 是这个场景下性价比很高的组合。Python 端不管是 Django 还是 Flask写业务逻辑的效率都足够高Vue 组件化很适合“三类角色、三类菜单”的界面差异同一个页面可以因为登录角色不同展示不同按钮。更重要的一点是PyCharm 对前后端分离工程很友好。你可以在一个窗口里同时管理后端 Python 代码、前端 JS 代码、数据库迁移文件和命令行终端。不需要在多个工具之间来回切换环境问题能少很多。对需要一个学期内完成系统设计、编码、文档的学生来说这套组合把技术负担降到比较低能把精力重点放在业务流程和功能完整性上。2. Django 和 Flask 的选型本质是“自己拼还是全家桶”标题里同时出现 Django 和 Flask 很常见因为你随便搜一个社团管理系统可能看到有人用 Django 写也有人用 Flask 写。对新手来说这看起来只是两个 Web 框架的名字但实际工程体验差别非常大。2.1 Django 的管理后台和 ORM 能省下大量重复工作Django 给我最大的感觉是“组织纪律性强”。它自带 ORM、数据迁移、Admin 后台、认证系统和表单校验创建完项目之后会给你一套比较标准的目录结构。对社团管理系统这类需要多个模型、多个外键关系的项目Django 自带的 ORM 和 migration 功能会减少很多低级错误。举个例子你要建一张“社团成员表”里面需要同时关联用户和社团。Django 模型里写一个外键然后执行python manage.py makemigrations和python manage.py migrate数据库表就同步完成了。如果后面要加一个“加入时间”字段直接改模型再迁移一次。这个流程对项目迭代非常友好。Django 还有个很实用的内置后台admin/。社团管理系统中总有一些需要管理端快速查看和修正数据的需求比如把某个审批状态改一下、给某个活动追加名额。用 Django Admin 只需要配置一个admin.py你就能得到一个可不费太多力气就能用的内部管理界面。即使前台用 Vue 完全分离后台的这个入口依然可以作为维护工具保留。2.2 Flask 入门友好但你要提前知道要补哪些模块Flask 的优点是“轻”一个简单的文件就能起一个 Web 服务很多课程也喜欢从 Flask 开始教。但轻不代表没有复杂度。你要做一个多角色、多模块的社团系统至少需要这些组件Flask-SQLAlchemy处理 ORMFlask-Migrate处理数据库迁移Flask-Login 或 Flask-JWT-Extended处理登录状态Blueprint把模块拆开Flask-CORS解决跨域这些组件本身不难但你要自己想清楚怎么组合。Flask 不会替你做决定模块的目录结构、模型如何拆分、权限怎么统一校验都需要自己搭。如果只是做一个很简单的“社团列表活动报名”示例Flask 完全够用但如果说要做管理端审批、多级角色、活动和经费的后台管理Flask 的“自由”反而会让你多花时间做设计。2.3 我的落地组合DRF 对外Django Admin 对内如果是完整的社团管理系统我更推荐Django Django REST Framework (DRF) Vue3这条路。用 DRF 提供 JSON API 给 Vue 调用用 Django Admin 给内部管理作保底用 Django 自带的用户系统和迁移机制解决认证和数据库变更问题。DRF 还有一个好处它自带浏览式 API 页面。你在浏览器打开一个接口地址以 Token 或 Session 登录后可以直接看到每个接口能返回什么 JSON。这对前后端联调时查看接口非常有用。Vue 端如果报错你可以先不打开前端直接在浏览器访问/api/xxx判断是不是后端问题会节省大量排查时间。2.4 具体版本怎么配比较稳为了减少不必要的折腾我建议使用这些版本组合Python 3.11 或 3.10不要一味追求最新版Django 4.2 LTS这是长期支持版本Django REST Framework 最新稳定版Vue 3 ViteElement Plus 或 Ant Design Vue二选一如果你的老师或项目文档明确要求用 Flask也不要慌。用 Flask-SQLAlchemy 加蓝图也可以完成类似功能。但从工程维护角度说Flask 更适合原型演示Django 更适合结构化开发。我在后面第 4 节会单独给出 Flask 版本的替换思路。3. PyCharm 里从零到一搭前后端工程骨架环境准备是很多人翻车的第一站。这里说的不是代码报错而是“明明照着安装的怎么就是跑不起来”的玄学问题。实际上大多数问题都出在 Python 解释器、npm 源、虚拟环境三个地方。3.1 PyCharm 与 Python 环境准备用社区版也够PyCharm 有社区版和专业版。如果只是做 Python Vue 的课设或训练项目官网下载的社区版完全够用不需要去找乱七八糟的注册码或激活工具。社区版支持 Python 代码编辑、调试、终端、Git这些已经覆盖日常开发。如果你有教育邮箱专业版可以免费申请但不是必要条件。安装完 PyCharm 后还要单独安装 Python。Windows 用户注意安装时勾选“Add Python to PATH”否则后面在终端里输入python会提示找不到命令。macOS 和 Linux 用户一般用系统自带或 Homebrew 安装问题不大。打开 PyCharm 后要给项目指定一个虚拟环境。我习惯用项目根目录下的venv目录。虚拟环境的作用是让 Django/Flask 的依赖只装在这个项目里不会污染全局 Python 环境也能避免“换台电脑跑不起来”的问题。3.2 创建 Django 后端项目不要直接在全局环境里装包先用 PyCharm 新建一个空项目例如叫campus_club_system。然后在 PyCharm 的 Terminal 里执行python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install django djangorestframework django-cors-headers python-dotenv激活虚拟环境后再安装 Django。注意终端提示符前面会出现(venv)这表示当前已经进入虚拟环境。如果你发现自己执行pip install后django-admin仍然找不到大概率是没激活虚拟环境。创建项目和应用django-admin startproject campus_club_backend cd campus_club_backend python manage.py startapp accounts python manage.py startapp clubs python manage.py startapp activities python manage.py startapp finance这里的accounts负责用户与认证clubs负责社团和成员activities负责活动和报名finance负责经费。名字按业务域拆而不是按“模型、视图、路由”这类代码层拆后面维护会清晰很多。在campus_club_backend/settings.py的INSTALLED_APPS里加上INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, rest_framework, corsheaders, accounts, clubs, activities, finance, ]顺手把MIDDLEWARE里的corsheaders.middleware.CorsMiddleware加上。如果缺失Vue 页面访问接口时会经常被浏览器拦跨域。3.3 创建 Vue3 前端项目选 Vite 而不是 Vue CLI后端骨架搭好后回到项目根目录用 Vite 创建前端。现在 Vue3 官方推荐的是npm create vuelatest它会生成一个比较规范的基础工程并且默认使用 Vite。npm create vuelatest frontend cd frontend npm install npm install axios vue-router pinia element-plus如果你看到“是否安装依赖”之类的提示选择 yes。把 Vue Router、Pinia 都加进去因为后续登录守卫和用户状态都依赖它们。很多老教程会推荐 Vue CLI也就是npm install -g vue/cli然后vue create frontend。说实话这条路现在有点过时了Vite 启动速度明显更快官方生态也已经完全切换。对新手来说Vite 的控制台报错更容易看懂。3.4 目录结构建议一个仓库两边隔离我建议把一个前后端分离项目放在同一个仓库下但不强耦合campus_club_system/ ├── venv/ ├── frontend/ │ ├── src/ │ ├── package.json │ └── vite.config.js └── campus_club_backend/ ├── accounts/ ├── clubs/ ├── activities/ ├── finance/ ├── manage.py └── requirements.txt这样的好处是方便整体打包和提交。你只需要一个 PyCharm 窗口打开根目录前后端代码都能看到。前端依赖是frontend/package.json后端依赖是campus_club_backend/requirements.txt两侧相对独立。如果以后项目变得很大再把前后端拆成两个独立仓库也不难。3.5 PyCharm 中同时启动前后端开发时需要两个进程后端python manage.py runserver 8000前端npm run dev -- --host 0.0.0.0或直接npm run dev我通常会给 PyCharm 配两个 Run Configuration也可以直接开两个终端窗口。后端跑在8000前端 Vite 默认跑在5173。为了让 Vue 请求后端不出现跨域问题最好在vite.config.js里配置代理server: { port: 5173, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, } } }这样前端页面里请求/api/clubsVite 开发服务器就会把它转发到后端的http://127.0.0.1:8000/api/clubs。浏览器里看不到跨域问题联调体验会好很多。4. 把社团业务落进数据库角色、审批与状态机到这一步代码能跑起来还不算完最关键的部分开始了。社团管理系统如果只是建几张表那和 Excel 没有区别。真正有价值的是把“谁能看、谁能改、状态怎么变”用数据模型和接口约束起来。4.1 用户模型给 Django User 挂上角色字段Django 默认的User表已经有用户名、密码、邮箱、是否管理员这些字段但缺少“角色”“学号”这些业务字段。最干净的做法是定义一个继承AbstractUser的模型而不是另建一张 UserProfile 表。后者不是不行但查询时多一层关联代码写起来更啰嗦。# accounts/models.py from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): class Role(models.TextChoices): STUDENT student, 学生 CLUB_MANAGER club_manager, 社团负责人 ADMIN admin, 管理员 role models.CharField( max_length20, choicesRole.choices, defaultRole.STUDENT ) student_no models.CharField(max_length20, blankTrue)设置好模型后还需要在settings.py里告诉 Django 使用自定义用户模型AUTH_USER_MODEL accounts.User这一点非常重要。如果一开始没有指定等到执行第一次迁移后再改会很麻烦。4.2 社团表与成员表既要审批也要角色社团不会是学生点一下“创建”就立刻出现在列表里。它必须有一个“提交申请 - 管理端审核 - 审核通过”的过程。所以我们给Club加一个status字段# clubs/models.py from django.db import models from django.conf import settings class Club(models.Model): class Status(models.TextChoices): PENDING pending, 待审核 APPROVED approved, 已通过 REJECTED rejected, 未通过 name models.CharField(max_length100, uniqueTrue) description models.TextField() category models.CharField(max_length50) owner models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameowned_clubs ) status models.CharField( max_length20, choicesStatus.choices, defaultStatus.PENDING ) created_at models.DateTimeField(auto_now_addTrue)成员关系单独用一张表维护。因为一个学生可以参加多个社团一个社团有多名成员这是典型的多对多关系但多对多关系需要额外字段比如加入时间、在社团内的职能。不要图省事用 Django 的ManyToManyField挂到 Club 上扩展不方便。# clubs/models.py class ClubMember(models.Model): class RoleInClub(models.TextChoices): OWNER owner, 负责人 MANAGER manager, 干事 MEMBER member, 成员 club models.ForeignKey(Club, on_deletemodels.CASCADE, related_namemembers) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameclub_memberships) role models.CharField(max_length20, choicesRoleInClub.choices, defaultRoleInClub.MEMBER) joined_at models.DateTimeField(auto_now_addTrue) class Meta: constraints [ models.UniqueConstraint(fields[club, user], nameunique_club_user) ]社团创建者owner同时也应该有一条ClubMember记录角色是owner。这样查询“某个社团的管理员有哪些”会很方便不用到处判断owner字段。4.3 活动与报名把状态变成可维护的字段活动模块最容易出现的问题是活动有草稿、已发布、已结束、已取消等多种状态但很多人只用一个is_active布尔字段。布尔字段只能表达“是或否”当活动取消后你还需要保留活动信息同时不想让它继续显示报名入口这时布尔字段就不够用了。推荐这样设计# activities/models.py class Activity(models.Model): class Status(models.TextChoices): DRAFT draft, 草稿 PUBLISHED published, 已发布 ENDED ended, 已结束 CANCELLED cancelled, 已取消 club models.ForeignKey(clubs.Club, on_deletemodels.CASCADE, related_nameactivities) title models.CharField(max_length200) description models.TextField() location models.CharField(max_length200) start_time models.DateTimeField() end_time models.DateTimeField() capacity models.PositiveIntegerField() status models.CharField(max_length20, choicesStatus.choices, defaultStatus.PENDING) created_at models.DateTimeField(auto_now_addTrue)活动也应该有一个审批状态所以我这里写的默认值是Status.PENDING但Status枚举里没有pending。需要补一个PENDING或者把“是否发布/是否审核”拆开。最简单的方法是再增加一组状态class Activity(models.Model): class Status(models.TextChoices): PENDING pending, 待审核 PUBLISHED published, 已发布 REJECTED rejected, 未通过 ENDED ended, 已结束 CANCELLED cancelled, 已取消学生在活动列表里只能看到PUBLISHED状态。如果一个活动是PENDING说明社团负责人在提交活动申请不应该被学生看到。报名表用于记录谁报名了哪场活动class ActivitySignUp(models.Model): activity models.ForeignKey(Activity, on_deletemodels.CASCADE, related_namesignups) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) status models.CharField( max_length20, choices[(registered, 已报名), (attended, 已签到), (cancelled, 已取消)], defaultregistered ) created_at models.DateTimeField(auto_now_addTrue) class Meta: constraints [ models.UniqueConstraint(fields[activity, user], nameunique_activity_user) ]添加唯一约束后同一个学生对同一场活动只能报一次名。这部分逻辑要在数据库层面做一次因为只靠前端隐藏按钮是挡不住重复请求的。4.4 经费记录别用 float 掉精度涉及到钱表里千万不要用FloatField。金额计算应该用DecimalField并且单位为元保留两位小数。经费记录也要关联到社团和操作人方便追溯。# finance/models.py from django.db import models from django.conf import settings from django.core.validators import MinValueValidator class FinanceRecord(models.Model): class RecordType(models.TextChoices): INCOME income, 收入 EXPENSE expense, 支出 club models.ForeignKey(clubs.Club, on_deletemodels.CASCADE, related_namefinance_records) record_type models.CharField(max_length10, choicesRecordType.choices) amount models.DecimalField(max_digits10, decimal_places2, validators[MinValueValidator(0)]) description models.CharField(max_length255) created_by models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.SET_NULL, nullTrue) created_at models.DateTimeField(auto_now_addTrue)经费记录不一定要做复杂账本但至少要能回答两个问题这个社团收了多少钱、花了多少钱。根据这些记录管理端统计页可以按月汇总按社团排序。4.5 DRF 接口权限的后端防线后端接口要考虑两件事登录用户能访问哪些接口以及登录用户操作的数据是否属于自己。DRF 里常见做法是用IsAuthenticated控制“必须登录”用IsAdminUser控制“必须是管理员”。像活动报名这种接口普通学生登录后就能报名。# activities/views.py from rest_framework.permissions import IsAuthenticated from rest_framework.viewsets import ModelViewSet from .models import Activity from .serializers import ActivitySerializer class ActivityViewSet(ModelViewSet): queryset Activity.objects.filter(statuspublished) serializer_class ActivitySerializer permission_classes [IsAuthenticated]如果要实现“社团负责人只能管理自己社团的活动”需要在自定义权限里判断而不是只依赖IsAuthenticated。# clubs/permissions.py from rest_framework.permissions import BasePermission class IsClubOwner(BasePermission): def has_object_permission(self, request, view, obj): return obj.owner_id request.user.id很多同学只在前端判断“你不是负责人就不显示编辑按钮”后端的ViewSet却没有任何权限校验。这样其实不安全因为别人可以直接用接口工具给任意社团发请求。后端权限必须作为最终的防线。4.6 如果你这版坚持用 Flask如果你的项目要求里明确写了 Flask用下面的替换方案能对应上。Flask 本身不提供 Django 那种开箱即用的 ORM所以需要安装 Flask-SQLAlchemy 和 Flask-Migrate。# app.py 片段 from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///project.db db SQLAlchemy() db.init_app(app) migrate Migrate(app, db) class Club(db.Model): id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(100), uniqueTrue) status db.Column(db.String(20), defaultpending)Flask 的登录认证可以选择 Flask-Login 管理 session或者 Flask-JWT-Extended 返回 token。API 接口可以用蓝图按模块拆但多用户角色和权限这块需要自己写装饰器或中间件。按我个人的经验Flask 想完成和 Django 一样的功能并不会在代码量上少很多只是把 Django 默认帮你处理的细节摊到了你自己面前。如果项目周期短、只需要演示核心流程Flask 没问题但是如果你想减少后续“阿姨也会来维护”的麻烦Django 更合适。能力Django DRFFlask 路线ORMDjango ORM 原生支持Flask-SQLAlchemy数据迁移自带 makemigrations/migrateFlask-Migrate管理后台Django AdminFlask-Admin 或自写REST APIDRF 自带路由和序列化Flask-RESTful 或手写登录认证AUTH_USER_MODELFlask-Login / Flask-JWT权限控制permission_classes装饰器5. Vue3 前端联调中绕不开的登录、路由与跨域问题后端接口写好后前端不是简单地请求一下就行。登录态怎么存、路由怎么拦截、按钮怎么根据状态变化这些细节直接影响项目能不能顺利演示。5.1 Vue3 工程内部按业务拆模块不按页面类型堆文件我见过有人把所有 API 请求都写在App.vue里或者把所有页面平铺在views目录下。前端代码确实能跑但到后期加角色权限时非常痛苦。比较清晰的目录结构是这样的src/ ├── api/ │ ├── request.js │ ├── auth.js │ └── club.js ├── router/ │ └── index.js ├── stores/ │ └── user.js ├── views/ │ ├── student/ │ ├── manager/ │ └── admin/ ├── components/ └── App.vueapi里统一封装请求stores里存用户登录信息views按用户角色拆页面。这样前端和后端的模块划分能对得上排查问题时一眼就能知道对应的代码在哪一边。5.2 登录态处理token 放哪、路由守卫怎么拦如果不加额外复杂依赖DRF 自带的 TokenAuthentication 够用。先在settings.py里注册rest_framework.authtoken然后加一条路由from rest_framework.authtoken.views import obtain_auth_token urlpatterns [ path(api-token-auth/, obtain_auth_token), ]前端登录页面提交用户名和密码拿到 token 后保存到localStorage。请求接口时在 axios 拦截器里统一加上请求头。// request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000, }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token token } return config }) request.interceptors.response.use( response response, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default request这段代码的作用是只要登录过request 自动带 token如果后端返回 401说明 token 失效或未登录前端立刻跳回登录页。路由守卫要根据页面要求来判断。比如“发布活动”页面要求club_manager“审核社团”页面要求admin。虽然后端会再次校验但前端提前拦截能让用户体验更好也能避免用户看到一堆没有权限的菜单和空白页面。// router/index.js router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next(/login) return } if (to.meta.role to.meta.role ! role) { next(/403) return } next() })这里要注意role不要只在登录时存一次如果管理端后台把某个用户角色改了前端要能重新获取最新角色信息。稳妥的做法是登录后调用一个/auth/profile/接口拿用户信息再把角色放进 Pinia 或者 localStorage。5.3 报名按钮的三种状态前端要跟后端状态对齐活动详情页的报名按钮不是简单写死一个“我要报名”。根据学生与当前活动的关系按钮至少要有三种状态未报名显示“报名参加”已报名显示“已报名点击取消”报名已满即使未报名也要禁用按钮这三种状态如何得到建议后端提供一个查询接口。比如活动列表里每个活动都返回signed_up和signed_up_count字段然后前端根据这两个字段决定按钮显示。如果后端只返回总报名人数前端依旧无法判断当前登录用户是否已经报名。你把“当前用户”的判断交给前端本地存储是不可靠的因为同一个用户可能换了浏览器。这个逻辑最好放在后端序列化器里根据request.user动态输出。# activities/serializers.py from rest_framework import serializers class ActivityListSerializer(serializers.ModelSerializer): signed_up serializers.SerializerMethodField() signed_up_count serializers.IntegerField(sourcesignups.count, read_onlyTrue) def get_signed_up(self, obj): request self.context.get(request) if request and request.user.is_authenticated: return obj.signups.filter(userrequest.user, statusregistered).exists() return False这样接口返回的 JSON 里signed_up会告诉前端当前用户是否已经报名。相当于后端把判断好了的结果交给前端前端只做展示省掉很多重复逻辑。5.4 联调时跨域和日期两个高频问题跨域问题虽然在开发时可以用 Vite proxy 解决但打包上线后如果前端静态资源由 Nginx 托管后端还是单独跑在 8000同样会出现跨域。最省心的做法是让配置里保留django-cors-headers并在settings.py中配置允许来源CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]如果前后端布置在同一个 Nginx 域名下可以通过反向代理让前端和后端同源这样反而不需要 CORS。具体配置见下一节。日期问题是另一个高频坑。Django 的DateTimeField序列化后一般是 ISO 格式字符串比如2025-05-20T19:00:0008:00。Vue 这边直接显示这个字符串非常难看需要解析成指定格式。建议安装dayjsnpm install dayjs然后在页面里统一用函数格式化import dayjs from dayjs function formatTime(value) { if (!value) return - return dayjs(value).format(YYYY-MM-DD HH:mm) }还有一个容易忽略的点是时区。Django 的settings.py里如果不设置默认可能是 UTC导致你看到的发布时间比本地时间差 8 小时。开发时直接加上TIME_ZONE Asia/Shanghai USE_TZ False如果你的项目要求严格使用 UTC也可以保留USE_TZ True但前端格式化的时候要记得转换时区。对课设和社团管理系统来说直接关闭 USE_TZ 会减少大量认知负担只是不适合跨国业务场景。6. 本地跑通之后从开发服务器到可访问的部署链路很多人项目在本地跑得很顺一到部署就崩。原因往往不是功能写错而是配置文件没有区分开发环境和生产环境或者迁移、静态文件、反向代理这几步没走通。6.1 把环境配置从 settings.py 里拆出来最基础的做法是不要把数据库密码和密钥直接写在settings.py中。你可以用python-dotenv读.env文件开发环境和生产环境各自准备一份.env。# settings.py import os from dotenv import load_dotenv load_dotenv() SECRET_KEY os.environ.get(SECRET_KEY, dev-only-key) DEBUG os.environ.get(DEBUG, True) True ALLOWED_HOSTS os.environ.get(ALLOWED_HOSTS, localhost,127.0.0.1).split(,)这样本地调试时.env里写DEBUGTrue上线时写DEBUGFalse。不要让DEBUGTrue的状态直接用于部署否则一旦程序抛异常会暴露完整调用栈和本地配置非常危险。6.2 执行数据库迁移和初始化流程部署到新服务器后第一件事不是直接runserver而是把数据库表建出来python manage.py migrate python manage.py collectstaticcollectstatic会把 Django Admin 等后台页面依赖的静态文件收集到一个目录一般静态文件目录需要配置为STATIC_ROOT。如果没配置Nginx 访问/static/admin/时会 404后台页面会变得很难看。如果你还需要一个初始管理员账号可以用python manage.py createsuperuser为了演示方便也可以在migrate后提供一个python manage.py loaddata初始数据 JSON 文件把预设社团分类、演示管理员账号放进去。不过这不影响系统核心可以后做。6.3 用 Gunicorn 运行 Django用 Nginx 托管前端生产环境不要用python manage.py runserver那是开发服务器扛不住并发而且 Django 官方明确说明它不适合生产使用。最常用的组合是前端构建成静态文件Nginx 托管后端用 Gunicorn 代理给 Nginx。先安装 Gunicornpip install gunicorn然后在后端目录启动gunicorn campus_club_backend.wsgi:application --bind 127.