设置中心与 Mock 数据功能

- 顶栏:移除 tauri-native 传输徽章;⋯ 更多菜单重做(设置/Mock/环境/主题/文档/官网/重置)
- 设置弹窗重写:左侧导航(外观/网络/Mock 数据/数据管理/关于)+ 分组卡片 + 开关控件
- Mock 引擎(packages/core/src/mock.ts):Mock.js 常用语法子集 + 智能字段匹配
  - 61 条默认规则(通配符/精确/正则 × string/number/integer/boolean)
  - 发送链路接入:Query/JSON Body/urlencoded/form-data/GraphQL Variables
  - Body 工具栏:智能填充(生成可编辑 @表达式模板)+ Mock 预览
  - 设置页规则表 CRUD + 表达式速查表即时预览
- 文档新增「Mock 数据」页(/docs/mock),Manifest 顺序调整
- 新增 16 个 Mock 单测(总 37 前端用例全绿)
This commit is contained in:
logan committed 2026-09-18 01:35:54 +08:00
1 parent 6307a017a3
commit 9a019408ab
18 files changed
+1684 -104

No files matched your search

@@ -0,0 +1,72 @@
# Mock 数据
联调时后端接口没就绪?OpsxAPI 内置 **Mock.js 风格的数据生成器**,两种用法覆盖绝大多数场景:
1. **显式表达式** —— 在参数值、Body 中直接写 `@cname`、`@integer(1,100)`;
2. **智能匹配** —— 开启后按**字段名**自动套用规则表(`username` → 姓名、`email` → 邮箱…),一行不用写。
## 开启与配置
顶栏 **⋯ → Mock 数据**(或「设置 → Mock 数据」):
- **启用 Mock 数据**:总开关,关闭后 `@` 表达式按普通文本原样发送;
- **智能 Mock 匹配**:JSON Body 字段值未写表达式时,按字段名匹配规则自动生成;
- **智能匹配规则表**:数据类型(string / number / integer / boolean)、匹配方式(通配符 / 完全匹配 / 正则)、匹配规则(如 `*name*`)、Mock 表达式、备注与启用开关,支持搜索、新增、删除与恢复默认。
规则**从上到下**匹配,先命中先用;类型需与 JSON 字段实际类型一致(KV 参数一律按 string 匹配)。
## 表达式速查
| 表达式 | 说明 | 示例输出 |
| --- | --- | --- |
| `@cname` | 中文姓名 | `王国强` |
| `@email` | 邮箱 | `kx8f2@qq.com` |
| `@phone` | 手机号 | `138****2733` 形式 11 位 |
| `@id` | 身份证号 | 18 位 |
| `@guid` | UUID / GUID | `3f2b…-…` |
| `@integer(1, 100)` | 整数 | `42` |
| `@float(1, 9999, 2)` | 小数(保留 2 位) | `128.50` |
| `@boolean` | 布尔 | `true` |
| `@string(8, 16)` | 随机字符串 | `aZ3k…` |
| `@datetime(YYYY-MM-DD HH:mm:ss)` | 日期时间 | `2026-09-17 14:30:00` |
| `@date(YYYY-MM-DD)` / `@time(HH:mm:ss)` | 日期 / 时间 | — |
| `@timestamp` | 毫秒时间戳 | `1760000000000` |
| `@url` / `@domain` / `@ip` | URL / 域名 / IPv4 | — |
| `@province` / `@city(true)` / `@county(true)` | 省市区 | `广东省深圳市南山区` |
| `@ctitle(4, 12)` / `@csentence(10, 24)` | 中文标题 / 句子 | — |
| `@image(200x200)` | 图片地址 | picsum 随机图 |
| `@pick("A", "B", "C")` | 从列表中选择 | `B` |
未列出的写法会**原样发送**,可放心与真实值混用。
## 在请求中使用
**Params / x-www-form-urlencoded / form-data**:值一栏直接填表达式。
```text
username = @cname
page = @integer(1, 10)
```
**JSON Body**:叶子值写表达式,或交给智能匹配:
```json
{
"username": "@cname",
"email": "@email",
"age": "@integer(18, 60)",
"vip": "@boolean"
}
```
Body 工具栏提供两个辅助按钮:
- **智能填充**:按规则把 JSON 各字段值批量替换为对应 `@` 表达式,生成可编辑的 Mock 模板;
- **Mock 预览**:即时查看发送时实际生成的数据,不必真的发请求。
## 生效范围
- 生效:Query 参数、JSON / XML / Text Body、x-www-form-urlencoded、form-data、GraphQL Variables;
- 不生效:Headers、Cookies、鉴权字段(避免意外改坏签名与令牌)。
> 提示:Mock 在**发送瞬间**求值,同一请求每次发送都会得到新数据;预览结果仅供核对格式。
+102 -16
View File
@@ -4,37 +4,123 @@
{
"name": "开始使用",
"pages": [
{ "file": "01-intro.md", "slug": "intro", "title": "产品介绍", "updated": "2026-09-17" },
{ "file": "02-quickstart.md", "slug": "quickstart", "title": "快速开始", "updated": "2026-09-17" },
{ "file": "03-interface.md", "slug": "interface", "title": "界面导览", "updated": "2026-09-17" }
{
"file": "01-intro.md",
"slug": "intro",
"title": "产品介绍",
"updated": "2026-09-17"
},
{
"file": "02-quickstart.md",
"slug": "quickstart",
"title": "快速开始",
"updated": "2026-09-17"
},
{
"file": "03-interface.md",
"slug": "interface",
"title": "界面导览",
"updated": "2026-09-17"
}
]
},
{
"name": "请求调试",
"pages": [
{ "file": "04-http.md", "slug": "http", "title": "发送 HTTP 请求", "updated": "2026-09-17" },
{ "file": "05-auth.md", "slug": "auth", "title": "接口鉴权", "updated": "2026-09-17" },
{ "file": "06-env.md", "slug": "env", "title": "环境与变量", "updated": "2026-09-17" },
{ "file": "07-graphql.md", "slug": "graphql", "title": "GraphQL", "updated": "2026-09-17" },
{ "file": "08-websocket.md", "slug": "websocket", "title": "WebSocket", "updated": "2026-09-17" },
{ "file": "09-sse.md", "slug": "sse", "title": "SSE 事件流", "updated": "2026-09-17" },
{ "file": "10-socketio.md", "slug": "socketio", "title": "Socket.IO", "updated": "2026-09-17" },
{ "file": "11-mqtt.md", "slug": "mqtt", "title": "MQTT", "updated": "2026-09-17" }
{
"file": "04-http.md",
"slug": "http",
"title": "发送 HTTP 请求",
"updated": "2026-09-17"
},
{
"file": "05-auth.md",
"slug": "auth",
"title": "接口鉴权",
"updated": "2026-09-17"
},
{
"file": "06-env.md",
"slug": "env",
"title": "环境与变量",
"updated": "2026-09-17"
},
{
"file": "07-graphql.md",
"slug": "graphql",
"title": "GraphQL",
"updated": "2026-09-17"
},
{
"file": "08-websocket.md",
"slug": "websocket",
"title": "WebSocket",
"updated": "2026-09-17"
},
{
"file": "09-sse.md",
"slug": "sse",
"title": "SSE 事件流",
"updated": "2026-09-17"
},
{
"file": "10-socketio.md",
"slug": "socketio",
"title": "Socket.IO",
"updated": "2026-09-17"
},
{
"file": "11-mqtt.md",
"slug": "mqtt",
"title": "MQTT",
"updated": "2026-09-17"
}
]
},
{
"name": "数据管理",
"pages": [
{ "file": "12-import.md", "slug": "import", "title": "数据导入", "updated": "2026-09-17" },
{ "file": "13-export.md", "slug": "export", "title": "导出与代码生成", "updated": "2026-09-17" },
{ "file": "14-manage.md", "slug": "manage", "title": "集合与历史", "updated": "2026-09-17" }
{
"file": "12-import.md",
"slug": "import",
"title": "数据导入",
"updated": "2026-09-17"
},
{
"file": "13-export.md",
"slug": "export",
"title": "导出与代码生成",
"updated": "2026-09-17"
},
{
"file": "14-mock.md",
"slug": "mock",
"title": "Mock 数据",
"updated": "2026-09-17"
},
{
"file": "15-manage.md",
"slug": "manage",
"title": "集合与历史",
"updated": "2026-09-17"
}
]
},
{
"name": "参考",
"pages": [
{ "file": "15-server-api.md", "slug": "server-api", "title": "服务端 API 参考", "updated": "2026-09-17" },
{ "file": "16-faq.md", "slug": "faq", "title": "常见问题", "updated": "2026-09-17" }
{
"file": "16-server-api.md",
"slug": "server-api",
"title": "服务端 API 参考",
"updated": "2026-09-17"
},
{
"file": "17-faq.md",
"slug": "faq",
"title": "常见问题",
"updated": "2026-09-17"
}
]
}
]
+105
View File
@@ -369,6 +369,111 @@
.ops-hint svg { color: var(--accent-strong); margin-top: 2px; }
.ops-hint b { color: var(--accent-strong); font-family: inherit; }
/* ---------- 设置弹窗(左侧导航 + 分区) ---------- */
.set-dialog { display: grid; grid-template-columns: 188px minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); gap: 0 16px; height: min(66vh, 620px); min-height: 420px; margin: 0 -1.25rem -0.25rem; }
.set-side {
border-right: 1px solid var(--border);
padding-right: 12px;
display: flex; flex-direction: column; gap: 2px;
}
.set-side-head {
@apply mb-2 px-2 text-[11px] font-semibold uppercase tracking-[0.08em];
color: var(--text-faint);
}
.set-side-item {
@apply flex items-center gap-2.5 rounded-lg px-2 py-2 transition;
color: var(--text-dim);
}
.set-side-item:hover { background: var(--hover); color: var(--text); }
.set-side-item.active { background: var(--active); color: var(--accent-strong); }
.set-side-icon {
@apply grid h-7 w-7 shrink-0 place-items-center rounded-lg;
background: var(--panel-2);
color: var(--text-dim);
}
.set-side-icon.active { color: var(--accent-strong); }
.set-main { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.set-main-head { padding-bottom: 10px; flex-shrink: 0; }
.set-main-body { flex: 1; min-height: 0; padding-top: 14px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; padding-right: 4px; }
.set-main-body > .set-group { flex-shrink: 0; }
.set-group {
@apply overflow-hidden rounded-xl;
border: 1px solid var(--border);
background: var(--panel-2);
}
.set-group-title {
@apply px-4 pt-3 pb-1 text-[11px] font-semibold uppercase tracking-[0.08em];
color: var(--text-faint);
}
.set-row {
@apply flex items-center gap-4 px-4 py-3;
}
.set-row + .set-row { border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.set-row-control { flex-shrink: 0; display: flex; align-items: center; gap: 6px; }
/* 开关 */
.set-toggle {
@apply relative h-[22px] w-[38px] rounded-full transition;
background: var(--border);
}
.set-toggle:hover { opacity: 0.85; }
.set-toggle.on { background: var(--accent); }
.set-toggle-dot {
@apply absolute top-[3px] left-[3px] h-4 w-4 rounded-full bg-white transition;
}
.set-toggle.on .set-toggle-dot { transform: translateX(16px); }
/* Mock 规则表(随右侧内容区整体滚动,表头吸附) */
.set-group .ops-hint { margin: 10px 12px 0; }
.mock-rules { @apply mt-2; }
.mock-rule-head, .mock-rule-row {
display: grid;
grid-template-columns: 88px 84px minmax(110px, 1fr) minmax(130px, 1.2fr) minmax(96px, 0.9fr) 44px 30px;
gap: 6px;
align-items: center;
padding: 4px 12px;
}
.mock-rule-head {
@apply sticky top-0 z-[1] text-[11px] font-semibold;
color: var(--text-faint);
background: var(--panel-2);
border-bottom: 1px solid var(--border);
padding-top: 8px;
padding-bottom: 8px;
}
.mock-rule-row { padding-top: 5px; padding-bottom: 5px; }
.mock-rule-row + .mock-rule-row { border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent); }
.mock-rule-pattern { font-family: ui-monospace, monospace; }
.mock-rule-expr { font-family: ui-monospace, monospace; color: var(--accent-strong); }
/* Mock 速查表 */
.mock-cheatsheet { @apply mt-2 grid grid-cols-2 gap-1.5; }
.mock-cheat-item {
@apply flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-[11px] transition;
background: var(--panel);
border: 1px solid var(--border);
color: var(--text-dim);
}
.mock-cheat-item:hover { border-color: var(--accent); color: var(--text); }
.mock-cheat-item code {
font-family: ui-monospace, monospace;
color: var(--accent-strong);
font-size: 11px;
flex-shrink: 0;
}
.mock-cheat-item .ops-faint { flex: 1; min-width: 0; @apply truncate; }
.mock-cheat-gen {
max-width: 46%;
@apply truncate font-mono text-[10px];
color: var(--text-faint);
}
.mock-preview-value {
@apply rounded-md px-2 py-1 font-mono text-[12px];
background: var(--panel-2);
border: 1px solid var(--border);
color: var(--accent-strong);
}
/* ---------- {{变量}} 补全浮层 ---------- */
.var-hint-wrap { position: relative; min-width: 0; flex: 1; display: inline-flex; }
.var-hint-panel {