@microi.net/cli 6.2.4 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/assets/build-meta.json +6 -6
  7. package/cordis.patch.yml +1 -1
  8. package/package.json +1 -1
  9. package/scripts/mcp-server.js +98 -98
  10. package/scripts/microi-cli.js +44 -44
  11. package/scripts/microi-codex-broker.js +2 -0
  12. package/scripts/microi-skills.meta.json +275 -257
  13. package/skills/.microi-skills-version.json +2 -2
  14. package/skills/.progressive-disclosure-manifest.json +89 -86
  15. package/skills/app-store/SKILL.md +555 -553
  16. package/skills/app-store/references/platform-row-format.md +7 -0
  17. package/skills/dos-orm/SKILL.md +2 -0
  18. package/skills/dos-orm/references/oceanbase-mysql.md +8 -0
  19. package/skills/message-notification/SKILL.md +2 -0
  20. package/skills/microi-ai-application/SKILL.md +1 -0
  21. package/skills/microi-client-frontend/SKILL.md +12 -48
  22. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +18 -1
  23. package/skills/microi-client-frontend/references/progressive-04-/345/211/215/347/253/257/346/216/245/345/217/243/344/270/216/347/274/223/345/255/230/346/201/242/345/244/215.md +45 -0
  24. package/skills/microi-codex/SKILL.md +2 -0
  25. package/skills/microi-deployment/SKILL.md +7 -0
  26. package/skills/microi-docs-coverage/references/capability-map.md +1 -1
  27. package/skills/microi-microservice/SKILL.md +14 -0
  28. package/skills/microi-mobile-app-quality/SKILL.md +2 -0
  29. package/skills/microi-mobile-app-quality/references/mobile-first-delivery.md +2 -1
  30. package/skills/microi-system-delivery/SKILL.md +8 -1
  31. package/skills/microi-system-delivery/references/delivery-pitfalls.md +19 -0
  32. package/skills/microi-ui/SKILL.md +7 -4
  33. package/skills/microi-uniapp-frontend/SKILL.md +10 -1
  34. package/skills/microi.v8.js +17 -3
  35. package/skills/module-engine/SKILL.md +5 -0
  36. package/skills/playwright-e2e/SKILL.md +11 -3
  37. package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +4 -4
  38. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +2 -2
  39. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +2 -2
  40. package/skills/ui-design/SKILL.md +19 -5
  41. package/skills/ui-design/assets/pattern-showcase/app.js +57 -0
  42. package/skills/ui-design/assets/pattern-showcase/index.html +35 -4
  43. package/skills/ui-design/assets/pattern-showcase/scenario-state.js +34 -0
  44. package/skills/ui-design/assets/pattern-showcase/styles.css +115 -0
  45. package/skills/ui-design/references/data-workspace-density.md +8 -0
  46. package/skills/ui-design/references/design-pattern-library.md +50 -1
  47. package/skills/ui-design/references/mobile-product-language.md +93 -0
  48. package/skills/uniapp-mall-assets/SKILL.md +1 -0
  49. package/skills/v8-crud-api/SKILL.md +2 -1
  50. package/skills/v8-file-upload/SKILL.md +2 -2
  51. package/skills/v8-frontend-events/SKILL.md +2 -1
  52. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +21 -1
  53. package/skills/v8-table-event/SKILL.md +2 -1
  54. package/skills/vision-engine/SKILL.md +284 -280
  55. package/skills/vision-engine/references/architecture-and-acceptance.md +14 -0
  56. package/skills/vision-engine/references/realtime-cpu-profiles.md +73 -0
  57. package/skills/vision-engine/references/video-timeline.md +176 -0
  58. package/skills/workspace-conventions/SKILL.md +3 -0
@@ -569,7 +569,7 @@ function installDatePrototypeCompat() {
569
569
  }
570
570
  }
571
571
 
572
- // BEGIN GENERATED PUBLIC RESOURCE ROUTER; source SHA256 94e9f239a70d003f6513a205d02e5d3dcaf3e80268bb3735523047949518ce9a
572
+ // BEGIN GENERATED PUBLIC RESOURCE ROUTER; source SHA256 724a7bfc54e88d348232943681bd70505d5c3af2b2be7b84992be8c574a1f5fc
573
573
  const createSdkPublicResourceClient = (() => {
574
574
  // 公有资源路由的纯校验器与实际消费者桥接;私有分支继续走既有授权接口。
575
575
  const HASH = /^[a-f0-9]{64}$/;
@@ -583,9 +583,21 @@ function key(value) {
583
583
  return value;
584
584
  }
585
585
  const encodeKey = value => value.split('/').map(encodeURIComponent).join('/');
586
+ function primaryHttpUrl(value) {
587
+ if (typeof URL === 'function') return new URL(value);
588
+ // 小程序可能没有 WHATWG URL;这里只校验原始主地址,不仿造全局 URL 或据此签发备份路由。
589
+ // 无法可靠识别的主机(含凭据、编码主机、IPv6/国际域名)保持失败关闭。
590
+ if (typeof value !== 'string' || /[\\@\u0000-\u0020\u007f]/.test(value)) throw Error('InvalidPublicBinding');
591
+ const match = /^(https?):\/\/([a-z0-9.-]+)(?::([0-9]{1,5}))?(\/[^?#]*)?(\?[^#]*)?(#.*)?$/i.exec(value);
592
+ if (!match || match[2].length > 253 || match[2].split('.').some(label =>
593
+ !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label))
594
+ || (match[3] && Number(match[3]) > 65535)) throw Error('InvalidPublicBinding');
595
+ return { protocol: match[1].toLowerCase() + ':', pathname: match[4] || '/',
596
+ search: match[5] || '', hash: match[6] || '', username: '', password: '' };
597
+ }
586
598
  function plainBase(value, originOnly = false, legacyPrimary = false) {
587
599
  if (typeof value !== 'string' || /[\\%?#@\u0000-\u0020\u007f]/.test(value)) throw Error('InvalidPublicBinding');
588
- const parsed = new URL(value);
600
+ const parsed = legacyPrimary ? primaryHttpUrl(value) : new URL(value);
589
601
  if ((parsed.protocol !== 'https:' && !(legacyPrimary && parsed.protocol === 'http:')) || parsed.username || parsed.password || parsed.search || parsed.hash
590
602
  || (originOnly && parsed.pathname !== '/')) throw Error('InvalidPublicBinding');
591
603
  return value.replace(/\/+$/, '');
@@ -607,7 +619,7 @@ function legacyPublicFallback(input, binding) {
607
619
  const path = input.path;
608
620
  try {
609
621
  if (/^https?:/i.test(path)) {
610
- const url = new URL(path);
622
+ const url = primaryHttpUrl(path);
611
623
  return !/[\\\u0000-\u0020\u007f]/.test(path) && !url.username && !url.password ? path : '';
612
624
  }
613
625
  if (path.startsWith('//') || path.startsWith('.')) return '';
@@ -841,6 +853,8 @@ function createPublicResourceClient({ getContext, post, now = Date.now, fetchHea
841
853
  async function ensureBinding() {
842
854
  const current = context(), id = signature(current);
843
855
  if (active !== id) { active = id; binding = null; bootstrap = null; expires = 0; generation++; router.clear(); aliases.clear(); }
856
+ // 缺少浏览器原子能力时只使用经过校验的原 FileServer;不启动会悬挂或返回空地址的备份解析。
857
+ if (typeof URL !== 'function' || typeof AbortController !== 'function' || typeof queueMicrotask !== 'function') return false;
844
858
  if (binding && expires > now()) return true;
845
859
  if (!binding && expires > now()) return false;
846
860
  if (bootstrap) return bootstrap;
@@ -19,6 +19,9 @@ description: Microi 模块引擎与 sys_menu 配置指南。用于创建或修
19
19
  代码字段是否显示按钮由各字段 `Config.CodeEditor.DisplayMode` 决定,禁止按 `sys_menu` 表名强制覆盖;
20
20
  未配置时默认 `Inline`,需要紧凑按钮的字段显式保存 `Dialog`。
21
21
 
22
+ 模块详情要由微服务接管时,配置真实入口模块的 `sys_menu.DetailPageV8`;隐藏子表模块也使用自己的该字段,不能只修改同表另一个可见菜单。`InFormV8` 在原生表单已打开后初始化字段,不能承担无闪窗的详情替换。迁移只移动 View 接管段,保留 Add/Edit 初始化、原按钮、事件、`ParentV8` 和数据权限;具体取消/异步边界见 `../v8-frontend-events/references/progressive-01-列表事件.md`。
23
+ 列表与 full 入口均为“配置即替换”,`undefined/Code=1` 不继续原生;显式原生用既有 `V8.OpenForm(V8.Form, 'View')` 防递归,无 Hook 时保持原生。微服务接管后显式 `return false` 兼容旧 PC,不新增继续原生 flag。
24
+
22
25
  列表【更多功能】使用与【更多搜索】一致的次级按钮样式。模块物理字段
23
26
  `sys_menu.HideMoreFunctions` 对应【隐藏更多功能】,只有明确 `1/true` 隐藏当前模块入口,
24
27
  缺失/空/0 保持显示;嵌入页 `PropsHideMoreFunctions=true` 同样可隐藏当前实例。
@@ -240,6 +243,8 @@ Iframe 不把长期 Token、密码或连接串放 URL。第三方单点登录使
240
243
 
241
244
  ## 跨端 ViewSchema
242
245
 
246
+ 移动端业务详情的人员电话组合使用 `ViewSchema.MobileDetail.PhoneBindings[]`,在模块展示设计器【移动端详情】中配置 `PersonField/PhoneField/Enabled/ShowNumber/EnableDial/HidePhoneRow`。这是标准详情的显式局部展示扩展,不依赖或修改 `EnableViewSchema`,不改变已有 Detail/Edit/List/Card。通过 `microi_update_module` 合并原 JSON、递增 `ViewConfigVersion` 并回读;不得覆盖其它视图或写入废弃 DiyConfig。只有同一可见分组的两个字段都存在、电话字段在当前查询中确实返回时才合并,缺失/隐藏/重复/交叉绑定回退原行;空值和脱敏值不得拨号,也不得另取电话原文。此功能需同时更新 PC 设计器和 UniApp `pages/business/detail`,保存菜单不等于客户端已更新。
247
+
243
248
  顶层 PC 数据列表默认使用紧凑的新模块标题样式;即使未启用自定义表单视图,也不能退回无标题的旧外观。无指标头部固定 `44px`、含指标头部固定 `62px`,连同间距总纵向占用约 `50px / 68px`。子表、关联表、嵌入表不重复显示,移动端由固定导航栏承载标题。`Scene=List/Card` 的个性化标题、指标、复合列和卡片配置存在时必须直接生效;`EnableViewSchema` 只控制 Detail/Edit 自定义表单视图。
244
249
 
245
250
  PC 列表的固定结构顺序是“模块 Hero(标题/副标题/动态指标)→ PageTabs → 查询与表格”,Hero 必须渲染在页面多 Tab 上方。头部只使用一次性入场和一次性轻量光效,禁止持续循环动画;`prefers-reduced-motion: reduce` 必须关闭动画和过渡。
@@ -7,7 +7,7 @@ description: 按 Microi 系统真实业务逻辑进行 Playwright 全自动化
7
7
 
8
8
  # Microi 吾码 Playwright E2E 自动化测试
9
9
 
10
- 用户浏览器的扩展、连接或控制能力不可用时,必须执行 [浏览器不可用时继续交付](../workspace-conventions/SKILL.md#浏览器不可用时继续交付强制入口):尝试宿主内置浏览器,展示需要用户登录的页面,并继续独立的 MCP、源码与验证工作。明确的安全拒绝不属于可换工具绕过的能力故障;等待登录、页面打开或接口成功都不能冒充业务验收完成。
10
+ 浏览器选择统一执行 [浏览器不可用时继续交付](../workspace-conventions/SKILL.md#浏览器不可用时继续交付强制入口):用户未指定浏览器时,当前 Codex/WorkBuddy/DSH 宿主内的 PC 后台、UniApp/H5、本地预览与截图交互验收默认使用宿主内置浏览器,复用已登录的目标标签;先核对 URL、账号及租户。只有没有内置浏览器、必要能力不受支持或有界诊断确认不可用时,才降级到本地 Edge/Chrome,并记录原因。不能因为要测试新开发后台或使用 Playwright,就默认启动本地浏览器。独立 CI/Playwright Test/VS Code 测试进程的浏览器矩阵按该统一规则区分处理,不把测试模板中的外部浏览器配置当作交互验收默认值。明确的安全拒绝不属于可换工具绕过的能力故障;等待登录、页面打开或接口成功都不能冒充业务验收完成。
11
11
 
12
12
  桌面人工补充或新建浏览器验收默认只选一个常规 CSS 视口:优先复现用户实际浏览器尺寸,未知时用 1920×1080。截图文件像素还受设备缩放影响,不能据此强制 4K;仅用户明确要求或问题涉及超宽屏时追加 4K,移动端或响应式改动按需补充相关窄屏。沿用已有有效验收,不为截图分辨率重复整套用例;正式脚本既有 Full 不在运行中删减。核心发布已冻结后,新增规范文案独立交付,不因此重启合格候选的构建、测试或上传。
13
13
 
@@ -90,7 +90,7 @@ AI 在工作区内生成的**一次性临时脚本、诊断文件、截图、测
90
90
  ```
91
91
 
92
92
  <!-- /microi-progressive:chunk -->
93
- <!-- microi-progressive:chunk id=playwright-e2e-004 sha256=f71fe44f7d5753b93804dac958ea8205cddcaee25188a8417b33770e305c46ed -->
93
+ <!-- microi-progressive:chunk id=playwright-e2e-004 sha256=b7a26c2ead0ff75d3aa6f19b96b86a28dcd07446c1a6f14049c220b3009677c6 -->
94
94
  ## 必备环境变量
95
95
 
96
96
  ```bash
@@ -99,9 +99,10 @@ PW_API_BASE=https://api.example.com
99
99
  PW_OS_CLIENT=demo
100
100
  PW_WEB_SERVER_COMMAND="npm run dev:h5 -- --host 0.0.0.0 --port 5180"
101
101
  PW_WEB_SERVER_URL=http://127.0.0.1:5180
102
- PW_BROWSER_CHANNEL=msedge
103
102
  ```
104
103
 
104
+ 以上为独立 Playwright Test 进程的连接配置。`PW_BROWSER_CHANNEL`、`PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE` 是外部浏览器模式的可选项;只有明确进入该模式后才设置,不作为宿主内置浏览器交互验收的必备变量或默认选择。
105
+
105
106
  常用业务变量:
106
107
 
107
108
  ```bash
@@ -208,3 +209,10 @@ uni-app H5 或移动端前端按项目自身命令启动,例如 `npm run dev:h
208
209
  - [references/progressive-03-microi-helper-模板.md](references/progressive-03-microi-helper-模板.md):Microi helper 模板;典型用例;Microi 专属测试策略;最少冒烟集;完整业务验收门槛;与 MCP 的配合;与 VS Code 插件的配合
209
210
  - [references/progressive-04-ci-建议.md](references/progressive-04-ci-建议.md):CI 建议;常见问题;前端微服务 E2E 必测点;动态模块按钮验收
210
211
  <!-- microi-progressive:end -->
212
+
213
+
214
+ ## 大资源响应体取证
215
+
216
+ - 页面先加载大型私有资源时,Playwright的response.body可能因浏览器inspector缓存淘汰而失败;这既不证明资源SHA不符,也不能算哈希验证通过。保留失败并区分实际HTTP/解码/图形错误与取证通道错误。
217
+ - 确需证明浏览器加载的实际字节时,可在加载前对任务自有页面建立独立CDP Network通道,按最大目标文件和可用内存设置有界per-resource/total buffer,在loadingFinished后读取响应体并校验原大小/SHA;只选目标应用路径,不读取无关私有资源,记录取证方式。保留所有原有哈希、权限、质量和完成断言,不能改成只查状态码。
218
+ - 匿名全文件回读、浏览器实际加载字节和静态像素门禁分别留证。截图和响应体复制不得混入连续FPS采样窗口;关闭本任务CDP会话与浏览器,不改变用户浏览器、驱动、画质或安全标志。
@@ -72,7 +72,7 @@ expect(lowContrast, '低对比度文字: ' + JSON.stringify(lowContrast.slice(0,
72
72
  5. 价格区间、状态、筛选这类**导航/筛选元素出现在列表中部作为标题**——必须移到顶部 sticky 工具栏,并与正文形成色彩区隔。
73
73
 
74
74
  <!-- /microi-progressive:chunk -->
75
- <!-- microi-progressive:chunk id=playwright-e2e-014 sha256=e5858cce6f743573d699ed7d2d07f958d15be96c28028a91163e841f6d3c016a -->
75
+ <!-- microi-progressive:chunk id=playwright-e2e-014 sha256=fa0a033169b6c3e53a2e8124c762d1d98b2e7c91f1cde56b4fdcecab58c43f65 -->
76
76
  ## 安装
77
77
 
78
78
  ```bash
@@ -80,9 +80,9 @@ npm i -D @playwright/test
80
80
  npx playwright install chromium
81
81
  ```
82
82
 
83
- ### 浏览器选择强制顺序
83
+ ### 独立 Playwright 进程的外部浏览器探测顺序
84
84
 
85
- 做本地页面级 Playwright 验收时,不能因为 Playwright 官方 Chromium 缓存缺失或下载超时就结束任务。必须按下面顺序自动兜底:
85
+ 当前 AI 宿主的页面操作先执行 `workspace-conventions/SKILL.md` 的内置浏览器优先规则;不得跳过宿主能力发现而直接使用以下本机探测。仅在已确认需要外部浏览器,或运行独立 CI/Playwright Test/VS Code 测试进程时,才按下列顺序处理 Chromium 缺失与下载超时,并记录适用模式及依据:
86
86
 
87
87
  1. 优先读取 `PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE`。
88
88
  2. Windows 优先探测本机 Edge / Chrome:`C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe`、`C:/Program Files/Microsoft/Edge/Application/msedge.exe`、`C:/Users/Administrator/AppData/Local/Google/Chrome/Bin/chrome.exe`、`C:/Program Files/Google/Chrome/Application/chrome.exe`、`C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`。
@@ -92,7 +92,7 @@ npx playwright install chromium
92
92
 
93
93
  手写一次性脚本必须把浏览器探测逻辑写在 `.tmp/*.mjs` 中,截图和报告也必须输出到 `.tmp/screenshots/` 或 `.tmp/reports/`,不要写到 `Microi.Client/`、`Microi.Server/` 等子项目目录。
94
94
 
95
- 国内或内网环境如果下载浏览器困难,可以使用本机 Edge:
95
+ 在上述外部浏览器模式中,国内或内网下载浏览器困难时可以使用本机 Edge:
96
96
 
97
97
  ```bash
98
98
  $env:PW_BROWSER_CHANNEL='msedge'
@@ -193,7 +193,7 @@ tests/e2e/
193
193
  - `microi_set_engine_anonymous`:登录、注册、公开首页接口需要匿名访问时使用;设置后仍要验证 HTTP `/apiengine/{key}` + `osclient` Header 返回标准 DosResult。
194
194
 
195
195
  <!-- /microi-progressive:chunk -->
196
- <!-- microi-progressive:chunk id=playwright-e2e-022 sha256=4c8a62a896b5f1f97ea41bcd3afdb0dc3c3609ead6c92283dcfa703c09311b42 -->
196
+ <!-- microi-progressive:chunk id=playwright-e2e-022 sha256=20ffa70428d90ae28b3961f6a1384066429c647d4fcb06915c08196096d30902 -->
197
197
  ## 与 VS Code 插件的配合
198
198
 
199
199
  ### Microi VS Code 插件
@@ -213,7 +213,7 @@ Microi VS Code 插件应提供三类能力:
213
213
  1. 项目内保留 `playwright.config.js` 作为唯一配置源,不为插件单独复制一份配置。
214
214
  2. `webServer.reuseExistingServer: true`,这样插件运行时如果 H5 静态服务已启动就复用,否则按配置启动。
215
215
  3. 本地 H5 先执行 `npm run build:h5:local`;插件再运行 `tests/e2e/*.spec.js` 时会服务 `dist/build/h5`。
216
- 4. Windows 本机推荐 `PW_BROWSER_CHANNEL=msedge` 或在配置中默认 `channel: 'msedge'`,避免每台机器重复下载 Chromium。
216
+ 4. 仅独立 VS Code/Playwright Test 进程且已选择外部浏览器模式时,可显式设置 `PW_BROWSER_CHANNEL=msedge` 复用本机 Edge;不在通用配置中固定默认 `channel: 'msedge'`。AI 在 Codex/WorkBuddy/DSH 中进行页面交互验收时仍先使用宿主内置浏览器。
217
217
  5. 需要自定义 `PW_API_BASE/PW_API_ENV/PW_PORT/PW_OS_CLIENT` 时,优先写入 `playwright.config.js` 的默认值或 VS Code 插件环境变量设置;CI 和一次性运行再用终端环境变量覆盖。
218
218
  6. 插件适合单个用例调试、断点、Trace 查看;最终交付仍要跑 CLI 全量命令并查看 `tests/e2e/report`。
219
219
  7. 插件截图/trace 与 CLI 使用同一目录;视觉回归仍必须 `view_image` 人眼复核关键 fullPage 截图。
@@ -28,7 +28,7 @@ jobs:
28
28
  ```
29
29
 
30
30
  <!-- /microi-progressive:chunk -->
31
- <!-- microi-progressive:chunk id=playwright-e2e-024 sha256=e14464e157beb905d85fdd1f284b677538f60d6d9d3a860515626e52738dac5b -->
31
+ <!-- microi-progressive:chunk id=playwright-e2e-024 sha256=49c23c0ea92474553c4406949b463e3ae8d1daaa62ba4791acf7524e61e4a053 -->
32
32
  ## 常见问题
33
33
 
34
34
  | 现象 | 原因 | 处理 |
@@ -41,7 +41,7 @@ jobs:
41
41
  | 保存接口代码后 MCP 自己报 `string does not contain DosIsNullOrWhiteSpace` | 后端 C# 在动态对象路径上调用扩展方法 | 改用 `string.IsNullOrWhiteSpace`,不要在 MCP 路径新增 `DosIsNullOrWhiteSpace` 调用 |
42
42
  | MCP `run_engine` 里 `V8.Db` 为 null | MCP 执行上下文没有注入直连 Db | 维护/测试引擎优先使用 `V8.FormEngine.GetTableData/UptFormData` |
43
43
  | H5 静态资源 404 | `vite.config.js` 未配置相对路径 | uni-app H5 设置 `base: './'` |
44
- | 本地浏览器下载失败 | Playwright 下载 Chromium 受阻 | 先用 `PW_BROWSER_CHANNEL=msedge`,没有 Edge 时从吾码 CDN 下载 Chromium 并设置 `PW_CHROMIUM_EXECUTABLE` |
44
+ | 独立测试进程的本地浏览器下载失败 | Playwright 下载 Chromium 受阻 | 仅在已选择外部浏览器的 CI/Playwright Test/VS Code 进程中,使用 `PW_BROWSER_CHANNEL=msedge`,或从吾码 CDN 下载 Chromium;宿主交互验收先检查内置浏览器,不能直接套用本机兜底 |
45
45
  | 用例偶发失败 | HMR 或网络请求未稳定 | CI 使用静态构建,断言明确等待关键元素 |
46
46
  | 页面有横向滚动 | 组件撑出视口 | 对根容器和列表容器加 `max-width:100vw; overflow-x:hidden` |
47
47
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ui-design
3
- description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus、uni-app H5、仪表盘、表单、卡片、渐变、响应式布局、骨架屏、移动安全区和视觉打磨。
3
+ description: Microi UI 设计系统指南。用于 PC 微服务与 uni-app 产品的视觉方向、独特构图、主题、组件、动效及真实状态验收,也覆盖标准后台表单和数据页。
4
4
  ---
5
5
 
6
6
  > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
@@ -13,12 +13,12 @@ description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus
13
13
  > **核心理念**:结构服务任务,主题保持一致,密度按场景选择;动效可选,多端尊重实际可用空间。
14
14
  > **变量前缀**:所有 CSS 变量统一使用 `--mci-` 前缀(Microi Interface)
15
15
 
16
- **默认紧凑适度**:不鼓励大标题、大圆角、大间距,不把放大、留白或圆润等同于品质。标题按层级而非面积强调,常规卡片建议 6–10px、控件 4–8px 圆角、页面间距 12–16px;只有用户明确选择表现性视觉,或可读性/触控用途确有需要时才增大并说明理由。现有宿主组件由其主题契约控制,改 Skill 不等于修改或部署平台组件。
16
+ **先区分标准后台与定制产品**:标准表单、数据表和高频操作区默认紧凑适度,常规卡片可从 6–10px、控件 4–8px 圆角、页面间距 12–16px 起步;这些是效率场景基线,不是所有产品的视觉上限。移动消费产品、会员门户和定制微服务按产品任务选择排版、尺度、媒体和构图,用户要求鲜明、个性或提供表现性参考时应主动实现有辨识度的方向。放大、圆润、留白或渐变本身不构成品质;也不能因“默认紧凑”把所有页面做成同一标题加等大白卡。现有宿主组件仍由其主题契约控制,改 Skill 不等于修改或部署平台组件。
17
17
 
18
18
  ---
19
19
 
20
20
  <!-- microi-progressive:begin -->
21
- <!-- microi-progressive:chunk id=ui-design-000 sha256=b8b3dedc3fc95b9d32189e094b8b90fbf67b9f7c2e4a0307fa3e58ccce6d5de1 -->
21
+ <!-- microi-progressive:chunk id=ui-design-000 sha256=59e6f7670a706097cb998754b392a6b7e94fd940b963c99a7dbd52e1294365d7 -->
22
22
  ## 整体风格定义
23
23
 
24
24
  - **禁止弧形包边**:Tab、卡片、导航项、提示条和分组标题不得把强调色 `border-left` / `border-right` 与容器圆角组合成沿上下圆角弯曲的侧框,也不得用渐变、阴影或伪元素模拟同样的包边。默认不加侧框;需要强调时使用与容器边框独立的直竖线,垂直居中、与上下边缘留出间距且不随容器圆角弯折。直线端点可以有轻微圆角,容器本身仍可使用中性圆角边框。横向 Tabs 继续使用独立选中表面,不添加装饰线。亮色、暗色与移动端截图都必须核对这一规则。
@@ -31,11 +31,12 @@ description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus
31
31
  - **视觉验收**:至少选择浅色、暗色及一个非默认租户主题色,检查文本对比度、焦点态、状态色、悬停与禁用状态。
32
32
  - **按模式取舍**:数据工作台以中性表面、细边界、紧凑对齐和清楚数字为默认;品牌/内容展示可以选择表现性字体、材质或动效。不要把“高级、科技感”等形容词当作必须增加渐变、发光、重阴影或留白的指令。
33
33
  - **主题继承**:嵌入式页面继承当前租户和用户的主题、明暗及密度;独立产品再声明自己的默认。不得为重设计覆盖用户已选主题。
34
- - **参考吸收**:可参考成熟移动端 UI 灵感库的组件完整度、色彩节奏、入场动效和模板化能力,但 Microi 视觉必须通过 `--mci-*` token 与 MCI-UI 组件形成自己的品牌系统,不照搬第三方外观,也不在吾码源码、文档或样式命名中保留外部 UI 品牌痕迹。
34
+ - **参考吸收**:可参考成熟移动端 UI 灵感库的组件完整度、色彩节奏、入场动效和模板化能力,但 Microi 视觉必须通过 `--mci-*` token 与 MCI-UI 组件形成自己的品牌系统。成品文案、公开组件和样式命名不冒用外部品牌;研究引用与必要版权、许可归因必须保留,不得为“去品牌”删除授权声明或掩盖素材来源。
35
35
 
36
36
  ### Microi 设计模式选择(必须)
37
37
 
38
38
  - 当需求只描述“高级、主流、好看”而没有明确视觉方向时,先从 Microi 内置模式中确定一种主结构:品牌叙事、真实产品流程、趋势构图、沉浸互动、动态首屏或数据工作台;再选择至多两种辅助能力。禁止把多套视觉语言堆成拼盘。
39
+ - 定制产品与整站重设计还应按 [项目设计签名与场景配方](references/design-pattern-library.md#项目设计签名与场景配方) 先确定可辨认的构图特征,再选 token;记录首页、内容/列表、资产、表单和工作台如何在同一系统内形成任务差异。调换颜色、把圆角加大或只替换 Hero 不算完成整站重设计。
39
40
  - 管理、监控、分析、报表和高频操作页必须读取 [数据工作台密度规范](references/data-workspace-density.md)。它优先于下文展示模式的通用字号、圆角、阴影与动效示例:使用宿主可用区的 100% 宽度、实际容器断点、紧凑操作和逐路由几何/截图验收,不套固定最大宽度的营销页壳。
40
41
  - 设计结果必须被拆成可执行规则:首屏目标、信息层级、栅格与最大宽度、字体层级、语义颜色、间距、圆角/阴影、组件状态、动效时间线、响应式重排、低性能降级和 `prefers-reduced-motion`。
41
42
  - 先完成默认、加载、空、错误、成功、权限、禁用等真实业务状态,再增加装饰和动效;任何看起来可点击的元素都必须有真实结果。
@@ -46,6 +47,8 @@ description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus
46
47
 
47
48
  ### 可执行设计契约(整站与长期项目强制)
48
49
 
50
+ 移动产品另必读 [移动产品设计语言与可执行视觉门禁](references/mobile-product-language.md),按任务区分消费展示、资料录入和桌面业务工作台;呈现目的不能由 `embedded` 布尔值推导。此规则与用户明确视觉方向优先于后台紧凑 token 的示例值。
51
+
49
52
  - `MCI-DESIGN.md` 必须同时维护机器可读层和人类可读层:前者保存精确 token、引用和组件状态,后者保存目标用户、情绪、具体视觉隐喻、选择理由和禁区。只有 token 会失去取舍依据,只有形容词无法稳定实现。
50
53
  - 设计意图按“用户任务 → 主情绪 → 具体视觉隐喻 → 应当/禁止 → token 与组件”收敛。具体隐喻必须能自然约束色彩、材质、密度、形状和动效;禁止只堆“高级、现代、极简、科技”等宽泛词。
51
54
  - 核心章节固定为:产品与用户、视觉性格、颜色、字体、布局与间距、层级与形状、组件与状态、页面模式、动效与媒体、响应式、安全与降级、应当与禁止。扩展章节放在末尾,未知扩展要保留,核心章节不得重复或用近似拼写另起一套。
@@ -69,7 +72,7 @@ description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus
69
72
  ---
70
73
 
71
74
  <!-- /microi-progressive:chunk -->
72
- <!-- microi-progressive:chunk id=ui-design-002 sha256=72fea6fc01b6bd0de8b0c7a1766f45e9dc92a9a0dbb0e5ad9b38b626e0dfd27a -->
75
+ <!-- microi-progressive:chunk id=ui-design-002 sha256=c8c59054fae6fcd9db153827ca5f79d5348aeae3a0d9cd056d47528115c1e309 -->
73
76
  ## 高端视觉标准
74
77
 
75
78
  - 每个新页面必须有首屏视觉重心:核心数据、主任务、产品/品牌对象或可操作内容应在第一屏明确出现,不能只有说明文字或空白装饰。
@@ -91,6 +94,17 @@ description: Microi UI 设计系统指南。用于设计 PC Vue、Element Plus
91
94
  - 日志、告警、监控和审计表格的主标题与副标题默认各最多两行,使用 CSS line clamp 与原文 Tooltip/title 保留完整可读性;异常行、异常值和风险标签必须使用主题兼容的红色语义,并在悬停/聚焦时同时说明“可能原因、排查顺序、解决方案”,不能只显示红色数字。
92
95
  - 驾驶舱/数据大屏风格必须跟随当前主题令牌并保持紧凑信息密度。持续动效仅允许少量低振幅 `transform`、`opacity`、`background-position`,页面隐藏、微服务 `afterhidden` 或低性能模式时暂停;必须实现 `prefers-reduced-motion` 静态降级,长表格启用虚拟化或 `content-visibility`,禁止每个单元格独立定时器、持续阴影/模糊重绘和高频全量图表重算。
93
96
 
97
+ ## 玻璃材质的分层与真实清晰度(强制)
98
+
99
+ - `Solid/Glass/Frosted/Liquid` 是独立材质维度,不替换明暗模式、主题色或用户密度。沿用吾码语义变量;不导入外部 Skill 命令、自动 DOM 接管或另一套持久偏好。表单 `CodeEditor` 例外:工具栏、原生 Monaco 画布/语法及 AI 内部控件保留独立固定暗色域,不跟随平台外观,也不逐项重写语法 token。
100
+ - 背景应保留壁纸色彩与明暗结构,不通过全局去饱和滤镜或厚重白/黑 veil 把整页压成灰底,再用更多透明层补救。一个独立外壳只采样一次背景;导航、表格/表单外壳、Dialog/Drawer、Popover 与 WebOS 窗口各有明确所有者,内层画布透明、按钮与控件轻填充,不给每个字段或表格单元格再套独立模糊。
101
+ - 背景采样、局部填充、边缘与正文分层。正文和图标保持正常 opacity,不用 `filter` 或 `mix-blend-mode` 混合文字;静态边为细暗边,有限方向反光只帮助识别轮廓,不画泛白整圈或发光外框。圆角、焦点与字号仍服从宿主、小密度及输入设备约定,不照搬展示型玻璃的尺寸。
102
+ - 弹窗、嵌套弹窗、普通下拉与列菜单必须阻止后方业务文字透穿,保留背景色块与纹理;不能只减低填充数值而让两套文字重叠,也不能为单个浮层加厚全局底。`body` Teleport 的浮层继续接收本宿主语义 token,保留焦点、层级、拖动、滚动和权限行为。
103
+ - 浅色玻璃使用明亮珍珠色局部填充,导航与内容明度一致、卡片轻填充并保留层次,不能把整屏等量染色。连续外壳必须覆盖四边 padding,内部页签条与路由画布不再铺独立玻璃;验收检查弹窗真实 Loading 骨架、分页和默认 Banner 指标的 DOM,不能只验证演示样板或变量,遗漏纯白底和二次模糊。
104
+ - 光学增强只作用于明确少量外壳;不全页创建 SVG/WebGL 滤镜、复制实时 DOM 或持续重绘。区分 stacking context 与 backdrop root,不按外部注释盲删布局 transform;检查父级 filter、opacity、mask/clip-path、自身 backdrop-filter 等真实采样边界,并以目标浏览器画面确认。
105
+ - 验收同时覆盖亮/暗、普通/磨砂/液态、真实彩色与明暗壁纸、表格/表单/微服务/WebOS、单层与嵌套浮层。除既有文字/焦点对比度外,核对背景纹理透射、层次、边缘与文字不受滤镜影响;不能用纯色板、构建成功或一次截图替代实页检查。无 backdrop-filter、减少透明度或 forced-colors 时降级可读实色;减少动态只停止动态,不改变静态材质开关。
106
+ - 设计研究可查看 [GlassDesk](https://github.com/everlosttime/glassdesk-open-source)(新增原创部分 PolyForm Noncommercial,历史 Apache/第三方范围另计)、[DeskBox](https://github.com/Tianyu199509/DeskBox)(GPL-3.0)、[Liquid Glass Components](https://github.com/jcm-developer/liquid-glass-components) 与 [sohumsuthar/liquid-glass](https://github.com/sohumsuthar/liquid-glass)(后二者 MIT)。前两者仅作本项目视觉参考;MIT 片段如直接复用须保留版权及许可,并审查交互/浏览器边界。外部规则不覆盖用户要求或吾码业务契约。
107
+
94
108
  ---
95
109
 
96
110
  <!-- /microi-progressive:chunk -->
@@ -10,6 +10,63 @@
10
10
  var status = document.getElementById('formStatus');
11
11
  var submit = document.getElementById('flowSubmit');
12
12
 
13
+ // 两个场景共用纯本地状态;所有数字均由示例集合推导,不代表在线业务结果。
14
+ var scene = window.MciPatternScenario;
15
+ var life = scene.lifeInitial();
16
+ var work = scene.workInitial();
17
+ var lifeRoot = document.getElementById('lifeScene');
18
+ var queueRoot = document.getElementById('workQueue');
19
+ var detailRoot = document.getElementById('workDetail');
20
+ var actionMessage = '';
21
+ function syncSceneVisibility() { root.dataset.mciPaused = document.hidden ? 'true' : 'false'; }
22
+ document.addEventListener('visibilitychange', syncSceneVisibility);
23
+ syncSceneVisibility();
24
+ function escapeText(value) { return String(value).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
25
+ function renderLife() {
26
+ var claimed = life.claimed.length;
27
+ document.getElementById('lifeClaimCount').textContent = claimed;
28
+ document.querySelectorAll('[data-life-view]').forEach(function (button) { button.setAttribute('aria-pressed', String(button.dataset.lifeView === life.view)); });
29
+ if (life.view === 'profile') {
30
+ lifeRoot.innerHTML = '<div class="mci-life-profile"><span class="mci-life-profile__seal" aria-hidden="true">' +
31
+ '<svg viewBox="0 0 48 48"><path d="M12 15h24v18H12zM18 10v10m12-10v10M18 25h12"/></svg></span><p>我的通行证</p><h3>从一次体验,<br>到喜欢的日常。</h3><dl><div><dt>已领取</dt><dd>' + claimed + ' 份</dd></div><div><dt>尚可体验</dt><dd>' + (scene.benefits.length - claimed) + ' 项</dd></div></dl><p class="mci-life-meta">此场景没有真实用户身份。接入项目后,从当前会话读取会员与授权权益。</p><button class="mci-life-action" type="button" data-life-view="benefits">查看已领取权益 <span aria-hidden="true">↗</span></button></div>';
32
+ return;
33
+ }
34
+ var hero = life.view === 'discover' ? '<section class="mci-life-editorial"><p>给忙碌留一点空隙</p><h3>城市与日常,<br>都有新鲜感。</h3><span class="mci-life-editorial__caption">从街角开始,发现一点不同</span><svg class="mci-life-city" viewBox="0 0 180 190" aria-label="原创城市路线插画" role="img"><circle class="mci-life-city__sun" cx="124" cy="35" r="24"/><path class="mci-life-city__ground" d="M0 150 180 107v83H0z"/><path class="mci-life-city__building" d="m28 66 60-14v86l-60 14zM100 84l43-11v45l-43 11z"/><path class="mci-life-city__window" d="m39 81 10-2v14l-10 2zm21-5 10-2v14l-10 2zm-21 29 10-2v14l-10 2zm21-5 10-2v14l-10 2z"/><path class="mci-life-city__route" d="M14 171c28-28 52 14 78-11s30-5 64-35"/><path class="mci-life-city__tree" d="M157 69v75m0-30c-23-3-25-26-25-26 23 1 25 26 25 26m0-18c24-6 22-29 22-29-24 5-22 29-22 29"/></svg></section>' : '';
35
+ var categories = [['all', '全部'], ['travel', '出行'], ['life', '日常'], ['service', '服务']].map(function (item) { return '<button type="button" data-life-category="' + item[0] + '" aria-pressed="' + (life.category === item[0]) + '">' + item[1] + '</button>'; }).join('');
36
+ var rows = scene.lifeVisible(life);
37
+ lifeRoot.innerHTML = hero + '<div class="mci-life-section-title"><h3>' + (life.view === 'benefits' ? '已经在你的通行证里' : '为日常,添一点喜欢') + '</h3><span>' + rows.length + ' 项</span></div><div class="mci-life-categories" role="group" aria-label="权益分类">' + categories + '</div><div class="mci-life-benefits">' + (rows.length ? rows.map(function (b) {
38
+ var received = life.claimed.indexOf(b.id) !== -1;
39
+ return '<article class="mci-life-benefit mci-life-benefit--' + b.tone + '"><div class="mci-life-benefit__mark" aria-hidden="true"><span>' + b.mark + '</span><i></i></div><div class="mci-life-benefit__body"><small>' + b.label + '</small><h4>' + escapeText(b.title) + '</h4><p>' + escapeText(b.detail) + '</p><button type="button" data-life-claim="' + b.id + '"' + (received ? ' disabled' : '') + '>' + (received ? '已领取' : '领取演示权益') + '<span aria-hidden="true">' + (received ? '✓' : '↗') + '</span></button></div></article>';
40
+ }).join('') : '<div class="mci-life-empty"><strong>这里还没有已领取权益</strong><p>回到精选领取一份,新的记录就会在这里出现。</p><button type="button" data-life-view="discover">去看看精选</button></div>') + '</div>';
41
+ }
42
+ function renderWork() {
43
+ var rows = scene.workRows(work), counts = scene.workCounts(work);
44
+ var selected = rows.find(function (t) { return t.id === work.selected; }) || rows[0];
45
+ if (selected) work.selected = selected.id;
46
+ document.getElementById('workSummary').textContent = '全部示例:' + counts.open + ' 项待处理 · ' + counts.done + ' 项已完成';
47
+ document.getElementById('workQueueCount').textContent = rows.length + ' 项匹配';
48
+ document.querySelectorAll('[data-work-day]').forEach(function (button) { button.setAttribute('aria-pressed', String(button.dataset.workDay === work.day)); });
49
+ document.getElementById('workFilter').value = work.filter;
50
+ queueRoot.innerHTML = rows.length ? rows.map(function (t) { return '<button class="mci-briefing-row" type="button" data-work-select="' + t.id + '" aria-pressed="' + (selected.id === t.id) + '"><span class="mci-briefing-row__signal" data-priority="' + t.priority + '" aria-hidden="true"></span><span class="mci-briefing-row__copy"><strong>' + escapeText(t.title) + '</strong><small>' + escapeText(t.team) + ' · ' + escapeText(t.time) + '</small></span><span class="mci-briefing-row__state">' + (t.state === 'done' ? '已完成' : t.priority === 1 ? '优先关注' : '待处理') + '</span><span aria-hidden="true">↗</span></button>'; }).join('') : '<div class="mci-workroom__empty"><strong>当前范围没有待处理事项</strong><p>可以切换日期或查看全部状态。</p><button type="button" data-work-show-all>查看全部任务</button></div>';
51
+ detailRoot.innerHTML = selected ? '<span class="mci-workroom__detail-label">本次行动</span><h4>' + escapeText(selected.title) + '</h4><p>' + escapeText(selected.summary) + '</p><dl><div><dt>负责小组</dt><dd>' + escapeText(selected.team) + '</dd></div><div><dt>约定时间</dt><dd>' + escapeText(selected.time) + '</dd></div></dl><button class="mci-life-action" type="button" data-work-complete="' + selected.id + '"' + (selected.state === 'done' ? ' disabled' : '') + '>' + (selected.state === 'done' ? '已完成复核' : '标记本地示例完成') + '<span aria-hidden="true">✓</span></button>' : '<span class="mci-workroom__detail-label">保持清晰</span><h4>把注意力留给真正要做的事。</h4><p>空队列保留上下文,筛选与统计仍然可用。</p>';
52
+ var feedback = document.createElement('p'); feedback.className = 'mci-workroom__feedback'; feedback.setAttribute('role', 'status'); feedback.textContent = actionMessage; detailRoot.appendChild(feedback);
53
+ }
54
+ document.getElementById('expressive').addEventListener('click', function (event) {
55
+ var button = event.target.closest('button'); if (!button || button.disabled) return;
56
+ if (button.dataset.lifeView) { life = scene.lifeReduce(life, { type: 'view', value: button.dataset.lifeView }); renderLife(); }
57
+ if (button.dataset.lifeCategory) { life = scene.lifeReduce(life, { type: 'category', value: button.dataset.lifeCategory }); renderLife(); lifeRoot.querySelector('[data-life-category="' + life.category + '"]').focus(); }
58
+ if (button.dataset.lifeClaim) { life = scene.lifeReduce(life, { type: 'claim', id: button.dataset.lifeClaim }); renderLife(); var heading = lifeRoot.querySelector('.mci-life-section-title h3'); heading.setAttribute('tabindex', '-1'); heading.focus(); }
59
+ if (button.dataset.workSelect) { work = scene.workReduce(work, { type: 'select', id: button.dataset.workSelect }); renderWork(); queueRoot.querySelector('[data-work-select="' + work.selected + '"]').focus(); }
60
+ if (button.dataset.workDay) { work = scene.workReduce(work, { type: 'day', value: button.dataset.workDay }); renderWork(); }
61
+ if (button.dataset.workComplete) { work = scene.workReduce(work, { type: 'complete', id: button.dataset.workComplete }); actionMessage = '已更新本地示例状态;接入业务时应等待后端成功回执。'; renderWork(); }
62
+ if (button.hasAttribute('data-work-show-all')) { work = scene.workReduce(scene.workReduce(work, { type: 'day', value: 'all' }), { type: 'filter', value: 'all' }); renderWork(); }
63
+ });
64
+ document.getElementById('workFilter').addEventListener('change', function (event) { work = scene.workReduce(work, { type: 'filter', value: event.target.value }); renderWork(); });
65
+ document.getElementById('scenePaletteToggle').addEventListener('click', function () { var violet = root.dataset.mciPalette !== 'violet'; root.dataset.mciPalette = violet ? 'violet' : 'forest'; this.setAttribute('aria-pressed', String(violet)); this.textContent = '场景配色:' + (violet ? '紫藤' : '海松'); });
66
+ document.getElementById('sceneMotionToggle').addEventListener('click', function () { var reduced = root.dataset.mciMotion !== 'reduced'; root.dataset.mciMotion = reduced ? 'reduced' : 'full'; this.setAttribute('aria-pressed', String(reduced)); this.textContent = '减少动态:' + (reduced ? '开启' : '关闭'); });
67
+ document.getElementById('sceneReset').addEventListener('click', function () { life = scene.lifeInitial(); work = scene.workInitial(); actionMessage = ''; renderLife(); renderWork(); });
68
+ renderLife(); renderWork();
69
+
13
70
  themeToggle.addEventListener('click', function () {
14
71
  var nextTheme = root.dataset.mciTheme === 'dark' ? 'light' : 'dark';
15
72
  root.dataset.mciTheme = nextTheme;
@@ -15,6 +15,7 @@
15
15
  <span>Microi Pattern Lab</span>
16
16
  </a>
17
17
  <nav class="mci-lab__nav-links" aria-label="页面导航">
18
+ <a href="#expressive">可运行场景</a>
18
19
  <a href="#patterns">设计模式</a>
19
20
  <a href="#flow">产品流程</a>
20
21
  <a href="#contract">设计契约</a>
@@ -32,20 +33,49 @@
32
33
  <div class="mci-hero__content">
33
34
  <span class="mci-eyebrow">MICROI ORIGINAL DESIGN SYSTEM</span>
34
35
  <h1>让业务界面先有<br><em>清晰的设计意图</em></h1>
35
- <p>本页展示多种明确视觉方向下的可选模式,不是通用页面的默认尺寸。默认紧凑适度,不鼓励大标题、大圆角和大间距;数据操作页按宿主可用区设计。</p>
36
+ <p>先决定主要对象和阅读顺序,再选择色彩与形状。下方两个原创交互场景展示:同一套语义主题怎样支撑不同的任务与构图,标准后台操作区仍保持清楚稳定。</p>
36
37
  <div class="mci-hero__actions">
37
- <a class="mci-button mci-button--primary" href="#patterns">浏览设计模式 <span aria-hidden="true">↘</span></a>
38
+ <a class="mci-button mci-button--primary" href="#expressive">体验原创场景 <span aria-hidden="true">↘</span></a>
38
39
  <a class="mci-button mci-button--quiet" href="#flow">查看真实流程</a>
39
40
  </div>
40
41
  </div>
41
42
  <div class="mci-hero__console" aria-label="设计系统实时状态">
42
43
  <div class="mci-console__head"><span></span><span></span><span></span><b>MCI-DESIGN</b></div>
43
44
  <div class="mci-console__orbit" aria-hidden="true"><i></i><i></i><i></i></div>
44
- <div class="mci-console__metric"><small>组件状态覆盖率</small><strong>08</strong><span>default → error</span></div>
45
+ <div class="mci-console__metric"><small>组件状态示意</small><strong>08</strong><span>default → error</span></div>
45
46
  <div class="mci-console__footer"><span>responsive</span><span>accessible</span><span>reduced-motion</span></div>
46
47
  </div>
47
48
  </section>
48
49
 
50
+ <section class="mci-expression" id="expressive" aria-labelledby="expressiveTitle">
51
+ <div class="mci-expression__heading">
52
+ <div><p class="mci-expression__kicker">原创交互样例 · 本地演示数据</p><h2 id="expressiveTitle">让同一套设计语言,长出不同的产品。</h2></div>
53
+ <div class="mci-expression__controls">
54
+ <button id="scenePaletteToggle" type="button" aria-pressed="false">场景配色:海松</button>
55
+ <button id="sceneMotionToggle" type="button" aria-pressed="false">减少动态:关闭</button>
56
+ <button id="sceneReset" type="button">重置演示</button>
57
+ </div>
58
+ </div>
59
+ <p class="mci-expression__notice">这里的领取、筛选和完成只改变本页状态,不连接租户、不执行交易。原生按钮可键盘操作;可切明暗、场景配色与减少动态。</p>
60
+ <div class="mci-expression__mobile-layout">
61
+ <article class="mci-life-phone" aria-label="编辑式会员服务场景">
62
+ <header class="mci-life-head"><span class="mci-life-mark" aria-hidden="true"><svg viewBox="0 0 32 32"><path d="M7 9h18v14H7zM11 5v8m10-8v8M11 18h10"/></svg></span><span>城市生活通行证<small>把时间留给喜欢的日常</small></span><span class="mci-life-head__dot" aria-hidden="true"></span></header>
63
+ <div id="lifeScene" class="mci-life-body" aria-live="polite"></div>
64
+ <nav class="mci-life-dock" aria-label="会员场景导航">
65
+ <button type="button" data-life-view="discover" aria-pressed="true"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m3 11 9-8 9 8v10h-6v-7H9v7H3z"/></svg><span>精选</span></button>
66
+ <button type="button" data-life-view="benefits" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 5h18v5a2 2 0 0 0 0 4v5H3v-5a2 2 0 0 0 0-4zM15 7v2m0 2v2m0 2v2"/></svg><span>权益</span><span id="lifeClaimCount" class="mci-life-dock__count">0</span></button>
67
+ <button type="button" data-life-view="profile" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a8 8 0 0 1 16 0v2"/></svg><span>我的</span></button>
68
+ </nav>
69
+ </article>
70
+ <aside class="mci-expression__notes"><span>移动产品 · 编辑式会员页</span><h3>一张通行证,<br>串起权益与生活。</h3><p>标题、主体插画、票券形态与不同尺度分区形成阅读顺序。选择分类会改变内容,领取会去重并更新导航计数;切到“权益”可查看已领取记录。</p><dl><div><dt>设计签名</dt><dd>票券切口、轻纸感、路线插画与悬浮导航</dd></div><div><dt>页面任务</dt><dd>先发现内容,再领取并管理权益</dd></div><div><dt>接入业务时</dt><dd>用真实商品、规则、身份和后端命令替换演示数据</dd></div></dl></aside>
71
+ </div>
72
+ <article class="mci-workroom" aria-labelledby="workroomTitle">
73
+ <header class="mci-workroom__head"><div><span>PC 定制微服务 · 运营简报</span><h3 id="workroomTitle">今天的服务,先处理要紧的事。</h3></div><span class="mci-workroom__stamp">本地示例状态</span></header>
74
+ <div class="mci-workroom__toolbar"><div role="group" aria-label="任务日期"><button type="button" data-work-day="today" aria-pressed="true">今天</button><button type="button" data-work-day="all" aria-pressed="false">全部日期</button></div><label>任务范围<select id="workFilter"><option value="open">待处理</option><option value="risk">优先关注</option><option value="all">全部状态</option></select></label><p id="workSummary" role="status"></p></div>
75
+ <div class="mci-workroom__canvas"><section class="mci-workroom__queue" aria-labelledby="workQueueTitle"><div class="mci-workroom__section-head"><h4 id="workQueueTitle">行动队列</h4><span id="workQueueCount"></span></div><div id="workQueue"></div></section><aside class="mci-workroom__detail" id="workDetail" aria-live="polite"></aside></div>
76
+ </article>
77
+ </section>
78
+
49
79
  <section class="mci-section" id="patterns">
50
80
  <div class="mci-section__head">
51
81
  <div><span class="mci-eyebrow">PATTERN LIBRARY</span><h2>六种模式,一套品牌语言</h2></div>
@@ -112,7 +142,7 @@
112
142
  <article class="mci-pattern mci-pattern--data" data-kind="data">
113
143
  <div class="mci-pattern__visual">
114
144
  <span class="mci-pattern__index">06</span>
115
- <div class="mci-data-head"><span>运营总览</span><b>实时</b></div>
145
+ <div class="mci-data-head"><span>运营总览</span><b>本地示例</b></div>
116
146
  <div class="mci-data-metrics"><span><small>服务中</small><b>128</b></span><span><small>完成率</small><b>96%</b></span><span><small>风险项</small><b>07</b></span></div>
117
147
  <div class="mci-data-chart" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
118
148
  </div>
@@ -158,6 +188,7 @@
158
188
 
159
189
  <footer class="mci-lab__footer"><span>Microi.UI Original Pattern Asset</span><span>Local only · No external dependency</span></footer>
160
190
  </main>
191
+ <script src="./scenario-state.js"></script>
161
192
  <script src="./app.js"></script>
162
193
  </body>
163
194
  </html>
@@ -0,0 +1,34 @@
1
+ /* 原创演示状态,与渲染分离;不读取会话、不请求网络、不模拟真实交易受理。 */
2
+ (function (scope) {
3
+ 'use strict';
4
+ var benefits = [
5
+ { id: 'city', kind: 'travel', label: '出行', title: '沿着街角,重新认识城市', detail: '城市漫游通行体验', mark: '路线', tone: 'route' },
6
+ { id: 'coffee', kind: 'life', label: '日常', title: '留一个下午,给咖啡香', detail: '周末到店体验权益', mark: '时光', tone: 'coffee' },
7
+ { id: 'care', kind: 'service', label: '服务', title: '让忙碌的日子,轻一点', detail: '到店洗护服务体验', mark: '轻松', tone: 'care' }
8
+ ];
9
+ var tasks = [
10
+ { id: 'reply', title: '服务异常待回应', team: '服务支持组', time: '今天 14:00', day: 'today', priority: 1, summary: '先核对影响范围,再联系提交人确认恢复结果。', state: 'open' },
11
+ { id: 'visit', title: '交付后的第一次回访', team: '客户成功组', time: '今天 16:30', day: 'today', priority: 2, summary: '确认使用情况,整理反馈并给出可执行的下一步。', state: 'open' },
12
+ { id: 'info', title: '资料补全与交接', team: '运营协作组', time: '明天 10:00', day: 'later', priority: 3, summary: '对照清单补齐缺失资料,保留处理记录与负责人。', state: 'open' },
13
+ { id: 'review', title: '服务记录已复核', team: '运营协作组', time: '今天 09:30', day: 'today', priority: 3, summary: '归档已确认的结果,之后仍可查看处理依据。', state: 'done' }
14
+ ];
15
+ function lifeInitial() { return { view: 'discover', category: 'all', claimed: [] }; }
16
+ function lifeReduce(state, action) {
17
+ if (action.type === 'view' && ['discover', 'benefits', 'profile'].indexOf(action.value) !== -1) return Object.assign({}, state, { view: action.value });
18
+ if (action.type === 'category' && ['all', 'travel', 'life', 'service'].indexOf(action.value) !== -1) return Object.assign({}, state, { category: action.value });
19
+ if (action.type === 'claim' && benefits.some(function (b) { return b.id === action.id; })) return Object.assign({}, state, { claimed: state.claimed.indexOf(action.id) === -1 ? state.claimed.concat(action.id) : state.claimed.slice() });
20
+ return state;
21
+ }
22
+ function lifeVisible(state) { return benefits.filter(function (b) { return (state.category === 'all' || b.kind === state.category) && (state.view !== 'benefits' || state.claimed.indexOf(b.id) !== -1); }); }
23
+ function workInitial() { return { day: 'today', filter: 'open', selected: 'reply', completed: [] }; }
24
+ function workRows(state) { return tasks.map(function (t) { return Object.assign({}, t, { state: state.completed.indexOf(t.id) !== -1 ? 'done' : t.state }); }).filter(function (t) { return (state.day === 'all' || t.day === state.day) && (state.filter === 'all' || t.state === 'open' && (state.filter !== 'risk' || t.priority === 1)); }).sort(function (a, b) { return a.priority - b.priority; }); }
25
+ function workReduce(state, action) {
26
+ if (action.type === 'day' && ['today', 'all'].indexOf(action.value) !== -1) return Object.assign({}, state, { day: action.value });
27
+ if (action.type === 'filter' && ['all', 'open', 'risk'].indexOf(action.value) !== -1) return Object.assign({}, state, { filter: action.value });
28
+ if (action.type === 'select' && tasks.some(function (t) { return t.id === action.id; })) return Object.assign({}, state, { selected: action.id });
29
+ if (action.type === 'complete' && tasks.some(function (t) { return t.id === action.id && t.state !== 'done'; })) return Object.assign({}, state, { completed: state.completed.indexOf(action.id) === -1 ? state.completed.concat(action.id) : state.completed.slice() });
30
+ return state;
31
+ }
32
+ function workCounts(state) { var done = tasks.filter(function (t) { return t.state === 'done' || state.completed.indexOf(t.id) !== -1; }).length; return { open: tasks.length - done, done: done }; }
33
+ scope.MciPatternScenario = { benefits: benefits, tasks: tasks, lifeInitial: lifeInitial, lifeReduce: lifeReduce, lifeVisible: lifeVisible, workInitial: workInitial, workReduce: workReduce, workRows: workRows, workCounts: workCounts };
34
+ }(typeof globalThis !== 'undefined' ? globalThis : this));