@_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.
Files changed (51) hide show
  1. package/.skills/tc-component-usage-skills/SKILL.md +31 -9
  2. package/.skills/tc-component-usage-skills/reference/component-api.md +63 -42
  3. package/.skills/tc-component-usage-skills/reference/examples.md +34 -24
  4. package/.skills/tc-component-usage-skills/reference/patterns.md +52 -17
  5. package/.skills/tc-component-usage-skills/reference/template-core-frontend.md +104 -0
  6. package/.skills/tc-generator/SKILL.md +11 -3
  7. package/.skills/tc-generator/reference/example.md +1 -1
  8. package/.skills/tc-generator/reference/model-schema.md +4 -2
  9. package/.skills/tc-generator/reference/project-template/config/config.default.js +4 -0
  10. package/.skills/tc-generator/reference/project-template/index.js +1 -1
  11. package/.skills/tc-generator/reference/project-template/model/product/project/default.js +1 -0
  12. package/.skills/tc-generator/reference/project-template/package.json +16 -1
  13. package/.skills/tc-generator/reference/runtime-extensions.md +190 -0
  14. package/README.md +217 -2
  15. package/cjs/app/extend/db.js +20 -1
  16. package/cjs/bundler/utils.js +1 -1
  17. package/esm/app/extend/db.js +138 -3
  18. package/esm/bundler/utils.js +2 -2
  19. package/fe/frontend/apps/dash/Dashboard.js +3 -1
  20. package/fe/frontend/apps/dash/types.d.ts +8 -0
  21. package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.d.ts +14 -0
  22. package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.js +32 -0
  23. package/fe/frontend/src/components/LanguageSwitch/index.d.ts +4 -0
  24. package/fe/frontend/src/components/LanguageSwitch/index.js +3 -0
  25. package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.d.ts +15 -0
  26. package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.js +59 -0
  27. package/fe/frontend/src/components/ThemeSwitch/index.d.ts +4 -0
  28. package/fe/frontend/src/components/ThemeSwitch/index.js +3 -0
  29. package/fe/frontend/src/components/index.d.ts +4 -0
  30. package/fe/frontend/src/components/index.js +3 -0
  31. package/fe/frontend/src/index.d.ts +1 -0
  32. package/fe/frontend/src/index.js +1 -0
  33. package/fe/frontend/src/language/en-US.d.ts +13 -0
  34. package/fe/frontend/src/language/en-US.js +13 -0
  35. package/fe/frontend/src/language/index.d.ts +7 -0
  36. package/fe/frontend/src/language/index.js +7 -0
  37. package/fe/frontend/src/language/zh-CN.d.ts +13 -0
  38. package/fe/frontend/src/language/zh-CN.js +13 -0
  39. package/fe/packages/ui/react/components/Button/{SumbitButton.d.ts → SubmitButton.d.ts} +5 -5
  40. package/fe/packages/ui/react/components/Button/{SumbitButton.js → SubmitButton.js} +3 -3
  41. package/fe/packages/ui/react/components/Button/index.d.ts +1 -1
  42. package/fe/packages/ui/react/components/Button/index.js +1 -1
  43. package/fe/packages/ui/react/components/Modal/ModalManager.d.ts +3 -3
  44. package/fe/packages/ui/react/components/TableSearch/index.d.ts +1 -0
  45. package/package.json +3 -3
  46. package/types/app/extend/db.d.ts +54 -1
  47. package/types/config/config.default.d.ts +30 -0
  48. package/types/index.d.ts +1 -0
  49. package/cjs/bundler/modulepreload-polyfill.js +0 -1
  50. package/esm/bundler/modulepreload-polyfill.js +0 -11
  51. 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
- | Dash 扩展类型 | `DashRoutesExtender`、`DashRoutesContext` |
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. 声明字段类型。
@@ -1 +1,20 @@
1
- async function e(e){return console.log(e),{}}module.exports=e;
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;
@@ -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`,modulePreload:{polyfill:!1},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`]])}}}),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;
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;
@@ -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
- async function e(e) {
3
- return console.log(e), {};
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 { e as default };
141
+ export { s as default };
@@ -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,4 @@
1
+ import LanguageSwitch from './LanguageSwitch';
2
+ export * from './LanguageSwitch';
3
+ export default LanguageSwitch;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ import LanguageSwitch from './LanguageSwitch';
2
+ export * from './LanguageSwitch';
3
+ 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