ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Vue3和FastAPI从零搭建高颜值开发者工具台

用Vue3和FastAPI从零搭建高颜值开发者工具台 浏览器收藏栏里的开发者工具网站越来越多JSON 格式化、时间戳换算、正则调试、JWT 解码、Base64 编解码、颜色转换……每个都挺好用可每个都要开一个新标签页还要忍受完全不同的交互方式和时不时弹出来的广告。我大概是在去年下半年彻底受不了这个状态决定用自己最顺手的 Vue3 和 FastAPI 从头撸一个高颜值的开发者工具台项目代号叫 Sidereal Hub。这个项目做下来将近四个月目前已经稳定跑在我自己的服务器上平时写代码、排查接口、调试数据基本不再开别的工具站。如果你也是做前端、后端或者全栈开发的平时被各种散装工具折磨得够呛又恰好对 Vue3 和 FastAPI 这套组合感兴趣那这篇复盘应该能帮到你。我不打算写什么系统教程就是想把这个项目从立项、选型、目录设计、前后端联调到那些真正让人抓狂的 Bug 排查过程完整地讲一遍。1. 从收藏栏一团乱麻到做出 Sidereal Hub立项逻辑1.1 我的真实痛点不是工具不够而是工具太散先说说我为什么非要自己做一个。我的浏览器收藏栏里光工具类网站就攒了三四十个JSON 格式化用 A 站时间戳转换用 B 站正则测试用 C 站颜色选择用 D 站JWT 解码用 E 站……每个工具单拿出来都还行但合在一起就是灾难。灾难体现在几个地方。第一交互不统一。有的工具回车触发有的点按钮触发有的输入即触发每次换着用都得重新适应。第二数据不敢乱贴。有些在线工具会把输入内容发到服务端解析我调试接口时经常要粘贴内部返回的 JSON心里总有点不踏实。第三想要的功能别人不一定有。比如我想把时间戳 请求体 响应体拼成一条调试记录保存下来方便后来回溯这种偏个性化的工作流几乎没有现成工具愿意做。后来我也试着用开源的导航站或者工具聚合项目直接部署一套但发现大部分项目要么很久没维护要么界面停留在几年前的风格要么扩展新工具非常麻烦。既然找不到合适的那就自己写一个。Sidereal Hub 的定位从第一天起就很明确一个能自己控制一切、可以随手加新工具、且界面拿得出手的私有开发者工具台。1.2 给工具台划定的三条边界条件立项光有冲动不够我给自己定了三个必须满足的条件后来的所有设计决策都是围绕这三条展开的。第一私有优先。工具台部署在自己的服务器上所有输入数据只在本机或内网处理绝不把数据转发给任何第三方接口。这是它区别于大部分在线工具站的核心价值。第二扩展要轻。我不想每加一个工具都要大动干戈改框架。理想状态是写一个独立的前端组件再往后端加一个注册接口五分钟内新工具就能出现在面板上。第三颜值在线。开发者工具大多是能用就行的样子但天天要用的东西难看真的会影响心情。我希望它有统一的色彩体系、舒服的间距、顺畅的暗色模式而不是各种组件库默认样式的大杂烩。这三条边界直接决定了后面 Vue3 和 FastAPI 的选型也决定了前端要采用组件自治 统一注册的架构。1.3 为什么叫 Sidereal Hub名字其实是我在听一首后摇时想到的。Sidereal 是恒星的的意思跟天文相关。我想把工具台做成一个导航枢纽——像星空一样每一颗星星都是一个独立的工具但它们在同一个坐标系里有规律地排列。这也间接影响了前端面板的卡片式布局和暗色主题的视觉方向。2. 技术选型复盘Vue3 FastAPI 这套组合到底香在哪2.1 前端为什么选 Vue3而不是 Next、Nuxt 或者 React工具台这个场景本质上是一个需要登录态的、带 CRUD 的后台管理系统只是内容变成了各种开发者工具。这类项目最关键的需求是开发效率、组件复用、状态管理清晰而不是 SEO、首屏服务端渲染这些东西。所以 SSR 框架对我没有吸引力。Vue3 打动我的是组合式 API 和响应式系统的配合。举个例子工具台左侧有一个工具分类栏右侧是根据分类过滤的卡片列表还要同步保持 URL 参数一致。如果用 Options API这些逻辑散落在 data、watch、mounted 里要来回跳着看用组合式 API 之后我把过滤逻辑、URL 同步逻辑、加载逻辑分别抽成几个 composable每个文件只管一件事后续维护真的很省心。热词里很多人搜vue3 后台管理系统或者vue3 商城其实都是在找类似的整体方案。我的建议是别一上来就套现成的后台脚手架先想想自己的核心逻辑是什么Vue3 最值钱的是你组织代码的方式而不是某个框架给你预置了多少页面。2.2 后端为什么是 FastAPI而不是 Flask 或 Node工具台需要一个后端是因为有些能力只靠浏览器是不好实现的调用需要签名算法的时间戳转换、生成 RSA 密钥对、数据持久化保存用户配置和操作记录、定时任务执行健康检查等。这些沾点计算密集 需要保密的活儿放在后端更靠谱。FastAPI 我是对比过 Flask 之后才定的。Flask 老牌、稳定、生态熟但它的异步支持是后面补的实际用起来要么靠 gevent 魔改要么老老实实写同步路由。FastAPI 天生就是 async配合异步 SQLAlchemy 和 asyncpg在 IO 密集的操作上表现明显更好——比如工具台首页要同时加载用户信息、工具列表、最近操作记录三个数据源用asyncio.gather并发请求响应时间比串行少了一半以上。更让我舒服的是 Pydantic。路由函数的参数直接声明成模型类FastAPI 会自动完成请求体解析和校验校验失败的错误信息还能直接映射成前端需要的格式。以前写 Flask 校验参数要自己先读request.json再手动判断字段是否存在、类型对不对写多了真的烦躁。FastAPI 把这部分干掉了我只需要定义好数据模型它自动生成 OpenAPI 文档前端同事甚至可以拿文档直接当接口契约用。2.3 SQLAlchemy 数据模型的两次救命时刻热词里有人搜fastapi 和 sqlalchemy 构建高性能 web 服务我实际用下来SQLAlchemy 2.0 的声明式模型和 FastAPI 配合得很流畅。我最喜欢的是select()语句的写法比 1.x 时代的session.query直观很多# models.py from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from datetime import datetime class Base(DeclarativeBase): pass class OperationLog(Base): __tablename__ operation_logs id: Mapped[int] mapped_column(primary_keyTrue) user_id: Mapped[int] mapped_column(indexTrue) action: Mapped[str] mapped_column(indexTrue) payload: Mapped[str] mapped_column(default{}) created_at: Mapped[datetime] mapped_column(defaultdatetime.utcnow)印象最深的第一次救命是给工具台加最近使用功能的时候。我想记录每个用户最近打开过哪些工具、操作过哪些数据于是加了一张操作日志表。当时很担心这会让工具台变慢实际上因为用了异步 SQLAlchemy 加上查询条件走索引插入日志和读取最近记录几乎不感知延迟。第二次是数据统计页面。工具台内部有一个每个工具被使用次数的折线图一开始我在 Python 里循环查数据库慢得要命。后来换成 SQLAlchemy 的分组聚合查询一次select(UserToolUsage.tool_id, func.count())把原始数据拿出来再在前端做图表聚合性能立刻正常。这个经验其实很通用别把所有逻辑都往后端塞前端能用缓存和本地计算解决的就地解决后端只提供纪元数据和聚合结果。3. 项目目录结构独立开发也要把仓库拆得明明白白3.1 后端目录按模块切而不是按文件类型切FastAPI 项目目录结构这个问题热词里搜的人很多。我看到很多新手喜欢把所有路由写在一个main.py里或者按文件类型建一堆models.py、apis.py、routers.py文件一多就混乱。我的做法是按业务模块切一个模块一个包每个包里自带路由、模型、Schema、服务backend/ ├── app/ │ ├── main.py # FastAPI 实例、CORS、路由汇总 │ ├── core/ # 配置、安全、依赖 │ │ ├── config.py │ │ ├── security.py # JWT 生成/校验 │ │ └── deps.py # 通用依赖比如 get_db │ ├── modules/ │ │ ├── auth/ # 登录注册模块 │ │ │ ├── router.py │ │ │ ├── schemas.py │ │ │ ├── service.py │ │ │ └── models.py │ │ ├── tools/ # 工具模块 │ │ │ ├── router.py │ │ │ ├── schemas.py │ │ │ ├── service.py │ │ │ └── data.py # 工具元数据注册表 │ │ └── logs/ # 操作日志模块 │ ├── models/ # 公共模型比如用户表 │ └── schemas/ # 公共 Schema ├── alembic/ # 数据库迁移 ├── requirements.txt └── .env这样的结构有几个明显好处。新增一个工具模块时只需要在app/modules下新建一个包然后在main.py里include_router一次其他模块完全不受影响。core目录放全局配置和安全依赖modules目录放业务逻辑职责边界清楚。Alembic 单独放迁移脚本改表结构时不用手动去同步生产库。3.2 前端目录除了 views 和 components还有第三个关键目录Vue3 项目的目录网上有无数种模板我用下来最顺手的结构是这样的frontend/ ├── src/ │ ├── api/ # 与后端接口一一对应的请求函数 │ │ ├── auth.ts │ │ ├── tools.ts │ │ └── logs.ts │ ├── assets/ │ ├── components/ # 通用 UI 组件与业务无关 │ ├── composables/ # 可复用的组合式函数 │ ├── layouts/ # 面板布局 │ ├── router/ │ ├── stores/ # Pinia │ ├── styles/ # 设计 Token、全局样式 │ ├── utils/ # 纯函数工具比如时间戳换算 │ ├── views/ # 页面级组件 │ └── main.ts这里最想强调的第三个目录是composables。很多 Vue3 项目的components目录会越滚越大各种页面也顺手往里塞代码。我的习惯是凡是涉及状态逻辑、浏览器 API 操作、数据请求这一段尽量抽成 composable组件里只保留模板和事件绑定。比如时间戳转换这个工具它的核心逻辑不是展示界面而是把时间戳、日期字符串、相对时间之间互相换算的算法我把它放在composables/useTimestampConverter.ts里组件只负责调用。3.3 前后端共享类型少写一半重复代码前后端联调时最烦人的是数据结构不一致前端以为返回的是created_at后端实际给的是createTime一改改半天。我在项目早期就被这个问题坑过几次后来学乖了先用 FastAPI 自动生成的 OpenAPI 文档固定住 schema然后手写一份对应的 TypeScript 类型定义放在src/api/types.ts里前后端都拿这套定义当契约。虽然手写类型还是会前后端各维护一份但至少接口字段名和层级是统一的。如果你有精力可以用openapi-typescript工具直接从openapi.json生成 TS 类型效果更好。这个环节的价值在项目后期体现得特别明显工具越来越多如果类型全靠人脑记迟早出乱子。4. CORS、请求封装与动态路由前后端分离开发里绕不开的几道坎4.1 FastAPI 的 CORS 配置为什么本地联调第一次就炸前后端分离开发时前端跑在http://localhost:5173后端跑在http://localhost:8000这俩端口不一样浏览器就会触发跨域限制。我记得第一次用 Vite 启动前端、用 Uvicorn 启动后端在页面上调用登录接口控制台直接报类似 CORS policy: No Access-Control-Allow-Origin header 的错误。这个问题的本质是浏览器的同源策略不同源的请求要放行服务端必须在响应头里明确告诉浏览器这个源允许访问。FastAPI 的解决方案是通过CORSMiddleware中间件配置允许的源、方法等。我的开发环境配置大概长这样from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[ http://localhost:5173, http://127.0.0.1:5173, ], allow_credentialsTrue, allow_methods[*], allow_headers[*], )注意allow_credentialsTrue时allow_origins不能写[*]必须明确列出允许的源不然浏览器还是会拦。这个细节坑过不少人——开发环境配置好了部署到服务器又可能因为域名变化再炸一次。所以我有两个.env文件dev环境允许本地源prod环境只允许正式域名避免把跨域接口裸奔到公网。4.2 axios 封装与前端怎么连接后端vue3 怎么连接后端这个热词几乎每天都有新人搜。其实说到底就是发 HTTP 请求、处理响应和错误。我在项目里用 axios 封装了一个统一请求层核心是拦截器// src/api/request.ts import axios from axios import { useUserStore } from /stores/user import router from /router const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, }) request.interceptors.request.use((config) { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) request.interceptors.response.use( (response) response.data, (error) { if (error.response?.status 401) { // token 过期跳回登录页 useUserStore().clear() router.push({ name: login }) } return Promise.reject(error) } )这个封装解决两个问题一是把 token 自动塞进请求头不用每个接口手动带二是统一处理 401 未授权登录过期时自动跳回登录页。我在封装时特别注意了响应拦截器的返回因为后端统一包的格式是{ code, message, data }所以我在这里直接剥掉外层让业务代码拿到的就是data字段少一层嵌套。4.3 动态路由与菜单让工具注册像填表单一样简单工具台的面板左侧是分类菜单右侧是工具卡片。分类和工具不能写死在前端路由里不然每加一个工具都要改路由表、改菜单太麻烦。我用的是后端注册表 前端动态渲染的方案。后端在app/modules/tools/data.py里维护一份工具注册表每个工具包含id、name、category、icon、description、route等字段TOOL_REGISTRY [ { id: timestamp, name: 时间戳转换, category: 数据转换, icon: Clock, description: 时间戳、日期字符串、相对时间互转, route: /tools/timestamp, enabled: True, }, # 更多工具... ]前端页面启动时通过/api/tools拉取这份注册表动态生成侧边菜单和工具卡片的点击路由。这样加一个新工具后端加一条记录前端写一个工具组件扔进views/tools/目录再补一条路由映射就完事了。按我熟练度十分钟不到就能上架一个工具。5. 高颜值是怎么落地的设计 Token、Element Plus 定制与主题切换5.1 先定义设计 Token再写组件很多后台管理项目颜值拉胯并不是开发能力不行而是没有统一的设计约束。我在写第一个页面之前先建了一个styles/tokens.css把所有颜色、间距、圆角、阴影、字体大小定义成一个一个的 CSS 变量:root { --color-bg-primary: #0f1115; --color-bg-secondary: #161a22; --color-bg-card: #1c212b; --color-border: #2a313c; --color-text-primary: #e5e9f0; --color-text-secondary: #8b95a7; --color-accent: #6c8cff; --radius-md: 8px; --radius-lg: 12px; --shadow-card: 0 4px 20px rgba(0, 0, 0, 0.3); --space-page: 24px; --space-card: 16px; }这一步的价值后面会成倍放大。想要统一调整工具卡片的圆角、让暗色模式整个变一种色调、或者把主色从蓝色换成紫色只需要改这几个变量而不是满项目找一个个 class。热词里有人搜vue3 修改 tabs 标签页样式其实根子也在 Token 上。如果组件里的颜色、边框都是从设计 Token 映射过去的改主题时就不用去挑一个个组件的内部类名全局换肤会轻松很多。5.2 Element Plus 的定制只用基础组件不让框架定义视觉我用的是 Element Plus但刻意没有直接用它的默认主题。默认主题的蓝色偏活泼跟我想做的深色专业工具台气质不搭。Element Plus 所有组件的 SCSS 变量是暴露出来的可以覆盖。我写了一个styles/element.scss在最前面用forward或者直接设置变量来覆盖主题色// 覆盖 Element Plus 的部分设计变量 $--color-primary: #6c8cff; $--border-radius-base: 8px;再配合全局 CSS 变量让 Element Plus 的按钮、输入框、弹窗融入整体暗色风格。这里要提醒一句不要试图把每个组件的内部样式都手动改一遍那是无底洞。我的原则是Element Plus 管交互逻辑和基本结构颜色、圆角、边框这些视觉属性尽量通过主题变量控制控制不了的小地方再单独覆盖。5.3 明暗主题切换与动效的克制开发者工具台的使用环境差异很大白天办公室光线亮晚上家里光线暗明暗主题切换几乎是刚需。我实现的方式比较传统但很稳定给html元素切换>html[data-themedark] { --color-bg-primary: #0f1115; --color-bg-card: #1c212b; --color-text-primary: #e5e9f0; } html[data-themelight] { --color-bg-primary: #f5f6f8; --color-bg-card: #ffffff; --color-text-primary: #1a1d24; }切换逻辑就是给document.documentElement设置>location / { try_files $uri $uri/ /index.html; }这样不管是用户直接访问/tools/timestamp还是刷新页面都能回到 Vue 应用再由前端路由自动定位到对应工具。7.2 独立开发期间最值得记住的三句话第一句工具的颜值不是后期加的滤镜而是贯穿在结构设计里的 Token 体系。我正是因为一开始就建立了统一的设计变量后面几十个工具组件才能保持视觉一致没有变成杂牌军。第二句比起功能数量工具的信任感更重要。我的工具台坚持私有部署、数据不落第三方这在一个人人都在谈论数据安全的时代其实就是最好的差异化。哪怕功能比在线工具少一点核心用户也会因为这一点留下来。第三句遇到 Bug 时不要急着看别人说的标准答案先自己把报错信息、渲染 DOM、请求状态完整梳理一遍。我记录在案的那几个疑难问题最后排查出的根因往往不是组件库写错了而是自己代码组织方式、字符细节、渲染时机出了问题。能把这些基础环节控制住开发效率会明显上一个大台阶。Sidereal Hub 目前还在慢慢迭代我给自己定的节奏是每两周新增一个工具顺便优化一个已有工具的交互细节。工具台这种项目没有做完的一天但每次往面板里加一个新工具或者在暗色主题下看到一个交互细节变得更顺滑都会觉得这四个月的前期投入是值得的。如果你也有类似的想法不需要等所有条件都完美先把最常用的两三个工具做成自己能接受的样子然后逐个补慢慢就会成为一个真正属于你自己的开发底座。
返回列表