---
name: microi-mobile-app-quality
description: Microi 移动端质量门禁，适用于 UniApp/H5/微信小程序。用于创建、重设计、修复、测试或交付任何 Microi 移动端项目，覆盖登录、底部导航、快捷入口、按钮、动效、菜单层级与移动端视觉验收。
---

# Microi 移动端质量门禁

每次交付 Microi UniApp/H5/微信小程序时都必须应用本 skill。这里记录的是移动端项目反复出现、必须避免的问题。

自动触发：只要任务涉及 Microi 移动端应用、H5、微信小程序、App 构建、uni-app 项目、登录页、tabbar、首页、我的页、工作台、报告页、视觉重设计或移动端验收，即使用户没有明确点名，也要应用本 skill。

## 1. 导航和快捷入口必须使用真实图标

底部导航、首页快捷入口、会员中心快捷项、九宫格操作和悬浮操作，必须在文字上方或旁边显示可识别的图标。

要求：
- 使用 Microi.UI 图标、项目 `mci-icon-*` CSS 图标、iconfont 或稳定的本地图标组件。
- 首页、订单、报告、报修、我的等底部导航项必须显示图标，不能用 `首` / `单` / `报` / `我` 这类单字代替。
- 首页入口宫格和我的/个人中心快捷项同样适用。
- 主题、租户、版本、账号、关于、客户绑定、服务入口等个人中心/设置/信息块必须使用真实图标，不能用 `租` / `版` / `客` 这类单字代替。
- 图标必须是真实视觉符号，不能依赖远程占位图。
- tabBar、返回、关闭等小型交互图标若使用 SVG 或图片，必须本地化、纳入版本管理并检查小程序打包结果；Hero、Banner、音视频、字体等大资源遵守 `microi-uniapp-frontend` 的租户 HDFS/CDN 规则，不得为“本地化”无边界增大主包。
- 彩色圆形、胶囊、浮动快捷入口必须同时定义图标底色和内部图标色，不能只依赖继承。红、绿、灰等深色背景优先白色图标；黄色、浅色背景必须使用深色图标。
- 如果基础样式里存在 `.entry:nth-child(n)`、`.mci-bubble:nth-child(n)` 等颜色规则，主题覆盖必须同时覆盖 `background` 和 `color`，避免只换圆底但图标仍沿用旧主题色。
- 主题图标色覆盖必须具备不低于基础规则的 CSS 优先级；构建压缩后仍要检查产物。如果压缩器移除了同值 `color`，可对图标对比度兜底规则使用 `!important`，但只限这类可读性护栏。

禁止：
- 纯文字导航图标。
- 用单个汉字冒充图标。
- 缺失图标占位、404 远程图标或只靠 emoji 的图标体系。

验收：
- 截图检查每个底部导航、首页快捷入口和会员快捷入口。
- 确认 H5、微信小程序和 App 构建目标中，图标加文字可见、对齐且可点击。
- 主题切换后重新截图底部导航、首页快捷入口和个人中心快捷入口；任何一个图标在圆底上看不清，都算验收失败。
- UniApp H5 桌面预览可以显示手机壳；真机和浏览器移动设备仿真必须自动去壳并铺满视口。自动化测试要分别断言桌面手机壳存在、移动端手机壳标题隐藏，同时检查所有底部菜单包含真实图标节点。

## 1.1 包体资源必须按用途分层

- 构建前扫描图片、音频、视频、字体和第三方资源；公开的大资源优先上传到当前租户 HDFS 公有桶并通过 `sys_config.FileServer`/CDN 引用，敏感资源使用私有桶。
- 只保留小型交互图标和离线关键资源在主包。任何远程迁移都必须同步检查小程序下载域名、失败占位、缓存策略和弱网首屏。
- 压缩以用户可感知质量为边界：图片检查文字与主体细节，音频抽听，视频抽播；禁止仅为满足扫描数字而过度压缩。
- 验收必须同时给出包体扫描、CDN 匿名 `200`、正确媒体类型和多尺寸截图证据。缺任一层都不能宣称资源问题已通过。

## 2. 不要猜测 Microi 前端 SDK 登录接口

编写登录代码前，先检查本地项目 SDK 封装，例如 `src/utils/microi.v8.js`、`src/utils/api.js`，或标准 `microi.uniapp` 登录实现。

要求：
- 使用实际导出的接口。如果 SDK 暴露的是 `V8.Login(param)`，不要写 `V8.Login.Login(...)`。
- 员工/账号登录应通过项目 SDK 封装调用平台登录端点，通常是 `/api/SysUser/Login` 或 `V8.Login(param)`。
- Token 提取必须同时支持响应头和响应体兜底。
- 登录成功必须同时满足 `Code=1`、已获取有效 token、已获取有效用户对象且存在 `Id`。任一条件缺失都必须清理 token、用户缓存和本地会话，并以失败处理；不能只因为接口返回成功或缓存了账号名就显示为半登录。
- 会话恢复必须重新校验 token 和用户 `Id`。如果本地只有 `staffUser.Account/Name` 但没有 token，必须清空缓存，不能出现“姓名是 admin、状态是未登录”的矛盾界面。
- 登录后所有页面的 `isLogin`、头像姓名、角色文本、未登录提示和可见按钮必须来自同一个 session store，不要让单页自己读取旧缓存。
- 登录实现后的第一个测试必须包含真实点击登录按钮，并检查控制台和网络请求。
- 登录必须传移动端 `_ClientType` 和稳定 `did`，并在 `App.onShow` 调用标准 SDK 的 `resumeAuthSession(false)`。系统休眠后不能只等定时器恢复。
- 登录失效提示必须原样展示后端 `Msg`；如果 Token 已过期，显示过期分钟/小时/天，如果 Token 属于其它租户，显示 Token 租户与当前租户。详细协议读取 `microi-frontend-sdk/SKILL.md`。

禁止：
- 凭空编造 SDK 子对象。
- 把接口引擎登录和 `SysUser` 账号登录当作同一个契约。
- 未测试真实按钮路径就交付登录页。

验收：
- H5 路由 `/pages/login/login` 或项目登录路由打开时无控制台错误。
- 点击账号登录不会抛出 `V8.Login.Login is not a function`。
- 使用真实系统账号登录后，必须验证“我的”页显示已登录角色，首页/工单/报告不再出现未登录提示，刷新页面或切换底部导航后仍保持一致。

## 2.1 登录验证码必须跟随 Sys_Config.EnableCaptcha

PC 端、H5、App、微信小程序或任何自定义前端只要调用 `/api/SysUser/login`、`/api/SysUser/Login` 或 `V8.Login(param)`，都必须先读取 `Sys_Config` 的 `EnableCaptcha` 配置，并按配置决定是否展示和提交图形验证码。

要求：
- 启动登录页时调用 `/api/DiyTable/GetSysConfig` 或项目 SDK 的 `V8.GetSysConfig(true)`，读取当前租户启用状态。
- `EnableCaptcha` 可能是 `1`、`true`、`'1'`、`'true'`，也可能是大小写不同的字符串。必须使用统一的 `isEnabledFlag(value)` 或等价函数判断，不能直接 `!!value`，否则字符串 `'0'` 会被误判为开启。
- 开启验证码时，登录表单必须显示验证码输入框和验证码图片；验证码图片通过 `GET /api/Captcha/GetCaptcha` 获取，读取响应头 `captchaid`，提交登录时附加 `_CaptchaId` 和 `_CaptchaValue`。
- 登录失败、验证码错误、网络错误后必须刷新验证码并清空验证码输入；验证码未填写时前端直接阻止提交并提示用户。
- 未开启验证码时不得显示验证码输入，也不得提交空 `_CaptchaId/_CaptchaValue` 影响正常登录。
- 微信小程序默认手机号授权登录仍是主路径；账号/手机号 + 密码兜底入口如果调用 `SysUser/login`，同样要遵守验证码规则。

禁止：
- 不读取 `Sys_Config.EnableCaptcha` 就直接调用账号登录。
- 只在 PC 端做验证码，移动端缺失验证码。
- 直接写 `if (SysConfig.EnableCaptcha)` 或 `!!cfg.EnableCaptcha`，没有兼容 `'0'`、`'1'`、`'true'` 等字符串。
- 登录失败后继续使用旧 `captchaid` 和旧验证码值。

验收：
- 人工或自动化把 `EnableCaptcha` 分别模拟为 `1`、`true`、`'1'`、`'true'`，确认验证码出现并随登录提交。
- 模拟 `EnableCaptcha` 为 `0`、`false`、`'0'`、空值，确认验证码不出现且登录请求不带空验证码字段。
- 检查网络请求：`/api/Captcha/GetCaptcha` 返回后已保存 `captchaid`；登录请求体包含 `_CaptchaId/_CaptchaValue`。
- 参考标准实现：`microi.uniapp/src/pages/login/index.vue`。

## 3. OsClient 请求头不得重复

Microi 请求只能发送一个不区分大小写的 OsClient 请求头。浏览器、代理或服务端运行时可能把 `OsClient` 和 `osclient` 这类大小写重复键合并成 `demo, demo`，导致租户识别失败。

要求：
- 通过统一工具构建请求头，设置 `osclient` 前先删除已有的不区分大小写匹配项。标准 SDK 的 `buildHeaders` 必须使用单值写入函数，不得直接写 `headers.OsClient = ...`。
- 优先使用一个标准键，通常是小写 `osclient`，并且只传一个运行期值，例如 `demo`。
- `Authorization` / `authorization` 以及其它单值鉴权头也要做同样的大小写去重。
- 当 Microi 端点契约需要时，请求体或查询参数可以包含 `OsClient`，但请求头仍只能包含一个 `osclient` 值。

禁止：
- 同时设置 `headers.OsClient` 和 `headers.osclient`。
- 同时设置 `headers.Authorization` 和 `headers.authorization`。
- 网络面板里已经看到 `demo, demo` 仍然交付。

验收：
- 在网络面板或请求适配器日志里检查登录请求。
- 确认目标租户请求头正好是一个运行期值（例如 `osclient: demo`），没有被逗号合并。
- 微信/支付宝/飞书/抖音等小程序授权登录接口也必须检查，例如 `/apiengine/miniprogram-login`，不得出现 `osclient: demo, demo` 这类合并值。

## 3.1 小程序授权登录必须可追踪、可读错误

微信开发者工具的模拟授权与体验版真机调用不是同一条真实链路。手机号授权登录接口必须按阶段诊断，不能只返回“手机号登录失败”。

要求：
- 接口入口生成短追踪号，并在微信身份交换、AccessToken、手机号交换、账号匹配、用户更新/注册、兼容映射、系统 Token 签发前更新阶段名。
- 每个失败出口和顶层 `catch` 都调用统一 `fail(stage, message, detail)`：写 `V8.Method.AddSysLog`，同时返回包含“阶段 + 原因 + 追踪号”的 `Msg`。系统日志使用独立 MongoDB 日志，不依赖业务事务提交。
- 日志只记录 OsClient、AppId、阶段、微信 `errcode/errmsg`、是否收到授权码、脱敏手机号和必要业务 Id；严禁记录小程序 Secret、AccessToken、完整手机号、`detail.code`、`LoginCode`、OpenId 或请求头 Token。
- 微信 `jscode2session`、AccessToken、`getuserphonenumber` 必须分别捕获 HTTP 异常与微信业务错误，保留官方 `errmsg`，不要用一个大 `catch` 抹掉失败位置。
- 前端请求适配器必须把 `uni.request.fail.errMsg`、HTTP 状态、后端 `Msg` 归一化；手机号登录失败使用可完整阅读的模态框展示，不用会截断长文本的短 Toast。
- 用户拒绝授权可以使用简短提示；非拒绝类授权错误、网络错误、后端错误必须显示真实原因和追踪号。

验收：
- 用无效 `LoginCode` 和无效手机号授权码分别烟测，响应包含不同阶段和追踪号，并能在系统日志按追踪号查到对应记录。
- 模拟 `uni.request` 域名未配置、超时、HTTP 500、接口 `Code=0`，前端均显示具体原因，loading 在 `finally` 中恢复。
- 体验版真机复测授权登录；不能只以开发者工具模拟成功作为上线依据。

## 3.2 微信小程序每个页面默认支持分享

小程序项目必须默认支持转发给朋友和分享到朋友圈，不能只给首页或公开页添加分享。登录和权限控制属于访问阶段，不得用来隐藏分享能力。

要求：
- 以 `pages.json` 为路由清单逐页接入 `onShareAppMessage` 与 `onShareTimeline`；Vue3 组合式 API 页面必须从 `@dcloudio/uni-app` 直接导入并注册两个生命周期，不能只写一个全局工具后假设编译器会自动发现。
- 页面显示时确保微信分享菜单包含 `shareAppMessage`、`shareTimeline`；分享标题使用业务标题或稳定页面标题，资讯/报告等内容页优先带预览图。
- 好友转发返回以 `/` 开头的完整 `path`；朋友圈返回当前页面业务 `query`。保留 `id`、分类、公开报告 ShareToken 等定位参数，剔除 Authorization、AccessToken、CustomerToken、手机号授权码、LoginCode、OpenId、验证码等敏感或一次性参数。
- 需要登录或角色权限的页面仍允许分享原页面。接收者打开后再由页面鉴权提示登录/无权限，不能把所有受保护页面的分享地址强制改成首页。
- 登录页本身也必须支持分享，避免接收者被鉴权重定向后失去转发入口。

验收：
- 静态扫描 `pages.json` 与页面源码，页面总数必须等于同时注册两种分享生命周期的页面数。
- 执行 `build:mp-weixin`，逐页检查编译后的页面 JS 存在 `onShareAppMessage`、`onShareTimeline`，不能只检查源码或只确认构建成功。
- 在微信开发者工具和体验版各抽测公开页、登录页、一个需登录详情页：朋友转发和朋友圈入口都存在，接收者路径参数正确；未登录接收者看到登录提示而不是白屏或 404。

## 4. 重要按钮必须带图标

醒目的主操作必须使用打磨过的图标加文字按钮。

要求：
- 登录、去登录、提交、保存、确认、接单、报修、生成报告、上传照片以及首屏主操作必须包含图标。
- 按钮必须有可见的按下态、加载态、禁用态，并有足够触控高度。
- 主按钮应使用项目品牌渐变或品牌纯色，阴影克制，文字对比度安全。
- `open-type="getPhoneNumber"` 这类小程序原生按钮必须样式化为 `mci-btn`，并移除默认边框。

禁止：
- 首屏、空状态、登录页或固定底栏里出现纯文字主按钮。
- 按钮文字没有垂直居中。
- 异步操作按钮没有加载反馈。

验收：
- 截图检查空状态、登录页和表单提交页。
- 确认主操作有图标、合适的加载文案和按下反馈。

## 4.1 模块列表优先使用声明式业务卡片

Microi.Client 的标准模块移动端不应把 PC 表格字段机械纵向堆叠。优先在
`sys_menu.ViewSchema` 配置 `Scene=Card, Device=Mobile`：

- `AvatarTextField`：头像首字或业务简称；没有头像时才显示序号。
- `TitleField`：唯一主标题，最多两行，不能被右侧金额挤成单字列。
- `TopFields/StatusFields`：顶部状态、分类、阶段标签，使用少量语义色。
- `SubtitleFields`：客户、负责人、供应商等一至两项关联信息。
- `RightFields`：金额、未付、库存等高辨识度数值，支持 `Prefix/Suffix/Tone/Color`。
- `Fields/MetaFields`：正文和编号、创建人、日期等弱化信息。
- `BottomFields`：联系人、跟进、合同、附件等可行动的计数或标签。

要求：

- 卡片内边距通常 12 至 14px，标题、弱信息、金额形成清晰三级层次；不能全卡同字号同颜色。
- 选择框、更多、底部操作和浮动主按钮的触控区域至少 40 至 44px。
- 选中态用边框/底色/勾选状态表达，不通过 `transform` 位移导致列表跳动。
- 只配置字段值和样式时使用 ViewSchema；复杂 HTML 才使用字段 `V8TmpEngineTable`，仍须净化。
- 未配置 Card 视图时兼容 `MobileListFields/CardTitleTagFields/CardBottomTagFields`，不得白屏。
- 卡片引用字段必须进入查询列；关联计数由列表接口批量返回，禁止每张卡片再次请求。

验收：

- 以 375x812、390x844、430x932 至少三种视口检查长标题、空值、大金额、多标签和选中态。
- 检查顶部/右侧/底部多字段与模板字段均能显示，滚动到底后浮动按钮不遮挡最后一张卡。
- 批量选择后出现底部操作条；取消选择、执行按钮、更多菜单均可单手点击。

## 5. 首屏文字和浮层不得重叠

移动端首屏常组合大首屏区域和悬浮快捷面板。这个布局必须视觉检查，因为过大的中文标题和激进的负边距容易造成难看的换行或遮挡主按钮。

要求：
- 首屏标题必须使用能适配真实中文文案的字号，在常见 375px 和 430px 手机宽度下都要可读。
- 两行中文标题要有足够行高；紧凑业务首屏内不要使用过大的展示字。
- 使用悬浮快捷面板时，首屏底部要给操作区预留内边距，负边距只能轻微覆盖装饰空间。
- 首屏主按钮必须完整可见且可点击，包括阴影和圆角底边。

禁止：
- 首屏标题换行成难看的单字或双字第二行。
- 悬浮面板覆盖登录、报告或提交按钮。
- 通过隐藏按钮或缩小触控区域来解决重叠。

验收：
- 在 375px 和 430px 宽度截图首屏。
- 检查首屏标题、主/次按钮和后续悬浮面板是否裁切或重叠。

## 5.1 未登录/授权提示必须在可用内容区居中

未登录、未授权、无权限等提示模块不能贴在页面顶部。页面上方有 hero/header，下方有 tabBar 或固定底栏时，提示卡片和“立即登录/去授权”按钮必须在剩余可用内容区上下左右居中。

要求：
- 复用统一组件，例如 `MciAuthPrompt` / `mci-auth-prompt`，不要每个页面复制一套未登录提示样式。
- 页面容器使用 `flex-direction: column` 时，未登录提示外层必须 `flex: 1`、`display:flex`、`align-items:center`、`justify-content:center`，并考虑底部安全区。
- 如果组件放在自定义组件根节点内，业务页仍要提供外层居中 wrapper，避免小程序自定义组件根节点不参与父级 flex 导致卡片贴顶。
- 未登录提示的按钮文字和图标必须在按钮内上下左右居中。

禁止：
- 未登录提示卡片紧贴 header 下方，只在横向居中、纵向不居中。
- 因 tabBar、刘海屏、安全区或固定底栏导致提示卡片视觉中心偏上。

验收：
- 截图检查工作台、消息、我的等未登录态页面，卡片和主按钮必须在 header 与 tabBar/底栏之间的可用区域居中。
- 375px、430px、iOS 刘海屏/灵动岛和 Android 状态栏场景均不得出现贴顶或按钮文字偏移。

## 5.2 自定义导航页面必须通过安全区与微信胶囊门禁

`navigationStyle: custom` 代表应用接管了系统导航区域，页面壳必须同时负责状态栏、刘海/灵动岛、微信右上角胶囊和底部手势区，不能把这一责任留给业务页面自行估算。

要求：
- 统一页面壳读取 `uni.getWindowInfo()`，旧运行时回退 `uni.getSystemInfoSync()`；顶部至少使用 `statusBarHeight`，底部使用 `safeAreaInsets.bottom` 或 `screenHeight - safeArea.bottom`。
- CSS `env(safe-area-inset-*)` 只能作为 H5 兜底，不能作为微信小程序唯一实现。真实值应注入 `--mci-safe-top`、`--mci-safe-bottom` 等共享变量。
- 微信小程序必须读取 `getMenuButtonBoundingClientRect()` 并为顶部栏预留胶囊右侧宽度；标题、登录、分享、状态按钮与返回按钮都不能和胶囊相交。
- 全屏弹层或工作台的多按钮头部必须纳入同一门禁。若按钮组不能完整放在胶囊左侧，标题和按钮组整体布局到胶囊底边以下；不得让关闭按钮被原生“更多/关闭”覆盖。
- 底部导航、fixed 提交栏、底部弹层和正文滚动区必须消费同一个底部安全变量，正文还要预留完整固定栏高度。
- 审计 `pages.json` 的全部页面：每一个自定义导航路由都必须使用统一安全页面壳。首页通过不代表详情页、表单页、管理页已经通过。

自动化验收：
- 在微信开发者工具至少选择一台 iPhone 刘海/灵动岛机型和一台 Android 机型，逐页访问 `pages.json` 全路由并截图。
- 断言首个可交互元素位于状态栏下方，逐个读取顶部按钮与胶囊的矩形并确认不相交，底部导航/按钮位于手势条上方，最后一条滚动内容可完整显示。
- 发现任意页面被遮挡时，必须修复共享页面壳并重跑全路由；禁止只给当前截图页面增加固定 padding。

## 5.3 全屏工具页必须遵守返回状态栈

- AI 助手、扫码工作台、全屏预览等独占视口功能需要手机侧滑返回时，优先使用独立路由承载；普通 `position:fixed` 蒙层不能冒充页面历史。
- 返回事件按“键盘/确认框 -> 内部抽屉/筛选 -> 当前全屏页 -> 底层业务页”的顺序消费。关闭按钮与手机返回手势必须得到一致结果。
- 从 Tab 页进入后，第一次返回只能关闭全屏工具页并回到原 Tab；路由栈异常或分享直达时才兜底回首页，禁止退出小程序或跳过原页面。
- 自动化至少覆盖关闭按钮、Android 返回键/`onBackPress`、微信侧滑返回三条路径，并验证返回后原页面和滚动状态仍然可用。

## 5.4 微信浮动入口必须通过真实事件桥门禁

- UniApp 自定义组件中的浮动按钮、拖拽助手和悬浮客服不得只做 H5 点击测试。必须在微信运行时找到真实组件节点，派发 `touchstart/touchend`，并断言页面栈、弹层状态或业务动作确实变化。
- `touch* / tap` 上的 `.stop/.prevent` 会编译成 `catchtouch* / catchtap`；禁止在可拖拽组件上整组滥用。若出现“看得见但点不动”，先检查生成 WXML 是 `catch*` 还是 `bind*`，再检查事件方法与路由，不要用构建成功替代交互验收。
- 短触与拖动应通过明确位移阈值区分。短触在 `touchend` 完成主动作，拖动只更新位置并持久化，导航失败必须给用户可见反馈。
- 自动化需增加对照按钮：同页普通按钮可点击、浮动入口也可点击，才能确认不是自动化连接或页面整体失效。

## 6. 后台菜单必须规划为至少两级

真实业务系统的后台菜单不能简单堆成一批一级菜单。

要求：
- 先创建父级菜单分组，再把子级 CRUD 模块放到对应分组下。
- 相关页面超过三个的业务模块必须有父级目录菜单。
- 建议分组示例：
  - 客户中心：客户、站点、联系人、客户账号绑定。
  - 设备中心：设备台账、设备模板、维保参数。
  - 维保运营：计划、工单、服务记录、维修申请。
  - 报告中心：巡检报告、阅读日志、打印/分享模板。
  - 系统配置：字典、任务、集成设置。
- 使用 Manifest/MCP 时，必须显式包含父模块和子模块。子模块必须设置 `ParentId`。
- dry-run 计划必须列出最终菜单树，不能只列平铺菜单名。
- 如果用户要求通过 MCP 修复已有后台，要真正执行远端工作：读取 `sys_menu`，创建缺失的父级 `SecondMenu` 行，更新现有子菜单 `ParentId` / `Sort`，给管理员角色授权新父菜单，然后回读菜单树。
- 当用户明确要求修改当前 MCP 租户时，不要只把规则写进 skill 就停下。

禁止：
- 把所有生成模块直接创建到根菜单。
- 把客户主数据、工单、报告、日志和设置混在同一级。

验收：
- MCP 生成后回读 `sys_menu` 并确认菜单深度。
- 最终回复必须说明通过 MCP 写入的真实菜单树，以及执行过的权限刷新。

## 7. 移动端页面需要动效，但动效必须有用

移动端产品不应像静态后台表单。

要求：
- 页面首屏、面板、卡片和重要操作区使用克制的入场动画。
- 点击目标要有按下反馈。
- 骨架屏应有轻微流光或脉冲。
- 装饰动效幅度要小，不能干扰任务完成。
- 支持时尊重减少动态效果偏好。

禁止：
- 整个应用没有交互反馈。
- 在密集业务列表上使用重度循环动画。
- 动效导致布局位移或文字重叠。

验收：
- 浏览器或设备检查确认卡片、面板、按钮或骨架屏有可见但克制的动效。
- 没有动画导致横向溢出、文字裁切或固定栏抖动。

## 8. 登录页必须是直接登录界面

登录页不能强迫用户先在两个身份标签之间切换才能登录。

要求：
- H5/App 提供一个账号或手机号 + 密码表单。除非后端明确支持并要求第二条路径，否则不要把独立手机号客户登录表单和账号密码表单并列展示。
- 微信小程序默认使用 `<button open-type="getPhoneNumber">` 的手机号授权登录。账号密码登录可以作为次级兜底，但必须折叠或弱化，不能和手机号授权并列成第二套完整登录系统。
- 微信小程序手机号快捷登录必须把返回的手机号 `code` 传给后端；当后端需要 OpenId/UnionId 时，还必须调用 `uni.login()` 获取新的 `LoginCode`。
- H5/App 兜底只有在后端支持手机号登录时才提供手动手机号输入。
- 登录文案要清楚：账号/手机号 + 密码是一条路径；微信手机号授权是小程序默认路径。
- 登录页不要展示当前租户、OsClient、移动端构建版本、API host、调试版本块等内部实现信息。

禁止：
- 除非用户明确要求，否则把员工/客户身份标签作为主登录模型。
- 同屏展示两套完整登录系统，例如“账号 + 密码”和“手机号输入登录”并列。
- 假设前端可以从 `getPhoneNumber` 直接读取微信手机号；现代微信返回的是 code。
- 目标是微信小程序时，只把手机号登录实现成文本输入。
- 向终端用户展示租户、版本或调试块。

验收：
- 实现前检查标准参考 `microi.uniapp/src/pages/login/index.vue`。
- 测试账号登录路径和手机号登录按钮渲染。
- 构建 H5 和微信小程序目标。

## 9. 主题切换必须真实且全局生效

当客户要求增加另一种视觉风格时，除非用户明确要求删除，否则要把当前已认可设计保留为一个命名主题，而不是直接覆盖。

要求：
- 主题命名要表达视觉意图，不要沿用临时客户措辞。例如：`清新绿红`、`品牌经典`、`专业深色`。
- 用 `uni.setStorageSync` 或项目主题运行时持久化主题选择。
- 如果产品有未登录也可打开的我的/个人中心/设置页，主题切换必须在登录前可用。
- 主题切换应是紧凑的“切换主题”操作，打开弹窗或底部面板；除非页面明确是完整设置页，否则不要把所有主题选项直接堆在我的/个人中心首页。
- 主题状态要作用到每个页面根节点、固定底部导航、空状态、骨架屏、加载过渡、按钮、卡片、报告详情和 H5 桌面手机壳。只改变当前页的主题是不完整的。
- 小程序构建不能只依赖 `document.documentElement`；应使用页面根类、CSS 变量或跨端主题服务。
- H5 uni-app 主题服务只能修改安全外壳：`html`、`body` 的 `data-*` 属性、主题 class 和 CSS 变量。不要通过 `querySelectorAll('.mci-page')`、`MutationObserver` 或定时扫描去改 `.mci-page`、`uni-page-body`、`uni-page`、`RouterView` 下的 Vue/uni 托管节点 class，否则容易触发 Vue 内部只读字段和空 vnode 错误。
- 固定底部导航、固定提交栏、悬浮操作条等 fixed 组件在 H5 中优先继承 `html/body` 上的 `--mci-*` 主题变量，避免因为组件自己订阅 theme store 或动态绑定 `bottom-nav--theme` class 导致导航时重渲染。小程序端可在组件根节点绑定稳定主题 class，但不要在 H5 路由切换过程中改变 Vue 托管根节点结构。
- 如果页面局部 scoped CSS 写死颜色，要增加主题覆盖或重构为 `--mci-*` 变量；骨架屏、加载流光、页面过渡遮罩、报告封面、英文小标题、印章/水印、摘要卡和富文本容器也必须使用主题变量。
- H5 主题变化不能破坏 uni-app 路由补丁。如果切主题后点击导航出现 `Cannot assign to read only property '_'`、`Cannot read properties of null (reading 'type')`、`parentNode`、`scheduler flush`、`updateSlots` 等错误，必须先移除对 Vue/uni 托管节点的 DOM 改写，保持页面 class 稳定，并改用 `html/body` 变量驱动主题。
- 底部导航必须防止重复点击当前路由、对连续点击做防抖，并在主题切换后略微延迟路由跳转，确保 DOM/主题更新先于 `uni.reLaunch` 完成。

禁止：
- 新增客户偏好主题时删除此前已认可设计。
- 主题切换效果在导航或重启后消失。
- 主题卡片/选项使用纯文字假图标。
- 主题切换破坏底部导航或产生 Vue scheduler 错误。
- 为了补主题而直接改 `.mci-page`、`uni-page-body`、`uni-page` 等运行时节点 class。
- 只让首页和列表页换主题，详情页、骨架屏、加载过渡或报告页仍残留旧主题。

验收：
- 在未登录的我的/个人中心页切换主题，并导航到首页、登录页、列表页、详情页和表单页。
- 确认底部导航、主按钮、卡片、空状态和页面背景都一致变化。
- 刷新 H5 页面或重启小程序后确认已选主题恢复。
- 对 `pages.json` 中每个路由、每个命名主题做截图验证。重点检查文字对比度，尤其是快捷卡片、报告卡片、空状态、底部导航、首屏文字和弹窗/底部面板内容。
- 主题切换后必须额外截图或断言：底部导航容器背景、未选中项文字、选中项文字、选中图标圆底、首页快捷入口图标颜色都已经切到当前主题，而不是残留旧主题。
- 主题切换后必须继续点击每个底部导航项，并打开至少一个详情路由（例如报告详情），断言控制台没有 `read only property '_'`、`null (reading 'type')`、`parentNode`、`scheduler flush` 等错误。
- 骨架屏和加载过渡要在切换主题后重新触发一次；颜色、流光、遮罩和空态不能残留上一个主题。
- 报告详情页必须逐主题截图，检查 `INSPECTION REPORT`、状态胶囊、封面标题、摘要卡、报告正文和富文本在当前主题下都有足够对比度。

## 10. 报告/列表详情必须保留用户身份

从列表进入详情时必须保留调用者身份模型。即使打开的是同一个视觉报告详情页，员工、客户和公开/分享路线也可能需要不同接口。

要求：
- 如果员工通过已鉴权 FormEngine 或后台账号接口能看到报告列表，报告详情必须使用同样的员工鉴权路径或传有效员工 token。
- 客户打开报告时，使用客户 token 或感知绑定关系的接口引擎。
- 外部用户打开分享报告时，使用分享 token 路由，不要求员工/客户登录。
- 不要给员工用户发送空 `CustomerToken`，然后把后端响应解释为“未登录”。
- 打开详情页前保留当前会话；除非员工鉴权端点真实返回登录过期码，否则不要清除员工 token。

禁止：
- 员工列表点击时，直接复用客户匿名报告详情引擎且没有员工凭证路径。
- 登录用户点击已经可见的报告/列表项后被重定向到登录页。

验收：
- 分别测试员工列表 -> 报告详情、客户列表 -> 报告详情、分享 token 详情。
- 确认点击可见卡片后不会发生意外登录跳转。

## 11. 角色与权限必须基于 sys_user.RoleIds 建模

移动端和后台不能只区分“已登录/未登录”。企业应用通常至少有内部员工、售后师傅、客服、客户账号等角色，必须在建模阶段明确角色、菜单权限和数据权限。

要求：
- 内部账号统一使用 `sys_user` 登录，身份可从 `RoleIds`、`_Roles`、`Roles`、`RoleName` 和 `Level` 推导展示能力；只有服务端已确认的 `Level>=9999` 才视为平台超级管理员。角色名和前端 `_IsAdmin` 不能代替接口授权。
- 角色能力要在统一 session/capability store 中计算，例如 `isTechnician`、`isServiceAgent`、`isCustomerAccount`、`canAcceptOrders`、`canManageCustomers`、`canViewReports`。一个人有多个角色时取能力并集。
- 客户账号角色要精确判断，不能把“客户管理”这类内部后台角色误判为客户账号。建议只把“客户”“客户账号”“客户用户”或明确包含“客户账号”的角色当作客户侧账号。
- 后台通过 `sys_role` + `sys_rolelimit` 建角色和菜单权限。售后师傅通常能看维保计划、工单、维保记录、报修和检测报告；客服通常能看客户中心、工单、报修、报告和资讯；客户账号默认不直接授予后台菜单，客户侧数据通过绑定关系和接口数据过滤提供。
- 移动端按钮和数据必须按 capability 控制：接单按钮只给售后师傅/超级管理员，客户资料管理只给客服/超级管理员，客户账号只能看自己绑定客户的数据。
- 菜单权限只是粗粒度入口控制，接口引擎和 FormEngine 查询仍必须按角色、`V8.CurrentUser`、客户绑定关系做行级过滤。
- 微信小程序手机号授权登录后，如果当前 OpenId/小程序用户还没有绑定客户或内部 `sys_user` 身份，移动端不能只显示“联系管理员”。必须提供“申请绑定身份”入口，允许用户申请绑定客户、售后师傅或客服等角色，并把申请写入独立审核表；后台审核通过后才写客户绑定表或 `Sys_User.MiniProgramOpenId` 等正式身份字段。
- 身份绑定申请必须显示审核状态：待审核、已通过、已驳回。待审核期间不要提前开放客户数据、接单、工单处理等能力；已驳回要展示原因或允许重新提交。
- 后台审核必须由有权限的内部账号执行。审核客户申请时应由后台选择真实客户 Id 后再写绑定关系，不能只相信用户填写的客户名称；审核售后师傅/客服申请时应由后台选择真实 `sys_user.Id` 后再绑定 OpenId。
- 使用 MCP 建角色前先回读 `sys_role` 和 `sys_menu`。如果 `microi_save_role` 或通用 `add_form_data('sys_role')` 因 `UpdateTime cannot be null` 等系统字段问题失败，要使用平台修复后的专用角色工具；临时迁移可用一次性接口引擎和参数化 `V8.Db` 补建角色，但必须回读验证并删除或禁用临时入口。

禁止：
- 只做前端显示区分，后台角色和菜单权限不落库。
- 把客户账号当作普通后台用户直接开放客户、报告、工单全量菜单。
- 客户授权手机号登录后没有绑定关系时，只提示“联系管理员”，却没有申请、审核、状态回显闭环。
- 审核绑定时只按用户填写的客户名或姓名自动匹配，未由后台确认真实 `CustomerId` / `Sys_User.Id`。
- 多角色用户只按第一个角色判断，导致能力丢失。
- 登录状态、角色文本和页面权限由多个页面各自读取缓存，造成互相矛盾。

验收：
- 用超级管理员、售后师傅、客服、客户账号分别登录截图，确认姓名、角色、按钮和数据范围一致。
- 用多角色账号登录，确认能力取并集。
- 回读 `sys_role`、`sys_rolelimit`、关键接口返回数据，确认角色存在、菜单授权正确、客户数据没有越权。
- 使用真实账号登录后刷新 H5 或重启小程序，不能出现缓存用户名但状态未登录的半登录状态。
- 用未绑定的小程序手机号账号登录，截图确认“我的”页出现申请绑定身份入口；提交申请后后台能看到待审核记录；通过审核前数据权限不提前开放，通过审核后对应客户数据或内部工作台能力才出现。

## 最终交付清单

移动端项目标记完成前必须确认：
- 图标：底部导航、首页快捷入口、个人中心快捷项、主按钮。
- 登录：H5/App 只有一条账号/手机号 + 密码路径；微信手机号授权是小程序默认路径；没有并列重复登录系统，没有租户/版本/调试块；SDK 接口已核验。
- 登录状态：成功登录必须同时有 token 和用户 `Id`；刷新、切换导航和进入“我的”页后，不得出现缓存用户名但仍提示未登录。
- 按钮：图标 + 文字、按下态、加载态。
- 菜单：后台规划为至少两级树。
- 请求头：`osclient` 是唯一标准请求头值，没有因大小写重复。
- 未登录态：授权/登录提示卡片和主按钮在可用内容区上下左右居中，不贴顶部。
- 首屏：首屏文字和悬浮快捷面板已截图检查，无裁切或重叠。
- 动效：入场、点击、骨架屏动画存在且克制。
- 主题：命名主题可持久化，未登录可切换，并影响所有页面和底部导航。
- 主题质检：每个 `pages.json` 路由在每个命名主题下都截图检查；切换主题再导航后，没有低对比文字，也没有 Vue scheduler/router 错误。
- 图标对比：首页快捷入口、底部导航、我的快捷入口在每个主题下都清晰可见；不得出现绿色圆底配绿色图标、灰色圆底配低对比图标等情况。
- 鉴权质检：列表到详情路由保留员工/客户/分享身份，不会把已可见项目重定向回登录页。
- 分享：`pages.json` 中每个页面都已注册好友转发和朋友圈生命周期；登录/受保护页面也可分享，接收者进入后再鉴权。
- 登录诊断：体验版手机号登录失败时显示阶段、原因和追踪号；后台系统日志可按追踪号查到脱敏错误上下文。
- 角色权限：`sys_user.RoleIds`、`sys_role`、`sys_rolelimit`、前端 capability 和接口行级过滤已联动验证。
- 验证：脚本存在时运行 `build:h5`、`build:mp-weixin` 和 `build:app`；执行 H5 路由冒烟测试和控制台检查。
