# 小程序角色区分架构 **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`。