@_tc/template-core 0.1.12 → 0.1.14
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.
- package/.skills/tc-component-usage-skills/SKILL.md +31 -9
- package/.skills/tc-component-usage-skills/reference/component-api.md +63 -42
- package/.skills/tc-component-usage-skills/reference/examples.md +34 -24
- package/.skills/tc-component-usage-skills/reference/patterns.md +52 -17
- package/.skills/tc-component-usage-skills/reference/template-core-frontend.md +104 -0
- package/.skills/tc-generator/SKILL.md +11 -3
- package/.skills/tc-generator/reference/example.md +1 -1
- package/.skills/tc-generator/reference/model-schema.md +4 -2
- package/.skills/tc-generator/reference/project-template/config/config.default.js +4 -0
- package/.skills/tc-generator/reference/project-template/index.js +1 -1
- package/.skills/tc-generator/reference/project-template/model/product/project/default.js +1 -0
- package/.skills/tc-generator/reference/project-template/package.json +16 -1
- package/.skills/tc-generator/reference/runtime-extensions.md +190 -0
- package/README.md +217 -2
- package/cjs/app/extend/db.js +20 -1
- package/cjs/bundler/utils.js +1 -1
- package/esm/app/extend/db.js +138 -3
- package/esm/bundler/utils.js +2 -2
- package/fe/frontend/apps/dash/Dashboard.js +3 -1
- package/fe/frontend/apps/dash/types.d.ts +8 -0
- package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.d.ts +14 -0
- package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.js +32 -0
- package/fe/frontend/src/components/LanguageSwitch/index.d.ts +4 -0
- package/fe/frontend/src/components/LanguageSwitch/index.js +3 -0
- package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.d.ts +15 -0
- package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.js +59 -0
- package/fe/frontend/src/components/ThemeSwitch/index.d.ts +4 -0
- package/fe/frontend/src/components/ThemeSwitch/index.js +3 -0
- package/fe/frontend/src/components/index.d.ts +4 -0
- package/fe/frontend/src/components/index.js +3 -0
- package/fe/frontend/src/index.d.ts +1 -0
- package/fe/frontend/src/index.js +1 -0
- package/fe/frontend/src/language/en-US.d.ts +13 -0
- package/fe/frontend/src/language/en-US.js +13 -0
- package/fe/frontend/src/language/index.d.ts +7 -0
- package/fe/frontend/src/language/index.js +7 -0
- package/fe/frontend/src/language/zh-CN.d.ts +13 -0
- package/fe/frontend/src/language/zh-CN.js +13 -0
- package/fe/packages/ui/react/components/Button/{SumbitButton.d.ts → SubmitButton.d.ts} +5 -5
- package/fe/packages/ui/react/components/Button/{SumbitButton.js → SubmitButton.js} +3 -3
- package/fe/packages/ui/react/components/Button/index.d.ts +1 -1
- package/fe/packages/ui/react/components/Button/index.js +1 -1
- package/fe/packages/ui/react/components/Modal/ModalManager.d.ts +3 -3
- package/fe/packages/ui/react/components/TableSearch/index.d.ts +1 -0
- package/package.json +3 -3
- package/types/app/extend/db.d.ts +54 -1
- package/types/config/config.default.d.ts +30 -0
- package/types/index.d.ts +1 -0
- package/cjs/bundler/modulepreload-polyfill.js +0 -1
- package/esm/bundler/modulepreload-polyfill.js +0 -11
- package/types/bundler/modulepreload-polyfill.d.ts +0 -1
package/README.md
CHANGED
|
@@ -157,7 +157,7 @@ http://localhost:9000/dash?projk=demo
|
|
|
157
157
|
| --- | --- |
|
|
158
158
|
| `@_tc/template-core` | 服务启动、基础 Controller/Service、Koa 类型 |
|
|
159
159
|
| `@_tc/template-core/bundler` | 前端构建函数 `buildFE` |
|
|
160
|
-
| `@_tc/template-core/fe` | 前端初始化、Dash 路由扩展、请求方法、SchemaPage
|
|
160
|
+
| `@_tc/template-core/fe` | 前端初始化、Dash 路由扩展、请求方法、SchemaPage 事件、共享状态和内置前端组件 |
|
|
161
161
|
| `@_tc/template-core/fe/rc` | UI/SchemaForm 组件和类型 |
|
|
162
162
|
| `@_tc/template-core/fe/tailwind_ui.css` | 前端全局样式入口,源码对应 `frontend/src/main.css` |
|
|
163
163
|
| `@_tc/template-core/model` | model 配置类型 |
|
|
@@ -241,15 +241,18 @@ VS Code 建议安装官方 Tailwind CSS IntelliSense 插件,并在工作区 `.
|
|
|
241
241
|
```ts
|
|
242
242
|
import {
|
|
243
243
|
apiFreezerStore,
|
|
244
|
+
AsyncSelect,
|
|
244
245
|
eventsInfo,
|
|
245
246
|
FreezeState,
|
|
246
247
|
get,
|
|
248
|
+
LanguageSwitch,
|
|
247
249
|
merge,
|
|
248
250
|
modeStore,
|
|
249
251
|
post,
|
|
250
252
|
request,
|
|
251
253
|
schemaEventBus,
|
|
252
254
|
schemaStore,
|
|
255
|
+
ThemeSwitch,
|
|
253
256
|
useApiFreezer,
|
|
254
257
|
useModeStore,
|
|
255
258
|
useSchemaStore,
|
|
@@ -258,6 +261,7 @@ import {
|
|
|
258
261
|
import type {
|
|
259
262
|
CallComComponentsMap,
|
|
260
263
|
CallComRenderer,
|
|
264
|
+
DashComponentsMap,
|
|
261
265
|
DashRoutesExtender,
|
|
262
266
|
FormFieldSchema,
|
|
263
267
|
ApiFreezerRequestMatcher,
|
|
@@ -272,7 +276,8 @@ import type {
|
|
|
272
276
|
| 类型 | 导出内容 |
|
|
273
277
|
| --- | --- |
|
|
274
278
|
| 应用启动 | `initApp` |
|
|
275
|
-
|
|
|
279
|
+
| 内置前端组件 | `AsyncSelect`、`LanguageSwitch`、`ThemeSwitch` |
|
|
280
|
+
| Dash 扩展类型 | `DashRoutesExtender`、`DashRoutesContext`、`DashComponentsMap`、`DashHeaderUserAreaProps` |
|
|
276
281
|
| SchemaPage 类型 | `CallComComponentsMap`、`CallComRenderer` |
|
|
277
282
|
| SchemaForm 类型 | `FormFieldSchema`、`SchemaFormComponentsMap`、`SchemaFormNamespace`、`SelectProps` |
|
|
278
283
|
| 请求方法 | `request`、`get`、`post`、`put`、`patch`、`del` |
|
|
@@ -383,6 +388,18 @@ i18nStore.getState().setLanguage('zh-CN')
|
|
|
383
388
|
|
|
384
389
|
默认会写入 localStorage,key 是 `tc_language`。刷新页面后会继续使用上次语言。
|
|
385
390
|
|
|
391
|
+
项目页面可以直接使用语言选择组件:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import { LanguageSwitch } from '@_tc/template-core/fe'
|
|
395
|
+
|
|
396
|
+
export function Toolbar() {
|
|
397
|
+
return <LanguageSwitch />
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`LanguageSwitch` 默认提供 `zh-CN` 和 `en-US`,也支持传入 `options` 覆盖语言列表。组件内部会调用 `i18nStore.getState().setLanguage()`,因此会沿用 `tc_language` 的持久化行为。
|
|
402
|
+
|
|
386
403
|
**AJV 校验文案**:
|
|
387
404
|
|
|
388
405
|
Schema 表单校验会把 AJV keyword 映射到内置语言 key,覆盖 `required`、`type`、`format`、`minimum`、`maximum`、`minLength`、`maxLength`、`pattern`、`enum`、`oneOf`、`anyOf` 等常见规则。
|
|
@@ -395,6 +412,30 @@ Schema 表单校验会把 AJV keyword 映射到内置语言 key,覆盖 `requir
|
|
|
395
412
|
2. `frontend/src/language/zh-CN.ts` 和 `frontend/src/language/en-US.ts` 文案。
|
|
396
413
|
3. `frontend/src/language/index.ts` 的 `frontendLangKeys`。
|
|
397
414
|
|
|
415
|
+
### 前端主题切换
|
|
416
|
+
|
|
417
|
+
项目页面可以使用 `ThemeSwitch` 切换浅色和深色模式:
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
import { ThemeSwitch } from '@_tc/template-core/fe'
|
|
421
|
+
|
|
422
|
+
export function Toolbar() {
|
|
423
|
+
return <ThemeSwitch />
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`ThemeSwitch` 会切换 `document.documentElement` 上的 `dark` class,并同步 `color-scheme`。默认写入 localStorage,key 是 `tc_theme`。刷新页面后会优先读取本地主题;没有本地主题时,读取当前根节点是否已有 `dark` class。
|
|
428
|
+
|
|
429
|
+
常用参数:
|
|
430
|
+
|
|
431
|
+
| 参数 | 说明 |
|
|
432
|
+
| --- | --- |
|
|
433
|
+
| `value` / `onChange` | 受控模式,值为 `light` 或 `dark`。 |
|
|
434
|
+
| `defaultValue` | 非受控初始主题。 |
|
|
435
|
+
| `showLabel` | 是否展示当前主题文本。 |
|
|
436
|
+
| `persist` | 是否写入 localStorage,默认 `true`。 |
|
|
437
|
+
| `storageKey` | 自定义本地存储 key,默认 `tc_theme`。 |
|
|
438
|
+
|
|
398
439
|
### 前端请求封装详解
|
|
399
440
|
|
|
400
441
|
框架内置的请求封装基于 `@tc/common/http`,底层使用原生 `fetch` API,实现了类似 Axios 的接口和拦截器机制,提供了签名、鉴权、错误处理等能力。
|
|
@@ -614,6 +655,159 @@ model/**/*.(js|ts) -> 项目模型配置
|
|
|
614
655
|
内置扩展:
|
|
615
656
|
|
|
616
657
|
- `app.extends.$fetch`:Node 侧基于 `fetch` 的 axios 风格请求实例,支持 `get/post/put/patch/delete` 和 `create(config)`。
|
|
658
|
+
- `app.extends.db`:默认 SQLite 框架数据库,提供 `getDBData/setDBData` 等通用数据方法。
|
|
659
|
+
|
|
660
|
+
### 数据库扩展
|
|
661
|
+
|
|
662
|
+
默认 `app.extends.db` 使用 SQLite 保存数据,数据库文件位于项目根目录 `.template-core/template-core.sqlite`。可以在配置中调整路径:
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
// config/config.default.ts
|
|
666
|
+
export default {
|
|
667
|
+
db: {
|
|
668
|
+
path: 'data/app.sqlite',
|
|
669
|
+
},
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Controller 中使用:
|
|
674
|
+
|
|
675
|
+
```ts
|
|
676
|
+
import { baseFn, type ControllerFN, type Ctx } from '@_tc/template-core'
|
|
677
|
+
|
|
678
|
+
const getSettingController = ((app) => {
|
|
679
|
+
const BaseController = baseFn.baseControllerFn(app)
|
|
680
|
+
|
|
681
|
+
return class SettingController extends BaseController {
|
|
682
|
+
detail = async (ctx: Ctx) => {
|
|
683
|
+
const data = app.extends.db.getDBData('site-setting', {
|
|
684
|
+
namespace: 'setting',
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
this.success(ctx, data ?? {})
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
save = async (ctx: Ctx) => {
|
|
691
|
+
app.extends.db.setDBData('site-setting', ctx.request.body, {
|
|
692
|
+
namespace: 'setting',
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
this.success(ctx, true)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}) satisfies ControllerFN
|
|
699
|
+
|
|
700
|
+
export default getSettingController
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
常用方法:
|
|
704
|
+
|
|
705
|
+
- `getDBData(key, options)`:读取 JSON 数据。
|
|
706
|
+
- `setDBData(key, value, options)`:写入 JSON 数据。
|
|
707
|
+
- `hasDBData(key, options)`:判断数据是否存在。
|
|
708
|
+
- `deleteDBData(key, options)`:删除单条数据。
|
|
709
|
+
- `listDBData(options)`:分页列出当前 namespace 下的数据。
|
|
710
|
+
- `countDBData(options)` / `clearDBData(options)`:统计和清空当前 namespace。
|
|
711
|
+
- `queryDB(sql, params)` / `runDB(sql, params)`:直接执行 SQLite SQL。
|
|
712
|
+
- `transactionDB(handler)`:事务执行。
|
|
713
|
+
- `closeDB()`:关闭连接。
|
|
714
|
+
|
|
715
|
+
SQL 注入边界:
|
|
716
|
+
|
|
717
|
+
- `getDBData`、`setDBData`、`deleteDBData`、`listDBData` 这类通用方法内部使用参数绑定,不需要手写 SQL。
|
|
718
|
+
- `queryDB`、`runDB`、`getDBFirst` 是原始 SQL 入口,用户输入必须放到 `params`,不要拼接到 SQL 字符串里。
|
|
719
|
+
- `execDB` 没有参数绑定能力,只建议用于固定 SQL,例如建表或迁移。
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
// 推荐:参数绑定
|
|
723
|
+
const users = app.extends.db.queryDB(
|
|
724
|
+
'SELECT * FROM user WHERE name = ?',
|
|
725
|
+
[ctx.query.name as string]
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
// 不推荐:拼接用户输入,有 SQL 注入风险
|
|
729
|
+
const unsafeUsers = app.extends.db.queryDB(
|
|
730
|
+
`SELECT * FROM user WHERE name = '${ctx.query.name}'`
|
|
731
|
+
)
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
业务项目可以用自己的 `app/extend/db.ts` 覆盖默认实现。建议保留同一组方法名,这样 controller/service 调用方不用调整。
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
// app/extend/db.ts
|
|
738
|
+
import type { DB, DBFactory } from '@_tc/template-core'
|
|
739
|
+
|
|
740
|
+
interface DataOptions {
|
|
741
|
+
namespace?: string
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const store = new Map<string, unknown>()
|
|
745
|
+
|
|
746
|
+
const getDB = ((_app) => {
|
|
747
|
+
const toKey = (key: string, namespace = 'framework') => `${namespace}:${key}`
|
|
748
|
+
|
|
749
|
+
const db = {
|
|
750
|
+
dbPath: 'memory',
|
|
751
|
+
getDBConnection: () => {
|
|
752
|
+
throw new Error('memory db 没有底层 SQLite connection')
|
|
753
|
+
},
|
|
754
|
+
execDB: () => undefined,
|
|
755
|
+
runDB: () => ({ changes: 0, lastInsertRowid: 0 }),
|
|
756
|
+
queryDB: <T extends object = Record<string, unknown>>() => [] as T[],
|
|
757
|
+
getDBFirst: <T extends object = Record<string, unknown>>() => undefined as T | undefined,
|
|
758
|
+
transactionDB: <T>(handler: (db: DB) => T) => handler(db),
|
|
759
|
+
getDBData: <T = unknown>(key: string, options?: DataOptions) => {
|
|
760
|
+
return store.get(toKey(key, options?.namespace)) as T | undefined
|
|
761
|
+
},
|
|
762
|
+
getDBDataRecord: <T = unknown>(key: string, options?: DataOptions) => {
|
|
763
|
+
const value = store.get(toKey(key, options?.namespace)) as T | undefined
|
|
764
|
+
if (value === undefined) return undefined
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
key,
|
|
768
|
+
value,
|
|
769
|
+
namespace: options?.namespace ?? 'framework',
|
|
770
|
+
createdAt: '',
|
|
771
|
+
updatedAt: '',
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
setDBData: <T = unknown>(key: string, value: T, options?: DataOptions) => {
|
|
775
|
+
store.set(toKey(key, options?.namespace), value)
|
|
776
|
+
return { changes: 1, lastInsertRowid: 0 }
|
|
777
|
+
},
|
|
778
|
+
hasDBData: (key: string, options?: DataOptions) => {
|
|
779
|
+
return store.has(toKey(key, options?.namespace))
|
|
780
|
+
},
|
|
781
|
+
deleteDBData: (key: string, options?: DataOptions) => {
|
|
782
|
+
return store.delete(toKey(key, options?.namespace))
|
|
783
|
+
},
|
|
784
|
+
listDBData: <T = unknown>(options?: DataOptions) => {
|
|
785
|
+
const namespace = options?.namespace ?? 'framework'
|
|
786
|
+
|
|
787
|
+
return Array.from(store.entries())
|
|
788
|
+
.filter(([key]) => key.startsWith(`${namespace}:`))
|
|
789
|
+
.map(([key, value]) => ({
|
|
790
|
+
key: key.slice(namespace.length + 1),
|
|
791
|
+
value: value as T,
|
|
792
|
+
namespace,
|
|
793
|
+
createdAt: '',
|
|
794
|
+
updatedAt: '',
|
|
795
|
+
}))
|
|
796
|
+
},
|
|
797
|
+
countDBData: () => store.size,
|
|
798
|
+
clearDBData: () => {
|
|
799
|
+
const size = store.size
|
|
800
|
+
store.clear()
|
|
801
|
+
return size
|
|
802
|
+
},
|
|
803
|
+
closeDB: () => undefined,
|
|
804
|
+
} satisfies DB
|
|
805
|
+
|
|
806
|
+
return db
|
|
807
|
+
}) satisfies DBFactory
|
|
808
|
+
|
|
809
|
+
export default getDB
|
|
810
|
+
```
|
|
617
811
|
|
|
618
812
|
Controller 示例:
|
|
619
813
|
|
|
@@ -926,6 +1120,27 @@ DELETE {api} -> 删除
|
|
|
926
1120
|
|
|
927
1121
|
## 如何扩展组件
|
|
928
1122
|
|
|
1123
|
+
### 扩展 Dashboard 顶部用户区域
|
|
1124
|
+
|
|
1125
|
+
业务项目可以创建 `frontend/extended/dash/components.tsx`,通过组件映射填充内置 Dashboard 页头右侧区域。
|
|
1126
|
+
|
|
1127
|
+
```tsx
|
|
1128
|
+
// frontend/extended/dash/components.tsx
|
|
1129
|
+
import type { DashComponentsMap } from '@_tc/template-core/fe'
|
|
1130
|
+
|
|
1131
|
+
const components = {
|
|
1132
|
+
'HeaderView.userArea': ({ projectInfo }) => {
|
|
1133
|
+
return <div>{projectInfo?.name}</div>
|
|
1134
|
+
},
|
|
1135
|
+
} satisfies DashComponentsMap
|
|
1136
|
+
|
|
1137
|
+
export default components
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
当前支持的 Dashboard 组件扩展槽位:
|
|
1141
|
+
|
|
1142
|
+
- `HeaderView.userArea`:Dashboard 顶部右侧区域。
|
|
1143
|
+
|
|
929
1144
|
### 扩展 SchemaForm 字段组件
|
|
930
1145
|
|
|
931
1146
|
1. 声明字段类型。
|
package/cjs/app/extend/db.js
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
|
|
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)
|
|
2
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3
|
+
ON CONFLICT(namespace, data_key) DO UPDATE SET
|
|
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
|
|
6
|
+
FROM tc_framework_data
|
|
7
|
+
WHERE namespace = ?
|
|
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/extend/db.ts。${y(e)}`)}if(!n.DatabaseSync)throw Error(`[db] ${t} 未提供 DatabaseSync,无法初始化默认 SQLite 数据库。`);return new n.DatabaseSync(e)}function o(e){e.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS tc_framework_data (
|
|
11
|
+
namespace TEXT NOT NULL,
|
|
12
|
+
data_key TEXT NOT NULL,
|
|
13
|
+
data_value TEXT NOT NULL,
|
|
14
|
+
created_at TEXT NOT NULL,
|
|
15
|
+
updated_at TEXT NOT NULL,
|
|
16
|
+
PRIMARY KEY (namespace, data_key)
|
|
17
|
+
);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_tc_framework_data_namespace_updated_at
|
|
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;
|
package/cjs/bundler/utils.js
CHANGED
|
@@ -1 +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(`@tailwindcss/vite`);s=e.__toESM(s);let c=require(`module`),l=require(`url`),u=require(`vite`);var d=typeof __filename==`string`?__filename:(0,l.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,c.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`));function x(){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;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){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 C=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 w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();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 T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y])=>{let s=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),c=i.default.resolve(f,`./defaultAlias`);return s&&(c=s),e[n]=c,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,u.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,s.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,
|
|
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(`@tailwindcss/vite`);s=e.__toESM(s);let c=require(`module`),l=require(`url`),u=require(`vite`);var d=typeof __filename==`string`?__filename:(0,l.fileURLToPath)({}.url),f=i.default.dirname(d),p=process.cwd(),m=(0,c.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`));function x(){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;let s;for(let n=0;n<e.length;n++){let c=e[n],l=i.default.resolve(c,`../`),u=a.sync(t.resolve(l,`*.html`))[0];if(u){let e=i.default.basename(u,`.html`);r[e]=i.default.resolve(p,u)}else{s||=(0,o.readFileSync)(i.default.resolve(g,`./app/view/entry.tpl`),`utf-8`);let e=c.split(i.default.sep).at(-1),t=s.replace(`<!-- {{InsertScript}} -->`,`<script type="module" src="./${e}"><\/script>`),n=l.split(i.default.sep).at(-1),a=i.default.join(l,n+`.html`);(0,o.writeFileSync)(a,t),r[n]=a}}return r}function S(){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 C=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 w(e=`.html`){let t=[...new Set(Array.isArray(e)?e:[e])];return{name:`flatten-html`,apply:`build`,closeBundle(){let e=S();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 T(e){let t=[``,`.ts`,`.tsx`,`.js`,`.jsx`];return e.reduce((e,[n,r,a=y])=>{let s=t.map(e=>i.default.resolve(a,r+e)).find(e=>(0,o.existsSync)(e)),c=i.default.resolve(f,`./defaultAlias`);return s&&(c=s),e[n]=c,e},{})}var E=(...e)=>new RegExp(e.join(`[\\/]`)),D=e=>`_${e}_tcC`,O=async(e,t)=>{try{await(0,u.build)({configFile:!1,base:`/dist`,plugins:[(await import(`@vitejs/plugin-react`)).default(),w([`.tpl`,`.html`]),(0,s.default)()],build:{outDir:S(),emptyOutDir:!0,sourcemap:t===`dev`?`inline`:`hidden`,rolldownOptions:{input:e,output:{codeSplitting:{groups:[{name:D(`__nm`),test:E(`node_modules`),maxSize:5e5,minShareCount:2,priority:1},{name:D(`r`),test:E(`node_modules`,`react`),minShareCount:2,maxSize:5e5,priority:5},{name:D(`r-d`),test:E(`node_modules`,`react-dom`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`r-r-d`),test:E(`node_modules`,`react-router-dom`),minShareCount:2,maxSize:5e5,priority:10},{name:D(`ajv`),test:E(`node_modules`,`ajv`),minShareCount:2,maxSize:2e5,priority:10},{name:D(`ui-lib`),test:E(`components`),minShareCount:2,maxSize:2e5,priority:5},{name:D(`ui-f-lib`),test:E(`components`,`.*?[Ff]orm.*?`),minShareCount:2,maxSize:5e4,priority:10}]}}}},resolve:{alias:{react:b(`react`),"react-dom":b(`react-dom`),"@tc/ui-react":i.default.resolve(f,`../../fe/packages/ui/react/`),...T([[`@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`]])}}}),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=O,exports.entries=x,exports.outDir=S,exports.watchFiles=C;
|
package/esm/app/extend/db.js
CHANGED
|
@@ -1,6 +1,141 @@
|
|
|
1
|
+
import { __require as e } from "../../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { mkdirSync as t } from "node:fs";
|
|
3
|
+
import { dirname as n, isAbsolute as r, resolve as i } from "node:path";
|
|
1
4
|
//#region app/extend/db.ts
|
|
2
|
-
|
|
3
|
-
|
|
5
|
+
var a = "framework", o = ".template-core/template-core.sqlite";
|
|
6
|
+
function s(e) {
|
|
7
|
+
let r = u(e), i, a = !1, o = 0, s = () => (x(a), i || (t(n(r), { recursive: !0 }), i = c(r), l(i)), i), d = {
|
|
8
|
+
dbPath: r,
|
|
9
|
+
getDBConnection: s,
|
|
10
|
+
execDB: (e) => {
|
|
11
|
+
s().exec(e);
|
|
12
|
+
},
|
|
13
|
+
runDB: (e, t) => {
|
|
14
|
+
let n = s().prepare(e).run(...p(t));
|
|
15
|
+
return {
|
|
16
|
+
changes: Number(n.changes ?? 0),
|
|
17
|
+
lastInsertRowid: n.lastInsertRowid ?? 0
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
queryDB: (e, t) => s().prepare(e).all(...p(t)),
|
|
21
|
+
getDBFirst: (e, t) => s().prepare(e).get(...p(t)),
|
|
22
|
+
transactionDB: (e) => {
|
|
23
|
+
let t = s(), n = `tc_savepoint_${o}`, r = o === 0;
|
|
24
|
+
t.exec(r ? "BEGIN IMMEDIATE" : `SAVEPOINT ${n}`), o += 1;
|
|
25
|
+
try {
|
|
26
|
+
let i = e(d);
|
|
27
|
+
return --o, t.exec(r ? "COMMIT" : `RELEASE SAVEPOINT ${n}`), i;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
throw --o, t.exec(r ? "ROLLBACK" : `ROLLBACK TO SAVEPOINT ${n}`), r || t.exec(`RELEASE SAVEPOINT ${n}`), e;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
getDBData: (e, t) => d.getDBDataRecord(e, t)?.value,
|
|
33
|
+
getDBDataRecord: (e, t) => {
|
|
34
|
+
let n = d.getDBFirst("SELECT namespace, data_key, data_value, created_at, updated_at FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1", [m(t?.namespace), h(e)]);
|
|
35
|
+
return n ? b(n) : void 0;
|
|
36
|
+
},
|
|
37
|
+
setDBData: (e, t, n) => {
|
|
38
|
+
let r = (/* @__PURE__ */ new Date()).toISOString();
|
|
39
|
+
return d.runDB("INSERT INTO tc_framework_data (namespace, data_key, data_value, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(namespace, data_key) DO UPDATE SET\n data_value = excluded.data_value,\n updated_at = excluded.updated_at", [
|
|
40
|
+
m(n?.namespace),
|
|
41
|
+
h(e),
|
|
42
|
+
v(t),
|
|
43
|
+
r,
|
|
44
|
+
r
|
|
45
|
+
]);
|
|
46
|
+
},
|
|
47
|
+
hasDBData: (e, t) => !!d.getDBFirst("SELECT 1 AS exists_value FROM tc_framework_data WHERE namespace = ? AND data_key = ? LIMIT 1", [m(t?.namespace), h(e)]),
|
|
48
|
+
deleteDBData: (e, t) => d.runDB("DELETE FROM tc_framework_data WHERE namespace = ? AND data_key = ?", [m(t?.namespace), h(e)]).changes > 0,
|
|
49
|
+
listDBData: (e) => {
|
|
50
|
+
let t = g(e?.limit), n = _(e?.offset);
|
|
51
|
+
return d.queryDB("SELECT namespace, data_key, data_value, created_at, updated_at\n FROM tc_framework_data\n WHERE namespace = ?\n ORDER BY updated_at DESC, data_key ASC\n LIMIT ? OFFSET ?", [
|
|
52
|
+
m(e?.namespace),
|
|
53
|
+
t,
|
|
54
|
+
n
|
|
55
|
+
]).map((e) => b(e));
|
|
56
|
+
},
|
|
57
|
+
countDBData: (e) => {
|
|
58
|
+
let t = d.getDBFirst("SELECT COUNT(1) AS count_value FROM tc_framework_data WHERE namespace = ?", [m(e?.namespace)]);
|
|
59
|
+
return Number(t?.count_value ?? 0);
|
|
60
|
+
},
|
|
61
|
+
clearDBData: (e) => d.runDB("DELETE FROM tc_framework_data WHERE namespace = ?", [m(e?.namespace)]).changes,
|
|
62
|
+
closeDB: () => {
|
|
63
|
+
a ||= (i?.close(), !0);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return d;
|
|
67
|
+
}
|
|
68
|
+
function c(t) {
|
|
69
|
+
let n = "node:sqlite", r;
|
|
70
|
+
try {
|
|
71
|
+
r = e(n);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
throw Error(`[db] 默认数据库依赖 Node.js 内置 ${n},当前运行环境不可用。请升级 Node.js,或在业务项目覆盖 app/extend/db.ts。${S(e)}`);
|
|
74
|
+
}
|
|
75
|
+
if (!r.DatabaseSync) throw Error(`[db] ${n} 未提供 DatabaseSync,无法初始化默认 SQLite 数据库。`);
|
|
76
|
+
return new r.DatabaseSync(t);
|
|
77
|
+
}
|
|
78
|
+
function l(e) {
|
|
79
|
+
e.exec("\n CREATE TABLE IF NOT EXISTS tc_framework_data (\n namespace TEXT NOT NULL,\n data_key TEXT NOT NULL,\n data_value TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (namespace, data_key)\n );\n CREATE INDEX IF NOT EXISTS idx_tc_framework_data_namespace_updated_at\n ON tc_framework_data (namespace, updated_at);\n ");
|
|
80
|
+
}
|
|
81
|
+
function u(e) {
|
|
82
|
+
let t = d(e), n = typeof t == "string" ? t : t?.path ?? t?.filename ?? t?.sqlitePath ?? f(t?.sqlite);
|
|
83
|
+
return n ? r(n) ? n : i(e.baseDir, n) : i(e.baseDir, o);
|
|
84
|
+
}
|
|
85
|
+
function d(e) {
|
|
86
|
+
let t = e.config;
|
|
87
|
+
return t.db ?? (t.sqlite ? { sqlite: t.sqlite } : void 0);
|
|
88
|
+
}
|
|
89
|
+
function f(e) {
|
|
90
|
+
if (e) return typeof e == "string" ? e : e.path ?? e.filename;
|
|
91
|
+
}
|
|
92
|
+
function p(e) {
|
|
93
|
+
return e ? Array.isArray(e) ? e : [e] : [];
|
|
94
|
+
}
|
|
95
|
+
function m(e = a) {
|
|
96
|
+
let t = e.trim();
|
|
97
|
+
if (!t) throw Error("[db] namespace 不能为空");
|
|
98
|
+
return t;
|
|
99
|
+
}
|
|
100
|
+
function h(e) {
|
|
101
|
+
let t = e.trim();
|
|
102
|
+
if (!t) throw Error("[db] key 不能为空");
|
|
103
|
+
return t;
|
|
104
|
+
}
|
|
105
|
+
function g(e = 100) {
|
|
106
|
+
if (!Number.isSafeInteger(e) || e <= 0) throw Error("[db] limit 必须是大于 0 的整数");
|
|
107
|
+
return e;
|
|
108
|
+
}
|
|
109
|
+
function _(e = 0) {
|
|
110
|
+
if (!Number.isSafeInteger(e) || e < 0) throw Error("[db] offset 必须是大于等于 0 的整数");
|
|
111
|
+
return e;
|
|
112
|
+
}
|
|
113
|
+
function v(e) {
|
|
114
|
+
try {
|
|
115
|
+
let t = JSON.stringify(e);
|
|
116
|
+
if (t === void 0) throw Error("value is not JSON serializable");
|
|
117
|
+
return t;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
throw Error(`[db] value 必须可以被 JSON.stringify 序列化。${S(e)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function y(e) {
|
|
123
|
+
return JSON.parse(e);
|
|
124
|
+
}
|
|
125
|
+
function b(e) {
|
|
126
|
+
return {
|
|
127
|
+
key: e.data_key,
|
|
128
|
+
value: y(e.data_value),
|
|
129
|
+
namespace: e.namespace,
|
|
130
|
+
createdAt: e.created_at,
|
|
131
|
+
updatedAt: e.updated_at
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function x(e) {
|
|
135
|
+
if (e) throw Error("[db] SQLite 数据库连接已关闭");
|
|
136
|
+
}
|
|
137
|
+
function S(e) {
|
|
138
|
+
return e instanceof Error && e.message ? ` Cause: ${e.message}` : "";
|
|
4
139
|
}
|
|
5
140
|
//#endregion
|
|
6
|
-
export {
|
|
141
|
+
export { s as default };
|
package/esm/bundler/utils.js
CHANGED
|
@@ -124,7 +124,6 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
|
|
|
124
124
|
outDir: j(),
|
|
125
125
|
emptyOutDir: !0,
|
|
126
126
|
sourcemap: i === "dev" ? "inline" : "hidden",
|
|
127
|
-
modulePreload: { polyfill: !1 },
|
|
128
127
|
rolldownOptions: {
|
|
129
128
|
input: e,
|
|
130
129
|
output: { codeSplitting: { groups: [
|
|
@@ -192,7 +191,8 @@ var F = (...e) => new RegExp(e.join("[\\/]")), I = (e) => `_${e}_tcC`, L = async
|
|
|
192
191
|
],
|
|
193
192
|
["@tc/scalability/SchemaForm/data", "./extended/SchemaForm/data"],
|
|
194
193
|
["@tc/scalability/SchemaPage/CallCom/data", "./extended/SchemaPage/CallCom/data"],
|
|
195
|
-
["@tc/scalability/dash/customRoutes", "./extended/dash/customRoutes"]
|
|
194
|
+
["@tc/scalability/dash/customRoutes", "./extended/dash/customRoutes"],
|
|
195
|
+
["@tc/scalability/dash/components", "./extended/dash/components"]
|
|
196
196
|
])
|
|
197
197
|
} }
|
|
198
198
|
}), n(t("builded", "green"), t(" -> ", "white"), t("time: ", "cyan"), t(/* @__PURE__ */ new Date(), "pink"));
|
|
@@ -7,6 +7,7 @@ import { findMenuItem, leftSidebarBasePath } from '../../src/common/menu';
|
|
|
7
7
|
import HeaderView from '../../src/components/BasePage/HeaderView';
|
|
8
8
|
import useRouterParams from '../../src/hooks/useRouterParams';
|
|
9
9
|
import { useModeStore } from '../../src/stores/mode';
|
|
10
|
+
import dashComponents from '@tc/scalability/dash/components';
|
|
10
11
|
import { Menu } from '../../../packages/ui/react/components';
|
|
11
12
|
import useInit from '../../../packages/ui/react/hooks/useInit';
|
|
12
13
|
const normalizePathname = (pathname) => {
|
|
@@ -92,6 +93,7 @@ const getRenderableHomePage = (projectInfo) => {
|
|
|
92
93
|
};
|
|
93
94
|
const Dashboard = () => {
|
|
94
95
|
const { initModeData, initData, projectInfo } = useModeStore();
|
|
96
|
+
const HeaderUserArea = dashComponents['HeaderView.userArea'];
|
|
95
97
|
useInit(() => {
|
|
96
98
|
!initData && initModeData();
|
|
97
99
|
});
|
|
@@ -168,7 +170,7 @@ const Dashboard = () => {
|
|
|
168
170
|
document.title = projectInfo?.name ?? title;
|
|
169
171
|
return title ? getText(title) : undefined;
|
|
170
172
|
};
|
|
171
|
-
return (_jsxs("div", { className: "dash-main h-screen flex flex-col overflow-hidden bg-theme-bg text-foreground", children: [_jsx(HeaderView, { icon: projectInfo?.icon, title: getTitle(), menu: menu }), _jsx("div", { className: "dash-container min-h-0 flex-1 overflow-hidden", children: _jsx(Outlet, {}) })] }));
|
|
173
|
+
return (_jsxs("div", { className: "dash-main h-screen flex flex-col overflow-hidden bg-theme-bg text-foreground", children: [_jsx(HeaderView, { icon: projectInfo?.icon, title: getTitle(), menu: menu, userArea: HeaderUserArea ? _jsx(HeaderUserArea, { projectInfo: projectInfo }) : undefined }), _jsx("div", { className: "dash-container min-h-0 flex-1 overflow-hidden", children: _jsx(Outlet, {}) })] }));
|
|
172
174
|
};
|
|
173
175
|
Dashboard.displayName = 'Dashboard';
|
|
174
176
|
export default Dashboard;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { ProjectInfo } from '../../src/api/baseInfo';
|
|
1
2
|
import type { RouteInfo } from '../../src/components/Router';
|
|
3
|
+
import type { ComponentType, LazyExoticComponent } from 'react';
|
|
2
4
|
export type DashRoutesContext = {
|
|
3
5
|
/**
|
|
4
6
|
* 扩展 topMenu 自定义页面
|
|
@@ -14,4 +16,10 @@ export type DashRoutesContext = {
|
|
|
14
16
|
sidebarRoutes: RouteInfo[];
|
|
15
17
|
};
|
|
16
18
|
export type DashRoutesExtender = (context: DashRoutesContext) => void;
|
|
19
|
+
export type DashHeaderUserAreaProps = {
|
|
20
|
+
projectInfo: ProjectInfo | null;
|
|
21
|
+
};
|
|
22
|
+
export type DashComponentsMap = {
|
|
23
|
+
'HeaderView.userArea'?: ComponentType<DashHeaderUserAreaProps> | LazyExoticComponent<ComponentType<DashHeaderUserAreaProps>>;
|
|
24
|
+
};
|
|
17
25
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type SelectProps } from '../../../../packages/ui/react';
|
|
2
|
+
export type LanguageSwitchValue = 'zh-CN' | 'en-US' | (string & {});
|
|
3
|
+
export type LanguageSwitchOption = Omit<NonNullable<SelectProps['options']>[number], 'value'> & {
|
|
4
|
+
value: LanguageSwitchValue;
|
|
5
|
+
};
|
|
6
|
+
export interface LanguageSwitchProps extends Omit<SelectProps, 'options' | 'value' | 'defaultValue' | 'onChange'> {
|
|
7
|
+
value?: LanguageSwitchValue;
|
|
8
|
+
defaultValue?: LanguageSwitchValue;
|
|
9
|
+
options?: LanguageSwitchOption[];
|
|
10
|
+
onChange?: (language: LanguageSwitchValue) => void;
|
|
11
|
+
}
|
|
12
|
+
declare const LanguageSwitch: ({ value, defaultValue, options, onChange, className, placeholder, clearable, searchable, getPopupContainer, ...props }: LanguageSwitchProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export default LanguageSwitch;
|
|
14
|
+
//# sourceMappingURL=LanguageSwitch.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getText } from '../../common/language';
|
|
3
|
+
import { frontendLangKeys } from '../../language';
|
|
4
|
+
import { cn, Select } from '../../../../packages/ui/react';
|
|
5
|
+
import { i18nStore, useI18nStore } from '../../../../packages/ui/react/i18n';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
const getDefaultLanguageOptions = () => [
|
|
8
|
+
{
|
|
9
|
+
label: getText(frontendLangKeys.languageSwitchZhCN),
|
|
10
|
+
value: 'zh-CN',
|
|
11
|
+
searchText: 'zh-CN 中文 简体中文',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: getText(frontendLangKeys.languageSwitchEnUS),
|
|
15
|
+
value: 'en-US',
|
|
16
|
+
searchText: 'en-US English',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
const LanguageSwitch = ({ value, defaultValue, options, onChange, className, placeholder, clearable = false, searchable = false, getPopupContainer = 'parent', ...props }) => {
|
|
20
|
+
const currentLanguage = useI18nStore((state) => state.language);
|
|
21
|
+
const mergedOptions = useMemo(() => options ?? getDefaultLanguageOptions(), [currentLanguage, options]);
|
|
22
|
+
const selectedLanguage = value ?? currentLanguage ?? defaultValue;
|
|
23
|
+
const handleChange = (language) => {
|
|
24
|
+
if (!language)
|
|
25
|
+
return;
|
|
26
|
+
const nextLanguage = String(language);
|
|
27
|
+
i18nStore.getState().setLanguage(nextLanguage);
|
|
28
|
+
onChange?.(nextLanguage);
|
|
29
|
+
};
|
|
30
|
+
return (_jsx(Select, { ...props, className: cn('w-32 shrink-0', className), clearable: clearable, getPopupContainer: getPopupContainer, options: mergedOptions, placeholder: placeholder ?? getText(frontendLangKeys.languageSwitchPlaceholder), searchable: searchable, value: selectedLanguage ?? defaultValue, onChange: handleChange }));
|
|
31
|
+
};
|
|
32
|
+
export default LanguageSwitch;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ThemeSwitchMode = 'light' | 'dark';
|
|
2
|
+
export interface ThemeSwitchProps {
|
|
3
|
+
value?: ThemeSwitchMode;
|
|
4
|
+
defaultValue?: ThemeSwitchMode;
|
|
5
|
+
onChange?: (mode: ThemeSwitchMode) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
showLabel?: boolean;
|
|
9
|
+
persist?: boolean;
|
|
10
|
+
storageKey?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const themeSwitchStorageKey = "tc_theme";
|
|
13
|
+
declare const ThemeSwitch: ({ value, defaultValue, onChange, disabled, className, showLabel, persist, storageKey, }: ThemeSwitchProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export default ThemeSwitch;
|
|
15
|
+
//# sourceMappingURL=ThemeSwitch.d.ts.map
|