@nocobase/portal-template-default 1.0.1 → 1.0.3

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 (186) hide show
  1. package/README.MD +22 -3
  2. package/package.json +4 -2
  3. package/registry/nocobase-acl/components/role-options.ts +7 -0
  4. package/registry/nocobase-acl/components/role-switcher.tsx +9 -4
  5. package/registry/nocobase-acl/demo/boundary-api.tsx +87 -16
  6. package/registry/nocobase-acl/demo/components.tsx +32 -11
  7. package/registry/nocobase-acl/demo/index.tsx +45 -14
  8. package/registry/nocobase-acl/demo/lazy-route.tsx +2 -7
  9. package/registry/nocobase-acl/demo/prompt-generator.tsx +52 -33
  10. package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +28 -9
  11. package/registry/nocobase-acl/extension.tsx +1 -1
  12. package/registry/nocobase-acl/tests/acl-regression.mjs +7 -6
  13. package/registry/nocobase-ai/components/chat/chat-messages.tsx +3 -4
  14. package/registry/nocobase-ai/components/chat/conversation-list.tsx +2 -4
  15. package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +16 -19
  16. package/registry/nocobase-ai/components/tools/chart-renderer.tsx +3 -14
  17. package/registry/nocobase-ai/components/tools/echarts-preview.tsx +2 -5
  18. package/registry/nocobase-ai/demo/configuration-gate.tsx +9 -12
  19. package/registry/nocobase-ai/extension.tsx +2 -7
  20. package/registry/nocobase-i18n/README.md +53 -0
  21. package/registry/nocobase-i18n/components/index.ts +1 -0
  22. package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
  23. package/registry/nocobase-i18n/demo/index.tsx +209 -0
  24. package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
  25. package/registry/nocobase-i18n/extension.tsx +46 -0
  26. package/registry/nocobase-i18n/i18n-provider.ts +9 -0
  27. package/registry/nocobase-i18n/index.ts +6 -0
  28. package/registry/nocobase-i18n/locale-store.ts +134 -0
  29. package/registry/nocobase-i18n/locales/en-US.ts +89 -0
  30. package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
  31. package/registry/nocobase-i18n/provider.tsx +32 -0
  32. package/registry/nocobase-i18n/runtime.ts +118 -0
  33. package/registry/nocobase-i18n/server-resources.ts +184 -0
  34. package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
  35. package/registry.config.json +34 -14
  36. package/scripts/registry.mjs +5 -0
  37. package/src/App.tsx +27 -86
  38. package/src/app/extension.ts +3 -1
  39. package/src/app/extensions.tsx +21 -0
  40. package/src/components/access-control/acl-gate.tsx +4 -10
  41. package/src/components/app-shell/breadcrumb.tsx +39 -3
  42. package/src/components/app-shell/document-title-handler.tsx +11 -20
  43. package/src/components/app-shell/header.tsx +18 -3
  44. package/src/components/app-shell/loading-overlay.tsx +2 -4
  45. package/src/components/app-shell/loading-state.tsx +15 -0
  46. package/src/components/app-shell/sidebar.tsx +75 -47
  47. package/src/components/data-table/data-table-pagination.tsx +27 -7
  48. package/src/components/data-table/data-table-styles.ts +57 -0
  49. package/src/components/data-table/data-table.tsx +14 -59
  50. package/src/components/resources/resource-label.ts +101 -0
  51. package/src/components/resources/views/create-view.tsx +17 -14
  52. package/src/components/resources/views/edit-view.tsx +17 -15
  53. package/src/components/resources/views/list-view.tsx +20 -10
  54. package/src/components/resources/views/show-view.tsx +20 -18
  55. package/src/extensions/nocobase-acl/README.md +19 -0
  56. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +106 -0
  57. package/src/extensions/nocobase-acl/components/index.ts +3 -0
  58. package/src/extensions/nocobase-acl/components/role-options.ts +36 -0
  59. package/src/extensions/nocobase-acl/components/role-switcher.tsx +135 -0
  60. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +186 -0
  61. package/src/extensions/nocobase-acl/demo/components.tsx +231 -0
  62. package/src/extensions/nocobase-acl/demo/index.tsx +505 -0
  63. package/src/extensions/nocobase-acl/demo/lazy-route.tsx +10 -0
  64. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +134 -0
  65. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
  66. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +37 -0
  67. package/src/extensions/nocobase-acl/extension.tsx +71 -0
  68. package/src/extensions/nocobase-acl/index.ts +1 -0
  69. package/src/extensions/nocobase-ai/README.md +70 -0
  70. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +20 -0
  71. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
  72. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +32 -0
  73. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
  74. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
  75. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +90 -0
  76. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +429 -0
  77. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
  78. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +88 -0
  79. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
  80. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +252 -0
  81. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +173 -0
  82. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +118 -0
  83. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +343 -0
  84. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +165 -0
  85. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +30 -0
  86. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
  87. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
  88. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
  89. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
  90. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
  91. package/src/extensions/nocobase-ai/components/index.ts +59 -0
  92. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
  93. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
  94. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
  95. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
  96. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
  97. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
  98. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
  99. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
  100. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
  101. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
  102. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
  103. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
  104. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
  105. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
  106. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
  107. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
  108. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
  109. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
  110. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
  111. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
  112. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
  113. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
  114. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
  115. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
  116. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
  117. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
  118. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +49 -0
  119. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +210 -0
  120. package/src/extensions/nocobase-ai/demo/floating.tsx +134 -0
  121. package/src/extensions/nocobase-ai/demo/index.tsx +346 -0
  122. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +151 -0
  123. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
  124. package/src/extensions/nocobase-ai/demo/page-context.tsx +884 -0
  125. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +197 -0
  126. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +41 -0
  127. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +452 -0
  128. package/src/extensions/nocobase-ai/demo/shortcut.tsx +1207 -0
  129. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +530 -0
  130. package/src/extensions/nocobase-ai/extension.tsx +158 -0
  131. package/src/extensions/nocobase-ai/global-ai-chat.tsx +213 -0
  132. package/src/extensions/nocobase-ai/index.ts +3 -0
  133. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +323 -0
  134. package/src/extensions/nocobase-ai/providers/avatars.ts +817 -0
  135. package/src/extensions/nocobase-ai/providers/chat-context.tsx +109 -0
  136. package/src/extensions/nocobase-ai/providers/chat-controller.ts +84 -0
  137. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +198 -0
  138. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +871 -0
  139. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +147 -0
  140. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +105 -0
  141. package/src/extensions/nocobase-ai/providers/chat-transport.ts +184 -0
  142. package/src/extensions/nocobase-ai/providers/form-registry.tsx +271 -0
  143. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
  144. package/src/extensions/nocobase-ai/providers/index.ts +61 -0
  145. package/src/extensions/nocobase-ai/providers/model.ts +32 -0
  146. package/src/extensions/nocobase-ai/providers/page-context.tsx +136 -0
  147. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +60 -0
  148. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +82 -0
  149. package/src/extensions/nocobase-ai/providers/stream-parser.ts +61 -0
  150. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +316 -0
  151. package/src/extensions/nocobase-ai/providers/types.ts +183 -0
  152. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +463 -0
  153. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
  154. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +206 -0
  155. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
  156. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +210 -0
  157. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +93 -0
  158. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
  159. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +178 -0
  160. package/src/extensions/nocobase-ai/services/index.ts +7 -0
  161. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +454 -0
  162. package/src/extensions/nocobase-ai/services/types.ts +82 -0
  163. package/src/index.tsx +2 -3
  164. package/src/lib/i18n.ts +161 -0
  165. package/src/lib/nocobase/client.ts +19 -2
  166. package/src/locales/en-US.ts +82 -0
  167. package/src/locales/index.ts +13 -0
  168. package/src/locales/zh-CN.ts +79 -0
  169. package/src/pages/users/create.tsx +98 -0
  170. package/src/pages/users/edit.tsx +92 -0
  171. package/src/pages/users/form-context.ts +57 -0
  172. package/src/pages/users/form-fields.tsx +211 -0
  173. package/src/pages/users/list.tsx +205 -0
  174. package/src/pages/users/show.tsx +187 -0
  175. package/src/pages/users/types.ts +20 -0
  176. package/src/providers/constants.ts +1 -0
  177. package/src/pages/blog-posts/create.tsx +0 -177
  178. package/src/pages/blog-posts/edit.tsx +0 -197
  179. package/src/pages/blog-posts/list.tsx +0 -131
  180. package/src/pages/blog-posts/show.tsx +0 -81
  181. package/src/pages/categories/create.tsx +0 -73
  182. package/src/pages/categories/edit.tsx +0 -73
  183. package/src/pages/categories/index.ts +0 -4
  184. package/src/pages/categories/list.tsx +0 -86
  185. package/src/pages/categories/show.tsx +0 -37
  186. /package/src/pages/{blog-posts → users}/index.ts +0 -0
@@ -2,6 +2,7 @@ import {
2
2
  API_ORIGIN,
3
3
  API_URL,
4
4
  NOCOBASE_AUTHENTICATOR,
5
+ NOCOBASE_LOCALE_KEY,
5
6
  NOCOBASE_ROLE_KEY,
6
7
  NOCOBASE_TOKEN_KEY,
7
8
  } from "@/providers/constants";
@@ -30,7 +31,7 @@ export type NocoBaseRequestOptions = {
30
31
  unwrap?: "data" | "deep-data" | "none";
31
32
  };
32
33
 
33
- const getClientLocale = () =>
34
+ const getBrowserLocale = () =>
34
35
  typeof navigator === "undefined" ? undefined : navigator.language;
35
36
 
36
37
  const getClientTimezone = () => {
@@ -109,6 +110,22 @@ export class NocoBaseClient {
109
110
  else localStorage.removeItem(NOCOBASE_ROLE_KEY);
110
111
  }
111
112
 
113
+ getStoredLocale() {
114
+ return typeof localStorage === "undefined"
115
+ ? undefined
116
+ : localStorage.getItem(NOCOBASE_LOCALE_KEY) ?? undefined;
117
+ }
118
+
119
+ getLocale() {
120
+ return this.getStoredLocale() ?? getBrowserLocale();
121
+ }
122
+
123
+ setLocale(locale?: string | null) {
124
+ if (typeof localStorage === "undefined") return;
125
+ if (locale) localStorage.setItem(NOCOBASE_LOCALE_KEY, locale);
126
+ else localStorage.removeItem(NOCOBASE_LOCALE_KEY);
127
+ }
128
+
112
129
  buildUrl(endpoint: string, query?: Record<string, QueryValue>) {
113
130
  const base = `${this.apiUrl.replace(/\/$/, "")}/${endpoint.replace(
114
131
  /^\//,
@@ -149,7 +166,7 @@ export class NocoBaseClient {
149
166
  | "accept"
150
167
  | "body"
151
168
  > = {}) {
152
- const locale = getClientLocale();
169
+ const locale = this.getLocale();
153
170
  const formData =
154
171
  typeof FormData !== "undefined" && body instanceof FormData;
155
172
  return {
@@ -0,0 +1,82 @@
1
+ export const starter = {
2
+ "buttons.create": "Create",
3
+ "buttons.edit": "Edit",
4
+ "buttons.show": "View",
5
+ "buttons.delete": "Delete",
6
+ "buttons.refresh": "Refresh",
7
+ "buttons.cancel": "Cancel",
8
+ "buttons.confirm": "Are you sure?",
9
+ "views.create.title": "Create {{resource}}",
10
+ "views.create.description":
11
+ "Add a new {{resource}} to your NocoBase workspace.",
12
+ "views.edit.title": "Edit {{resource}}",
13
+ "views.edit.description":
14
+ "Update this {{resource}} while NocoBase keeps the data consistent.",
15
+ "views.show.description": "Review the record and its NocoBase-managed data.",
16
+ "table.empty.title": "No data to display",
17
+ "table.empty.description": "This table is empty for the time being.",
18
+ "table.pagination.total": "{{count}} row(s)",
19
+ "table.pagination.pageSize": "Rows per page",
20
+ "table.pagination.page": "Page {{current}} of {{total}}",
21
+ "table.pagination.first": "Go to first page",
22
+ "table.pagination.previous": "Go to previous page",
23
+ "table.pagination.next": "Go to next page",
24
+ "table.pagination.last": "Go to last page",
25
+ "notifications.success": "Success",
26
+ "notifications.createSuccess": "Successfully created {{resource}}",
27
+ "notifications.editSuccess": "Successfully updated {{resource}}",
28
+ "notifications.deleteSuccess": "Successfully deleted {{resource}}",
29
+ "users.users": "User",
30
+ Admin: "Admin",
31
+ Root: "Root",
32
+ Member: "Member",
33
+ Anonymous: "Anonymous",
34
+ "Full permissions": "Full permissions",
35
+ } as const;
36
+
37
+ export const app = {
38
+ "resources.users": "Users",
39
+ "resources.user": "User",
40
+ "resources.users.description":
41
+ "Manage the people who can sign in and work in this NocoBase application.",
42
+ "users.fields.id": "ID",
43
+ "users.fields.nickname": "Nickname",
44
+ "users.fields.username": "Username",
45
+ "users.fields.email": "Email",
46
+ "users.fields.phone": "Phone",
47
+ "users.fields.password": "Password",
48
+ "users.fields.roles": "Roles",
49
+ "users.fields.createdAt": "Created at",
50
+ "users.fields.updatedAt": "Updated at",
51
+ "users.fields.actions": "Actions",
52
+ "users.actions.view": "View user",
53
+ "users.actions.edit": "Edit user",
54
+ "users.actions.delete": "Delete user",
55
+ "users.form.create.submit": "Create user",
56
+ "users.form.create.submitting": "Creating...",
57
+ "users.form.edit.submit": "Save changes",
58
+ "users.form.edit.submitting": "Saving...",
59
+ "users.form.cancel": "Cancel",
60
+ "users.form.nickname.placeholder": "Enter a display name",
61
+ "users.form.username.placeholder": "Enter a unique username",
62
+ "users.form.email.placeholder": "Enter an email address",
63
+ "users.form.phone.placeholder": "Enter a phone number",
64
+ "users.form.password.placeholder": "Set an initial password",
65
+ "users.form.password.aiNotice":
66
+ "Passwords are never included in AI page context or Form filler.",
67
+ "users.validation.username": "Username is required",
68
+ "users.validation.email": "Email is required",
69
+ "users.validation.emailFormat": "Enter a valid email address",
70
+ "users.validation.phone": "Phone is required",
71
+ "users.validation.password": "Password is required",
72
+ "users.detail.identity": "Identity",
73
+ "users.detail.contact": "Contact",
74
+ "users.detail.access": "Access",
75
+ "users.detail.timestamps": "Timestamps",
76
+ "users.detail.noRoles": "No assigned roles",
77
+ "users.detail.unnamed": "Unnamed user",
78
+ "users.ai.table": "Users table",
79
+ "users.ai.detail": "User details",
80
+ "users.ai.createForm": "Create user form",
81
+ "users.ai.editForm": "Edit user form",
82
+ } as const;
@@ -0,0 +1,13 @@
1
+ import { registerTranslationResources } from "@/lib/i18n";
2
+ import { app as enUSApp, starter as enUSStarter } from "./en-US";
3
+ import { app as zhCNApp, starter as zhCNStarter } from "./zh-CN";
4
+
5
+ registerTranslationResources("starter", {
6
+ "en-US": enUSStarter,
7
+ "zh-CN": zhCNStarter,
8
+ });
9
+
10
+ registerTranslationResources("app", {
11
+ "en-US": enUSApp,
12
+ "zh-CN": zhCNApp,
13
+ });
@@ -0,0 +1,79 @@
1
+ export const starter = {
2
+ "buttons.create": "新建",
3
+ "buttons.edit": "编辑",
4
+ "buttons.show": "查看",
5
+ "buttons.delete": "删除",
6
+ "buttons.refresh": "刷新",
7
+ "buttons.cancel": "取消",
8
+ "buttons.confirm": "确定要执行此操作吗?",
9
+ "views.create.title": "新建{{resource}}",
10
+ "views.create.description": "在 NocoBase 工作区中添加新的{{resource}}。",
11
+ "views.edit.title": "编辑{{resource}}",
12
+ "views.edit.description": "更新此{{resource}},数据一致性由 NocoBase 保障。",
13
+ "views.show.description": "查看记录及其由 NocoBase 管理的数据。",
14
+ "table.empty.title": "暂无数据",
15
+ "table.empty.description": "当前表格中没有可显示的数据。",
16
+ "table.pagination.total": "共 {{count}} 条",
17
+ "table.pagination.pageSize": "每页行数",
18
+ "table.pagination.page": "第 {{current}} 页,共 {{total}} 页",
19
+ "table.pagination.first": "前往第一页",
20
+ "table.pagination.previous": "前往上一页",
21
+ "table.pagination.next": "前往下一页",
22
+ "table.pagination.last": "前往最后一页",
23
+ "notifications.success": "操作成功",
24
+ "notifications.createSuccess": "已成功创建{{resource}}",
25
+ "notifications.editSuccess": "已成功更新{{resource}}",
26
+ "notifications.deleteSuccess": "已成功删除{{resource}}",
27
+ "users.users": "用户",
28
+ Admin: "管理员",
29
+ Root: "超级管理员",
30
+ Member: "成员",
31
+ Anonymous: "匿名用户",
32
+ "Full permissions": "全部权限",
33
+ } as const;
34
+
35
+ export const app = {
36
+ "resources.users": "用户",
37
+ "resources.user": "用户",
38
+ "resources.users.description": "管理可以登录并使用此 NocoBase 应用的用户。",
39
+ "users.fields.id": "ID",
40
+ "users.fields.nickname": "昵称",
41
+ "users.fields.username": "用户名",
42
+ "users.fields.email": "邮箱",
43
+ "users.fields.phone": "手机号",
44
+ "users.fields.password": "密码",
45
+ "users.fields.roles": "角色",
46
+ "users.fields.createdAt": "创建时间",
47
+ "users.fields.updatedAt": "更新时间",
48
+ "users.fields.actions": "操作",
49
+ "users.actions.view": "查看用户",
50
+ "users.actions.edit": "编辑用户",
51
+ "users.actions.delete": "删除用户",
52
+ "users.form.create.submit": "创建用户",
53
+ "users.form.create.submitting": "正在创建...",
54
+ "users.form.edit.submit": "保存修改",
55
+ "users.form.edit.submitting": "正在保存...",
56
+ "users.form.cancel": "取消",
57
+ "users.form.nickname.placeholder": "输入显示名称",
58
+ "users.form.username.placeholder": "输入唯一用户名",
59
+ "users.form.email.placeholder": "输入邮箱地址",
60
+ "users.form.phone.placeholder": "输入手机号",
61
+ "users.form.password.placeholder": "设置初始密码",
62
+ "users.form.password.aiNotice":
63
+ "密码不会包含在 AI 页面上下文中,Form filler 也无法填写密码。",
64
+ "users.validation.username": "请输入用户名",
65
+ "users.validation.email": "请输入邮箱",
66
+ "users.validation.emailFormat": "请输入有效的邮箱地址",
67
+ "users.validation.phone": "请输入手机号",
68
+ "users.validation.password": "请输入密码",
69
+ "users.detail.identity": "身份信息",
70
+ "users.detail.contact": "联系方式",
71
+ "users.detail.access": "访问权限",
72
+ "users.detail.timestamps": "时间信息",
73
+ "users.detail.noRoles": "尚未分配角色",
74
+ "users.detail.unnamed": "未命名用户",
75
+ "users.ai.table": "用户表格",
76
+ "users.ai.detail": "用户详情",
77
+ "users.ai.createForm": "新建用户表单",
78
+ "users.ai.editForm": "编辑用户表单",
79
+ } as const;
@@ -0,0 +1,98 @@
1
+ import { type HttpError, useTranslate } from "@refinedev/core";
2
+ import { useForm } from "@refinedev/react-hook-form";
3
+ import { useMemo } from "react";
4
+ import { useNavigate } from "react-router";
5
+
6
+ import { CreateView } from "@/components/resources/views/create-view";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Card, CardContent } from "@/components/ui/card";
9
+ import { Form } from "@/components/ui/form";
10
+ import { useAIForm, type AIFormField } from "@/extensions/nocobase-ai";
11
+ import {
12
+ applyAIUserFormValues,
13
+ getAIUserFormFields,
14
+ getAIUserFormValues,
15
+ } from "./form-context";
16
+ import { UserFormFields } from "./form-fields";
17
+ import type { UserFormValues, UserRecord } from "./types";
18
+
19
+ export const UserCreate = () => {
20
+ const translate = useTranslate();
21
+ const navigate = useNavigate();
22
+ const {
23
+ refineCore: { onFinish },
24
+ ...form
25
+ } = useForm<UserRecord, HttpError, UserFormValues>({
26
+ refineCoreProps: {
27
+ redirect: "list",
28
+ },
29
+ defaultValues: {
30
+ nickname: "",
31
+ username: "",
32
+ email: "",
33
+ phone: "",
34
+ password: "",
35
+ },
36
+ });
37
+
38
+ const aiFields = useMemo<AIFormField[]>(
39
+ () => getAIUserFormFields(translate),
40
+ [translate]
41
+ );
42
+ const aiFormRef = useAIForm({
43
+ id: "users-create-form",
44
+ title: translate("users.ai.createForm", { ns: "app" }, "Create user form"),
45
+ fields: aiFields,
46
+ getValues: () => getAIUserFormValues(form.getValues()),
47
+ setValues: (values) => applyAIUserFormValues(form, values),
48
+ });
49
+
50
+ return (
51
+ <CreateView>
52
+ <Card className="max-w-3xl">
53
+ <CardContent>
54
+ <Form {...form}>
55
+ <form
56
+ ref={aiFormRef}
57
+ onSubmit={form.handleSubmit((values) => onFinish(values))}
58
+ className="resource-form"
59
+ >
60
+ <UserFormFields
61
+ form={form}
62
+ includePassword
63
+ translate={translate}
64
+ />
65
+
66
+ <div className="flex flex-wrap gap-2">
67
+ <Button
68
+ type="submit"
69
+ {...form.saveButtonProps}
70
+ disabled={form.formState.isSubmitting}
71
+ >
72
+ {form.formState.isSubmitting
73
+ ? translate(
74
+ "users.form.create.submitting",
75
+ { ns: "app" },
76
+ "Creating..."
77
+ )
78
+ : translate(
79
+ "users.form.create.submit",
80
+ { ns: "app" },
81
+ "Create user"
82
+ )}
83
+ </Button>
84
+ <Button
85
+ type="button"
86
+ variant="outline"
87
+ onClick={() => navigate(-1)}
88
+ >
89
+ {translate("users.form.cancel", { ns: "app" }, "Cancel")}
90
+ </Button>
91
+ </div>
92
+ </form>
93
+ </Form>
94
+ </CardContent>
95
+ </Card>
96
+ </CreateView>
97
+ );
98
+ };
@@ -0,0 +1,92 @@
1
+ import {
2
+ type HttpError,
3
+ useResourceParams,
4
+ useTranslate,
5
+ } from "@refinedev/core";
6
+ import { useForm } from "@refinedev/react-hook-form";
7
+ import { useMemo } from "react";
8
+ import { useNavigate } from "react-router";
9
+
10
+ import { EditView } from "@/components/resources/views/edit-view";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Card, CardContent } from "@/components/ui/card";
13
+ import { Form } from "@/components/ui/form";
14
+ import { useAIForm, type AIFormField } from "@/extensions/nocobase-ai";
15
+ import {
16
+ applyAIUserFormValues,
17
+ getAIUserFormFields,
18
+ getAIUserFormValues,
19
+ } from "./form-context";
20
+ import { UserFormFields } from "./form-fields";
21
+ import type { UserFormValues, UserRecord } from "./types";
22
+
23
+ export const UserEdit = () => {
24
+ const translate = useTranslate();
25
+ const navigate = useNavigate();
26
+ const { id } = useResourceParams();
27
+ const {
28
+ refineCore: { onFinish },
29
+ ...form
30
+ } = useForm<UserRecord, HttpError, UserFormValues>({
31
+ refineCoreProps: {
32
+ redirect: "list",
33
+ },
34
+ });
35
+
36
+ const aiFields = useMemo<AIFormField[]>(
37
+ () => getAIUserFormFields(translate),
38
+ [translate]
39
+ );
40
+ const aiFormRef = useAIForm({
41
+ id: `users-edit-form-${id ?? "current"}`,
42
+ title: translate("users.ai.editForm", { ns: "app" }, "Edit user form"),
43
+ fields: aiFields,
44
+ getValues: () => getAIUserFormValues(form.getValues()),
45
+ setValues: (values) => applyAIUserFormValues(form, values),
46
+ });
47
+
48
+ return (
49
+ <EditView>
50
+ <Card className="max-w-3xl">
51
+ <CardContent>
52
+ <Form {...form}>
53
+ <form
54
+ ref={aiFormRef}
55
+ onSubmit={form.handleSubmit((values) => onFinish(values))}
56
+ className="resource-form"
57
+ >
58
+ <UserFormFields form={form} translate={translate} />
59
+
60
+ <div className="flex flex-wrap gap-2">
61
+ <Button
62
+ type="submit"
63
+ {...form.saveButtonProps}
64
+ disabled={form.formState.isSubmitting}
65
+ >
66
+ {form.formState.isSubmitting
67
+ ? translate(
68
+ "users.form.edit.submitting",
69
+ { ns: "app" },
70
+ "Saving..."
71
+ )
72
+ : translate(
73
+ "users.form.edit.submit",
74
+ { ns: "app" },
75
+ "Save changes"
76
+ )}
77
+ </Button>
78
+ <Button
79
+ type="button"
80
+ variant="outline"
81
+ onClick={() => navigate(-1)}
82
+ >
83
+ {translate("users.form.cancel", { ns: "app" }, "Cancel")}
84
+ </Button>
85
+ </div>
86
+ </form>
87
+ </Form>
88
+ </CardContent>
89
+ </Card>
90
+ </EditView>
91
+ );
92
+ };
@@ -0,0 +1,57 @@
1
+ import type { useTranslate } from "@refinedev/core";
2
+ import type { UseFormReturn } from "react-hook-form";
3
+
4
+ import { applyReactHookFormValues } from "@/extensions/nocobase-ai/adapters/react-hook-form";
5
+ import type { UserFormValues } from "./types";
6
+
7
+ type Translate = ReturnType<typeof useTranslate>;
8
+
9
+ const AI_EDITABLE_FIELDS = ["nickname", "username", "email", "phone"] as const;
10
+
11
+ export function getAIUserFormValues(values: UserFormValues) {
12
+ return Object.fromEntries(
13
+ AI_EDITABLE_FIELDS.map((name) => [name, values[name] ?? ""])
14
+ );
15
+ }
16
+
17
+ export function applyAIUserFormValues(
18
+ form: Pick<UseFormReturn<UserFormValues>, "setValue">,
19
+ values: Record<string, unknown>
20
+ ) {
21
+ applyReactHookFormValues(
22
+ form,
23
+ Object.fromEntries(
24
+ Object.entries(values).filter(([name]) =>
25
+ AI_EDITABLE_FIELDS.includes(name as (typeof AI_EDITABLE_FIELDS)[number])
26
+ )
27
+ )
28
+ );
29
+ }
30
+
31
+ export function getAIUserFormFields(translate: Translate) {
32
+ return [
33
+ {
34
+ name: "nickname",
35
+ title: translate("users.fields.nickname", { ns: "app" }, "Nickname"),
36
+ type: "string",
37
+ },
38
+ {
39
+ name: "username",
40
+ title: translate("users.fields.username", { ns: "app" }, "Username"),
41
+ type: "string",
42
+ required: true,
43
+ },
44
+ {
45
+ name: "email",
46
+ title: translate("users.fields.email", { ns: "app" }, "Email"),
47
+ type: "email",
48
+ required: true,
49
+ },
50
+ {
51
+ name: "phone",
52
+ title: translate("users.fields.phone", { ns: "app" }, "Phone"),
53
+ type: "string",
54
+ required: true,
55
+ },
56
+ ];
57
+ }
@@ -0,0 +1,211 @@
1
+ import { type useTranslate } from "@refinedev/core";
2
+ import type { UseFormReturn } from "react-hook-form";
3
+
4
+ import {
5
+ FormControl,
6
+ FormDescription,
7
+ FormField,
8
+ FormItem,
9
+ FormLabel,
10
+ FormMessage,
11
+ } from "@/components/ui/form";
12
+ import { Input } from "@/components/ui/input";
13
+ import type { UserFormValues } from "./types";
14
+
15
+ type Translate = ReturnType<typeof useTranslate>;
16
+
17
+ export function UserFormFields({
18
+ form,
19
+ includePassword,
20
+ translate,
21
+ }: {
22
+ form: UseFormReturn<UserFormValues>;
23
+ includePassword?: boolean;
24
+ translate: Translate;
25
+ }) {
26
+ return (
27
+ <>
28
+ <FormField
29
+ control={form.control}
30
+ name="nickname"
31
+ render={({ field }) => (
32
+ <FormItem>
33
+ <FormLabel>
34
+ {translate("users.fields.nickname", { ns: "app" }, "Nickname")}
35
+ </FormLabel>
36
+ <FormControl
37
+ render={
38
+ <Input
39
+ {...field}
40
+ value={field.value ?? ""}
41
+ placeholder={translate(
42
+ "users.form.nickname.placeholder",
43
+ { ns: "app" },
44
+ "Enter a display name"
45
+ )}
46
+ />
47
+ }
48
+ />
49
+ <FormMessage />
50
+ </FormItem>
51
+ )}
52
+ />
53
+
54
+ <FormField
55
+ control={form.control}
56
+ name="username"
57
+ rules={{
58
+ required: translate(
59
+ "users.validation.username",
60
+ { ns: "app" },
61
+ "Username is required"
62
+ ),
63
+ }}
64
+ render={({ field }) => (
65
+ <FormItem>
66
+ <FormLabel>
67
+ {translate("users.fields.username", { ns: "app" }, "Username")}
68
+ </FormLabel>
69
+ <FormControl
70
+ render={
71
+ <Input
72
+ {...field}
73
+ value={field.value ?? ""}
74
+ autoComplete="username"
75
+ placeholder={translate(
76
+ "users.form.username.placeholder",
77
+ { ns: "app" },
78
+ "Enter a unique username"
79
+ )}
80
+ />
81
+ }
82
+ />
83
+ <FormMessage />
84
+ </FormItem>
85
+ )}
86
+ />
87
+
88
+ <FormField
89
+ control={form.control}
90
+ name="email"
91
+ rules={{
92
+ required: translate(
93
+ "users.validation.email",
94
+ { ns: "app" },
95
+ "Email is required"
96
+ ),
97
+ pattern: {
98
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
99
+ message: translate(
100
+ "users.validation.emailFormat",
101
+ { ns: "app" },
102
+ "Enter a valid email address"
103
+ ),
104
+ },
105
+ }}
106
+ render={({ field }) => (
107
+ <FormItem>
108
+ <FormLabel>
109
+ {translate("users.fields.email", { ns: "app" }, "Email")}
110
+ </FormLabel>
111
+ <FormControl
112
+ render={
113
+ <Input
114
+ {...field}
115
+ value={field.value ?? ""}
116
+ type="email"
117
+ autoComplete="email"
118
+ placeholder={translate(
119
+ "users.form.email.placeholder",
120
+ { ns: "app" },
121
+ "Enter an email address"
122
+ )}
123
+ />
124
+ }
125
+ />
126
+ <FormMessage />
127
+ </FormItem>
128
+ )}
129
+ />
130
+
131
+ <FormField
132
+ control={form.control}
133
+ name="phone"
134
+ rules={{
135
+ required: translate(
136
+ "users.validation.phone",
137
+ { ns: "app" },
138
+ "Phone is required"
139
+ ),
140
+ }}
141
+ render={({ field }) => (
142
+ <FormItem>
143
+ <FormLabel>
144
+ {translate("users.fields.phone", { ns: "app" }, "Phone")}
145
+ </FormLabel>
146
+ <FormControl
147
+ render={
148
+ <Input
149
+ {...field}
150
+ value={field.value ?? ""}
151
+ type="tel"
152
+ autoComplete="tel"
153
+ placeholder={translate(
154
+ "users.form.phone.placeholder",
155
+ { ns: "app" },
156
+ "Enter a phone number"
157
+ )}
158
+ />
159
+ }
160
+ />
161
+ <FormMessage />
162
+ </FormItem>
163
+ )}
164
+ />
165
+
166
+ {includePassword ? (
167
+ <FormField
168
+ control={form.control}
169
+ name="password"
170
+ rules={{
171
+ required: translate(
172
+ "users.validation.password",
173
+ { ns: "app" },
174
+ "Password is required"
175
+ ),
176
+ }}
177
+ render={({ field }) => (
178
+ <FormItem>
179
+ <FormLabel>
180
+ {translate("users.fields.password", { ns: "app" }, "Password")}
181
+ </FormLabel>
182
+ <FormControl
183
+ render={
184
+ <Input
185
+ {...field}
186
+ value={field.value ?? ""}
187
+ type="password"
188
+ autoComplete="new-password"
189
+ placeholder={translate(
190
+ "users.form.password.placeholder",
191
+ { ns: "app" },
192
+ "Set an initial password"
193
+ )}
194
+ />
195
+ }
196
+ />
197
+ <FormDescription>
198
+ {translate(
199
+ "users.form.password.aiNotice",
200
+ { ns: "app" },
201
+ "Passwords are never included in AI page context or Form filler."
202
+ )}
203
+ </FormDescription>
204
+ <FormMessage />
205
+ </FormItem>
206
+ )}
207
+ />
208
+ ) : null}
209
+ </>
210
+ );
211
+ }