@_tc/template-core 0.2.0-bate.9 → 0.2.1-bate.1

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 (90) hide show
  1. package/AGENT_README.md +5 -2
  2. package/README.md +76 -31
  3. package/cjs/app/controller/project.js +1 -1
  4. package/cjs/app/controller/view.js +1 -1
  5. package/cjs/app/extends/db.js +4 -4
  6. package/cjs/app/middleware.js +1 -1
  7. package/cjs/app/middlewares/error-handle.js +1 -1
  8. package/cjs/app/middlewares/project-handler.js +1 -1
  9. package/cjs/app/utils/i18n.js +1 -0
  10. package/cjs/app/view/entry.tpl +1 -0
  11. package/cjs/config/config.default.js +1 -1
  12. package/cjs/packages/common/i18n/default.js +1 -1
  13. package/cjs/packages/common/i18n/en-US.js +1 -1
  14. package/cjs/packages/common/index.js +1 -1
  15. package/esm/app/controller/project.js +12 -11
  16. package/esm/app/controller/view.js +2 -1
  17. package/esm/app/extends/db.js +64 -60
  18. package/esm/app/middleware.js +1 -1
  19. package/esm/app/middlewares/error-handle.js +16 -15
  20. package/esm/app/middlewares/project-handler.js +8 -7
  21. package/esm/app/utils/i18n.js +36 -0
  22. package/esm/app/view/entry.tpl +1 -0
  23. package/esm/config/config.default.js +2 -1
  24. package/esm/packages/common/i18n/default.js +13 -0
  25. package/esm/packages/common/i18n/en-US.js +13 -0
  26. package/esm/packages/common/index.js +9 -9
  27. package/fe/frontend/apps/dash/Dashboard.js +6 -4
  28. package/fe/frontend/apps/dash/dash.entry.js +2 -0
  29. package/fe/frontend/src/common/generateMenuData.d.ts +4 -1
  30. package/fe/frontend/src/common/generateMenuData.js +3 -3
  31. package/fe/frontend/src/common/language.d.ts +1 -2
  32. package/fe/frontend/src/common/language.js +10 -10
  33. package/fe/frontend/src/common/request.js +3 -1
  34. package/fe/frontend/src/components/AsyncSelect/AsyncSelect.js +10 -2
  35. package/fe/frontend/src/components/BasePage/HeaderView.js +2 -2
  36. package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.js +13 -9
  37. package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.js +4 -3
  38. package/fe/frontend/src/defaultPages/NotFoundPage/index.js +4 -3
  39. package/fe/frontend/src/defaultPages/SchemaPage/components/CallCom/DetailPanel.js +14 -9
  40. package/fe/frontend/src/defaultPages/SchemaPage/components/CallCom/PopFrom.js +8 -7
  41. package/fe/frontend/src/defaultPages/SchemaPage/components/SchemaSearch/index.js +6 -12
  42. package/fe/frontend/src/defaultPages/SchemaPage/components/SchemaTable/index.js +25 -18
  43. package/fe/frontend/src/defaultPages/SchemaPage/index.js +5 -3
  44. package/fe/frontend/src/defaultPages/SidebarSlotPageTmp.js +4 -2
  45. package/fe/frontend/src/hooks/useText.d.ts +3 -0
  46. package/fe/frontend/src/hooks/useText.js +14 -0
  47. package/fe/frontend/src/index.d.ts +1 -0
  48. package/fe/frontend/src/index.js +3 -2
  49. package/fe/frontend/src/language/index.d.ts +0 -2
  50. package/fe/frontend/src/language/index.js +1 -7
  51. package/fe/frontend/src/language/resources.d.ts +2 -0
  52. package/fe/frontend/src/language/resources.js +9 -0
  53. package/fe/packages/common/i18n/default.d.ts +15 -0
  54. package/fe/packages/common/i18n/default.js +16 -0
  55. package/fe/packages/common/i18n/en-US.d.ts +15 -0
  56. package/fe/packages/common/i18n/en-US.js +18 -2
  57. package/fe/packages/common/i18n/index.js +13 -0
  58. package/fe/packages/common/i18n/locales.js +7 -0
  59. package/fe/packages/react/ui/components/Date/Calendar.js +9 -5
  60. package/fe/packages/react/ui/components/Date/LocaleContext.d.ts +22 -1
  61. package/fe/packages/react/ui/components/Date/LocaleContext.js +28 -3
  62. package/fe/packages/react/ui/components/Date/LocaleProvider.d.ts +1 -1
  63. package/fe/packages/react/ui/components/Date/LocaleProvider.js +7 -16
  64. package/fe/packages/react/ui/components/Date/index.js +2 -2
  65. package/fe/packages/react/ui/components/Date/locales.d.ts +2 -0
  66. package/fe/packages/react/ui/components/Date/locales.js +29 -13
  67. package/fe/packages/react/ui/components/TableSearch/TableSearch.js +21 -1
  68. package/fe/packages/react/ui/components/TableSearch/lang.d.ts +2 -0
  69. package/fe/packages/react/ui/components/TableSearch/lang.js +18 -7
  70. package/fe/packages/react/ui/components/Textarea/Textarea.d.ts +1 -1
  71. package/fe/packages/react/ui/components/Textarea/Textarea.js +1 -1
  72. package/model/frontend/src/common/language.d.ts +1 -2
  73. package/model/frontend/src/hooks/useText.d.ts +3 -0
  74. package/model/frontend/src/language/index.d.ts +0 -2
  75. package/model/frontend/src/language/resources.d.ts +2 -0
  76. package/model/packages/common/i18n/default.d.ts +15 -0
  77. package/model/packages/common/i18n/en-US.d.ts +15 -0
  78. package/model/packages/react/ui/components/Date/LocaleContext.d.ts +22 -1
  79. package/model/packages/react/ui/components/Date/LocaleProvider.d.ts +1 -1
  80. package/model/packages/react/ui/components/Date/locales.d.ts +2 -0
  81. package/model/packages/react/ui/components/TableSearch/lang.d.ts +2 -0
  82. package/model/packages/react/ui/components/Textarea/Textarea.d.ts +1 -1
  83. package/package.json +1 -1
  84. package/types/app/utils/i18n.d.ts +12 -0
  85. package/types/config/config.default.d.ts +6 -0
  86. package/types/packages/common/i18n/default.d.ts +19 -0
  87. package/types/packages/common/i18n/en-US.d.ts +21 -2
  88. package/types/packages/common/i18n/index.d.ts +21 -0
  89. package/types/packages/common/i18n/locales.d.ts +10 -0
  90. package/types/packages/common/i18n/types.d.ts +40 -0
package/AGENT_README.md CHANGED
@@ -82,11 +82,14 @@ export default {
82
82
  ATKey: 'Authorization',
83
83
  RTKey: 'RT',
84
84
  },
85
+ resourceCacheTimeMs: 5 * 60 * 1000,
85
86
  }
86
87
  ```
87
88
 
88
89
  `auth.ATKey` / `auth.RTKey` 是业务鉴权中间件读取短 token 和刷新 token header 的默认约定;默认分别是 `Authorization` / `RT`。
89
90
 
91
+ `resourceCacheTimeMs` 控制生产环境静态资源缓存时间,单位是 ms,默认 5 分钟。
92
+
90
93
  访问内置后台:
91
94
 
92
95
  ```text
@@ -369,7 +372,6 @@ import '@_tc/template-core/fe/tailwind_ui.css'
369
372
 
370
373
  ```ts
371
374
  import {
372
- addLanguage,
373
375
  addLanguageResources,
374
376
  clearRafTimer,
375
377
  api,
@@ -387,6 +389,7 @@ import {
387
389
  setAuthToken,
388
390
  setLanguage,
389
391
  useSchemaStore,
392
+ useText,
390
393
  LanguageSwitch,
391
394
  ThemeSwitch,
392
395
  } from '@_tc/template-core/fe'
@@ -423,7 +426,7 @@ import { Button, DataTable, Form, Input, Modal, Select } from '@_tc/template-cor
423
426
  - 组件加载辅助:`renderImportComponent(import('./Page'))` 会用 `React.lazy` + `Suspense` 渲染动态组件。
424
427
  - Schema 通信:`schemaEventBus`、`eventsInfo`、`merge`。
425
428
  - 共享状态:`modeStore`、`schemaStore`、`apiFreezerStore` 及对应 hooks。
426
- - 多语言:`addLanguageResources()` / `addResources()` 追加某语种文案,`addLanguage()` 新增语种,`setLanguage()` 切换语言,配置文案可写 `$i18n::...`。
429
+ - 多语言:`addLanguageResources()` 追加或新增某语种文案,`setLanguage()` 切换语言。React render 中用 `useText()`,非 React 工具代码用 `getText()`;`$i18n::` 前缀会被去掉后查询资源,无前缀字符串会按原 key 查询,未命中时原样返回。配置文案推荐写 `$i18n::...`,也可写普通字符串。
427
430
  - 主题:`ThemeSwitch` 会同步根节点 `dark` class 和 `localStorage`。
428
431
  - `Input type="password"` 会自动显示密码显隐按钮;`allowClear` 不作用于密码输入。
429
432
  - RAF 风格计时器由 `@tc/common/rafTimer` 提供,浏览器优先使用 RAF,Node/SSR 自动降级到 `setTimeout`。
package/README.md CHANGED
@@ -101,7 +101,7 @@ main().catch((error) => {
101
101
  | `homePage` | `string` | 否 | - | 应用首页路径 |
102
102
  | `additionalPublicPaths` | `string | string[]` | 否 | - | 追加静态资源目录;如需同时作为 Nunjucks 模板查找目录,建议传绝对路径数组 |
103
103
 
104
- 框架会自动读取 `config/config.default.ts` 中的配置(如 `port`、`env`、`auth` 等)。
104
+ 框架会自动读取 `config/config.default.ts` 中的配置(如 `port`、`env`、`auth`、`resourceCacheTimeMs` 等)。
105
105
 
106
106
  常用默认配置示例:
107
107
 
@@ -113,10 +113,15 @@ export default {
113
113
  ATKey: 'Authorization',
114
114
  RTKey: 'RT',
115
115
  },
116
+ resourceCacheTimeMs: 5 * 60 * 1000,
116
117
  }
117
118
  ```
118
119
 
119
- `auth.ATKey` / `auth.RTKey` 用于约定业务鉴权中间件读取短 token 和刷新 token 的 header 名;默认分别为 `Authorization` 和 `RT`。
120
+ `auth.ATKey` / `auth.RTKey` 用于约定项目鉴权中间件读取短 token 和刷新 token 的 header 名;默认分别为 `Authorization` 和 `RT`。
121
+
122
+ `resourceCacheTimeMs` 控制生产环境静态资源缓存时间,单位是 ms,默认 5 分钟。
123
+
124
+ 服务端返回给前端的错误消息会按请求头里的语种翻译。当前优先读取 `x-tc-language`,也兼容 `tc_language`、`tc-language`、`language`、`lang`、`locale` 和 `accept-language`;如果没取到,默认按英文 `en-US` 处理。
120
125
 
121
126
  静态资源挂载顺序为:项目 `app/public`、框架 `app/public`、`additionalPublicPaths`。Nunjucks 模板查找顺序为:项目 `app/public/dist`、框架 `app/public/dist`、`additionalPublicPaths/dist`。这些目录由 `app.publicsPath` 统一维护。当 `buildFE` 输出到自定义目录时,可以用 `additionalPublicPaths` 把该目录交给 Koa 提供静态资源访问和模板渲染;如果需要模板查找也命中追加目录,请传绝对路径数组。
122
127
 
@@ -127,7 +132,7 @@ buildFE(mode: 'dev' | 'prod', options?: BuildFEOptions)
127
132
  ```
128
133
 
129
134
  **mode**:构建模式
130
- - `'dev'`:开发模式,构建一次并监听业务 `frontend/` 变化后重建
135
+ - `'dev'`:开发模式,构建一次并监听项目 `frontend/` 变化后重建
131
136
  - `'prod'`:生产模式,构建优化后的静态资源
132
137
 
133
138
  **options.output**:构建输出位置
@@ -163,6 +168,11 @@ await buildFE('prod', { minifyHtml: true })
163
168
  // config/config.default.ts
164
169
  export default {
165
170
  port: 9000,
171
+ auth: {
172
+ ATKey: 'Authorization',
173
+ RTKey: 'RT',
174
+ },
175
+ resourceCacheTimeMs: 5 * 60 * 1000,
166
176
  }
167
177
  ```
168
178
 
@@ -192,7 +202,7 @@ http://localhost:9000/dash?projk=demo
192
202
 
193
203
  ### 后端类型
194
204
 
195
- `@_tc/template-core` 会导出常用 Node 侧类型,业务项目写 `app/*` 文件时建议优先复用这些类型。
205
+ `@_tc/template-core` 会导出常用 Node 侧类型,项目编写 `app/*` 文件时建议优先复用这些类型。
196
206
 
197
207
  | 类型 | 使用场景 |
198
208
  | --- | --- |
@@ -313,9 +323,7 @@ VS Code 建议安装官方 Tailwind CSS IntelliSense 插件,并在工作区 `.
313
323
  import {
314
324
  api,
315
325
  apiFreezerStore,
316
- addLanguage,
317
326
  addLanguageResources,
318
- addResources,
319
327
  AsyncSelect,
320
328
  clearAuthToken,
321
329
  eventsInfo,
@@ -331,6 +339,7 @@ import {
331
339
  modeStore,
332
340
  post,
333
341
  renderImportComponent,
342
+ registerFrontendI18nResources,
334
343
  request,
335
344
  schemaEventBus,
336
345
  schemaStore,
@@ -343,6 +352,7 @@ import {
343
352
  useApiFreezer,
344
353
  useModeStore,
345
354
  useSchemaStore,
355
+ useText,
346
356
  } from '@_tc/template-core/fe'
347
357
 
348
358
  import type {
@@ -380,7 +390,7 @@ import type {
380
390
  | 请求类型 | `BaseResponse`、`PageParams`、`PageResponse`、`RequestConfig`、`ResponseConfig`、`AxiosError` |
381
391
  | RAF 风格计时器 | `rafSetTimeout`、`rafSetInterval`、`rafClearTimeout`、`rafClearInterval`、`clearRafTimer`、`RafTimerId`、`RafTimerCallback` |
382
392
  | Token 工具 | `getAuthToken`、`setAuthToken`、`clearAuthToken`、`localKeyMap` |
383
- | 多语言工具 | `getText`、`t`、`addLanguage`、`addLanguageResources`、`addResources`、`setLanguage`、`setFallbackLanguage`、`setResources`、`getCurrentLanguage`、`getFallbackLanguage`、`getSupportedLanguages` |
393
+ | 多语言工具 | `useText`、`getText`、`t`、`registerFrontendI18nResources`、`addLanguageResources`、`setLanguage`、`setFallbackLanguage`、`setResources`、`getCurrentLanguage`、`getFallbackLanguage`、`getSupportedLanguages` |
384
394
  | 请求冻结 | `apiFreezerStore`、`useApiFreezer`、`FreezeState`、`ApiFreezerRequestMatcher` |
385
395
  | 事件工具 | `eventsInfo`、`merge` |
386
396
  | 共享状态 | `modeStore`、`useModeStore`、`schemaStore`、`useSchemaStore`、`schemaEventBus` |
@@ -393,7 +403,21 @@ import type {
393
403
 
394
404
  ### 前端多语言
395
405
 
396
- TemplateCore 前端通过 `@_tc/template-core/fe` 暴露 i18n 能力。框架内置中文、英文资源,项目可以继续追加自己的资源或新增语种。
406
+ TemplateCore 前端通过 `@_tc/template-core/fe` 暴露 i18n 能力。框架内置中文、英文资源,但不会在普通自定义页面 import 前端入口时自动注册,避免非 Dashboard 页面把框架文案整包带入运行时资源。
407
+
408
+ 默认 Dashboard 入口会自动调用 `registerFrontendI18nResources()`,所以 Dashboard、Schema CRUD、内置页头、语言切换等框架页面可以直接使用框架内置文案。自定义入口如果也要复用这些框架文案,需要在渲染前手动注册:
409
+
410
+ ```ts
411
+ import { initApp, registerFrontendI18nResources } from '@_tc/template-core/fe'
412
+
413
+ initApp(App, {
414
+ beforeRender: async () => {
415
+ await registerFrontendI18nResources()
416
+ },
417
+ })
418
+ ```
419
+
420
+ 项目可以继续追加自己的资源或新增语种。
397
421
 
398
422
  **追加自定义语言资源**:
399
423
 
@@ -401,7 +425,7 @@ TemplateCore 前端通过 `@_tc/template-core/fe` 暴露 i18n 能力。框架内
401
425
  import { addLanguageResources } from '@_tc/template-core/fe'
402
426
 
403
427
  addLanguageResources('zh-CN', {
404
- business: {
428
+ app: {
405
429
  product: {
406
430
  menu: '商品管理',
407
431
  name: '商品名称',
@@ -411,8 +435,8 @@ addLanguageResources('zh-CN', {
411
435
  },
412
436
  })
413
437
 
414
- i18nStore.getState().addResources('en-US', {
415
- business: {
438
+ addLanguageResources('en-US', {
439
+ app: {
416
440
  product: {
417
441
  menu: 'Products',
418
442
  name: 'Product name',
@@ -426,10 +450,10 @@ i18nStore.getState().addResources('en-US', {
426
450
  **新增语种并切换**:
427
451
 
428
452
  ```ts
429
- import { addLanguage, setLanguage } from '@_tc/template-core/fe'
453
+ import { addLanguageResources, setLanguage } from '@_tc/template-core/fe'
430
454
 
431
- addLanguage('ja-JP', {
432
- business: {
455
+ addLanguageResources('ja-JP', {
456
+ app: {
433
457
  product: {
434
458
  menu: '商品管理',
435
459
  },
@@ -439,19 +463,40 @@ addLanguage('ja-JP', {
439
463
  setLanguage('ja-JP')
440
464
  ```
441
465
 
442
- `addLanguageResources()` / `addResources()` 默认会深合并同语种文案;传 `{ merge: false }` 可以替换该语种资源。`setResources(resources, { merge: true })` 可批量合并多语种资源。`LanguageSwitch` 默认会把已注册语种加入选项;需要自定义展示文案时仍可传入 `options`。
466
+ `addLanguageResources()` 用于追加或新增某个语种的文案,默认会深合并同语种资源;传 `{ merge: false }` 可以替换该语种资源。`setResources(resources, { merge: true })` 可批量合并多语种资源。`LanguageSwitch` 默认会把已注册语种加入选项;需要自定义展示文案时仍可传入 `options`。
467
+
468
+ React 组件 render 中使用 `useText()` 读取文案。它会订阅语言和资源变化,调用 `setLanguage()` 后组件会自动重渲染。
469
+
470
+ `useText()` / `getText()` 遇到 `$i18n::` 前缀会先去掉前缀再查语言资源;没有前缀时会直接把传入字符串作为 key 查询,查不到才原样返回:
471
+
472
+ ```tsx
473
+ import { useText } from '@_tc/template-core/fe'
474
+
475
+ function ProductTitle() {
476
+ const text = useText()
477
+
478
+ return (
479
+ <>
480
+ <h1>{text('$i18n::app.product.menu')}</h1>
481
+ <span>{text('Plain title')}</span>
482
+ </>
483
+ )
484
+ }
485
+ ```
486
+
487
+ `getText()` 是普通函数,适合请求错误、日志、校验工具或事件回调等非 React 场景;它不会主动触发 React 组件重渲染。
443
488
 
444
489
  资源注册时不写 `$i18n::` 前缀,配置中使用时才写:
445
490
 
446
491
  ```ts
447
- const title = '$i18n::business.product.createTitle'
492
+ const title = '$i18n::app.product.createTitle'
448
493
  ```
449
494
 
450
495
  **在 model / Schema 配置中使用**:
451
496
 
452
497
  ```ts
453
498
  {
454
- name: '$i18n::business.product.menu',
499
+ name: '$i18n::app.product.menu',
455
500
  moduleType: 'schema',
456
501
  schemaConfig: {
457
502
  schema: {
@@ -459,7 +504,7 @@ const title = '$i18n::business.product.createTitle'
459
504
  properties: {
460
505
  name: {
461
506
  type: 'string',
462
- label: '$i18n::business.product.name',
507
+ label: '$i18n::app.product.name',
463
508
  minLength: 2,
464
509
  createFormOption: {
465
510
  comType: 'input',
@@ -473,7 +518,7 @@ const title = '$i18n::business.product.createTitle'
473
518
  tableConfig: {
474
519
  headerButtons: [
475
520
  {
476
- label: '$i18n::business.product.create',
521
+ label: '$i18n::app.product.create',
477
522
  eventKey: 'callComponent',
478
523
  eventOption: { comName: 'createForm' },
479
524
  },
@@ -481,7 +526,7 @@ const title = '$i18n::business.product.createTitle'
481
526
  },
482
527
  componentConfig: {
483
528
  createForm: {
484
- title: '$i18n::business.product.createTitle',
529
+ title: '$i18n::app.product.createTitle',
485
530
  saveBtnText: '$i18n::common.submit',
486
531
  },
487
532
  },
@@ -939,7 +984,7 @@ const unsafeUsers = app.extends.db.queryDB(
939
984
  )
940
985
  ```
941
986
 
942
- 业务项目可以用自己的 `app/extends/db.ts` 覆盖默认实现。建议保留同一组方法名,这样 controller/service 调用方不用调整。
987
+ 项目可以用自己的 `app/extends/db.ts` 覆盖默认实现。建议保留同一组方法名,这样 controller/service 调用方不用调整。
943
988
 
944
989
  ```ts
945
990
  // app/extends/db.ts
@@ -1055,7 +1100,7 @@ export default (app: KoaApp, router: Router) => {
1055
1100
  - 每个 `app/router/*.ts` 文件都会获得独立的 `koa-router` 实例。
1056
1101
  - 可以通过 `router.level = number` 控制挂载顺序,数值越小越早挂载。
1057
1102
  - 默认 `level` 是 `0`,框架兜底 router 是 `99`。
1058
- - 通配、兜底、重定向类路由建议设置较大的 `level`,避免抢先匹配业务 API。
1103
+ - 通配、兜底、重定向类路由建议设置较大的 `level`,避免抢先匹配项目 API。
1059
1104
 
1060
1105
  ```ts
1061
1106
  import type { KoaApp, Router } from '@_tc/template-core'
@@ -1344,7 +1389,7 @@ DELETE {api} -> 删除
1344
1389
 
1345
1390
  ### 服务端兜底路由守卫
1346
1391
 
1347
- 业务项目可以创建 `app/router-guard.ts` 或 `app/router-guard.js`。它只在所有正常 `app/router/*` 路由都没有命中时执行。
1392
+ 项目可以创建 `app/router-guard.ts` 或 `app/router-guard.js`。它只在所有正常 `app/router/*` 路由都没有命中时执行。
1348
1393
 
1349
1394
  ```ts
1350
1395
  import type { Ctx, KoaApp } from '@_tc/template-core'
@@ -1369,7 +1414,7 @@ export default (_app: KoaApp) => {
1369
1414
 
1370
1415
  ### 扩展 Dashboard 顶部用户区域
1371
1416
 
1372
- 业务项目可以创建 `frontend/extended/dash/components.tsx`,通过组件映射填充内置 Dashboard 页头右侧区域。
1417
+ 项目可以创建 `frontend/extended/dash/components.tsx`,通过组件映射填充内置 Dashboard 页头右侧区域。
1373
1418
 
1374
1419
  ```tsx
1375
1420
  // frontend/extended/dash/components.tsx
@@ -1390,7 +1435,7 @@ export default components
1390
1435
 
1391
1436
  ### 扩展 Dashboard 登录守卫
1392
1437
 
1393
- 业务项目可以创建 `frontend/extended/dash/routeGuard.ts`,用于在 Dashboard 内置 `homePage` / 首菜单重定向前做登录判断。
1438
+ 项目可以创建 `frontend/extended/dash/routeGuard.ts`,用于在 Dashboard 内置 `homePage` / 首菜单重定向前做登录判断。
1394
1439
 
1395
1440
  ```ts
1396
1441
  // frontend/extended/dash/routeGuard.ts
@@ -1406,9 +1451,9 @@ export default dashRouteGuard
1406
1451
  返回值规则:
1407
1452
 
1408
1453
  - 返回 `string`:框架使用 `window.location.href` 跳转,登录页不需要在 Dash 路由下。
1409
- - 返回非 `string`:中断 Dashboard 内置重定向,业务方可自行跳转或处理提示。
1454
+ - 返回非 `string`:中断 Dashboard 内置重定向,使用方可自行跳转或处理提示。
1410
1455
  - `DashRouteGuardResult` 是 `string | undefined`;`undefined`(包括已登录分支的隐式返回)也会打断 Dashboard 内置重定向。
1411
- - 没有业务 `routeGuard` 文件时,框架使用兜底空对象,不影响默认 Dashboard 重定向。
1456
+ - 没有自定义 `routeGuard` 文件时,框架使用兜底空对象,不影响默认 Dashboard 重定向。
1412
1457
 
1413
1458
  ### 扩展 SchemaForm 字段组件
1414
1459
 
@@ -1580,7 +1625,7 @@ remark: {
1580
1625
 
1581
1626
  ### 扩展 SchemaTable 单元格渲染组件
1582
1627
 
1583
- SchemaPage 表格列支持通过 `tableOption.renderComponent` 使用内置或业务注册的渲染组件。内置组件:
1628
+ SchemaPage 表格列支持通过 `tableOption.renderComponent` 使用内置或自定义注册的渲染组件。内置组件:
1584
1629
 
1585
1630
  - `PreviewImage`:把当前单元格值渲染成图片预览;值是数组时直接作为图片列表,值是单个字符串时自动转成单图数组。
1586
1631
 
@@ -1635,7 +1680,7 @@ const componentsMap = {
1635
1680
  export default componentsMap
1636
1681
  ```
1637
1682
 
1638
- 组件会收到 `value`、`record`、`rowIndex`、`fieldKey`,以及 `renderComponentProps` 中的业务参数:
1683
+ 组件会收到 `value`、`record`、`rowIndex`、`fieldKey`,以及 `renderComponentProps` 中的自定义参数:
1639
1684
 
1640
1685
  ```tsx
1641
1686
  // frontend/components/PriceCell.tsx
@@ -1775,7 +1820,7 @@ declare module '@_tc/template-core/model' {
1775
1820
 
1776
1821
  - `RenderComponentPropsMap` 的 key 对应 `tableOption.renderComponent`。
1777
1822
  - 运行时组件注册在 `frontend/extended/SchemaPage/SchemaTable/data.ts`。
1778
- - 组件运行时会自动收到 `value`、`record`、`rowIndex`、`fieldKey`;`renderComponentProps` 只配置业务参数。
1823
+ - 组件运行时会自动收到 `value`、`record`、`rowIndex`、`fieldKey`;`renderComponentProps` 只配置自定义参数。
1779
1824
 
1780
1825
  ### KoaApp 类型扩展
1781
1826
 
@@ -2130,6 +2175,6 @@ await buildBE({
2130
2175
  - `model` 数组合并依赖稳定 `key`。
2131
2176
  - Dashboard 默认需要 URL 带 `?projk=项目key`。
2132
2177
  - Dashboard 登录守卫放在 `frontend/extended/dash/routeGuard.ts`;登录页通常不在 Dash 路由下,返回登录页 URL 即可。
2133
- - 服务端兜底守卫放在业务 `app/router-guard.ts|js`,不是 `app/business` 子目录。
2178
+ - 服务端兜底守卫放在项目根目录的 `app/router-guard.ts|js`。
2134
2179
  - 前端入口必须命名为 `.entry.tsx`、`.entry.ts`、`.entry.jsx` 或 `.entry.js`。
2135
2180
  - 入口同目录的 `.html` 只能作为插槽文件使用,不能写完整 HTML 文档;页面外壳始终由 `app/view/entry.tpl` 生成。
@@ -1 +1 @@
1
- const e=require(`./base.js`);var t=(t=>{let n=e(t);return class extends n{getModelList=async e=>{let t=(await this.service.project.getModelList()).reduce((e,t)=>{let{model:n,project:r}=t,{key:i,name:a,desc:o}=n,s={key:i,name:a,desc:o},c=Object.keys(r).reduce((e,t)=>{let{key:n,name:i,desc:a,homePage:o}=r[t];return e[t]={key:n,name:i,desc:a,homePage:o},e},{});return e.push({model:s,project:c}),e},[]);this.success(e,t)};getProjectList=async e=>{let t=e.reqData?.data.projk,n=(await this.service.project.getList(t))?.map(e=>{let{modelKey:t,key:n,name:r,desc:i,homePage:a}=e;return{modelKey:t,key:n,name:r,desc:i,homePage:a}});this.success(e,n)};getProject=async e=>{let{key:t}=e.params;if(!t){this.fail(e,10001,`获取项目异常`);return}let n=this.service.project.getProject(t);if(!n){this.fail(e,10001,`获取项目异常`);return}this.success(e,n)}}});module.exports=t;
1
+ const e=require(`./base.js`),t=require(`../utils/i18n.js`);var n=(n=>{let r=e(n);return class extends r{getModelList=async e=>{let t=(await this.service.project.getModelList()).reduce((e,t)=>{let{model:n,project:r}=t,{key:i,name:a,desc:o}=n,s={key:i,name:a,desc:o},c=Object.keys(r).reduce((e,t)=>{let{key:n,name:i,desc:a,homePage:o}=r[t];return e[t]={key:n,name:i,desc:a,homePage:o},e},{});return e.push({model:s,project:c}),e},[]);this.success(e,t)};getProjectList=async e=>{let t=e.reqData?.data.projk,n=(await this.service.project.getList(t))?.map(e=>{let{modelKey:t,key:n,name:r,desc:i,homePage:a}=e;return{modelKey:t,key:n,name:r,desc:i,homePage:a}});this.success(e,n)};getProject=async e=>{let{key:n}=e.params;if(!n){this.fail(e,10001,t.requestT(e,`server.errors.getProjectFailed`));return}let r=this.service.project.getProject(n);if(!r){this.fail(e,10001,t.requestT(e,`server.errors.getProjectFailed`));return}this.success(e,r)}}});module.exports=n;
@@ -1 +1 @@
1
- const e=require(`../../packages/common/string/index.js`);var t=e=>JSON.stringify(e).replace(/</g,`\\u003c`).replace(/>/g,`\\u003e`).replace(/&/g,`\\u0026`).replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`),n=(n=>class{async renderPage(r,i){let a=r.path;if(n.extends.localCacheHtmlEtag.keepFreshFEBuildKey(),a.includes(`${n.options.apiPrefix}/`))await i();else{n.extends.logger.log(` - render page: `+r.params.page);try{let i=r.query.projk;Array.isArray(i)&&(i=e.joinStr(...i));let a={projKey:i??``,name:n.options?.name??``,env:n.envs.get(),basePath:t(`${n.options.pageBasePage}${r.params.page}`),projKeyJson:t(String(r.query.projk??``)),signKey:t(n.config.signKey),options:t(n.options)},o=n.extends.renderView(`${r.params.page}.entry.tpl`,r,a),s=e.joinStr(a.basePath,a.name,a.env,a.projKey);if(!n.extends.localCacheHtmlEtag.hasEtag(s)){let e=n.extends.crypto.hmacSign(o);n.extends.localCacheHtmlEtag.setEtag(s,e)}let c=n.extends.localCacheHtmlEtag.getEtag(s);if(c||console.log(`---- etag 缓存异常 请检查服务 ----`),r.etag=c??Date.now()+``,r.fresh){r.status=304;return}}catch(e){console.log(`-------------- render view error --------------`),console.log(e),n.extends.generateErrorMessage(`template not found`,{status:404,showError:!0,code:0})}}}});module.exports=n;
1
+ const e=require(`../../packages/common/string/index.js`);var t=e=>JSON.stringify(e).replace(/</g,`\\u003c`).replace(/>/g,`\\u003e`).replace(/&/g,`\\u0026`).replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`),n=(n=>class{async renderPage(r,i){let a=r.path;if(n.extends.localCacheHtmlEtag.keepFreshFEBuildKey(),a.includes(`${n.options.apiPrefix}/`))await i();else{n.extends.logger.log(` - render page: `+r.params.page);try{let i=r.query.projk;Array.isArray(i)&&(i=e.joinStr(...i));let a={projKey:i??``,name:n.options?.name??``,env:n.envs.get(),FEBasePage:t(`${n.options.pageBasePage}`),basePath:t(`${n.options.pageBasePage}${r.params.page}`),projKeyJson:t(String(r.query.projk??``)),signKey:t(n.config.signKey),options:t(n.options)},o=n.extends.renderView(`${r.params.page}.entry.tpl`,r,a),s=e.joinStr(a.basePath,a.name,a.env,a.projKey);if(!n.extends.localCacheHtmlEtag.hasEtag(s)){let e=n.extends.crypto.hmacSign(o);n.extends.localCacheHtmlEtag.setEtag(s,e)}let c=n.extends.localCacheHtmlEtag.getEtag(s);if(c||console.log(`---- etag 缓存异常 请检查服务 ----`),r.etag=c??Date.now()+``,r.set(`Cache-Control`,`public, max-age=${n.config.resourceCacheTimeMs??300*1e3}, immutable`),r.fresh){r.status=304;return}}catch(e){console.log(`-------------- render view error --------------`),console.log(e),n.extends.generateErrorMessage(`template not found`,{status:404,showError:!0,code:0})}}}});module.exports=n;
@@ -1,12 +1,12 @@
1
- require(`../../_virtual/_rolldown/runtime.js`);let e=require(`node:fs`),t=require(`node:path`);var n=`framework`,r=`.template-core/template-core.sqlite`;function i(n){let r=s(n),i,c=!1,l=0,g=()=>(v(c),i||((0,e.mkdirSync)((0,t.dirname)(r),{recursive:!0}),i=a(r),o(i)),i),y={dbPath:r,getDBConnection:g,execDB:e=>{g().exec(e)},runDB:(e,t)=>{let n=g().prepare(e).run(...u(t));return{changes:Number(n.changes??0),lastInsertRowid:n.lastInsertRowid??0}},queryDB:(e,t)=>g().prepare(e).all(...u(t)),getDBFirst:(e,t)=>g().prepare(e).get(...u(t)),transactionDB:e=>{let t=g(),n=`tc_savepoint_${l}`,r=l===0;t.exec(r?`BEGIN IMMEDIATE`:`SAVEPOINT ${n}`),l+=1;try{let i=e(y);return--l,t.exec(r?`COMMIT`:`RELEASE SAVEPOINT ${n}`),i}catch(e){throw--l,t.exec(r?`ROLLBACK`:`ROLLBACK TO SAVEPOINT ${n}`),r||t.exec(`RELEASE SAVEPOINT ${n}`),e}},getDBData:(e,t)=>y.getDBDataRecord(e,t)?.value,getDBDataRecord:(e,t)=>{let n=y.getDBFirst(`SELECT namespace, data_key, data_value, created_at, updated_at FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1`,[d(t?.namespace),f(e)]);return n?_(n):void 0},setDBData:(e,t,n)=>{let r=new Date().toISOString();return y.runDB(`INSERT INTO tc_framework_data (namespace, data_key, data_value, created_at, updated_at)
1
+ require(`../../_virtual/_rolldown/runtime.js`);const e=require(`../utils/i18n.js`);let t=require(`node:fs`),n=require(`node:path`);var r=`framework`,i=`.template-core/template-core.sqlite`;function a(e){let r=c(e),i,a=!1,l=0,u=()=>(y(a),i||((0,t.mkdirSync)((0,n.dirname)(r),{recursive:!0}),i=o(r),s(i)),i),_={dbPath:r,getDBConnection:u,execDB:e=>{u().exec(e)},runDB:(e,t)=>{let n=u().prepare(e).run(...d(t));return{changes:Number(n.changes??0),lastInsertRowid:n.lastInsertRowid??0}},queryDB:(e,t)=>u().prepare(e).all(...d(t)),getDBFirst:(e,t)=>u().prepare(e).get(...d(t)),transactionDB:e=>{let t=u(),n=`tc_savepoint_${l}`,r=l===0;t.exec(r?`BEGIN IMMEDIATE`:`SAVEPOINT ${n}`),l+=1;try{let i=e(_);return--l,t.exec(r?`COMMIT`:`RELEASE SAVEPOINT ${n}`),i}catch(e){throw--l,t.exec(r?`ROLLBACK`:`ROLLBACK TO SAVEPOINT ${n}`),r||t.exec(`RELEASE SAVEPOINT ${n}`),e}},getDBData:(e,t)=>_.getDBDataRecord(e,t)?.value,getDBDataRecord:(e,t)=>{let n=_.getDBFirst(`SELECT namespace, data_key, data_value, created_at, updated_at FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1`,[f(t?.namespace),p(e)]);return n?v(n):void 0},setDBData:(e,t,n)=>{let r=new Date().toISOString();return _.runDB(`INSERT INTO tc_framework_data (namespace, data_key, data_value, created_at, updated_at)
2
2
  VALUES (?, ?, ?, ?, ?)
3
3
  ON CONFLICT(namespace, data_key) DO UPDATE SET
4
4
  data_value = excluded.data_value,
5
- updated_at = excluded.updated_at`,[d(n?.namespace),f(e),h(t),r,r])},hasDBData:(e,t)=>!!y.getDBFirst(`SELECT 1 AS exists_value FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1`,[d(t?.namespace),f(e)]),deleteDBData:(e,t)=>y.runDB(`DELETE FROM tc_framework_data WHERE namespace = ? AND data_key = ?`,[d(t?.namespace),f(e)]).changes>0,listDBData:e=>{let t=p(e?.limit),n=m(e?.offset);return y.queryDB(`SELECT namespace, data_key, data_value, created_at, updated_at
5
+ updated_at = excluded.updated_at`,[f(n?.namespace),p(e),g(t),r,r])},hasDBData:(e,t)=>!!_.getDBFirst(`SELECT 1 AS exists_value FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1`,[f(t?.namespace),p(e)]),deleteDBData:(e,t)=>_.runDB(`DELETE FROM tc_framework_data WHERE namespace = ? AND data_key = ?`,[f(t?.namespace),p(e)]).changes>0,listDBData:e=>{let t=m(e?.limit),n=h(e?.offset);return _.queryDB(`SELECT namespace, data_key, data_value, created_at, updated_at
6
6
  FROM tc_framework_data
7
7
  WHERE namespace = ?
8
8
  ORDER BY updated_at DESC, data_key ASC
9
- LIMIT ? OFFSET ?`,[d(e?.namespace),t,n]).map(e=>_(e))},countDBData:e=>{let t=y.getDBFirst(`SELECT COUNT(1) AS count_value FROM tc_framework_data WHERE namespace = ?`,[d(e?.namespace)]);return Number(t?.count_value??0)},clearDBData:e=>y.runDB(`DELETE FROM tc_framework_data WHERE namespace = ?`,[d(e?.namespace)]).changes,closeDB:()=>{c||=(i?.close(),!0)}};return y}function a(e){let t=`node:sqlite`,n;try{n=require(t)}catch(e){throw Error(`[db] 默认数据库依赖 Node.js 内置 ${t},当前运行环境不可用。请升级 Node.js,或在业务项目覆盖 app/extends/db.ts。${y(e)}`)}if(!n.DatabaseSync)throw Error(`[db] ${t} 未提供 DatabaseSync,无法初始化默认 SQLite 数据库。`);return new n.DatabaseSync(e)}function o(e){e.exec(`
9
+ LIMIT ? OFFSET ?`,[f(e?.namespace),t,n]).map(e=>v(e))},countDBData:e=>{let t=_.getDBFirst(`SELECT COUNT(1) AS count_value FROM tc_framework_data WHERE namespace = ?`,[f(e?.namespace)]);return Number(t?.count_value??0)},clearDBData:e=>_.runDB(`DELETE FROM tc_framework_data WHERE namespace = ?`,[f(e?.namespace)]).changes,closeDB:()=>{a||=(i?.close(),!0)}};return _}function o(t){let n=`node:sqlite`,r;try{r=require(n)}catch(t){throw e.createI18nError(`server.errors.dbNodeSqliteUnavailable`,{sqliteSpecifier:n,cause:b(t)})}if(!r.DatabaseSync)throw e.createI18nError(`server.errors.dbDatabaseSyncUnavailable`,{sqliteSpecifier:n});return new r.DatabaseSync(t)}function s(e){e.exec(`
10
10
  CREATE TABLE IF NOT EXISTS tc_framework_data (
11
11
  namespace TEXT NOT NULL,
12
12
  data_key TEXT NOT NULL,
@@ -17,4 +17,4 @@ require(`../../_virtual/_rolldown/runtime.js`);let e=require(`node:fs`),t=requir
17
17
  );
18
18
  CREATE INDEX IF NOT EXISTS idx_tc_framework_data_namespace_updated_at
19
19
  ON tc_framework_data (namespace, updated_at);
20
- `)}function s(e){let n=c(e),i=typeof n==`string`?n:n?.path??n?.filename??n?.sqlitePath??l(n?.sqlite);return i?(0,t.isAbsolute)(i)?i:(0,t.resolve)(e.baseDir,i):(0,t.resolve)(e.baseDir,r)}function c(e){let t=e.config;return t.db??(t.sqlite?{sqlite:t.sqlite}:void 0)}function l(e){if(e)return typeof e==`string`?e:e.path??e.filename}function u(e){return e?Array.isArray(e)?e:[e]:[]}function d(e=n){let t=e.trim();if(!t)throw Error(`[db] namespace 不能为空`);return t}function f(e){let t=e.trim();if(!t)throw Error(`[db] key 不能为空`);return t}function p(e=100){if(!Number.isSafeInteger(e)||e<=0)throw Error(`[db] limit 必须是大于 0 的整数`);return e}function m(e=0){if(!Number.isSafeInteger(e)||e<0)throw Error(`[db] offset 必须是大于等于 0 的整数`);return e}function h(e){try{let t=JSON.stringify(e);if(t===void 0)throw Error(`value is not JSON serializable`);return t}catch(e){throw Error(`[db] value 必须可以被 JSON.stringify 序列化。${y(e)}`)}}function g(e){return JSON.parse(e)}function _(e){return{key:e.data_key,value:g(e.data_value),namespace:e.namespace,createdAt:e.created_at,updatedAt:e.updated_at}}function v(e){if(e)throw Error(`[db] SQLite 数据库连接已关闭`)}function y(e){return e instanceof Error&&e.message?` Cause: ${e.message}`:``}module.exports=i;
20
+ `)}function c(e){let t=l(e),r=typeof t==`string`?t:t?.path??t?.filename??t?.sqlitePath??u(t?.sqlite);return r?(0,n.isAbsolute)(r)?r:(0,n.resolve)(e.baseDir,r):(0,n.resolve)(e.baseDir,i)}function l(e){let t=e.config;return t.db??(t.sqlite?{sqlite:t.sqlite}:void 0)}function u(e){if(e)return typeof e==`string`?e:e.path??e.filename}function d(e){return e?Array.isArray(e)?e:[e]:[]}function f(t=r){let n=t.trim();if(!n)throw e.createI18nError(`server.errors.dbNamespaceRequired`);return n}function p(t){let n=t.trim();if(!n)throw e.createI18nError(`server.errors.dbKeyRequired`);return n}function m(t=100){if(!Number.isSafeInteger(t)||t<=0)throw e.createI18nError(`server.errors.dbLimitInvalid`);return t}function h(t=0){if(!Number.isSafeInteger(t)||t<0)throw e.createI18nError(`server.errors.dbOffsetInvalid`);return t}function g(t){try{let e=JSON.stringify(t);if(e===void 0)throw Error(`value is not JSON serializable`);return e}catch(t){throw e.createI18nError(`server.errors.dbValueNotSerializable`,{cause:b(t)})}}function _(e){return JSON.parse(e)}function v(e){return{key:e.data_key,value:_(e.data_value),namespace:e.namespace,createdAt:e.created_at,updatedAt:e.updated_at}}function y(t){if(t)throw e.createI18nError(`server.errors.dbClosed`)}function b(e){return e instanceof Error&&e.message?` Cause: ${e.message}`:``}module.exports=a;
@@ -1 +1 @@
1
- const e=require(`../_virtual/_rolldown/runtime.js`);let t=require(`koa-compress`);t=e.__toESM(t);let n=require(`koa-bodyparser`);n=e.__toESM(n);let r=require(`koa-static`);r=e.__toESM(r);var i=(0,n.default)({formLimit:`1000mb`,enableTypes:[`form`,`json`,`text`]}),a=e=>{let n=e.envs.isLocal()?{maxage:0,setHeaders(e){e.setHeader(`Cache-Control`,`no-store, no-cache, must-revalidate, proxy-revalidate`),e.setHeader(`Pragma`,`no-cache`),e.setHeader(`Expires`,`0`)}}:{maxage:365*24*60*60*1e3};e.use(e.middlewares.errorHandle),e.use((0,t.default)({threshold:2048})),e.publicsPath.forEach(t=>e.use((0,r.default)(t,n))),e.use(i),e.use(e.middlewares.requestParameterParsing),e.use(e.middlewares.projectHandler),!e.envs.isLocal()&&e.use(e.middlewares.apiSignVerify),e.use(e.middlewares.apiParamsVerify)};module.exports=a;
1
+ const e=require(`../_virtual/_rolldown/runtime.js`);let t=require(`koa-compress`);t=e.__toESM(t);let n=require(`koa-bodyparser`);n=e.__toESM(n);let r=require(`koa-static`);r=e.__toESM(r);var i=(0,n.default)({formLimit:`1000mb`,enableTypes:[`form`,`json`,`text`]}),a=e=>{let n=e.envs.isLocal()?{maxage:0,setHeaders(e){e.setHeader(`Cache-Control`,`no-store, no-cache, must-revalidate, proxy-revalidate`),e.setHeader(`Pragma`,`no-cache`),e.setHeader(`Expires`,`0`)}}:{maxage:e.config.resourceCacheTimeMs??300*1e3};e.use(e.middlewares.errorHandle),e.use((0,t.default)({threshold:2048})),e.publicsPath.forEach(t=>e.use((0,r.default)(t,n))),e.use(i),e.use(e.middlewares.requestParameterParsing),e.use(e.middlewares.projectHandler),!e.envs.isLocal()&&e.use(e.middlewares.apiSignVerify),e.use(e.middlewares.apiParamsVerify)};module.exports=a;
@@ -1 +1 @@
1
- var e={},t=t=>async(n,r)=>{try{await r()}catch(r){let{status:i=200,message:a,detail:o,code:s=5e4,returnError:c}=r;if(t.extends.logger?.info(`error info -> ${JSON.stringify(r)}`),t.extends.logger?.error(`-- [excption] --`,r),t.extends.logger?.error(`-- [excption] info --`,i,a,o),a?.includes(`template not found`)){let r=n.path??n.req.url??n.url;if(e[r]===void 0?e[r]=0:e[r]+=1,e[r]<t.config.notFoundRedirectCount){n.status=302,n.redirect(t.options?.homePage??`/`);return}else e[r]=0}c?(n.status=i,n.body={code:s,message:a}):(n.status=200,n.body={code:5e4,message:`网络错误`})}};module.exports=t;
1
+ const e=require(`../utils/i18n.js`);var t={},n=n=>async(r,i)=>{try{await i()}catch(i){let a=i,{status:o=200,message:s,detail:c,code:l=5e4,returnError:u}=a,d=e.getRequestErrorMessage(r,a,s);if(n.extends.logger?.info(`error info -> ${JSON.stringify(i)}`),n.extends.logger?.error(`-- [excption] --`,i),n.extends.logger?.error(`-- [excption] info --`,o,s,c),s?.includes(`template not found`)){let e=r.path??r.req.url??r.url;if(t[e]===void 0?t[e]=0:t[e]+=1,t[e]<n.config.notFoundRedirectCount){r.status=302,r.redirect(n.options?.homePage??`/`);return}else t[e]=0}u?(r.status=o,r.body={code:l,message:d}):(r.status=200,r.body={code:5e4,message:e.requestT(r,`server.errors.network`)})}};module.exports=n;
@@ -1 +1 @@
1
- var e=e=>async(t,n)=>{let r=t.headers.projk,{path:i}=t;if(i.indexOf(`${e.options.apiPrefix}/proj/`)!==-1&&(!r||[`undefined`,`null`].includes(r))){t.status=200,t.body={code:110,data:null,message:`请求不合法`};return}t.extInfo={key:r},await n()};module.exports=e;
1
+ const e=require(`../utils/i18n.js`);var t=t=>async(n,r)=>{let i=n.headers.projk,{path:a}=n;if(a.indexOf(`${t.options.apiPrefix}/proj/`)!==-1&&(!i||[`undefined`,`null`].includes(i))){n.status=200,n.body={code:110,data:null,message:e.requestT(n,`server.errors.invalidRequest`)};return}n.extInfo={key:i},await r()};module.exports=t;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../../packages/common/i18n/index.js`);var t=`x-tc-language`,n=`en-US`,r=[t,`tc_language`,`tc-language`,`language`,`lang`,`locale`],i=e=>Array.isArray(e)?e[0]:e,a=(e,t)=>i(e[t.toLowerCase()]),o=e=>e?e.split(`,`).map(e=>e.trim().split(`;`)[0]?.trim()).filter(e=>!!e):[],s=t=>{if(!t)return n;let r=Object.keys(e.i18nStore.getState().resources),i=t.trim().toLowerCase(),a=r.find(e=>e.toLowerCase()===i);if(a)return a;let o=i.split(`-`)[0];return r.find(e=>e.toLowerCase().split(`-`)[0]===o)??n},c=e=>{for(let t of r){let n=a(e.headers,t);if(n)return s(n)}let t=o(a(e.headers,`accept-language`));for(let e of t){let t=s(e);if(t)return t}return n},l=(t,r,i,a)=>e.i18n(r,i,{...a,language:a?.language??c(t),fallbackLanguage:a?.fallbackLanguage??n}),u=(e,t,n)=>{let r=Error(n??e);return r.i18nKey=e,r.i18nData=t,r},d=(e,t,n)=>t.i18nKey?l(e,t.i18nKey,t.i18nData,{fallback:n??t.message}):n??t.message;exports.LANGUAGE_HEADER_KEY=t,exports.createI18nError=u,exports.getRequestErrorMessage=d,exports.getRequestLanguage=c,exports.normalizeI18nLanguage=s,exports.requestT=l;
@@ -10,6 +10,7 @@
10
10
 
11
11
  <script id="input">
12
12
  window['_basePath'] = {{basePath | safe}}
13
+ window['_FEBasePage'] = {{FEBasePage | safe}}
13
14
  window['_projKey'] = {{projKeyJson | safe}}
14
15
  window['_signKey'] = {{signKey | safe}}
15
16
  const s = document.getElementById('input')
@@ -1 +1 @@
1
- var e={frameName:`TemplateCore`,signKey:`sakjdfnkjwjfnfkjkjldljksndf`,apiSignVerify:{whiteList:[]},auth:{ATKey:`Authorization`,RTKey:`RT`},notFoundRedirectCount:5};module.exports=e;
1
+ var e={frameName:`TemplateCore`,signKey:`sakjdfnkjwjfnfkjkjldljksndf`,apiSignVerify:{whiteList:[]},auth:{ATKey:`Authorization`,RTKey:`RT`},notFoundRedirectCount:5,resourceCacheTimeMs:300*1e3};module.exports=e;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={common:{tip:`提示`,confirm:`确认`,cancel:`取消`,close:`关闭`,submit:`提交`,reset:`重置`},components:{breadcrumb:{close:`关闭`},confirmDialog:{title:`提示`,okText:`确认`,cancelText:`取消`},dataTable:{noData:`暂无数据`},date:{placeholder:`请选择日期`,placeholderDateTime:`请选择日期时间`,placeholderRange:`选择日期范围`,placeholderDateTimeRange:`选择日期时间范围`,weekDays:[`日`,`一`,`二`,`三`,`四`,`五`,`六`],selectTime:`选择时间`,startTime:`开始时间`,endTime:`结束时间`,confirm:`确认`,monthFormat:`yyyy年 M月`,quickRanges:`快速选择`,quickRangeToday:`今天`,quickRangeYesterday:`昨天`,quickRangeLast7Days:`近7天`,quickRangeLast30Days:`近30天`,quickRangeThisMonth:`本月`,quickRangeLastYear:`最近一年`,clear:`清除`},form:{submit:`提交`,cancel:`取消`},imagePreview:{closePreview:`关闭预览`,previousImage:`上一张`,nextImage:`下一张`,zoomOut:`缩小`,zoomIn:`放大`,reset:`重置`,rotateLeft:`左旋转`,rotateRight:`右旋转`,flipHorizontal:`左右镜像`,flipVertical:`上下镜像`,previewImage:`预览第{index}张`},modal:{close:`关闭`,confirmTitle:`提示`,okText:`确认`,cancelText:`取消`},message:{close:`关闭`},select:{empty:`暂无选项`},tableSearch:{reset:`重置`,search:`查询`,export:`导出Excel`,expand:`展开`,collapse:`收起`,exportCurrentPage:`当前页`,exportCurrentQuery:`当前查询条件`},upload:{deleteImage:`删除图片`,upload:`上传`}}};exports.defaultLanguageResources=e;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={common:{tip:`提示`,confirm:`确认`,cancel:`取消`,close:`关闭`,submit:`提交`,reset:`重置`},server:{errors:{network:`网络错误`,invalidRequest:`请求不合法`,getProjectFailed:`获取项目异常`,dbNodeSqliteUnavailable:`[db] 默认数据库依赖 Node.js 内置 {sqliteSpecifier},当前运行环境不可用。请升级 Node.js,或在业务项目覆盖 app/extends/db.ts。{cause}`,dbDatabaseSyncUnavailable:`[db] {sqliteSpecifier} 未提供 DatabaseSync,无法初始化默认 SQLite 数据库。`,dbNamespaceRequired:`[db] namespace 不能为空`,dbKeyRequired:`[db] key 不能为空`,dbLimitInvalid:`[db] limit 必须是大于 0 的整数`,dbOffsetInvalid:`[db] offset 必须是大于等于 0 的整数`,dbValueNotSerializable:`[db] value 必须可以被 JSON.stringify 序列化。{cause}`,dbClosed:`[db] SQLite 数据库连接已关闭`}},components:{breadcrumb:{close:`关闭`},confirmDialog:{title:`提示`,okText:`确认`,cancelText:`取消`},dataTable:{noData:`暂无数据`},date:{placeholder:`请选择日期`,placeholderDateTime:`请选择日期时间`,placeholderRange:`选择日期范围`,placeholderDateTimeRange:`选择日期时间范围`,weekDays:[`日`,`一`,`二`,`三`,`四`,`五`,`六`],selectTime:`选择时间`,startTime:`开始时间`,endTime:`结束时间`,confirm:`确认`,monthFormat:`yyyy年 M月`,quickRanges:`快速选择`,quickRangeToday:`今天`,quickRangeYesterday:`昨天`,quickRangeLast7Days:`近7天`,quickRangeLast30Days:`近30天`,quickRangeThisMonth:`本月`,quickRangeLastYear:`最近一年`,clear:`清除`},form:{submit:`提交`,cancel:`取消`},imagePreview:{closePreview:`关闭预览`,previousImage:`上一张`,nextImage:`下一张`,zoomOut:`缩小`,zoomIn:`放大`,reset:`重置`,rotateLeft:`左旋转`,rotateRight:`右旋转`,flipHorizontal:`左右镜像`,flipVertical:`上下镜像`,previewImage:`预览第{index}张`},modal:{close:`关闭`,confirmTitle:`提示`,okText:`确认`,cancelText:`取消`},message:{close:`关闭`},select:{empty:`暂无选项`},tableSearch:{reset:`重置`,search:`查询`,export:`导出Excel`,expand:`展开`,collapse:`收起`,exportCurrentPage:`当前页`,exportCurrentQuery:`当前查询条件`},upload:{deleteImage:`删除图片`,upload:`上传`}}};exports.defaultLanguageResources=e;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={common:{tip:`Prompt`,confirm:`Confirm`,cancel:`Cancel`,close:`Close`,submit:`Submit`,reset:`Reset`},components:{breadcrumb:{close:`Close`},confirmDialog:{title:`Prompt`,okText:`Confirm`,cancelText:`Cancel`},dataTable:{noData:`No Data`},date:{placeholder:`Select date`,placeholderDateTime:`Select date and time`,placeholderRange:`Select date range`,placeholderDateTimeRange:`Select date and time range`,weekDays:[`Sun`,`Mon`,`Tue`,`Wed`,`Thu`,`Fri`,`Sat`],selectTime:`Select time`,startTime:`Start time`,endTime:`End time`,confirm:`Confirm`,monthFormat:`MMM yyyy`,quickRanges:`Quick ranges`,quickRangeToday:`Today`,quickRangeYesterday:`Yesterday`,quickRangeLast7Days:`Last 7 days`,quickRangeLast30Days:`Last 30 days`,quickRangeThisMonth:`This month`,quickRangeLastYear:`Last year`,clear:`Clear`},form:{submit:`Submit`,cancel:`Cancel`},imagePreview:{closePreview:`Close preview`,previousImage:`Previous image`,nextImage:`Next image`,zoomOut:`Zoom out`,zoomIn:`Zoom in`,reset:`Reset`,rotateLeft:`Rotate left`,rotateRight:`Rotate right`,flipHorizontal:`Flip horizontal`,flipVertical:`Flip vertical`,previewImage:`Preview image {index}`},modal:{close:`Close`,confirmTitle:`Prompt`,okText:`Confirm`,cancelText:`Cancel`},message:{close:`Close`},select:{empty:`No options`},tableSearch:{reset:`Reset`,search:`Search`,export:`Export Excel`,expand:`Expand`,collapse:`Collapse`,exportCurrentPage:`Current Page`,exportCurrentQuery:`Current Filters`},upload:{deleteImage:`Delete image`,upload:`Upload`}}};exports.defaultEnglishResources=e;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={common:{tip:`Prompt`,confirm:`Confirm`,cancel:`Cancel`,close:`Close`,submit:`Submit`,reset:`Reset`},server:{errors:{network:`Network error`,invalidRequest:`Invalid request`,getProjectFailed:`Failed to get project`,dbNodeSqliteUnavailable:`[db] The default database depends on Node.js built-in {sqliteSpecifier}, which is unavailable in the current runtime. Upgrade Node.js or override app/extends/db.ts in the business project.{cause}`,dbDatabaseSyncUnavailable:`[db] {sqliteSpecifier} does not provide DatabaseSync, so the default SQLite database cannot be initialized.`,dbNamespaceRequired:`[db] namespace cannot be empty`,dbKeyRequired:`[db] key cannot be empty`,dbLimitInvalid:`[db] limit must be an integer greater than 0`,dbOffsetInvalid:`[db] offset must be an integer greater than or equal to 0`,dbValueNotSerializable:`[db] value must be serializable by JSON.stringify.{cause}`,dbClosed:`[db] SQLite database connection is closed`}},components:{breadcrumb:{close:`Close`},confirmDialog:{title:`Prompt`,okText:`Confirm`,cancelText:`Cancel`},dataTable:{noData:`No Data`},date:{placeholder:`Select date`,placeholderDateTime:`Select date and time`,placeholderRange:`Select date range`,placeholderDateTimeRange:`Select date and time range`,weekDays:[`Sun`,`Mon`,`Tue`,`Wed`,`Thu`,`Fri`,`Sat`],selectTime:`Select time`,startTime:`Start time`,endTime:`End time`,confirm:`Confirm`,monthFormat:`MMM yyyy`,quickRanges:`Quick ranges`,quickRangeToday:`Today`,quickRangeYesterday:`Yesterday`,quickRangeLast7Days:`Last 7 days`,quickRangeLast30Days:`Last 30 days`,quickRangeThisMonth:`This month`,quickRangeLastYear:`Last year`,clear:`Clear`},form:{submit:`Submit`,cancel:`Cancel`},imagePreview:{closePreview:`Close preview`,previousImage:`Previous image`,nextImage:`Next image`,zoomOut:`Zoom out`,zoomIn:`Zoom in`,reset:`Reset`,rotateLeft:`Rotate left`,rotateRight:`Rotate right`,flipHorizontal:`Flip horizontal`,flipVertical:`Flip vertical`,previewImage:`Preview image {index}`},modal:{close:`Close`,confirmTitle:`Prompt`,okText:`Confirm`,cancelText:`Cancel`},message:{close:`Close`},select:{empty:`No options`},tableSearch:{reset:`Reset`,search:`Search`,export:`Export Excel`,expand:`Expand`,collapse:`Collapse`,exportCurrentPage:`Current Page`,exportCurrentQuery:`Current Filters`},upload:{deleteImage:`Delete image`,upload:`Upload`}}};exports.defaultEnglishResources=e;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./guards/index.js`),t=require(`./array/index.js`),n=require(`./cache/LRUCache.js`),r=require(`./http/index.js`),i=require(`./i18n/default.js`),a=require(`./i18n/en-US.js`),o=require(`./i18n/locales.js`),s=require(`./i18n/index.js`),c=require(`./log/index.js`),l=require(`./number/index.js`),u=require(`./object/filterEmpty.js`),d=require(`./object/index.js`),f=require(`./rafTimer.js`),p=require(`./string/index.js`);exports.ANSI_RESET=c.ANSI_RESET,exports.FetchAxios=r.FetchAxios,exports.LRUCache=n.LRUCache,exports.ansiColors=c.ansiColors,exports.axios=r.axios,exports.capitalize=p.capitalize,exports.chunk=t.chunk,exports.clamp=l.clamp,exports.clearRafTimer=f.clearRafTimer,exports.colorLog=c.colorLog,exports.colorize=c.colorize,exports.compact=t.compact,exports.createInstance=r.createInstance,exports.defaultEnglishResources=a.defaultEnglishResources,exports.defaultLanguage=o.defaultLanguage,exports.defaultLanguageResources=i.defaultLanguageResources,exports.filterEmpty=u.filterEmpty,exports.filtereEmpty=u.filtereEmpty,exports.getI18nPathValue=s.getI18nPathValue,exports.getLanguage=s.getLanguage,exports.groupBy=t.groupBy,exports.i18n=s.i18n,exports.i18nStore=s.i18nStore,exports.interpolateI18nMessage=s.interpolateI18nMessage,exports.isBlank=p.isBlank,exports.isBoolean=e.isBoolean,exports.isFunction=e.isFunction,exports.isNil=e.isNil,exports.isNonNullable=e.isNonNullable,exports.isNumber=e.isNumber,exports.isPlainI18nDictionary=s.isPlainI18nDictionary,exports.isPlainObject=e.isPlainObject,exports.isString=e.isString,exports.joinColorized=c.joinColorized,exports.joinStr=p.joinStr,exports.kebabCase=p.kebabCase,exports.languageLocalKey=o.languageLocalKey,exports.logColor=c.logColor,exports.logColorized=c.logColorized,exports.logGreen=c.logGreen,exports.logJoinColorized=c.logJoinColorized,exports.logPink=c.logPink,exports.logRed=c.logRed,exports.logWhite=c.logWhite,exports.logYellow=c.logYellow,exports.mapValues=d.mapValues,exports.mergeI18nDictionary=s.mergeI18nDictionary,exports.mergeI18nResources=s.mergeI18nResources,exports.omit=d.omit,exports.pick=d.pick,exports.rafClearInterval=f.rafClearInterval,exports.rafClearTimeout=f.rafClearTimeout,exports.rafSetInterval=f.rafSetInterval,exports.rafSetTimeout=f.rafSetTimeout,exports.t=s.t,exports.toArray=t.toArray,exports.toFiniteNumber=l.toFiniteNumber,exports.translate=s.translate,exports.translations=o.translations,exports.uniqueBy=t.uniqueBy;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./i18n/default.js`),t=require(`./i18n/en-US.js`),n=require(`./i18n/locales.js`),r=require(`./i18n/index.js`),i=require(`./guards/index.js`),a=require(`./array/index.js`),o=require(`./cache/LRUCache.js`),s=require(`./http/index.js`),c=require(`./log/index.js`),l=require(`./number/index.js`),u=require(`./object/filterEmpty.js`),d=require(`./object/index.js`),f=require(`./rafTimer.js`),p=require(`./string/index.js`);exports.ANSI_RESET=c.ANSI_RESET,exports.FetchAxios=s.FetchAxios,exports.LRUCache=o.LRUCache,exports.ansiColors=c.ansiColors,exports.axios=s.axios,exports.capitalize=p.capitalize,exports.chunk=a.chunk,exports.clamp=l.clamp,exports.clearRafTimer=f.clearRafTimer,exports.colorLog=c.colorLog,exports.colorize=c.colorize,exports.compact=a.compact,exports.createInstance=s.createInstance,exports.defaultEnglishResources=t.defaultEnglishResources,exports.defaultLanguage=n.defaultLanguage,exports.defaultLanguageResources=e.defaultLanguageResources,exports.filterEmpty=u.filterEmpty,exports.filtereEmpty=u.filtereEmpty,exports.getI18nPathValue=r.getI18nPathValue,exports.getLanguage=r.getLanguage,exports.groupBy=a.groupBy,exports.i18n=r.i18n,exports.i18nStore=r.i18nStore,exports.interpolateI18nMessage=r.interpolateI18nMessage,exports.isBlank=p.isBlank,exports.isBoolean=i.isBoolean,exports.isFunction=i.isFunction,exports.isNil=i.isNil,exports.isNonNullable=i.isNonNullable,exports.isNumber=i.isNumber,exports.isPlainI18nDictionary=r.isPlainI18nDictionary,exports.isPlainObject=i.isPlainObject,exports.isString=i.isString,exports.joinColorized=c.joinColorized,exports.joinStr=p.joinStr,exports.kebabCase=p.kebabCase,exports.languageLocalKey=n.languageLocalKey,exports.logColor=c.logColor,exports.logColorized=c.logColorized,exports.logGreen=c.logGreen,exports.logJoinColorized=c.logJoinColorized,exports.logPink=c.logPink,exports.logRed=c.logRed,exports.logWhite=c.logWhite,exports.logYellow=c.logYellow,exports.mapValues=d.mapValues,exports.mergeI18nDictionary=r.mergeI18nDictionary,exports.mergeI18nResources=r.mergeI18nResources,exports.omit=d.omit,exports.pick=d.pick,exports.rafClearInterval=f.rafClearInterval,exports.rafClearTimeout=f.rafClearTimeout,exports.rafSetInterval=f.rafSetInterval,exports.rafSetTimeout=f.rafSetTimeout,exports.t=r.t,exports.toArray=a.toArray,exports.toFiniteNumber=l.toFiniteNumber,exports.translate=r.translate,exports.translations=n.translations,exports.uniqueBy=a.uniqueBy;
@@ -1,8 +1,9 @@
1
1
  import e from "./base.js";
2
+ import { requestT as t } from "../utils/i18n.js";
2
3
  //#region app/controller/project.ts
3
- var t = ((t) => {
4
- let n = e(t);
5
- return class extends n {
4
+ var n = ((n) => {
5
+ let r = e(n);
6
+ return class extends r {
6
7
  getModelList = async (e) => {
7
8
  let t = (await this.service.project.getModelList()).reduce((e, t) => {
8
9
  let { model: n, project: r } = t, { key: i, name: a, desc: o } = n, s = {
@@ -39,19 +40,19 @@ var t = ((t) => {
39
40
  this.success(e, n);
40
41
  };
41
42
  getProject = async (e) => {
42
- let { key: t } = e.params;
43
- if (!t) {
44
- this.fail(e, 10001, "获取项目异常");
43
+ let { key: n } = e.params;
44
+ if (!n) {
45
+ this.fail(e, 10001, t(e, "server.errors.getProjectFailed"));
45
46
  return;
46
47
  }
47
- let n = this.service.project.getProject(t);
48
- if (!n) {
49
- this.fail(e, 10001, "获取项目异常");
48
+ let r = this.service.project.getProject(n);
49
+ if (!r) {
50
+ this.fail(e, 10001, t(e, "server.errors.getProjectFailed"));
50
51
  return;
51
52
  }
52
- this.success(e, n);
53
+ this.success(e, r);
53
54
  };
54
55
  };
55
56
  });
56
57
  //#endregion
57
- export { t as default };
58
+ export { n as default };
@@ -13,6 +13,7 @@ var t = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u003e
13
13
  projKey: i ?? "",
14
14
  name: n.options?.name ?? "",
15
15
  env: n.envs.get(),
16
+ FEBasePage: t(`${n.options.pageBasePage}`),
16
17
  basePath: t(`${n.options.pageBasePage}${r.params.page}`),
17
18
  projKeyJson: t(String(r.query.projk ?? "")),
18
19
  signKey: t(n.config.signKey),
@@ -23,7 +24,7 @@ var t = (e) => JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u003e
23
24
  n.extends.localCacheHtmlEtag.setEtag(s, e);
24
25
  }
25
26
  let c = n.extends.localCacheHtmlEtag.getEtag(s);
26
- if (c || console.log("---- etag 缓存异常 请检查服务 ----"), r.etag = c ?? Date.now() + "", r.fresh) {
27
+ if (c || console.log("---- etag 缓存异常 请检查服务 ----"), r.etag = c ?? Date.now() + "", r.set("Cache-Control", `public, max-age=${n.config.resourceCacheTimeMs ?? 300 * 1e3}, immutable`), r.fresh) {
27
28
  r.status = 304;
28
29
  return;
29
30
  }