# 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