- 后端
- 前端
- 认证鉴权
- 低代码
- 企业应用
【免费下载链接】gin-vue-admin
🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。
导读
本文围绕 gin-vue-admin 前端工程web/src/utils/目录下的统一工具函数层展开,系统梳理request.js、dictionary.js、format.js、asyncRouter.js、btnAuth.js等 17 个核心工具模块的职责边界、调用方式与底层实现。读者读完后,将掌握在 gin-vue-admin 中"优先复用既有能力、杜绝重复造轮子"的前端开发规范,能够在开发新页面、新接口、新组件时准确选用对应的工具函数,并理解动态路由、按钮权限、字典缓存、请求拦截等关键机制的源码级原理。
一、核心原则:复用优先,禁止重复造轮子
aiDoc/frontend-backend/frontend-utils.md是 gin-vue-admin 前后端协作边界文档体系中关于前端工具函数复用约束的规范文件(同目录下还有 boundary.md 与 frontend-rules.md)。它确立了一条贯穿整个前端工程的核心原则:
开发任何前端功能前,必须优先检查并复用
src/utils/下已有能力,严禁重复造轮子。
这一约束的目的很明确:gin-vue-admin 的前端(基于 Vite + Vue3 + Element Plus,支持 TS/JS 混用)将高频横切能力全部收敛到了web/src/utils/目录下,包括 HTTP 请求、字典解析、命名转换、系统参数、路由权限、事件通信等。所有工具函数都以统一封装、统一出口的形式存在,任何业务页面直接import即可使用,从而保证:
- 行为一致:所有请求都走同一套 token 注入、错误处理、loading 管理逻辑;
- 维护集中:修复或升级底层能力只需改动一处;
- 降低心智负担:新成员只需查询工具清单即可找到可复用能力。
下文将按功能域逐一剖析这些工具,并给出源码实现依据与使用示例。
二、HTTP 与数据请求层
2.1request.js:HTTP 请求统一入口
web/src/utils/request.js是基于 axios 二次封装的全局请求单例,是整个前端工程的网络出口。强制使用场景:发起 HTTP 请求时,必须使用@/utils/request。
从源码可以看到它在请求拦截器(service.interceptors.request)中完成了四件核心工作:
const DEFAULT_REQUEST_TIMEOUT = 1000 * 60 * 10 const DEFAULT_LOADING_FORCE_CLOSE_DELAY = 30000- 默认超时:未显式传入
timeout时,默认超时时间为 10 分钟(1000 * 60 * 10),适用于大文件上传等长耗时场景; - 全局 Loading 管理:请求发起后延迟 400ms 显示
ElLoading(避免瞬时请求闪烁),并通过计数器activeAxios支持并发请求合并——只要还有未完成的请求,loading 就不关闭;同时内置 30 秒强制关闭保护(DEFAULT_LOADING_FORCE_CLOSE_DELAY),防止异常场景下 loading 永久残留,并导出resetLoading用于兜底重置; - BaseURL 注入:
config.baseURL = config.baseURL || import.meta.env.VITE_BASE_API,环境变量VITE_BASE_API定义在web/.env*系列文件中; - 身份信息注入:自动携带
x-token(用户 token)与x-user-id(用户 ID),请求头默认Content-Type: application/json,来源为 Pinia 中的 user store。
响应拦截器(service.interceptors.response)的判定逻辑同样值得关注:
if (response.headers['new-token']) { userStore.setToken(response.headers['new-token']) } if (response.data.code === 0 || response.headers.success === 'true') { if (response.headers.msg) { response.data.msg = decodeURI(response.headers.msg) } return response.data }- Token 续期:后端返回
new-token响应头时自动更新本地 token,实现无感续期; - 统一成功判定:
code === 0或响应头success === 'true'视为成功,直接返回response.data,业务代码拿到的是干净的响应体; - 统一错误提示:失败时通过
ElMessage弹出后端返回的msg; - 401 统一处理:请求返回 401 时,通过事件总线
emitter.emit('show-error', ...)触发全局错误提示,并回调中执行userStore.ClearStorage()与跳转登录页(router.push({ name: 'Login', replace: true })); - 网络异常兜底:无响应(断网/超时)时调用
resetLoading()重置所有 loading 状态,防止卡死。
实践中,业务层调用方式为:
import service from '@/utils/request' // 普通 GET/POST const res = await service({ url: '/user/info', method: 'get' }) // 不显示全局 loading(如轮询、后台静默请求) const res = await service({ url: '/xxx', method: 'post', donNotShowLoading: true })2.2 请求工具在业务 API 中的落地
业务侧所有 API 定义均统一依赖该封装。以web/src/api/目录下的模块为例,它们只负责描述"请求什么",而 token、loading、错误处理全部交由request.js完成,这正是"统一入口"约束的直接体现。
三、字典与数据格式化
3.1dictionary.js:字典数据获取
强制使用场景:获取字典数据时,必须优先使用@/utils/dictionary。web/src/utils/dictionary.js是字典能力的唯一入口,向上对接 Pinia 的 dictionary store,向下提供两个核心方法。
getDict(type, options)支持按深度和指定节点获取字典,并内置了缓存 key 生成规则(generateCacheKey):
export const getDict = async (type, options = { depth: 0, value: null }) => { // 参数校验:type 必须为非空字符串,depth 必须为非负数 await dictionaryStore.getDictionary(type, options.depth, options.value) const cacheKey = generateCacheKey(type, options.depth, options.value) const result = dictionaryStore.dictionaryMap[cacheKey] return Array.isArray(result) ? result : [] }缓存 key 规则为:
- 传了
value:`${type}_value_${value}_depth_${depth}`; - 未传
value且depth === 0:`${type}_tree`(完整树); - 未传
value且depth > 0:`${type}_depth_${depth}`(指定深度扁平数据)。
使用示例(源码注释中自带):
// 获取完整的字典树形结构 const dictTree = await getDict('user_status') // 获取指定深度的扁平化字典数据 const dictFlat = await getDict('user_status', { depth: 2 }) // 获取指定节点的 children const children = await getDict('user_status', { value: 'active' })配套的showDictLabel(dict, code, keyCode = 'value', valueCode = 'label')用于将字典 value 翻译成展示 label,适合表格列渲染场景:
// dict 为字典数组,code 为待翻译的值 const label = showDictLabel(dict, row.status) // 返回对应 label,找不到返回 ''3.2format.js:常用格式化能力
web/src/utils/format.js集成了布尔值、日期、字典、URL、主题色、UUID 等常用格式化能力,是业务页面最常用的工具模块之一,主要导出:
| 函数 | 能力 | 源码要点 |
|---|---|---|
formatBoolean(bool) | 布尔值展示 | 非 null 时输出「是/否」,null 输出空串 |
formatDate(time) | 日期格式化 | 基于date.js的formatTimeToStr,输出yyyy-MM-dd hh:mm:ss |
filterDict(value, options) | 字典值翻译 | 递归查找options中value对应的label,支持children树形结构 |
filterDataSource(dataSource, value) | 数据源翻译 | 同 filterDict,但额外支持传入数组批量翻译 |
getDictFunc(type) | 异步字典获取 | 包装getDict(type) |
ReturnArrImg(arr) | 图片地址拼接 | 单值/数组统一走getUrl处理,兼容数组与非数组入参 |
onDownloadFile(url) | 文件下载 | window.open(getUrl(url)) |
setBodyPrimaryColor(color, darkMode) | 主题色切换 | 计算并注入--el-color-primary及 light/dark 系列 CSS 变量 |
getBaseUrl() | BaseURL 获取 | 读取VITE_BASE_API,/时返回空串 |
CreateUUID() | UUID 生成 | 基于时间戳 +performance.now()+ 随机数,符合 UUID v4 格式 |
强制使用场景:生成 UUID 时,必须优先使用CreateUUID。其实现采用标准 RFC 4122 格式(xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx),种子由当前时间与performance.now()混合,保证同一毫秒内多次调用也能得到不同值:
export const CreateUUID = () => { let d = new Date().getTime() if (window.performance && typeof window.performance.now === 'function') { d += performance.now() } return '00000000-0000-0000-0000-000000000000'.replace(/0/g, (c) => { const r = (d + Math.random() * 16) % 16 | 0 d = Math.floor(d / 16) return (c === '0' ? r : (r & 0x3) | 0x8).toString(16) }) }主题色能力在个人中心/系统设置中用于动态换肤,通过修改 Element Plus 的 CSS 变量实现全局生效。
3.3date.js:日期格式化
web/src/utils/date.js通过扩展Date.prototype.Format提供模板化日期格式化,并在文件头注释中给出了完整的占位符约定:
- 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q):可用 1~2 个占位符;
- 年(y):可用 1~4 个占位符;
- 毫秒(S):只能用 1 个占位符(1~3 位数字)。
示例对照:
(new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18对外主要暴露formatTimeToStr(times, pattern),未传pattern时默认yyyy-MM-dd hh:mm:ss。format.js中的formatDate即基于它实现。
四、命名转换与字符串工具
4.1stringFun.js:命名格式转换
强制使用场景:进行命名格式转换时,必须优先使用@/utils/stringFun。web/src/utils/stringFun.js提供四个纯函数,覆盖前后端字段命名的双向转换:
// 首字母大写 / 小写 toUpperCase('hello') // 'Hello' toLowerCase('Hello') // 'hello' // 驼峰转下划线(ID 特判保持原样) toSQLLine('userName') // 'user_name','ID' -> 'ID' // 下划线转驼峰 toHump('user_name') // 'userName'toSQLLine对ID做了特判直接返回原值,避免主键字段被误转成_i_d。这类转换在代码生成器、表格列字段映射、前后端字段对齐等场景中高频使用。
五、系统参数与全局状态
5.1params.js:系统参数获取
web/src/utils/params.js提供getParams(key)方法,统一从 Pinia 的 params store 读取系统参数(后端sys_params表配置的全局参数):
// 同步方式 const res = await getParams('key') // 异步函数内使用 const fun = async () => { res.value = await getParams('test') } fun()params store 内部维护paramsMap,同一 key 只向后端请求一次,后续读取直接命中内存缓存。
5.2bus.js:跨组件事件通信
强制使用场景:跨组件通信优先使用事件总线,避免滥用 Pinia。web/src/utils/bus.js基于mitt实现了一个极简的全局事件总线:
import mitt from 'mitt' export const emitter = mitt()mitt 是体积极小的事件发射器库,提供on/off/emit三个核心方法。request.js的错误上报、closeThisPage.js的关页信号都复用了这个总线。对于"一次触发、多处订阅"或非层级组件间的通信,使用事件总线比把状态硬塞进 Pinia 更轻量、更贴合事件语义。
六、路由、标题与权限
6.1asyncRouter.js:异步路由处理
web/src/utils/asyncRouter.js负责将后端返回的路由配置字符串解析为真正的 Vue 组件,是动态路由机制的关键一环。它利用 Vite 的import.meta.glob预扫描所有视图组件:
const viewModules = import.meta.glob('../view/**/*.vue') const pluginModules = import.meta.glob('../plugin/**/*.vue') export const asyncRouterHandle = (asyncRouter) => { asyncRouter.forEach((item) => { if (item.component && typeof item.component === 'string') { item.meta.path = '/src/' + item.component if (item.component.split('/')[0] === 'view') { item.component = dynamicImport(viewModules, item.component) } else if (item.component.split('/')[0] === 'plugin') { item.component = dynamicImport(pluginModules, item.component) } } if (item.children) { asyncRouterHandle(item.children) } }) }要点:
- 路由的
component字段在后端以字符串形式存储(如view/superAdmin/menu/index),前端通过dynamicImport在预扫描的模块表中精确匹配并转换为组件; - 递归处理
children,支持多级菜单; - 按
view/plugin前缀分流,插件页面的路由组件同样支持动态加载——这与仓库中web/src/plugin/目录下的插件页面机制一一对应。
6.2btnAuth.js:按钮级权限控制
强制使用场景:处理按钮权限时,必须优先使用useBtnAuth。web/src/utils/btnAuth.js提供组合式函数useBtnAuth:
export const useBtnAuth = () => { const route = useRoute() return route.meta.btns || reactive({}) }它直接读取当前路由meta.btns(由后端按用户角色下发的按钮权限集),返回一个响应式对象。页面中典型用法:
const btns = useBtnAuth() // 模板中:v-if="btns['user:add']" 控制新增按钮显隐配合仓库中的按钮权限指令(web/src/directive/auth.js)与权限源数据(server/source/system/authority.go等),构成"路由级 + 按钮级"的完整 RBAC 前端实现。
6.3fmtRouterTitle.js与page.js:页面标题生成
web/src/utils/fmtRouterTitle.js的fmtTitle(title, now)支持在标题中使用${param}占位符,运行时从路由的params/query中取值替换:
export const fmtTitle = (title, now) => { const reg = /\$\{(.+?)\}/ // 逐个替换 ${key} 为 now.params[key] || now.query[key] }web/src/utils/page.js的getPageTitle(pageTitle, route)在其基础上拼接应用名,最终生成浏览器标签页标题:
export default function getPageTitle(pageTitle, route) { if (pageTitle) { const title = fmtTitle(pageTitle, route) return `${title} - ${config.appName}` } return `${config.appName}` }应用名来自web/src/core/config.js中的config.appName。例如路由配置标题为用户详情 - ${id},跳转后标签页会显示为「用户详情 - 1024 - xxx」。
6.4closeThisPage.js:关闭当前标签页
web/src/utils/closeThisPage.js通过事件总线广播关闭信号:
import { emitter } from '@/utils/bus.js' export const closeThisPage = () => { emitter.emit('closeThisPage') }标签页管理器(web/src/view/layout/tabs/)订阅该事件后执行关闭当前 tab 并跳转的逻辑。业务页面(如详情页、表单页)在保存完成后调用closeThisPage()即可优雅返回,无需感知内部实现。
七、资源、媒体与 DOM 工具
7.1image.js:图片压缩与 URL 处理
web/src/utils/image.js默认导出ImageCompress类,用于上传前的图片压缩:
class ImageCompress { constructor(file, fileSize, maxWH = 1920) // 文件、目标大小KB、最大边长 compress() // 压缩主流程,返回 Promise<File> }实现原理:FileReader读取文件为 DataURL → 绘制到 canvas → 等比缩放(dWH保持长宽比,长边不超过maxWH,默认 1920)→canvas.toDataURL(fileType, 0.9)以 0.9 质量导出 → 经dataURLtoBlob转回 Blob 并包装为File返回。若压缩后体积仍超过fileSize目标值,会在控制台输出告警。
同文件还导出:
getUrl(url):拼接文件访问地址,基于VITE_FILE_API环境变量,非http开头的相对路径自动补全(根路径/时直接返回原值);isVideoExt(url)/isVideoMime(type)/isImageMime(type):按扩展名(.mp4、.mov、.webm、.ogg)或 MIME 类型(jpeg/png/webp/svg 等)判断媒体类型,供列表展示、上传校验复用。
7.2downloadImg.js:图片下载
web/src/utils/downloadImg.js的downloadImage(imgsrc, name)采用"跨域 canvas 重绘"方案规避直接下载的跨域限制:
image.setAttribute('crossOrigin', 'anonymous') // 图片加载完成后绘制到 canvas,toDataURL('image/png') 转 base64 // 创建 <a download="name"> 并派发 click 事件触发下载name缺省时默认文件名为photo。
7.3event.js:DOM 事件管理
web/src/utils/event.js提供addEventListen与removeEventListen两个薄封装,内部先做能力检测(target.addEventListener存在且为函数)再绑定/解绑,并透传capture捕获参数。用于统一事件绑定入口,避免重复书写能力判断逻辑。
7.4env.js:环境判断
web/src/utils/env.js直接暴露 Vite 注入的环境标志:
export const isDev = import.meta.env.DEV export const isProd = import.meta.env.PROD业务中可据此在开发/生产环境执行差异化逻辑(如本地调试打印、生产环境埋点)。
7.5doc.js:文档跳转
web/src/utils/doc.js的toDoc(url)以新窗口打开文档链接:
export const toDoc = (url) => { window.open(url, '_blank') }通常用于"帮助文档""使用指南"等入口,保持当前页面状态不被覆盖。
八、强制使用场景对照速查
原文档明确了以下强制性场景,任何新代码都不得绕过:
| 场景 | 必须使用的工具 | 对应源码 |
|---|---|---|
| 发起 HTTP 请求 | @/utils/request | web/src/utils/request.js |
| 获取字典数据 | @/utils/dictionary(getDict) | web/src/utils/dictionary.js |
| 生成 UUID | CreateUUID(来自 format.js) | web/src/utils/format.js |
| 处理按钮权限 | useBtnAuth | web/src/utils/btnAuth.js |
| 命名格式转换 | @/utils/stringFun | web/src/utils/stringFun.js |
| 跨组件通信 | 事件总线emitter(bus.js),避免滥用 Pinia | web/src/utils/bus.js |
九、开发前的复用检查清单
结合 frontend-utils.md 的规范与上述源码分析,建议在开发每个前端功能前依次自检:
- 要发请求?确认是否已在
web/src/api/下有过同类接口定义,并一律经由@/utils/request发出,不要新建 axios 实例; - 要渲染字典/下拉/表格枚举?先查
getDict/filterDict/showDictLabel/filterDataSource是否已覆盖,不要手写 value→label 映射; - 要拼接图片/文件地址?使用
getUrl,不要硬编码VITE_FILE_API; - 要做命名转换?使用
toSQLLine/toHump,不要自行编写正则; - 要控制按钮显隐?使用
useBtnAuth,不要重复从接口拉取权限; - 要做组件间通知?优先
emitter事件总线;只有需要跨页面共享响应式状态时才使用 Pinia; - 要动态路由/标题/关页?分别对应
asyncRouterHandle、getPageTitle、closeThisPage,这些能力在布局与路由基建中已闭环,业务侧只需调用。
遵循这份清单,即可让新功能无缝融入 gin-vue-admin 现有的请求、权限、字典与路由体系,既降低维护成本,也保证整个工程行为的一致性。
- 后端
- 前端
- 认证鉴权
- 低代码
- 企业应用
【免费下载链接】gin-vue-admin
🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。
相关推荐
OpenCore Legacy Patcher终极指南:让老Mac免费运行最新macOS的完整教程
OpenCore Legacy Patcher终极指南:让老Mac免费运行最新macOS的完整教程 你是否有一台被苹果官方"抛弃"的老款Mac?当系统更新提示"
后端前端认证鉴权低代码企业应用gin-vue-admin 前端代码示例指南:API 封装、Pinia Store、页面组件与工具函数复用规范
gin vue admin 前端代码示例指南:API 封装、Pinia Store、页面组件与工具函数复用规范 本篇指南以 gin vue admin 仓库 a
后端前端认证鉴权低代码任务调度gin-vue-admin 前端工具函数复用指南:以 src/utils 为核心的能力沉淀与调用规范
gin vue admin 前端工具函数复用指南:以 src/utils 为核心的能力沉淀与调用规范 导读 本指南围绕 gin vue admin 前端工程中
后端前端认证鉴权低代码任务调度
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考