weifuwu 0.36.2 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +97 -3
  2. package/dist/client/app.d.ts +4 -7
  3. package/dist/client/i18n.d.ts +27 -0
  4. package/dist/client/index.d.ts +4 -0
  5. package/dist/client/index.js +171 -54
  6. package/dist/client/locale/en_US.d.ts +38 -0
  7. package/dist/client/locale/zh_CN.d.ts +38 -0
  8. package/dist/components/Accordion/Accordion.d.ts +11 -0
  9. package/dist/components/Alert/Alert.d.ts +9 -0
  10. package/dist/components/Avatar/Avatar.d.ts +7 -0
  11. package/dist/components/Badge/Badge.d.ts +8 -0
  12. package/dist/components/Breadcrumb/Breadcrumb.d.ts +9 -0
  13. package/dist/components/Button/Button.d.ts +12 -0
  14. package/dist/components/Card/Card.d.ts +9 -0
  15. package/dist/components/Checkbox/Checkbox.d.ts +8 -0
  16. package/dist/components/Divider/Divider.d.ts +6 -0
  17. package/dist/components/Drawer/Drawer.d.ts +11 -0
  18. package/dist/components/Dropdown/Dropdown.d.ts +14 -0
  19. package/dist/components/EmptyState/EmptyState.d.ts +8 -0
  20. package/dist/components/Field/Field.d.ts +9 -0
  21. package/dist/components/FileUpload/FileUpload.d.ts +13 -0
  22. package/dist/components/Form/Form.d.ts +6 -0
  23. package/dist/components/Input/Input.d.ts +15 -0
  24. package/dist/components/Loading/Loading.d.ts +5 -0
  25. package/dist/components/Modal/Modal.d.ts +9 -0
  26. package/dist/components/PageHeader/PageHeader.d.ts +7 -0
  27. package/dist/components/Pagination/Pagination.d.ts +8 -0
  28. package/dist/components/ProgressBar/ProgressBar.d.ts +8 -0
  29. package/dist/components/RadioGroup/RadioGroup.d.ts +14 -0
  30. package/dist/components/SearchInput/SearchInput.d.ts +8 -0
  31. package/dist/components/Select/Select.d.ts +18 -0
  32. package/dist/components/Slider/Slider.d.ts +10 -0
  33. package/dist/components/StatCard/StatCard.d.ts +9 -0
  34. package/dist/components/Steps/Steps.d.ts +12 -0
  35. package/dist/components/Switch/Switch.d.ts +8 -0
  36. package/dist/components/Table/Table.d.ts +13 -0
  37. package/dist/components/Tabs/Tabs.d.ts +12 -0
  38. package/dist/components/Tag/Tag.d.ts +8 -0
  39. package/dist/components/Textarea/Textarea.d.ts +13 -0
  40. package/dist/components/Toast/Toast.d.ts +12 -0
  41. package/dist/components/Tooltip/Tooltip.d.ts +9 -0
  42. package/dist/components/index.d.ts +75 -0
  43. package/dist/components/index.js +887 -0
  44. package/dist/components/style.css +1779 -0
  45. package/package.json +7 -2
package/README.md CHANGED
@@ -22,7 +22,8 @@ npm install weifuwu
22
22
  | **redis** | `weifuwu` | Redis 客户端 → `ctx.redis` | `Router` |
23
23
  | **ui** | `weifuwu` | SSR 渲染 + 动态 JS/CSS 编译 → `ctx.ui` | `Router` |
24
24
  | **graphql** | `weifuwu` | GraphQL 端点 | `Router` |
25
- | **client** | `weifuwu/client` | 前端 VDOM + Proxy 框架 | — |
25
+ | **client** | `weifuwu/client` | 前端 VDOM + Proxy 框架 + i18n + ErrorBoundary | — |
26
+ | **components** | `weifuwu/components` | 34 个 HTML 原语组件(Button/Table/Modal/...) | `client` |
26
27
  | **layout** | `weifuwu/layout` | 纯 CSS 布局原语 + 主题 Token | — |
27
28
 
28
29
  ---
@@ -39,6 +40,7 @@ npm install weifuwu
39
40
  ctx.sql ctx.ws
40
41
  ctx.redis ctx.route
41
42
  ctx.ui ctx.api / ctx.auth
43
+ ctx.i18n
42
44
  ```
43
45
 
44
46
  ---
@@ -71,7 +73,7 @@ serve(app, { port: 3000 })
71
73
 
72
74
  ```tsx
73
75
  // src/main.tsx —— 前端
74
- import { createApp, router, RouteView } from 'weifuwu/client'
76
+ import { createApp, router, RouteView, i18n } from 'weifuwu/client'
75
77
  import type { WfuiContext, RouteDef } from 'weifuwu/client'
76
78
 
77
79
  function Home(_props: {}, ctx: WfuiContext) {
@@ -497,9 +499,36 @@ import { ErrorBoundary } from 'weifuwu/client'
497
499
  |------|------|
498
500
  | `extendCtx(ctx, fields)` | 创建新 ctx,继承原 ctx 的 getter |
499
501
 
502
+ ### 国际化 — `ctx.i18n`
503
+
504
+ `i18n()` 中间件注入 `ctx.i18n`,支持运行时语言切换:
505
+
506
+ ```ts
507
+ import { createApp, i18n } from 'weifuwu/client'
508
+
509
+ createApp()
510
+ .use(i18n({
511
+ locale: 'zh-CN',
512
+ messages: { 'users.title': '用户管理' },
513
+ }))
514
+ .mount('#root', () => <App />)
515
+
516
+ // 页面中使用
517
+ ctx.i18n?.t('users.title') // → '用户管理'
518
+
519
+ // 运行时切换
520
+ ctx.i18n?.setLocale('en-US') // → 自动触发重渲染
521
+ ```
522
+
523
+ 内置语言包:`zh-CN`(默认)、`en-US`。组件文案(Button 的 `加载中...`、FileUpload 的 `点击或拖拽上传文件`)随语言自动切换。组件通过 `ctx.i18n?.components?.ComponentName.field` 读取,支持 `props.locale` 局部覆盖。
524
+
525
+ ```ts
526
+ import { i18n, zhCN, enUS } from 'weifuwu/client'
527
+ ```
528
+
500
529
  ### 前端类型
501
530
 
502
- `VNode`, `VNodeType`, `Component`, `WfuiContext`, `AppMiddleware`, `RouteDef`, `ApiClient`, `ApiOptions`, `ApiRequestOptions`, `ApiError`, `AuthClient`, `AuthOptions`, `ErrorBoundaryProps`
531
+ `VNode`, `VNodeType`, `Component`, `WfuiContext`, `AppMiddleware`, `RouteDef`, `ApiClient`, `ApiOptions`, `ApiRequestOptions`, `ApiError`, `AuthClient`, `AuthOptions`, `ErrorBoundaryProps`, `I18nOptions`, `I18nState`, `LocalePackage`
503
532
 
504
533
  ---
505
534
 
@@ -614,6 +643,65 @@ document.documentElement.setAttribute('data-theme', 'dark')
614
643
 
615
644
  ---
616
645
 
646
+ ## 组件库 — `weifuwu/components`
647
+
648
+ 29 个 **HTML 原语**,覆盖 90% 的 SaaS 页面 HTML 需求。每个组件是 `(props, ctx) => VNode` 纯函数,引用 `weifuwu/layout` 的 CSS 变量做主题。
649
+
650
+ ```ts
651
+ import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
652
+ import 'weifuwu/components/style.css'
653
+ ```
654
+
655
+ ### 模块总览
656
+
657
+ | 类别 | 组件 | 用途 |
658
+ |------|------|------|
659
+ | **表单核心** | `Button` `Input` `Textarea` `Select` | 4 个最常用的表单元素 |
660
+ | **表单核心** | `InputNumber` | 数字输入,带自定义步进按钮 (`showStepper`) |
661
+ | **表单选择** | `Checkbox` `Switch` `RadioGroup` `Slider` | 选择类输入 |
662
+ | **表单增强** | `Form` `Field` `FileUpload` `SearchInput` `ProgressBar` | 文件上传、搜索、进度 |
663
+ | **数据展示** | `Table` `Card` `Badge` `Tag` `Avatar` `StatCard` `PageHeader` | 数据展示与页面标题 |
664
+ | **数据反馈** | `Modal` `Drawer` `Tooltip` `Toast` `Alert` `Loading` `EmptyState` | 弹窗、抽屉、提示 |
665
+ | **导航组件** | `Breadcrumb` `Tabs` `Dropdown` `Pagination` `Steps` `Accordion` | 面包屑、标签页、分页 |
666
+ | **布局** | `Divider` | 分割线 (水平/垂直/带文字) |
667
+
668
+ ### 状态管理说明
669
+
670
+ `ctx.ui.$` 是**组件级**状态——每个组件实例有独立的 Proxy(基于 `vnode._$`),同名变量不会冲突:
671
+
672
+ ```tsx
673
+ // 组件 A
674
+ const $ = ctx.ui.$
675
+ $.open = true // 只影响组件 A
676
+
677
+ // 组件 B(在同一页面)
678
+ const $ = ctx.ui.$
679
+ $.open = false // 只影响组件 B,不影响 A
680
+ ```
681
+
682
+ 跨组件共享状态请使用 `ctx` 直接挂载属性(延续中间件模式):
683
+
684
+ ```ts
685
+ ctx.theme = 'dark' // 所有组件可读
686
+ ctx.toast?.success('成功') // 如已注入 toast 中间件
687
+ ```
688
+
689
+ ### 页面模板 — `docs/pages/`
690
+
691
+ | 模板 | 文件 | 用途 |
692
+ |------|------|------|
693
+ | **列表页** | `docs/pages/list-page.md` | 搜索 + 表格 + 分页 + 加载/空/错误状态 |
694
+ | **表单页** | `docs/pages/form-page.md` | 表单 + 字段 + 校验 + 提交 |
695
+ | **详情页** | `docs/pages/detail-page.md` | 信息展示 + Tabs + 操作 |
696
+ | **设置页** | `docs/pages/settings-page.md` | 分组设置 + 独立保存 |
697
+ | **仪表盘** | `docs/pages/dashboard-page.md` | KPI 卡片 + 图表 + 列表 |
698
+ | **认证页** | `docs/pages/auth-page.md` | 居中卡片 + 表单 + 错误提示 |
699
+ | **应用壳** | `docs/pages/app-layout.md` | 侧边栏 + 导航菜单 + 认证守卫 |
700
+
701
+ 每个模板标注了「改这里」——复制代码后改 API 路径、字段定义、操作按钮、导航项即可使用。
702
+
703
+ ---
704
+
617
705
  ## 环境变量
618
706
 
619
707
  | 变量 | 用途 | 默认值 |
@@ -654,6 +742,12 @@ src/
654
742
  │ ├── api.ts
655
743
  │ ├── auth.ts
656
744
  │ └── ws.ts
745
+ ├── components/ # 29 个 HTML 原语组件
746
+ │ ├── index.ts
747
+ │ ├── Button/ # Button.ts + .css + .test.ts
748
+ │ ├── Input/
749
+ │ ├── ...
750
+ │ └── PageHeader/
657
751
  └── layout/ # 纯 CSS 布局 + 主题
658
752
  ├── weifuwu-layout.css
659
753
  ├── _tokens.css
@@ -5,15 +5,12 @@
5
5
  *
6
6
  * ctx.ui 在 mount 时注入:
7
7
  * ctx.ui.render() 触发组件重渲染
8
- * ctx.ui.$ 持久化组件状态(由渲染器管理)
8
+ * ctx.ui.$ 组件级状态 Proxy(由 renderComponent 创建)
9
9
  * ctx.ui.ready 首次执行标记(由渲染器管理)
10
10
  *
11
- * ctx.ui.$ 是深度 Proxy
12
- * $.x = val → 自动渲染(顶层属性赋值)
13
- * $.items.push(val) 自动渲染(数组突变方法)
14
- * $.items[0].x = val → 自动渲染(对象属性突变)
15
- * $.items.push({x: 1}) → 新对象自动深度包装
16
- * ctx.ui.dirty() → 极少需要(仅当绕过 Proxy 直接操作底层对象)
11
+ * ctx.ui.$ 是组件级 Proxy(组件间隔离):
12
+ * $.x = val → 设置当前组件状态,自动触发渲染
13
+ * ctx.ui.dirty() 标记脏状态(仅深层突变时需要)
17
14
  */
18
15
  import type { WfuiContext, AppMiddleware } from './types.ts';
19
16
  import type { Component } from './vnode.ts';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * weifuwu i18n — 中间件
3
+ *
4
+ * 注入 ctx.i18n 支持运行时语言切换。
5
+ *
6
+ * 使用:
7
+ * app.use(i18n({ locale: 'en', messages: { 'title': 'Dashboard' } }))
8
+ * ctx.i18n?.t('title') // → 'Dashboard'
9
+ * ctx.i18n?.setLocale('zh-CN') // → 切换语言,自动重渲染
10
+ */
11
+ import type { AppMiddleware } from './types.ts';
12
+ export interface I18nOptions {
13
+ locale?: string;
14
+ messages?: Record<string, string>;
15
+ components?: Record<string, Record<string, string>>;
16
+ }
17
+ export interface LocalePackage {
18
+ messages?: Record<string, string>;
19
+ components?: Record<string, Record<string, string>>;
20
+ }
21
+ export interface I18nState {
22
+ locale: string;
23
+ t: (key: string, fallback?: string) => string;
24
+ setLocale: (lang: string) => void;
25
+ components: Record<string, Record<string, string>>;
26
+ }
27
+ export declare function i18n(opts?: I18nOptions): AppMiddleware;
@@ -29,3 +29,7 @@ export { extendCtx } from './types.ts';
29
29
  export type { WfuiContext, AppMiddleware, RouteDef } from './types.ts';
30
30
  export { ErrorBoundary } from './error-boundary.ts';
31
31
  export type { ErrorBoundaryProps } from './error-boundary.ts';
32
+ export { i18n } from './i18n.ts';
33
+ export type { I18nOptions, I18nState } from './i18n.ts';
34
+ export { zhCN } from './locale/zh_CN.ts';
35
+ export { enUS } from './locale/en_US.ts';
@@ -29,6 +29,57 @@ function normalizeProps(props) {
29
29
  }
30
30
 
31
31
  // src/client/render.ts
32
+ var mutationMethods = ["push", "pop", "splice", "shift", "unshift", "sort", "reverse"];
33
+ var wrappedCache = /* @__PURE__ */ new WeakMap();
34
+ function wrapDeep(val, dirty) {
35
+ if (val === null || typeof val !== "object") return val;
36
+ if (val instanceof Node) return val;
37
+ if (typeof Blob !== "undefined" && val instanceof Blob) return val;
38
+ if (wrappedCache.has(val)) return wrappedCache.get(val);
39
+ const handler = Array.isArray(val) ? {
40
+ get(target, key, receiver) {
41
+ const v = Reflect.get(target, key, receiver);
42
+ if (typeof key === "string" && mutationMethods.includes(key) && typeof v === "function") {
43
+ return function(...args) {
44
+ const r = v.apply(target, args);
45
+ dirty();
46
+ return r;
47
+ };
48
+ }
49
+ return wrapDeep(v, dirty);
50
+ },
51
+ set(target, key, v) {
52
+ Reflect.set(target, key, wrapDeep(v, dirty));
53
+ dirty();
54
+ return true;
55
+ }
56
+ } : {
57
+ get(_target, key, receiver) {
58
+ const v = Reflect.get(_target, key, receiver);
59
+ return wrapDeep(v, dirty);
60
+ },
61
+ set(target, key, v) {
62
+ Reflect.set(target, key, wrapDeep(v, dirty));
63
+ dirty();
64
+ return true;
65
+ }
66
+ };
67
+ const proxy = new Proxy(val, handler);
68
+ wrappedCache.set(val, proxy);
69
+ return proxy;
70
+ }
71
+ function createComponentProxy(target, dirty) {
72
+ return new Proxy(target, {
73
+ set(t, k, v) {
74
+ t[k] = wrapDeep(v, dirty);
75
+ dirty();
76
+ return true;
77
+ },
78
+ get(t, k) {
79
+ return wrapDeep(t[k], dirty);
80
+ }
81
+ });
82
+ }
32
83
  function render(input, ctx) {
33
84
  return renderValue(input, ctx);
34
85
  }
@@ -66,6 +117,8 @@ function renderComponent(Comp, props, vnode, ctx) {
66
117
  if (!prev$) vnode._$ = {};
67
118
  ctx.ui = ctx.ui ?? {};
68
119
  ctx.ui.ready = !!prev$;
120
+ const _target = vnode._$;
121
+ ctx.ui.$ = createComponentProxy(_target, () => ctx.ui?.dirty?.());
69
122
  let childVNode;
70
123
  try {
71
124
  childVNode = Comp(props, ctx);
@@ -169,6 +222,8 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
169
222
  if (oldV._$) newV._$ = oldV._$;
170
223
  ctx.ui = ctx.ui ?? {};
171
224
  ctx.ui.ready = !!newV._$;
225
+ const _tgt = newV._$;
226
+ ctx.ui.$ = createComponentProxy(_tgt, () => ctx.ui?.dirty?.());
172
227
  const childNew = comp(newV.props, ctx);
173
228
  newV._child = childNew;
174
229
  return patchValue(parent, oldNode, oldV._child, childNew, ctx);
@@ -211,7 +266,9 @@ function patchProps(el, oldProps, newProps) {
211
266
  const newKeys = newProps ? Object.keys(newProps).filter((k) => k !== "children" && k !== "key" && k !== "ref") : [];
212
267
  for (const key of oldKeys) {
213
268
  if (!newKeys.includes(key)) {
214
- if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
269
+ if (key.startsWith("on") && typeof oldProps[key] === "function") {
270
+ el.removeEventListener(key.slice(2).toLowerCase(), oldProps[key]);
271
+ } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
215
272
  ;
216
273
  el.value = "";
217
274
  } else {
@@ -413,18 +470,21 @@ function router(opts) {
413
470
  params[match.keys[i]] = decodeURIComponent(m[i + 1]);
414
471
  }
415
472
  }
473
+ const last = match.chain[match.chain.length - 1];
416
474
  return {
417
475
  path: match.def.path,
418
476
  params,
419
477
  query: Object.fromEntries(new URLSearchParams(window.location.search)),
420
- chain: match.chain
478
+ chain: match.chain,
479
+ title: last?.title ?? ""
421
480
  };
422
481
  }
423
- return { path, params: {}, query: {}, chain: [] };
482
+ return { path, params: {}, query: {}, chain: [], title: "" };
424
483
  }
425
484
  return (ctx) => {
426
485
  const resolved = resolve(getPath());
427
486
  ctx.route = resolved;
487
+ if (resolved.title) document.title = resolved.title;
428
488
  if (!ctx.app) ctx.app = {};
429
489
  ctx.app.navigate = (path) => {
430
490
  if (mode === "hash") {
@@ -436,11 +496,13 @@ function router(opts) {
436
496
  const resolved2 = resolve(path);
437
497
  ctx.route = resolved2;
438
498
  }
499
+ if (ctx.route?.title) document.title = ctx.route.title;
439
500
  ctx.ui?.render();
440
501
  };
441
502
  const onPop = () => {
442
503
  const resolved2 = resolve(getPath());
443
504
  ctx.route = resolved2;
505
+ if (ctx.route?.title) document.title = ctx.route.title;
444
506
  ctx.ui?.render();
445
507
  };
446
508
  window.addEventListener("popstate", onPop);
@@ -466,48 +528,6 @@ function RouteView(_props, ctx) {
466
528
  }
467
529
 
468
530
  // src/client/app.ts
469
- var mutationMethods = ["push", "pop", "splice", "shift", "unshift", "sort", "reverse"];
470
- var wrappedCache = /* @__PURE__ */ new WeakMap();
471
- function wrapDeep(val, dirty) {
472
- if (val === null || typeof val !== "object") return val;
473
- if (val instanceof Node) return val;
474
- if (wrappedCache.has(val)) return wrappedCache.get(val);
475
- let proxy;
476
- if (Array.isArray(val)) {
477
- proxy = new Proxy(val, {
478
- get(target, key, receiver) {
479
- const v = Reflect.get(target, key, receiver);
480
- if (typeof key === "string" && mutationMethods.includes(key) && typeof v === "function") {
481
- return function(...args) {
482
- const r = v.apply(target, args);
483
- dirty();
484
- return r;
485
- };
486
- }
487
- return wrapDeep(v, dirty);
488
- },
489
- set(target, key, val2) {
490
- target[key] = wrapDeep(val2, dirty);
491
- dirty();
492
- return true;
493
- }
494
- });
495
- } else {
496
- proxy = new Proxy(val, {
497
- get(_target, key, receiver) {
498
- const v = Reflect.get(_target, key, receiver);
499
- return wrapDeep(v, dirty);
500
- },
501
- set(target, key, val2) {
502
- target[key] = wrapDeep(val2, dirty);
503
- dirty();
504
- return true;
505
- }
506
- });
507
- }
508
- wrappedCache.set(val, proxy);
509
- return proxy;
510
- }
511
531
  function createApp() {
512
532
  const middlewares = [];
513
533
  let ctx = {};
@@ -542,7 +562,7 @@ function createApp() {
542
562
  ctx.ui.render();
543
563
  });
544
564
  }
545
- const $target = {};
565
+ ;
546
566
  ctx.ui = {
547
567
  render: () => {
548
568
  if (!container || !rootComponent || !oldVNode) return;
@@ -559,13 +579,11 @@ function createApp() {
559
579
  dirty: () => {
560
580
  scheduleRender();
561
581
  },
562
- $: new Proxy($target, {
563
- set(_target, key, val) {
564
- _target[key] = wrapDeep(val, scheduleRender);
565
- scheduleRender();
566
- return true;
567
- }
568
- }),
582
+ /**
583
+ * 组件级 $ — 在 renderComponent 中被覆盖为每个组件独立的 Proxy。
584
+ * 这里保留占位,组件实际使用 ctx.ui.$ 时拿到的是 vnode._$ 上的 Proxy。
585
+ */
586
+ $: {},
569
587
  ready: false
570
588
  };
571
589
  oldVNode = wrapComponent(RootComponent, ctx);
@@ -875,6 +893,102 @@ function ErrorBoundary(props, ctx) {
875
893
  return fb ?? null;
876
894
  }
877
895
  }
896
+
897
+ // src/client/locale/zh_CN.ts
898
+ var zhCN = {
899
+ components: {
900
+ Button: { loading: "\u52A0\u8F7D\u4E2D..." },
901
+ FileUpload: {
902
+ placeholder: "\u70B9\u51FB\u6216\u62D6\u62FD\u4E0A\u4F20\u6587\u4EF6",
903
+ supportedFormats: "\u652F\u6301\u683C\u5F0F: ",
904
+ maxSize: "\u6700\u5927 ",
905
+ remove: "\u5220\u9664"
906
+ },
907
+ Pagination: { ariaLabel: "\u5206\u9875" },
908
+ ProgressBar: { ariaLabel: "\u8FDB\u5EA6" },
909
+ Switch: { ariaLabel: "\u5207\u6362" },
910
+ Breadcrumb: { ariaLabel: "\u9762\u5305\u5C51" },
911
+ Modal: { ariaLabel: "\u5F39\u7A97" },
912
+ Drawer: { ariaLabel: "\u4FA7\u8FB9\u9762\u677F" },
913
+ InputNumber: { increase: "\u589E\u52A0", decrease: "\u51CF\u5C11" }
914
+ }
915
+ };
916
+
917
+ // src/client/locale/en_US.ts
918
+ var enUS = {
919
+ components: {
920
+ Button: { loading: "Loading..." },
921
+ FileUpload: {
922
+ placeholder: "Click or drag to upload",
923
+ supportedFormats: "Supported formats: ",
924
+ maxSize: "Max size: ",
925
+ remove: "Remove"
926
+ },
927
+ Pagination: { ariaLabel: "Pagination" },
928
+ ProgressBar: { ariaLabel: "Progress" },
929
+ Switch: { ariaLabel: "Toggle" },
930
+ Breadcrumb: { ariaLabel: "Breadcrumb" },
931
+ Modal: { ariaLabel: "Dialog" },
932
+ Drawer: { ariaLabel: "Panel" },
933
+ InputNumber: { increase: "Increase", decrease: "Decrease" }
934
+ }
935
+ };
936
+
937
+ // src/client/i18n.ts
938
+ var LOCALE_PACKAGES = {
939
+ "zh-CN": zhCN,
940
+ "en-US": enUS
941
+ };
942
+ function resolveLang(locale) {
943
+ if (LOCALE_PACKAGES[locale]) return locale;
944
+ const prefix = locale.split("-")[0];
945
+ const match = Object.keys(LOCALE_PACKAGES).find((k) => k.startsWith(prefix));
946
+ return match ?? "zh-CN";
947
+ }
948
+ function i18n(opts = {}) {
949
+ const { locale: raw = "zh-CN", messages = {}, components = {} } = opts;
950
+ const lang = resolveLang(raw);
951
+ const pkg = LOCALE_PACKAGES[lang];
952
+ let merged = mergeLocales(pkg, messages, components);
953
+ const state = {
954
+ locale: lang,
955
+ t: (key, fallback) => merged.messages?.[key] ?? fallback ?? key,
956
+ setLocale: () => {
957
+ },
958
+ components: merged.components
959
+ };
960
+ return (ctx) => {
961
+ ;
962
+ ctx.i18n = state;
963
+ state.setLocale = (raw2) => {
964
+ const lang2 = resolveLang(raw2);
965
+ const pkg2 = LOCALE_PACKAGES[lang2];
966
+ if (!pkg2) return;
967
+ merged = mergeLocales(pkg2, messages, components);
968
+ state.locale = lang2;
969
+ state.components = merged.components;
970
+ ctx?.ui?.render();
971
+ };
972
+ return ctx;
973
+ };
974
+ }
975
+ function mergeLocales(pkg, userMessages, userComponents) {
976
+ return {
977
+ messages: { ...pkg?.messages, ...userMessages },
978
+ components: deepMerge(pkg?.components ?? {}, userComponents)
979
+ };
980
+ }
981
+ function deepMerge(a, b) {
982
+ const result = { ...a };
983
+ for (const key of Object.keys(b)) {
984
+ if (typeof b[key] === "object" && b[key] !== null && typeof result[key] === "object") {
985
+ result[key] = { ...result[key], ...b[key] };
986
+ } else {
987
+ result[key] = b[key];
988
+ }
989
+ }
990
+ return result;
991
+ }
878
992
  export {
879
993
  ApiError,
880
994
  ErrorBoundary,
@@ -883,11 +997,14 @@ export {
883
997
  api,
884
998
  auth,
885
999
  createApp,
1000
+ enUS,
886
1001
  extendCtx,
887
1002
  h,
1003
+ i18n,
888
1004
  jsx,
889
1005
  jsxDEV,
890
1006
  jsxs,
891
1007
  router,
892
- ws
1008
+ ws,
1009
+ zhCN
893
1010
  };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * weifuwu i18n — 英文组件文案
3
+ */
4
+ export declare const enUS: {
5
+ components: {
6
+ Button: {
7
+ loading: string;
8
+ };
9
+ FileUpload: {
10
+ placeholder: string;
11
+ supportedFormats: string;
12
+ maxSize: string;
13
+ remove: string;
14
+ };
15
+ Pagination: {
16
+ ariaLabel: string;
17
+ };
18
+ ProgressBar: {
19
+ ariaLabel: string;
20
+ };
21
+ Switch: {
22
+ ariaLabel: string;
23
+ };
24
+ Breadcrumb: {
25
+ ariaLabel: string;
26
+ };
27
+ Modal: {
28
+ ariaLabel: string;
29
+ };
30
+ Drawer: {
31
+ ariaLabel: string;
32
+ };
33
+ InputNumber: {
34
+ increase: string;
35
+ decrease: string;
36
+ };
37
+ };
38
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * weifuwu i18n — 中文组件文案(缺省值)
3
+ */
4
+ export declare const zhCN: {
5
+ components: {
6
+ Button: {
7
+ loading: string;
8
+ };
9
+ FileUpload: {
10
+ placeholder: string;
11
+ supportedFormats: string;
12
+ maxSize: string;
13
+ remove: string;
14
+ };
15
+ Pagination: {
16
+ ariaLabel: string;
17
+ };
18
+ ProgressBar: {
19
+ ariaLabel: string;
20
+ };
21
+ Switch: {
22
+ ariaLabel: string;
23
+ };
24
+ Breadcrumb: {
25
+ ariaLabel: string;
26
+ };
27
+ Modal: {
28
+ ariaLabel: string;
29
+ };
30
+ Drawer: {
31
+ ariaLabel: string;
32
+ };
33
+ InputNumber: {
34
+ increase: string;
35
+ decrease: string;
36
+ };
37
+ };
38
+ };
@@ -0,0 +1,11 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface AccordionItem {
3
+ key: string;
4
+ title: string;
5
+ content?: any;
6
+ }
7
+ export interface AccordionProps {
8
+ items?: AccordionItem[];
9
+ multiple?: boolean;
10
+ }
11
+ export declare const Accordion: Component<AccordionProps>;
@@ -0,0 +1,9 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
3
+ export interface AlertProps {
4
+ variant?: AlertVariant;
5
+ closable?: boolean;
6
+ onClose?: () => void;
7
+ children?: any;
8
+ }
9
+ export declare const Alert: Component<AlertProps>;
@@ -0,0 +1,7 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface AvatarProps {
3
+ name?: string;
4
+ src?: string;
5
+ size?: 'sm' | 'md' | 'lg';
6
+ }
7
+ export declare const Avatar: Component<AvatarProps>;
@@ -0,0 +1,8 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
3
+ export interface BadgeProps {
4
+ variant?: BadgeVariant;
5
+ dot?: boolean;
6
+ children?: any;
7
+ }
8
+ export declare const Badge: Component<BadgeProps>;
@@ -0,0 +1,9 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ }
6
+ export interface BreadcrumbProps {
7
+ items: BreadcrumbItem[];
8
+ }
9
+ export declare const Breadcrumb: Component<BreadcrumbProps>;
@@ -0,0 +1,12 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface ButtonProps {
3
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ block?: boolean;
6
+ loading?: boolean;
7
+ disabled?: boolean;
8
+ type?: 'button' | 'submit';
9
+ onClick?: (e: MouseEvent) => void;
10
+ children?: any;
11
+ }
12
+ export declare const Button: Component<ButtonProps>;
@@ -0,0 +1,9 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface CardProps {
3
+ variant?: 'default' | 'outlined';
4
+ padding?: 'sm' | 'md' | 'lg';
5
+ clickable?: boolean;
6
+ onClick?: () => void;
7
+ children?: any;
8
+ }
9
+ export declare const Card: Component<CardProps>;
@@ -0,0 +1,8 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface CheckboxProps {
3
+ label?: string;
4
+ checked?: boolean;
5
+ disabled?: boolean;
6
+ onChange?: (checked: boolean) => void;
7
+ }
8
+ export declare const Checkbox: Component<CheckboxProps>;
@@ -0,0 +1,6 @@
1
+ import type { Component } from '../../client/vnode.ts';
2
+ export interface DividerProps {
3
+ vertical?: boolean;
4
+ children?: any;
5
+ }
6
+ export declare const Divider: Component<DividerProps>;