gin-vue-admin 前端工具函数库全解:从 request 到 asyncRouter 的复用规范与源码实践
2026/9/20 16:07:08 网站建设 项目流程
  • 后端
  • 前端
  • 认证鉴权
  • 低代码
  • 企业应用

【免费下载链接】gin-vue-admin

🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。

项目地址:https://gitcode.com/flipped-aurora/gin-vue-admin
点击查看免费下载

导读

本文围绕 gin-vue-admin 前端工程web/src/utils/目录下的统一工具函数层展开,系统梳理request.jsdictionary.jsformat.jsasyncRouter.jsbtnAuth.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
  1. 默认超时:未显式传入timeout时,默认超时时间为 10 分钟(1000 * 60 * 10),适用于大文件上传等长耗时场景;
  2. 全局 Loading 管理:请求发起后延迟 400ms 显示ElLoading(避免瞬时请求闪烁),并通过计数器activeAxios支持并发请求合并——只要还有未完成的请求,loading 就不关闭;同时内置 30 秒强制关闭保护(DEFAULT_LOADING_FORCE_CLOSE_DELAY),防止异常场景下 loading 永久残留,并导出resetLoading用于兜底重置;
  3. BaseURL 注入config.baseURL = config.baseURL || import.meta.env.VITE_BASE_API,环境变量VITE_BASE_API定义在web/.env*系列文件中;
  4. 身份信息注入:自动携带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/dictionaryweb/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}`
  • 未传valuedepth === 0`${type}_tree`(完整树);
  • 未传valuedepth > 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.jsformatTimeToStr,输出yyyy-MM-dd hh:mm:ss
filterDict(value, options)字典值翻译递归查找optionsvalue对应的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:ssformat.js中的formatDate即基于它实现。


四、命名转换与字符串工具

4.1stringFun.js:命名格式转换

强制使用场景:进行命名格式转换时,必须优先使用@/utils/stringFunweb/src/utils/stringFun.js提供四个纯函数,覆盖前后端字段命名的双向转换:

// 首字母大写 / 小写 toUpperCase('hello') // 'Hello' toLowerCase('Hello') // 'hello' // 驼峰转下划线(ID 特判保持原样) toSQLLine('userName') // 'user_name','ID' -> 'ID' // 下划线转驼峰 toHump('user_name') // 'userName'

toSQLLineID做了特判直接返回原值,避免主键字段被误转成_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:按钮级权限控制

强制使用场景:处理按钮权限时,必须优先使用useBtnAuthweb/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.jspage.js:页面标题生成

web/src/utils/fmtRouterTitle.jsfmtTitle(title, now)支持在标题中使用${param}占位符,运行时从路由的params/query中取值替换:

export const fmtTitle = (title, now) => { const reg = /\$\{(.+?)\}/ // 逐个替换 ${key} 为 now.params[key] || now.query[key] }

web/src/utils/page.jsgetPageTitle(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.jsdownloadImage(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提供addEventListenremoveEventListen两个薄封装,内部先做能力检测(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.jstoDoc(url)以新窗口打开文档链接:

export const toDoc = (url) => { window.open(url, '_blank') }

通常用于"帮助文档""使用指南"等入口,保持当前页面状态不被覆盖。


八、强制使用场景对照速查

原文档明确了以下强制性场景,任何新代码都不得绕过:

场景必须使用的工具对应源码
发起 HTTP 请求@/utils/requestweb/src/utils/request.js
获取字典数据@/utils/dictionarygetDictweb/src/utils/dictionary.js
生成 UUIDCreateUUID(来自 format.js)web/src/utils/format.js
处理按钮权限useBtnAuthweb/src/utils/btnAuth.js
命名格式转换@/utils/stringFunweb/src/utils/stringFun.js
跨组件通信事件总线emitter(bus.js),避免滥用 Piniaweb/src/utils/bus.js

九、开发前的复用检查清单

结合 frontend-utils.md 的规范与上述源码分析,建议在开发每个前端功能前依次自检:

  1. 要发请求?确认是否已在web/src/api/下有过同类接口定义,并一律经由@/utils/request发出,不要新建 axios 实例;
  2. 要渲染字典/下拉/表格枚举?先查getDict/filterDict/showDictLabel/filterDataSource是否已覆盖,不要手写 value→label 映射;
  3. 要拼接图片/文件地址?使用getUrl,不要硬编码VITE_FILE_API
  4. 要做命名转换?使用toSQLLine/toHump,不要自行编写正则;
  5. 要控制按钮显隐?使用useBtnAuth,不要重复从接口拉取权限;
  6. 要做组件间通知?优先emitter事件总线;只有需要跨页面共享响应式状态时才使用 Pinia;
  7. 要动态路由/标题/关页?分别对应asyncRouterHandlegetPageTitlecloseThisPage,这些能力在布局与路由基建中已闭环,业务侧只需调用。

遵循这份清单,即可让新功能无缝融入 gin-vue-admin 现有的请求、权限、字典与路由体系,既降低维护成本,也保证整个工程行为的一致性。

  • 后端
  • 前端
  • 认证鉴权
  • 低代码
  • 企业应用

【免费下载链接】gin-vue-admin

🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。

项目地址:https://gitcode.com/flipped-aurora/gin-vue-admin
点击查看免费下载

相关推荐

上一篇:Flow.Launcher插件依赖自动安装:智能解决扩展运行环境的完整指南
下一篇:Tiptap 核心包 `@tiptap/core` 演进全解析:从 v3 重构到 Decorations 时代

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询