@_tc/template-core 0.0.1-bate.59 → 0.0.1-bate.60

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.
@@ -6,10 +6,12 @@ Use these when building business UI on top of TemplateCore, especially Dashboard
6
6
 
7
7
  ```tsx
8
8
  import { AsyncSelect, LanguageSwitch, ThemeSwitch } from '@_tc/template-core/fe'
9
+ import { api, get, post, request } from '@_tc/template-core/fe/common/request'
10
+ import type { BaseResponse, RequestConfig, ResponseConfig } from '@_tc/template-core/fe/common/request'
9
11
  import { Button, DataTable, TableSearch } from '@_tc/template-core/fe/rc'
10
12
  ```
11
13
 
12
- Inside this repository, use `@tc/ui-react` for the raw UI workspace package. In published business projects, prefer `@_tc/template-core/fe/rc` for UI components and `@_tc/template-core/fe` for frontend runtime helpers.
14
+ Inside this repository, use `@tc/ui-react` for the raw UI workspace package. In published business projects, prefer `@_tc/template-core/fe/rc` for UI components, `@_tc/template-core/fe` for frontend runtime helpers, and `@_tc/template-core/fe/common/request` when only the request layer is needed.
13
15
 
14
16
  ## LanguageSwitch
15
17
 
@@ -239,6 +239,16 @@ Import TemplateCore frontend helpers from `@_tc/template-core/fe`:
239
239
  import { AsyncSelect, LanguageSwitch, ThemeSwitch } from '@_tc/template-core/fe'
240
240
  ```
241
241
 
242
+ Request helpers and request types are available from the root frontend entry or the explicit request subpath:
243
+
244
+ ```ts
245
+ import { api, get, post, request } from '@_tc/template-core/fe'
246
+ import type { BaseResponse, RequestConfig, ResponseConfig } from '@_tc/template-core/fe'
247
+
248
+ import { api as requestApi } from '@_tc/template-core/fe/common/request'
249
+ import type { BaseResponse as ApiResponse } from '@_tc/template-core/fe/common/request'
250
+ ```
251
+
242
252
  Import UI components from the published UI re-export:
243
253
 
244
254
  ```tsx
package/AGENT_README.md CHANGED
@@ -19,6 +19,7 @@ TemplateCore 是一个 TypeScript + Koa + React 的后台框架包。它提供
19
19
  | `@_tc/template-core` | `serverStart`、`baseFn`、Koa/Controller/Service 类型。 |
20
20
  | `@_tc/template-core/bundler` | `buildFE()`、`buildBE()`。 |
21
21
  | `@_tc/template-core/fe` | 前端初始化、请求方法、Dashboard/Schema 类型、共享状态、业务前端组件。 |
22
+ | `@_tc/template-core/fe/common/request` | 请求实例、请求方法和请求类型的显式子路径。 |
22
23
  | `@_tc/template-core/fe/rc` | React UI 组件和 SchemaForm 组件。 |
23
24
  | `@_tc/template-core/fe/tailwind_ui.css` | 内置前端/UI 全局样式。 |
24
25
  | `@_tc/template-core/model` | `ModelDataType` 等 model 配置类型。 |
@@ -365,6 +366,7 @@ import '@_tc/template-core/fe/tailwind_ui.css'
365
366
 
366
367
  ```ts
367
368
  import {
369
+ api,
368
370
  get,
369
371
  post,
370
372
  request,
@@ -374,6 +376,19 @@ import {
374
376
  LanguageSwitch,
375
377
  ThemeSwitch,
376
378
  } from '@_tc/template-core/fe'
379
+
380
+ import type {
381
+ BaseResponse,
382
+ RequestConfig,
383
+ ResponseConfig,
384
+ } from '@_tc/template-core/fe'
385
+ ```
386
+
387
+ 只需要请求层时,可以走显式子路径:
388
+
389
+ ```ts
390
+ import { api, get, post, request } from '@_tc/template-core/fe/common/request'
391
+ import type { BaseResponse, RequestConfig, ResponseConfig } from '@_tc/template-core/fe/common/request'
377
392
  ```
378
393
 
379
394
  UI 组件从 `@_tc/template-core/fe/rc` 导入:
@@ -384,7 +399,8 @@ import { Button, DataTable, Form, Input, Modal, Select } from '@_tc/template-cor
384
399
 
385
400
  常用前端能力:
386
401
 
387
- - 请求方法:`request`、`get`、`post`、`put`、`patch`、`del`。
402
+ - 请求方法:`api`、`request`、`get`、`post`、`put`、`patch`、`del`。
403
+ - 请求类型:`BaseResponse`、`PageParams`、`PageResponse`、`RequestConfig`、`ResponseConfig`、`AxiosError`。
388
404
  - 请求 header:自动添加 `s_t`、`s_sign`、`projk`;存在短 token 时添加 `Authorization: Bearer ${token}`。
389
405
  - 本地 key:短 token 是 `localStorage.auth_token`,项目 key 是 `localStorage.p_J_k`;刷新 token header 默认名是 `RT`。
390
406
  - `getAuthToken()` 是同步函数,读取前无需 `await`。
@@ -413,6 +429,12 @@ const AdminApp = ({ children }: { children: ReactNode }) => (
413
429
 
414
430
  initApp(AdminApp, {
415
431
  initModeData: false,
432
+ beforeRender: async () => {
433
+ // 注册请求拦截器等渲染前初始化逻辑
434
+ },
435
+ afterRender: () => {
436
+ // 首次渲染提交后的逻辑
437
+ },
416
438
  })
417
439
  ```
418
440
 
@@ -586,9 +608,11 @@ chokidar
586
608
 
587
609
  1. SchemaForm 字段类型、CallCom 组件、Koa app 类型都支持 TypeScript 声明合并。
588
610
  2. 运行时组件通常放在业务 `frontend/extended/`。
589
- 3. Dashboard 扩展点包括 `frontend/extended/dash/customRoutes.tsx`、`frontend/extended/dash/components.tsx`、`frontend/extended/dash/routeGuard.ts`。
590
- 4. `dash/routeGuard` 用来判断登录。返回登录页 URL 字符串时,Dashboard `window.location.href` 跳转;`DashRouteGuardResult` 是 `string | undefined`,`undefined`(包括已登录分支的隐式返回)会打断 Dashboard 内置重定向,业务方可自行跳转。
591
- 5. 优先查看公开入口 `.d.ts` npm 包根目录 `README.md`;如果当前环境是源码仓库,再继续读 `docs/type-extension.md` `docs/schema-form.md`。
611
+ 3. Dashboard 扩展点包括 `frontend/extended/dash/init.ts`、`frontend/extended/dash/customRoutes.tsx`、`frontend/extended/dash/components.tsx`、`frontend/extended/dash/routeGuard.ts`。
612
+ 4. `dash/init` React 渲染前执行,适合注册 axios 拦截器等全局初始化逻辑;类型是 `DashInit`。
613
+ 5. `dash/routeGuard` 用来判断登录。返回登录页 URL 字符串时,Dashboard `window.location.href` 跳转;`DashRouteGuardResult``string | undefined`,`undefined`(包括已登录分支的隐式返回)会打断 Dashboard 内置重定向,业务方可自行跳转。
614
+ 6. 自定义入口的 `initApp` 支持 `beforeRender` / `afterRender`,分别在 React 渲染前和首次渲染提交后触发。
615
+ 7. 优先查看公开入口 `.d.ts` 和 npm 包根目录 `README.md`;如果当前环境是源码仓库,再继续读 `docs/type-extension.md` 和 `docs/schema-form.md`。
592
616
 
593
617
  ## 8. 继续深入
594
618
 
package/README.md CHANGED
@@ -189,7 +189,7 @@ http://localhost:9000/dash?projk=demo
189
189
  | `Ctx` | Controller、middleware、router handler 的 Koa 请求上下文类型,包含框架补充的 `ctx.params` 和可选 `ctx.reqData`。 |
190
190
  | `ControllerFN` | `app/controller/*.ts` 默认导出工厂函数的约束类型,适合配合 `satisfies ControllerFN` 保留 controller 实例方法类型。 |
191
191
  | `ServiceFN` | `app/service/*.ts` 默认导出工厂函数的约束类型,适合配合 `satisfies ServiceFN` 约束 service class 工厂。 |
192
- | `MiddlewareFN` | `app/middleware/*.ts` 默认导出工厂函数的约束类型,返回标准 Koa middleware。 |
192
+ | `MiddlewareFN` | `app/middlewares/*.ts` 默认导出工厂函数的约束类型,返回标准 Koa middleware。 |
193
193
  | `RouterFN` | `app/router/*.ts` 默认导出函数的约束类型,用于接收 `app` 和 `router` 并注册路由。 |
194
194
 
195
195
  示例:
@@ -301,6 +301,7 @@ VS Code 建议安装官方 Tailwind CSS IntelliSense 插件,并在工作区 `.
301
301
 
302
302
  ```ts
303
303
  import {
304
+ api,
304
305
  apiFreezerStore,
305
306
  AsyncSelect,
306
307
  eventsInfo,
@@ -320,6 +321,7 @@ import {
320
321
  } from '@_tc/template-core/fe'
321
322
 
322
323
  import type {
324
+ BaseResponse,
323
325
  CallComComponentsMap,
324
326
  CallComRenderer,
325
327
  DashComponentsMap,
@@ -327,6 +329,10 @@ import type {
327
329
  DashRoutesExtender,
328
330
  FormFieldSchema,
329
331
  ApiFreezerRequestMatcher,
332
+ PageParams,
333
+ PageResponse,
334
+ RequestConfig,
335
+ ResponseConfig,
330
336
  SchemaFormComponentsMap,
331
337
  SchemaFormNamespace,
332
338
  SelectProps,
@@ -342,13 +348,16 @@ import type {
342
348
  | Dash 扩展类型 | `DashRoutesExtender`、`DashRoutesContext`、`DashComponentsMap`、`DashHeaderUserAreaProps`、`DashRouteGuard` |
343
349
  | SchemaPage 类型 | `CallComComponentsMap`、`CallComRenderer` |
344
350
  | SchemaForm 类型 | `FormFieldSchema`、`SchemaFormComponentsMap`、`SchemaFormNamespace`、`SelectProps` |
345
- | 请求方法 | `request`、`get`、`post`、`put`、`patch`、`del` |
351
+ | 请求方法 | `api`、`request`、`get`、`post`、`put`、`patch`、`del` |
352
+ | 请求类型 | `BaseResponse`、`PageParams`、`PageResponse`、`RequestConfig`、`ResponseConfig`、`AxiosError` |
346
353
  | 请求冻结 | `apiFreezerStore`、`useApiFreezer`、`FreezeState`、`ApiFreezerRequestMatcher` |
347
354
  | 事件工具 | `eventsInfo`、`merge` |
348
355
  | 共享状态 | `modeStore`、`useModeStore`、`schemaStore`、`useSchemaStore`、`schemaEventBus` |
349
356
 
350
357
  `modeStore` 管项目模型和当前项目数据,`schemaStore` 管当前 Schema 页面的 schema/cache,`schemaEventBus` 用来在 Search、Table、CallCom 之间通信。
351
358
 
359
+ 只使用请求层时,也可以从 `@_tc/template-core/fe/common/request` 导入 `api`、请求方法和请求类型;这是发布包显式子路径,不要下钻到 `frontend/src/common/request`。
360
+
352
361
  ### 前端多语言
353
362
 
354
363
  TemplateCore 前端通过 `@_tc/template-core/fe` 暴露 i18n 能力。框架内置中文、英文资源,项目可以继续追加自己的资源。
@@ -538,7 +547,8 @@ export function Toolbar() {
538
547
  **使用示例**:
539
548
 
540
549
  ```ts
541
- import { get, post, put, del } from '@_tc/template-core/fe'
550
+ import { api, get, post, put, del } from '@_tc/template-core/fe'
551
+ import type { BaseResponse, RequestConfig, ResponseConfig } from '@_tc/template-core/fe'
542
552
 
543
553
  // GET 请求
544
554
  const data = await get('/product/list', { page: 1, pageSize: 10 })
@@ -553,6 +563,22 @@ await put('/product', { id: 1, name: '新名称' })
553
563
  await del('/product', { id: 1 })
554
564
  ```
555
565
 
566
+ 如果希望把请求能力单独拆出来导入:
567
+
568
+ ```ts
569
+ import {
570
+ api,
571
+ get,
572
+ post,
573
+ request,
574
+ } from '@_tc/template-core/fe/common/request'
575
+ import type {
576
+ BaseResponse,
577
+ RequestConfig,
578
+ ResponseConfig,
579
+ } from '@_tc/template-core/fe/common/request'
580
+ ```
581
+
556
582
  **请求冻结器**:
557
583
 
558
584
  请求冻结器用于在某段流程中暂停普通 API 请求,典型场景是 RT 刷新 token:刷新期间冻结其它请求,刷新接口本身走白名单放行,刷新完成后再解冻并回放队列。
@@ -708,7 +734,7 @@ function MyComponent() {
708
734
  ```text
709
735
  app/controller/**/*.(js|ts) -> app.controller
710
736
  app/service/**/*.(js|ts) -> app.service
711
- app/middleware/**/*.(js|ts) -> app.middlewares
737
+ app/middlewares/**/*.(js|ts) -> app.middlewares
712
738
  app/middleware.(js|ts) -> 全局中间件编排,按框架 -> 项目顺序执行
713
739
  app/router/**/*.(js|ts) -> Koa router
714
740
  app/router-schema/**/*.(js|ts) -> app.routerSchema
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.js`),t=require(`../packages/utils/path.js`);require(`../packages/utils/index.js`);const n=require(`../packages/common/log/index.js`),r=require(`./state.js`);let i=require(`path`);i=e.__toESM(i);let a=require(`glob`);a=e.__toESM(a);let o=require(`fs`),s=require(`vite`),c=require(`@tailwindcss/vite`);c=e.__toESM(c);let l=require(`module`),u=require(`url`);var d=typeof __filename==`string`?__filename:(0,u.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,l.createRequire)(d),h=i.default.resolve(f,`../../`),g=i.default.resolve(f,`../`),_=i.default.resolve(h,`./fe/frontend`),v=i.default.resolve(p,`.`),y=i.default.resolve(v,`./frontend`),b=e=>i.default.dirname(m.resolve(`${e}/package.json`)),x=i.default.join(v,`.tc-entry-html`),S=`<!-- {{InsertScript}} -->`,C={head:`<!-- TC_HEAD_SLOT -->`,bodyBeforeRoot:`<!-- TC_BODY_BEFORE_ROOT_SLOT -->`,bodyAfterRoot:`<!-- TC_BODY_AFTER_ROOT_SLOT -->`},w=[`head`,`body-before-root`,`body-after-root`],T=()=>({head:``,"body-before-root":``,"body-after-root":``}),E=e=>e.split(i.default.sep).join(`/`),D=e=>{let t=E(e);return t.startsWith(`.`)||t.startsWith(`/`)?t:`./${t}`},O=e=>/<html[\s>]/i.test(e)&&e.includes(`window['_basePath']`)&&/<div\s+id=["']root["']\s*><\/div>/i.test(e),k=e=>{let t=T(),r=(0,o.readFileSync)(e,`utf-8`),i=/<!--\s*tc-slot:([\w-]+)\s*-->([\s\S]*?)<!--\s*\/tc-slot:\1\s*-->/g,a=!1,s=r;if(r.replace(i,(n,r,i)=>{if(a=!0,!w.includes(r))throw Error(`Invalid html slot "${r}" in ${e}. Allowed slots: ${w.join(`, `)}.`);return t[r]+=i.trim()?`${i}\n`:``,s=s.replace(n,``),n}),s.trim()){if(O(r))return n.logYellow(`ignored legacy generated entry html: ${e}`),T();throw Error([`Invalid custom entry html: ${e}.`,`Custom entry html can only contain tc-slot blocks, for example:`,`<!-- tc-slot:head -->`,`<meta name="description" content="..." />`,`<!-- /tc-slot:head -->`].join(`
2
2
  `))}return a&&n.logYellow(`using custom entry html slots: ${e}`),t},A=(e,n)=>{let r=i.default.basename(e),s=[i.default.join(e,`${n}.html`),i.default.join(e,`${r}.html`)].find(e=>(0,o.existsSync)(e));if(s)return s;if(a.sync(t.resolve(e,`*.html`)).length)throw Error([`Entry html filename is not supported in ${e}.`,`Use ${n}.html or ${r}.html as a tc-slot file.`].join(`
3
- `))},j=(e,t)=>e.replace(C.head,t.head).replace(C.bodyBeforeRoot,t[`body-before-root`]).replace(C.bodyAfterRoot,t[`body-after-root`]),M=(e,t,n,r)=>{let a=i.default.join(x,`${n}.html`),s=D(i.default.relative(i.default.dirname(a),t));return(0,o.writeFileSync)(a,j(e,r).replace(S,`<script type="module" src="${s}"><\/script>`)),a};function N(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;(0,o.rmSync)(x,{recursive:!0,force:!0}),(0,o.mkdirSync)(x,{recursive:!0});let s=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);for(let t=0;t<e.length;t++){let n=e[t],a=i.default.resolve(n,`../`),o=A(a,i.default.basename(n).replace(/\.entry\.[^.]+$/,``)),c=o?i.default.basename(o,`.html`):a.split(i.default.sep).at(-1);r[c]=M(s,n,c,o?k(o):T())}return r}function P(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var F=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{if(!r){r=!0,n.logYellow(`changed: ${t}`);try{await e?.()}catch(e){n.logRed(e)}finally{r=!1}}}),n.logYellow(`watching ${y}/...`)};function I(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=P();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function L(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y,s=`./defaultAlias`])=>{let c=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),l=i.default.resolve(f,s);return c&&(l=c),e[n]=l,e},{})}var R=(...e)=>new RegExp(e.join(`[\\/]`)),z=e=>`_${e}_tcC`,B=async(e,t)=>{try{await(0,s.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),I([`.tpl`,`.html`]),(0,c.default)()],build:{outDir:P(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:z(`__nm`),test:R(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:z(`r`),test:R(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:z(`r-d`),test:R(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`r-r-d`),test:R(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:z(`ajv`),test:R(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`ui-lib`),test:R(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:z(`ui-f-lib`),test:R(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react/hooks":i.default.resolve(f,`../../fe/packages/react/hooks/`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/react/ui/`),...L([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`],[`@tc/scalability/dash/components`,`./extended/dash/components`],[`@tc/scalability/dash/routeGuard`,`./extended/dash/routeGuard`,void 0,`./defaultRouteGuard`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){if(n.logRed(e),t!==`dev`)throw e}};exports.VBuildFE=B,exports.entries=N,exports.outDir=P,exports.watchFiles=F;
3
+ `))},j=(e,t)=>e.replace(C.head,t.head).replace(C.bodyBeforeRoot,t[`body-before-root`]).replace(C.bodyAfterRoot,t[`body-after-root`]),M=(e,t,n,r)=>{let a=i.default.join(x,`${n}.html`),s=D(i.default.relative(i.default.dirname(a),t));return(0,o.writeFileSync)(a,j(e,r).replace(S,`<script type="module" src="${s}"><\/script>`)),a};function N(){n.logWhite(`frameFEPath: `+_),n.logWhite(`runFEPath: `+y);let e=[_,y].reduce((e,n)=>{let r=[`js`,`jsx`,`ts`,`tsx`].map(e=>t.resolve(n,`/**`,`/*.`+e)),i=a.sync(r).filter(e=>e.includes(`.entry.`)).filter(e=>!e.includes(`.d.ts`));return[...e,...i]},[]),r={};if(!e.length)return r;(0,o.rmSync)(x,{recursive:!0,force:!0}),(0,o.mkdirSync)(x,{recursive:!0});let s=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);for(let t=0;t<e.length;t++){let n=e[t],a=i.default.resolve(n,`../`),o=A(a,i.default.basename(n).replace(/\.entry\.[^.]+$/,``)),c=o?i.default.basename(o,`.html`):a.split(i.default.sep).at(-1);r[c]=M(s,n,c,o?k(o):T())}return r}function P(){let e=r.buildFESharedState.options.output??`frame`;return e!==`frame`&&n.logRed(`Please pay attention to the build output.`),e===`run`?i.default.join(v,`./app/public/dist`):typeof e==`function`?e():i.default.join(g,`./app/public/dist`)}var F=async e=>{let t=(await import(`chokidar`)).watch(y,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),r=!1;t.on(`change`,async t=>{if(!r){r=!0,n.logYellow(`changed: ${t}`);try{await e?.()}catch(e){n.logRed(e)}finally{r=!1}}}),n.logYellow(`watching ${y}/...`)};function I(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=P();if(!(0,o.existsSync)(e))return;let r=a.sync(`**/*.html`,{cwd:e,dot:!0});for(let n of r){let r=i.default.join(e,n),a=i.default.basename(n,`.html`),s=`.entry`;a.indexOf(s)===-1&&(a+=s);for(let n of t){let t=a+n,s=i.default.join(e,t);r!==s&&(0,o.copyFileSync)(r,s)}t.some(t=>r===i.default.join(e,a+t))||(0,o.unlinkSync)(r)}let s=[...new Set(r.map(e=>i.default.dirname(e)))].sort().reverse(),c=e=>{if(!((0,o.existsSync)(e)?(0,o.readdirSync)(e):[1]).length){(0,o.rmdirSync)(e);let t=e.split(i.default.sep).slice(0,-1);t.length&&c(t.join(i.default.sep))}};for(let t of s)t&&t!==`.`&&c(i.default.join(e,t));let l=t.flatMap(t=>a.sync(`**/*`+t,{cwd:e,dot:!0}));for(let e of l)n.logYellow(`final file path -----> `+e)}}}function L(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y,s=`./defaultAlias`])=>{let c=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),l=i.default.resolve(f,s);return c&&(l=c),e[n]=l,e},{})}var R=(...e)=>new RegExp(e.join(`[\\/]`)),z=e=>`_${e}_tcC`,B=async(e,t)=>{try{await(0,s.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),I([`.tpl`,`.html`]),(0,c.default)()],build:{outDir:P(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:z(`__nm`),test:R(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:z(`r`),test:R(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:z(`r-d`),test:R(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`r-r-d`),test:R(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:z(`ajv`),test:R(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:z(`ui-lib`),test:R(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:z(`ui-f-lib`),test:R(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react/hooks":i.default.resolve(f,`../../fe/packages/react/hooks/`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/react/ui/`),...L([[`@tc/scalability/SchemaForm/frameData`,`./extended/SchemaForm/data`,_],[`@tc/scalability/SchemaForm/data`,`./extended/SchemaForm/data`],[`@tc/scalability/SchemaPage/CallCom/data`,`./extended/SchemaPage/CallCom/data`],[`@tc/scalability/dash/customRoutes`,`./extended/dash/customRoutes`],[`@tc/scalability/dash/init`,`./extended/dash/init`],[`@tc/scalability/dash/components`,`./extended/dash/components`],[`@tc/scalability/dash/routeGuard`,`./extended/dash/routeGuard`,void 0,`./defaultRouteGuard`]])}}}),n.logJoinColorized(n.colorize(`builded`,`green`),n.colorize(` -> `,`white`),n.colorize(`time: `,`cyan`),n.colorize(new Date,`pink`))}catch(e){if(n.logRed(e),t!==`dev`)throw e}};exports.VBuildFE=B,exports.entries=N,exports.outDir=P,exports.watchFiles=F;
@@ -228,6 +228,7 @@ var Y = (...e) => new RegExp(e.join("[\\/]")), X = (e) => `_${e}_tcC`, Z = async
228
228
  ["@tc/scalability/SchemaForm/data", "./extended/SchemaForm/data"],
229
229
  ["@tc/scalability/SchemaPage/CallCom/data", "./extended/SchemaPage/CallCom/data"],
230
230
  ["@tc/scalability/dash/customRoutes", "./extended/dash/customRoutes"],
231
+ ["@tc/scalability/dash/init", "./extended/dash/init"],
231
232
  ["@tc/scalability/dash/components", "./extended/dash/components"],
232
233
  [
233
234
  "@tc/scalability/dash/routeGuard",
@@ -50,5 +50,10 @@ var getDefaultPageRoutes = () => {
50
50
  initApp(({ children }) => /* @__PURE__ */ jsx("div", {
51
51
  className: "dash",
52
52
  children
53
- }), { routes: getDefaultPageRoutes() });
53
+ }), {
54
+ routes: getDefaultPageRoutes(),
55
+ beforeRender: async (context) => {
56
+ if (typeof defaultAlias_default === "function") await defaultAlias_default(context);
57
+ }
58
+ });
54
59
  //#endregion
@@ -1,11 +1,13 @@
1
1
  import type { ProjectInfo } from "../../src/api/baseInfo";
2
2
  import type { RouteInfo } from "../../src/components/Router";
3
+ import type { InitAppHook } from "../../src/main";
3
4
  import type { ComponentType, LazyExoticComponent } from "react";
4
5
  export type DashRoutesContext = {
5
6
  topRoutes: RouteInfo[];
6
7
  sidebarRoutes: RouteInfo[];
7
8
  };
8
9
  export type DashRoutesExtender = (context: DashRoutesContext) => void;
10
+ export type DashInit = InitAppHook;
9
11
  export type DashHeaderUserAreaProps = {
10
12
  projectInfo: ProjectInfo | null;
11
13
  };
@@ -4,7 +4,8 @@ export * from "./main";
4
4
  export type { CallComComponentsMap, CallComRenderer } from "./defaultPages/SchemaPage/schemaType";
5
5
  export * from "./components/index";
6
6
  export type { FormFieldSchema, SchemaFormComponentsMap, SchemaFormNamespace, SelectProps } from "../../packages/react/ui/index";
7
- export { del, get, patch, post, put, request } from "./common/request";
7
+ export { api, del, get, patch, post, put, request } from "./common/request";
8
+ export type { AxiosError, BaseResponse, PageParams, PageResponse, RequestConfig, ResponseConfig } from "./common/request";
8
9
  export * from "./defaultPages/SchemaPage/data/eventInfo";
9
10
  export { merge } from "./defaultPages/SchemaPage/utils/permissions";
10
11
  export * from "./exportStore";
@@ -2,7 +2,7 @@ import { getAuthToken, localKeyMap, setAuthToken } from "./common/auth/index.js"
2
2
  import { i18n, i18nStore } from "../../packages/common/i18n/index.js";
3
3
  import "../../packages/react/ui/i18n/index.js";
4
4
  import { FreezeState, apiFreezerStore, useApiFreezer } from "./stores/apiFreezer.js";
5
- import { del, get, patch, post, put, request } from "./common/request.js";
5
+ import { api, del, get, patch, post, put, request } from "./common/request.js";
6
6
  import { modeStore, useModeStore } from "./stores/mode.js";
7
7
  import HeaderView from "./components/BasePage/HeaderView.js";
8
8
  import { generateRouter } from "./components/Router/index.js";
@@ -17,4 +17,4 @@ import ThemeSwitch_default from "./components/ThemeSwitch/index.js";
17
17
  import "./components/index.js";
18
18
  import { eventsInfo } from "./defaultPages/SchemaPage/data/eventInfo.js";
19
19
  import { merge } from "./defaultPages/SchemaPage/utils/permissions.js";
20
- export { AsyncSelect_default as AsyncSelect, FreezeState, HeaderView, LanguageSwitch_default as LanguageSwitch, ThemeSwitch_default as ThemeSwitch, apiFreezerStore, del, eventsInfo, generateRouter, get, getAuthToken, i18n, i18nStore, initApp, localKeyMap, merge, modeStore, patch, post, put, request, schemaEventBus, schemaStore, setAuthToken, themeSwitchStorageKey, useApiFreezer, useModeStore, useSchemaStore };
20
+ export { AsyncSelect_default as AsyncSelect, FreezeState, HeaderView, LanguageSwitch_default as LanguageSwitch, ThemeSwitch_default as ThemeSwitch, api, apiFreezerStore, del, eventsInfo, generateRouter, get, getAuthToken, i18n, i18nStore, initApp, localKeyMap, merge, modeStore, patch, post, put, request, schemaEventBus, schemaStore, setAuthToken, themeSwitchStorageKey, useApiFreezer, useModeStore, useSchemaStore };
@@ -4,11 +4,17 @@ import { RouterType } from "./components/Router/type";
4
4
  import { type ReactNode } from "react";
5
5
  import "./main.css";
6
6
  import "./typing/window";
7
+ export type InitAppHookContext = {
8
+ rootElement: HTMLElement;
9
+ };
10
+ export type InitAppHook = (context: InitAppHookContext) => void | Promise<void>;
7
11
  export declare const initApp: (RootComponent: (props: {
8
12
  children: Exclude<ReactNode, null | undefined>;
9
13
  }) => JSX.Element, options?: {
10
14
  routes?: RouteInfo[];
11
15
  routerType?: RouterType;
12
16
  modelType?: string;
17
+ beforeRender?: InitAppHook;
18
+ afterRender?: InitAppHook;
13
19
  initModeData?: boolean;
14
20
  }) => void;
@@ -4,7 +4,8 @@ import { renderImportComponent } from "./common/importComponent.js";
4
4
  import { generateRouter } from "./components/Router/index.js";
5
5
  import "./exportStore.js";
6
6
  /* empty css */
7
- import { Fragment, jsx } from "react/jsx-runtime";
7
+ import { useEffect } from "react";
8
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
8
9
  import { createRoot } from "react-dom/client";
9
10
  //#region frontend/src/main.tsx
10
11
  var renderRouter = async ({ routerDom, requestModelData }) => {
@@ -40,21 +41,37 @@ var renderRouter = async ({ routerDom, requestModelData }) => {
40
41
  }, fail);
41
42
  });
42
43
  }
43
- return { default: () => /* @__PURE__ */ jsx(Fragment, { children: routerDom }) };
44
+ return { default: () => /* @__PURE__ */ jsx(Fragment$1, { children: routerDom }) };
45
+ };
46
+ var AppLifecycle = ({ children, hookContext, afterRender }) => {
47
+ useEffect(() => {
48
+ afterRender?.(hookContext);
49
+ }, [afterRender, hookContext]);
50
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
44
51
  };
45
52
  var initApp = (RootComponent, options) => {
46
53
  console.log("initApp");
47
- const root = createRoot(document.getElementById("root"));
48
- const { routerType = "browser", routes: pRoutes = [], initModeData = true, modelType = "MD" } = options ?? {};
49
- const routes = [...pRoutes];
50
- const usedRouter = !!routes.length;
51
- let routerDom;
52
- if (usedRouter) routerDom = generateRouter(routes, routerType);
53
- const Component = renderImportComponent(renderRouter({
54
- routerDom,
55
- requestModelData: initModeData || modelType === "MD"
56
- }));
57
- root.render(/* @__PURE__ */ jsx(RootComponent, { children: Component }));
54
+ const bootstrap = async () => {
55
+ const rootElement = document.getElementById("root");
56
+ const root = createRoot(rootElement);
57
+ const { routerType = "browser", routes: pRoutes = [], initModeData = true, modelType = "MD", beforeRender, afterRender } = options ?? {};
58
+ const routes = [...pRoutes];
59
+ const usedRouter = !!routes.length;
60
+ let routerDom;
61
+ if (usedRouter) routerDom = generateRouter(routes, routerType);
62
+ const hookContext = { rootElement };
63
+ await beforeRender?.(hookContext);
64
+ const Component = renderImportComponent(renderRouter({
65
+ routerDom,
66
+ requestModelData: initModeData || modelType === "MD"
67
+ }));
68
+ root.render(/* @__PURE__ */ jsx(AppLifecycle, {
69
+ hookContext,
70
+ afterRender,
71
+ children: /* @__PURE__ */ jsx(RootComponent, { children: Component })
72
+ }));
73
+ };
74
+ bootstrap();
58
75
  };
59
76
  //#endregion
60
77
  export { initApp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_tc/template-core",
3
- "version": "0.0.1-bate.59",
3
+ "version": "0.0.1-bate.60",
4
4
  "description": "A full-stack TypeScript admin framework powered by Koa, React, and Vite - monorepo root",
5
5
  "types": "./types/index.d.ts",
6
6
  "exports": {
@@ -24,6 +24,11 @@
24
24
  "require": "./fe/frontend/src/index.js",
25
25
  "types": "./fe/frontend/src/index.d.ts"
26
26
  },
27
+ "./fe/common/request": {
28
+ "import": "./fe/frontend/src/common/request.js",
29
+ "require": "./fe/frontend/src/common/request.js",
30
+ "types": "./fe/frontend/src/common/request.d.ts"
31
+ },
27
32
  "./fe/rc": {
28
33
  "import": "./fe/packages/react/ui/index.js",
29
34
  "require": "./fe/packages/react/ui/index.js",
@@ -5,7 +5,7 @@ import type { KoaApp } from "../types";
5
5
  *
6
6
  * @param app
7
7
  *
8
- * 加载 xx/app/middleware 文件夹下所有文件,可通过 'app.middleware.${目录}.${文件}' 访问
8
+ * 加载 xx/app/middlewares 文件夹下所有文件,可通过 'app.middlewares.${目录}.${文件}' 访问
9
9
  */
10
10
  declare const _default: (app: KoaApp) => void;
11
11
  export default _default;