weifuwu 0.61.1 → 0.62.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/README.md +148 -57
- package/dist/client/diff.d.ts +13 -1
- package/dist/client/index.js +3 -3
- package/dist/client/popup.d.ts +22 -0
- package/dist/client/types.d.ts +4 -0
- package/dist/components/AvatarGroup/AvatarGroup.d.ts +21 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +14 -0
- package/dist/components/Descriptions/Descriptions.d.ts +21 -0
- package/dist/components/Highlight/Highlight.d.ts +13 -0
- package/dist/components/Icon/Icon.d.ts +1 -1
- package/dist/components/InputNumber/InputNumber.d.ts +25 -0
- package/dist/components/List/List.d.ts +18 -0
- package/dist/components/Markdown/Markdown.d.ts +14 -0
- package/dist/components/Markdown/parser.d.ts +32 -0
- package/dist/components/Menu/Menu.d.ts +25 -0
- package/dist/components/MessageBubble/MessageBubble.d.ts +20 -0
- package/dist/components/PasswordInput/PasswordInput.d.ts +21 -0
- package/dist/components/Result/Result.d.ts +17 -0
- package/dist/components/TagsInput/TagsInput.d.ts +21 -0
- package/dist/components/Timeline/Timeline.d.ts +25 -0
- package/dist/components/index.d.ts +27 -0
- package/dist/components/index.js +7 -1
- package/dist/components/style.css +677 -1
- package/dist/core/router.d.ts +2 -7
- package/dist/core/ws.d.ts +20 -2
- package/dist/db/postgres/connection.d.ts +24 -3
- package/dist/db/postgres/pool.d.ts +30 -8
- package/dist/db/redis/client.d.ts +43 -0
- package/dist/db/redis/connection.d.ts +12 -0
- package/dist/db/redis/pipeline.d.ts +26 -0
- package/dist/db/redis/pool.d.ts +27 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1059 -97
- package/dist/layout/weifuwu-layout.css +11 -1
- package/dist/messager/index.d.ts +1 -1
- package/dist/postgres/types.d.ts +2 -1
- package/dist/redis/types.d.ts +6 -0
- package/dist/scheduler/cron.d.ts +26 -0
- package/dist/scheduler/index.d.ts +69 -0
- package/dist/types.d.ts +3 -0
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ npm install weifuwu
|
|
|
62
62
|
|
|
63
63
|
**自研数据层** — `ctx.sql`(PG v3 协议)与 `ctx.redis`(RESP2 协议)为**自研客户端**:确定性输出、行为可预测、统一错误模型。jsonb 自动解码、TTL 安全 API、schema 写前校验——高频痛点(双重编码/parseRow 样板/`'EX'` 参数顺序)从根上消除。
|
|
64
64
|
|
|
65
|
-
> **实践验证**:多租户 AI 平台(`apps/agent-platform`——14 页 + 部门聊天 + 知识库 + HITL 审批)已完全运行在框架上:auth(userSystem)/ AI 引擎(ai)/ 实时消息(messager)/ UI(
|
|
65
|
+
> **实践验证**:多租户 AI 平台(`apps/agent-platform`——14 页 + 部门聊天 + 知识库 + HITL 审批)已完全运行在框架上:auth(userSystem)/ AI 引擎(ai)/ 实时消息(messager)/ UI(61 组件)/ 数据管道(ctx.api)零自研替代。框架哲学(中间件注入、诚实裁剪、机制与策略分离)经受住了真实复杂应用的检验——这也是我们确定「哪些进框架、哪些留应用层」的依据。
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
@@ -164,7 +164,7 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
164
164
|
|
|
165
165
|
- 访问页面:SPA 客户端渲染;SSR 页面内容直接进 HTML(`curl /` 可见,SEO)
|
|
166
166
|
- 改组件刷新即生效,无需构建步骤
|
|
167
|
-
- 完整可运行示例见 `apps/demo
|
|
167
|
+
- 完整可运行示例见 `apps/components-demo`(组件 cheatsheet)与 `apps/agent-platform`(全栈 SaaS 应用)
|
|
168
168
|
|
|
169
169
|
> 想**零后端、零构建**最快跑起来?直接跳到下面的「CDN 快速原型」。
|
|
170
170
|
|
|
@@ -235,14 +235,14 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
235
235
|
</html>
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
-
将此 HTML
|
|
238
|
+
将此 HTML 保存到本地用浏览器打开即可运行。
|
|
239
239
|
|
|
240
240
|
### CDN 资源地址说明
|
|
241
241
|
|
|
242
242
|
| 资源 | CDN 地址 | 说明 |
|
|
243
243
|
|------|---------|------|
|
|
244
244
|
| `weifuwu/client` | `https://unpkg.com/weifuwu@latest/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
|
|
245
|
-
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` |
|
|
245
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 61 个 UI 组件(Button, Card, Table, Modal, Icon 等) |
|
|
246
246
|
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 141 个主题 Token + 67 个布局原语 |
|
|
247
247
|
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
248
248
|
|
|
@@ -266,6 +266,7 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
266
266
|
| `weifuwu` | **userSystem** | 用户系统(scrypt 密码哈希 + 混合会话 + 多租户感知)→ `ctx.user` / `ctx.auth` / `ctx.tenantId` + `/api/auth/*` | Router, postgres |
|
|
267
267
|
| `weifuwu` | **messager** | 消息系统(会话/消息持久化 + WS 实时投递 + Redis 跨进程广播)→ `ctx.msg` + `/api/messages/*` | Router, postgres, (redis) |
|
|
268
268
|
| `weifuwu` | **queue** | 可靠任务队列(Redis Streams,at-least-once + DLQ)→ `ctx.queue` | Router, redis |
|
|
269
|
+
| `weifuwu` | **scheduler** | 计划任务(延时 `ctx.schedule` + cron `ctx.cron`/`ctx.cancelCron`,触发后入队) | Router, redis, queue |
|
|
269
270
|
| `weifuwu` | **ai** | LLM 对话(自研 OpenAI 兼容协议 + 自研 SSE 解码,默认 DeepSeek)→ `ctx.ai` + embedding + `ctx.ui.useChat` + `AiChat` | Router |
|
|
270
271
|
| `weifuwu/dev` | **dev loader** | Node loader:服务端直接跑 `.ts/.tsx`(`--import weifuwu/dev`) | esbuild |
|
|
271
272
|
| `weifuwu` | **graphql** | GraphQL 端点(支持 GraphiQL) | Router |
|
|
@@ -282,7 +283,7 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
282
283
|
| `weifuwu/client` | **ErrorBoundary** | 错误边界组件 | createApp |
|
|
283
284
|
| `weifuwu/client` | **lockScroll/trapFocus** | 滚动锁定 / 焦点陷阱工具 | — |
|
|
284
285
|
| `weifuwu/client` | **popup** | 弹层 fixed 定位工具(`computeFixedPos` / `computeFixedPosRect`) | — |
|
|
285
|
-
| `weifuwu/components` | **
|
|
286
|
+
| `weifuwu/components` | **61 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/client |
|
|
286
287
|
| `weifuwu/layout` | **CSS 布局** | 67 个布局原语 + 141 个主题 Token(也支持 `weifuwu/layout/style.css`) | — |
|
|
287
288
|
|
|
288
289
|
---
|
|
@@ -551,7 +552,7 @@ await server.stop(2000) // 超时毫秒
|
|
|
551
552
|
| `hostname` | `string` | `'0.0.0.0'` | 监听地址 |
|
|
552
553
|
| `signal` | `AbortSignal` | — | 通过信号停止 |
|
|
553
554
|
| `maxBodySize` | `number` | `10MB` | 请求体上限(0=无限) |
|
|
554
|
-
| `timeout` | `number` | `
|
|
555
|
+
| `timeout` | `number` | `120000` | Socket 超时(ms,2 分钟,适配 LLM 生成等长任务) |
|
|
555
556
|
| `keepAliveTimeout` | `number` | `5000` | Keep-Alive 超时 |
|
|
556
557
|
| `headersTimeout` | `number` | `6000` | 请求头超时 |
|
|
557
558
|
| `shutdown` | `boolean` | `true` | 自动注册 SIGTERM/SIGINT |
|
|
@@ -669,7 +670,9 @@ app.post('/transfer', async (req, ctx) => {
|
|
|
669
670
|
| **int8(超出安全范围)** | **`string`**(防静默丢精度,金额/ID 关键) |
|
|
670
671
|
| float / numeric | `number` |
|
|
671
672
|
| boolean | `boolean` |
|
|
672
|
-
| text / varchar / uuid
|
|
673
|
+
| text / varchar / uuid | `string` |
|
|
674
|
+
| **timestamptz** | **`Date`**(带时区,ISO 解析无本地时区魔法) |
|
|
675
|
+
| timestamp / date / interval | `string`(无时区语义——转 Date 按本地时区解析即时区魔法,诚实裁剪不转) |
|
|
673
676
|
| NULL | `null` |
|
|
674
677
|
|
|
675
678
|
### 类型层(查询泛型 + schema 写前校验)
|
|
@@ -699,9 +702,28 @@ await ctx.sql.insert('decks', { title: 'x', status: 'INVALID' }) // → Validati
|
|
|
699
702
|
| `ctx.sql.transaction(fn)` | 事务(回调收到 `{ query }`) |
|
|
700
703
|
| `ctx.sql.register(table, schema)` | 注册表结构(写前校验) |
|
|
701
704
|
| `ctx.sql.insert(table, row)` | schema 校验 + 参数化插入 |
|
|
705
|
+
| `ctx.sql.insertMany(table, rows[], { batchSize? })` | **批量插入**:多行 VALUES 单次往返(默认 500/批;所有行键必须一致) |
|
|
706
|
+
| `ctx.sql.update(table, set, where, { returning? })` | **参数化 UPDATE**:SET/WHERE 全部参数化,返回 `affectedRows` |
|
|
707
|
+
| `ctx.sql.delete(table, where)` | **参数化 DELETE**:WHERE 必填(防全表误删),返回 `affectedRows` |
|
|
702
708
|
| `ctx.sql\`...\` 内嵌片段` | 条件 SQL 片段(嵌套过滤,参数自动重编号) |
|
|
703
709
|
| `ctx.sql.close()` | 关闭连接池 |
|
|
704
710
|
|
|
711
|
+
### 影响行数(affectedRows)
|
|
712
|
+
|
|
713
|
+
`INSERT / UPDATE / DELETE / MERGE` 的返回行数组带**非枚举** `affectedRows` 属性(不干扰 `deepEqual`/`JSON.stringify`):
|
|
714
|
+
|
|
715
|
+
```ts
|
|
716
|
+
const r = await ctx.sql`UPDATE messages SET read = true WHERE id = ${id}`
|
|
717
|
+
if (r.affectedRows === 0) return new Response('not found', { status: 404 })
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
// 批量插入:100 行 1 次往返
|
|
722
|
+
await ctx.sql.insertMany('agent_logs', logs, { batchSize: 500 })
|
|
723
|
+
// 语义化更新/删除:WHERE 全参数化 + 返回影响行数
|
|
724
|
+
await ctx.sql.update('users', { role: 'admin' }, { id: userId })
|
|
725
|
+
await ctx.sql.delete('messages', { id: msgId })
|
|
726
|
+
|
|
705
727
|
### 条件片段(嵌套过滤)
|
|
706
728
|
|
|
707
729
|
```ts
|
|
@@ -721,7 +743,8 @@ const rows = await ctx.sql`
|
|
|
721
743
|
| `max`(或 `poolSize`) | `number` | `10` | 连接池大小 |
|
|
722
744
|
| `acquireTimeoutMs` | `number` | `30000` | 池全忙时 acquire 超时(防饿死,0=无限) |
|
|
723
745
|
| `statementTimeoutMs`(或 `statementTimeout`) | `number` | `0` | 语句超时(慢查询保护,0=禁用) |
|
|
724
|
-
| `
|
|
746
|
+
| `idleTimeoutMs` | `number` | `0` | 空闲连接回收(超时未用关闭,容量收缩后自动重建;0=禁用) |
|
|
747
|
+
| `onQuery` | `(sql, durationMs, rowCount, traceId?) => void` | — | 查询观测钩子;第 4 参数为请求级 traceId(`x-trace-id` 头经 ALS 传播) |
|
|
725
748
|
|
|
726
749
|
### 幂等迁移(内置)
|
|
727
750
|
|
|
@@ -816,6 +839,12 @@ await ctx.redis.set('user', 1) // 实际写入 'api:user'
|
|
|
816
839
|
| `cache(key, fn, ttl)` | 缓存读-算-写(null 不缓存防穿透) |
|
|
817
840
|
| `publish(channel, msg)` | Pub-Sub 发布 |
|
|
818
841
|
| `createSubscriber()` | 独立订阅连接(`subscribe`/`psubscribe` 回调式) |
|
|
842
|
+
| `hset / hget / hgetall / hdel` | hash 字段读写(`hgetall` → `Record`,缺失 `{}`) |
|
|
843
|
+
| `lpush / rpush / lpop / rpop / lrange` | list 队列操作(`lrange` 支持负数区间) |
|
|
844
|
+
| `sadd / srem / smembers` | set 成员操作(`sadd` 重复不加) |
|
|
845
|
+
| `zadd / zrange` | zset 有序集(score 升序) |
|
|
846
|
+
| `mget / mset / exists / setnx / incrby` | 批量读写 / 存在性 / 原子设值(锁基础)/ 增量 |
|
|
847
|
+
| `pipeline()` | 管道:批量命令一次往返(池级,key 自动加前缀) |
|
|
819
848
|
| `command(name, ...args)` | 底层命令透传 |
|
|
820
849
|
| `close()` | 关闭连接池 |
|
|
821
850
|
|
|
@@ -824,11 +853,50 @@ await ctx.redis.set('user', 1) // 实际写入 'api:user'
|
|
|
824
853
|
| `url` | `string` | `REDIS_URL` 环境变量 | 连接字符串 |
|
|
825
854
|
| `poolSize` | `number` | `5` | 连接池大小 |
|
|
826
855
|
| `keyPrefix` | `string` | `''` | 所有 key 自动加前缀(多应用隔离) |
|
|
856
|
+
| `commandTimeoutMs` | `number` | `0` | 命令超时(阻塞命令 resolve(null);防挂起。0=禁用) |
|
|
857
|
+
| `socketTimeoutMs` | `number` | `0` | socket 响应超时(僵尸连接自愈:pending 有命令且超时无数据 → 主动断开重连。0=禁用) |
|
|
858
|
+
|
|
859
|
+
> **连接健康**:断线自动剔除死连接并重建(池不萎缩);`CLIENT KILL`/网络抖动后服务自愈,命令不命中死连接。
|
|
827
860
|
|
|
828
861
|
> **裁剪声明**:集群(MOVED 路由)/ 哨兵 / 自动管道不支持(standalone 优先)。
|
|
829
862
|
|
|
830
863
|
---
|
|
831
864
|
|
|
865
|
+
## scheduler — 计划任务(即时/延时/cron)
|
|
866
|
+
|
|
867
|
+
> 依赖 `queue`(触发后入队执行)。三类任务:即时(queue.add 已有)、延时(`ctx.schedule`)、定时(`ctx.cron`)。
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
import { queue, scheduler } from 'weifuwu'
|
|
871
|
+
|
|
872
|
+
const q = queue()
|
|
873
|
+
app.use(q)
|
|
874
|
+
app.use(scheduler({ queue: q })) // 依赖 ctx.queue(触发后入队)
|
|
875
|
+
|
|
876
|
+
// 延时任务(单次):delayMs 或指定时间
|
|
877
|
+
await ctx.schedule('email.send', { to, body }, { delayMs: 30_000 })
|
|
878
|
+
await ctx.schedule('report.build', {}, { when: new Date('2026-09-01T00:00:00Z') })
|
|
879
|
+
|
|
880
|
+
// cron 定时任务(重复):每分钟触发 → 入队执行
|
|
881
|
+
ctx.cron('* * * * *', 'heartbeat.check', { scope: 'health' })
|
|
882
|
+
// 改需求 = 重新注册(同 name 覆盖更新,旧定义不残留)
|
|
883
|
+
ctx.cron('*/5 * * * *', 'heartbeat.check', { scope: 'health' })
|
|
884
|
+
// 停用 = cancel(删定义 + 清理 pending 触发点)
|
|
885
|
+
await ctx.cancelCron('heartbeat.check')
|
|
886
|
+
|
|
887
|
+
// 执行端:与 queue 完全一致
|
|
888
|
+
const worker = ctx.queue.worker('email.send', async (job) => { ... })
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
- **延时**:ZSET(score=触发时间戳)+ 守护循环(独立连接)→ 到期 `ZREM` 原子抢占(多实例不重复)→ `queue.add`
|
|
892
|
+
- **多应用隔离**:`scheduler({ prefix })`——ZSET/HASH 应用级共享,多应用共用 redis 时必须各自 prefix(同应用多实例共享 prefix = 协作消费)
|
|
893
|
+
- **cron**:HASH 注册表(**field = name,同 name 重新注册 = 覆盖更新**,改表达式不残留旧定义)+ 滚动生成触发点(`ZADD NX` 幂等)→ 复用延时链路;`nextRunAt` 原子推进
|
|
894
|
+
- **取消**:`ctx.cancelCron(name)` 删定义 + 清理 pending 触发点(停用 cron 必须 cancel——定义无 TTL 会累积)
|
|
895
|
+
- **崩溃恢复**:未消费触发点留在 ZSET,重启后补扫立即触发(at-least-once,幂等由业务保证)
|
|
896
|
+
- **cron 表达式**:5 字段(分 时 日 月 周),支持 `*`/步进/列表/范围;时区 = 服务器本地;非法表达式注册即抛错
|
|
897
|
+
- **裁剪**:❌ cron 秒/年/别名(@daily)/特殊字符(L/W/#)、时区配置、单次任务取消(v2)、分布式锁(原子命令抢占替代)
|
|
898
|
+
- **文档红线**:cron 定义持久化在 HASH——进程重启后守护循环恢复即继续触发(无需重新注册);**停用必须 `cancelCron`**(定义无 TTL,不取消会永久触发)
|
|
899
|
+
|
|
832
900
|
## ui — SSR 渲染 + JS/CSS 编译
|
|
833
901
|
|
|
834
902
|
```ts
|
|
@@ -1095,7 +1163,8 @@ app.get('/secure', () => {
|
|
|
1095
1163
|
| API | 说明 |
|
|
1096
1164
|
|-----|------|
|
|
1097
1165
|
| `new HttpError(msg, status)` | 创建 HTTP 错误,name = 'HttpError' |
|
|
1098
|
-
|
|
1166
|
+
|
|
1167
|
+
> 请求体上限常量 `DEFAULT_MAX_BODY`(10MB)见上方 serve 选项表 `maxBodySize`。
|
|
1099
1168
|
|
|
1100
1169
|
---
|
|
1101
1170
|
|
|
@@ -1991,7 +2060,7 @@ createApp()
|
|
|
1991
2060
|
locale: 'zh-CN',
|
|
1992
2061
|
messages: {
|
|
1993
2062
|
'title': '仪表盘',
|
|
1994
|
-
'welcome': '
|
|
2063
|
+
'welcome': '欢迎光临',
|
|
1995
2064
|
},
|
|
1996
2065
|
}))
|
|
1997
2066
|
.mount('#root', App)
|
|
@@ -2226,7 +2295,7 @@ import type { RouterOptions } from 'weifuwu/client'
|
|
|
2226
2295
|
|
|
2227
2296
|
# 组件库 (`weifuwu/components`)
|
|
2228
2297
|
|
|
2229
|
-
|
|
2298
|
+
61 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
|
|
2230
2299
|
|
|
2231
2300
|
```ts
|
|
2232
2301
|
import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
|
|
@@ -2273,35 +2342,35 @@ import 'weifuwu/components/style.css' // 包含 Token + 67 布局原语 + 组
|
|
|
2273
2342
|
<Alert variant="warning" closable>注意:磁盘空间不足</Alert>
|
|
2274
2343
|
|
|
2275
2344
|
// ├─ 标签 / 徽标 / 头像
|
|
2276
|
-
<Badge
|
|
2277
|
-
<Badge variant="success">通过</Badge>
|
|
2278
|
-
<Tag variant="
|
|
2345
|
+
<Badge variant="primary">消息</Badge>
|
|
2346
|
+
<Badge variant="success" dot>通过</Badge>
|
|
2347
|
+
<Tag variant="primary" closable onClose={() => {}}>标签</Tag>
|
|
2279
2348
|
<Avatar name="张三" size="lg" />
|
|
2280
2349
|
|
|
2281
2350
|
// ├─ 卡片 / 统计卡片
|
|
2282
|
-
<Card
|
|
2283
|
-
<StatCard
|
|
2351
|
+
<Card variant="outlined" padding="md">卡片内容</Card>
|
|
2352
|
+
<StatCard label="总用户" value="1,234" trend="up" trendLabel="12%" />
|
|
2284
2353
|
|
|
2285
2354
|
// ├─ 标签页 / 下拉菜单
|
|
2286
|
-
<Tabs items={[{ key: 'a', label: '标签A' }, { key: 'b', label: '标签B' }]}
|
|
2287
|
-
<Dropdown items={[{ label: '编辑', onClick: () => {} }, { label: '删除',
|
|
2355
|
+
<Tabs items={[{ key: 'a', label: '标签A' }, { key: 'b', label: '标签B' }]} active="a" onChange={setTab} />
|
|
2356
|
+
<Dropdown items={[{ label: '编辑', onClick: () => {} }, { label: '删除', variant: 'danger' }]}>操作</Dropdown>
|
|
2288
2357
|
|
|
2289
2358
|
// ├─ 分页 / 步骤条
|
|
2290
2359
|
<Pagination total={100} page={1} pageSize={10} onChange={setPage} />
|
|
2291
|
-
<Steps items={[{
|
|
2360
|
+
<Steps items={[{ key: 's1', label: '第一步' }, { key: 's2', label: '第二步' }]} current={1} />
|
|
2292
2361
|
|
|
2293
2362
|
// ├─ 滑块 / 进度条
|
|
2294
2363
|
<Slider min={0} max={100} value={50} onChange={setValue} />
|
|
2295
|
-
<ProgressBar value={75}
|
|
2364
|
+
<ProgressBar value={75} label="75%" />
|
|
2296
2365
|
|
|
2297
2366
|
// ├─ 面包屑 / 分割线
|
|
2298
2367
|
<Breadcrumb items={[{ label: '首页' }, { label: '用户管理' }]} />
|
|
2299
2368
|
<Divider />
|
|
2300
|
-
<Divider
|
|
2369
|
+
<Divider>分割文字</Divider>
|
|
2301
2370
|
|
|
2302
2371
|
// ├─ 加载 / 空状态 / 骨架屏
|
|
2303
2372
|
<Loading text="加载中..." />
|
|
2304
|
-
<EmptyState
|
|
2373
|
+
<EmptyState text="暂无数据" hint="请先创建一条记录"><Button>新建</Button></EmptyState>
|
|
2305
2374
|
<Skeleton variant="text" lines={3} />
|
|
2306
2375
|
<Skeleton variant="table" lines={5} cols={4} />
|
|
2307
2376
|
<Skeleton variant="avatar" />
|
|
@@ -2366,42 +2435,53 @@ props 变化 ──────────────────────
|
|
|
2366
2435
|
|-----|--------|-----------|------|
|
|
2367
2436
|
| Button | `Button` | `variant`, `size`, `loading`, `disabled`, `block`, `type` | 按钮 |
|
|
2368
2437
|
| Input | `Input` | `label`, `name`, `type`, `value`, `placeholder`, `required`, `disabled`, `error`, `hint`, `onInput`, `onChange` | 输入框 |
|
|
2369
|
-
| Textarea | `Textarea` | `rows`, `
|
|
2438
|
+
| Textarea | `Textarea` | `rows`, `maxLength`, `showCount`, `error` | 文本域 |
|
|
2370
2439
|
| Select | `Select` | `options: SelectOption[]`, `placeholder`, `searchable` | 下拉选择 |
|
|
2371
2440
|
|
|
2372
2441
|
### 表单选择
|
|
2373
2442
|
|
|
2374
2443
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2375
2444
|
|-----|--------|-----------|------|
|
|
2376
|
-
| Checkbox | `Checkbox` | `checked`, `label`, `
|
|
2377
|
-
| Switch | `Switch` | `checked`, `
|
|
2445
|
+
| Checkbox | `Checkbox` | `checked`, `label`, `onChange` | 复选框 |
|
|
2446
|
+
| Switch | `Switch` | `checked`, `label`, `onChange` | 开关 |
|
|
2378
2447
|
| RadioGroup | `RadioGroup` | `options: RadioOption[]`, `value`, `name` | 单选组 |
|
|
2379
|
-
| Slider | `Slider` | `min`, `max`, `step`, `value`, `
|
|
2448
|
+
| Slider | `Slider` | `min`, `max`, `step`, `value`, `onChange` | 滑块 |
|
|
2380
2449
|
|
|
2381
2450
|
### 表单增强
|
|
2382
2451
|
|
|
2383
2452
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2384
2453
|
|-----|--------|-----------|------|
|
|
2385
2454
|
| Form | `Form` | `onSubmit`, `validation` | 表单容器 |
|
|
2386
|
-
| Field | `Field` | `label`, `error`, `required`, `
|
|
2387
|
-
| FileUpload | `FileUpload` | `accept`, `multiple`, `maxSize`, `
|
|
2388
|
-
| SearchInput | `SearchInput` | `value`, `placeholder`, `
|
|
2455
|
+
| Field | `Field` | `label`, `error`, `required`, `hint` | 字段包装 |
|
|
2456
|
+
| FileUpload | `FileUpload` | `accept`, `multiple`, `maxSize`, `onChange` | 文件上传 |
|
|
2457
|
+
| SearchInput | `SearchInput` | `value`, `placeholder`, `onInput`, `onClear` | 搜索框 |
|
|
2389
2458
|
| SegmentedControl | `SegmentedControl` | `options: SegmentedOption[]`, `value`, `onChange`, `size` | 分段选择器 |
|
|
2390
|
-
| ProgressBar | `ProgressBar` | `value`, `max`, `
|
|
2459
|
+
| ProgressBar | `ProgressBar` | `value`, `max`, `label`, `showValue` | 进度条 |
|
|
2460
|
+
| InputNumber | `InputNumber` | `value`, `min`, `max`, `step`, `precision`, `onChange` | 数字输入(增减按钮) |
|
|
2461
|
+
| PasswordInput | `PasswordInput` | `value`, `onInput`, `autoComplete` | 密码输入(可见性切换) |
|
|
2462
|
+
| TagsInput | `TagsInput` | `value: string[]`, `maxTags`, `allowDuplicates` | 标签输入(中文输入法感知) |
|
|
2391
2463
|
|
|
2392
2464
|
### 数据展示
|
|
2393
2465
|
|
|
2394
2466
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2395
2467
|
|-----|--------|-----------|------|
|
|
2396
|
-
| Table | `Table` | `columns: TableColumn[]`, `data`, `loading`, `
|
|
2397
|
-
| Card | `Card` | `
|
|
2398
|
-
| Badge | `Badge` | `variant: BadgeVariant`, `
|
|
2399
|
-
| Tag | `Tag` | `variant`, `closable`, `onClose` | 标签 |
|
|
2400
|
-
| Avatar | `Avatar` | `src`, `name`, `size`, `
|
|
2401
|
-
|
|
|
2402
|
-
|
|
|
2403
|
-
|
|
|
2404
|
-
|
|
|
2468
|
+
| Table | `Table` | `columns: TableColumn[]`, `data`, `loading`, `sortKey`, `sortOrder`, `onSort`, `onRowClick` | 表格 |
|
|
2469
|
+
| Card | `Card` | `variant`, `outlined`, `padding`, `clickable`, `hover`, `active`, `onClick` | 卡片 |
|
|
2470
|
+
| Badge | `Badge` | `variant: BadgeVariant`, `dot` | 徽标 |
|
|
2471
|
+
| Tag | `Tag` | `variant: 'default'\|'primary'\|'success'\|'danger'`, `closable`, `onClose` | 标签 |
|
|
2472
|
+
| Avatar | `Avatar` | `src`, `name`, `size`, `color` | 头像 |
|
|
2473
|
+
| AvatarGroup | `AvatarGroup` | `items`, `max`, `size` | 头像组(堆叠 + 溢出 +N) |
|
|
2474
|
+
| Timeline | `Timeline` | `items: TimelineItem[]`, `mode`, `reverse` | 时间线(执行日志/历史) |
|
|
2475
|
+
| Descriptions | `Descriptions` | `items: DescriptionItem[]`, `column`, `bordered` | 描述列表(详情页字段) |
|
|
2476
|
+
| Markdown | `Markdown` | `content` | AI 回复渲染(安全子集 parser) |
|
|
2477
|
+
| CodeBlock | `CodeBlock` | `code`, `lang`, `title` | 代码块(语言标签 + 复制) |
|
|
2478
|
+
| Highlight | `Highlight` | `text`, `query: string \| string[]` | 搜索词高亮(mark) |
|
|
2479
|
+
| List | `List` | `items`, `renderItem`, `divided`, `header/footer/empty` | 通用列表 |
|
|
2480
|
+
| Result | `Result` | `status`, `title`, `desc`, `extra` | 结果页(成功/失败/警告/信息) |
|
|
2481
|
+
| Icon | `Icon` | `name: IconName`, `size` | 图标(内置 25 个 stroke 图标,currentColor 随字号) |
|
|
2482
|
+
| StatCard | `StatCard` | `label`, `value`, `trend: 'up'\|'down'`, `trendLabel`, `icon`, `animate` | 统计卡片 |
|
|
2483
|
+
| PageHeader | `PageHeader` | `title`, `sub`, `display` | 页面标题(actions 放 children) |
|
|
2484
|
+
| Img | `Img` | `src`, `alt`, `fallback`, `loading`, `width`, `height` | 图片(含 fallback) |
|
|
2405
2485
|
| InView | `InView` | `once`, `threshold`, `rootMargin`, `placeholder`, `onEnter` | 进入视窗后懒加载内容 |
|
|
2406
2486
|
|
|
2407
2487
|
### 数据反馈
|
|
@@ -2410,13 +2490,13 @@ props 变化 ──────────────────────
|
|
|
2410
2490
|
|-----|--------|-----------|------|
|
|
2411
2491
|
| Modal | `Modal` | `open`, `title`, `onClose`, `width`, `footer`, `closable` | 模态框 |
|
|
2412
2492
|
| Confirm | `Confirm` | `open`, `message`, `confirmText`, `cancelText`, `variant`, `onConfirm`, `onCancel` | 确认对话框(同 `ctx.confirm()` 命令式) |
|
|
2413
|
-
| Drawer | `Drawer` | `open`, `title`, `
|
|
2493
|
+
| Drawer | `Drawer` | `open`, `title`, `position: DrawerPosition`, `onClose`, `footer` | 抽屉 |
|
|
2414
2494
|
| Tooltip | `Tooltip` | `content`, `position: TooltipPosition`, `disabled` | 工具提示(hover/focus 触发) |
|
|
2415
2495
|
| Popover | `Popover` | `content`, `position: PopoverPosition`, `trigger`, `open`, `onOpenChange`, `disabled` | 弹出层 |
|
|
2416
|
-
| Toast | `Toast` | `
|
|
2417
|
-
| Alert | `Alert` | `variant: AlertVariant`, `
|
|
2418
|
-
| Loading | `Loading` | `
|
|
2419
|
-
| EmptyState | `EmptyState` | `
|
|
2496
|
+
| Toast | `Toast` | `toasts: ToastItem[]`, `position`, `max`, `onRemove` | 消息提示 |
|
|
2497
|
+
| Alert | `Alert` | `variant: AlertVariant`, `closable`, `onClose` | 警告提示(内容放 children) |
|
|
2498
|
+
| Loading | `Loading` | `text` | 加载中 |
|
|
2499
|
+
| EmptyState | `EmptyState` | `icon`, `text`, `hint` | 空状态(操作放 children) |
|
|
2420
2500
|
| Skeleton | `Skeleton` | `variant: SkeletonVariant`, `lines`, `cols`, `width`, `height` | 骨架屏 |
|
|
2421
2501
|
|
|
2422
2502
|
### 导航组件
|
|
@@ -2424,11 +2504,12 @@ props 变化 ──────────────────────
|
|
|
2424
2504
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2425
2505
|
|-----|--------|-----------|------|
|
|
2426
2506
|
| Breadcrumb | `Breadcrumb` | `items: BreadcrumbItem[]` | 面包屑 |
|
|
2427
|
-
|
|
|
2428
|
-
|
|
|
2507
|
+
| Menu | `Menu` | `items: MenuItem[]`, `activeKey`, `onSelect` | 侧栏导航(分组 + 图标 + 方向键) |
|
|
2508
|
+
| Tabs | `Tabs` | `items: TabItem[]`, `active`, `onChange` | 标签页 |
|
|
2509
|
+
| Dropdown | `Dropdown` | `trigger`, `items: DropdownItem[]`, `open`, `onOpenChange` | 下拉菜单 |
|
|
2429
2510
|
| Pagination | `Pagination` | `total`, `page`, `pageSize`, `onChange` | 分页 |
|
|
2430
|
-
| Steps | `Steps` | `items: StepItem[]
|
|
2431
|
-
| Accordion | `Accordion` | `items: AccordionItem[]`, `multiple
|
|
2511
|
+
| Steps | `Steps` | `items: StepItem[]`(`{ key, label }`), `current`, `active` | 步骤条 |
|
|
2512
|
+
| Accordion | `Accordion` | `items: AccordionItem[]`, `multiple` | 手风琴 |
|
|
2432
2513
|
|
|
2433
2514
|
### 图表
|
|
2434
2515
|
|
|
@@ -2442,13 +2523,14 @@ props 变化 ──────────────────────
|
|
|
2442
2523
|
|
|
2443
2524
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2444
2525
|
|-----|--------|-----------|------|
|
|
2445
|
-
| Divider | `Divider` | `
|
|
2526
|
+
| Divider | `Divider` | `vertical` | 分割线(水平带文字放 children,`vertical` 垂直) |
|
|
2446
2527
|
|
|
2447
2528
|
### AI 交互原语(wf: 协议配套)
|
|
2448
2529
|
|
|
2449
2530
|
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2450
2531
|
|-----|--------|-----------|------|
|
|
2451
2532
|
| AiChat | `AiChat` | `chat`, `maxHeight?`, `labels?`, `renderMessage?`, `renderToolArgs?` | 标准 AI 对话界面:气泡 + 工具卡 + 审批卡 + 自动滚动 + 错误重试(接收 `ctx.ui.useChat()` handle) |
|
|
2533
|
+
| MessageBubble | `MessageBubble` | `content`, `role`, `status`, `actions` | 独立消息气泡(业务聊天页复用) |
|
|
2452
2534
|
| ToolCallCard | `ToolCallCard` | `call`, `progress?`, `result?`, `renderArgs?` | 工具调用卡片:running(进度条)/ ok / error 三态(协议 §4) |
|
|
2453
2535
|
| ApprovalCard | `ApprovalCard` | `request`, `status?`, `onApprove`, `onReject` | 人工审批卡片:待批(允许/拒绝+备注)/ 已批 / 已拒 / 超时(协议 §4.5) |
|
|
2454
2536
|
|
|
@@ -2708,7 +2790,7 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2708
2790
|
</style>
|
|
2709
2791
|
```
|
|
2710
2792
|
|
|
2711
|
-
完整的零样式示例:`apps/
|
|
2793
|
+
完整的零样式示例:`apps/components-demo`(组件 + 原语即插即用,无手写样式)。
|
|
2712
2794
|
|
|
2713
2795
|
诚实例外(合理场景,仍可内联 `<style>` 解决):打印/PDF 导出规则、第三方库宿主样式、
|
|
2714
2796
|
业务特有的一次性视觉(如色板选择器交互)。
|
|
@@ -2799,7 +2881,7 @@ const LoginPage = (_init, ctx) => {
|
|
|
2799
2881
|
|
|
2800
2882
|
return (props) =>
|
|
2801
2883
|
h('div', { class: 'wf-stack', style: { maxWidth: 400, margin: '40px auto' } },
|
|
2802
|
-
h(Card, {
|
|
2884
|
+
h(Card, { padding: 'lg' },
|
|
2803
2885
|
h('div', { class: 'wf-stack', style: { gap: 'var(--wf-space-md)' } },
|
|
2804
2886
|
h('h2', {}, '登录'),
|
|
2805
2887
|
h(Form, {
|
|
@@ -2847,7 +2929,7 @@ const UserList = (_init, ctx) => {
|
|
|
2847
2929
|
|
|
2848
2930
|
return h('div', { class: 'wf-stack', style: { gap: 'var(--wf-space-md)' } },
|
|
2849
2931
|
h('div', { class: 'wf-row', style: { justifyContent: 'space-between', alignItems: 'center' } },
|
|
2850
|
-
h(SearchInput, { placeholder: '搜索用户...', value: $.keyword,
|
|
2932
|
+
h(SearchInput, { placeholder: '搜索用户...', value: $.keyword, onInput: (e: Event) => { $.keyword = (e.target as HTMLInputElement).value } }),
|
|
2851
2933
|
h(Button, { variant: 'primary' }, '新建用户'),
|
|
2852
2934
|
),
|
|
2853
2935
|
h(Table, {
|
|
@@ -2956,8 +3038,16 @@ app.get('/api/search', async (req, ctx) => {
|
|
|
2956
3038
|
await ctx.limit('search', { max: 30, windowMs: 60_000 }) // 手动限流,超限抛 429
|
|
2957
3039
|
})
|
|
2958
3040
|
|
|
2959
|
-
//
|
|
2960
|
-
app.
|
|
3041
|
+
// 登录/注册防爆破:ctx.limit 默认按 IP 维度(每 IP 独立计数)
|
|
3042
|
+
app.post('/api/auth/register', async (req, ctx) => {
|
|
3043
|
+
await ctx.limit('register', { max: 5, windowMs: 60_000 }) // 每 IP 每分钟 5 次
|
|
3044
|
+
})
|
|
3045
|
+
|
|
3046
|
+
// 系统级总量限制:scope: 'global' 全局共享维度
|
|
3047
|
+
await ctx.limit('total-jobs', { max: 1000, windowMs: 60_000, scope: 'global' })
|
|
3048
|
+
|
|
3049
|
+
// 登录防爆破(配合 userSystem):组合键 ip:email(key 接收标准 Request,取头拿 IP)
|
|
3050
|
+
app.use(rateLimit({ key: (req) => `login:${req.headers.get('x-forwarded-for')}:${req.headers.get('x-user-email')}`, max: 5, windowMs: 15 * 60_000 }))
|
|
2961
3051
|
```
|
|
2962
3052
|
|
|
2963
3053
|
| 选项 | 默认 | 说明 |
|
|
@@ -3023,7 +3113,7 @@ await msg.migrate() // 幂等建表(conversations + members + messa
|
|
|
3023
3113
|
app.use(db)
|
|
3024
3114
|
app.use(msg) // 注入 ctx.msg
|
|
3025
3115
|
msg.routes(app) // /api/messages/*(会话/历史/发消息/已读)
|
|
3026
|
-
app.ws('/ws',
|
|
3116
|
+
app.ws('/ws', msg.handler()) // 标准 WS 协议内置
|
|
3027
3117
|
|
|
3028
3118
|
// 业务代码:持久化 + 鉴权 + 广播 + 未读 + 历史,一次调用
|
|
3029
3119
|
const conv = await ctx.msg.createConversation(ctx.user.id, { type: 'group', memberIds: ['u2'] })
|
|
@@ -3034,7 +3124,8 @@ ctx.msg.sendTo('u2', { type: 'mention' }) // 用户维度点对点
|
|
|
3034
3124
|
|
|
3035
3125
|
- **数据模型**:`_weifuwu_conversations` / `_weifuwu_conversation_members` / `_weifuwu_messages`(`sender_type + sender_id` 不 FK users——user/agent/system 消息天然可存);direct 会话同对用户唯一、历史游标分页、未读数(`last_read_at`)、编辑/删除软删
|
|
3036
3126
|
- **实时协议内置**:`handler()` 提供 `connected / subscribe→subscribed / unsubscribe / ping→pong`——前端 `ctx.ws.send({ type: 'subscribe', room })` 直接可用,两端协议由框架定义
|
|
3037
|
-
- **跨进程**:`redis` 选项 → Redis pub/sub 广播(psubscribe 模式),多实例部署天然一致;无 redis
|
|
3127
|
+
- **跨进程**:`redis` 选项 → Redis pub/sub 广播(psubscribe 模式),多实例部署天然一致;无 redis 优雅降级单进程。
|
|
3128
|
+
**环回去重**:`broadcast` = 本地直发 + Redis publish,本实例的 subscriber 会收到自己 publish 的消息——publish 携带实例唯一标识 `_pid`(`wf:{pid}:{seq}`),订阅回调跳过自己的环回,保证每个事件恰好投递一次(防 token 级事件重复/乱序)
|
|
3038
3129
|
- **与 userSystem 咬合**:`sendTo(ctx.user.id)` 按身份路由、`createConversation(ctx.user.id)` 创建者即身份、成员校验自动对齐——身份是消息的路由,消息是身份的交互
|
|
3039
3130
|
- **裁剪**:已读回执状态机(只做未读数)、附件存储、全文搜索、消息确认/重试(可靠投递用 queue)、移动端推送
|
|
3040
3131
|
|
|
@@ -3153,7 +3244,7 @@ return () => <AiChat chat={$} />
|
|
|
3153
3244
|
|
|
3154
3245
|
```ts
|
|
3155
3246
|
app.use(redis())
|
|
3156
|
-
app.use(rateLimit({ key: (req) => `login:${req.
|
|
3247
|
+
app.use(rateLimit({ key: (req) => `login:${req.headers.get('x-forwarded-for')}`, max: 5, windowMs: 60_000 })) // 防爆破
|
|
3157
3248
|
app.use(email({ from: 'no-reply@x.com', adapter: 'resend', resend: { apiKey } }))
|
|
3158
3249
|
app.use(db)
|
|
3159
3250
|
app.use(users)
|
package/dist/client/diff.d.ts
CHANGED
|
@@ -11,5 +11,17 @@ export declare function patchValue(parent: Node, oldNode: Node | null, oldInput:
|
|
|
11
11
|
export declare function patchProps(el: Element, oldProps: any, newProps: any): void;
|
|
12
12
|
/** 为无 key 的子节点自动分配位置 key,确保 keyed diff 正确性 */
|
|
13
13
|
export declare function ensureKeys(oldChildren: any[], newChildren: any[]): void;
|
|
14
|
+
/**
|
|
15
|
+
* 将 children 映射到它们实际产生的 DOM 节点(每个 child 一个 Node[])。
|
|
16
|
+
*
|
|
17
|
+
* 1 VNode ≠ 1 DOM 节点:Fragment 展开成多个直属节点(_childNodes),
|
|
18
|
+
* null/boolean/Portal 产生 0 个。explicitNodes 提供限定范围(Fragment 子项
|
|
19
|
+
* 用其自身 _childNodes),否则从 source(parent.childNodes 或给定快照)按计数游标分配。
|
|
20
|
+
*/
|
|
21
|
+
export declare function mapChildDomNodes(source: Node[], children: any[]): (Node[] | null)[];
|
|
14
22
|
export declare function normalize(children: any): any[];
|
|
15
|
-
|
|
23
|
+
/**
|
|
24
|
+
* keyed 子节点 diff。返回每个 new child 的 DOM 节点范围(newNodes,可为空)。
|
|
25
|
+
* oldNodes:old children → 实际 DOM 节点(mapChildDomNodes 结果)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function patchKeyedChildren(parent: Node, oldChildren: any[], newChildren: any[], ctx: WfuiContext, oldNodes?: (Node[] | null)[], rangeStart?: Node | null): (Node[] | null)[];
|