@lovrabet/rabetbase-cli 2.5.2-beta.2 → 2.5.2-beta.4

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 (354) hide show
  1. package/README.md +2 -2
  2. package/lib/api/api-doc.js +1 -1
  3. package/lib/api/fetch-model-list.js +1 -1
  4. package/lib/api/generate-api-file.js +1 -1
  5. package/lib/api/generate-sdk-config-file.js +1 -1
  6. package/lib/auth/auth-server-ui.js +1 -1
  7. package/lib/auth/auth-server.js +1 -1
  8. package/lib/auth/constant.js +1 -1
  9. package/lib/auth/get-cookie.js +1 -1
  10. package/lib/auth/get-session-user.js +1 -1
  11. package/lib/auth/is-session-valid.js +1 -1
  12. package/lib/auth/login-success-html.js +1 -1
  13. package/lib/auth/logout.js +1 -1
  14. package/lib/cli-flags.js +1 -1
  15. package/lib/cli.js +1 -1
  16. package/lib/commands/api/generate.js +1 -1
  17. package/lib/commands/api/index.js +1 -1
  18. package/lib/commands/api/list.js +1 -1
  19. package/lib/commands/api/pull.js +1 -1
  20. package/lib/commands/api/shared.js +1 -1
  21. package/lib/commands/app/index.js +1 -1
  22. package/lib/commands/app/list.js +1 -1
  23. package/lib/commands/app/members-list.js +1 -1
  24. package/lib/commands/app/remote-directory.js +1 -1
  25. package/lib/commands/app/shared.d.ts +1 -0
  26. package/lib/commands/app/shared.js +1 -1
  27. package/lib/commands/app-config/delete.js +1 -1
  28. package/lib/commands/app-config/get.js +1 -1
  29. package/lib/commands/app-config/index.js +1 -1
  30. package/lib/commands/app-config/list.js +1 -1
  31. package/lib/commands/app-config/set.js +1 -1
  32. package/lib/commands/app-config/shared.js +1 -1
  33. package/lib/commands/auth/index.js +1 -1
  34. package/lib/commands/bff/create.js +1 -1
  35. package/lib/commands/bff/delete.js +1 -1
  36. package/lib/commands/bff/detail.js +1 -1
  37. package/lib/commands/bff/index.js +1 -1
  38. package/lib/commands/bff/list.js +1 -1
  39. package/lib/commands/bff/pull.js +1 -1
  40. package/lib/commands/bff/push.js +1 -1
  41. package/lib/commands/bff/status.js +1 -1
  42. package/lib/commands/cli-skill/index.js +1 -1
  43. package/lib/commands/cli-update.js +1 -1
  44. package/lib/commands/codegen/index.js +1 -1
  45. package/lib/commands/codegen/sdk.js +1 -1
  46. package/lib/commands/codegen/sql.js +1 -1
  47. package/lib/commands/common/app-registry.js +1 -1
  48. package/lib/commands/common/app-selector.js +1 -1
  49. package/lib/commands/common/async-task.js +1 -1
  50. package/lib/commands/common/dry-run.js +1 -1
  51. package/lib/commands/common/flags.js +1 -1
  52. package/lib/commands/common/local-file.js +1 -1
  53. package/lib/commands/common/validate.js +1 -1
  54. package/lib/commands/config/delete.js +1 -1
  55. package/lib/commands/config/get.js +1 -1
  56. package/lib/commands/config/index.js +1 -1
  57. package/lib/commands/config/init.js +1 -1
  58. package/lib/commands/config/list.js +1 -1
  59. package/lib/commands/config/set.js +1 -1
  60. package/lib/commands/config/shared.js +1 -1
  61. package/lib/commands/dataset/business-group-update.js +1 -1
  62. package/lib/commands/dataset/business-groups.js +1 -1
  63. package/lib/commands/dataset/capability.js +1 -1
  64. package/lib/commands/dataset/delete.js +1 -1
  65. package/lib/commands/dataset/detail.js +1 -1
  66. package/lib/commands/dataset/extend-update.js +1 -1
  67. package/lib/commands/dataset/field-restore.js +1 -1
  68. package/lib/commands/dataset/field-update.js +1 -1
  69. package/lib/commands/dataset/generate.js +1 -1
  70. package/lib/commands/dataset/index.js +1 -1
  71. package/lib/commands/dataset/list.js +1 -1
  72. package/lib/commands/dataset/operations.js +1 -1
  73. package/lib/commands/dataset/relation-audit.js +1 -1
  74. package/lib/commands/dataset/relation-create.js +1 -1
  75. package/lib/commands/dataset/relation-delete.js +1 -1
  76. package/lib/commands/dataset/relation-shared.js +1 -1
  77. package/lib/commands/dataset/relation-update.js +1 -1
  78. package/lib/commands/dataset/relations.js +1 -1
  79. package/lib/commands/dataset/rename.js +1 -1
  80. package/lib/commands/dataset/restore.js +1 -1
  81. package/lib/commands/dataset/user-deleted-field-list.js +1 -1
  82. package/lib/commands/db/analysis-batching.js +1 -1
  83. package/lib/commands/db/analyze-batch-plan.js +1 -1
  84. package/lib/commands/db/analyze-cancel.js +1 -1
  85. package/lib/commands/db/analyze-start.js +1 -1
  86. package/lib/commands/db/analyze-status.js +1 -1
  87. package/lib/commands/db/create.js +1 -1
  88. package/lib/commands/db/delete.js +1 -1
  89. package/lib/commands/db/detail.js +1 -1
  90. package/lib/commands/db/diff-refresh-start.js +1 -1
  91. package/lib/commands/db/diff-refresh-status.js +1 -1
  92. package/lib/commands/db/diff.js +1 -1
  93. package/lib/commands/db/index.js +1 -1
  94. package/lib/commands/db/list.js +1 -1
  95. package/lib/commands/db/shared.js +1 -1
  96. package/lib/commands/db/table-diff-shared.js +1 -1
  97. package/lib/commands/db/tables.js +1 -1
  98. package/lib/commands/db/test.js +1 -1
  99. package/lib/commands/db/update.js +1 -1
  100. package/lib/commands/deployment/auto-sync.js +1 -1
  101. package/lib/commands/deployment/index.js +1 -1
  102. package/lib/commands/deployment/sync-all.js +1 -1
  103. package/lib/commands/deployment/sync-jobs.js +1 -1
  104. package/lib/commands/deployment/sync-status.js +1 -1
  105. package/lib/commands/deployment/sync.js +1 -1
  106. package/lib/commands/doctor.js +1 -1
  107. package/lib/commands/file/index.js +1 -1
  108. package/lib/commands/instant-api-policy/current.js +1 -1
  109. package/lib/commands/instant-api-policy/index.js +1 -1
  110. package/lib/commands/instant-api-policy/init.js +1 -1
  111. package/lib/commands/instant-api-policy/publish.js +1 -1
  112. package/lib/commands/instant-api-policy/pull.js +1 -1
  113. package/lib/commands/instant-api-policy/revision.js +1 -1
  114. package/lib/commands/instant-api-policy/revisions.js +1 -1
  115. package/lib/commands/instant-api-policy/rollback.js +1 -1
  116. package/lib/commands/instant-api-policy/shared.js +1 -1
  117. package/lib/commands/instant-api-policy/validate.js +1 -1
  118. package/lib/commands/issue/index.js +1 -1
  119. package/lib/commands/issue/nudge.js +1 -1
  120. package/lib/commands/issue/report.js +1 -1
  121. package/lib/commands/issue/shared.js +1 -1
  122. package/lib/commands/kb/create.js +1 -1
  123. package/lib/commands/kb/delete.js +1 -1
  124. package/lib/commands/kb/detail.js +1 -1
  125. package/lib/commands/kb/index.js +1 -1
  126. package/lib/commands/kb/list.js +1 -1
  127. package/lib/commands/kb/search.js +1 -1
  128. package/lib/commands/kb/shared.js +1 -1
  129. package/lib/commands/kb/update.js +1 -1
  130. package/lib/commands/logs/index.js +1 -1
  131. package/lib/commands/menu/asset-update.js +1 -1
  132. package/lib/commands/menu/delete.js +1 -1
  133. package/lib/commands/menu/external-link-create.js +1 -1
  134. package/lib/commands/menu/external-link-update.js +1 -1
  135. package/lib/commands/menu/group-create.js +1 -1
  136. package/lib/commands/menu/group-update.js +1 -1
  137. package/lib/commands/menu/index.js +1 -1
  138. package/lib/commands/menu/list.js +1 -1
  139. package/lib/commands/menu/move.js +1 -1
  140. package/lib/commands/menu/regroup-start.js +1 -1
  141. package/lib/commands/menu/rename.js +1 -1
  142. package/lib/commands/menu/shared/compare-table.js +1 -1
  143. package/lib/commands/menu/shared/delete-plan.js +1 -1
  144. package/lib/commands/menu/shared/facts.js +1 -1
  145. package/lib/commands/menu/shared/index.js +1 -1
  146. package/lib/commands/menu/shared/inquirer.js +1 -1
  147. package/lib/commands/menu/shared/local-pages.js +1 -1
  148. package/lib/commands/menu/shared/logic.js +1 -1
  149. package/lib/commands/menu/shared/mutations.js +1 -1
  150. package/lib/commands/menu/shared/service.js +1 -1
  151. package/lib/commands/menu/shared/sync-core.js +1 -1
  152. package/lib/commands/menu/shared/update-core.js +1 -1
  153. package/lib/commands/menu/shared/valid-url.js +1 -1
  154. package/lib/commands/menu/sync.js +1 -1
  155. package/lib/commands/menu/visibility-update.js +1 -1
  156. package/lib/commands/notification/config-create.js +1 -1
  157. package/lib/commands/notification/config-delete.js +1 -1
  158. package/lib/commands/notification/config-list.js +1 -1
  159. package/lib/commands/notification/config-update.js +1 -1
  160. package/lib/commands/notification/index.js +1 -1
  161. package/lib/commands/notification/shared.js +1 -1
  162. package/lib/commands/ocr/index.js +1 -1
  163. package/lib/commands/page/create.js +1 -1
  164. package/lib/commands/page/custom/detail.js +1 -1
  165. package/lib/commands/page/custom/list.js +1 -1
  166. package/lib/commands/page/custom/publish.js +1 -1
  167. package/lib/commands/page/custom/shared.d.ts +0 -8
  168. package/lib/commands/page/custom/shared.js +1 -1
  169. package/lib/commands/page/custom/syntax.js +1 -1
  170. package/lib/commands/page/custom/update.js +1 -1
  171. package/lib/commands/page/data-list-status.js +1 -1
  172. package/lib/commands/page/generate-start.js +1 -1
  173. package/lib/commands/page/generate-status.js +1 -1
  174. package/lib/commands/page/index.js +1 -1
  175. package/lib/commands/page/pull.js +1 -1
  176. package/lib/commands/page/push.js +1 -1
  177. package/lib/commands/page/relation-audit.js +1 -1
  178. package/lib/commands/page/restore.js +1 -1
  179. package/lib/commands/page/shared.js +1 -1
  180. package/lib/commands/page/sync.js +1 -1
  181. package/lib/commands/project/create/enhanced-guided-create.js +1 -1
  182. package/lib/commands/project/create/format-elapsed.js +1 -1
  183. package/lib/commands/project/create/main.js +1 -1
  184. package/lib/commands/project/create/materialize-project-template.js +1 -1
  185. package/lib/commands/project/create/project-name.js +1 -1
  186. package/lib/commands/project/create/project-template-archive.js +1 -1
  187. package/lib/commands/project/create/project-template-path.js +1 -1
  188. package/lib/commands/project/create/use-copy-project-template.js +1 -1
  189. package/lib/commands/project/create/use-format-code.js +1 -1
  190. package/lib/commands/project/create/use-install-dependencies.js +1 -1
  191. package/lib/commands/project/domain-routing-sync.js +1 -1
  192. package/lib/commands/project/index.js +1 -1
  193. package/lib/commands/project/upgrade.js +1 -1
  194. package/lib/commands/registry.js +1 -1
  195. package/lib/commands/role/delete.js +1 -1
  196. package/lib/commands/role/detail.js +1 -1
  197. package/lib/commands/role/index.js +1 -1
  198. package/lib/commands/role/list.js +1 -1
  199. package/lib/commands/role/shared.js +1 -1
  200. package/lib/commands/role/update.js +1 -1
  201. package/lib/commands/role/user-add.js +1 -1
  202. package/lib/commands/role/user-remove.js +1 -1
  203. package/lib/commands/role/user-resolve.js +1 -1
  204. package/lib/commands/rule/get.js +1 -1
  205. package/lib/commands/rule/index.js +1 -1
  206. package/lib/commands/rule/list.js +1 -1
  207. package/lib/commands/rule/set.js +1 -1
  208. package/lib/commands/rule/shared.js +1 -1
  209. package/lib/commands/run/index.d.ts +4 -0
  210. package/lib/commands/run/index.js +1 -1
  211. package/lib/commands/schema.js +1 -1
  212. package/lib/commands/sql/create.js +1 -1
  213. package/lib/commands/sql/delete.js +1 -1
  214. package/lib/commands/sql/detail.js +1 -1
  215. package/lib/commands/sql/exec.js +1 -1
  216. package/lib/commands/sql/index.js +1 -1
  217. package/lib/commands/sql/list.js +1 -1
  218. package/lib/commands/sql/pull.js +1 -1
  219. package/lib/commands/sql/push.js +1 -1
  220. package/lib/commands/sql/shared.js +1 -1
  221. package/lib/commands/sql/status.js +1 -1
  222. package/lib/commands/sql/validate.js +1 -1
  223. package/lib/commands/task/index.js +1 -1
  224. package/lib/commands/task/status.js +1 -1
  225. package/lib/commands/tenant/index.js +1 -1
  226. package/lib/commands/tenant/members-list.js +1 -1
  227. package/lib/commands/tenant/shared.js +1 -1
  228. package/lib/commands/user-account/dingding-sandbox-bind.js +1 -1
  229. package/lib/commands/user-account/index.js +1 -1
  230. package/lib/commands/workspace/add.js +1 -1
  231. package/lib/commands/workspace/index.js +1 -1
  232. package/lib/commands/workspace/remove.js +1 -1
  233. package/lib/config/domain-config.d.ts +3 -7
  234. package/lib/config/domain-config.js +1 -1
  235. package/lib/config/project-domain-routing.d.ts +5 -1
  236. package/lib/config/project-domain-routing.js +1 -1
  237. package/lib/config/schema.js +1 -1
  238. package/lib/constant/cdn.js +1 -1
  239. package/lib/constant/cli.js +1 -1
  240. package/lib/constant/defaults.js +1 -1
  241. package/lib/constant/domain.js +1 -1
  242. package/lib/constant/env.js +1 -1
  243. package/lib/constant/output.js +1 -1
  244. package/lib/constant/paths.js +1 -1
  245. package/lib/constant/region.js +1 -1
  246. package/lib/constant/risk.js +1 -1
  247. package/lib/constant/routing-profile.js +1 -1
  248. package/lib/context/app-resolver.js +1 -1
  249. package/lib/context/auth-resolver.js +1 -1
  250. package/lib/context/config-loader.js +1 -1
  251. package/lib/context.js +1 -1
  252. package/lib/core/alias-resolver.js +1 -1
  253. package/lib/core/api-client.d.ts +1 -26
  254. package/lib/core/api-client.js +1 -1
  255. package/lib/core/bff/config.js +1 -1
  256. package/lib/core/bff/file-system.js +1 -1
  257. package/lib/core/bff/hash.js +1 -1
  258. package/lib/core/bff/lock.js +1 -1
  259. package/lib/core/bff/utils.js +1 -1
  260. package/lib/core/db-resolver.js +1 -1
  261. package/lib/core/instant-api-policy/config.js +1 -1
  262. package/lib/core/kb-search-client.js +1 -1
  263. package/lib/core/page/file-system.d.ts +43 -0
  264. package/lib/core/page/file-system.js +1 -1
  265. package/lib/core/page/hash.js +1 -1
  266. package/lib/core/page/lock.js +1 -1
  267. package/lib/core/sql-index-auditor.js +1 -1
  268. package/lib/core/sql-sync/config.js +1 -1
  269. package/lib/core/sql-sync/file-system.js +1 -1
  270. package/lib/core/sql-sync/hash.js +1 -1
  271. package/lib/core/sql-sync/lock.js +1 -1
  272. package/lib/core/sql-sync/utils.js +1 -1
  273. package/lib/core/sql-validator.js +1 -1
  274. package/lib/errors.js +1 -1
  275. package/lib/framework/build-all-flags.js +1 -1
  276. package/lib/framework/error-output.js +1 -1
  277. package/lib/framework/explicit-yes.js +1 -1
  278. package/lib/framework/flags.js +1 -1
  279. package/lib/framework/help.js +1 -1
  280. package/lib/framework/index.js +1 -1
  281. package/lib/framework/output.js +1 -1
  282. package/lib/framework/response.js +1 -1
  283. package/lib/framework/runner-alias.js +1 -1
  284. package/lib/framework/runner.js +1 -1
  285. package/lib/framework/schema-export.js +1 -1
  286. package/lib/framework/supported-flags.js +1 -1
  287. package/lib/framework/types.js +1 -1
  288. package/lib/generated/build-info.d.ts +4 -4
  289. package/lib/generated/build-info.js +1 -1
  290. package/lib/generated/official-routing.d.ts +1 -1
  291. package/lib/generated/official-routing.js +1 -1
  292. package/lib/generated/routing-contract.d.ts +1 -3
  293. package/lib/generated/routing-contract.js +1 -1
  294. package/lib/help.js +1 -1
  295. package/lib/postinstall.js +1 -1
  296. package/lib/runtime/confirmation.js +1 -1
  297. package/lib/runtime/event.js +1 -1
  298. package/lib/runtime/index.js +1 -1
  299. package/lib/runtime/queue.js +1 -1
  300. package/lib/runtime/resolve.js +1 -1
  301. package/lib/skills/builtin-skill.js +1 -1
  302. package/lib/skills/main.js +1 -1
  303. package/lib/skills/npx-skills-add.js +1 -1
  304. package/lib/skills/skill-presence.js +1 -1
  305. package/lib/telemetry/cli-command-trace.js +1 -1
  306. package/lib/telemetry/cli-help-trace.js +1 -1
  307. package/lib/telemetry/ensure-cli-otel-config.js +1 -1
  308. package/lib/telemetry/register-otel-exit-shutdown.js +1 -1
  309. package/lib/telemetry/send-cli-trace-log.js +1 -1
  310. package/lib/telemetry/send-platform-issue-collect-log.js +1 -1
  311. package/lib/utils/ai_config.js +1 -1
  312. package/lib/utils/apply-jq-filter.js +1 -1
  313. package/lib/utils/cdn-config.js +1 -1
  314. package/lib/utils/check-sdk-version.js +1 -1
  315. package/lib/utils/cli-version-check.js +1 -1
  316. package/lib/utils/cli-version-policy.js +1 -1
  317. package/lib/utils/config.js +1 -1
  318. package/lib/utils/entity-with-id.js +1 -1
  319. package/lib/utils/file-utils.js +1 -1
  320. package/lib/utils/guides-cdn.js +1 -1
  321. package/lib/utils/http-client.js +1 -1
  322. package/lib/utils/is-non-interactive.js +1 -1
  323. package/lib/utils/logger.js +1 -1
  324. package/lib/utils/params.js +1 -1
  325. package/lib/utils/platform.js +1 -1
  326. package/lib/utils/sleep.js +1 -1
  327. package/lib/utils/template-replacer.js +1 -1
  328. package/lib/utils/update-notice.js +1 -1
  329. package/lib/utils/version.js +1 -1
  330. package/lib/utils/with-active-cookie.js +1 -1
  331. package/lib/utils/write-cli-side-channel.js +1 -1
  332. package/package.json +2 -1
  333. package/skills/rabetbase/SKILL.md +6 -6
  334. package/skills/rabetbase/guides/custom-page-workflow.md +35 -17
  335. package/skills/rabetbase/guides/page-development-workflow.md +1 -1
  336. package/skills/rabetbase/knowledge/components.md +292 -0
  337. package/skills/rabetbase/knowledge/custom-page/generation-standards.md +74 -4
  338. package/skills/rabetbase/knowledge/custom-page/page-templates.md +68 -0
  339. package/skills/rabetbase/references/rabetbase-app-members-list.md +2 -0
  340. package/skills/rabetbase/references/rabetbase-page-create.md +21 -9
  341. package/skills/rabetbase/references/rabetbase-page-custom-detail.md +1 -1
  342. package/skills/rabetbase/references/rabetbase-page-custom-update.md +13 -9
  343. package/skills/rabetbase/references/rabetbase-project-create.md +5 -2
  344. package/skills/rabetbase/references/rabetbase-project-domain-routing-sync.md +2 -2
  345. package/skills/rabetbase/references/rabetbase-run.md +1 -0
  346. package/skills/rabetbase.manifest.json +5 -4
  347. package/templates/README.md +10 -4
  348. package/templates/custom-pages/blank.json +5 -0
  349. package/templates/custom-pages/dashboard.json +5 -0
  350. package/templates/custom-pages/onepage.json +5 -0
  351. package/templates/generate-api/api.ts.tpl +1 -1
  352. package/lib/commands/page/custom/template.d.ts +0 -8
  353. package/lib/commands/page/custom/template.js +0 -1
  354. package/skills/rabetbase/knowledge/custom-page/components.md +0 -225
@@ -7,7 +7,9 @@
7
7
  ```bash
8
8
  rabetbase page create --page-pattern BLANK --name "客户看板" --appcode <appCode> --dry-run --format compress
9
9
  rabetbase page create --page-pattern BLANK --name "客户看板" --parent-menu-id 100 --appcode <appCode>
10
- rabetbase page create --name "客户看板" --page-content '<json>' --appcode <appCode>
10
+ rabetbase page create --page-pattern ONEPAGE --name "客户看板" --appcode <appCode> --dry-run --format compress
11
+ rabetbase page create --page-pattern DASHBOARD --name "业务数据看板" --appcode <appCode> --dry-run --format compress
12
+ rabetbase page create --name "客户看板" --page-dir ./customer-dashboard --appcode <appCode>
11
13
  ```
12
14
 
13
15
  ## 参数
@@ -16,16 +18,26 @@ rabetbase page create --name "客户看板" --page-content '<json>' --appcode <a
16
18
  |---|---|---|
17
19
  | `--name <name>` | 否 | 可选页面名称,1–100 个字符;省略时默认 `Custom_page_<timestamp>` |
18
20
  | `--parent-menu-id <id>` | 否 | 父菜单 ID |
19
- | `--page-pattern <pattern>` | 条件必填 | 页面模式;当前只支持 `BLANK`,与 `--page-content` 必须且只能提供一个 |
20
- | `--page-content <json>` | 条件必填 | 完整页面文件 JSON;对象的键为相对路径,值为文件内容,与 `--page-pattern` 必须且只能提供一个 |
21
+ | `--page-pattern <pattern>` | 条件必填 | 页面模式;支持 `BLANK`、`ONEPAGE`、`DASHBOARD`,不能与 `--page-dir` 同时使用;模板详情见 [`page-templates.md`](../knowledge/custom-page/page-templates.md) |
22
+ | `--page-dir <dir>` | 条件必填 | 完整页面文件目录;不能与 `--page-pattern` 同时使用,目录必须包含文件;CLI 递归读取目录内的常规文件,将相对路径和 UTF-8 文件内容作为完整页面文件提交 |
21
23
  | `--appcode <code>` | 是 | 目标应用 code |
22
24
 
25
+ ## 页面内容来源
26
+
27
+ 1. 使用内置模板时显式传入 `--page-pattern`
28
+ 2. 使用完整页面文件时显式传入 `--page-dir`
29
+ 3. 两者同时提供、均未提供或目录为空时,本地校验失败
30
+
23
31
  ## 执行步骤
24
32
 
25
- 1. 使用基础模板时传 `--page-pattern BLANK`;已有完整源码时只传 `--page-content`。
26
- 2. 使用 `--dry-run` 确认页面名称、父菜单与最终页面文件。
27
- 3. 确认后使用相同参数执行创建。
28
- 4. 从结构化输出的 `data.after.pageId` 确认新页面 ID,并核对 `data.pageType=CUSTOM`;模式创建返回 `data.pagePattern=BLANK`,完整源码创建返回 `null`。
29
- 5. 输出的 `pageUrl` 用于查看最新保存内容,包含尚未发布的修改;`editPageUrl` 用于打开页面编辑器,继续修改页面。
33
+ 1. 不使用页面模板时,若本地已有完整页面目录,直接将该目录传给 `--page-dir`;若本地没有已有内容,则先在当前工作目录创建临时专用页面目录并生成完整页面文件,再传 `--page-dir`。CLI 读取目录后负责转换为请求内容,调用方不需要也不得自行拼接 `page-content` JSON。
34
+ 2. 使用内置模板时,按 [`page-templates.md`](../knowledge/custom-page/page-templates.md) 选择 `BLANK`、`ONEPAGE` 或 `DASHBOARD`,并显式传入对应的 `--page-pattern`。
35
+ 3. 使用 `--dry-run` 确认页面名称、父菜单与最终页面文件。
36
+ 4. 确认后使用相同参数执行创建。
37
+ 5. 仅当步骤 1 创建了临时页面目录时,正式创建流程成功、失败、中断或取消后删除该目录,删除范围仅限本流程创建的临时目录;通过 `--page-dir` 传入的本地已有页面目录不得删除。
38
+ 6. 创建成功时,从结构化输出的 `data.after.pageId` 确认新页面 ID,并核对 `data.pageType=CUSTOM`;模板创建返回对应的 `data.pagePattern`,目录创建返回 `data.pagePattern=null`。
39
+ 7. 输出的 `pageUrl` 用于查看最新保存内容,包含尚未发布的修改;`editPageUrl` 用于打开页面编辑器,继续修改页面。
40
+
41
+ 页面创建始终同时创建菜单。`BLANK`、`ONEPAGE` 与 `DASHBOARD` 分别读取内置的 `templates/custom-pages/blank.json`、`templates/custom-pages/onepage.json` 与 `templates/custom-pages/dashboard.json` 作为完整页面文件。未发布模式会在本地失败,不请求服务端。
30
42
 
31
- 页面创建始终同时创建菜单。`BLANK` 模式会按应用语言设置生成词包;读取设置失败时使用默认中英文词包。未发布模式会在本地失败,不请求服务端。
43
+ 三个模板的适用场景、内容边界、mock 数据替换、真实数据接入和交互设计统一见 [`page-templates.md`](../knowledge/custom-page/page-templates.md)。页面开发和 `client` 使用方式见 [`generation-standards.md`](../knowledge/custom-page/generation-standards.md)。
@@ -23,4 +23,4 @@ rabetbase page custom-detail --id <pageId> --format compress
23
23
  - `runtimePageUrl`:查看已发布内容的页面地址,仅在有已发布内容时返回;host 为 `<appCode>.<appDomain host>`
24
24
  - `editPageUrl`:打开当前节点或独立部署工作台中的页面编辑器
25
25
 
26
- `data.codeContent` 是当前完整页面文件,可在本地完成新增、修改或删除后,作为 `page custom-update --page-content <json>` 的完整页面内容提交。
26
+ `data.codeContent` 是当前完整页面文件。将其写入本地目录后完成新增、修改或删除,再以 `page custom-update --page-dir <dir>` 提交该目录中的完整页面内容。
@@ -1,10 +1,10 @@
1
1
  # page custom-update
2
2
 
3
- 以完整页面文件 JSON 更新 自定义页面,并创建新的保存版本。未包含的文件会被删除。
3
+ 从本地目录读取完整页面文件更新 自定义页面,并创建新的保存版本。目录中未包含的文件会被删除。
4
4
 
5
5
  ```bash
6
- rabetbase page custom-update --id <pageId> --page-content '<json>' --dry-run --format compress
7
- rabetbase page custom-update --id <pageId> --page-content '<json>' --appcode <appCode>
6
+ rabetbase page custom-update --id <pageId> --page-dir <dir> --dry-run --format compress
7
+ rabetbase page custom-update --id <pageId> --page-dir <dir> --appcode <appCode>
8
8
  ```
9
9
 
10
10
  ## 参数
@@ -12,13 +12,17 @@ rabetbase page custom-update --id <pageId> --page-content '<json>' --appcode <ap
12
12
  | 参数 | 必填 | 说明 |
13
13
  |---|---|---|
14
14
  | `--id <pageId>` | 是 | 自定义页面 ID |
15
- | `--page-content <json>` | 是 | 完整页面文件 JSON;对象的键为相对路径,值为文件内容 |
15
+ | `--page-dir <dir>` | 是 | 包含完整页面文件的本地目录,CLI 递归读取其中的常规文件 |
16
16
  | `--appcode <code>` | 否 | 目标应用 code,省略时根据 `pageId` 查询 |
17
17
 
18
18
  ## 执行步骤
19
19
 
20
- 1. 先使用 `page custom-detail` 读取 `data.codeContent` 中的完整页面文件。
21
- 2. 在本地完成所有文件新增、修改或删除后,使用 `--dry-run` 确认完整页面文件。
22
- 3. 确认后使用相同参数执行更新。
23
- 4. 从结构化输出的 `data.after.version` 确认已保存新版本。
24
- 5. 输出的 `pageUrl` 用于查看最新保存内容,包含尚未发布的修改;`editPageUrl` 用于打开页面编辑器,继续修改页面。
20
+ 1. 先使用 `page custom-detail` 读取 `data.codeContent` 中的完整页面文件,并写入专用本地目录。
21
+ 2. 需要调整页面布局或交互时,以具体需求和最新页面文件为准。[`page-templates.md`](../knowledge/custom-page/page-templates.md) 中的 `BLANK`、`ONEPAGE` 和 `DASHBOARD` 仅作为可选参考;模板实现适合当前需求时,可将相关结构、样式和交互选择性合并到最新页面文件中,不要求使用模板。
22
+ 3. 在该目录完成所有文件新增、修改或删除后,使用 `--page-dir` 与 `--dry-run` 确认完整页面文件。
23
+ 4. 确认后使用相同参数执行更新。
24
+ 5. 正式更新命令结束后,无论成功、失败或中断,删除步骤 1 创建的临时页面目录,删除范围仅限该目录。
25
+ 6. 从结构化输出的 `data.after.version` 确认已保存新版本。
26
+ 7. 输出的 `pageUrl` 用于查看最新保存内容,包含尚未发布的修改;`editPageUrl` 用于打开页面编辑器,继续修改页面。
27
+
28
+ `page custom-update` 不接受 `--page-pattern`。内置模板在更新场景中仅作为实现参考,详细规则见 [`page-templates.md`](../knowledge/custom-page/page-templates.md);更新始终以 `page custom-detail` 返回的最新完整页面内容为基线,避免覆盖已有功能和尚未发布的修改。
@@ -57,6 +57,9 @@ rabetbase project create my-project --appcode <code>
57
57
 
58
58
  `api pull` 的单应用/项目应用清单输出结构见 [`rabetbase-api-pull.md`](rabetbase-api-pull.md),TypeScript 合并规则见 [`sdk-client-generation.md`](../guides/sdk-client-generation.md)。Agent 可根据实际输出选择检查、修复和验证方式。
59
59
 
60
+
61
+ 项目创建完成后,如需在子应用页面中使用公共组件,先阅读 [`components.md`](../knowledge/components.md) 确认最新组件用法,并可参考项目内 `src/pages/components-demo` 的交互和实现。`components-demo` 必须在项目配置了有效 AppCode 后使用,优先在创建工程时传入 `--appcode <code>`;如创建后补充 AppCode,需执行 `rabetbase config set appcode <code>` 和 `rabetbase api pull` 完成初始化,否则用户选择、附件上传等依赖应用信息的组件无法使用。示例中的 mock 数据和富文本图片 mock 上传仅用于展示效果,正式页面需按实际业务替换。
62
+
60
63
  ## 提示
61
64
 
62
65
  - `--name` 和位置参数二选一,`--name` 优先
@@ -67,7 +70,7 @@ rabetbase project create my-project --appcode <code>
67
70
  - 交互与非交互模式使用同一创建流程,都会安装依赖、格式化代码并写入项目配置
68
71
  - 项目模板从 CDN 下载并校验 SHA-256;CDN 不可用、模板不兼容或校验失败时命令停止,修复后重试
69
72
  - 新项目中的 `.rabetbase.json` **只继承**全局中的少量偏好及 `region` / Domain 路由配置,**不会**把全局的 `apps` / `defaultApp` 复制进新项目
70
- - `src/api/sdk-config.ts` 与 `rabetbase.domain-routing.json` 只生成浏览器可公开的最终路由,不会写入 `cookie`、`accessKey` 等认证配置;Domain 配置变化后用 [`project domain-routing-sync`](rabetbase-project-domain-routing-sync.md) 刷新项目快照。`api.ts` / `client.ts` 仅在缺失时由 CLI 写脚手架,已有文件按 [`sdk-client-generation.md`](../guides/sdk-client-generation.md) 更新;若项目创建时首次拉取失败,按 CLI 提示执行 `rabetbase api pull --force --yes` 完成占位脚手架初始化
73
+ - `src/api/sdk-config.ts` 与 `rabetbase.domain-routing.json` 只生成浏览器可公开的最终路由,不会写入 `cookie`、`accessKey` 等认证配置;`rabetbase run start|dev|build|preview` 会在执行脚本前刷新公开 Domain 快照,也可用 [`project domain-routing-sync`](rabetbase-project-domain-routing-sync.md) 立即显式刷新。`api.ts` / `client.ts` 仅在缺失时由 CLI 写脚手架,已有文件按 [`sdk-client-generation.md`](../guides/sdk-client-generation.md) 更新;若项目创建时首次拉取失败,按 CLI 提示执行 `rabetbase api pull --force --yes` 完成占位脚手架初始化
71
74
 
72
75
  | 当前有效路由 | `src/api/sdk-config.ts` |
73
76
  |--------------|-------------------------|
@@ -76,7 +79,7 @@ rabetbase project create my-project --appcode <code>
76
79
  | Global | `{ runtimeDomain: "https://runtime.lovrabet.ai" }`,兼容 SDK 1.5.3 |
77
80
  | 独立部署 | `{ runtimeDomain: "https://…" }` |
78
81
 
79
- 模板依赖 SDK 1.5.3+。CLI 从统一 Routing Profile 生成 `rabetbase.domain-routing.json`,其中只包含最终 User/App/Runtime Domain、`cdn.libraries`、`cdn.lovrabet`、本地地址和证书 URL,不复制节点注册表。每个官方节点直接配置两个 CDN 地址;中国大陆当前使用 AliCDN,印尼当前使用 Cloudflare cdnjs,任一新节点都可以使用独立 CDN。企业独立部署使用清单中的显式值。
82
+ 模板依赖 SDK 1.5.3+。CLI 从统一 Routing Profile 生成 `rabetbase.domain-routing.json`,其中只包含最终 API/User/App/Runtime/Skill/KB Domain、`cdn.libraries`、`cdn.lovrabet`、本地地址和证书 URL,不复制节点注册表。每个官方节点直接配置两个 CDN 地址;中国大陆当前使用 AliCDN,印尼当前使用 Cloudflare cdnjs,任一新节点都可以使用独立 CDN。企业独立部署使用清单中的显式值。
80
83
 
81
84
  ## 参考
82
85
 
@@ -16,12 +16,12 @@ rabetbase project domain-routing-sync --format compress
16
16
  - 不要求登录或 AppCode,不访问平台,不生成全局副本。
17
17
  - 该文件供项目模板与 Vite 消费,不是 React Router、页面 path 或菜单路由配置。
18
18
 
19
- 生成内容只包含公开信息:User/App/Runtime Domain、资源策略、本地开发 Domain 与可选证书 URL;不会写入 Cookie、AccessKey 等认证配置。
19
+ 生成内容只包含公开信息:API/User/App/Runtime/Skill/KB Domain、资源策略、本地开发 Domain 与可选证书 URL;不会写入 Cookie、AccessKey 等认证配置。
20
20
 
21
21
  ## 使用时机
22
22
 
23
23
  - `project create` 会自动完成首次生成,无需紧接着重复执行。
24
- - 修改项目级 Domain 配置后执行。
24
+ - 修改项目级 Domain 配置后需要立即刷新时执行;后续执行 `rabetbase run start|dev|build|preview` 也会在脚本启动前自动刷新。
25
25
  - 全局 Domain 配置变化且当前项目需要采用新结果时执行。
26
26
  - 文件缺失或模板提示重新生成时执行。
27
27
 
@@ -34,6 +34,7 @@ rabetbase run build -- --mode production
34
34
  ## 特殊行为
35
35
 
36
36
  - **自动检测包管理器**:按 lock 文件判断 bun / pnpm / yarn / npm
37
+ - **公开 Domain 快照**:在 Rabetbase 项目中执行 `start`、`dev`、`build` 或 `preview` 前,根据当前有效配置刷新 `rabetbase.domain-routing.json`;内容未变化时不重写、不输出提示
37
38
  - **`start` / `dev` 脚本**:执行前自动检查 CLI 和 SDK 版本,有更新时打印升级警告
38
39
  - **`preview --watch`**:并行启动 `vite build --watch` 和 `vite preview`,适合开发时预览生产构建
39
40
 
@@ -2,8 +2,8 @@
2
2
  "schemaVersion": 1,
3
3
  "name": "rabetbase",
4
4
  "packageName": "@lovrabet/rabetbase-cli",
5
- "packageVersion": "2.5.2-beta.2",
6
- "skillVersion": "2.5.2-beta.2",
5
+ "packageVersion": "2.5.2-beta.4",
6
+ "skillVersion": "2.5.2-beta.4",
7
7
  "files": [
8
8
  "SKILL.md",
9
9
  "guides/backend-function.md",
@@ -22,8 +22,9 @@
22
22
  "guides/sql-mybatis.md",
23
23
  "guides/troubleshooting.md",
24
24
  "guides/typescript-sdk.md",
25
- "knowledge/custom-page/components.md",
25
+ "knowledge/components.md",
26
26
  "knowledge/custom-page/generation-standards.md",
27
+ "knowledge/custom-page/page-templates.md",
27
28
  "knowledge/page-schema/LrSmartCreate.md",
28
29
  "knowledge/page-schema/LrSmartDetail.md",
29
30
  "knowledge/page-schema/LrSmartFilter.md",
@@ -139,5 +140,5 @@
139
140
  "references/rabetbase-user-account.md",
140
141
  "references/rabetbase-workspace.md"
141
142
  ],
142
- "digest": "0367fd969de461022c6e87709aa5802f1c1403aaec8165599f0c49716d438912"
143
+ "digest": "dffe4df9136a202fd8cb263d8c4c6e4cb4f0f50b18b59e8745eb2fc282a50f58"
143
144
  }
@@ -7,6 +7,7 @@
7
7
  ```
8
8
  templates/
9
9
  ├── README.md # 本说明文档
10
+ ├── custom-pages/ # React 自定义页面内置模板
10
11
  ├── generate-api/ # API 生成模板
11
12
  └── projects/ # 项目模板,用于 `rabetbase project create` 命令
12
13
  └── sub-app-react-demo/ # React 微前端项目模板真源
@@ -16,6 +17,10 @@ templates/
16
17
 
17
18
  `generate-api/` 目录包含用于生成 API 配置的模板。
18
19
 
20
+ ## 自定义页面模板
21
+
22
+ `custom-pages/` 目录包含 React 自定义页面内置模板。模板选型、内容边界和使用方式统一见 [`page-templates.md`](../skills/rabetbase/knowledge/custom-page/page-templates.md);命令参数见 [`page create` 命令说明](../skills/rabetbase/references/rabetbase-page-create.md)。
23
+
19
24
  ## 项目模板
20
25
 
21
26
  ### 模板真源与 CDN 资产
@@ -67,10 +72,11 @@ npm run sync:cdn
67
72
 
68
73
  添加新模板时:
69
74
 
70
- 1. **项目模板**:放置在 `templates/projects/`
71
- 2. **文件扩展名**:需要处理的模板文件使用 `.tpl`
72
- 3. **本地测试**:提交前使用新模板生成项目进行测试
73
- 4. **更新此 README**:记录任何新功能
75
+ 1. **自定义页面模板**:放置在 `templates/custom-pages/` 中;模板标识使用大写,JSON 文件名使用对应的小写形式,并同步命令枚举、测试和 [`page-templates.md`](../skills/rabetbase/knowledge/custom-page/page-templates.md)
76
+ 2. **项目模板**:放置在 `templates/projects/` 中
77
+ 3. **文件扩展名**:需要处理的项目模板文件使用 `.tpl`
78
+ 4. **本地测试**:提交前使用新模板生成页面或项目进行测试
79
+ 5. **更新此 README**:记录任何新功能
74
80
 
75
81
  ## 注意事项
76
82
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "src/app/index.css": ".page-container {\n padding: 12px;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n}\n",
3
+ "src/app/index.jsx": "import { Card, ConfigProvider, theme as antdTheme } from \"antd\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\nconst App = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useAppTheme();\n\n $i18n.addLocale(locales);\n\n return (\n <ConfigProvider\n theme={{\n algorithm:\n theme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n cssVar: { key: \"blank-theme\" },\n ...(theme?.skinColor\n ? { token: { colorPrimary: theme.skinColor } }\n : {}),\n }}\n >\n <main className=\"blank-theme page-container\">\n <Card>{$i18n.t(\"pageDescription\")}</Card>\n </main>\n </ConfigProvider>\n );\n};\n\nexport default App;\n",
4
+ "src/locales/index.js": "export const locales = {\n 'zh-CN': {\n pageDescription: '这是一个基础页面模板,你可以在此基础上进行开发',\n },\n 'en-US': {\n pageDescription: 'This is a basic page template, you can develop based on it',\n },\n 'ja-JP': {\n pageDescription: 'これは基本ページテンプレートです。あなたは、これに基づいて開発できます',\n },\n 'tr-TR': {\n pageDescription: 'Bu bir temel sayfa şablonudur. Bu temaplana dayanarak geliştirilebilir',\n },\n 'id-ID': {\n pageDescription: 'Ini adalah sebuah halaman dasar. Anda dapat mengembangkan ini',\n },\n};\n"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "src/app/index.css": ".dashboard-container {\n padding: 12px;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n}\n.dashboard-toolbar {\n display: flex;\n justify-content: flex-end;\n margin-bottom: 20px;\n}\n.dashboard-metrics__list,\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid {\n display: grid;\n gap: 12px;\n}\n.dashboard-card__content,\n.dashboard-metrics__list,\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid,\n.dashboard-chart-card,\n.dashboard-chart-card .ant-card-body,\n.dashboard-chart {\n min-width: 0;\n}\n.dashboard-metrics,\n.dashboard-section {\n margin-bottom: 12px;\n}\n.dashboard-metrics__list {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.dashboard-card {\n min-width: 0;\n padding: 12px;\n border-radius: 8px;\n background: var(--ant-color-bg-container);\n box-shadow: var(--ant-box-shadow-tertiary);\n}\n.dashboard-card__title {\n display: block;\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 600;\n}\n.dashboard-card__content {\n margin-top: 12px;\n}\n.dashboard-metric {\n height: 100%;\n border-radius: 8px;\n transition:\n box-shadow 0.3s ease,\n transform 0.3s ease;\n}\n.dashboard-metric:hover {\n box-shadow: var(--ant-box-shadow-secondary);\n cursor: pointer;\n transform: translateY(-2px);\n}\n.dashboard-metric__body {\n display: flex;\n min-height: 118px;\n flex-direction: column;\n}\n.dashboard-metric__statistic {\n text-align: left;\n}\n.dashboard-metric .ant-statistic-content {\n color: var(--ant-color-text-heading);\n font-weight: 700;\n}\n.dashboard-metric .ant-statistic-content-suffix {\n color: var(--ant-color-text);\n font-size: 14px;\n}\n.dashboard-metric__trend {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: auto;\n color: var(--ant-color-text-secondary);\n font-size: 12px;\n line-height: 1.4;\n}\n.dashboard-metric__trend-value {\n color: var(--ant-color-error);\n font-weight: 700;\n}\n.dashboard-chart-card {\n display: flex;\n height: 100%;\n flex-direction: column;\n}\n.dashboard-chart-card .ant-card-body {\n flex: 1;\n}\n.dashboard-chart-card__header {\n padding: 0 12px;\n border-bottom: 2px solid var(--ant-color-primary);\n}\n.dashboard-chart-card__title {\n color: var(--ant-color-text);\n font-weight: 600;\n}\n.dashboard-grid,\n.dashboard-split,\n.dashboard-chart-grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.dashboard-chart {\n width: 100%;\n height: 220px;\n}\n.dashboard-chart--trend {\n height: 244px;\n}\n@media (max-width: 960px) {\n .dashboard-metrics__list {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n@media (max-width: 600px) {\n .dashboard-toolbar .ant-picker {\n width: 100%;\n }\n .dashboard-metrics__list,\n .dashboard-grid,\n .dashboard-chart-grid,\n .dashboard-split {\n grid-template-columns: 1fr;\n }\n}\n",
3
+ "src/app/index.jsx": "import { useEffect, useRef, useState } from \"react\";\nimport {\n Card,\n ConfigProvider,\n DatePicker,\n Statistic,\n Tag,\n theme as antdTheme,\n} from \"antd\";\nimport * as echarts from \"echarts/core\";\nimport { BarChart, FunnelChart, LineChart, PieChart } from \"echarts/charts\";\nimport {\n GridComponent,\n LegendComponent,\n TooltipComponent,\n} from \"echarts/components\";\nimport { CanvasRenderer } from \"echarts/renderers\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\necharts.use([\n BarChart,\n FunnelChart,\n LineChart,\n PieChart,\n GridComponent,\n LegendComponent,\n TooltipComponent,\n CanvasRenderer,\n]);\n\nconst DETAIL_PAGE_CODE = \"/replace-with-dashboard-detail-page-code\";\nconst metrics = [\n {\n key: \"dealAmount\",\n value: 128.6,\n precision: 1,\n suffix: \"unit.tenThousand\",\n },\n {\n key: \"paymentAmount\",\n value: 96.2,\n precision: 1,\n suffix: \"unit.tenThousand\",\n },\n { key: \"newOpportunities\", value: 36, suffix: \"unit.count\" },\n { key: \"newCustomers\", value: 18, suffix: \"unit.count\" },\n];\nconst documents = [\n [\"sales.opportunity\", 86],\n [\"sales.quotation\", 54],\n [\"sales.order\", 31],\n [\"sales.payment\", 22],\n];\nconst channels = [\n [\"channel.online\", 42],\n [\"channel.referral\", 28],\n [\"channel.event\", 18],\n [\"channel.other\", 12],\n];\nconst customerLevels = [\n [\"customer.a\", 28],\n [\"customer.b\", 35],\n [\"customer.c\", 24],\n [\"customer.d\", 13],\n];\nconst opportunityStages = [\n [\"stage.contact\", 34],\n [\"stage.requirement\", 29],\n [\"stage.solution\", 22],\n [\"stage.contract\", 15],\n];\nconst revenueTrend = [42, 54, 48, 68, 73, 65, 82, 91, 86, 102, 109, 128];\nconst departments = [\n [\"department.east\", 92],\n [\"department.south\", 86],\n [\"department.north\", 79],\n [\"department.west\", 72],\n];\n\nconst getThemeConfig = (appTheme) => ({\n algorithm:\n appTheme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n components: {\n Card: { bodyPadding: 12 },\n Statistic: { contentFontSize: 24, titleFontSize: 14 },\n },\n cssVar: { key: \"dashboard-theme\" },\n token: {\n borderRadius: 8,\n ...(appTheme?.skinColor ? { colorPrimary: appTheme.skinColor } : {}),\n },\n});\n\nconst Section = ({ children, className = \"\", title }) => (\n <section className={[\"dashboard-card\", className].filter(Boolean).join(\" \")}>\n <span className=\"dashboard-card__title\">{title}</span>\n <div className=\"dashboard-card__content\">{children}</div>\n </section>\n);\n\nconst ChartSection = ({ children, title }) => (\n <Card\n className=\"dashboard-chart-card\"\n classNames={{\n header: \"dashboard-chart-card__header\",\n title: \"dashboard-chart-card__title\",\n }}\n title={title}\n >\n {children}\n </Card>\n);\n\nconst createPieOption = (data, $i18n, colors, token) => ({\n color: colors,\n legend: {\n bottom: 0,\n left: \"center\",\n textStyle: { color: token.colorTextSecondary },\n },\n series: [\n {\n data: data.map(([key, value]) => ({ name: $i18n.t(key), value })),\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowColor: token.colorBgMask,\n shadowOffsetX: 0,\n },\n },\n radius: \"50%\",\n type: \"pie\",\n },\n ],\n tooltip: { trigger: \"item\" },\n});\n\nconst createBarOption = (data, $i18n, colors, token, horizontal = false) => {\n const labels = data.map(([key]) => $i18n.t(key));\n const categoryAxis = {\n axisLabel: { color: token.colorTextSecondary },\n axisLine: { lineStyle: { color: token.colorBorderSecondary } },\n data: labels,\n type: \"category\",\n };\n const valueAxis = {\n axisLabel: { color: token.colorTextSecondary },\n splitLine: { lineStyle: { color: token.colorBorderSecondary } },\n type: \"value\",\n };\n\n return {\n grid: horizontal\n ? { bottom: 16, containLabel: true, left: 16, right: 16, top: 20 }\n : { bottom: 52, containLabel: true, left: 16, right: 16, top: 20 },\n series: [\n {\n barMaxWidth: 18,\n data: data.map(([, value], index) => ({\n itemStyle: { color: colors[index % colors.length] },\n value,\n })),\n type: \"bar\",\n },\n ],\n tooltip: { trigger: \"axis\" },\n xAxis: horizontal ? valueAxis : categoryAxis,\n yAxis: horizontal ? categoryAxis : valueAxis,\n };\n};\n\nconst Chart = ({ className = \"\", onItemClick, option }) => {\n const elementRef = useRef(null);\n const chartRef = useRef(null);\n\n useEffect(() => {\n const chart = echarts.init(elementRef.current);\n const resize = () => chart.resize();\n\n chartRef.current = chart;\n window.addEventListener(\"resize\", resize);\n\n return () => {\n window.removeEventListener(\"resize\", resize);\n chart.dispose();\n chartRef.current = null;\n };\n }, []);\n\n useEffect(() => {\n chartRef.current?.setOption(option, { notMerge: true });\n }, [option]);\n\n useEffect(() => {\n const chart = chartRef.current;\n\n if (!chart || !onItemClick) return;\n\n chart.on(\"click\", onItemClick);\n return () => {\n if (!chart.isDisposed()) chart.off(\"click\", onItemClick);\n };\n }, [onItemClick]);\n\n return (\n <div\n className={[\"dashboard-chart\", className].filter(Boolean).join(\" \")}\n ref={elementRef}\n />\n );\n};\n\nconst DashboardContent = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const { token } = antdTheme.useToken();\n const [filter, setFilter] = useState({ factor: 1, range: [] });\n const chartColors = [\n token.colorPrimary,\n token.colorInfo,\n token.colorSuccess,\n token.colorWarning,\n ];\n\n const scale = (data) =>\n data.map(([key, value]) => [key, Math.round(value * filter.factor)]);\n const filteredRevenueTrend = revenueTrend.map((value) =>\n Math.round(value * filter.factor),\n );\n\n $i18n.addLocale(locales);\n\n const openDetail = ({ name, value }) =>\n navigate(DETAIL_PAGE_CODE, {\n onePage: true,\n state: {\n dimension: name,\n filterRange: filter.range,\n from: location.pathname + location.search,\n value,\n },\n });\n\n const changeRange = (dates) => {\n if (!dates) {\n setFilter({ factor: 1, range: [] });\n return;\n }\n\n const days = dates[1].diff(dates[0], \"day\") + 1;\n setFilter({\n factor: Math.max(0.75, Math.min(1.25, days / 30)),\n range: dates.map((date) => date.format(\"YYYY-MM-DD\")),\n });\n };\n\n return (\n <main className=\"dashboard-container\">\n <div className=\"dashboard-toolbar\">\n <DatePicker.RangePicker\n allowClear={false}\n onChange={changeRange}\n placeholder={[\n $i18n.t(\"date.startPlaceholder\"),\n $i18n.t(\"date.endPlaceholder\"),\n ]}\n />\n </div>\n\n <Section className=\"dashboard-metrics\" title={$i18n.t(\"metrics.title\")}>\n <div className=\"dashboard-metrics__list\">\n {metrics.map((metric) => {\n const value = Number(\n (metric.value * filter.factor).toFixed(metric.precision ?? 0),\n );\n const openMetric = () =>\n openDetail({\n name: $i18n.t(\"metrics.\" + metric.key),\n value,\n });\n\n return (\n <Card\n className=\"dashboard-metric\"\n classNames={{ body: \"dashboard-metric__body\" }}\n key={metric.key}\n onClick={openMetric}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") openMetric();\n }}\n role=\"button\"\n tabIndex={0}\n title={$i18n.t(\"detail.pageCodeHint\")}\n >\n <Statistic\n className=\"dashboard-metric__statistic\"\n precision={metric.precision}\n suffix={$i18n.t(metric.suffix)}\n title={$i18n.t(\"metrics.\" + metric.key)}\n value={value}\n />\n <span className=\"dashboard-metric__trend\">\n {$i18n.t(\"monthOnMonth\")}\n <span className=\"dashboard-metric__trend-value\">\n +{(8 + filter.factor * 4.8).toFixed(1)}%\n </span>\n </span>\n </Card>\n );\n })}\n </div>\n </Section>\n\n <Section\n className=\"dashboard-section\"\n title={$i18n.t(\"salesConversion\")}\n >\n <div className=\"dashboard-chart-grid\">\n <ChartSection title={$i18n.t(\"salesDocuments\")}>\n <Chart\n onItemClick={openDetail}\n option={{\n color: chartColors,\n series: [\n {\n data: scale(documents).map(([key, value]) => ({\n name: $i18n.t(key),\n value,\n })),\n label: { formatter: \"{b} {c}\", position: \"inside\" },\n type: \"funnel\",\n },\n ],\n tooltip: { trigger: \"item\" },\n }}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"salesChannels\")}>\n <Chart\n onItemClick={openDetail}\n option={createBarOption(\n scale(channels),\n $i18n,\n chartColors,\n token,\n )}\n />\n </ChartSection>\n </div>\n </Section>\n\n <Section\n className=\"dashboard-section\"\n title={$i18n.t(\"customerOpportunityAnalysis\")}\n >\n <div className=\"dashboard-split\">\n <ChartSection title={$i18n.t(\"customerLevel\")}>\n <Chart\n onItemClick={openDetail}\n option={createPieOption(\n scale(customerLevels),\n $i18n,\n chartColors,\n token,\n )}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"opportunityStage\")}>\n <Chart\n onItemClick={openDetail}\n option={createBarOption(\n scale(opportunityStages),\n $i18n,\n chartColors,\n token,\n true,\n )}\n />\n </ChartSection>\n </div>\n </Section>\n\n <section className=\"dashboard-grid\">\n <ChartSection title={$i18n.t(\"revenueDealTrendAnalysis\")}>\n <Chart\n className=\"dashboard-chart--trend\"\n onItemClick={openDetail}\n option={{\n color: [token.colorPrimary],\n grid: { bottom: 28, left: 40, right: 16, top: 20 },\n tooltip: { trigger: \"axis\" },\n xAxis: {\n axisLabel: { color: token.colorTextSecondary },\n axisLine: {\n lineStyle: { color: token.colorBorderSecondary },\n },\n boundaryGap: false,\n data: filteredRevenueTrend.map(\n (_, index) => index + 1 + $i18n.t(\"month\"),\n ),\n type: \"category\",\n },\n yAxis: {\n axisLabel: { color: token.colorTextSecondary },\n splitLine: {\n lineStyle: { color: token.colorBorderSecondary },\n },\n type: \"value\",\n },\n series: [\n {\n areaStyle: { opacity: 0.16 },\n data: filteredRevenueTrend,\n lineStyle: { width: 3 },\n smooth: true,\n type: \"line\",\n },\n ],\n }}\n />\n </ChartSection>\n <ChartSection title={$i18n.t(\"departmentEfficiency\")}>\n <Chart\n onItemClick={openDetail}\n option={createPieOption(\n scale(departments),\n $i18n,\n chartColors,\n token,\n )}\n />\n <Tag color={token.colorPrimary}>{$i18n.t(\"efficiencyHint\")}</Tag>\n </ChartSection>\n </section>\n </main>\n );\n};\n\nconst Dashboard = () => {\n const theme = useAppTheme();\n\n return (\n <ConfigProvider theme={getThemeConfig(theme)}>\n <DashboardContent />\n </ConfigProvider>\n );\n};\n\nexport default Dashboard;\n",
4
+ "src/locales/index.js": "export const locales = {\n \"zh-CN\": {\n monthOnMonth: \"环比\",\n month: \"月\",\n \"date.startPlaceholder\": \"开始日期\",\n \"date.endPlaceholder\": \"结束日期\",\n \"unit.tenThousand\": \"万\",\n \"unit.count\": \"个\",\n \"detail.pageCodeHint\": \"示例页面地址,请替换为实际明细页面 code\",\n \"metrics.title\": \"核心指标\",\n \"metrics.dealAmount\": \"成交金额\",\n \"metrics.paymentAmount\": \"回款金额\",\n \"metrics.newOpportunities\": \"新增商机\",\n \"metrics.newCustomers\": \"新增客户\",\n salesConversion: \"销售转化\",\n salesDocuments: \"销售单据\",\n salesChannels: \"销售渠道\",\n \"sales.opportunity\": \"销售机会\",\n \"sales.quotation\": \"报价单\",\n \"sales.order\": \"订单\",\n \"sales.payment\": \"回款\",\n \"channel.online\": \"线上获客\",\n \"channel.referral\": \"客户转介绍\",\n \"channel.event\": \"市场活动\",\n \"channel.other\": \"其他渠道\",\n customerOpportunityAnalysis: \"客户与商机分析\",\n customerLevel: \"客户等级\",\n opportunityStage: \"商机阶段\",\n \"customer.a\": \"A 级客户\",\n \"customer.b\": \"B 级客户\",\n \"customer.c\": \"C 级客户\",\n \"customer.d\": \"D 级客户\",\n \"stage.contact\": \"初步接触\",\n \"stage.requirement\": \"需求确认\",\n \"stage.solution\": \"方案制定\",\n \"stage.contract\": \"合同签约\",\n revenueDealTrendAnalysis: \"营收与成交趋势分析\",\n departmentEfficiency: \"部门效能\",\n \"department.east\": \"华东销售部\",\n \"department.south\": \"华南销售部\",\n \"department.north\": \"华北销售部\",\n \"department.west\": \"华西销售部\",\n efficiencyHint: \"整体转化效率较上月提升 8.6%\",\n },\n \"en-US\": {\n monthOnMonth: \"MoM\",\n month: \"M\",\n \"date.startPlaceholder\": \"Start date\",\n \"date.endPlaceholder\": \"End date\",\n \"unit.tenThousand\": \"10k\",\n \"unit.count\": \"items\",\n \"detail.pageCodeHint\": \"Replace the example path with the detail page code\",\n \"metrics.title\": \"Key metrics\",\n \"metrics.dealAmount\": \"Deal amount\",\n \"metrics.paymentAmount\": \"Payment amount\",\n \"metrics.newOpportunities\": \"New opportunities\",\n \"metrics.newCustomers\": \"New customers\",\n salesConversion: \"Sales conversion\",\n salesDocuments: \"Sales documents\",\n salesChannels: \"Sales channels\",\n \"sales.opportunity\": \"Opportunity\",\n \"sales.quotation\": \"Quotation\",\n \"sales.order\": \"Order\",\n \"sales.payment\": \"Payment\",\n \"channel.online\": \"Online\",\n \"channel.referral\": \"Referral\",\n \"channel.event\": \"Event\",\n \"channel.other\": \"Other\",\n customerOpportunityAnalysis: \"Customer and opportunity analysis\",\n customerLevel: \"Customer level\",\n opportunityStage: \"Opportunity stage\",\n \"customer.a\": \"Level A\",\n \"customer.b\": \"Level B\",\n \"customer.c\": \"Level C\",\n \"customer.d\": \"Level D\",\n \"stage.contact\": \"Initial contact\",\n \"stage.requirement\": \"Requirement\",\n \"stage.solution\": \"Solution\",\n \"stage.contract\": \"Contract\",\n revenueDealTrendAnalysis: \"Revenue and deal trend analysis\",\n departmentEfficiency: \"Department efficiency\",\n \"department.east\": \"East sales\",\n \"department.south\": \"South sales\",\n \"department.north\": \"North sales\",\n \"department.west\": \"West sales\",\n efficiencyHint: \"Overall conversion efficiency is up 8.6% month over month\",\n },\n};\n"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "src/app/index.css": ".crm-page {\n display: flex;\n gap: 12px;\n min-width: 1180px;\n align-items: start;\n color: var(--ant-color-text);\n background: var(--ant-color-bg-layout);\n font-size: 14px;\n padding: 12px;\n}\n.crm-page *,\n.crm-page *::before,\n.crm-page *::after {\n box-sizing: border-box;\n}\n.crm-panel-actions,\n.crm-record-card__top,\n.crm-record-card__bottom,\n.crm-note-card__top {\n display: flex;\n align-items: center;\n}\n.crm-column {\n display: grid;\n flex: 1 1 270px;\n gap: 12px;\n min-width: 0;\n}\n.crm-column--main {\n flex: 2 1 550px;\n}\n.crm-panel.ant-card {\n overflow: hidden;\n border-radius: 8px;\n background: var(--ant-color-bg-container);\n box-shadow: var(--ant-box-shadow-tertiary);\n}\n.crm-panel .ant-card-head {\n min-height: 40px;\n padding: 0 12px;\n border-bottom: 0;\n}\n.crm-panel .ant-card-head-title {\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 500;\n}\n.crm-panel .ant-card-extra {\n padding: 12px 0;\n}\n.crm-panel .ant-card-body {\n padding: 12px;\n}\n.crm-customer-card__select {\n width: 100%;\n height: 54px;\n}\n.crm-customer-card__select .ant-select-selector {\n height: 54px !important;\n padding: 0 12px !important;\n border-color: var(--ant-color-border) !important;\n border-radius: 8px !important;\n}\n.crm-customer-card__select .ant-select-selection-item {\n overflow: hidden;\n color: var(--ant-color-text-heading);\n font-size: 20px;\n font-weight: 700;\n line-height: 52px !important;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.crm-customer-card__select .ant-select-arrow {\n top: 0;\n display: flex;\n height: 54px;\n margin-top: 0;\n align-items: center;\n color: var(--ant-color-text-secondary);\n font-size: 16px;\n}\n.crm-customer-card__contact {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 12px;\n margin-top: 12px;\n}\n.crm-customer-card__contact span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.crm-tag-row {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n}\n.crm-tag-row .ant-tag,\n.crm-record-card .ant-tag,\n.crm-note-card .ant-tag {\n margin-inline-end: 0;\n font-size: 13px;\n}\n.crm-metric-panel > .ant-card-body {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 14px;\n}\n.crm-metric-card {\n padding: 12px;\n border: 1px solid var(--ant-color-border-secondary);\n border-radius: 12px;\n}\n.crm-metric-card .ant-typography {\n color: var(--ant-color-text);\n}\n.crm-metric-card__value {\n margin-top: 12px;\n color: var(--ant-color-text-heading);\n font-size: 31px;\n font-weight: 700;\n line-height: 1;\n}\n.crm-metric-card__unit {\n margin-left: 4px;\n font-size: 15px;\n font-weight: 500;\n}\n.crm-info-list {\n display: grid;\n gap: 12px;\n margin: 0;\n}\n.crm-info-list > div {\n display: grid;\n gap: 7px;\n}\n.crm-info-list dt,\n.crm-info-list dd {\n margin: 0;\n}\n.crm-info-list dt {\n color: var(--ant-color-text-secondary);\n}\n.crm-info-list dd {\n overflow-wrap: anywhere;\n}\n.crm-info-link {\n color: var(--ant-color-primary);\n text-decoration: underline;\n}\n.crm-record-panel > .ant-card-body,\n.crm-note-panel > .ant-card-body {\n display: grid;\n gap: 12px;\n}\n.crm-record-card,\n.crm-note-card {\n padding: 12px;\n border: 1px solid var(--ant-color-border-secondary);\n border-radius: 10px;\n cursor: pointer;\n}\n.crm-record-card.is-selected,\n.crm-note-card.is-active {\n border-color: var(--ant-color-primary);\n background: var(--ant-color-primary-bg);\n}\n.crm-record-card__top,\n.crm-record-card__bottom,\n.crm-note-card__top {\n justify-content: space-between;\n}\n.crm-record-card__title {\n margin: 6px 0;\n font-weight: 500;\n}\n.crm-record-card__bottom {\n color: var(--ant-color-text-secondary);\n}\n.crm-record-card__amount {\n color: var(--ant-color-text-heading);\n font-size: 17px;\n font-weight: 700;\n}\n.crm-panel-actions {\n gap: 8px;\n}\n.crm-opportunity-title {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr) max-content max-content;\n gap: 12px 18px;\n align-items: center;\n}\n.crm-opportunity-title .ant-typography {\n color: var(--ant-color-text-secondary);\n}\n.crm-opportunity-title__name,\n.crm-opportunity-title__id {\n color: var(--ant-color-text-heading);\n}\n.crm-opportunity-title__name {\n font-size: 16px;\n font-weight: 500;\n}\n.crm-opportunity-title__id {\n font-weight: 700;\n}\n.crm-stage-bar {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n margin: 20px 0 26px;\n overflow: hidden;\n border-radius: 2px;\n}\n.crm-stage-bar span {\n padding: 10px 6px;\n color: var(--ant-color-text);\n background: var(--ant-color-primary-bg);\n text-align: center;\n}\n.crm-stage-bar span + span {\n border-left: 2px solid var(--ant-color-bg-container);\n}\n.crm-stage-bar .is-current {\n color: var(--ant-color-white);\n background: var(--ant-color-primary);\n font-weight: 600;\n}\n.crm-info-list--two-column {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n column-gap: 44px;\n row-gap: 12px;\n}\n.crm-info-list--two-column > div {\n grid-template-columns: 92px minmax(0, 1fr);\n gap: 8px;\n}\n.crm-description {\n margin-top: 20px;\n}\n.crm-info-list dd,\n.crm-record-card__title,\n.crm-description p,\n.crm-note-card p,\n.crm-note-card__owner {\n color: var(--ant-color-text);\n}\n.crm-description p,\n.crm-note-card p {\n margin: 6px 0;\n}\n.crm-followup-panel__subtitle {\n display: block;\n margin: 28px 0 18px;\n color: var(--ant-color-text-heading);\n font-size: 16px;\n font-weight: 600;\n}\n.crm-calendar-button {\n margin-top: 28px;\n}\n@media (max-width: 1500px) {\n .crm-page {\n min-width: 0;\n }\n .crm-column--main {\n flex-basis: 520px;\n }\n}\n@media (max-width: 1080px) {\n .crm-column--main {\n flex-basis: 100%;\n }\n}\n@media (max-width: 680px) {\n .crm-page {\n padding: 12px;\n }\n .crm-column {\n flex-basis: 100%;\n }\n .crm-panel .ant-card-head,\n .crm-panel .ant-card-body {\n padding-inline: 14px;\n }\n .crm-metric-card {\n min-height: 96px;\n padding: 14px 12px;\n }\n .crm-metric-card__value {\n font-size: 25px;\n }\n .crm-opportunity-title,\n .crm-info-list--two-column {\n grid-template-columns: 1fr;\n }\n .crm-opportunity-title {\n gap: 6px;\n }\n .crm-info-list--two-column > div {\n grid-template-columns: 84px minmax(0, 1fr);\n }\n .crm-stage-bar {\n overflow-x: auto;\n grid-template-columns: repeat(4, 112px);\n }\n}\n",
3
+ "src/app/index.jsx": "import { useEffect, useState } from \"react\";\nimport {\n Button,\n Card,\n ConfigProvider,\n Select,\n Tag,\n Typography,\n theme as antdTheme,\n} from \"antd\";\nimport {\n useAppTheme,\n useI18n,\n useLocation,\n useNavigate,\n useSdkClient,\n} from \"@/context/app-context\";\nimport { locales } from \"../locales\";\nimport \"./index.css\";\n\nconst { Text } = Typography;\nconst CHILD_PAGE_CODE = \"/replace-with-page-code\";\nconst localizedFieldKeys = new Set([\n \"customer.level\",\n \"customer.type\",\n \"followup.method\",\n \"followup.nextMethod\",\n \"opportunity.source\",\n \"opportunity.stage\",\n]);\nconst stages = [\n \"stage.contact\",\n \"stage.requirement\",\n \"stage.solution\",\n \"stage.contract\",\n];\nconst metrics = [\n { key: \"historyAmount\", value: \"4,500\", unit: \"unit.tenThousandYuan\" },\n { key: \"orderAmount\", value: \"502\", unit: \"unit.tenThousandYuan\" },\n { key: \"opportunityCount\", value: \"12\", unit: \"unit.record\" },\n { key: \"quotationCount\", value: \"5\", unit: \"unit.record\" },\n];\nconst customerFields = [\n [\"customer.name\", \"科技有限公司\"],\n [\"customer.id\", \"000001\"],\n [\"customer.level\", \"customer.levelValue\"],\n [\"customer.type\", \"customer.typeValue\"],\n [\"customer.address\", \"浙江省/杭州市/余杭区/五常街道/***软件园 A6\"],\n];\nconst createNote = ({\n id,\n document,\n customer,\n date,\n type,\n content,\n nextDate,\n nextMethod,\n remark,\n}) => ({\n date,\n type,\n content,\n followup: {\n summaryFields: [\n [\"followup.id\", id],\n [\"followup.document\", document],\n [\"followup.customer\", customer],\n [\"followup.owner\", \"张明明\"],\n ],\n infoFields: [\n [\"followup.method\", type],\n [\"followup.date\", date],\n [\"followup.content\", content],\n ],\n planFields: [\n [\"followup.nextDate\", nextDate],\n [\"followup.nextMethod\", nextMethod],\n [\"followup.remark\", remark],\n ],\n },\n});\nconst opportunities = [\n {\n id: \"12321\",\n name: \"员工绩效系统改造项目\",\n owner: \"张明明\",\n amount: \"30,000\",\n stage: \"stage.contact\",\n color: \"blue\",\n fields: [\n [\"opportunity.stage\", \"stage.contact\"],\n [\"opportunity.customer\", \"科技有限公司\"],\n [\"opportunity.contact\", \"张明明\"],\n [\"opportunity.phone\", \"1808888****\"],\n [\"opportunity.amount\", \"30,000\"],\n [\"opportunity.currency\", \"CNY\"],\n [\"opportunity.rate\", \"50%\"],\n [\"opportunity.closeDate\", \"2026-05-30\"],\n [\"opportunity.source\", \"source.clueConversion\"],\n [\"opportunity.createdAt\", \"2026-01-03 14:31:22\"],\n [\"opportunity.clue\", \"4523671\"],\n [\"opportunity.owner\", \"张明明\"],\n [\"opportunity.competitor\", \"云科技\"],\n [\"opportunity.updatedAt\", \"2026-01-05 14:31:22\"],\n ],\n description:\n \"客户计划由传统 KPI 考核切换为 OKR 管理模式,需要采购并实施一套新的绩效管理系统\",\n notes: [\n createNote({\n id: \"691\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-20 10:22:55\",\n type: \"method.phone\",\n content: \"了解客户需求,客户正在调研绩效管理系统市场并制定新的管理制度\",\n nextDate: \"2026-03-24\",\n nextMethod: \"method.offlineMeeting\",\n remark: \"安排技术人员参与,确认系统迁移方案\",\n }),\n createNote({\n id: \"690\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-16 14:02:30\",\n type: \"method.offlineMeeting\",\n content: \"介绍系统能力与成功案例,客户表示还需要进一步研究管理制度\",\n nextDate: \"2026-03-20\",\n nextMethod: \"method.phone\",\n remark: \"收集客户对 OKR 管理模式的反馈\",\n }),\n createNote({\n id: \"689\",\n document: \"OP-12321\",\n customer: \"科技有限公司\",\n date: \"2026-03-12 16:11:37\",\n type: \"method.phone\",\n content: \"管理层希望通过新的制度化管理提升员工积极性和目标管理效率\",\n nextDate: \"2026-03-16\",\n nextMethod: \"method.offlineMeeting\",\n remark: \"准备绩效管理案例与实施建议\",\n }),\n ],\n },\n {\n id: \"12323\",\n name: \"积分兑换系统采购\",\n owner: \"张明明\",\n amount: \"52,000\",\n stage: \"stage.solution\",\n color: \"purple\",\n fields: [\n [\"opportunity.stage\", \"stage.solution\"],\n [\"opportunity.customer\", \"积分科技有限公司\"],\n [\"opportunity.contact\", \"张明明\"],\n [\"opportunity.phone\", \"1862688****\"],\n [\"opportunity.amount\", \"52,000\"],\n [\"opportunity.currency\", \"CNY\"],\n [\"opportunity.rate\", \"60%\"],\n [\"opportunity.closeDate\", \"2026-06-18\"],\n [\"opportunity.source\", \"source.onlineInquiry\"],\n [\"opportunity.createdAt\", \"2026-01-09 10:20:18\"],\n [\"opportunity.clue\", \"4523672\"],\n [\"opportunity.owner\", \"张明明\"],\n [\"opportunity.competitor\", \"叮咚科技\"],\n [\"opportunity.updatedAt\", \"2026-01-12 16:45:03\"],\n ],\n description:\n \"客户拟升级会员积分体系,需要建设积分兑换商城,并与现有会员系统打通\",\n notes: [\n createNote({\n id: \"692\",\n document: \"OP-12323\",\n customer: \"积分科技有限公司\",\n date: \"2026-03-21 14:30:18\",\n type: \"method.videoMeeting\",\n content: \"确认积分兑换商品范围,并沟通与现有会员系统的接口方案\",\n nextDate: \"2026-03-26\",\n nextMethod: \"method.phone\",\n remark: \"整理方案与报价,确认接口对接范围\",\n }),\n ],\n },\n];\nconst quotations = [\n {\n id: \"12321\",\n code: \"BJ-20240901-095\",\n date: \"2026-03-18\",\n amount: \"30,000\",\n status: \"quotation.sent\",\n color: \"blue\",\n },\n {\n id: \"12323\",\n code: \"BJ-20240833-012\",\n date: \"2026-03-20\",\n amount: \"52,000\",\n status: \"quotation.accepted\",\n color: \"green\",\n },\n];\n\nconst InfoList = ({ fields, twoColumn = false }) => {\n const $i18n = useI18n();\n\n return (\n <dl\n className={`crm-info-list${twoColumn ? \" crm-info-list--two-column\" : \"\"}`}\n >\n {fields.map(([label, value]) => (\n <div key={label}>\n <dt>{$i18n.t(label)}</dt>\n <dd>\n {label === \"opportunity.clue\" ? (\n <a className=\"crm-info-link\" href=\"#clue-4523671\">\n {value}\n </a>\n ) : (\n localizedFieldKeys.has(label) ? $i18n.t(value) : value\n )}\n </dd>\n </div>\n ))}\n </dl>\n );\n};\n\nconst CustomerColumn = () => {\n const $i18n = useI18n();\n\n return (\n <aside className=\"crm-column\">\n <Card className=\"crm-panel\" variant=\"borderless\">\n <Select\n className=\"crm-customer-card__select\"\n defaultValue=\"default\"\n options={[{ label: $i18n.t(\"customer.nameValue\"), value: \"default\" }]}\n />\n <div className=\"crm-tag-row\">\n <Tag color=\"cyan\" variant=\"solid\">\n {$i18n.t(\"customer.active\")}\n </Tag>\n <Tag color=\"blue\" variant=\"solid\">\n {$i18n.t(\"customer.levelValue\")}\n </Tag>\n </div>\n <div className=\"crm-customer-card__contact\">\n <Text type=\"secondary\">{$i18n.t(\"customer.primaryContact\")}</Text>\n <span>张明明</span>\n </div>\n </Card>\n <Card className=\"crm-metric-panel crm-panel\" variant=\"borderless\">\n {metrics.map((metric) => (\n <div className=\"crm-metric-card\" key={metric.key}>\n <Text>{$i18n.t(`metrics.${metric.key}`)}</Text>\n <div className=\"crm-metric-card__value\">\n {metric.value}\n <span className=\"crm-metric-card__unit\">\n {$i18n.t(metric.unit)}\n </span>\n </div>\n </div>\n ))}\n </Card>\n <Card\n className=\"crm-panel\"\n title={$i18n.t(\"customer.title\")}\n variant=\"borderless\"\n >\n <InfoList fields={customerFields} />\n </Card>\n </aside>\n );\n};\n\nconst RecordCard = ({ item, onClick, selected, quotation }) => {\n const $i18n = useI18n();\n\n return (\n <article\n className={`crm-record-card ${selected ? \"is-selected\" : \"\"}`}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n >\n <div className=\"crm-record-card__top\">\n <Text type=\"secondary\">ID: {item.id}</Text>\n <Tag color={item.color}>\n {$i18n.t(quotation ? item.status : item.stage)}\n </Tag>\n </div>\n <div className=\"crm-record-card__title\">\n {quotation ? item.code : item.name}\n </div>\n <div className=\"crm-record-card__bottom\">\n <span>\n {quotation ? item.date : `${$i18n.t(\"related.owner\")}: ${item.owner}`}\n </span>\n <span className=\"crm-record-card__amount\">¥{item.amount}</span>\n </div>\n </article>\n );\n};\n\nconst RelatedColumn = ({\n onOpenChildPage,\n onSelectOpportunity,\n selectedOpportunityId,\n}) => {\n const $i18n = useI18n();\n const [selectedQuotationId, setSelectedQuotationId] = useState(\n quotations[0].id,\n );\n\n return (\n <section className=\"crm-column\">\n <Card\n className=\"crm-panel crm-record-panel\"\n extra={\n <Button\n ghost\n onClick={() => onOpenChildPage(\"createOpportunity\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.add\")}\n </Button>\n }\n title={`${$i18n.t(\"related.opportunities\")} (${opportunities.length})`}\n variant=\"borderless\"\n >\n {opportunities.map((item) => (\n <RecordCard\n item={item}\n key={item.id}\n onClick={() => onSelectOpportunity(item)}\n selected={item.id === selectedOpportunityId}\n />\n ))}\n </Card>\n <Card\n className=\"crm-panel crm-record-panel\"\n title={`${$i18n.t(\"related.quotations\")} (${quotations.length})`}\n variant=\"borderless\"\n >\n {quotations.map((item) => (\n <RecordCard\n item={item}\n key={item.id}\n onClick={() => setSelectedQuotationId(item.id)}\n quotation\n selected={item.id === selectedQuotationId}\n />\n ))}\n </Card>\n </section>\n );\n};\n\nconst OpportunityColumn = ({\n onOpenChildPage,\n onRefresh,\n onSelectNote,\n opportunity,\n selectedNoteDate,\n}) => {\n const $i18n = useI18n();\n return (\n <section className=\"crm-column crm-column--main\">\n <Card\n className=\"crm-panel\"\n extra={\n <div className=\"crm-panel-actions\">\n <Button onClick={onRefresh} size=\"small\">\n {$i18n.t(\"actions.refresh\")}\n </Button>\n <Button\n ghost\n onClick={() => onOpenChildPage(\"confirmOpportunity\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.confirm\")}\n </Button>\n </div>\n }\n title={$i18n.t(\"opportunity.basicInfo\")}\n variant=\"borderless\"\n >\n <div className=\"crm-opportunity-title\">\n <Text type=\"secondary\">{$i18n.t(\"opportunity.name\")}</Text>\n <span className=\"crm-opportunity-title__name\">\n {opportunity.name}\n </span>\n <Text type=\"secondary\">{$i18n.t(\"opportunity.id\")}</Text>\n <span className=\"crm-opportunity-title__id\">{opportunity.id}</span>\n </div>\n <div\n className=\"crm-stage-bar\"\n aria-label={$i18n.t(\"opportunity.stage\")}\n >\n {stages.map((stage) => (\n <span\n className={stage === opportunity.stage ? \"is-current\" : \"\"}\n key={stage}\n >\n {$i18n.t(stage)}\n </span>\n ))}\n </div>\n <InfoList fields={opportunity.fields} twoColumn />\n <div className=\"crm-description\">\n <Text type=\"secondary\">{$i18n.t(\"opportunity.description\")}</Text>\n <p>{opportunity.description}</p>\n </div>\n </Card>\n <Card\n className=\"crm-note-panel crm-panel\"\n extra={\n <Button\n ghost\n onClick={() => onOpenChildPage(\"createNote\")}\n size=\"small\"\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.addNote\")}\n </Button>\n }\n title={`${$i18n.t(\"notes.title\")} (${opportunity.notes.length})`}\n variant=\"borderless\"\n >\n {opportunity.notes.map((note) => (\n <article\n className={`crm-note-card ${note.date === selectedNoteDate ? \"is-active\" : \"\"}`}\n key={note.date}\n onClick={() => onSelectNote(note)}\n role=\"button\"\n tabIndex={0}\n >\n <div className=\"crm-note-card__top\">\n <Text type=\"secondary\">{note.date}</Text>\n <Tag color=\"blue\">{$i18n.t(note.type)}</Tag>\n </div>\n <p>{note.content}</p>\n <Text type=\"secondary\">{$i18n.t(\"notes.owner\")}: </Text>\n <span className=\"crm-note-card__owner\">张明明</span>\n </article>\n ))}\n </Card>\n </section>\n );\n};\n\nconst FollowupColumn = ({ note, onOpenChildPage }) => {\n const $i18n = useI18n();\n const { followup } = note;\n\n return (\n <aside className=\"crm-column\">\n <Card\n className=\"crm-panel\"\n title={$i18n.t(\"followup.title\")}\n variant=\"borderless\"\n >\n <InfoList fields={followup.summaryFields} />\n <span className=\"crm-followup-panel__subtitle\">\n {$i18n.t(\"followup.info\")}\n </span>\n <InfoList fields={followup.infoFields} />\n <span className=\"crm-followup-panel__subtitle\">\n {$i18n.t(\"followup.plan\")}\n </span>\n <InfoList fields={followup.planFields} />\n <Button\n block\n className=\"crm-calendar-button\"\n onClick={() => onOpenChildPage(\"createCalendar\")}\n title={$i18n.t(\"actions.pageCodeHint\")}\n type=\"primary\"\n >\n {$i18n.t(\"actions.addCalendar\")}\n </Button>\n </Card>\n </aside>\n );\n};\n\nconst CrmPage = ({ location, navigate }) => {\n const [selectedOpportunity, setSelectedOpportunity] = useState(\n opportunities[0],\n );\n const [selectedNote, setSelectedNote] = useState(opportunities[0].notes[0]);\n\n const selectOpportunity = (opportunity) => {\n setSelectedOpportunity(opportunity);\n setSelectedNote();\n };\n\n const refreshSelectedOpportunity = () => {\n const opportunity = opportunities.find(\n ({ id }) => id === selectedOpportunity.id,\n );\n\n setSelectedOpportunity({ ...opportunity });\n setSelectedNote(\n (note) => note && opportunity.notes.find(({ date }) => date === note.date),\n );\n };\n\n const openChildPage = (action) =>\n navigate(CHILD_PAGE_CODE, {\n onePage: true,\n state: {\n action,\n from: location.pathname,\n opportunityId: selectedOpportunity.id,\n },\n });\n\n useEffect(refreshSelectedOpportunity, [location.key]);\n\n return (\n <main className=\"crm-page onepage-theme\">\n <CustomerColumn />\n <RelatedColumn\n onOpenChildPage={openChildPage}\n onSelectOpportunity={selectOpportunity}\n selectedOpportunityId={selectedOpportunity.id}\n />\n <OpportunityColumn\n onOpenChildPage={openChildPage}\n onRefresh={refreshSelectedOpportunity}\n onSelectNote={setSelectedNote}\n opportunity={selectedOpportunity}\n selectedNoteDate={selectedNote?.date}\n />\n {selectedNote && (\n <FollowupColumn note={selectedNote} onOpenChildPage={openChildPage} />\n )}\n </main>\n );\n};\n\nconst OnePage = () => {\n const client = useSdkClient();\n const $i18n = useI18n();\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useAppTheme();\n\n $i18n.addLocale(locales);\n\n return (\n <ConfigProvider\n theme={{\n algorithm:\n theme?.mode === \"dark\"\n ? antdTheme.darkAlgorithm\n : antdTheme.defaultAlgorithm,\n cssVar: { key: \"onepage-theme\" },\n ...(theme?.skinColor\n ? { token: { colorPrimary: theme.skinColor } }\n : {}),\n }}\n >\n <CrmPage location={location} navigate={navigate} />\n </ConfigProvider>\n );\n};\n\nexport default OnePage;\n",
4
+ "src/locales/index.js": "export const locales = {\n \"zh-CN\": {\n \"customer.title\": \"客户信息\",\n \"customer.name\": \"客户名称\",\n \"customer.nameValue\": \"科技有限公司\",\n \"customer.id\": \"客户 ID\",\n \"customer.level\": \"客户等级\",\n \"customer.levelValue\": \"A级客户\",\n \"customer.type\": \"客户类型\",\n \"customer.typeValue\": \"互联网\",\n \"customer.address\": \"公司所在地\",\n \"customer.active\": \"活跃客户\",\n \"customer.primaryContact\": \"主要联系人\",\n \"metrics.historyAmount\": \"历史成交总额\",\n \"metrics.orderAmount\": \"当前订单总金额\",\n \"metrics.opportunityCount\": \"当前商机数量\",\n \"metrics.quotationCount\": \"当前报价数量\",\n \"unit.tenThousandYuan\": \"万元\",\n \"unit.record\": \"条\",\n \"related.opportunities\": \"销售机会\",\n \"related.quotations\": \"报价\",\n \"related.owner\": \"负责人\",\n \"opportunity.basicInfo\": \"基本信息\",\n \"opportunity.name\": \"商机名称\",\n \"opportunity.id\": \"商机 ID\",\n \"opportunity.stage\": \"商机阶段\",\n \"opportunity.customer\": \"客户\",\n \"opportunity.contact\": \"联系人\",\n \"opportunity.phone\": \"联系电话\",\n \"opportunity.amount\": \"预计成交金额\",\n \"opportunity.currency\": \"币种\",\n \"opportunity.rate\": \"成功概率\",\n \"opportunity.closeDate\": \"预计成交日期\",\n \"opportunity.source\": \"来源类型\",\n \"opportunity.createdAt\": \"创建时间\",\n \"opportunity.clue\": \"来源线索\",\n \"opportunity.owner\": \"负责人\",\n \"opportunity.competitor\": \"主要竞争对手\",\n \"opportunity.updatedAt\": \"最后更新时间\",\n \"opportunity.description\": \"需求详细描述\",\n \"stage.contact\": \"初步接触\",\n \"stage.requirement\": \"需求确认\",\n \"stage.solution\": \"方案制定\",\n \"stage.contract\": \"合同签约\",\n \"source.clueConversion\": \"线索转化\",\n \"source.onlineInquiry\": \"线上咨询\",\n \"quotation.sent\": \"已发送\",\n \"quotation.accepted\": \"已接受\",\n \"method.phone\": \"电话\",\n \"method.offlineMeeting\": \"线下会议\",\n \"method.videoMeeting\": \"视频会议\",\n \"notes.title\": \"跟进小记\",\n \"notes.owner\": \"跟进人\",\n \"followup.title\": \"跟进小记\",\n \"followup.id\": \"跟进记录 ID\",\n \"followup.document\": \"关联单据\",\n \"followup.customer\": \"客户名称\",\n \"followup.owner\": \"跟进人\",\n \"followup.info\": \"跟进信息\",\n \"followup.method\": \"跟进方式\",\n \"followup.date\": \"跟进时间\",\n \"followup.content\": \"跟进内容\",\n \"followup.plan\": \"跟进计划\",\n \"followup.nextDate\": \"下次跟进时间\",\n \"followup.nextMethod\": \"跟进方式\",\n \"followup.remark\": \"备注\",\n \"actions.add\": \"新增\",\n \"actions.refresh\": \"更新\",\n \"actions.confirm\": \"确认需求\",\n \"actions.addNote\": \"新增小记\",\n \"actions.addCalendar\": \"添加日程\",\n \"actions.pageCodeHint\": \"示例页面地址,请替换为实际关联页面 code\",\n },\n \"en-US\": {\n \"customer.title\": \"Customer information\",\n \"customer.name\": \"Customer name\",\n \"customer.nameValue\": \"Mayou CRM Technology\",\n \"customer.id\": \"Customer ID\",\n \"customer.level\": \"Customer level\",\n \"customer.levelValue\": \"Level A\",\n \"customer.type\": \"Customer type\",\n \"customer.typeValue\": \"Internet\",\n \"customer.address\": \"Company address\",\n \"customer.active\": \"Active customer\",\n \"customer.primaryContact\": \"Primary contact\",\n \"metrics.historyAmount\": \"Historical revenue\",\n \"metrics.orderAmount\": \"Current order value\",\n \"metrics.opportunityCount\": \"Current opportunities\",\n \"metrics.quotationCount\": \"Current quotations\",\n \"unit.tenThousandYuan\": \"10k CNY\",\n \"unit.record\": \"items\",\n \"related.opportunities\": \"Opportunities\",\n \"related.quotations\": \"Quotations\",\n \"related.owner\": \"Owner\",\n \"opportunity.basicInfo\": \"Basic information\",\n \"opportunity.name\": \"Opportunity name\",\n \"opportunity.id\": \"Opportunity ID\",\n \"opportunity.stage\": \"Opportunity stage\",\n \"opportunity.customer\": \"Customer\",\n \"opportunity.contact\": \"Contact\",\n \"opportunity.phone\": \"Phone\",\n \"opportunity.amount\": \"Expected amount\",\n \"opportunity.currency\": \"Currency\",\n \"opportunity.rate\": \"Win rate\",\n \"opportunity.closeDate\": \"Expected close date\",\n \"opportunity.source\": \"Source type\",\n \"opportunity.createdAt\": \"Created at\",\n \"opportunity.clue\": \"Source lead\",\n \"opportunity.owner\": \"Owner\",\n \"opportunity.competitor\": \"Main competitor\",\n \"opportunity.updatedAt\": \"Updated at\",\n \"opportunity.description\": \"Requirement details\",\n \"stage.contact\": \"Initial contact\",\n \"stage.requirement\": \"Requirement confirmation\",\n \"stage.solution\": \"Solution design\",\n \"stage.contract\": \"Contract signing\",\n \"source.clueConversion\": \"Lead conversion\",\n \"source.onlineInquiry\": \"Online inquiry\",\n \"quotation.sent\": \"Sent\",\n \"quotation.accepted\": \"Accepted\",\n \"method.phone\": \"Phone\",\n \"method.offlineMeeting\": \"In-person meeting\",\n \"method.videoMeeting\": \"Video meeting\",\n \"notes.title\": \"Follow-up notes\",\n \"notes.owner\": \"Owner\",\n \"followup.title\": \"Follow-up note\",\n \"followup.id\": \"Follow-up ID\",\n \"followup.document\": \"Related document\",\n \"followup.customer\": \"Customer\",\n \"followup.owner\": \"Owner\",\n \"followup.info\": \"Follow-up information\",\n \"followup.method\": \"Method\",\n \"followup.date\": \"Follow-up time\",\n \"followup.content\": \"Content\",\n \"followup.plan\": \"Follow-up plan\",\n \"followup.nextDate\": \"Next follow-up\",\n \"followup.nextMethod\": \"Method\",\n \"followup.remark\": \"Remark\",\n \"actions.add\": \"Add\",\n \"actions.refresh\": \"Refresh\",\n \"actions.confirm\": \"Confirm requirement\",\n \"actions.addNote\": \"Add note\",\n \"actions.addCalendar\": \"Add calendar\",\n \"actions.pageCodeHint\": \"Replace the example path with the related page code\",\n },\n};\n"
5
+ }
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { registerModels, CONFIG_NAMES, type ModelsConfig } from "@lovrabet/sdk";
10
10
 
11
- export const {VarPrefix}_APP_CODE = "{AppCode}";
11
+ export const {VarPrefix}_APP_CODE: string = "{AppCode}";
12
12
 
13
13
  /**
14
14
  * 项目的模型配置
@@ -1,8 +0,0 @@
1
- /**
2
- * 组装创建 React 页面所需的完整初始文件集
3
- *
4
- * @param pageName 页面名称
5
- * @param languages 应用启用的语言编码列表
6
- * @returns 以相对路径为键的初始文件内容
7
- */
8
- export declare function buildInitialFiles(pageName: string, languages: string[]): Record<string, string>;
@@ -1 +0,0 @@
1
- (function(_0x597001,_0x2c512b){const _0x92ea85=a167_0x58eb,_0xe856bc=_0x597001();while(!![]){try{const _0xbc6284=parseInt(_0x92ea85(0x149))/0x1*(parseInt(_0x92ea85(0x144))/0x2)+-parseInt(_0x92ea85(0x13e))/0x3+-parseInt(_0x92ea85(0x148))/0x4*(-parseInt(_0x92ea85(0x141))/0x5)+parseInt(_0x92ea85(0x142))/0x6*(parseInt(_0x92ea85(0x145))/0x7)+-parseInt(_0x92ea85(0x146))/0x8+parseInt(_0x92ea85(0x138))/0x9+-parseInt(_0x92ea85(0x140))/0xa;if(_0xbc6284===_0x2c512b)break;else _0xe856bc['push'](_0xe856bc['shift']());}catch(_0x1a8fe9){_0xe856bc['push'](_0xe856bc['shift']());}}}(a167_0x4036,0x23876));function buildIndexFile(_0x38ffe6){const _0x2afeb4=a167_0x58eb;return'import\x20React\x20from\x20\x27react\x27;\x0aimport\x20{\x20Card,\x20Typography\x20}\x20from\x20\x27antd\x27;\x0aimport\x20{\x20useSdkClient,\x20useI18n,\x20useNavigate,\x20useLocation\x20}\x20from\x20\x22@/context/app-context\x22;\x0aimport\x20{\x20locales\x20}\x20from\x20\x27../locales\x27;\x0aimport\x20\x27./index.css\x27;\x0a\x0aconst\x20App\x20=\x20()\x20=>\x20{\x0a\x20\x20const\x20client\x20=\x20useSdkClient();\x20//\x20client\x20可以用于获取其他数据集的数据\x0a\x20\x20const\x20$i18n\x20=\x20useI18n();\x20//\x20国际化多语言\x20i18n\x20实例\x0a\x20\x20const\x20navigate\x20=\x20useNavigate();\x20//\x20react-router\x20navigate\x20导航实例\x0a\x20\x20const\x20location\x20=\x20useLocation();\x20//\x20浏览器\x20location\x20实例\x0a\x0a\x20\x20$i18n.addLocale(locales);\x20//\x20添加多语言词包\x0a\x0a\x20\x20return\x20(\x0a\x20\x20\x20\x20<div\x20className=\x22page-container\x22>\x0a\x20\x20\x20\x20\x20\x20<Card>\x0a\x20\x20\x20\x20\x20\x20\x20\x20<Typography>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<Typography.Title\x20level={2}>{'+JSON[_0x2afeb4(0x147)](_0x38ffe6)+_0x2afeb4(0x13c);}function buildStyleFile(){const _0x3bdd96=a167_0x58eb;return _0x3bdd96(0x14a);}function a167_0x58eb(_0x2f7c68,_0x12b4c5){_0x2f7c68=_0x2f7c68-0x134;const _0x4036fd=a167_0x4036();let _0x58ebfe=_0x4036fd[_0x2f7c68];return _0x58ebfe;}function buildLocalesFile(_0x8b5c9){const _0x23c17e=a167_0x58eb,_0x45eb97=_0x8b5c9['map'](_0x417013=>{const _0x15399f=a167_0x58eb;let _0x2cadb6='';switch(_0x417013){case'zh-CN':_0x2cadb6=_0x15399f(0x13b);break;case'en-US':_0x2cadb6=_0x15399f(0x13d);break;case _0x15399f(0x135):_0x2cadb6='これは基本ページテンプレートです。あなたは、これに基づいて開発できます。';break;case _0x15399f(0x136):_0x2cadb6='Bu\x20bir\x20temel\x20sayfa\x20şablonudur.\x20Bu\x20temaplana\x20dayanarak\x20geliştirilebilir.';break;case _0x15399f(0x13f):_0x2cadb6='Ini\x20adalah\x20sebuah\x20halaman\x20dasar.\x20Anda\x20dapat\x20mengembangkan\x20ini.';break;default:_0x2cadb6=_0x15399f(0x13d);}return'\x20\x20'+JSON[_0x15399f(0x147)](_0x417013)+_0x15399f(0x143)+JSON[_0x15399f(0x147)](_0x15399f(0x139))+':\x20'+JSON[_0x15399f(0x147)](_0x2cadb6)+_0x15399f(0x13a);})['join'](',\x0a');return _0x23c17e(0x137)+_0x45eb97+_0x23c17e(0x134);}export function buildInitialFiles(_0x5888a2,_0x17c88d){return{'src/app/index.jsx':buildIndexFile(_0x5888a2),'src/app/index.css':buildStyleFile(),'src/locales/index.js':buildLocalesFile(_0x17c88d)};}function a167_0x4036(){const _0x9b6014=['stringify','1018964oKyFMd','361yvOWVN','.page-container\x20{\x0a\x20\x20padding:\x2024px;\x0a\x20\x20background:\x20#f0f2f5;\x0a\x20\x20min-height:\x20100vh;\x0a}\x0a','\x0a};\x0a','ja-JP','tr-TR','export\x20const\x20locales\x20=\x20{\x0a','1517679YkQjHa','pageDescription',',\x0a\x20\x20}','这是一个基础页面模板,你可以在此基础上进行开发。','}</Typography.Title>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20<Typography.Paragraph>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{$i18n.t(\x27pageDescription\x27,\x20\x27这是一个基础页面模板,你可以在此基础上进行开发。\x27)}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20</Typography.Paragraph>\x0a\x20\x20\x20\x20\x20\x20\x20\x20</Typography>\x0a\x20\x20\x20\x20\x20\x20</Card>\x0a\x20\x20\x20\x20</div>\x0a\x20\x20);\x0a};\x0a\x0aexport\x20default\x20App;\x0a','This\x20is\x20a\x20basic\x20page\x20template,\x20you\x20can\x20develop\x20based\x20on\x20it.','55659sXIuaa','id-ID','4434630YWXENJ','5qZdAOX','3504hiCAMZ',':\x20{\x0a\x20\x20\x20\x20','758RvBBAn','2646sHANOz','1387208wXcDMi'];a167_0x4036=function(){return _0x9b6014;};return a167_0x4036();}