slexkit 0.2.0 → 0.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/CHANGELOG.md +52 -0
- package/LICENSE +21 -21
- package/dist/ai/llms-authoring.txt +2 -0
- package/dist/ai/llms-capabilities.txt +126 -0
- package/dist/ai/llms-components.txt +27 -6
- package/dist/ai/llms-full.txt +1729 -4
- package/dist/ai/llms-runtime.txt +7 -1
- package/dist/ai/llms.txt +22 -1
- package/dist/ai/slexkit-ai-manifest.json +674 -23
- package/dist/base.css +359 -359
- package/dist/chunks/{accordion-cfjyxw93.js → button-53ccjq5p.js} +11 -11
- package/dist/chunks/{accordion-nw12ytps.js → button-cr1fhsa7.js} +48 -2
- package/dist/chunks/{accordion-5f0nvjjm.js → button-dsftwzvg.js} +4 -3
- package/dist/chunks/{accordion-hzyrngd6.js → button-faf563xf.js} +2 -2
- package/dist/chunks/{accordion-ehnhpeca.js → button-jxv4c65t.js} +2 -2
- package/dist/chunks/{accordion-cw5r75jm.js → button-xv2dz7vn.js} +1 -1
- package/dist/chunks/{accordion-830dw78f.js → button-z5yv24ks.js} +2 -2
- package/dist/components/accordion.js +2 -2
- package/dist/components/badge.js +2 -2
- package/dist/components/button.css +101 -101
- package/dist/components/button.js +3 -3
- package/dist/components/callout.js +4 -4
- package/dist/components/card.js +2 -2
- package/dist/components/checkbox.js +2 -2
- package/dist/components/choice.css +151 -151
- package/dist/components/code-block.js +2 -2
- package/dist/components/collapsible.js +2 -2
- package/dist/components/column.js +1 -1
- package/dist/components/content.css +273 -250
- package/dist/components/divider.js +2 -2
- package/dist/components/grid.js +1 -1
- package/dist/components/index.js +13945 -26
- package/dist/components/input.css +777 -777
- package/dist/components/input.js +8 -8
- package/dist/components/link.js +2 -2
- package/dist/components/progress.js +2 -2
- package/dist/components/radio-group.js +2 -2
- package/dist/components/row.js +1 -1
- package/dist/components/section.js +2 -2
- package/dist/components/select.css +178 -178
- package/dist/components/select.js +3 -3
- package/dist/components/slider.css +116 -116
- package/dist/components/slider.js +2 -2
- package/dist/components/specs.js +32 -0
- package/dist/components/stat.js +2 -2
- package/dist/components/submit.css +8 -8
- package/dist/components/submit.js +1 -1
- package/dist/components/switch.css +105 -105
- package/dist/components/switch.js +3 -3
- package/dist/components/table.js +4 -4
- package/dist/components/tabs.css +95 -95
- package/dist/components/tabs.js +4 -4
- package/dist/components/text-input.css +23 -23
- package/dist/components/text.js +1 -1
- package/dist/components/toast.js +4 -4
- package/dist/components/tooling.js +73 -8
- package/dist/runtime.cjs +1590 -14
- package/dist/runtime.js +1589 -13
- package/dist/slexkit.cjs +28254 -13848
- package/dist/slexkit.css +1538 -1515
- package/dist/slexkit.js +28253 -13847
- package/dist/tooling.js +117 -11
- package/dist/types/components/svelte/helpers.d.ts +8 -1
- package/dist/types/engine/capabilities.d.ts +54 -0
- package/dist/types/engine/index.d.ts +6 -0
- package/dist/types/engine/secure-runtime.d.ts +9 -1
- package/dist/types/engine/stdlib.d.ts +30 -0
- package/dist/types/engine/types.d.ts +1 -0
- package/dist/types/engine/validation.d.ts +28 -0
- package/dist/types/index.d.ts +6 -3
- package/dist/types/runtime.d.ts +6 -3
- package/dist/types/version.d.ts +2 -2
- package/dist/umd/slexkit.tooling.umd.js +45016 -44626
- package/dist/umd/slexkit.umd.js +28255 -13849
- package/package.json +3 -2
- package/src/components/svelte/content/Formula.svelte +27 -0
- package/src/components/svelte/content/Table.svelte +1 -1
- package/src/components/svelte/helpers.ts +56 -1
- package/src/components/svelte/input/Input.svelte +7 -7
- package/src/components/svelte/input/Select.svelte +2 -2
- package/src/components/svelte/input/Switch.svelte +1 -1
- package/src/components/svelte/input/Tabs.svelte +7 -7
- package/src/components/svelte/tooling/PlaygroundMarkdown.svelte +84 -2
- package/src/styles/components/button.css +101 -101
- package/src/styles/components/choice.css +152 -152
- package/src/styles/components/select.css +178 -178
- package/src/styles/components/slider.css +116 -116
- package/src/styles/components/submit.css +8 -8
- package/src/styles/components/switch.css +105 -105
- package/src/styles/components/tabs.css +95 -95
- package/src/styles/components/text-input.css +23 -23
- package/src/styles/content.css +274 -251
- package/src/styles/input.css +8 -8
- package/src/styles/layout.css +360 -360
package/dist/ai/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SlexKit Full LLM Documentation
|
|
2
2
|
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
|
|
5
5
|
This file concatenates SlexKit's canonical English Markdown docs. `slex` fences are preserved exactly.
|
|
6
6
|
|
|
@@ -1105,6 +1105,7 @@ SlexKit follows the assistant-ui information architecture: a clear index, a full
|
|
|
1105
1105
|
"link:full": { href: "/llms-full.txt", text: "/llms-full.txt - full English context", icon: "book-open-text" },
|
|
1106
1106
|
"link:components": { href: "/llms-components.txt", text: "/llms-components.txt - components and API", icon: "puzzle-piece" },
|
|
1107
1107
|
"link:runtime": { href: "/llms-runtime.txt", text: "/llms-runtime.txt - runtime and host integration", icon: "cpu" },
|
|
1108
|
+
"link:capabilities": { href: "/llms-capabilities.txt", text: "/llms-capabilities.txt - std and api capabilities", icon: "function" },
|
|
1108
1109
|
"link:toolhost": { href: "/llms-toolhost.txt", text: "/llms-toolhost.txt - structured input", icon: "cursor-click" },
|
|
1109
1110
|
"link:authoring": { href: "/llms-authoring.txt", text: "/llms-authoring.txt - slex fence authoring rules", icon: "pencil-simple" },
|
|
1110
1111
|
"link:manifest": { href: "/slexkit-ai-manifest.json", text: "/slexkit-ai-manifest.json - machine-readable index", icon: "brackets-curly" },
|
|
@@ -1119,8 +1120,9 @@ Minimal reading path:
|
|
|
1119
1120
|
1. Start with [`/llms.txt`](/llms.txt) for the grouped index.
|
|
1120
1121
|
2. Use [`/llms-full.txt`](/llms-full.txt) when the agent needs broad context.
|
|
1121
1122
|
3. Use [`/llms-components.txt`](/llms-components.txt) and raw component `.md` pages when authoring UI.
|
|
1122
|
-
4. Use [`/llms-
|
|
1123
|
-
5. Use [`/llms-
|
|
1123
|
+
4. Use [`/llms-capabilities.txt`](/llms-capabilities.txt) for `std.*` and policy-gated `api.*`.
|
|
1124
|
+
5. Use [`/llms-runtime.txt`](/llms-runtime.txt) for host and secure runtime integration.
|
|
1125
|
+
6. Use [`/llms-toolhost.txt`](/llms-toolhost.txt) only when user input must return structured data to the host.
|
|
1124
1126
|
|
|
1125
1127
|
SlexKit raw docs are ordinary `.md` pages with explicit `slex` fences. There is no `.mdx` route — `slex` fences are the interactive layer.
|
|
1126
1128
|
|
|
@@ -1138,6 +1140,7 @@ Documentation: https://slexkit.dev/llms-full.txt
|
|
|
1138
1140
|
Key patterns:
|
|
1139
1141
|
- Display UI uses explicit `slex` fenced blocks plus Markdown fallback.
|
|
1140
1142
|
- Slex source uses `{ slex, namespace, g, layout }`; use `slex: "0.1"` for the current public protocol.
|
|
1143
|
+
- Use `std.*` for common calculations, formatting, units, and small statistics.
|
|
1141
1144
|
- ToolHost is only for structured user input flows.
|
|
1142
1145
|
- Untrusted or agent-generated source should use the secure runtime.
|
|
1143
1146
|
- Raw docs are `.md` files with `slex` fences, not `.mdx`.
|
|
@@ -1260,6 +1263,1606 @@ npx add-mcp @slexkit/mcp -a zed
|
|
|
1260
1263
|
|
|
1261
1264
|
---
|
|
1262
1265
|
|
|
1266
|
+
# 你的第一个 SlexKit 卡片
|
|
1267
|
+
|
|
1268
|
+
URL: /zh-CN/examples/hello-slexkit
|
|
1269
|
+
Raw Markdown: /zh-CN/examples/hello-slexkit.md
|
|
1270
|
+
Source: content/examples/hello-slexkit/zh-CN.md
|
|
1271
|
+
|
|
1272
|
+
# 你的第一个 SlexKit 卡片
|
|
1273
|
+
|
|
1274
|
+
SlexKit 的核心思想:**用声明式 JSON 描述 UI,而不仅是 Markdown**。下面是一个纯静态的卡片——没有 `g` 对象,没有交互,所有内容都直接写在结构中。
|
|
1275
|
+
|
|
1276
|
+
```slex
|
|
1277
|
+
{
|
|
1278
|
+
slex: "0.1",
|
|
1279
|
+
namespace: "learn_hello_slexkit",
|
|
1280
|
+
layout: {
|
|
1281
|
+
"section:hello": {
|
|
1282
|
+
eyebrow: "入门教程 · 1/4",
|
|
1283
|
+
title: "你的第一个 SlexKit 卡片",
|
|
1284
|
+
subtitle: "所有内容都是声明式的——数字、颜色、布局,全部来自 DSL。",
|
|
1285
|
+
"grid:top-stats": {
|
|
1286
|
+
columns: 1, mdColumns: 3,
|
|
1287
|
+
"stat:users": { label: "活跃用户", value: "12,847", unit: "人" },
|
|
1288
|
+
"stat:uptime": { label: "正常运行", value: "99.97", unit: "%" },
|
|
1289
|
+
"stat:latency": { label: "服务延迟", value: "42", unit: "ms" }
|
|
1290
|
+
},
|
|
1291
|
+
"table:pricing": {
|
|
1292
|
+
columns: ["功能", "免费版", "专业版"],
|
|
1293
|
+
rows: [
|
|
1294
|
+
["可用组件", "全部", "全部"],
|
|
1295
|
+
["自定义主题", "3 种", "无限"],
|
|
1296
|
+
["数据导出", "JSON", "JSON / CSV / SQL"]
|
|
1297
|
+
]
|
|
1298
|
+
},
|
|
1299
|
+
"callout:tip": {
|
|
1300
|
+
tone: "info",
|
|
1301
|
+
text: "你现在看到的每一样东西——标题、数值、表格、颜色——都来自上面的 DSL 声明,没有一行 HTML。这就是 SlexKit 的核心理念:Markdown 提供叙事,DSL 提供交互。"
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
只看不动,感受一下结构和布局语法。下一节我们给卡片加上第一条响应式数据。
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
思考:如果 `"12,847"` 需要从里面计算出来,显然直接写死字符串不够。这就需要引入 **`g` 对象**——下一节的主角。
|
|
1314
|
+
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
# 第一个交互:滑块操控数据
|
|
1318
|
+
|
|
1319
|
+
URL: /zh-CN/examples/first-interaction
|
|
1320
|
+
Raw Markdown: /zh-CN/examples/first-interaction.md
|
|
1321
|
+
Source: content/examples/first-interaction/zh-CN.md
|
|
1322
|
+
|
|
1323
|
+
# 第一个交互:滑块操控数据
|
|
1324
|
+
|
|
1325
|
+
上一节全是静态数据。要让 UI "活起来",需要三样东西:
|
|
1326
|
+
|
|
1327
|
+
1. **`g` 对象** — 响应式状态容器
|
|
1328
|
+
2. **`$value` / `$label` / `$tone`** — 读表达式(从 g 取值渲染)
|
|
1329
|
+
3. **`onchange`** — 写表达式(用户操作写入 g)
|
|
1330
|
+
|
|
1331
|
+
```slex
|
|
1332
|
+
{
|
|
1333
|
+
slex: "0.1",
|
|
1334
|
+
namespace: "learn_first_interaction",
|
|
1335
|
+
g: { count: 42 },
|
|
1336
|
+
layout: {
|
|
1337
|
+
"section:interact": {
|
|
1338
|
+
eyebrow: "入门教程 · 2/4",
|
|
1339
|
+
title: "第一个交互:滑块操控数据",
|
|
1340
|
+
subtitle: "滑动下面的滑块,stat 和 callout 会跟着变。这就是 SlexKit 的响应式核心。",
|
|
1341
|
+
"column:controls": {
|
|
1342
|
+
"slider:count": {
|
|
1343
|
+
label: "选择数值",
|
|
1344
|
+
"$value": "g.count",
|
|
1345
|
+
min: 0,
|
|
1346
|
+
max: 100,
|
|
1347
|
+
step: 1,
|
|
1348
|
+
onchange: "g.count = Number($event)"
|
|
1349
|
+
},
|
|
1350
|
+
"stat:count": { label: "当前数值", "$value": "g.count" },
|
|
1351
|
+
"badge:level": {
|
|
1352
|
+
"$label": "g.count < 30 ? '低' : g.count < 70 ? '中' : '高'",
|
|
1353
|
+
"$tone": "g.count < 30 ? 'success' : g.count < 70 ? 'warning' : 'danger'"
|
|
1354
|
+
},
|
|
1355
|
+
"callout:note": {
|
|
1356
|
+
"$tone": "g.count < 50 ? 'success' : 'info'",
|
|
1357
|
+
"$text": "g.count < 50 ? '当前值偏低,适合入门级负载。' : '当前值偏高,注意监控资源消耗。'"
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
核心公式:
|
|
1366
|
+
```
|
|
1367
|
+
用户操作 → onchange → g → SlexKit 检测变化 → 所有 $value/$tone/$text 自动重算 → UI 更新
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
这就是**单向数据流** + **响应式重渲染**。不需要手动 `setState`,不需要 DOM 操作。
|
|
1371
|
+
|
|
1372
|
+
---
|
|
1373
|
+
|
|
1374
|
+
# 多输入协同:两个滑块联动
|
|
1375
|
+
|
|
1376
|
+
URL: /zh-CN/examples/multi-input-coordination
|
|
1377
|
+
Raw Markdown: /zh-CN/examples/multi-input-coordination.md
|
|
1378
|
+
Source: content/examples/multi-input-coordination/zh-CN.md
|
|
1379
|
+
|
|
1380
|
+
# 多输入协同:两个滑块联动
|
|
1381
|
+
|
|
1382
|
+
真实场景中往往有多个输入变量,它们相互影响。这需要引入两样新东西:
|
|
1383
|
+
1. **`g.method()`** — 依赖其他状态的计算值(类似 Vue computed)
|
|
1384
|
+
2. **`$if`** — 条件渲染(根据状态决定显示或隐藏组件)
|
|
1385
|
+
|
|
1386
|
+
```slex
|
|
1387
|
+
{
|
|
1388
|
+
slex: "0.1",
|
|
1389
|
+
namespace: "learn_multi_coordination",
|
|
1390
|
+
g: {
|
|
1391
|
+
width: 120,
|
|
1392
|
+
height: 80,
|
|
1393
|
+
area: function () { return this.width * this.height; },
|
|
1394
|
+
isLandscape: function () { return this.width > this.height; },
|
|
1395
|
+
ratio: function () { return (this.width / this.height).toFixed(2); }
|
|
1396
|
+
},
|
|
1397
|
+
layout: {
|
|
1398
|
+
"section:coordinated": {
|
|
1399
|
+
eyebrow: "入门教程 · 3/4",
|
|
1400
|
+
title: "多输入协同:矩形尺寸联动",
|
|
1401
|
+
subtitle: "同时调整宽和高,面积和宽高比自动重新计算。这就是 g 方法的力量。",
|
|
1402
|
+
"grid:params": {
|
|
1403
|
+
columns: 1, mdColumns: 2,
|
|
1404
|
+
"column:w": {
|
|
1405
|
+
"input:width": { label: "宽度", "$value": "g.width", type: "number", unit: "px", onchange: "g.width = Number($event || 0)" },
|
|
1406
|
+
"slider:width": { label: "宽度", "$value": "g.width", min: 20, max: 300, step: 5, unit: "px", onchange: "g.width = Number($event)" }
|
|
1407
|
+
},
|
|
1408
|
+
"column:h": {
|
|
1409
|
+
"input:height": { label: "高度", "$value": "g.height", type: "number", unit: "px", onchange: "g.height = Number($event || 0)" },
|
|
1410
|
+
"slider:height": { label: "高度", "$value": "g.height", min: 20, max: 300, step: 5, unit: "px", onchange: "g.height = Number($event)" }
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
"grid:results": {
|
|
1414
|
+
columns: 1, mdColumns: 3,
|
|
1415
|
+
"stat:area": { label: "面积", "$value": "g.area()", unit: "px²" },
|
|
1416
|
+
"stat:ratio": { label: "宽高比", "$value": "g.ratio()" },
|
|
1417
|
+
"badge:orientation": {
|
|
1418
|
+
"$label": "g.isLandscape() ? '横向' : g.width === g.height ? '正方形' : '纵向'",
|
|
1419
|
+
"$tone": "g.isLandscape() ? 'info' : g.width === g.height ? 'success' : 'warning'"
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
"formula:areaEq": { "$tex": "'\\\\text{面积} = ' + g.width + ' \\\\times ' + g.height + ' = ' + g.area() + '\\\\text{ px}^2'" },
|
|
1423
|
+
"callout:tip": {
|
|
1424
|
+
"$tone": "g.isLandscape() ? 'info' : 'warning'",
|
|
1425
|
+
"$text": "g.isLandscape() ? '当前为横向(Landscape)。横向更适用于宽屏展示。' : '当前为纵向(Portrait)。纵向更适用于移动端阅读。'"
|
|
1426
|
+
},
|
|
1427
|
+
"callout:squareTip": {
|
|
1428
|
+
"$if": "g.width === g.height",
|
|
1429
|
+
tone: "success",
|
|
1430
|
+
text: "这是一个正方形!宽高完全相等。"
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
**三个新知识点:**
|
|
1438
|
+
|
|
1439
|
+
| 概念 | 写法 | 含义 |
|
|
1440
|
+
|:---|:---|:---|
|
|
1441
|
+
| g 方法 | `area: function() { return this.width * this.height; }` | `this` 指向 g 对象本身,返回动态计算值 |
|
|
1442
|
+
| 动态公式 | `"$tex": "'...' + g.width + '...'"` | formula 组件可根据 g 值实时渲染 KaTeX |
|
|
1443
|
+
| 条件渲染 | `"$if": "g.width === g.height"` | 只有表达式返回 true 时该组件才渲染 |
|
|
1444
|
+
|
|
1445
|
+
---
|
|
1446
|
+
|
|
1447
|
+
# 分支与切换:模式选择器
|
|
1448
|
+
|
|
1449
|
+
URL: /zh-CN/examples/tabs-and-branching
|
|
1450
|
+
Raw Markdown: /zh-CN/examples/tabs-and-branching.md
|
|
1451
|
+
Source: content/examples/tabs-and-branching/zh-CN.md
|
|
1452
|
+
|
|
1453
|
+
# 分支与切换:模式选择器
|
|
1454
|
+
|
|
1455
|
+
上一节是一个场景内的协同。现实中有多个场景需要在同一空间内切换——这时候用 **select** 实现分支。
|
|
1456
|
+
|
|
1457
|
+
核心思想:**UI = f(state)**。切换 `mode` 状态变量,整个视图区域自动切换。
|
|
1458
|
+
|
|
1459
|
+
```slex
|
|
1460
|
+
{
|
|
1461
|
+
slex: "0.1",
|
|
1462
|
+
namespace: "learn_tabs_branching",
|
|
1463
|
+
g: {
|
|
1464
|
+
mode: "length",
|
|
1465
|
+
value: 100,
|
|
1466
|
+
convert: function () {
|
|
1467
|
+
if (this.mode === "length") return (this.value / 100).toFixed(2) + " m";
|
|
1468
|
+
if (this.mode === "weight") return (this.value * 2.20462).toFixed(2) + " 磅 (lbs)";
|
|
1469
|
+
if (this.mode === "temp") return (this.value * 9 / 5 + 32).toFixed(1) + " °F";
|
|
1470
|
+
return "—";
|
|
1471
|
+
},
|
|
1472
|
+
label: function () {
|
|
1473
|
+
if (this.mode === "length") return "厘米转米";
|
|
1474
|
+
if (this.mode === "weight") return "公斤转磅";
|
|
1475
|
+
return "摄氏度转华氏度";
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
layout: {
|
|
1479
|
+
"section:branching": {
|
|
1480
|
+
eyebrow: "入门教程 · 4/4",
|
|
1481
|
+
title: "分支与切换:模式选择器",
|
|
1482
|
+
subtitle: "切换下面的模式,输入的参数和计算结果会跟着变化。一种模式 = 一种 UI 状态。",
|
|
1483
|
+
"select:mode": {
|
|
1484
|
+
label: "转换模式",
|
|
1485
|
+
"$value": "g.mode",
|
|
1486
|
+
options: [
|
|
1487
|
+
{ label: "长度 (cm → m)", value: "length" },
|
|
1488
|
+
{ label: "重量 (kg → lbs)", value: "weight" },
|
|
1489
|
+
{ label: "温度 (°C → °F)", value: "temp" }
|
|
1490
|
+
],
|
|
1491
|
+
onchange: "g.mode = String($event)"
|
|
1492
|
+
},
|
|
1493
|
+
"input:value": { label: "输入值", "$value": "g.value", type: "number", onchange: "g.value = Number($event || 0)" },
|
|
1494
|
+
"stat:result": { "$label": "g.label()", "$value": "g.convert()" },
|
|
1495
|
+
"callout:guide": {
|
|
1496
|
+
"$tone": "g.mode === 'temp' ? 'warning' : 'info'",
|
|
1497
|
+
"$text": "g.mode === 'length' ? '1 米 = 100 厘米,除以 100 即可。' : g.mode === 'weight' ? '1 公斤 ≈ 2.20462 磅。' : '°F = °C × 9/5 + 32。华氏度范围更大,注意精度。'"
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
---
|
|
1505
|
+
|
|
1506
|
+
# 五险一金计算器
|
|
1507
|
+
|
|
1508
|
+
URL: /zh-CN/examples/salary-calculator
|
|
1509
|
+
Raw Markdown: /zh-CN/examples/salary-calculator.md
|
|
1510
|
+
Source: content/examples/salary-calculator/zh-CN.md
|
|
1511
|
+
|
|
1512
|
+
# 五险一金计算器
|
|
1513
|
+
|
|
1514
|
+
你面试拿到一个offer,月薪2万,到手能拿多少?HR说五险一金要扣一大笔,但具体扣多少、怎么算,每个城市还不一样。
|
|
1515
|
+
|
|
1516
|
+
```slex
|
|
1517
|
+
{
|
|
1518
|
+
slex: "0.1",
|
|
1519
|
+
namespace: "example_salary_calculator",
|
|
1520
|
+
g: {
|
|
1521
|
+
base: 20000,
|
|
1522
|
+
city: "beijing",
|
|
1523
|
+
rates: {
|
|
1524
|
+
beijing: { pensionP: 8, medicalP: 2, unemploymentP: 0.5, pensionC: 16, medicalC: 8, unemploymentC: 0.5, injury: 0.2, maternity: 0.8, housing: 12 },
|
|
1525
|
+
shanghai: { pensionP: 8, medicalP: 2, unemploymentP: 0.5, pensionC: 16, medicalC: 8, unemploymentC: 0.5, injury: 0.2, maternity: 0.8, housing: 7 },
|
|
1526
|
+
guangzhou: { pensionP: 8, medicalP: 2, unemploymentP: 0.5, pensionC: 16, medicalC: 8, unemploymentC: 0.5, injury: 0.2, maternity: 0.8, housing: 5 },
|
|
1527
|
+
shenzhen: { pensionP: 8, medicalP: 2, unemploymentP: 0.5, pensionC: 16, medicalC: 8, unemploymentC: 0.5, injury: 0.2, maternity: 0.8, housing: 5 }
|
|
1528
|
+
},
|
|
1529
|
+
currentRate: function () { return this.rates[this.city] || this.rates.beijing; },
|
|
1530
|
+
personalRate: function () { var r = this.currentRate(); return r.pensionP + r.medicalP + r.unemploymentP + r.housing; },
|
|
1531
|
+
companyRate: function () { var r = this.currentRate(); return r.pensionC + r.medicalC + r.unemploymentC + r.injury + r.maternity + r.housing; },
|
|
1532
|
+
personalTotal: function () { return this.base * this.personalRate() / 100; },
|
|
1533
|
+
companyTotal: function () { return this.base * this.companyRate() / 100; },
|
|
1534
|
+
total: function () { return this.personalTotal() + this.companyTotal(); },
|
|
1535
|
+
takeHome: function () { return this.base - this.personalTotal(); },
|
|
1536
|
+
cityLabel: function () { return { beijing: "北京", shanghai: "上海", guangzhou: "广州", shenzhen: "深圳" }[this.city] || this.city; }
|
|
1537
|
+
},
|
|
1538
|
+
layout: {
|
|
1539
|
+
"section:salary": {
|
|
1540
|
+
eyebrow: "计算器",
|
|
1541
|
+
title: "五险一金计算器",
|
|
1542
|
+
subtitle: "输入税前工资和城市,实时计算五险一金明细。",
|
|
1543
|
+
"grid:params": {
|
|
1544
|
+
columns: 1, mdColumns: 2,
|
|
1545
|
+
"column:baseField": {
|
|
1546
|
+
"input:base": { label: "税前工资", "$value": "g.base", type: "number", unit: "元/月", onchange: "g.base = Number($event || 0)" },
|
|
1547
|
+
"slider:base": { label: "税前工资", "$value": "g.base", min: 3000, max: 50000, step: 500, unit: "元", onchange: "g.base = Number($event)" }
|
|
1548
|
+
},
|
|
1549
|
+
"column:cityField": {
|
|
1550
|
+
"select:city": {
|
|
1551
|
+
label: "缴纳城市",
|
|
1552
|
+
"$value": "g.city",
|
|
1553
|
+
options: [
|
|
1554
|
+
{ label: "北京", value: "beijing" },
|
|
1555
|
+
{ label: "上海", value: "shanghai" },
|
|
1556
|
+
{ label: "广州", value: "guangzhou" },
|
|
1557
|
+
{ label: "深圳", value: "shenzhen" }
|
|
1558
|
+
],
|
|
1559
|
+
onchange: "g.city = String($event)"
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
"grid:summary": {
|
|
1564
|
+
columns: 1, mdColumns: 3,
|
|
1565
|
+
"stat:personal": { label: "个人扣除", "$value": "g.personalTotal().toFixed(0)", unit: "元" },
|
|
1566
|
+
"stat:company": { label: "公司缴纳", "$value": "g.companyTotal().toFixed(0)", unit: "元" },
|
|
1567
|
+
"stat:takehome": { label: "到手工资", "$value": "g.takeHome().toFixed(0)", unit: "元" }
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
换个城市看看,公积金比例差很多——北京12%,上海7%,到手工资能差好几百。
|
|
1575
|
+
|
|
1576
|
+
```slex
|
|
1577
|
+
{
|
|
1578
|
+
slex: "0.1",
|
|
1579
|
+
namespace: "example_salary_calculator",
|
|
1580
|
+
layout: {
|
|
1581
|
+
"card:detail": {
|
|
1582
|
+
title: "各项明细",
|
|
1583
|
+
"grid:personal": {
|
|
1584
|
+
columns: 1, mdColumns: 4,
|
|
1585
|
+
"stat:pension_p": { label: "养老(8%)", "$value": "(g.base * 0.08).toFixed(0)", unit: "元" },
|
|
1586
|
+
"stat:medical_p": { label: "医疗(2%)", "$value": "(g.base * 0.02).toFixed(0)", unit: "元" },
|
|
1587
|
+
"stat:unemployment_p": { label: "失业(0.5%)", "$value": "(g.base * 0.005).toFixed(0)", unit: "元" },
|
|
1588
|
+
"stat:housing_p": { label: "公积金", "$value": "(g.base * g.currentRate().housing / 100).toFixed(0)", unit: "元" }
|
|
1589
|
+
},
|
|
1590
|
+
"grid:company": {
|
|
1591
|
+
columns: 1, mdColumns: 4,
|
|
1592
|
+
"stat:pension_c": { label: "养老(16%)", "$value": "(g.base * 0.16).toFixed(0)", unit: "元" },
|
|
1593
|
+
"stat:medical_c": { label: "医疗(8%)", "$value": "(g.base * 0.08).toFixed(0)", unit: "元" },
|
|
1594
|
+
"stat:unemployment_c": { label: "失业(0.5%)", "$value": "(g.base * 0.005).toFixed(0)", unit: "元" },
|
|
1595
|
+
"stat:other_c": { label: "工伤+生育", "$value": "(g.base * 0.01).toFixed(0)", unit: "元" }
|
|
1596
|
+
},
|
|
1597
|
+
"callout:note": {
|
|
1598
|
+
"$tone": "g.personalTotal() > 3000 ? 'warning' : 'info'",
|
|
1599
|
+
"$text": "g.personalTotal() > 3000 ? '个人扣除超过3000元,到手工资可能低于预期。' : '公积金比例越高,到手工资越少,但公积金可以提取使用。'"
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
| 城市 | 养老 | 医疗 | 失业 | 公积金 | 个人合计 |
|
|
1607
|
+
|------|------|------|------|--------|----------|
|
|
1608
|
+
| 北京 | 8% | 2% | 0.5% | 12% | 22.5% |
|
|
1609
|
+
| 上海 | 8% | 2% | 0.5% | 7% | 17.5% |
|
|
1610
|
+
| 广州 | 8% | 2% | 0.5% | 5% | 15.5% |
|
|
1611
|
+
| 深圳 | 8% | 2% | 0.5% | 5% | 15.5% |
|
|
1612
|
+
|
|
1613
|
+
---
|
|
1614
|
+
|
|
1615
|
+
# 软件项目成本估算器
|
|
1616
|
+
|
|
1617
|
+
URL: /zh-CN/examples/project-cost-estimator
|
|
1618
|
+
Raw Markdown: /zh-CN/examples/project-cost-estimator.md
|
|
1619
|
+
Source: content/examples/project-cost-estimator/zh-CN.md
|
|
1620
|
+
|
|
1621
|
+
# 软件项目成本估算器
|
|
1622
|
+
|
|
1623
|
+
老板问你:"这个项目要多少钱?"你说:"我算算。"然后打开Excel,填一堆公式。其实不用——输入团队配置和周期,成本自动算出来。
|
|
1624
|
+
|
|
1625
|
+
```slex
|
|
1626
|
+
{
|
|
1627
|
+
slex: "0.1",
|
|
1628
|
+
namespace: "example_project_cost",
|
|
1629
|
+
g: {
|
|
1630
|
+
frontend: 2, backend: 3, tester: 1, designer: 1,
|
|
1631
|
+
months: 6,
|
|
1632
|
+
salary: 15000,
|
|
1633
|
+
teamSize: function () { return this.frontend + this.backend + this.tester + this.designer; },
|
|
1634
|
+
laborCost: function () { return this.teamSize() * this.salary * this.months; },
|
|
1635
|
+
equipmentCost: function () { return this.teamSize() * 5000; },
|
|
1636
|
+
officeCost: function () { return this.teamSize() * 2000 * this.months; },
|
|
1637
|
+
subtotal: function () { return this.laborCost() + this.equipmentCost() + this.officeCost(); },
|
|
1638
|
+
riskBuffer: function () { return this.subtotal() * 0.15; },
|
|
1639
|
+
totalCost: function () { return this.subtotal() + this.riskBuffer(); },
|
|
1640
|
+
perPersonCost: function () { return this.teamSize() > 0 ? this.totalCost() / this.teamSize() : 0; },
|
|
1641
|
+
monthlyBurn: function () { return this.months > 0 ? this.totalCost() / this.months : 0; }
|
|
1642
|
+
},
|
|
1643
|
+
layout: {
|
|
1644
|
+
"section:estimator": {
|
|
1645
|
+
eyebrow: "计算器",
|
|
1646
|
+
title: "软件项目成本估算器",
|
|
1647
|
+
subtitle: "输入团队配置和周期,成本自动算出来。",
|
|
1648
|
+
"card:estimator": {
|
|
1649
|
+
title: "项目成本估算",
|
|
1650
|
+
"grid:team": {
|
|
1651
|
+
columns: 1, mdColumns: 4,
|
|
1652
|
+
"column:fe": {
|
|
1653
|
+
"input:frontend": { label: "前端", "$value": "g.frontend", type: "number", unit: "人", onchange: "g.frontend = Number($event || 0)" }
|
|
1654
|
+
},
|
|
1655
|
+
"column:be": {
|
|
1656
|
+
"input:backend": { label: "后端", "$value": "g.backend", type: "number", unit: "人", onchange: "g.backend = Number($event || 0)" }
|
|
1657
|
+
},
|
|
1658
|
+
"column:qa": {
|
|
1659
|
+
"input:tester": { label: "测试", "$value": "g.tester", type: "number", unit: "人", onchange: "g.tester = Number($event || 0)" }
|
|
1660
|
+
},
|
|
1661
|
+
"column:ui": {
|
|
1662
|
+
"input:designer": { label: "设计", "$value": "g.designer", type: "number", unit: "人", onchange: "g.designer = Number($event || 0)" }
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
"grid:params": {
|
|
1666
|
+
columns: 1, mdColumns: 2,
|
|
1667
|
+
"column:period": {
|
|
1668
|
+
"input:months": { label: "开发周期", "$value": "g.months", type: "number", unit: "个月", onchange: "g.months = Number($event || 0)" },
|
|
1669
|
+
"slider:months": { label: "开发周期", "$value": "g.months", min: 1, max: 24, step: 1, unit: "月", onchange: "g.months = Number($event)" }
|
|
1670
|
+
},
|
|
1671
|
+
"column:salaryField": {
|
|
1672
|
+
"input:salary": { label: "人均月薪", "$value": "g.salary", type: "number", unit: "元", onchange: "g.salary = Number($event || 0)" },
|
|
1673
|
+
"slider:salary": { label: "人均月薪", "$value": "g.salary", min: 8000, max: 50000, step: 1000, unit: "元", onchange: "g.salary = Number($event)" }
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
"grid:results": {
|
|
1677
|
+
columns: 1, mdColumns: 4,
|
|
1678
|
+
"stat:team": { label: "团队", "$value": "g.teamSize()", unit: "人" },
|
|
1679
|
+
"stat:total": { label: "总成本", "$value": "g.totalCost().toFixed(0)", unit: "元" },
|
|
1680
|
+
"stat:perperson": { label: "人均成本", "$value": "g.perPersonCost().toFixed(0)", unit: "元" },
|
|
1681
|
+
"stat:monthly": { label: "月均消耗", "$value": "g.monthlyBurn().toFixed(0)", unit: "元" }
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
7个人做半年,月均消耗多少?加个测试人员会不会超预算?拖一下就知道。
|
|
1690
|
+
|
|
1691
|
+
```slex
|
|
1692
|
+
{
|
|
1693
|
+
slex: "0.1",
|
|
1694
|
+
namespace: "example_project_cost",
|
|
1695
|
+
layout: {
|
|
1696
|
+
"card:breakdown": {
|
|
1697
|
+
title: "成本构成",
|
|
1698
|
+
"grid:costs": {
|
|
1699
|
+
columns: 1, mdColumns: 3,
|
|
1700
|
+
"stat:labor": { label: "人力成本", "$value": "g.laborCost().toFixed(0)", unit: "元" },
|
|
1701
|
+
"stat:equipment": { label: "设备成本", "$value": "g.equipmentCost().toFixed(0)", unit: "元" },
|
|
1702
|
+
"stat:office": { label: "办公成本", "$value": "g.officeCost().toFixed(0)", unit: "元" }
|
|
1703
|
+
},
|
|
1704
|
+
"grid:extra": {
|
|
1705
|
+
columns: 1, mdColumns: 2,
|
|
1706
|
+
"stat:risk": { label: "风险缓冲(15%)", "$value": "g.riskBuffer().toFixed(0)", unit: "元" },
|
|
1707
|
+
"stat:total": { label: "总计", "$value": "g.totalCost().toFixed(0)", unit: "元" }
|
|
1708
|
+
},
|
|
1709
|
+
"callout:tip": {
|
|
1710
|
+
"$tone": "g.months > 12 ? 'warning' : 'info'",
|
|
1711
|
+
"$text": "g.months > 12 ? '项目周期超过1年,建议分阶段交付以降低风险。' : '风险缓冲15%是经验值,复杂项目可调高到20-25%。'"
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
```
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
常见配置参考:
|
|
1720
|
+
|
|
1721
|
+
| 团队 | 周期 | 月薪 | 总成本 |
|
|
1722
|
+
|------|------|------|--------|
|
|
1723
|
+
| 3人 | 3个月 | 15k | 196,650 |
|
|
1724
|
+
| 5人 | 6个月 | 15k | 655,500 |
|
|
1725
|
+
| 8人 | 9个月 | 20k | 1,989,000 |
|
|
1726
|
+
| 10人 | 12个月 | 25k | 4,140,000 |
|
|
1727
|
+
|
|
1728
|
+
风险缓冲15%是经验值,复杂项目可以调到20-25%。设备和办公成本按人头估算,不含服务器和第三方服务。
|
|
1729
|
+
|
|
1730
|
+
---
|
|
1731
|
+
|
|
1732
|
+
# 分压器计算器
|
|
1733
|
+
|
|
1734
|
+
URL: /zh-CN/examples/voltage-divider
|
|
1735
|
+
Raw Markdown: /zh-CN/examples/voltage-divider.md
|
|
1736
|
+
Source: content/examples/voltage-divider/zh-CN.md
|
|
1737
|
+
|
|
1738
|
+
# 分压器计算器
|
|
1739
|
+
|
|
1740
|
+
两个电阻串联,从中间引出电压——模拟电路中最简单的信号调理手段。做 ADC 电平转换、设定阈值电压、生成偏置电压,处处都在用。
|
|
1741
|
+
|
|
1742
|
+
## 核心公式
|
|
1743
|
+
|
|
1744
|
+
空载分压:$V_{out} = V_{in} \times \frac{R_2}{R_1 + R_2}$
|
|
1745
|
+
|
|
1746
|
+
带负载 $R_L$ 时:$V_{out,loaded} = V_{in} \times \frac{R_2 \parallel R_L}{R_1 + R_2 \parallel R_L}$
|
|
1747
|
+
|
|
1748
|
+
**关键经验**:分压器阻抗($R_1 \parallel R_2$)应至少 < 后级输入阻抗的 1/10。
|
|
1749
|
+
|
|
1750
|
+
```slex
|
|
1751
|
+
{
|
|
1752
|
+
slex: "0.1",
|
|
1753
|
+
namespace: "example_voltage_divider",
|
|
1754
|
+
g: {
|
|
1755
|
+
vin: 5, r1: 10000, r2: 10000, rl: 100000,
|
|
1756
|
+
rParallel: function () { return this.r2 * this.rl / (this.r2 + this.rl); },
|
|
1757
|
+
vout: function () { return this.vin * this.r2 / (this.r1 + this.r2); },
|
|
1758
|
+
voutLoaded: function () { return this.vin * this.rParallel() / (this.r1 + this.rParallel()); },
|
|
1759
|
+
errorPercent: function () { return Math.abs((this.voutLoaded() - this.vout()) / this.vout() * 100); },
|
|
1760
|
+
impedanceRatio: function () { var zout = this.r1 * this.r2 / (this.r1 + this.r2); return this.rl / zout; },
|
|
1761
|
+
loadWarning: function () { return this.impedanceRatio() < 10 ? "负载效应显著,增大 RL 或减小 R1/R2" : "分压器阻抗足够低"; }
|
|
1762
|
+
},
|
|
1763
|
+
layout: {
|
|
1764
|
+
"section:divider": {
|
|
1765
|
+
eyebrow: "计算器",
|
|
1766
|
+
title: "分压器计算器",
|
|
1767
|
+
subtitle: "两个电阻串联,从中间引出电压。",
|
|
1768
|
+
"card:divider": {
|
|
1769
|
+
title: "分压器计算器",
|
|
1770
|
+
"grid:params": {
|
|
1771
|
+
columns: 1, mdColumns: 2,
|
|
1772
|
+
"column:r1Field": { "input:r1": { label: "R1", "$value": "g.r1", type: "number", unit: "Ω", onchange: "g.r1 = Number($event || 0)" }, "slider:r1": { label: "R1", "$value": "g.r1", min: 100, max: 1000000, step: 100, unit: "Ω", onchange: "g.r1 = Number($event)" } },
|
|
1773
|
+
"column:r2Field": { "input:r2": { label: "R2", "$value": "g.r2", type: "number", unit: "Ω", onchange: "g.r2 = Number($event || 0)" }, "slider:r2": { label: "R2", "$value": "g.r2", min: 100, max: 1000000, step: 100, unit: "Ω", onchange: "g.r2 = Number($event)" } }
|
|
1774
|
+
},
|
|
1775
|
+
"grid:params2": {
|
|
1776
|
+
columns: 1, mdColumns: 2,
|
|
1777
|
+
"column:vinField": { "input:vin": { label: "输入电压 Vin", "$value": "g.vin", type: "number", unit: "V", onchange: "g.vin = Number($event || 0)" }, "slider:vin": { label: "Vin", "$value": "g.vin", min: 0.1, max: 48, step: 0.1, unit: "V", onchange: "g.vin = Number($event)" } },
|
|
1778
|
+
"column:rlField": { "input:rl": { label: "负载电阻 RL", "$value": "g.rl", type: "number", unit: "Ω", onchange: "g.rl = Number($event || 0)" }, "slider:rl": { label: "RL", "$value": "g.rl", min: 1000, max: 10000000, step: 1000, unit: "Ω", onchange: "g.rl = Number($event)" } }
|
|
1779
|
+
},
|
|
1780
|
+
"formula:eq1": { "$tex": "'V_{out} = ' + g.vin.toFixed(1) + ' \\\\times \\\\frac{' + (g.r2/1000).toFixed(1) + '\\\\text{k}}{' + (g.r1/1000).toFixed(1) + '\\\\text{k} + ' + (g.r2/1000).toFixed(1) + '\\\\text{k}} = ' + g.vout().toFixed(3) + '\\\\text{ V}'" },
|
|
1781
|
+
"grid:results": {
|
|
1782
|
+
columns: 1, mdColumns: 4,
|
|
1783
|
+
"stat:vout": { label: "空载 Vout", "$value": "g.vout().toFixed(3)", unit: "V" },
|
|
1784
|
+
"stat:voutLoaded": { label: "带载 Vout", "$value": "g.voutLoaded().toFixed(3)", unit: "V" },
|
|
1785
|
+
"stat:error": { label: "负载误差", "$value": "g.errorPercent().toFixed(2)", unit: "%" },
|
|
1786
|
+
"badge:ratio": { "$label": "g.impedanceRatio() < 10 ? '⚠ 负载效应' : '✓ 匹配良好'", "$tone": "g.impedanceRatio() < 10 ? 'warning' : 'success'" }
|
|
1787
|
+
},
|
|
1788
|
+
"callout:warning": { "$tone": "g.impedanceRatio() < 10 ? 'warning' : 'info'", "$text": "g.loadWarning()" }
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
|
|
1796
|
+
## 工程笔记
|
|
1797
|
+
|
|
1798
|
+
| R1 | R2 | Vout/Vin | 阻抗 |
|
|
1799
|
+
|----|----|---------|------|
|
|
1800
|
+
| 10k | 10k | 0.50 | 5k |
|
|
1801
|
+
| 10k | 3.3k | 0.25 | 2.5k |
|
|
1802
|
+
| 10k | 1k | 0.09 | 909 |
|
|
1803
|
+
| 33k | 10k | 0.23 | 7.7k |
|
|
1804
|
+
|
|
1805
|
+
- **ADC 应用**:分压器阻抗应 < ADC 输入阻抗的 1/10,必要时加缓冲运放
|
|
1806
|
+
- **高精度场景**:用 1% 精度电阻,R1 和 R2 用同一批次减少温漂差异
|
|
1807
|
+
- **功率限制**:$P = V^2/R$,小阻值分压器注意发热
|
|
1808
|
+
|
|
1809
|
+
---
|
|
1810
|
+
|
|
1811
|
+
# RC 低通滤波器
|
|
1812
|
+
|
|
1813
|
+
URL: /zh-CN/examples/rc-low-pass-filter
|
|
1814
|
+
Raw Markdown: /zh-CN/examples/rc-low-pass-filter.md
|
|
1815
|
+
Source: content/examples/rc-low-pass-filter/zh-CN.md
|
|
1816
|
+
|
|
1817
|
+
# RC 低通滤波器
|
|
1818
|
+
|
|
1819
|
+
RC 低通滤波器是模拟电路中最基础的无源滤波器——一个电阻加一个电容,就能把高频噪声从信号路径上滤掉。做 ADC 前端抗混叠、音频去嘶声、PWM 平滑输出,都会用到它。
|
|
1820
|
+
|
|
1821
|
+
## 核心公式
|
|
1822
|
+
|
|
1823
|
+
截止频率 $f_c$(-3dB 点):
|
|
1824
|
+
|
|
1825
|
+
$$f_c = \frac{1}{2\pi RC}$$
|
|
1826
|
+
|
|
1827
|
+
对于任意频率 $f$ 的输入信号,输出幅度增益为:
|
|
1828
|
+
|
|
1829
|
+
$$|H(f)| = \frac{1}{\sqrt{1 + (f/f_c)^2}}$$
|
|
1830
|
+
|
|
1831
|
+
## 参数区
|
|
1832
|
+
|
|
1833
|
+
```slex
|
|
1834
|
+
{
|
|
1835
|
+
slex: "0.1",
|
|
1836
|
+
namespace: "example_rc_low_pass_filter",
|
|
1837
|
+
g: {
|
|
1838
|
+
r: 10000,
|
|
1839
|
+
c: 100,
|
|
1840
|
+
f: 1000,
|
|
1841
|
+
cutoff: function () { return 1 / (2 * Math.PI * this.r * this.c * 1e-9); },
|
|
1842
|
+
gain: function () { return 1 / Math.sqrt(1 + Math.pow(this.f / this.cutoff(), 2)); },
|
|
1843
|
+
gainDb: function () { return (20 * Math.log10(this.gain())).toFixed(1); },
|
|
1844
|
+
regimeLabel: function () { return this.f < this.cutoff() * 0.1 ? "通带" : this.f > this.cutoff() * 10 ? "阻带" : "过渡带"; }
|
|
1845
|
+
},
|
|
1846
|
+
layout: {
|
|
1847
|
+
"section:params": {
|
|
1848
|
+
eyebrow: "计算器",
|
|
1849
|
+
title: "RC 低通滤波器",
|
|
1850
|
+
subtitle: "一个电阻加一个电容,把高频噪声滤掉。",
|
|
1851
|
+
"card:params": {
|
|
1852
|
+
title: "参数输入",
|
|
1853
|
+
"grid:inputs": {
|
|
1854
|
+
columns: 1, mdColumns: 2,
|
|
1855
|
+
"column:rField": { "input:r": { label: "电阻 R", "$value": "g.r", type: "number", unit: "Ω", onchange: "g.r = Number($event || 0)" }, "slider:r": { label: "R", "$value": "g.r", min: 100, max: 100000, step: 100, unit: "Ω", onchange: "g.r = Number($event)" } },
|
|
1856
|
+
"column:cField": { "input:c": { label: "电容 C", "$value": "g.c", type: "number", unit: "nF", onchange: "g.c = Number($event || 0)" }, "slider:c": { label: "C", "$value": "g.c", min: 1, max: 1000, step: 1, unit: "nF", onchange: "g.c = Number($event)" } },
|
|
1857
|
+
"column:fField": { "input:f": { label: "输入频率 f", "$value": "g.f", type: "number", unit: "Hz", onchange: "g.f = Number($event || 0)" }, "slider:f": { label: "f", "$value": "g.f", min: 1, max: 100000, step: 1, unit: "Hz", onchange: "g.f = Number($event)" } }
|
|
1858
|
+
},
|
|
1859
|
+
"stat:fc": { label: "截止频率", "$value": "g.cutoff().toFixed(1)", unit: "Hz" },
|
|
1860
|
+
"badge:regime": { "$label": "g.regimeLabel()", "$tone": "g.f < g.cutoff() * 0.1 ? 'success' : g.f > g.cutoff() * 10 ? 'danger' : 'warning'" }
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
## 计算结果
|
|
1869
|
+
|
|
1870
|
+
```slex
|
|
1871
|
+
{
|
|
1872
|
+
slex: "0.1",
|
|
1873
|
+
namespace: "example_rc_low_pass_filter",
|
|
1874
|
+
layout: {
|
|
1875
|
+
"card:results": {
|
|
1876
|
+
title: "计算结果",
|
|
1877
|
+
"formula:fc_eq": { "$tex": "'f_c = \\\\frac{1}{2\\\\pi \\\\times ' + (g.r/1000).toFixed(1) + 'k\\\\Omega \\\\times ' + g.c + '\\\\text{nF}} = ' + g.cutoff().toFixed(1) + '\\\\text{ Hz}'" },
|
|
1878
|
+
"stat:gain_val": { label: "幅值增益 |H(f)|", "$value": "g.gain().toFixed(4)" },
|
|
1879
|
+
"stat:gain_db": { label: "增益", "$value": "g.gainDb()", unit: "dB" },
|
|
1880
|
+
"callout:verdict": { "$tone": "g.f < g.cutoff() * 0.1 ? 'success' : g.f > g.cutoff() * 10 ? 'danger' : 'warning'", "$text": "g.f < g.cutoff() * 0.1 ? '信号完整通过,衰减 < 0.04 dB。' : g.f > g.cutoff() * 10 ? '信号被强烈衰减超过 20 dB,滤波器有效工作。' : '信号处于过渡带,衰减约 ' + (-20 * Math.log10(1 / Math.sqrt(1 + Math.pow(g.f / g.cutoff(), 2)))).toFixed(1) + ' dB。'" }
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
## 选型参考
|
|
1888
|
+
|
|
1889
|
+
下表是常见场景下的经验参数组合。把截止频率设为目标信号最高频率的 5-10 倍,可以保证通带平坦。
|
|
1890
|
+
|
|
1891
|
+
```slex
|
|
1892
|
+
{
|
|
1893
|
+
slex: "0.1",
|
|
1894
|
+
namespace: "example_rc_low_pass_filter",
|
|
1895
|
+
layout: {
|
|
1896
|
+
"card:selection": {
|
|
1897
|
+
title: "选型建议",
|
|
1898
|
+
"table:guide": {
|
|
1899
|
+
columns: ["R", "C", "fc", "典型用途"],
|
|
1900
|
+
rows: [
|
|
1901
|
+
["1 kΩ", "100 nF", "1592 Hz", "音频低通"],
|
|
1902
|
+
["10 kΩ", "100 nF", "159 Hz", "ADC 抗混叠"],
|
|
1903
|
+
["100 kΩ", "10 nF", "159 Hz", "PWM 平滑"],
|
|
1904
|
+
["1 kΩ", "1 µF", "159 Hz", "电源纹波滤波"],
|
|
1905
|
+
["10 kΩ", "1 nF", "15915 Hz", "高频噪声抑制"]
|
|
1906
|
+
]
|
|
1907
|
+
},
|
|
1908
|
+
"callout:tip": { "$tone": "g.cutoff() < 100 ? 'info' : g.cutoff() > 10000 ? 'warning' : 'success'", "$text": "g.cutoff() < 100 ? '低截止频率适合电源滤波和慢信号。' : g.cutoff() > 10000 ? '高截止频率可能无法有效滤除高频噪声。' : '当前截止频率适合大多数应用场景。'" }
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
## 工程笔记
|
|
1916
|
+
|
|
1917
|
+
- **R 取值**:太大会增加输出阻抗和后级负载效应;太大会增大热噪声。1kΩ–100kΩ 是常用范围
|
|
1918
|
+
- **C 取值**:nF 级 NP0/C0G 陶瓷电容温漂小、精度高;超过 100nF 考虑薄膜电容
|
|
1919
|
+
- **负载效应**:后级输入阻抗应至少 > 10 × R,否则 $f_c$ 会偏移
|
|
1920
|
+
- **级联**:两级 RC 串联可得到 -40dB/dec 的二阶滚降,但截止频率会降低到约 $0.37f_c$
|
|
1921
|
+
|
|
1922
|
+
| $f/f_c$ | 增益 | 衰减 |
|
|
1923
|
+
|:---:|:---:|:---:|
|
|
1924
|
+
| 0.1 | 0.995 | -0.04 dB |
|
|
1925
|
+
| 1.0 | 0.707 | -3 dB |
|
|
1926
|
+
| 5.0 | 0.196 | -14.2 dB |
|
|
1927
|
+
| 10.0 | 0.100 | -20 dB |
|
|
1928
|
+
|
|
1929
|
+
---
|
|
1930
|
+
|
|
1931
|
+
# 波特率误差计算器
|
|
1932
|
+
|
|
1933
|
+
URL: /zh-CN/examples/baud-rate-calculator
|
|
1934
|
+
Raw Markdown: /zh-CN/examples/baud-rate-calculator.md
|
|
1935
|
+
Source: content/examples/baud-rate-calculator/zh-CN.md
|
|
1936
|
+
|
|
1937
|
+
# 波特率误差计算器
|
|
1938
|
+
|
|
1939
|
+
嵌入式开发中,UART 波特率由系统时钟分频得到。晶振频率不能整除目标波特率时会产生误差,误差超过 ±2% 通信就可能丢帧。
|
|
1940
|
+
|
|
1941
|
+
## 核心公式
|
|
1942
|
+
|
|
1943
|
+
$$Error = \frac{|BR_{actual} - BR_{target}|}{BR_{target}} \times 100\%$$
|
|
1944
|
+
|
|
1945
|
+
实际波特率 $BR_{actual} = f_{osc} / (16 \times N)$,其中 $N = \text{round}(f_{osc} / (16 \times BR_{target}))$ 为分频寄存器整数。
|
|
1946
|
+
|
|
1947
|
+
```slex
|
|
1948
|
+
{
|
|
1949
|
+
slex: "0.1",
|
|
1950
|
+
namespace: "example_baud_rate_calculator",
|
|
1951
|
+
g: {
|
|
1952
|
+
freq: 8,
|
|
1953
|
+
freqUnit: "MHz",
|
|
1954
|
+
baud: 115200,
|
|
1955
|
+
freqHz: function () { var m = { MHz: 1e6, kHz: 1e3, Hz: 1 }; return this.freq * (m[this.freqUnit] || 1e6); },
|
|
1956
|
+
divisor: function () { return this.freqHz() / (16 * this.baud); },
|
|
1957
|
+
regValue: function () { return Math.round(this.divisor()); },
|
|
1958
|
+
actualBaud: function () { return this.freqHz() / (16 * this.regValue()); },
|
|
1959
|
+
error: function () { return Math.abs((this.actualBaud() - this.baud) / this.baud * 100); },
|
|
1960
|
+
reliability: function () { var e = this.error(); return e < 0.5 ? "优秀" : e < 2 ? "可接受" : e < 5 ? "有风险" : "不可用"; },
|
|
1961
|
+
tone: function () { var e = this.error(); return e < 0.5 ? "success" : e < 2 ? "info" : e < 5 ? "warning" : "danger"; }
|
|
1962
|
+
},
|
|
1963
|
+
layout: {
|
|
1964
|
+
"section:baud": {
|
|
1965
|
+
eyebrow: "计算器",
|
|
1966
|
+
title: "波特率误差计算器",
|
|
1967
|
+
subtitle: "输入晶振频率和目标波特率,计算误差。",
|
|
1968
|
+
"card:baud": {
|
|
1969
|
+
title: "波特率误差计算器",
|
|
1970
|
+
"grid:params": {
|
|
1971
|
+
columns: 1, mdColumns: 2,
|
|
1972
|
+
"column:freqField": {
|
|
1973
|
+
"input:freq": { label: "晶振频率", "$value": "g.freq", type: "number", unit: "MHz", onchange: "g.freq = Number($event || 0)" }
|
|
1974
|
+
},
|
|
1975
|
+
"column:baudField": {
|
|
1976
|
+
"select:baud": { label: "目标波特率", "$value": "g.baud", options: [{ label: "9600", value: 9600 }, { label: "19200", value: 19200 }, { label: "38400", value: 38400 }, { label: "57600", value: 57600 }, { label: "115200", value: 115200 }, { label: "230400", value: 230400 }, { label: "460800", value: 460800 }], onchange: "g.baud = Number($event)" }
|
|
1977
|
+
},
|
|
1978
|
+
"column:unitField": {
|
|
1979
|
+
"select:unit": { label: "频率单位", "$value": "g.freqUnit", options: [{ label: "MHz", value: "MHz" }, { label: "kHz", value: "kHz" }, { label: "Hz", value: "Hz" }], onchange: "g.freqUnit = String($event)" }
|
|
1980
|
+
},
|
|
1981
|
+
"column:divField": {
|
|
1982
|
+
"stat:divisor": { label: "分频比", "$value": "g.divisor().toFixed(3)" }
|
|
1983
|
+
}
|
|
1984
|
+
},
|
|
1985
|
+
"formula:equation": { "$tex": "'\\\\text{Error} = \\\\frac{|' + g.actualBaud().toFixed(0) + ' - ' + g.baud + '|}{' + g.baud + '} \\\\times 100\\\\% = ' + g.error().toFixed(2) + '\\\\%'" },
|
|
1986
|
+
"grid:results": {
|
|
1987
|
+
columns: 1, mdColumns: 4,
|
|
1988
|
+
"stat:actualBaud": { label: "实际波特率", "$value": "g.actualBaud().toFixed(0)", unit: "bps" },
|
|
1989
|
+
"stat:error": { label: "误差", "$value": "g.error().toFixed(2)", unit: "%" },
|
|
1990
|
+
"stat:reliability": { label: "可靠性", "$value": "g.reliability()", "$tone": "g.tone()" },
|
|
1991
|
+
"stat:regValue": { label: "寄存器值", "$value": "g.regValue()" }
|
|
1992
|
+
},
|
|
1993
|
+
"callout:advice": { "$tone": "g.tone()", "$text": "g.error() < 0.5 ? '误差极小,通信可靠。' : g.error() < 2 ? '误差在可接受范围内(<2%),绝大多数场景可用。' : g.error() < 5 ? '误差偏大,长帧通信可能失败,建议更换晶振或降低波特率。' : '误差过大,通信不可靠。请选择能整除的晶振频率。'" }
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
## 常用晶振频率与波特率误差表
|
|
2002
|
+
|
|
2003
|
+
| 晶振 | 9600 | 19200 | 38400 | 57600 | 115200 |
|
|
2004
|
+
|------|------|-------|-------|-------|--------|
|
|
2005
|
+
| 1.8432 MHz | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
2006
|
+
| 3.6864 MHz | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
2007
|
+
| 4.0000 MHz | 8.51 | 8.51 | 8.51 | 8.51 | 8.51 |
|
|
2008
|
+
| 7.3728 MHz | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
2009
|
+
| 8.0000 MHz | 8.51 | 8.51 | 8.51 | 8.51 | 8.51 |
|
|
2010
|
+
| 11.0592 MHz | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
2011
|
+
| 14.7456 MHz | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
2012
|
+
| 16.0000 MHz | 8.51 | 8.51 | 8.51 | 8.51 | 8.51 |
|
|
2013
|
+
|
|
2014
|
+
**工程笔记**:
|
|
2015
|
+
|
|
2016
|
+
- 零误差的关键:晶振频率能被 $16 \times BR$ 整除
|
|
2017
|
+
- **11.0592 MHz** 是 UART 最经典的选择,对所有标准波特率零误差
|
|
2018
|
+
- **7.3728 MHz** 和 **14.7456 MHz** 同样零误差
|
|
2019
|
+
- 错误 < 2% 在实际工程中通常可工作,但高速长帧场景建议 < 1%
|
|
2020
|
+
|
|
2021
|
+
---
|
|
2022
|
+
|
|
2023
|
+
# 搜索与过滤表格
|
|
2024
|
+
|
|
2025
|
+
URL: /zh-CN/examples/search-filter-table
|
|
2026
|
+
Raw Markdown: /zh-CN/examples/search-filter-table.md
|
|
2027
|
+
Source: content/examples/search-filter-table/zh-CN.md
|
|
2028
|
+
|
|
2029
|
+
# 搜索与过滤表格
|
|
2030
|
+
|
|
2031
|
+
文档里放一个静态表格很常见,但加上搜索和过滤就会变成立即可用的工具。这里用 `input` 绑搜索关键词 + 动态过滤 + `collapsible` 展开行详情。
|
|
2032
|
+
|
|
2033
|
+
```slex
|
|
2034
|
+
{
|
|
2035
|
+
slex: "0.1",
|
|
2036
|
+
namespace: "example_search_filter",
|
|
2037
|
+
g: {
|
|
2038
|
+
query: "", selected: "", selectedItem: null,
|
|
2039
|
+
allItems: [
|
|
2040
|
+
{ id: "btn-1", name: "Button 按钮", category: "Action", status: "ready", notes: "支持 variant 和 size。" },
|
|
2041
|
+
{ id: "card-1", name: "Card 卡片", category: "Layout", status: "ready", notes: "最常用的分组容器。" },
|
|
2042
|
+
{ id: "input-1", name: "Input 输入框", category: "Input", status: "ready", notes: "支持 type、unit 和 placeholder。" },
|
|
2043
|
+
{ id: "tabs-1", name: "Tabs 选项卡", category: "Navigation", status: "ready", notes: "支持水平和垂直方向。" },
|
|
2044
|
+
{ id: "table-1", name: "Table 表格", category: "Display", status: "ready", notes: "columns 数组 + rows 数组。" },
|
|
2045
|
+
{ id: "formula-1", name: "Formula 公式", category: "Display", status: "ready", notes: "依赖 KaTeX 渲染 LaTeX。" },
|
|
2046
|
+
{ id: "toast-1", name: "Toast 通知", category: "Feedback", status: "experimental", notes: "支持 type 变体。" },
|
|
2047
|
+
{ id: "secure-1", name: "Secure 安全运行时", category: "Tooling", status: "draft", notes: "基于 iframe 沙箱。" }
|
|
2048
|
+
],
|
|
2049
|
+
filtered: function () {
|
|
2050
|
+
var q = this.query.toLowerCase();
|
|
2051
|
+
if (!q) return this.allItems;
|
|
2052
|
+
return this.allItems.filter(function (item) { return item.name.toLowerCase().includes(q) || item.category.toLowerCase().includes(q); });
|
|
2053
|
+
},
|
|
2054
|
+
matched: function () { return this.filtered().length; },
|
|
2055
|
+
select: function (id) { this.selected = this.selected === id ? "" : id; }
|
|
2056
|
+
},
|
|
2057
|
+
layout: {
|
|
2058
|
+
"section:search": {
|
|
2059
|
+
eyebrow: "组件查询",
|
|
2060
|
+
title: "搜索与过滤表格",
|
|
2061
|
+
subtitle: "输入关键词实时过滤组件列表,点击任意行查看详情。",
|
|
2062
|
+
"input:query": { label: "搜索组件", "$value": "g.query", type: "text", placeholder: "输入名称或分类关键词...", onchange: "g.query = String($event || '')" },
|
|
2063
|
+
"stat:matched": { "$label": "'匹配结果'", "$value": "g.matched()", "$unit": "'/' + g.allItems.length + ' 项'" },
|
|
2064
|
+
"table:list": {
|
|
2065
|
+
columns: ["名称", "分类", "状态", ""],
|
|
2066
|
+
"$rows": "g.filtered().map(function(item) { return [item.name, item.category, item.status, item.id]; })"
|
|
2067
|
+
},
|
|
2068
|
+
"callout:empty": {
|
|
2069
|
+
"$if": "g.matched() === 0",
|
|
2070
|
+
tone: "warning",
|
|
2071
|
+
"$text": "'未找到匹配「' + g.query + '」的组件。'"
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
```
|
|
2077
|
+
|
|
2078
|
+
**这个示例的核心技巧:**
|
|
2079
|
+
|
|
2080
|
+
- `input` 的 `onchange` 更新 `g.query` → 触发 `g.filtered()` 重新计算
|
|
2081
|
+
- `g.filtered()` 用 `filter` 过滤 `allItems` 数组
|
|
2082
|
+
- `"$rows"` 动态计算行的二维数组——每个 item 映射为一行
|
|
2083
|
+
- `g.matched()` 返回过滤后数量,用于 stat 和 callout 的条件显示
|
|
2084
|
+
- `$if` 在无匹配时显示空结果提示
|
|
2085
|
+
|
|
2086
|
+
这就是 "搜索框 → 动态表格" 的基础范式。相比硬编码的 table rows,动态 rows 可以随输入实时变化。
|
|
2087
|
+
|
|
2088
|
+
---
|
|
2089
|
+
|
|
2090
|
+
## 进阶玩法:展开行详情
|
|
2091
|
+
|
|
2092
|
+
```slex
|
|
2093
|
+
{
|
|
2094
|
+
slex: "0.1",
|
|
2095
|
+
namespace: "example_search_filter",
|
|
2096
|
+
layout: {
|
|
2097
|
+
"section:detail": {
|
|
2098
|
+
eyebrow: "组件详情",
|
|
2099
|
+
title: "选中查看详情",
|
|
2100
|
+
"accordion:faq": {
|
|
2101
|
+
multiple: true,
|
|
2102
|
+
items: [
|
|
2103
|
+
{ value: "btn-1", label: "Button 按钮", icon: "cursor-click", content: "触发操作的基础组件。支持 variant (solid/outline/ghost) 和 size (sm/md/lg) 等变体。" },
|
|
2104
|
+
{ value: "card-1", label: "Card 卡片", icon: "rectangle", content: "内容分组容器。最常用的布局组件之一,比 section 更轻量。" },
|
|
2105
|
+
{ value: "input-1", label: "Input 输入框", icon: "textbox", content: "单行或数字输入。支持 type、unit、placeholder 等属性。" }
|
|
2106
|
+
]
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
```
|
|
2112
|
+
|
|
2113
|
+
## 为什么动态 rows 值得学?
|
|
2114
|
+
|
|
2115
|
+
在静态表格中,rows 是硬编码的二维数组。一旦数据量增加,或者需要按条件筛选,静态 rows 就力不从心了。`$rows` 允许你在 g 方法中动态生成行数据——这对于:
|
|
2116
|
+
|
|
2117
|
+
- **文档内数据浏览**:目录、API 列表、配置项
|
|
2118
|
+
- **AI 输出展示**:大模型生成的表格结果需要可筛选
|
|
2119
|
+
- **知识库查询**:搜索内部组件/API/术语
|
|
2120
|
+
|
|
2121
|
+
都非常实用。
|
|
2122
|
+
|
|
2123
|
+
---
|
|
2124
|
+
|
|
2125
|
+
---
|
|
2126
|
+
|
|
2127
|
+
# 项目仪表盘
|
|
2128
|
+
|
|
2129
|
+
URL: /zh-CN/examples/project-dashboard
|
|
2130
|
+
Raw Markdown: /zh-CN/examples/project-dashboard.md
|
|
2131
|
+
Source: content/examples/project-dashboard/zh-CN.md
|
|
2132
|
+
|
|
2133
|
+
# 项目仪表盘
|
|
2134
|
+
|
|
2135
|
+
真实项目需要一览全局——不是单一张计算卡片,而是一个**仪表盘**。用 `section` 做区块分组,`grid` 做多列布局,每个 card 展示一个关注领域。
|
|
2136
|
+
|
|
2137
|
+
```slex
|
|
2138
|
+
{
|
|
2139
|
+
slex: "0.1",
|
|
2140
|
+
namespace: "example_dashboard",
|
|
2141
|
+
g: {
|
|
2142
|
+
sprint: 8, team: 6, sprintProgress: 72, bugs: 12, resolved: 9,
|
|
2143
|
+
scope: "on-track",
|
|
2144
|
+
bugRate: function () { return this.resolved / this.bugs * 100; },
|
|
2145
|
+
teamLoad: function () { return Math.min(100, this.sprint / this.team * 20); },
|
|
2146
|
+
scopeStatus: function () { return this.scope === "on-track" ? "正常" : this.scope === "at-risk" ? "有风险" : "已偏离"; }
|
|
2147
|
+
},
|
|
2148
|
+
layout: {
|
|
2149
|
+
"section:dashboard": {
|
|
2150
|
+
eyebrow: "Sprint 概览",
|
|
2151
|
+
title: "项目仪表盘 · Sprint 24",
|
|
2152
|
+
subtitle: "数据截止今日 10:00 AM。拖滑动条查看不同假想数据的效果。",
|
|
2153
|
+
"grid:row1": {
|
|
2154
|
+
columns: 1, mdColumns: 3,
|
|
2155
|
+
"card:sprint": {
|
|
2156
|
+
title: "Sprint 进度",
|
|
2157
|
+
"progress:sp": { label: "完成度", "$value": "g.sprintProgress" },
|
|
2158
|
+
"stat:scope": { "$label": "范围状态", "$value": "g.scopeStatus()" },
|
|
2159
|
+
"badge:flag": { "$label": "g.scope === 'on-track' ? '正常' : g.scope === 'at-risk' ? '⚠ 预警' : '🚨 偏离'", "$tone": "g.scope === 'on-track' ? 'success' : g.scope === 'at-risk' ? 'warning' : 'danger'" }
|
|
2160
|
+
},
|
|
2161
|
+
"card:quality": {
|
|
2162
|
+
title: "质量指标",
|
|
2163
|
+
"stat:bugs": { label: "剩余缺陷", value: "3", unit: "个" },
|
|
2164
|
+
"progress:bugFix": { label: "修复率", "$value": "g.bugRate()" },
|
|
2165
|
+
"badge:trend": { label: "趋势:改善中", tone: "success" }
|
|
2166
|
+
},
|
|
2167
|
+
"card:team": {
|
|
2168
|
+
title: "团队健康度",
|
|
2169
|
+
"stat:members": { label: "团队成员", value: "6", unit: "人" },
|
|
2170
|
+
"progress:load": { label: "负载指数", "$value": "g.teamLoad()" },
|
|
2171
|
+
"callout:note": { "$tone": "g.teamLoad() > 80 ? 'warning' : 'info'", "$text": "g.teamLoad() > 80 ? '负载偏高,建议调整任务分配。' : '团队负载在健康范围内。'" }
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
"card:detail": {
|
|
2175
|
+
title: "详细数据",
|
|
2176
|
+
"table:tasks": {
|
|
2177
|
+
columns: ["任务", "负责人", "状态", "耗时"],
|
|
2178
|
+
rows: [["API 重构", "张三", "已完成", "3d"], ["前端组件", "李四", "进行中", "2d"], ["集成测试", "王五", "代码审查", "1.5d"], ["性能优化", "赵六", "待开始", "—"]]
|
|
2179
|
+
},
|
|
2180
|
+
"callout:help": { "$tone": "g.bugRate() < 80 ? 'warning' : 'success'", "$text": "g.bugRate() < 80 ? '缺陷修复率低于80%,建议优先处理高优先级缺陷。' : '缺陷修复率良好,项目质量可控。'" }
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
```
|
|
2186
|
+
|
|
2187
|
+
**仪表盘的设计思路:**
|
|
2188
|
+
|
|
2189
|
+
- 用 `section` 做顶层分组(带 eyebrow 和 subtitle,语义清晰)
|
|
2190
|
+
- 用 `grid` 做多列布局,每个格子放一个 `card`
|
|
2191
|
+
- 每个 card 内放不同关注维度的 stat / progress / badge
|
|
2192
|
+
- 底部放一个详情 card,展示表格数据(任务列表)
|
|
2193
|
+
- 通过 `$text` 和 `$tone` 在 callout 中做条件提示
|
|
2194
|
+
|
|
2195
|
+
|
|
2196
|
+
这个模式适用于:技术 Leader 周报面板、发布质检看板、团队 OKR 追踪、SRE 服务大盘。
|
|
2197
|
+
|
|
2198
|
+
---
|
|
2199
|
+
|
|
2200
|
+
# ToolHost 表单提问
|
|
2201
|
+
|
|
2202
|
+
URL: /zh-CN/examples/form-wizard-steps
|
|
2203
|
+
Raw Markdown: /zh-CN/examples/form-wizard-steps.md
|
|
2204
|
+
Source: content/examples/form-wizard-steps/zh-CN.md
|
|
2205
|
+
|
|
2206
|
+
# ToolHost 表单提问
|
|
2207
|
+
|
|
2208
|
+
AI 对话过程中,有时需要收集用户信息——创建项目、配置服务、提交工单。这时候 AI 会弹出一个表单卡片,用户填写后提交,AI 继续处理。
|
|
2209
|
+
|
|
2210
|
+
```slex
|
|
2211
|
+
{
|
|
2212
|
+
slex: "0.1",
|
|
2213
|
+
namespace: "example_form_wizard",
|
|
2214
|
+
g: {
|
|
2215
|
+
submitted: false,
|
|
2216
|
+
formData: null,
|
|
2217
|
+
fields: { name: "", description: "", type: "web", priority: "medium" },
|
|
2218
|
+
submit: function () {
|
|
2219
|
+
this.submitted = true;
|
|
2220
|
+
this.formData = { name: this.fields.name, description: this.fields.description, type: this.fields.type, priority: this.fields.priority, timestamp: new Date().toLocaleString() };
|
|
2221
|
+
}
|
|
2222
|
+
},
|
|
2223
|
+
layout: {
|
|
2224
|
+
"section:toolhost": {
|
|
2225
|
+
eyebrow: "ToolHost · 表单提问",
|
|
2226
|
+
title: "AI 需要收集信息",
|
|
2227
|
+
subtitle: "AI 弹出表单,用户填写后提交,AI 继续处理。",
|
|
2228
|
+
"callout:context": {
|
|
2229
|
+
tone: "info",
|
|
2230
|
+
text: "AI:我需要为你创建一个新项目,请填写以下信息。"
|
|
2231
|
+
},
|
|
2232
|
+
"grid:fields": {
|
|
2233
|
+
columns: 1, mdColumns: 2,
|
|
2234
|
+
"input:name": { label: "项目名称", "$value": "g.fields.name", placeholder: "my-project", onchange: "g.fields.name = String($event || '')" },
|
|
2235
|
+
"input:description": { label: "项目描述", "$value": "g.fields.description", placeholder: "简短描述项目用途", onchange: "g.fields.description = String($event || '')" },
|
|
2236
|
+
"select:type": {
|
|
2237
|
+
label: "项目类型",
|
|
2238
|
+
"$value": "g.fields.type",
|
|
2239
|
+
options: [
|
|
2240
|
+
{ label: "Web 应用", value: "web" },
|
|
2241
|
+
{ label: "API 服务", value: "api" },
|
|
2242
|
+
{ label: "CLI 工具", value: "cli" }
|
|
2243
|
+
],
|
|
2244
|
+
onchange: "g.fields.type = String($event)"
|
|
2245
|
+
},
|
|
2246
|
+
"select:priority": {
|
|
2247
|
+
label: "优先级",
|
|
2248
|
+
"$value": "g.fields.priority",
|
|
2249
|
+
options: [
|
|
2250
|
+
{ label: "低", value: "low" },
|
|
2251
|
+
{ label: "中", value: "medium" },
|
|
2252
|
+
{ label: "高", value: "high" }
|
|
2253
|
+
],
|
|
2254
|
+
onchange: "g.fields.priority = String($event)"
|
|
2255
|
+
}
|
|
2256
|
+
},
|
|
2257
|
+
"grid:actions": {
|
|
2258
|
+
columns: 2,
|
|
2259
|
+
"button:submit": { label: "提交", onclick: "g.submit()" },
|
|
2260
|
+
"button:skip": { label: "跳过" }
|
|
2261
|
+
},
|
|
2262
|
+
"callout:result": {
|
|
2263
|
+
"$tone": "g.submitted ? 'success' : 'info'",
|
|
2264
|
+
"$text": "g.submitted ? '已提交:' + g.formData.name + '(' + g.formData.type + ')' : '等待用户填写...'"
|
|
2265
|
+
},
|
|
2266
|
+
"code-block:toolresult": {
|
|
2267
|
+
title: "返回给 AI 的 ToolResult",
|
|
2268
|
+
language: "json",
|
|
2269
|
+
"$code": "g.submitted ? JSON.stringify({ toolCallId: 'call_abc123', toolName: 'create-project', status: 'submitted', value: g.formData }, null, 2) : '// 提交后显示 ToolResult'"
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
```
|
|
2275
|
+
|
|
2276
|
+
---
|
|
2277
|
+
|
|
2278
|
+
---
|
|
2279
|
+
|
|
2280
|
+
# 技术选型评估
|
|
2281
|
+
|
|
2282
|
+
URL: /zh-CN/examples/tech-selection-evaluator
|
|
2283
|
+
Raw Markdown: /zh-CN/examples/tech-selection-evaluator.md
|
|
2284
|
+
Source: content/examples/tech-selection-evaluator/zh-CN.md
|
|
2285
|
+
|
|
2286
|
+
# 技术选型评估
|
|
2287
|
+
|
|
2288
|
+
团队要选前端框架,React、Vue、Svelte、Angular,各有各的好。性能、生态、学习曲线、维护成本——怎么量化比较?这里用跨 fence 联动,选一个技术栈,下面的评分和结论自动跟着变。
|
|
2289
|
+
|
|
2290
|
+
```slex
|
|
2291
|
+
{
|
|
2292
|
+
slex: "0.1",
|
|
2293
|
+
namespace: "example_tech_selection",
|
|
2294
|
+
g: {
|
|
2295
|
+
tech: "react",
|
|
2296
|
+
performance: 85,
|
|
2297
|
+
ecosystem: 95,
|
|
2298
|
+
learning: 70,
|
|
2299
|
+
maintenance: 80,
|
|
2300
|
+
techLabel: function () { return { react: "React", vue: "Vue", svelte: "Svelte", angular: "Angular" }[this.tech] || this.tech; },
|
|
2301
|
+
totalScore: function () { return (this.performance * 0.3 + this.ecosystem * 0.25 + this.learning * 0.2 + this.maintenance * 0.25).toFixed(1); },
|
|
2302
|
+
recommendation: function () { var s = parseFloat(this.totalScore()); return s >= 85 ? "强烈推荐" : s >= 75 ? "推荐" : s >= 60 ? "可以考虑" : "不推荐"; },
|
|
2303
|
+
riskLevel: function () { var s = parseFloat(this.totalScore()); return s >= 85 ? "低" : s >= 75 ? "中" : "高"; },
|
|
2304
|
+
scores: function () {
|
|
2305
|
+
var data = {
|
|
2306
|
+
react: { performance: 85, ecosystem: 95, learning: 70, maintenance: 80 },
|
|
2307
|
+
vue: { performance: 80, ecosystem: 85, learning: 85, maintenance: 85 },
|
|
2308
|
+
svelte: { performance: 95, ecosystem: 70, learning: 90, maintenance: 90 },
|
|
2309
|
+
angular: { performance: 80, ecosystem: 80, learning: 60, maintenance: 75 }
|
|
2310
|
+
};
|
|
2311
|
+
return data[this.tech] || data.react;
|
|
2312
|
+
}
|
|
2313
|
+
},
|
|
2314
|
+
layout: {
|
|
2315
|
+
"section:select": {
|
|
2316
|
+
eyebrow: "决策辅助",
|
|
2317
|
+
title: "技术选型评估",
|
|
2318
|
+
subtitle: "选一个技术栈,下面的评分和结论自动跟着变。",
|
|
2319
|
+
"card:select": {
|
|
2320
|
+
title: "选择技术栈",
|
|
2321
|
+
"select:tech": {
|
|
2322
|
+
label: "技术栈",
|
|
2323
|
+
"$value": "g.tech",
|
|
2324
|
+
options: [
|
|
2325
|
+
{ label: "React", value: "react" },
|
|
2326
|
+
{ label: "Vue", value: "vue" },
|
|
2327
|
+
{ label: "Svelte", value: "svelte" },
|
|
2328
|
+
{ label: "Angular", value: "angular" }
|
|
2329
|
+
],
|
|
2330
|
+
onchange: "g.tech = String($event); var s = g.scores(); g.performance = s.performance; g.ecosystem = s.ecosystem; g.learning = s.learning; g.maintenance = s.maintenance;"
|
|
2331
|
+
},
|
|
2332
|
+
"badge:current": {
|
|
2333
|
+
"$label": "'当前:' + g.techLabel()",
|
|
2334
|
+
tone: "info"
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
```
|
|
2341
|
+
|
|
2342
|
+
选了React,觉得性能分太低?拖一下滑块调整,下面的推荐和风险等级实时更新。这就是跨 fence 联动的价值——三个独立的代码块,共享同一份状态。
|
|
2343
|
+
|
|
2344
|
+
```slex
|
|
2345
|
+
{
|
|
2346
|
+
slex: "0.1",
|
|
2347
|
+
namespace: "example_tech_selection",
|
|
2348
|
+
layout: {
|
|
2349
|
+
"card:scoring": {
|
|
2350
|
+
title: "评分调整",
|
|
2351
|
+
"grid:sliders": {
|
|
2352
|
+
columns: 1, mdColumns: 2,
|
|
2353
|
+
"column:left": {
|
|
2354
|
+
"slider:performance": { label: "性能(30%)", "$value": "g.performance", min: 0, max: 100, step: 5, onchange: "g.performance = Number($event)" },
|
|
2355
|
+
"slider:ecosystem": { label: "生态系统(25%)", "$value": "g.ecosystem", min: 0, max: 100, step: 5, onchange: "g.ecosystem = Number($event)" }
|
|
2356
|
+
},
|
|
2357
|
+
"column:right": {
|
|
2358
|
+
"slider:learning": { label: "学习曲线(20%)", "$value": "g.learning", min: 0, max: 100, step: 5, onchange: "g.learning = Number($event)" },
|
|
2359
|
+
"slider:maintenance": { label: "维护成本(25%)", "$value": "g.maintenance", min: 0, max: 100, step: 5, onchange: "g.maintenance = Number($event)" }
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
"grid:weights": {
|
|
2363
|
+
columns: 1, mdColumns: 4,
|
|
2364
|
+
"stat:perf": { label: "性能(30%)", "$value": "g.performance", unit: "分" },
|
|
2365
|
+
"stat:eco": { label: "生态(25%)", "$value": "g.ecosystem", unit: "分" },
|
|
2366
|
+
"stat:learn": { label: "学习(20%)", "$value": "g.learning", unit: "分" },
|
|
2367
|
+
"stat:maint": { label: "维护(25%)", "$value": "g.maintenance", unit: "分" }
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
```
|
|
2373
|
+
|
|
2374
|
+
```slex
|
|
2375
|
+
{
|
|
2376
|
+
slex: "0.1",
|
|
2377
|
+
namespace: "example_tech_selection",
|
|
2378
|
+
layout: {
|
|
2379
|
+
"card:result": {
|
|
2380
|
+
title: "综合评估",
|
|
2381
|
+
"grid:scores": {
|
|
2382
|
+
columns: 1, mdColumns: 3,
|
|
2383
|
+
"stat:total": { label: "综合评分", "$value": "g.totalScore()" },
|
|
2384
|
+
"stat:recommendation": { label: "推荐程度", "$value": "g.recommendation()", "$tone": "parseFloat(g.totalScore()) >= 85 ? 'success' : parseFloat(g.totalScore()) >= 75 ? 'info' : 'warning'" },
|
|
2385
|
+
"stat:risk": { label: "风险等级", "$value": "g.riskLevel()", "$tone": "parseFloat(g.totalScore()) >= 85 ? 'success' : parseFloat(g.totalScore()) >= 75 ? 'warning' : 'danger'" }
|
|
2386
|
+
},
|
|
2387
|
+
"callout:advice": {
|
|
2388
|
+
"$tone": "parseFloat(g.totalScore()) >= 85 ? 'success' : parseFloat(g.totalScore()) >= 75 ? 'info' : 'warning'",
|
|
2389
|
+
"$text": "parseFloat(g.totalScore()) >= 85 ? g.techLabel() + ' 综合优秀,强烈推荐。' : parseFloat(g.totalScore()) >= 75 ? g.techLabel() + ' 综合良好,推荐采用。' : g.techLabel() + ' 综合一般,建议谨慎。'"
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
```
|
|
2395
|
+
|
|
2396
|
+
|
|
2397
|
+
默认评分参考:
|
|
2398
|
+
|
|
2399
|
+
| 技术栈 | 性能 | 生态 | 学习 | 维护 | 综合 |
|
|
2400
|
+
|--------|------|------|------|------|------|
|
|
2401
|
+
| React | 85 | 95 | 70 | 80 | 82.5 |
|
|
2402
|
+
| Vue | 80 | 85 | 85 | 85 | 83.75 |
|
|
2403
|
+
| Svelte | 95 | 70 | 90 | 90 | 85.5 |
|
|
2404
|
+
| Angular | 80 | 80 | 60 | 75 | 73.75 |
|
|
2405
|
+
|
|
2406
|
+
权重分配:性能30%、生态25%、学习曲线20%、维护成本25%。可以按团队实际情况调整。
|
|
2407
|
+
|
|
2408
|
+
---
|
|
2409
|
+
|
|
2410
|
+
# ToolHost 对话演示
|
|
2411
|
+
|
|
2412
|
+
URL: /zh-CN/examples/toolhost-demo
|
|
2413
|
+
Raw Markdown: /zh-CN/examples/toolhost-demo.md
|
|
2414
|
+
Source: content/examples/toolhost-demo/zh-CN.md
|
|
2415
|
+
|
|
2416
|
+
## ToolHost 对话演示
|
|
2417
|
+
|
|
2418
|
+
AI 对话中需要收集用户信息时,会调用 **ToolHost** 弹出交互式表单卡片。用户填写并提交后,表单数据以结构化的 `ToolResult` 返回给 AI,AI 继续处理。
|
|
2419
|
+
|
|
2420
|
+
下方是一个完整的对话演示——模拟用户请求创建项目,AI 调用 `fill-form` 模板收集项目信息,提交后返回 ToolResult。
|
|
2421
|
+
|
|
2422
|
+
**流程:** 用户发起请求 → AI 判断需要工具调用 → ToolHost 弹出表单 → 用户填写并提交 → 返回 ToolResult → AI 继续响应
|
|
2423
|
+
|
|
2424
|
+
AI 端调用 ToolHost 的代码如下:
|
|
2425
|
+
|
|
2426
|
+
```js
|
|
2427
|
+
import { renderToolCall } from "slexkit/toolhost";
|
|
2428
|
+
|
|
2429
|
+
const { promise } = renderToolCall({
|
|
2430
|
+
name: "fill-form",
|
|
2431
|
+
arguments: {
|
|
2432
|
+
title: "创建新项目",
|
|
2433
|
+
description: "请填写项目的基本信息。",
|
|
2434
|
+
submitLabel: "提交",
|
|
2435
|
+
ignoreLabel: "取消",
|
|
2436
|
+
fields: [
|
|
2437
|
+
{ name: "name", label: "项目名称", type: "text", required: true },
|
|
2438
|
+
{ name: "type", label: "项目类型", type: "select", options: [
|
|
2439
|
+
{ label: "Web 应用", value: "web" },
|
|
2440
|
+
{ label: "API 服务", value: "api" },
|
|
2441
|
+
{ label: "CLI 工具", value: "cli" },
|
|
2442
|
+
]},
|
|
2443
|
+
{ name: "priority", label: "优先级", type: "select", options: [
|
|
2444
|
+
{ label: "低", value: "low" },
|
|
2445
|
+
{ label: "中", value: "medium" },
|
|
2446
|
+
{ label: "高", value: "high" },
|
|
2447
|
+
]},
|
|
2448
|
+
],
|
|
2449
|
+
},
|
|
2450
|
+
}, container);
|
|
2451
|
+
|
|
2452
|
+
// 用户提交后,promise resolve 为 ToolResult
|
|
2453
|
+
const result = await promise;
|
|
2454
|
+
// → { toolName: "fill-form", status: "submitted", value: { name, type, priority } }
|
|
2455
|
+
```
|
|
2456
|
+
|
|
2457
|
+
---
|
|
2458
|
+
|
|
2459
|
+
# 自建还是外购决策
|
|
2460
|
+
|
|
2461
|
+
URL: /zh-CN/examples/roi-estimator
|
|
2462
|
+
Raw Markdown: /zh-CN/examples/roi-estimator.md
|
|
2463
|
+
Source: content/examples/roi-estimator/zh-CN.md
|
|
2464
|
+
|
|
2465
|
+
# 自建还是外购决策
|
|
2466
|
+
|
|
2467
|
+
技术团队永恒的难题:这个功能是自己做,还是买现成的?这里提供一个结构化决策框架——不是算一个数字,而是多维度权衡。
|
|
2468
|
+
|
|
2469
|
+
```slex
|
|
2470
|
+
{
|
|
2471
|
+
slex: "0.1",
|
|
2472
|
+
namespace: "example_build_vs_buy",
|
|
2473
|
+
g: {
|
|
2474
|
+
scope: "core",
|
|
2475
|
+
buildFit: 60, buildTime: 6, buildCost: 80,
|
|
2476
|
+
buyFit: 85, buyTime: 1, buyCost: 40, buyVendorLock: 30,
|
|
2477
|
+
buildTotal: function () { return (this.buildFit + (100 - this.buildCost) + (100 - this.buildTime * 10)) / 3; },
|
|
2478
|
+
buyTotal: function () { return (this.buyFit + (100 - this.buyCost) + (100 - this.buyTime * 10) - this.buyVendorLock * 0.5) / 3; },
|
|
2479
|
+
recommendation: function () {
|
|
2480
|
+
if (this.scope === "core") return this.buildTotal() >= this.buyTotal() ? "自建" : "外购(但建议评估长期成本)";
|
|
2481
|
+
return this.buyTotal() >= this.buildTotal() ? "外购" : "自建(非核心功能自建需谨慎)";
|
|
2482
|
+
},
|
|
2483
|
+
diff: function () { return Math.abs(this.buildTotal() - this.buyTotal()); }
|
|
2484
|
+
},
|
|
2485
|
+
layout: {
|
|
2486
|
+
"section:decision": {
|
|
2487
|
+
eyebrow: "技术决策",
|
|
2488
|
+
title: "自建还是外购决策",
|
|
2489
|
+
subtitle: "对比两个方案的各维度评分,系统综合推荐。拖滑块调整评分看结论变化。",
|
|
2490
|
+
"select:scope": {
|
|
2491
|
+
label: "功能定位",
|
|
2492
|
+
"$value": "g.scope",
|
|
2493
|
+
options: [
|
|
2494
|
+
{ label: "核心业务(差异化的关键)", value: "core" },
|
|
2495
|
+
{ label: "辅助功能(非核心)", value: "non-core" }
|
|
2496
|
+
],
|
|
2497
|
+
onchange: "g.scope = String($event)"
|
|
2498
|
+
},
|
|
2499
|
+
"table:comparison": {
|
|
2500
|
+
columns: ["维度", "自建方案", "外购方案"],
|
|
2501
|
+
rows: [
|
|
2502
|
+
["功能匹配度", "g.buildFit + '%'", "g.buyFit + '%'"],
|
|
2503
|
+
["上线时间", "g.buildTime + ' 个月'", "g.buyTime + ' 个月'"],
|
|
2504
|
+
["成本评分", "g.buildCost + '/100'", "g.buyCost + '/100'"],
|
|
2505
|
+
["供应商锁定", "—", "g.buyVendorLock + '/100'"]
|
|
2506
|
+
]
|
|
2507
|
+
},
|
|
2508
|
+
"grid:sliders": {
|
|
2509
|
+
columns: 1, mdColumns: 2,
|
|
2510
|
+
"column:build": {
|
|
2511
|
+
"card:buildSliders": {
|
|
2512
|
+
title: "自建方案",
|
|
2513
|
+
"slider:buildFit": { label: "功能匹配度", "$value": "g.buildFit", min: 0, max: 100, step: 5, onchange: "g.buildFit = Number($event)" },
|
|
2514
|
+
"slider:buildTime": { label: "上线时间(月)", "$value": "g.buildTime", min: 1, max: 24, step: 1, unit: "月", onchange: "g.buildTime = Number($event)" },
|
|
2515
|
+
"slider:buildCost": { label: "成本评分", "$value": "g.buildCost", min: 0, max: 100, step: 5, onchange: "g.buildCost = Number($event)" }
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
"column:buy": {
|
|
2519
|
+
"card:buySliders": {
|
|
2520
|
+
title: "外购方案",
|
|
2521
|
+
"slider:buyFit": { label: "功能匹配度", "$value": "g.buyFit", min: 0, max: 100, step: 5, onchange: "g.buyFit = Number($event)" },
|
|
2522
|
+
"slider:buyTime": { label: "上线时间(月)", "$value": "g.buyTime", min: 1, max: 24, step: 1, unit: "月", onchange: "g.buyTime = Number($event)" },
|
|
2523
|
+
"slider:buyCost": { label: "成本评分", "$value": "g.buyCost", min: 0, max: 100, step: 5, onchange: "g.buyCost = Number($event)" },
|
|
2524
|
+
"slider:lock": { label: "供应商锁定风险", "$value": "g.buyVendorLock", min: 0, max: 100, step: 5, onchange: "g.buyVendorLock = Number($event)" }
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
},
|
|
2528
|
+
"grid:results": {
|
|
2529
|
+
columns: 1, mdColumns: 3,
|
|
2530
|
+
"stat:buildScore": { label: "自建综合得分", "$value": "g.buildTotal().toFixed(1)" },
|
|
2531
|
+
"stat:buyScore": { label: "外购综合得分", "$value": "g.buyTotal().toFixed(1)" },
|
|
2532
|
+
"badge:winner": { "$label": "g.recommendation().startsWith('自建') ? '建议自建' : '建议外购'", "$tone": "g.diff() < 10 ? 'warning' : g.recommendation().startsWith('自建') ? 'info' : 'success'" }
|
|
2533
|
+
},
|
|
2534
|
+
"callout:advice": {
|
|
2535
|
+
"$tone": "g.diff() < 10 ? 'warning' : 'info'",
|
|
2536
|
+
"$text": "g.diff() < 10 ? '两个方案得分非常接近——建议引入更多决策者参与讨论,或做小范围 POC。' : g.recommendation() + ' 方案的得分明显更高。但请结合团队实际能力和战略方向做最终决定。'"
|
|
2537
|
+
},
|
|
2538
|
+
"accordion:detail": {
|
|
2539
|
+
multiple: true,
|
|
2540
|
+
items: [
|
|
2541
|
+
{ value: "cost", label: "成本说明", content: "成本评分越低越好(0 = 零成本,100 = 极高成本)。外购方案需额外考虑供应商锁定风险。" },
|
|
2542
|
+
{ value: "scope", label: "核心 vs 非核心策略", content: "核心业务功能通常倾向自建以保持控制力和差异化。非核心功能外购可以释放团队精力。" },
|
|
2543
|
+
{ value: "hybrid", label: "第三条路:混合方案", content: "也可以考虑先外购快速上线,同时内部规划自建替代方案,等自建成熟后迁移。" }
|
|
2544
|
+
]
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
```
|
|
2550
|
+
|
|
2551
|
+
|
|
2552
|
+
**Build vs Buy 决策模型的要点:**
|
|
2553
|
+
|
|
2554
|
+
- `select` 定义功能定位(核心/非核心),影响决策偏向
|
|
2555
|
+
- 两套 slider 独立调整各自评分
|
|
2556
|
+
- `buildTotal()` 和 `buyTotal()` 用加权公式计算综合得分
|
|
2557
|
+
- `recommendation()` 结合功能定位和得分给出建议
|
|
2558
|
+
- accordion 提供额外的决策指南(成本说明、策略建议、混合方案)
|
|
2559
|
+
|
|
2560
|
+
这个框架帮你把"凭感觉拍脑袋"变成"有依据的比较"。
|
|
2561
|
+
|
|
2562
|
+
---
|
|
2563
|
+
|
|
2564
|
+
# 跨文档状态实验室
|
|
2565
|
+
|
|
2566
|
+
URL: /zh-CN/examples/cross-doc-state-lab
|
|
2567
|
+
Raw Markdown: /zh-CN/examples/cross-doc-state-lab.md
|
|
2568
|
+
Source: content/examples/cross-doc-state-lab/zh-CN.md
|
|
2569
|
+
|
|
2570
|
+
# 跨文档状态实验室
|
|
2571
|
+
|
|
2572
|
+
这是 SlexKit 最独特的能力之一:**在同一个 Markdown 文档的不同位置放置多块 `slex` 代码,只要它们使用相同的 `namespace`,就会共享同一份 `g` 状态**。
|
|
2573
|
+
|
|
2574
|
+
下面是三个独立的 ` ```slex ` fence——一个控制面板和两个观察面板。试试修改控制面板的值,看下方两个面板实时响应。
|
|
2575
|
+
|
|
2576
|
+
## 主控面板
|
|
2577
|
+
|
|
2578
|
+
```slex
|
|
2579
|
+
{
|
|
2580
|
+
slex: "0.1",
|
|
2581
|
+
namespace: "example_cross_doc_lab",
|
|
2582
|
+
g: {
|
|
2583
|
+
color: "blue", size: 16, theme: "light",
|
|
2584
|
+
style: function () {
|
|
2585
|
+
return 'color: ' + this.color + '; font-size: ' + this.size + 'px;';
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
layout: {
|
|
2589
|
+
"section:control": {
|
|
2590
|
+
eyebrow: "平台能力",
|
|
2591
|
+
title: "跨文档状态实验室 · 主控面板",
|
|
2592
|
+
subtitle: "修改以下任何参数——下方两个独立 fence 块的卡片会同步更新。",
|
|
2593
|
+
"grid:controls": {
|
|
2594
|
+
columns: 1, mdColumns: 3,
|
|
2595
|
+
"select:color": {
|
|
2596
|
+
label: "文字颜色",
|
|
2597
|
+
"$value": "g.color",
|
|
2598
|
+
options: [
|
|
2599
|
+
{ label: "蓝色", value: "blue" },
|
|
2600
|
+
{ label: "绿色", value: "green" },
|
|
2601
|
+
{ label: "橙色", value: "orange" },
|
|
2602
|
+
{ label: "紫色", value: "purple" }
|
|
2603
|
+
],
|
|
2604
|
+
onchange: "g.color = String($event)"
|
|
2605
|
+
},
|
|
2606
|
+
"slider:size": { label: "字体大小", "$value": "g.size", min: 8, max: 48, step: 2, unit: "px", onchange: "g.size = Number($event)" },
|
|
2607
|
+
"select:theme": {
|
|
2608
|
+
label: "卡片主题",
|
|
2609
|
+
"$value": "g.theme",
|
|
2610
|
+
options: [
|
|
2611
|
+
{ label: "明亮", value: "light" },
|
|
2612
|
+
{ label: "暗色", value: "dark" },
|
|
2613
|
+
{ label: "信息", value: "info" }
|
|
2614
|
+
],
|
|
2615
|
+
onchange: "g.theme = String($event)"
|
|
2616
|
+
}
|
|
2617
|
+
},
|
|
2618
|
+
"badge:note": { "$label": "'样式 ' + g.color + ' ' + g.size + 'px'", tone: "info" }
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
```
|
|
2623
|
+
|
|
2624
|
+
## 观察面板 A(同一 namespace,不同 fence 块)
|
|
2625
|
+
|
|
2626
|
+
```slex
|
|
2627
|
+
{
|
|
2628
|
+
slex: "0.1",
|
|
2629
|
+
namespace: "example_cross_doc_lab",
|
|
2630
|
+
layout: {
|
|
2631
|
+
"card:a": {
|
|
2632
|
+
title: "观察面板 A — 纯文本样式",
|
|
2633
|
+
"stat:size": { "$label": "'字体大小:' + g.size + 'px'", "$value": "g.color" },
|
|
2634
|
+
"callout:preview": {
|
|
2635
|
+
"$tone": "g.theme === 'dark' ? 'danger' : g.theme === 'info' ? 'info' : 'success'",
|
|
2636
|
+
"$text": "g.theme === 'dark' ? '暗色模式:适合夜间阅读的配色方案。' : g.theme === 'info' ? '信息模式:用于强调技术细节。' : '明亮模式:默认的文档阅读配色。'"
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
```
|
|
2642
|
+
|
|
2643
|
+
## 观察面板 B
|
|
2644
|
+
|
|
2645
|
+
```slex
|
|
2646
|
+
{
|
|
2647
|
+
slex: "0.1",
|
|
2648
|
+
namespace: "example_cross_doc_lab",
|
|
2649
|
+
layout: {
|
|
2650
|
+
"card:b": {
|
|
2651
|
+
title: "观察面板 B — 参数详情",
|
|
2652
|
+
"grid:params": {
|
|
2653
|
+
columns: 1, mdColumns: 3,
|
|
2654
|
+
"stat:col": { label: "颜色", "$value": "g.color" },
|
|
2655
|
+
"stat:sz": { label: "字号", "$value": "g.size", unit: "px" },
|
|
2656
|
+
"stat:th": { label: "主题", "$value": "g.theme" }
|
|
2657
|
+
},
|
|
2658
|
+
"badge:sync": { "$label": "'已同步 ' + g.color", "$tone": "g.color === 'blue' ? 'info' : g.color === 'green' ? 'success' : g.color === 'orange' ? 'warning' : 'info'" }
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
```
|
|
2663
|
+
|
|
2664
|
+
**三个 fence 块**,同一个 `namespace: "example_cross_doc_lab"`,所有组件共享 `g` 对象。上面你在主控面板改颜色和大小,下面两个观察面板马上更新。
|
|
2665
|
+
|
|
2666
|
+
---
|
|
2667
|
+
|
|
2668
|
+
### 这意味着什么?
|
|
2669
|
+
|
|
2670
|
+
假设你在一篇长篇 Markdown 文档中:
|
|
2671
|
+
|
|
2672
|
+
```
|
|
2673
|
+
[控制面板 — 选择行业/指标/时间范围]
|
|
2674
|
+
... 30 段 Markdown 叙事 ...
|
|
2675
|
+
[图表 A — 自动反映控制面板的选项]
|
|
2676
|
+
... 更多分析文字 ...
|
|
2677
|
+
[图表 B — 同一份状态的不同可视化]
|
|
2678
|
+
```
|
|
2679
|
+
|
|
2680
|
+
每个 ` ```slex ` 块可以独立渲染,但只要 namespace 相同,它们就共享状态。这对于:
|
|
2681
|
+
|
|
2682
|
+
- **技术白皮书**:顶部选参数,中间分析,底部结论,全程联动
|
|
2683
|
+
- **项目协作文档**:状态跟踪表格在顶部,各团队任务卡片散布在正文中
|
|
2684
|
+
- **AI 输出增强**:模型生成的多个可视化节点共享同一份推理结果
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
都是极其强大的模式。这不是一个 "组件库" 能做到的——这是 SlexKit 的核心设计。
|
|
2688
|
+
|
|
2689
|
+
---
|
|
2690
|
+
|
|
2691
|
+
# JSONPlaceholder 网络请求实验台
|
|
2692
|
+
|
|
2693
|
+
URL: /zh-CN/examples/network-policy-fetch-card
|
|
2694
|
+
Raw Markdown: /zh-CN/examples/network-policy-fetch-card.md
|
|
2695
|
+
Source: content/examples/network-policy-fetch-card/zh-CN.md
|
|
2696
|
+
|
|
2697
|
+
# JSONPlaceholder 网络请求实验台
|
|
2698
|
+
|
|
2699
|
+
这个示例不再只是展示一段策略配置,而是让用户在沙盒内选择真实的网络任务:读取 posts、查看详情、拉取评论、按用户过滤,或者向 JSONPlaceholder 发起一个演示用 POST。请求不会从 iframe 直接出网,而是通过 `api.fetch()` 交给宿主,由宿主按 allowlist、方法、超时、请求体大小和响应类型决定是否代理。
|
|
2700
|
+
|
|
2701
|
+
```slex
|
|
2702
|
+
{
|
|
2703
|
+
slex: "0.1",
|
|
2704
|
+
namespace: "example_jsonplaceholder_network_lab",
|
|
2705
|
+
g: {
|
|
2706
|
+
scenario: "posts",
|
|
2707
|
+
postId: 1,
|
|
2708
|
+
userId: 1,
|
|
2709
|
+
timeout: 3000,
|
|
2710
|
+
title: "SlexKit network demo",
|
|
2711
|
+
status: "未请求",
|
|
2712
|
+
statusCode: "-",
|
|
2713
|
+
elapsed: "-",
|
|
2714
|
+
response: "选择一个网络任务,然后点击“发起请求”。",
|
|
2715
|
+
lastUrl: "https://jsonplaceholder.typicode.com/posts",
|
|
2716
|
+
method: function () { return this.scenario === "create" ? "POST" : "GET"; },
|
|
2717
|
+
url: function () {
|
|
2718
|
+
if (this.scenario === "detail") return "https://jsonplaceholder.typicode.com/posts/" + this.postId;
|
|
2719
|
+
if (this.scenario === "comments") return "https://jsonplaceholder.typicode.com/posts/" + this.postId + "/comments";
|
|
2720
|
+
if (this.scenario === "user-posts") return "https://jsonplaceholder.typicode.com/users/" + this.userId + "/posts";
|
|
2721
|
+
if (this.scenario === "create") return "https://jsonplaceholder.typicode.com/posts";
|
|
2722
|
+
return "https://jsonplaceholder.typicode.com/posts?_limit=5";
|
|
2723
|
+
},
|
|
2724
|
+
requestBody: function () {
|
|
2725
|
+
if (this.scenario !== "create") return undefined;
|
|
2726
|
+
return {
|
|
2727
|
+
title: this.title,
|
|
2728
|
+
body: "这是一条从 SlexKit secure runtime 发起、由宿主代理的演示请求。",
|
|
2729
|
+
userId: this.userId
|
|
2730
|
+
};
|
|
2731
|
+
},
|
|
2732
|
+
description: function () {
|
|
2733
|
+
if (this.scenario === "detail") return "读取单篇 post,适合详情页或引用卡片。";
|
|
2734
|
+
if (this.scenario === "comments") return "读取某篇 post 的评论,适合评论摘要、审阅流和证据面板。";
|
|
2735
|
+
if (this.scenario === "user-posts") return "按用户读取 posts,适合个人空间、作者档案或关联资源列表。";
|
|
2736
|
+
if (this.scenario === "create") return "提交一条演示 post。JSONPlaceholder 会返回假写入结果,不会持久化。";
|
|
2737
|
+
return "读取最新 posts 列表,适合 feed、任务列表和知识库索引。";
|
|
2738
|
+
},
|
|
2739
|
+
riskText: function () {
|
|
2740
|
+
if (this.method() === "POST") return "POST 已被限制为 JSONPlaceholder origin,且请求体大小受 policy 约束。";
|
|
2741
|
+
return "GET 请求仍然要经过 origin、超时、响应大小和 content-type 校验。";
|
|
2742
|
+
},
|
|
2743
|
+
requestSnippet: function () {
|
|
2744
|
+
var body = this.requestBody();
|
|
2745
|
+
var lines = [
|
|
2746
|
+
"await api.fetch('" + this.url() + "', {",
|
|
2747
|
+
" method: '" + this.method() + "',",
|
|
2748
|
+
" timeoutMs: " + this.timeout + ","
|
|
2749
|
+
];
|
|
2750
|
+
if (body) lines.push(" body: " + JSON.stringify(body, null, 2).replace(/\n/g, "\n ") + ",");
|
|
2751
|
+
lines.push(" credentials: 'omit'");
|
|
2752
|
+
lines.push("})");
|
|
2753
|
+
return lines.join("\n");
|
|
2754
|
+
},
|
|
2755
|
+
policyRows: function () {
|
|
2756
|
+
return [
|
|
2757
|
+
{ item: "Origin", value: "https://jsonplaceholder.typicode.com", reason: "只允许演示 API" },
|
|
2758
|
+
{ item: "Method", value: "GET, POST", reason: "读列表/详情和创建演示数据" },
|
|
2759
|
+
{ item: "Credentials", value: "omit", reason: "不携带 cookie 或站点身份" },
|
|
2760
|
+
{ item: "Content-Type", value: "application/json", reason: "拒绝非 JSON 响应进入沙盒" },
|
|
2761
|
+
{ item: "Body", value: "<= 4096 bytes", reason: "避免大请求体被模型输出滥用" }
|
|
2762
|
+
];
|
|
2763
|
+
},
|
|
2764
|
+
async run(api) {
|
|
2765
|
+
var targetUrl = String(this.url());
|
|
2766
|
+
this.status = "请求中";
|
|
2767
|
+
this.statusCode = "-";
|
|
2768
|
+
this.elapsed = "-";
|
|
2769
|
+
this.lastUrl = targetUrl;
|
|
2770
|
+
this.response = "等待宿主代理 " + this.method() + " " + targetUrl;
|
|
2771
|
+
try {
|
|
2772
|
+
var result = await api.fetch(targetUrl, {
|
|
2773
|
+
method: this.method(),
|
|
2774
|
+
timeoutMs: this.timeout,
|
|
2775
|
+
body: this.requestBody(),
|
|
2776
|
+
credentials: "omit"
|
|
2777
|
+
});
|
|
2778
|
+
this.status = result.ok ? "成功" : "HTTP 错误";
|
|
2779
|
+
this.statusCode = String(result.status) + " " + result.statusText;
|
|
2780
|
+
this.elapsed = Math.round(result.elapsedMs) + " ms";
|
|
2781
|
+
this.response = JSON.stringify(result.data === undefined ? result.text : result.data, null, 2).slice(0, 2400);
|
|
2782
|
+
} catch (error) {
|
|
2783
|
+
this.status = api.isPolicyError(error) ? "Policy 拦截" : api.isTimeoutError(error) ? "超时" : "网络失败";
|
|
2784
|
+
this.statusCode = api.isPolicyError(error) ? "policy" : api.isTimeoutError(error) ? "timeout" : "network";
|
|
2785
|
+
var elapsedMs = error && typeof error === "object" && typeof error.elapsedMs === "number" ? error.elapsedMs : undefined;
|
|
2786
|
+
this.elapsed = elapsedMs === undefined ? "-" : Math.round(elapsedMs) + " ms";
|
|
2787
|
+
this.response = targetUrl + "\n" + api.errorMessage(error);
|
|
2788
|
+
}
|
|
2789
|
+
},
|
|
2790
|
+
async runBlocked(api) {
|
|
2791
|
+
var targetUrl = "https://example.com/posts/1";
|
|
2792
|
+
this.status = "请求中";
|
|
2793
|
+
this.statusCode = "-";
|
|
2794
|
+
this.elapsed = "-";
|
|
2795
|
+
this.lastUrl = targetUrl;
|
|
2796
|
+
this.response = "这次请求故意访问 allowlist 外的 origin,应该被 host policy 拦截。";
|
|
2797
|
+
try {
|
|
2798
|
+
await api.get(targetUrl, { timeoutMs: this.timeout, credentials: "omit" });
|
|
2799
|
+
this.status = "异常通过";
|
|
2800
|
+
this.statusCode = "unexpected";
|
|
2801
|
+
this.response = "如果看到这行,说明 policy 没有按预期拦截。";
|
|
2802
|
+
} catch (error) {
|
|
2803
|
+
this.status = api.isPolicyError(error) ? "Policy 拦截" : "失败";
|
|
2804
|
+
this.statusCode = api.isPolicyError(error) ? "origin_blocked" : "network";
|
|
2805
|
+
this.response = targetUrl + "\n" + api.errorMessage(error);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
layout: {
|
|
2810
|
+
"section:network": {
|
|
2811
|
+
eyebrow: "平台能力",
|
|
2812
|
+
title: "JSONPlaceholder 网络请求实验台",
|
|
2813
|
+
subtitle: "在沙盒内选择网络任务,请求通过 host policy 代理。",
|
|
2814
|
+
"card:network": {
|
|
2815
|
+
title: "JSONPlaceholder 请求实验台",
|
|
2816
|
+
"callout:intent": { tone: "info", "$text": "g.description()" },
|
|
2817
|
+
"grid:controls": {
|
|
2818
|
+
columns: 1,
|
|
2819
|
+
mdColumns: 2,
|
|
2820
|
+
"select:scenario": {
|
|
2821
|
+
label: "网络任务",
|
|
2822
|
+
"$value": "g.scenario",
|
|
2823
|
+
options: [
|
|
2824
|
+
{ label: "Posts 列表 GET /posts", value: "posts" },
|
|
2825
|
+
{ label: "Post 详情 GET /posts/:id", value: "detail" },
|
|
2826
|
+
{ label: "评论列表 GET /posts/:id/comments", value: "comments" },
|
|
2827
|
+
{ label: "用户 posts GET /users/:id/posts", value: "user-posts" },
|
|
2828
|
+
{ label: "创建 post POST /posts", value: "create" }
|
|
2829
|
+
],
|
|
2830
|
+
onchange: "g.scenario = String($event)"
|
|
2831
|
+
},
|
|
2832
|
+
"slider:postId": { label: "Post ID", "$value": "g.postId", min: 1, max: 10, step: 1, onchange: "g.postId = Number($event)" },
|
|
2833
|
+
"slider:userId": { label: "User ID", "$value": "g.userId", min: 1, max: 10, step: 1, onchange: "g.userId = Number($event)" },
|
|
2834
|
+
"slider:timeout": { label: "超时上限", "$value": "g.timeout", min: 500, max: 8000, step: 500, unit: "ms", onchange: "g.timeout = Number($event)" }
|
|
2835
|
+
},
|
|
2836
|
+
"row:actions": {
|
|
2837
|
+
"button:run": { label: "发起请求", icon: "paper-plane-tilt", onclick: "g.run(api)" },
|
|
2838
|
+
"button:block": { label: "测试拦截", variant: "secondary", icon: "shield-warning", onclick: "g.runBlocked(api)" }
|
|
2839
|
+
},
|
|
2840
|
+
"grid:status": {
|
|
2841
|
+
columns: 1,
|
|
2842
|
+
mdColumns: 3,
|
|
2843
|
+
"stat:method": { label: "方法", "$value": "g.method()" },
|
|
2844
|
+
"stat:status": { label: "状态", "$value": "g.status" },
|
|
2845
|
+
"stat:elapsed": { label: "耗时", "$value": "g.elapsed" }
|
|
2846
|
+
},
|
|
2847
|
+
"code-block:request": { title: "沙盒内请求代码", language: "ts", "$code": "g.requestSnippet()" },
|
|
2848
|
+
"code-block:response": { title: "宿主代理响应", language: "json", "$code": "g.response" },
|
|
2849
|
+
"table:policy_matrix": {
|
|
2850
|
+
columns: [
|
|
2851
|
+
{ key: "item", label: "Policy 项" },
|
|
2852
|
+
{ key: "value", label: "允许值" },
|
|
2853
|
+
{ key: "reason", label: "原因" }
|
|
2854
|
+
],
|
|
2855
|
+
"$rows": "g.policyRows()"
|
|
2856
|
+
},
|
|
2857
|
+
"callout:policy_note": { "$tone": "g.status === 'Policy 拦截' ? 'warning' : 'success'", "$text": "g.riskText()" }
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
```
|
|
2863
|
+
|
|
2864
|
+
---
|
|
2865
|
+
|
|
1263
2866
|
# Accordion
|
|
1264
2867
|
|
|
1265
2868
|
URL: /docs/components/accordion
|
|
@@ -2048,6 +3651,64 @@ Horizontal separator, optionally with a centered text label.
|
|
|
2048
3651
|
|
|
2049
3652
|
---
|
|
2050
3653
|
|
|
3654
|
+
# Formula
|
|
3655
|
+
|
|
3656
|
+
URL: /docs/components/formula
|
|
3657
|
+
Raw Markdown: /docs/components/formula.md
|
|
3658
|
+
Source: site/content/components/formula/en-US.md
|
|
3659
|
+
|
|
3660
|
+
---
|
|
3661
|
+
title: "Formula"
|
|
3662
|
+
category: Display
|
|
3663
|
+
status: ready
|
|
3664
|
+
order: 11
|
|
3665
|
+
summary: "Reactive KaTeX formula display."
|
|
3666
|
+
---
|
|
3667
|
+
# Formula
|
|
3668
|
+
|
|
3669
|
+
Render SlexKit state and computed values through KaTeX. Use it when Markdown explains the model and the interactive block needs the formula itself to update.
|
|
3670
|
+
|
|
3671
|
+
<!-- slex:spec-example:start component="formula" id="basic" sourceHash="1578d25d" -->
|
|
3672
|
+
```slex
|
|
3673
|
+
{
|
|
3674
|
+
"slex": "0.1",
|
|
3675
|
+
"namespace": "doc_formula_typical",
|
|
3676
|
+
"g": {
|
|
3677
|
+
"r": 10000,
|
|
3678
|
+
"c": 100,
|
|
3679
|
+
"fc": 159.15
|
|
3680
|
+
},
|
|
3681
|
+
"layout": {
|
|
3682
|
+
"formula:cutoff": {
|
|
3683
|
+
"$tex": "'f_c = \\\\frac{1}{2\\\\pi RC} = ' + g.fc + '\\\\text{ Hz}'"
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
```
|
|
3688
|
+
<!-- slex:spec-example:end -->
|
|
3689
|
+
|
|
3690
|
+
## Usage Notes
|
|
3691
|
+
|
|
3692
|
+
- Use for formulas whose variables come from SlexKit state.
|
|
3693
|
+
- Keep the explanatory derivation in Markdown and use `formula` for the live expression.
|
|
3694
|
+
- Use `displayMode: false` for inline formula fragments.
|
|
3695
|
+
- Invalid TeX is rendered by KaTeX as an error expression instead of throwing.
|
|
3696
|
+
|
|
3697
|
+
## API Reference {#api}
|
|
3698
|
+
|
|
3699
|
+
<!-- slex:spec-api:start component="formula" sourceHash="144588e3" -->
|
|
3700
|
+
| Field | Type | Required | Dynamic | Default | Description |
|
|
3701
|
+
|---|---|---|---|---|---|
|
|
3702
|
+
| `tex` | string | No | Yes | | KaTeX source to render. |
|
|
3703
|
+
| `formula` | string | No | Yes | | Alias for tex. |
|
|
3704
|
+
| `value` | string | No | Yes | | Alias for tex. |
|
|
3705
|
+
| `displayMode` | boolean | No | No | `true` | Render as display math when true; inline math when false. |
|
|
3706
|
+
| `display` | boolean | No | No | `true` | Alias for displayMode. |
|
|
3707
|
+
| `block` | boolean | No | No | `true` | Alias for displayMode. |
|
|
3708
|
+
<!-- slex:spec-api:end -->
|
|
3709
|
+
|
|
3710
|
+
---
|
|
3711
|
+
|
|
2051
3712
|
# Grid
|
|
2052
3713
|
|
|
2053
3714
|
URL: /docs/components/grid
|
|
@@ -3757,7 +5418,7 @@ ComponentKey = ComponentType ":" Identifier
|
|
|
3757
5418
|
- Named components use `Identifier` for instance state and lifecycle hooks.
|
|
3758
5419
|
- Keys without `:` are not rendered as component nodes.
|
|
3759
5420
|
|
|
3760
|
-
Reserved context names: `g`, `api`, `$event`, `$item`, `$index`, `$key`.
|
|
5421
|
+
Reserved context names: `g`, `std`, `api`, `$event`, `$item`, `$index`, `$key`.
|
|
3761
5422
|
|
|
3762
5423
|
## 3. Props classification
|
|
3763
5424
|
|
|
@@ -3814,6 +5475,7 @@ Expressions can access these variables:
|
|
|
3814
5475
|
| Variable | Type | Scope |
|
|
3815
5476
|
| ------------------ | ----------------------------- | ------------------------ |
|
|
3816
5477
|
| `g` | Reactive state proxy | Always |
|
|
5478
|
+
| `std` | Pure SlexKit standard library | Always |
|
|
3817
5479
|
| Component state | e.g. `slider.value` | Named components |
|
|
3818
5480
|
| `api` | Host-injected object | If `api` option provided |
|
|
3819
5481
|
| `$event` | Event data | `on*` handlers only |
|
|
@@ -3822,6 +5484,8 @@ Expressions can access these variables:
|
|
|
3822
5484
|
| `$key` | Current item key | `$for` context only |
|
|
3823
5485
|
| Named `$for` alias | e.g. `user` for `"card:user"` | `$for` context only |
|
|
3824
5486
|
|
|
5487
|
+
`std` contains deterministic helpers for math, formatting, units, and small statistics. Sensitive capabilities stay under host-injected `api.*` and may require secure runtime policy.
|
|
5488
|
+
|
|
3825
5489
|
Expression evaluation errors are caught and produce a warning with namespace and path information. The last known value is returned as a fallback.
|
|
3826
5490
|
|
|
3827
5491
|
## 6. Component instance state
|
|
@@ -4232,6 +5896,7 @@ The `RenderContext` provides:
|
|
|
4232
5896
|
| Property | Type | Description |
|
|
4233
5897
|
|----------|------|-------------|
|
|
4234
5898
|
| `g` | reactive proxy | Global state |
|
|
5899
|
+
| `std` | `SlexKitStdlib` | Pure deterministic helpers |
|
|
4235
5900
|
| `api` | `Record<string, unknown>` | Host-injected capabilities |
|
|
4236
5901
|
| `dir` | `"ltr"` or `"rtl"` | Resolved direction |
|
|
4237
5902
|
| `labels` | `Partial<Record<string, string>>` | Runtime labels |
|
|
@@ -4301,6 +5966,8 @@ type MountOptions = {
|
|
|
4301
5966
|
|
|
4302
5967
|
Calling `mount()` again on the same `container` clears the old root first, then appends a new root. The returned cleanup only unmounts the current root; it does not delete the namespace store.
|
|
4303
5968
|
|
|
5969
|
+
Every expression receives `std`, SlexKit's pure deterministic standard library. Hosts can still inject capability objects through `api`, but network, timers, animation, and canvas should remain policy-gated in secure mode.
|
|
5970
|
+
|
|
4304
5971
|
### `ingest(input)`
|
|
4305
5972
|
|
|
4306
5973
|
Ingests state-only Slex: updates `g` without rendering UI. Used by the Markdown runtime host for state-only fences. Returns `true` if parsing succeeded.
|
|
@@ -5895,6 +7562,44 @@ slexkitRenderMode: component
|
|
|
5895
7562
|
|
|
5896
7563
|
All notable changes to SlexKit.
|
|
5897
7564
|
|
|
7565
|
+
## v0.3.0 - Examples overhaul with component audit and i18n
|
|
7566
|
+
|
|
7567
|
+
### Added
|
|
7568
|
+
- Example gallery: 17 high-quality examples organized by usage scenario (Getting Started, Calculators, Data Browsing, Dashboards, Config Wizards, Decision Support, Platform Features)
|
|
7569
|
+
- English translations for all 17 example pages
|
|
7570
|
+
- `toolhost-demo`: real `renderToolCall` API with chat-style conversation UI
|
|
7571
|
+
- Example rendering infrastructure: `site/routes/examples.js`, `site/pages/examples.slex.js`, `site/data/examples.js`
|
|
7572
|
+
- Formula component (`src/components/svelte/content/Formula.svelte`) with KaTeX rendering
|
|
7573
|
+
- `src/engine/capabilities.ts`: structured capability docs for AI agents
|
|
7574
|
+
- `src/engine/validation.ts`: SPEC contract validation
|
|
7575
|
+
- `src/engine/stdlib.ts`: standard library with `math.clamp`, `math.safeDivide`, and other utilities
|
|
7576
|
+
- `src/engine/sandbox-runner.ts`: sandbox runner for secure runtime
|
|
7577
|
+
- Component state eval context shadowing test suite (`component-state-shadowing.test.ts`)
|
|
7578
|
+
- Collapsible and Callout double-rendering regression tests
|
|
7579
|
+
- Slider component name shadowing regression test
|
|
7580
|
+
|
|
7581
|
+
### Changed
|
|
7582
|
+
- Examples reduced from 64 to 17 high-quality examples, organized by user story
|
|
7583
|
+
- Example source locale: `zh-CN` (with `en-US` translations)
|
|
7584
|
+
- `renderChildren` (`helpers.ts`) now clears existing content when children are present
|
|
7585
|
+
- Switch component now accepts `checked`/`value` props for initialization consistency with Checkbox
|
|
7586
|
+
- Site UI: DocsShell, DocRail, router, shell improvements
|
|
7587
|
+
- Components: Input, Select, Tabs, Table, PlaygroundMarkdown refinements
|
|
7588
|
+
- CSS: theme-shadcn, text-input, docs-shell styling updates
|
|
7589
|
+
|
|
7590
|
+
### Fixed
|
|
7591
|
+
- Eval context shadowing: component names `g` and `api` no longer overwrite reserved context keys
|
|
7592
|
+
- `renderChildren` double rendering in Collapsible and Callout
|
|
7593
|
+
- Voltage divider summary typo ("输入输入电压")
|
|
7594
|
+
- Salary calculator fallback numbers to match actual calculator output
|
|
7595
|
+
- Tabs-and-branching: title and length conversion mismatch
|
|
7596
|
+
- 4 pre-existing test failures (ai-docs, page-structure, theme, markdown-content)
|
|
7597
|
+
|
|
7598
|
+
### Removed
|
|
7599
|
+
- 47 low-quality/duplicate examples (reduced from 64 to 17)
|
|
7600
|
+
- Dead "Fallback" copywriting from all example files
|
|
7601
|
+
- Unused `DialogShell.svelte` component
|
|
7602
|
+
|
|
5898
7603
|
## v0.2.0 - First public release
|
|
5899
7604
|
|
|
5900
7605
|
### Added
|
|
@@ -6182,6 +7887,26 @@ No child components.
|
|
|
6182
7887
|
|
|
6183
7888
|
---
|
|
6184
7889
|
|
|
7890
|
+
## Formula API (`formula`)
|
|
7891
|
+
|
|
7892
|
+
Category: Display
|
|
7893
|
+
Status: ready
|
|
7894
|
+
State mode: none
|
|
7895
|
+
Since: 0.1.0
|
|
7896
|
+
|
|
7897
|
+
### Props
|
|
7898
|
+
- `tex` (string; optional; dynamic): KaTeX source to render.
|
|
7899
|
+
- `formula` (string; optional; dynamic): Alias for tex.
|
|
7900
|
+
- `value` (string; optional; dynamic): Alias for tex.
|
|
7901
|
+
- `displayMode` (boolean; optional; static; default: true): Render as display math when true; inline math when false.
|
|
7902
|
+
- `display` (boolean; optional; static; default: true): Alias for displayMode.
|
|
7903
|
+
- `block` (boolean; optional; static; default: true): Alias for displayMode.
|
|
7904
|
+
|
|
7905
|
+
### Children
|
|
7906
|
+
No child components.
|
|
7907
|
+
|
|
7908
|
+
---
|
|
7909
|
+
|
|
6185
7910
|
## Grid API (`grid`)
|
|
6186
7911
|
|
|
6187
7912
|
Category: Layout
|