资讯动态

Flask+Vue前后端分离博客开发复盘:技术选型与踩坑实录

发布时间:2026/10/21 18:13:56 来源:尧图企业网站定制
自己折腾过好几个博客系统的人都知道从零写一个“能跑”的博客不难但从零写一个“能看、能用、能维护”的博客中间隔着至少十条踩坑记录。最近用 Flask 搭了一个个人博客网站前端选了 Vue开发环境是 PyCharm整个项目从设计到实现花了三个星期。这篇就当成一份完整的过程复盘记录了技术选型的纠结、数据库怎么设计、接口怎么规划、前端页面怎么对接以及那些文档里根本不会写的坑。如果你正打算做类似的个人博客项目或者在纠结用 Flask 还是 Django、前后端要不要分离、Vue 到底该不该上这篇文章应该能帮你省下不少时间。标题里看到 flask、django、vue、pycharm 这几个关键词同时出现并不奇怪很多人都在这几个选项之间犹豫过我把自己最终的选择逻辑和实现过程完整拆开一步一步说清楚。1. 整体设计与技术选型思路1.1 博客项目的第一原则别把事情搞复杂个人博客的核心需求其实很清晰写文章、展示文章、管理文章。听起来简单但真开工的时候会发现所谓的“简单”很容易被自己搞复杂。今天想加个标签系统明天想加个阅读量统计后天又想搞个评论模块结果数据库表越建越多代码越写越乱。我给自己定了一条原则第一版只做四个核心模块文章发布、文章列表与详情、分类筛选、后台管理。其他功能比如评论、友链、站点统计全部留到第二版再说。这个决定后来被证明非常正确因为光是这四个核心模块涉及的前端页面、后端接口、数据库表之间的联动就已经足够让人头疼了。技术选型方面flask 和 django 之间我犹豫了挺久。Django 确实强大自带 Admin 后台、ORM、认证体系开箱即用但对个人博客这种规模的项目来说有点“杀鸡用牛刀”的意思。Flask 的优势是轻量灵活路由、请求处理、模板渲染这些核心功能都有其他功能按需引入插件整个项目的结构和逻辑都在自己掌控之中。最终选了 Flask还有一个实际原因PyCharm 对 Flask 的支持非常完善创建项目、调试、虚拟环境管理都有一键式支持对新手来说能省掉很多环境配置的麻烦。1.2 前后端分离还是不分离这是另一个让人纠结的问题。传统 Flask 方案用 Jinja2 模板渲染页面后端把数据和 HTML 一起返回简单直接适合纯后端开发者。前后端分离方案则是 Flask 只提供 JSON 接口Vue 负责所有页面渲染和数据交互职责清晰适合想把前端技术栈也带起来的项目。我选了前后端分离原因有两点。一是项目标题里明确带了 vue那就说明前端技术栈应该是 Vue如果最后只用了 Jinja2等于这个技术选的没啥意义。二是我确实想把这套项目做成一个可扩展的基础后续如果加小程序端或者其他客户端后端接口可以直接复用。前后端分离的代价是多了跨域处理、接口联调、打包部署这些工作但对应地代码结构比混编模式清晰很多前端的交互体验也更好控制。前端的技术栈选了 Vue 3 加 Vite。Vue 2 虽然生态成熟但 Vue 3 的 Composition API 写起来更顺手Vite 的开发服务器启动速度也比 Webpack 快很多。UI 库选了 Element Plus表格、表单、弹窗这些后台管理常用的组件都有现成的能省下不少造轮子的时间。1.3 用 PyCharm 组织整个项目PyCharm 在这个项目里的角色不只是编辑器它承担了虚拟环境管理、依赖安装、Flask 应用配置、调试运行这几项核心工作。项目结构上我按照 Flask 官方推荐的工厂模式组织blog-backend/ app/ __init__.py models/ # 数据库模型 api/ # 蓝图路由 utils/ # 工具函数 config.py run.py requirements.txt blog-frontend/ src/ views/ # 页面组件 components/ # 公共组件 router/ # Vue路由 store/ # 状态管理 api/ # 接口请求封装 package.json这个结构的好处是后端代码按照模块切分前端页面和逻辑也各自归位整个项目的可读性远高于把所有代码堆在一个文件里的方式。PyCharm 里直接打开这个项目目录前端代码可以用终端跑 npm 命令后端代码用 Flask 运行配置启动两边互不干扰。2. 数据库设计与核心功能拆解2.1 数据库表怎么设计才够用数据库用 SQLite 还是 MySQL我也纠结了一阵。SQLite 零配置文件即数据库做开发测试非常省事。MySQL 功能全面但需要额外安装和配置服务。个人博客的并发访问量通常不会太大SQLite 其实完全扛得住但我最后还是选了 MySQL原因是部署到云服务器后MySQL 在并发和稳定性上更让人放心而且后续如果博客要加评论、友链这些功能MySQL 的扩展空间更大。数据库表我设计了四张不多不少刚好覆盖核心功能users 表存管理员账号信息包括用户名、密码哈希、创建时间categories 表文章分类字段包括分类名、别名、创建时间articles 表文章主体字段包括标题、摘要、正文、封面图、状态、浏览次数、所属分类、发布时间、更新时间tags 表文章标签配合中间表 article_tags 实现文章和标签的多对多关系关于文章正文存什么格式的问题第一次做博客的人可能不太注意。如果是纯文本Markdown 写了没法直接渲染需要额外引入前端 Markdown 渲染库如果直接存 HTML后端写文章时就得用富文本编辑器。我选了存 Markdown 文本前端用 markdown-it 渲染成 HTML。这样后端文章数据体积小、可读性好前端渲染也灵活。数据库字段类型上正文用 Text 类型不限制长度免得文章写长一点就报错。2.2 后端接口规划写接口之前先想清楚Flask 后端我用蓝图Blueprint按模块拆分了路由一共规划了这些接口文章模块 GET /api/articles 文章列表分页、分类筛选 GET /api/articles/id 文章详情 POST /api/articles 新建文章 PUT /api/articles/id 修改文章 DELETE /api/articles/id 删除文章 PUT /api/articles/id/view 文章浏览次数1 分类模块 GET /api/categories 分类列表 POST /api/categories 新建分类 DELETE /api/categories/id 删除分类 标签模块 GET /api/tags 标签列表 认证模块 POST /api/login 管理员登录 POST /api/logout 退出登录写接口时有一个很重要的设计决策列表接口文章的正文内容不需要返回。列表页只需要标题、摘要、封面图、分类、发布时间这些摘要信息。如果一次性把全部正文返给前端响应体膨胀好几倍页面加载速度明显变慢。我的做法是列表接口的序列化器里排除正文字段详情接口才返回完整正文。分页接口的参数也提前想好了page 表示页码per_page 表示每页数量size 表示分类ID。前端请求时带上这些参数后端用 SQLAlchemy 的 paginate 方法处理分页返回的 JSON 结构包含 records、total、page、per_page 四个字段前端拿到之后既好渲染列表也好计算总页数。2.3 登录认证管理员功能的看门人博客后台必须做登录认证不然任何人都能新建和删除文章这就闹笑话了。Flask 生态里做认证常用 Flask-Login我对它的实现做了精简没有用复杂的权限框架只保留了最基础的会话登录能力。密码存储用了 Werkzeug 自带的 generate_password_hash 和 check_password_hash这是 Flask 生态里最常用的密码哈希方案。需要注意一个细节密码哈希是一个不可逆的过程数据库里存的不是明文密码也不用存 session。用户登录时后端核对用户名和密码验证通过后使用 Flask-Login 的 login_user 方法建立会话后续的请求通过 login_required 装饰器保护后台接口。前端的登录状态怎么判断登录成功后后端返回一个用户信息字段前端存储在本地状态里同时利用 Cookie 中自动携带的 Session ID 保持后端会话。这个机制简洁可靠不用额外引入 JWT 那套东西对个人博客来说已经足够。3. 实操过程与核心代码实现3.1 后端骨架搭建虚拟环境创建和依赖安装这里不细说requirements.txt 里核心依赖大概是这样flask3.0.0 flask-sqlalchemy3.1.1 flask-login0.6.3 flask-cors4.0.0 flask-migrate4.0.5 pymysql1.1.0 markdown3.5.1应用入口 run.py 主要做几件事创建 Flask 应用实例、加载配置、数据库初始化、注册蓝图、跨域配置。配置信息放到 config.py数据库连接字符串大概是下面这种格式SQLALCHEMY_DATABASE_URI mysqlpymysql://用户名:密码服务器地址/数据库名?charsetutf8mb4这里有一个非常重要的操作细节charset 一定要用 utf8mb4而不是 utf8。MySQL 的 utf8 最多支持三个字节的字符像 emoji 表情这类四字节字符会直接报错。文章中偶尔插入一个表情符号是很正常的事如果因为编码问题显示不了排查起来很让人头疼。3.2 数据库模型的核心代码用户的数据库模型我用 SQLAlchemy 定义核心代码如下class User(UserMixin, db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(80), uniqueTrue, nullableFalse) password_hash db.Column(db.String(256), nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) def set_password(self, password): self.password_hash generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password)文章的模型稍复杂一点因为它要关联分类和标签class Article(db.Model): __tablename__ articles id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(200), nullableFalse) summary db.Column(db.String(500), nullableFalse, default) content db.Column(db.Text, nullableFalse) cover_image db.Column(db.String(300), nullableTrue) view_count db.Column(db.Integer, default0, nullableFalse) status db.Column(db.String(20), defaultpublished, nullableFalse) category_id db.Column(db.Integer, db.ForeignKey(categories.id), nullableTrue) created_at db.Column(db.DateTime, defaultdatetime.utcnow) updated_at db.Column(db.DateTime, defaultdatetime.utcnow, onupdatedatetime.utcnow) category db.relationship(Category, backrefarticles) tags db.relationship(Tag, secondaryarticle_tags, backrefarticles)标签和文章的多对多关系中中间表的定义需要注意外键的指向article_tags db.Table( article_tags, db.Column(article_id, db.Integer, db.ForeignKey(articles.id), primary_keyTrue), db.Column(tag_id, db.Integer, db.ForeignKey(tags.id), primary_keyTrue) )这里一个很容易犯的错是把中间表当成普通模型来建其实如果只存两个外键关联用 db.Table 定义轻量中间表就够了不需要额外的模型类。定义完模型后在终端执行数据库迁移命令三行代码就能让表结构同步到 MySQLflask db init flask db migrate -m init tables flask db upgrade3.3 文章接口的实现细节文章列表接口是最常用的接口它的实现逻辑值得展开讲一下。这个接口要支持分页、分类筛选、关键词搜索三个能力代码写起来不难但参数的组合和边界情况要处理好。article_bp.route(/api/articles, methods[GET]) def get_articles(): page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 10, typeint) category_id request.args.get(category_id, None, typeint) keyword request.args.get(keyword, , typestr) query Article.query.filter_by(statuspublished) if category_id: query query.filter_by(category_idcategory_id) if keyword: like_keyword f%{keyword}% query query.filter( db.or_(Article.title.like(like_keyword), Article.summary.like(like_keyword)) ) articles query.order_by(Article.created_at.desc()).paginate( pagepage, per_pageper_page, error_outFalse ) result [] for article in articles.items: result.append({ id: article.id, title: article.title, summary: article.summary, cover_image: article.cover_image, view_count: article.view_count, category: article.category.name if article.category else None, tags: [tag.name for tag in article.tags], created_at: article.created_at.strftime(%Y-%m-%d %H:%M), updated_at: article.updated_at.strftime(%Y-%m-%d %H:%M) }) return jsonify({ records: result, total: articles.total, page: articles.page, per_page: articles.per_page })这个接口有几个值得注意的细节。第一order_by 必须放在 paginate 之前这样分页结果才是按发布时间倒序排列的最新文章。第二error_outFalse 这个参数必须写否则用户访问不存在的页码时会直接返回 404而不是返回空列表。第三分类和标签在序列化时取的是关联表的名称字段而不是 ID这样前端拿到数据后不需要再做一次映射请求省一次接口调用。文章详情的接口实现相对简单按 ID 查询文章然后做一次浏览数的异步更新。浏览数更新这里犯过一个很经典的错误先查询文章再修改 view_count最后提交结果每次刷新页面浏览量加一这个逻辑在低流量下没问题但仔细想的话文章详情接口被正常访问一次浏览量就应该加一次这个行为是对的。真正的教训是要把浏览量更新和文章内容查询分开不然收藏爬虫频繁请求时数据库会频繁更新行数据。最终用了只更新浏览数不阻塞查询返回的方案article_bp.route(/api/articles/int:article_id, methods[GET]) def get_article_detail(article_id): article Article.query.get_or_404(article_id) # 异步更新浏览数 Article.query.filter_by(idarticle_id).update({view_count: Article.view_count 1}) db.session.commit() # 序列化返回 return jsonify({...})3.4 前端 Vue 项目的搭建前端项目用了 Vite 创建 Vue 3 项目npm create vitelatest blog-frontend -- --template vue cd blog-frontend npm install npm install vue-router element-plus axios markdown-it项目创建后第一步不是写页面而是先封装 API 请求模块。Axios 的封装有一个容易忽略但特别实用的点就是创建一个统一实例配置 baseURL 和拦截器// src/api/request.js import axios from axios const request axios.create({ baseURL: http://localhost:5000/api, timeout: 10000, withCredentials: true }) // 响应拦截器 request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { window.location.href /login } return Promise.reject(error) } ) export default requestwithCredentials 字段是前后端分离模式下处理会话认证的关键配置。它的作用是允许跨域请求携带 Cookie。如果这个字段不设置即使后端 Flask-CORS 已经配置了允许跨域Session ID 也无法写入浏览器导致你明明登录成功了下一次请求却被判定为未登录。这个字段和后端 Flask 的CORS(app, supports_credentialsTrue)要配套使用缺一不可。然后写具体的 API 调用模块// src/api/article.js import request from ./request export const getArticles (params) request.get(/articles, { params }) export const getArticleDetail (id) request.get(/articles/${id}) export const createArticle (data) request.post(/articles, data) export const updateArticle (id, data) request.put(/articles/${id}, data) export const deleteArticle (id) request.delete(/articles/${id})3.5 博客前台页面的实现博客前台至少要做四个页面文章列表页、文章详情页、分类页、后台管理页。文章列表页是门面决定用户对博客的第一印象我把这个页面的布局设计成左侧主列表、右侧侧边栏的结构侧边栏放分类列表和标签云。前端页面组件用script setup语法列表数据在组件挂载时加载onMounted(async () { const res await getArticles({ page: currentPage, per_page: 8 }) articles.value res.records total.value res.total })文章详情页的核心不只是展示文章内容还有 Markdown 渲染。正文从后端拿回来是 Markdown 原文需要通过 markdown-it 转成 HTML// src/components/MarkdownView.vue import MarkdownIt from markdown-it const md new MarkdownIt({ html: true, linkify: true, breaks: true }) const renderContent computed(() md.render(props.content))前端拿到渲染后的 HTML可以直接绑定到 div 的 v-html 指令上。但这里就要注意 XSS 注入的风险了。个人博客只有管理员一个人能发布内容风险相对可控但 Markdown 渲染配置里把 html 选项设为 true 后任何内嵌的 HTML 标签都会被解析执行。稳妥起见html 选项设成 false只启用标准的 Markdown 语法解析就足够了。后台管理页面我用 Element Plus 的表格组件来展示文章列表每一行都有编辑、删除按钮还有一个新建文章的按钮。新建和编辑共用一个表单组件通过传入的 articleId 区分是编辑还是新建。表单提交前做一次校验标题和正文不能为空校验逻辑放在前端可以降低无效请求对后端的压力。3.6 跨域配置与前后端联调前后端分离开发中跨域是绕不开的问题。Flask-Cors 插件的配置如下from flask_cors import CORS CORS(app, supports_credentialsTrue, origins[http://localhost:5173])注意 origins 参数要写成数组里面是你前端开发服务器的地址。如果不写这个参数默认会允许所有来源的跨域请求这在开发阶段省事但部署到线上之后是一个安全隐患。联调时如果遇到跨域报错优先检查 CORS 配置里 origins 是否覆盖了当前前端地址以及是否开启了 supports_credentials。前端开发服务器 Vite 默认跑在 5173 端口后端 Flask 跑在 5000 端口两地并跑就是完整的开发环境。一个实用的技巧是配置 Vite 的代理把前端请求转发给后端这样生产环境部署时后端接口路径和前端页面是同源的不需要再处理跨域// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:5000, changeOrigin: true } } } })配置代理之后Axios 的 baseURL 可以改成空字符串请求相对路径 /api/articles开发和生产环境都不用改代码了。4. 常见问题与排查技巧实录4.1 会话失效与登录跳转问题前后端分离模式下最诡异的故障就是明明登录成功了刷新页面又变成未登录。排查这个问题按照这个顺序来。第一检查浏览器的开发者工具看 Network 面板里登录请求的响应中 Set-Cookie 是否生效。如果响应头里有 Set-Cookie 但没有写入浏览器十有八九是跨域请求的 withCredentials 配置有问题后端 CORS 的 supports_credentials 和前端 Axios 的 withCredentials 两个地方都要打开。第二检查登录成功后后续请求的 Cookie 头是否自动携带了 Session ID。如果没有同样检查 withCredentials 是否遗漏。第三检查 PyCharm 终端里 Flask 应用的日志。Flask 的 WERKZEUG 日志会自动打印每个请求的状态码如果请求在 401 和 200 之间反复横跳重点排查 session 的配置。有时候原因很简单就是 Flask 配置里的 SECRET_KEY 变了比如配置文件中有一个空格或者换行导致每次重启 Flask 都生成不同的签名。SECRET_KEY 要写死成一个固定值不能靠随机生成。4.2 MySQL 中文乱码和数据导入出错数据库连接字符串的 charset 没写成 utf8mb4中文数据就会变成一堆问号。这个问题的变种很多比如数据库表已经建好了但是表结构和连接的字符集不一致也会出现乱码。排查方法是登录 MySQL 命令行执行show create table articles;看表结构的 CHARSET 是否和连接字符串一致。如果表结构没问题再看连接字符串。最稳妥的方案是在建库的时候指定默认字符集CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;使用 SQLAlchemy 连接时依然要带上 charsetutf8mb4 参数双保险。还有文章标题和正文里带英文单引号的情况写入 MySQL 时如果没做转义会直接报错。用 SQLAlchemy 的参数化查询能规避这个问题拼接 SQL 字符串的写法是隐患源头。标题或正文本身含单引号就会导致 SQL 语法错误用 ORM 框架的参数传递方式就不会有这个问题。4.3 Vue 页面数据不更新的诡异问题前端列表页数据在修改分类或删除文章后不刷新大概率是组件缓存或者响应式数据没有触发更新。Vue 3 的 Composition API 中如果直接修改一个 ref 包裹的对象的属性页面很多时候不会自动更新。一个具体场景删除一篇文章后调用了getArticles重新拉数据接口返回了最新的列表但页面没有变化。排查发现问题出在 Vue 的复用机制上。列表页使用了router-view组件缓存当路由跳转到其他页面再跳回来时组件实例被复用onMounted 钩子不会再次触发。解决办法有两个在路由配置里给列表页加上 meta 标记每次进入都强制刷新组件或者用一个全局变量保存列表版本号删除操作后自增版本号列表页通过 watch 版本号触发重新拉取数据。比较省事的做法是给router-view加上:key$route.fullPath路径变化时强制重建组件。4.4 图片上传与静态文件的问题博客写文章通常要插图。最初想做一个图片上传功能用 Element Plus 的 Upload 组件把图片传到后端接口后端保存到独立目录返回图片 URL。实际操作中踩了个坑上传接口报 413 Request Entity Too Large。这是 Flask 默认请求体大小限制导致的需要在配置里设置 MAX_CONTENT_LENGTHapp.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024图片保存在项目的 static 目录下。Flask 的静态文件路由默认是 /static所以上传后的图片 URL 是 http://域名/static/uploads/xxx.jpg。如果开发时前端页面在 5173 端口、图片在后端 5000 端口图片 URL 要写全路径不然前端页面会尝试从 5173 端口请求图片自然 404。这个看似小的细节实际处理起来很影响使用体验。为了省事我最后采用了更简单的方案文章内容里的图片直接外链不上传到自己的服务器减少静态文件的维护成本。封面图则通过后台上传走静态文件路由。4.5 分页组件和关键字搜索的联动Element Plus 的 Pagination 组件有自己的 current-page 和 total 属性。后端返回的 total 字段是文章总数前端根据 per_page 计算出总页数。但这里有一个前后端数据同步的问题用户点了第 3 页然后输入关键词搜索此时页码要重置为 1不然搜索之后后端仍然从第 3 页返回数据用户看到的是第 3 页的搜索结果直觉上是“搜索到的不全”。所以搜索操作触发时除了拼接新的请求参数还要把 currentPage 重置成 1。分页组件绑定的 total 是后端返回后的最新总数这个值用起来没问题但计算总页数时要判断 total 是否可以被 per_page 整除不能整除时要向上取整不然最后一页的数据会被吞掉。4.6 部署环节的坑开发环境一切正常部署到服务器后就出问题这是最常见的排障场景。Flask 自带的开发服务器只能用于开发调试部署时要用 Gunicorn 或 uWSGI 跑生产服务。用 Gunicorn 启动 Flask 应用时很多人会忘记在启动命令里指定绑定地址导致只能本机访问gunicorn -b 0.0.0.0:5000 -w 4 run:app-w 4 表示启动四个 worker 进程多进程模式下要注意 MySQL 连接数会不会被打满以及 session 的存储是否可用。多个 worker 进程各自维护自己的内存状态如果用了 PyMySQL 连接池要留意连接数上限。前端打包后是静态文件用 Nginx 托管同时配置反向代理把 /api 开头的请求转发到 Gunicorn 监听的 5000 端口。Nginx 的静态文件缓存策略记得开启图片和 JS、CSS 文件加个过期时间页面加载速度会有明显提升。部署时还有一个容易被忽略的问题Flask 的 SECRET_KEY 改了之后所有用户会话全部失效已登录的管理员会被强制下线。这个不算 bug但第一次部署的人容易因为这个误会以为是代码有问题。5. 从第一版博客到可维护项目的进阶建议第一版功能跑通之后我开始梳理哪些地方可以做得更好总结几条个人体会。博客的内容模型可以做扩展。现在只有的文章、分类、标签三个维度其实可以再加一个“自定义页面”的概念用来写关于我、项目导航、友链这类独立页面。数据结构上很简单就是一张页面表前端用固定路由渲染后端有相应接口支撑即可。评论模块第一版省掉了但挂上之后有个好处增加用户粘性。做评论模块之前先把审核流程想清楚。个人博客的评论量不会特别大可以先做成必须人工审核后才能展示用一张参考评论表和审核状态字段即可。相比接入第三方评论系统这种自建方案数据保留在自己手上不会受限于第三方服务的政策变化。站内搜索优化很值得做。现在的关键词搜索用的 SQL 的 LIKE 模糊匹配文章多了之后性能会下降。SQLite 环境里这个问题还不明显MySQL 环境里数据量上万之后LIKE 查询会让数据库压力变大。可以引入全文搜索引擎或者至少给 title 字段加索引。页面缓存值得考虑。博客文章的更新频率很低用户读到的内容基本不变适合加一层缓存。Flask 有 Flask-Caching 插件可以把文章列表接口的响应缓存一段时间。个人博客的访问量虽然不大但缓存能让响应速度提升明显也能减轻数据库负担。整个项目下来最大的体会是个人博客这个项目前端 Vue 和后端 Flask 的代码量都不算很大真正消耗时间的是数据结构的理顺、接口和数据模型的设计、前后端联调的排错以及部署时各种环境细节的处理。这也解释了为什么这类项目经常被当作课程设计和入门项目——麻雀虽小五脏俱全从数据库到后端接口再到前端页面整条链路都能锻炼到。另外想分享一个非常实用的小经验文章 ID 不要直接暴露给用户生成的链接使用不连续的 ID。爬虫会从 /articles/1 一直访问到 /articles/99把未发布的文章抓出来。一个简单的方法是使用字符串 ID格式可以是毫秒级时间戳加随机数。这样既不影响正常访问又能减少被遍历访问的可能。Flask 和 Vue 的组合比较适合喜欢掌控每一个细节的人Django 则适合希望开箱即用、把精力更多放在业务逻辑上的人。没有绝对的标准答案。Pycharm 可以同时管理前后端项目用数据库工具看数据、调试、比较前后端代码都很顺手。我这个项目最终选定的技术组合核心就是基于灵活性与可控性的权衡至少在当前需求下用起来非常扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑