# 小程序角色区分架构
**Repository Path**: ssrss_admin/xcxrole
## Basic Information
- **Project Name**: 小程序角色区分架构
- **Description**: 小程序角色区分架构 ,不同角色页面控制,按钮控制,页面路由守卫
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-07-21
- **Last Updated**: 2026-07-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# wxapp-native-demo
原生写法(无框架)微信小程序项目模板,覆盖:
- 模拟登录(账号 / 微信一键 / 手机号+验证码)
- 获取微信头像(`button open-type="chooseAvatar"`)
- 获取微信手机号(`button open-type="getPhoneNumber"`)
- 业务组件封装与组件间传值(properties + 事件 + slot + 事件总线)
- 页面跳转传参与回传(`router.push / pushAndWait / resolve / receive`)
- 业务层、工具层、状态层清晰分层
## 目录
```
wxapp-native-demo/
├── app.js / app.json / app.wxss / sitemap.json
├── project.config.json / project.private.config.json
├── utils/ constants / logger / storage / eventbus / auth / params / router / request
├── services/ user / mock
├── components/ nav-bar / user-card / card-item / empty-state / action-bar
├── pages/ index / login / profile / list / detail
└── images/ tabBar 图标(见下)
```
## 快速运行
1. 用 **微信开发者工具** 打开本目录。
2. `project.config.json` 已使用 `touristappid`(游客模式,无需 AppID)。
3. 编译运行即可,默认进入 `pages/index/index`。
4. **tabBar 图标**:在 `images/` 放 `tab-home.png / tab-home-on.png / tab-list.png / tab-list-on.png / tab-me.png / tab-me-on.png`(建议 81×81 PNG),否则开发者工具会告警但不影响功能。
你也可以直接编辑 `app.json` 的 `tabBar` 移除 `iconPath / selectedIconPath`,或保持纯文字 tabBar。
## 4 种登录方式
入口:`pages/login/login`
| 方式 | 实现 | 关键 API |
| --- | --- | --- |
| 账号 | `services/user.js → loginByAccount` | mock |
| 微信一键 | `wx.login` 拿 code → mock 换 token | `wx.login` |
| 手机号+验证码 | mock 短信 + 倒计时 | `setInterval` |
| 头像 / 手机号 | `button open-type="chooseAvatar"` / `getPhoneNumber"` | `chooseavatar` / `getphonenumber` |
> **真后端**接入:把 `services/mock.js` 替换为 `utils/request.js` 调用 `request.post('/api/...')` 即可。
## 组件间传值
- **父 → 子**:`` + 组件内 `properties: { user: Object }`
- **子 → 父**:`` + 组件内 `this.triggerEvent('tap', { item })`
- **slot**:`详情`
- **跨页通信**:`utils/eventbus.js`(`on / off / emit / once`)
## 页面跳转传参
```js
// 列表页
router.push('/pages/detail/detail', { id: 'A001', title: 'xxx', from: 'list' })
// 详情页
onLoad(options) {
const params = router.receive(options) // 还原对象
// params = { id: 'A001', title: 'xxx', from: 'list' }
}
```
带 **回传** 的跳转:
```js
// 列表页
const r = await router.pushAndWait('/pages/detail/detail', { id: 1 })
// r = { ok: true, data: { read: true, at: 1730000000000 } }
// 详情页
onAction(e) {
if (e.detail.key === 'read') {
router.resolve({ read: true, at: Date.now() }) // 回传
router.back()
}
}
```
## 全局封装一览
| 模块 | 能力 |
| --- | --- |
| `utils/router.js` | `push / redirect / reLaunch / switchTab / back / ensureLogin / pushAndWait / resolve / receive / RouterGuard` |
| `utils/request.js` | `wx.request` Promise 化、拦截器、401 自动跳登录 |
| `utils/storage.js` | 同步/异步 + JSON 编解码 |
| `utils/auth.js` | token / userInfo / phoneInfo / loginType 持久化与读写 |
| `utils/eventbus.js` | 全局 on/off/emit/once |
| `utils/params.js` | 对象与 query 字符串互转 |
| `utils/logger.js` | 带开关的日志 |
| `utils/perm.js` | 角色与权限码:`has / hasAny / hasAll / setup / refreshFromAuth`(JS 端) |
| `utils/perm.wxs` | wxml 端:`$perm.has(code, role) / $perm.hasAny(...codes, role) / $perm.hasAll(...codes, role)` |
| `utils/tabbar.js` | 按角色动态控制 tabBar 显隐:`applyTabBar / show / hide` |
## 三层鉴权体系
```
┌──────────────────────────────────────────────────────────┐
│ ① 页面级(utils/router.js → RouterGuard) │
│ Page(RouterGuard({ meta: { requiresAuth: true, │
│ roles: ['admin'] } })) │
├──────────────────────────────────────────────────────────┤
│ ② 按钮级(utils/perm.wxs + utils/perm.js) │
│ wxml: │
│ js : if (perm.has('list.export')) { ... } │
├──────────────────────────────────────────────────────────┤
│ ③ tabBar 显隐(utils/tabbar.js) │
│ 按角色 + perm 自动 setTabBarItem({ visible }) │
│ 监听 LOGIN_SUCCESS / LOGOUT / PERMISSION_CHANGED 自动刷新│
└──────────────────────────────────────────────────────────┘
```
## 权限码与角色映射
权限码定义在 `utils/constants.js → PERMS`:
| 权限码 | 含义 |
| --- | --- |
| `home.view` | 首页可见 |
| `tab.list` | tabBar 列表 tab |
| `tab.me` | tabBar 我的 tab |
| `profile.view / profile.edit / profile.logout` | 个人中心 |
| `list.view / list.search / list.export / list.batch` | 列表 |
| `detail.view / detail.edit / detail.audit / detail.delete` | 详情 |
| `admin.panel / admin.users` | 管理功能 |
角色 → 权限码:
| 角色 | 权限 |
| --- | --- |
| `admin` | `*`(全部) |
| `operator` | home / 全部 tab / profile.* / list.* / detail.{view,edit,audit} |
| `viewer` | home / tab.me / profile.{view,logout} / list.{view,search} / detail.view |
| `guest` | home.view |
## 按钮级权限用法(wxml)
```xml
无 profile.edit 权限
```
> 页面 onShow 时 `this.setData({ role: perm.getCurrentRole() })`;权限变化会通过 `EVENTS.PERMISSION_CHANGED` 自动 setData。
## 按钮级权限用法(js)
```js
const perm = require('../../utils/perm.js')
if (!perm.has('detail.delete')) return wx.showToast({ title: '无权限', icon: 'none' })
```
## tabBar 动态显隐
`app.json` 中的 tabBar 是"基线"(保证 switchTab 不报错),运行时 `utils/tabbar.js → applyTabBar` 根据 `TAB_RULES.visibleRoles` 和 `tab.perm` 调用:
```js
wx.setTabBarItem({ index, visible: true }) // 基础库 2.21.0+
```
不满足基础库时降级为 `text:''` + `iconPath:''`。
**可见性规则:**
| 角色 | 首页 | 列表 | 我的 |
| --- | --- | --- | --- |
| `admin` | ✅ | ✅ | ✅ |
| `operator` | ✅ | ✅ | ✅ |
| `viewer` | ✅ | ❌ | ✅ |
| `guest` | ✅ | ❌ | ❌ |
**手动触发刷新:**
```js
const tabbar = require('../../utils/tabbar.js')
tabbar.applyTabBar() // 用当前 role
tabbar.applyTabBar({ role: 'admin' }) // 强制指定
```
**自动触发:**
- 登录成功 → `perm.refreshFromAuth()` → `applyTabBar()`
- 退出登录 → `perm.setup('guest')` → `applyTabBar()`
- 切换角色 → `perm.setup(role)` → `applyTabBar()`
## 测试场景
1. 打开 App → tabBar 3 项全显示(初始 guest 实际只显示首页,**因为 tabBar 在 onLaunch 时按当前 role 渲染**)
2. 去「登录」选 admin → 登录 → tabBar 3 项全显
3. 去「我的」→「切换角色」选 viewer → tabBar 列表 tab 立即消失
4. 切到 operator → 列表 tab 重新出现,但"管理面板"按钮不显示
5. 切到 admin → 全部按钮 + 管理面板出现
6. 退出登录 → tabBar 只剩首页 + 我的(如果还有的话)
## 注意事项
- `getPhoneNumber` 仅企业小程序可获取真实手机号;个人/测试号会返回 `getPhoneNumber:fail`。Demo 内做了 mock 处理。
- `chooseAvatar` 需要基础库 `2.21.2+`。
- 已在 `project.config.json` 中开启 `useApiHook` / `useApiHostProcess` / `useMultiFrameRuntime`。