# build-admin-uni-app **Repository Path**: mhsoft/build-admin-uni-app ## Basic Information - **Project Name**: build-admin-uni-app - **Description**: BuildAdmin 框架的 uni-app 跨端客户端。基于 Vue3 + TypeScript + Pinia,支持 H5、微信/支付宝/抖音等多端小程序。内置 HTTP 请求封装(Token 自动注入与刷新)、用户状态管理、路由配置、文件上传等能力,对接 BuildAdmin 后台 API,开箱即用。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 15 - **Forks**: 1 - **Created**: 2026-06-03 - **Last Updated**: 2026-06-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # build-admin-uni-app 基于 uni-app + Vue3 + TypeScript + Pinia + Vite 的跨端应用开发框架。 ## 功能特性 - **Vue3 Composition API** - 使用最新的组合式 API 编写代码 - **TypeScript** - 完整类型支持,更好的开发体验 - **Pinia 状态管理** - 轻量级状态管理库,支持持久化存储 - **uni-app 多端支持** - 一套代码,编译到微信/支付宝/百度等小程序及 H5/App - **HTTP 请求封装** - 支持请求/响应拦截器、Token 刷新、自动错误提示 - **ESLint + Prettier** - 规范代码风格,保证代码质量 ## 环境要求 - Node.js >= 18.x - pnpm >= 8.x(推荐) - HBuilderX(开发小程序推荐) ## 快速开始 ### 1. 安装依赖 ```bash pnpm install ``` ### 2. 开发模式 ```bash # H5 开发 pnpm dev:h5 # 微信小程序 pnpm dev:mp-weixin # 支付宝小程序 pnpm dev:mp-alipay # 快应用 pnpm dev:mp-kuaishou # HarmonyOS pnpm dev:mp-harmony # 其他平台... pnpm dev:mp-baidu # 百度小程序 pnpm dev:mp-toutiao # 抖音小程序 pnpm dev:mp-qq # QQ 小程序 pnpm dev:mp-lark # 飞书小程序 pnpm dev:mp-jd # 京东小程序 pnpm dev:mp-xhs # 小红书小程序 ``` ### 3. 生产构建 ```bash # H5 构建 pnpm build:h5 # 小程序构建(对应 dev:xxx) pnpm build:mp-weixin pnpm build:mp-alipay # ...其他平台 ``` ### 4. 代码检查与格式化 ```bash # ESLint 检查 pnpm lint # 自动修复 pnpm lint-fix # Prettier 格式化 pnpm format # TypeScript 类型检查 pnpm type-check ``` ## 项目结构 ``` ├── src/ │ ├── api/ # API 接口层 │ │ ├── index.ts # API 统一导出 │ │ ├── common.ts # 公共接口(验证码/短信/上传) │ │ └── user/ # 用户模块接口 │ │ ├── index.ts # 用户相关(登出/余额/积分/资料) │ │ └── login.ts # 登录注册(登录/注册/找回密码/微信登录) │ ├── config/ # 配置文件 │ │ └── pages.ts # 页面路由配置(登录页/用户中心/Tabbar列表) │ ├── pages/ # 页面目录 │ │ └── index/ │ │ └── index.vue # 首页 │ ├── stores/ # Pinia 状态管理 │ │ ├── constant/ # 常量定义 │ │ │ └── cacheKey.ts # 缓存 Key 常量 │ │ ├── runtime.ts # 运行时状态(Tab切换参数) │ │ ├── siteConfig.ts # 网站配置状态 │ │ ├── userInfo.ts # 用户信息状态 │ │ └── menuRule.ts # 菜单权限状态 │ ├── styles/ # 样式文件 │ │ └── index.scss # 全局样式入口 │ ├── types/ # TypeScript 类型定义 │ │ ├── common.d.ts # 公共类型 │ │ └── stores.d.ts # Store 类型定义 │ ├── utils/ # 工具函数 │ │ ├── common.ts # 通用工具 │ │ ├── debounce.ts # 防抖/节流 │ │ ├── directives.ts # Vue 指令(v-auth 鉴权指令) │ │ ├── http.ts # HTTP 请求封装(含拦截器) │ │ ├── random.ts # 随机数/UUID 工具 │ │ ├── request.ts # 请求实例(配置拦截器) │ │ ├── router.ts # 路由权限处理 │ │ └── time.ts # 时间格式化 │ ├── App.vue # 应用入口 │ ├── main.ts # 主入口文件 │ ├── manifest.json # 应用配置 │ ├── pages.json # 页面路由配置 │ └── uni.scss # uni-app 全局样式变量 ├── public/ # 静态资源 ├── index.html # HTML 入口 ├── vite.config.ts # Vite 配置 ├── tsconfig.json # TypeScript 配置 ├── eslint.config.mjs # ESLint 配置 ├── .prettierrc.mjs # Prettier 配置 ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 └── package.json # 项目依赖 ``` ## 环境配置 ### 配置应用标识(AppID) 打开 `src/manifest.json`,在对应平台的配置中添加 `appid`: ```json { "mp-weixin": { "appid": "wxxxxxxxxxxxxx", "setting": { "urlCheck": false }, "usingComponents": true } } ``` ### 配置 API 域名 分别在 `.env.development` 和 `.env.production` 中配置开发和线上环境的接口地址: ```bash # .env.development - 开发环境 ENV = 'development' VITE_REQUEST_BASE_URL = 'http://127.0.0.1:8000' ``` ```bash # .env.production - 生产环境 ENV = 'production' VITE_REQUEST_BASE_URL = 'https://your-domain.com' ``` > 注意:后端部署 BuildAdmin 后,需在 `根目录/config/buildadmin.php` 中配置本 uni-app 工程的域名允许跨域请求。 ## 核心模块说明 ### HTTP 请求封装(request.ts / http.ts) 基于 `uni.request` 封装,提供完整的请求/响应拦截功能。 #### request.ts - 请求实例配置 已封装好的请求实例,配置了以下拦截器: - **请求拦截器** - 自动注入 `ba-user-token`(用户认证Token) - **响应拦截器** - 处理以下状态码: - `code 303` - 跳转登录页或用户中心(通过 `src/config/pages.ts` 配置) - `code 409` - Token 过期,自动刷新并重试请求 - `code 1` - 显示成功提示(需 `showSuccessMessage: true`) - 其他错误码 - 显示错误提示 #### http.ts - 核心请求类 提供以下方法: | 方法 | 说明 | 示例 | |------|------|------| | `request(config)` | 通用请求方法 | `http.request({ url: '/api/test', method: 'GET' })` | | `get(url, config?)` | GET 请求 | `http.get('/api/user/info')` | | `post(url, data?, config?)` | POST 请求 | `http.post('/api/user/update', { nickname: '新昵称' })` | | `upload(config)` | 文件上传 | `http.upload({ url: '/api/upload', filePath: '...', fileType: 'image' })` | | `download(config)` | 文件下载 | `http.download({ url: 'https://...' })` | #### 请求配置参数 ```typescript interface RequestConfig { url: string method?: 'GET' | 'POST' | 'PUT' | 'DELETE' data?: any header?: anyObj showLoading?: boolean // 是否显示 Loading(默认 false) showSuccessMessage?: boolean // 是否显示成功提示(默认 false) showCodeMessage?: boolean // 是否显示错误提示(默认 true) showErrorMessage?: boolean // 是否显示错误弹窗(默认 true) } ``` #### 使用示例 ```typescript import http from '@/utils/http' // GET 请求 http.get('/api/user/info').then((res) => { console.log(res.data) }) // POST 请求(带 Loading 和成功提示) http.post( '/api/user/update', { nickname: '新昵称' }, { showLoading: true, showSuccessMessage: true } ) // 文件上传 http.upload({ url: '/api/ajax/upload', filePath: tempFilePath, fileType: 'image', name: 'file', formData: { driver: 'local' }, showLoading: true }) ``` ### 页面配置(config/pages.ts) ```typescript // 登录页(请根据实际项目路径修改) export const LOGIN_PAGE: string = '/pages/user/login' // 用户中心(请根据实际项目路径修改) export const USER_PAGE: string = '/pages/user/user' // Tabbar 页面列表(请根据实际项目路径修改) export const TABBAR_PAGES: string[] = ['/pages/index/index', '/pages/user/user'] ``` 这些配置用于: - HTTP 拦截器处理 `code 303` 时判断使用 `switchTab` 还是 `redirectTo` - 刷新 Token 失败后跳转登录页 ### 状态管理(Pinia Stores) #### userInfo.ts - 用户信息状态 ```typescript import { useUserInfo } from '@/stores/userInfo' const userInfo = useUserInfo() // 设置令牌 userInfo.setToken('token值', 'auth') // auth=认证令牌, refresh=刷新令牌 // 获取令牌 userInfo.getToken() // 默认获取认证令牌 userInfo.getToken('refresh') // 获取刷新令牌 // 批量填充用户信息(排除 token 和 refresh_token) userInfo.dataFill({ id: 1, username: '张三', nickname: '昵称' }) // 判断登录状态 userInfo.isLogin() // 返回: id > 0 且 token 不为空 // 用户登出 userInfo.logout(() => { uni.redirectTo({ url: '/pages/login/index' }) }) // 移除令牌 userInfo.removeToken() ``` #### runtime.ts - 运行时状态 用于 Tab 页面之间跳转时传递参数(Tab 页面无法通过 `navigateTo` 传递参数)。 ```typescript import { useRuntime } from '@/stores/runtime' const runtime = useRuntime() // 设置 Tab 切换参数 runtime.setSwitchTabParams({ navUrl: '/pages/user/index', navType: 'switchTab' }) ``` #### siteConfig.ts - 网站配置状态 用于存储网站基本信息(名称、备案号、版本、CDN地址、上传配置等)。 ```typescript import { useSiteConfig } from '@/stores/siteConfig' const siteConfig = useSiteConfig() // 批量填充配置信息 siteConfig.dataFill({ siteName: '我的网站', recordNumber: '京ICP备xxxxxx号', version: '1.0.0', cdnUrl: 'https://cdn.example.com', apiUrl: 'https://api.example.com', upload: { mode: 'local', maxSize: 10485760, allowedSuffixes: '.jpg,.png,.gif', allowedMimeTypes: ['image/jpeg', 'image/png'] } }) ``` #### menuRule.ts - 菜单权限状态 用于存储和管理页面按钮鉴权信息。 ```typescript import { useMenuRule } from '@/stores/menuRule' const menuRule = useMenuRule() // 设置指定key的权限节点 menuRule.setAuthNode('/pages/index/index', ['index/add', 'index/edit', 'index/del']) // 合并权限节点 menuRule.mergeAuthNode(authNodeMap) // 添加需要登录的路径 menuRule.addNeedAuthPath('/pages/user/user') // 批量添加需要登录的路径 menuRule.addNeedAuthPaths(['/pages/order/index', '/pages/cart/index']) // 判断路径是否需要登录 menuRule.isNeedAuth('/pages/user/user') // true / false ``` ### API 接口层(api/) #### index.ts - API 统一导出 ```typescript // 从统一入口导入所有 API import { logout, getBalanceLog, getIntegralLog, postProfile, retrievePassword, wechatMiniProgramLogin, getIndexData } from '@/api' ``` #### index.ts - 首页接口 ```typescript import { getIndexData } from '@/api/index' // 获取首页初始化数据(包含用户信息、菜单规则、路由配置等) getIndexData(requiredLogin?:0 | 1) ``` #### common.ts - 公共接口 ```typescript import { refreshTokenUrl, uploadUrl, upload, sendSms, getCaptchaData, checkClickCaptcha } from '@/api/common' // 刷新 Token(已在 request.ts 中自动调用,无需手动调用) refreshTokenUrl // '/api/common/refreshToken' // 文件上传 upload(filePath: string, fileType: 'image' | 'video' | 'audio') // 发送短信 sendSms(mobile, templateCode, captchaInfo, captchaId) // 获取验证码数据 getCaptchaData(id) // 校验验证码 checkClickCaptcha(id, info, unset) ``` #### user/login.ts - 登录注册接口 ```typescript import { checkIn, retrievePassword, wechatMiniProgramLogin } from '@/api/user/login' // 用户登入/注册(支持登录和注册两种模式) checkIn({ tab: 'login', // 'login' | 'register' username: '手机号/邮箱', password: '密码', keep: true, // 是否记住登录 captchaId: '验证码ID', captchaInfo: '验证码坐标信息' }) // 找回密码 retrievePassword(data) // 微信小程序登录 wechatMiniProgramLogin(code) ``` #### user/index.ts - 用户模块接口 ```typescript import { logout, getBalanceLog, getIntegralLog, postProfile } from '@/api/user' // 用户登出 logout() // 获取余额日志 getBalanceLog(page, pageSize) // 获取积分日志 getIntegralLog(page, pageSize) // 更新用户资料 postProfile(data) ``` ### 工具函数(utils/) #### common.ts - 通用工具 ```typescript import { navigate, getBaseUrl, fullUrl, refresh, onTabBarPageLoad, getNavigateType, getCurrentRoutePath, deepClone, auth } from '@/utils/common' // 路由跳转(自动适配 switchTab/navigateTo/redirectTo/reLaunch/navigateBack) navigate('switchTab', { url: '/pages/index/index' }) navigate('navigateTo', { url: '/pages/detail/index?id=1' }) // 获取基础 URL getBaseUrl() // 返回 VITE_REQUEST_BASE_URL // 获取完整资源地址(自动处理协议、域名拼接) fullUrl('/uploads/avatar.png') // 返回完整 URL fullUrl('//cdn.example.com/logo.png') // 自动补全协议 // 刷新当前页面 refresh() // TabBar 页面加载处理(用于带参数的 TabBar 页面跳转) onTabBarPageLoad() // 获取导航类型(根据路径判断是 switchTab 还是 redirectTo) getNavigateType('/pages/index/index') // 'switchTab' getNavigateType('/pages/detail/index') // 'redirectTo' // 获取当前页面路由路径 getCurrentRoutePath() // '/pages/home/index' // 鉴权(判断当前用户是否有指定按钮权限) auth('add') // 检查当前页面的 add 按钮权限 auth({ name: '/pages/index/index', subNodeName: '/pages/index/index/add' }) // 检查指定页面的权限 // 深度克隆(支持 Date、RegExp、Map、Set、Array、Object) deepClone(obj) ``` #### time.ts - 时间格式化 ```typescript import { timeFormat, timeFrom } from '@/utils/time' // 格式化时间 timeFormat(null, 'yyyy-mm-dd hh:MM:ss') // '2026-06-03 14:30:00' timeFormat(Date.now(), 'yyyy-mm-dd') // '2026-06-03' timeFormat('2024-01-01', 'yyyy年mm月dd日') // '2024年01月01日' // 相对时间(多久以前) timeFrom(Date.now() - 120000) // '2分钟前' timeFrom('2024-01-01') // '1年前' timeFrom('2024-01-01', false) // '17个月前'(超出范围返回固定格式) timeFrom(1704067200, 'yyyy-mm-dd') // '2024-01-01'(时间戳支持 10 位秒或 13 位毫秒) ``` #### debounce.ts - 防抖/节流 ```typescript import { debounce, throttle } from '@/utils/debounce' // 防抖(延迟 delay 毫秒后执行,期间再次触发则重新计时) const debouncedSearch = debounce((keyword: string) => { console.log('搜索:', keyword) }, 300) // 节流(delay 毫秒内只执行一次) const throttledScroll = throttle(() => { console.log('滚动') }, 100) ``` #### random.ts - 随机数工具 ```typescript import { random, uuid } from '@/utils/random' // 生成指定范围内的随机整数 random(1, 100) // 1 ~ 100 之间的随机整数 // 生成 UUID uuid() // 'a1b2c3d4-e5f6-4xxx-yxxx-xxxxxxxxxxxx' ``` #### router.ts - 路由权限处理 ```typescript import { handleRoute } from '@/utils/router' // 处理路由权限节点(通常在登录成功后调用) handleRoute(routes, menus) // routes - 路由配置数据 // menus - 菜单配置数据 ``` ### 菜单权限配置 在小程序端使用按钮级权限控制前,需在 BuildAdmin 后台进行以下配置: 1. 进入 **会员管理 → 会员规则管理** 2. 点击 **添加普通路由**,填写路由名称和路径 3. 在该路由下添加 **按钮规则**(如:index、add、edit、del 等) > 注意:拓展属性请选择 **只添加为路由**,避免影响 Web端正常使用。 #### directives.ts - Vue 指令 ```typescript // 在 main.ts 中注册指令 import { directives } from '@/utils/directives' // 使用 v-auth 指令鉴权按钮 // // // //指令会自动根据当前页面路由路径查找权限节点,无权限的按钮会被移除 ``` ## 开发指南 ### 新建页面 1. 在 `src/pages/` 下创建页面目录,如 `src/pages/user/` 2. 创建 `index.vue` 文件 3. 在 `pages.json` 中注册页面(若使用 HBuilderX,可右键新建自动注册) ### 新建 API 接口 在 `src/api/` 下创建模块目录,如 `src/api/user/index.ts`: ```typescript import http from '@/utils/http' export const getUserInfo = () => { return http.get('/api/user/info') } export const updateUser = (data: anyObj) => { return http.post('/api/user/update', data, { showSuccessMessage: true }) } ``` ### 使用状态管理 1. 在 `src/types/stores.d.ts` 中定义状态类型 2. 创建 Store 文件(如 `src/stores/userInfo.ts`) 3. 在组件中使用: ```typescript import { useUserInfo } from '@/stores/userInfo' const userInfo = useUserInfo() ``` ### 登录页面示例 以下是一个完整的登录页示例,实现了登录成功后返回原页面的功能: ```vue ``` ## 常见问题 ### 小程序中 getCurrentPages() 获取不到页面栈 某些框架模式下可能无法直接调用。已在 `src/utils/common.ts` 中封装 `onTabBarPageLoad` 方法处理 Tabbar 页面参数传递。 ### Token 过期处理 HTTP 拦截器已自动处理 409 状态码(Token 过期),会尝试刷新 Token 并重试原请求。若刷新失败,将跳转至登录页。 ### 多端兼容 框架已处理各平台的差异: - `nvue` 页面使用 `uni.scss` 全局样式 - App 端权限配置在 `manifest.json` 中 - 小程序端的 appid 在 `manifest.json` 中配置 ## License MIT