@microi.net/cli 6.2.3 → 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.
- package/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +6 -6
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/mcp-server.js +122 -122
- package/scripts/microi-cli.js +44 -44
- package/scripts/microi-codex-broker.js +2 -0
- package/scripts/microi-skills.meta.json +288 -261
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +104 -101
- package/skills/app-store/SKILL.md +555 -553
- package/skills/app-store/references/platform-row-format.md +7 -0
- package/skills/dos-orm/SKILL.md +2 -0
- package/skills/dos-orm/references/oceanbase-mysql.md +8 -0
- package/skills/message-notification/SKILL.md +2 -0
- package/skills/microi-ai-application/SKILL.md +1 -0
- package/skills/microi-client-frontend/SKILL.md +18 -50
- 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
- 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
- package/skills/microi-codex/SKILL.md +2 -0
- package/skills/microi-deployment/SKILL.md +7 -0
- package/skills/microi-docs-coverage/references/capability-map.md +3 -3
- package/skills/microi-form-engine/SKILL.md +27 -0
- package/skills/microi-form-engine/references/component-config-index.md +1 -1
- package/skills/microi-left-right-layout/SKILL.md +22 -0
- package/skills/microi-microservice/SKILL.md +15 -0
- package/skills/microi-mobile-app-quality/SKILL.md +2 -0
- package/skills/microi-mobile-app-quality/references/mobile-first-delivery.md +2 -1
- package/skills/microi-system-delivery/SKILL.md +8 -1
- package/skills/microi-system-delivery/references/delivery-pitfalls.md +19 -0
- package/skills/microi-ui/SKILL.md +7 -4
- package/skills/microi-uniapp-frontend/SKILL.md +10 -1
- package/skills/microi.v8.js +17 -3
- package/skills/module-engine/SKILL.md +7 -0
- package/skills/page-engine/SKILL.md +7 -1
- package/skills/page-engine/examples/go-view-operations.json +8467 -0
- package/skills/page-engine/examples/go-view-procurement.json +2800 -0
- package/skills/page-engine/references/go-view-json.md +62 -0
- package/skills/playwright-e2e/SKILL.md +11 -3
- 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
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +2 -2
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +2 -2
- package/skills/ui-design/SKILL.md +19 -5
- package/skills/ui-design/assets/pattern-showcase/app.js +57 -0
- package/skills/ui-design/assets/pattern-showcase/index.html +35 -4
- package/skills/ui-design/assets/pattern-showcase/scenario-state.js +34 -0
- package/skills/ui-design/assets/pattern-showcase/styles.css +115 -0
- package/skills/ui-design/references/data-workspace-density.md +8 -0
- package/skills/ui-design/references/design-pattern-library.md +50 -1
- package/skills/ui-design/references/mobile-product-language.md +93 -0
- package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +2 -1
- package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +2 -2
- package/skills/uniapp-mall-assets/SKILL.md +1 -0
- package/skills/v8-crud-api/SKILL.md +2 -1
- package/skills/v8-file-upload/SKILL.md +2 -2
- package/skills/v8-frontend-events/SKILL.md +2 -1
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +21 -1
- package/skills/v8-menu-buttons/SKILL.md +2 -0
- package/skills/v8-table-event/SKILL.md +2 -1
- package/skills/vision-engine/SKILL.md +284 -280
- package/skills/vision-engine/references/architecture-and-acceptance.md +14 -0
- package/skills/vision-engine/references/realtime-cpu-profiles.md +73 -0
- package/skills/vision-engine/references/video-timeline.md +176 -0
- package/skills/workspace-conventions/SKILL.md +3 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 吾码内置 GoView:发现、JSON 生成与验收
|
|
2
|
+
|
|
3
|
+
## 先发现真实引擎
|
|
4
|
+
|
|
5
|
+
GoView 已集成在 `Microi.Client/src/views/go-view/`,复用吾码 Vue、Pinia、DiyToken 和租户上下文,不要求单独运行 GoView 容器、3000/9527 端口或其原项目后端。`数据大屏` 菜单名称不包含 GoView 也不能作为未集成的证据。不要安装另一套 GoView 或拿 PageEngine/Vue 整页替代原生大屏 JSON。
|
|
6
|
+
|
|
7
|
+
1. 调用 `microi_codex profiles` 选定实际 `ApiBase/OsClient`,`microi_get_status` 检查连通与权限。
|
|
8
|
+
2. `microi_get_go_view_schema` 返回本机集成组件的真实 `key/chartKey/conKey/package/category/chartFrame`、原生协议和路由;可传 `component:"LineCommon"` 精确读取。目录由实际组件元数据生成,不能凭名称臆造组件。
|
|
9
|
+
3. `microi_list_go_view_projects` 分页盘点现有大屏;`microi_get_go_view_project {projectId}` 完整读取要修改的大屏及 `ContentHash`。旧 MCP 未提供 GoView 工具时先升级 MCP/CLI;允许使用已有同源 FormEngine/受保护管理员工具读取实时 `mic_data_dashboard`,不能因工具缺失声称产品无此功能。
|
|
10
|
+
|
|
11
|
+
存储表是 `mic_data_dashboard`,名称字段 `ProjectName`,设计字段 `ContentData`(JSON 字符串),其余常见字段 `IndexImage/State/Remarks` 以实时 Schema 为准。设计路由 `/mic/data-dashboard/design/:Id`,预览 `/mic/data-dashboard/preview/:Id`。PC 地址把 `ApiBase/OsClient` 放 `#` 前,例如 `http://localhost:61500/?ApiBase=https%3A%2F%2Fapi.example.com&OsClient=tenant#/mic/data-dashboard/preview/{Id}`。
|
|
12
|
+
|
|
13
|
+
## 原生 JSON 根与节点
|
|
14
|
+
|
|
15
|
+
根对象必须同时包含 `editCanvasConfig`(画布)、`componentList`(图层)和 `requestGlobalConfig`(全局请求)。先取 schema.root 作为最小根,1920×1080 示例可直接读取:
|
|
16
|
+
|
|
17
|
+
- [制造数字化运营平台完整 JSON](../examples/go-view-operations.json):交付、安全/人员、仓储、设备、质量、进度六区域,折线、数字翻牌、可编辑边框/标题/表格。
|
|
18
|
+
- [采购信息看板完整 JSON](../examples/go-view-procurement.json):双环形指标、原生仪表盘、供应商表与多指标趋势。
|
|
19
|
+
|
|
20
|
+
这些是可编辑原生组件,所有数值在画布及 Remarks 明确标为**参考图静态示例,非真实经营指标**。可用于排版起点,不可声称已接通客户业务。
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"editCanvasConfig":{"projectName":"示例","width":1920,"height":1080,"background":"#1e2128","selectColor":true,"chartThemeColor":"dark","previewScaleType":"fit"},
|
|
25
|
+
"requestGlobalConfig":{"requestDataPond":[],"requestOriginUrl":"","requestInterval":30,"requestIntervalUnit":"second","requestParams":{"Body":{},"Header":{},"Params":{}}},
|
|
26
|
+
"componentList":[]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
节点不可只写 ECharts option:还要有 `id/key/isGroup/attr/styles/preview/status/chartConfig/option/request/events`。以 schema.componentShape 或完整示例节点为骨架,组件身份必须来自真实目录。`attr` 使用设计画布坐标 `x/y/w/h`,`w/h>0`;`id` 在所有分组内外全局唯一,不能复制模板时沿用 Id。分组 `isGroup:true` 使用 `groupList`,深度最多8、总节点最多500。底层装饰先入列表,设置合理 `zIndex`,文字/数据置上层。
|
|
31
|
+
|
|
32
|
+
文字 `TextCommon.option.dataset` 是纯文本;设置 `fontSize/fontColor/textAlign/fontWeight/paddingX/paddingY/letterSpacing`。不使用大段 HTML,更不能把截图设为全屏背景伪装成可编辑组件。边框 `Border06/Border07` 的 `option.colors` 与 `backgroundColor` 控制科技边框及透明底色;标题可用 `Decorates06` 和独立 TextCommon,长分隔线用 `Decorates05`。
|
|
33
|
+
|
|
34
|
+
## 图表、表格与数据格式
|
|
35
|
+
|
|
36
|
+
| 原生组件 | 必需格式与注意事项 |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `LineCommon` | `option.dataset={dimensions:["month","value"],source:[{month:"2026-01",value:10}]}`;`series:[{type:"line",encode:{x:"month",y:"value"}}]`,多指标每个系列明确 encode,xAxis.category/yAxis.value。该组件注册折线,不可把 gauge/pie 塞进来代替对应组件。 |
|
|
39
|
+
| `PieCommon` | 普通/环形饼图用 dimensions/source,`series.type:"pie"`、`encode:{itemName:"name",value:"value"}`;环形半径 `radius:["65%","80%"]`。 |
|
|
40
|
+
| `PieCircle` | 指标环:`option.dataset` 为0–1的小数(0.96表示96%);保留原生 `title`、两个 `series.data[].value` 数组形状,组件会更新它们。直接复用采购示例,不把96作为小数输入。 |
|
|
41
|
+
| `Dial` | 原生仪表盘,`option.dataset` 数字,`series:[{type:"gauge",min:0,max:30,data:[{value:4}]}]`,量程/单位必须与真实口径一致。 |
|
|
42
|
+
| `FlipperNumber` | `dataset` 数值,`flipperLength` 位数,`flipperWidth/flipperHeight/flipperGap/fontSize` 控制翻牌;以实际组件 config 字段为准。 |
|
|
43
|
+
| `TableScrollBoard` | `header:["供应商","评级"]`、`dataset:[["供应商A","一级"],...]`,不是对象行;`index/columnWidth/align/rowNum/headerHeight/headerBGC/oddRowBGC/evenRowBGC` 控制表格。列宽总和与组件宽度相符。 |
|
|
44
|
+
|
|
45
|
+
ECharts 显式设置 `grid/legend/tooltip/xAxis/yAxis/series` 及字体色;画布全局 `chartThemeSetting` 会合并部分主题。按参考图先拆解面板比例、坐标、层级、颜色、标题与空态,再生成所有节点,禁止用随机数、无来源 KPI 或图表占位声称完成。
|
|
46
|
+
|
|
47
|
+
实际业务改用**当前租户已有固定接口引擎**批量聚合,服务端执行参数化查询、角色/部门/行级数据范围和时间口径;前端只传日期、维度等业务参数,绝不传 SQL。设计 JSON 不保存 DiyToken、Cookie、ApiKey、数据库密码、可执行 filter/事件源码、内嵌二进制 Base64。图片使用已授权 HDFS/CDN 地址,原始私有文件需经过现有权限流程。JSON 最大4MiB。
|
|
48
|
+
|
|
49
|
+
## 保存、回读与真实验收
|
|
50
|
+
|
|
51
|
+
先 `microi_validate_go_view_design {json}`。`microi_save_go_view_design` 参数为 `title/json`,可带 `projectId/remarks`;更新必须传刚读取的 `expectedContentHash`,创建建议预先指定稳定 `projectId`。默认预演;真实写入传 `confirmExecution` 等于 title。普通用户遵守原表权限;只有真实平台管理员可显式 `administrative:true`,后端再次独立校验 Level,不得用管理员参数给普通用户绕权。
|
|
52
|
+
|
|
53
|
+
内容哈希检查是客户端写前检查,**不是数据库原子CAS**,协作编辑要再次回读合并,不能声称可阻止所有并发覆盖。保存后工具独立回读同一 Id 并校验内容。传输超时/结果未知先回读该 Id;禁止盲目重试新建出重复大屏。
|
|
54
|
+
|
|
55
|
+
验收必须包含:
|
|
56
|
+
|
|
57
|
+
1. 官方 MCP 独立回读名称、完整 ContentData、组件数量和哈希;检查原有大屏未被覆盖。
|
|
58
|
+
2. 用真实登录用户在目标 ApiBase/OsClient 打开原生预览,1920×1080 检查全部面板/图表/表格、字体可读及参考图构图,捕获相关 JS/网络失败。不能把 JSON 校验、接口200或图片截图当成页面已渲染。
|
|
59
|
+
3. 打开原生设计器,确认各组件/图层可选择、属性面板可编辑,重开数据仍一致;实际数据源另验时间/筛选/权限/失败与真实明细对账。
|
|
60
|
+
4. 区分布局相似度与真实业务接入。没有合法 Logo/背景素材不复制品牌;保留可编辑组件并如实说明差异,不声称逐像素一致。
|
|
61
|
+
|
|
62
|
+
开发 MCP 目录时运行 `node microi.mcp/scripts/generate-go-view-catalog.mjs --check`,组件源变动后重新生成目录。新增组件必须同时维护原生渲染器、目录、示例/Skills、校验和真实预览,不能只新增工具名称或提示词。
|
|
@@ -7,7 +7,7 @@ description: 按 Microi 系统真实业务逻辑进行 Playwright 全自动化
|
|
|
7
7
|
|
|
8
8
|
# Microi 吾码 Playwright E2E 自动化测试
|
|
9
9
|
|
|
10
|
-
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
95
|
+
在上述外部浏览器模式中,国内或内网下载浏览器困难时可以使用本机 Edge:
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
$env:PW_BROWSER_CHANNEL='msedge'
|
package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md
CHANGED
|
@@ -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=
|
|
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.
|
|
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=
|
|
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
|
-
|
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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=
|
|
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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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
|
|
36
|
+
<p>先决定主要对象和阅读顺序,再选择色彩与形状。下方两个原创交互场景展示:同一套语义主题怎样支撑不同的任务与构图,标准后台操作区仍保持清楚稳定。</p>
|
|
36
37
|
<div class="mci-hero__actions">
|
|
37
|
-
<a class="mci-button mci-button--primary" href="#
|
|
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
|
|
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
|
|
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));
|