@_tc/template-core 0.0.1-bate.58 → 0.0.1-bate.59
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 +5 -0
- package/.skills/tc-component-usage-skills/reference/component-api.md +3 -0
- package/.skills/tc-generator/reference/example.md +4 -0
- package/.skills/tc-generator/reference/project-template/config/config.default.js +4 -0
- package/AGENT_README.md +11 -0
- package/README.md +21 -2
- package/cjs/config/config.default.js +1 -1
- package/cjs/packages/core/index.js +1 -1
- package/cjs/packages/core/loader/middleware.js +1 -1
- package/esm/app/{middleware → middlewares}/api-params-verify.js +1 -1
- package/esm/app/{middleware → middlewares}/api-sign-verify.js +1 -1
- package/esm/app/{middleware → middlewares}/error-handle.js +1 -1
- package/esm/app/{middleware → middlewares}/project-handler.js +1 -1
- package/esm/app/{middleware → middlewares}/request-parameter-parsing.js +1 -1
- package/esm/config/config.default.js +4 -0
- package/esm/packages/core/index.js +37 -33
- package/esm/packages/core/loader/middleware.js +1 -1
- package/fe/frontend/src/common/request.js +1 -1
- package/fe/packages/react/ui/components/Input/Input.js +93 -39
- package/fe/packages/react/ui/components/testPage/index.js +29 -3
- package/package.json +1 -1
- package/types/config/config.default.d.ts +27 -1
- package/types/packages/core/loader/config.d.ts +1 -1
- /package/cjs/app/{middleware → middlewares}/api-params-verify.js +0 -0
- /package/cjs/app/{middleware → middlewares}/api-sign-verify.js +0 -0
- /package/cjs/app/{middleware → middlewares}/error-handle.js +0 -0
- /package/cjs/app/{middleware → middlewares}/project-handler.js +0 -0
- /package/cjs/app/{middleware → middlewares}/request-parameter-parsing.js +0 -0
- /package/types/app/{middleware → middlewares}/api-params-verify.d.ts +0 -0
- /package/types/app/{middleware → middlewares}/api-sign-verify.d.ts +0 -0
- /package/types/app/{middleware → middlewares}/error-handle.d.ts +0 -0
- /package/types/app/{middleware → middlewares}/project-handler.d.ts +0 -0
- /package/types/app/{middleware → middlewares}/request-parameter-parsing.d.ts +0 -0
|
@@ -31,8 +31,13 @@ Every input-like component supports both modes. Check for `value !== undefined`
|
|
|
31
31
|
|
|
32
32
|
// Uncontrolled
|
|
33
33
|
<Input defaultValue="hello" />
|
|
34
|
+
|
|
35
|
+
// Password fields get a built-in show/hide toggle
|
|
36
|
+
<Input type="password" value={password} onChange={setPassword} />
|
|
34
37
|
```
|
|
35
38
|
|
|
39
|
+
For `type="password"`, do not add a separate eye icon or clear button. The built-in password toggle is automatic; `allowClear` is hidden on password inputs.
|
|
40
|
+
|
|
36
41
|
### Form Integration (rc-field-form)
|
|
37
42
|
|
|
38
43
|
`Form` / `FormItem` / `SchemaForm` wrap `rc-field-form`. `FormItem` auto-clones children to inject `value`/`onChange`. Prefer `SchemaForm` for data-driven forms:
|
|
@@ -45,12 +45,15 @@ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, '
|
|
|
45
45
|
onChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void
|
|
46
46
|
allowClear?: boolean | { clearIcon: ReactNode }
|
|
47
47
|
addonAfter?: ReactNode
|
|
48
|
+
inputClassName?: string
|
|
48
49
|
className?: string
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
Note: `onChange` callback receives `(stringValue, event)` — two arguments, not one.
|
|
53
54
|
|
|
55
|
+
Use `type="password"` for password fields. The component shows a built-in visibility toggle, supports controlled and uncontrolled values, and removes the native DOM `value` attribute while preserving the input value property. `allowClear` is intentionally hidden for password inputs. `addonAfter` shares the right-side accessory area with the password toggle.
|
|
56
|
+
|
|
54
57
|
---
|
|
55
58
|
|
|
56
59
|
## InputNumber
|
package/AGENT_README.md
CHANGED
|
@@ -74,9 +74,15 @@ main().catch((error) => {
|
|
|
74
74
|
// config/config.default.ts
|
|
75
75
|
export default {
|
|
76
76
|
port: 9000,
|
|
77
|
+
auth: {
|
|
78
|
+
ATKey: 'Authorization',
|
|
79
|
+
RTKey: 'RT',
|
|
80
|
+
},
|
|
77
81
|
}
|
|
78
82
|
```
|
|
79
83
|
|
|
84
|
+
`auth.ATKey` / `auth.RTKey` 是业务鉴权中间件读取短 token 和刷新 token header 的默认约定;默认分别是 `Authorization` / `RT`。
|
|
85
|
+
|
|
80
86
|
访问内置后台:
|
|
81
87
|
|
|
82
88
|
```text
|
|
@@ -93,6 +99,7 @@ http://localhost:9000/dash?projk=demo
|
|
|
93
99
|
app/controller/**/*.(js|ts) -> app.controller
|
|
94
100
|
app/service/**/*.(js|ts) -> app.service
|
|
95
101
|
app/middleware/**/*.(js|ts) -> app.middlewares
|
|
102
|
+
app/middleware.(js|ts) -> 全局中间件编排,按 frame -> business 顺序执行
|
|
96
103
|
app/router/**/*.(js|ts) -> Koa router
|
|
97
104
|
app/router-guard.(js|ts) -> 兜底路由守卫
|
|
98
105
|
app/router-schema/**/*.(js|ts) -> app.routerSchema
|
|
@@ -378,10 +385,14 @@ import { Button, DataTable, Form, Input, Modal, Select } from '@_tc/template-cor
|
|
|
378
385
|
常用前端能力:
|
|
379
386
|
|
|
380
387
|
- 请求方法:`request`、`get`、`post`、`put`、`patch`、`del`。
|
|
388
|
+
- 请求 header:自动添加 `s_t`、`s_sign`、`projk`;存在短 token 时添加 `Authorization: Bearer ${token}`。
|
|
389
|
+
- 本地 key:短 token 是 `localStorage.auth_token`,项目 key 是 `localStorage.p_J_k`;刷新 token header 默认名是 `RT`。
|
|
390
|
+
- `getAuthToken()` 是同步函数,读取前无需 `await`。
|
|
381
391
|
- Schema 通信:`schemaEventBus`、`eventsInfo`、`merge`。
|
|
382
392
|
- 共享状态:`modeStore`、`schemaStore`、`apiFreezerStore` 及对应 hooks。
|
|
383
393
|
- 多语言:`i18nStore.getState().addResources()`,配置文案可写 `$i18n::...`。
|
|
384
394
|
- 主题:`ThemeSwitch` 会同步根节点 `dark` class 和 `localStorage`。
|
|
395
|
+
- `Input type="password"` 会自动显示密码显隐按钮;`allowClear` 不作用于密码输入。
|
|
385
396
|
|
|
386
397
|
不要直接依赖 `frontend/src/...`、`packages/react/ui/...` 这类包内部路径。
|
|
387
398
|
|
package/README.md
CHANGED
|
@@ -101,7 +101,22 @@ main().catch((error) => {
|
|
|
101
101
|
| `homePage` | `string` | 否 | - | 应用首页路径 |
|
|
102
102
|
| `additionalPublicPaths` | `string | string[]` | 否 | - | 追加静态资源目录和 Nunjucks 模板查找目录,必须传绝对路径 |
|
|
103
103
|
|
|
104
|
-
框架会自动读取 `config/config.default.ts` 中的配置(如 `port`、`env` 等)。
|
|
104
|
+
框架会自动读取 `config/config.default.ts` 中的配置(如 `port`、`env`、`auth` 等)。
|
|
105
|
+
|
|
106
|
+
常用默认配置示例:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// config/config.default.ts
|
|
110
|
+
export default {
|
|
111
|
+
port: 9000,
|
|
112
|
+
auth: {
|
|
113
|
+
ATKey: 'Authorization',
|
|
114
|
+
RTKey: 'RT',
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`auth.ATKey` / `auth.RTKey` 用于约定业务鉴权中间件读取短 token 和刷新 token 的 header 名;默认分别为 `Authorization` 和 `RT`。
|
|
105
120
|
|
|
106
121
|
静态资源挂载和 Nunjucks 模板查找顺序均为:框架 `app/public`、项目 `app/public`、`additionalPublicPaths`。当 `buildFE` 输出到自定义目录时,可以用 `additionalPublicPaths` 把该目录交给 Koa 提供静态资源访问和模板渲染。
|
|
107
122
|
|
|
@@ -506,6 +521,8 @@ export function Toolbar() {
|
|
|
506
521
|
}
|
|
507
522
|
```
|
|
508
523
|
|
|
524
|
+
`Authorization` 默认对应服务端配置 `config.auth.ATKey`。前端本地 token 存在 `localStorage.auth_token`,项目 key 存在 `localStorage.p_J_k`;刷新 token header 名预留为 `config.auth.RTKey`,默认是 `RT`。
|
|
525
|
+
|
|
509
526
|
**签名机制**:
|
|
510
527
|
- 签名密钥:服务端从 `app.config.signKey` 读取,并在页面渲染时注入为 `window._signKey`
|
|
511
528
|
- 签名算法:`md5(签名密钥_${时间戳})`
|
|
@@ -583,7 +600,7 @@ try {
|
|
|
583
600
|
| 组件 | 用途 | 导入路径 |
|
|
584
601
|
| --- | --- | --- |
|
|
585
602
|
| `Button` | 按钮组件,支持多种样式和尺寸 | `@_tc/template-core/fe/rc` |
|
|
586
|
-
| `Input` |
|
|
603
|
+
| `Input` | 输入框组件,支持清空、右侧附加内容和密码显隐 | `@_tc/template-core/fe/rc` |
|
|
587
604
|
| `InputNumber` | 数字输入框组件 | `@_tc/template-core/fe/rc` |
|
|
588
605
|
| `Textarea` | 多行文本输入框组件 | `@_tc/template-core/fe/rc` |
|
|
589
606
|
| `Select` | 下拉选择组件,支持单选和多选 | `@_tc/template-core/fe/rc` |
|
|
@@ -666,6 +683,7 @@ function MyComponent() {
|
|
|
666
683
|
</Button>
|
|
667
684
|
|
|
668
685
|
<Input placeholder="请输入内容" />
|
|
686
|
+
<Input type="password" placeholder="请输入密码" />
|
|
669
687
|
|
|
670
688
|
<Select
|
|
671
689
|
options={[
|
|
@@ -691,6 +709,7 @@ function MyComponent() {
|
|
|
691
709
|
app/controller/**/*.(js|ts) -> app.controller
|
|
692
710
|
app/service/**/*.(js|ts) -> app.service
|
|
693
711
|
app/middleware/**/*.(js|ts) -> app.middlewares
|
|
712
|
+
app/middleware.(js|ts) -> 全局中间件编排,按框架 -> 项目顺序执行
|
|
694
713
|
app/router/**/*.(js|ts) -> Koa router
|
|
695
714
|
app/router-schema/**/*.(js|ts) -> app.routerSchema
|
|
696
715
|
app/extend/*.(js|ts) -> app.extends
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e={frameName:`TemplateCore`,signKey:`sakjdfnkjwjfnfkjkjldljksndf`,apiSignVerify:{whiteList:[]},notFoundRedirectCount:5};module.exports=e;
|
|
1
|
+
var e={frameName:`TemplateCore`,signKey:`sakjdfnkjwjfnfkjkjldljksndf`,apiSignVerify:{whiteList:[]},auth:{ATKey:`Authorization`,RTKey:`RT`},notFoundRedirectCount:5};module.exports=e;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../../_virtual/_rolldown/runtime.js`),t=require(`./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../../_virtual/_rolldown/runtime.js`),t=require(`./paths.js`),n=require(`./env.js`),r=require(`../utils/path.js`),i=require(`../utils/runFileFn.js`);require(`../utils/index.js`);const a=require(`./loader/config.js`),o=require(`./loader/controller.js`),s=require(`./loader/extend.js`),c=require(`./loader/middleware.js`),l=require(`./loader/model.js`),u=require(`./loader/router.js`),d=require(`./loader/router-schema.js`),f=require(`./loader/service.js`);let p=require(`koa`);p=e.__toESM(p,1);var m=l,h=async(e={})=>{let t=new p.default;t.hooks={},t.hooks.initing?.();let{frameBaseDir:i,baseDir:l,...m}=e;t.options=m,t.options.pageBasePage=t.options.pageBasePage??`/`,t.options.apiPrefix=t.options.apiPrefix||`/api`;let h=t.baseDir=l??process.cwd(),_=t.frameBaseDir=i??r.resolve(__dirname,`..`);t.businessAppPath=r.resolve(h,`app`),t.frameAppPath=r.resolve(_,`app`),t.paths=[{type:`frame`,path:t.frameAppPath},{type:`business`,path:t.businessAppPath}],t.envs=n(),console.log(`-- [init] env: ${t.envs.get()} --`),a(t),console.log(`-- [init] loader config done --`),await s(t),console.log(`-- [init] loader extend done --`),d(t),console.log(`-- [init] loader routerSchema done --`),f(t),console.log(`-- [init] loader service done --`),o(t),console.log(`-- [init] loader controller done --`),c(t),console.log(`-- [init] loader middleware done --`),g(t),u(t),console.log(`-- [init] loader router done --`);try{let e=t.config.port||process.env.PORT||`8080`,r=parseInt(e+``,10),i=process.env.IP||`0.0.0.0`;t.listen(r,i),console.log(`Server port: http://localhost:${r}`),console.log(`run env is ${n().get()}`)}catch(e){console.error(e)}return t.hooks.inited?.(),t},g=e=>{[`frame`,`business`].map(n=>({type:n,path:t.getAppPath(e,n)})).forEach(t=>{for(let n of[`ts`,`js`]){let a=`middleware.${n}`,o=r.resolve(t.path,a);try{i.runFileFn(o)?.(e),console.log(`-- [init] loader ${t.type} appMiddleware ${a} done -- (${t.path})`);break}catch(e){let n=e;if(!(n?.code===`MODULE_NOT_FOUND`&&typeof n?.message==`string`&&(n.message.includes(`Cannot find module '${o}'`)||n.message.includes(`Cannot find module "${o}"`))))throw console.log(`-- [error] loader ${t.type} appMiddleware ${a} error -- (${o})`),e}}})};exports.loaderModel=m,exports.start=h;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=require(`../paths.js`),t=require(`../../utils/loadFile.js`),n=require(`../../utils/runFileFn.js`);require(`../../utils/index.js`);var r=r=>{r.middlewares=e.getAppPaths(r).reduce((e,i)=>{let a=t.loadFileFn(i,`
|
|
1
|
+
const e=require(`../paths.js`),t=require(`../../utils/loadFile.js`),n=require(`../../utils/runFileFn.js`);require(`../../utils/index.js`);var r=r=>{r.middlewares=e.getAppPaths(r).reduce((e,i)=>{let a=t.loadFileFn(i,`middlewares`,{loadFileCallback:e=>n.runFileFn(e)(r)});return{...e,...a}},{})};module.exports=r;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import e from "md5";
|
|
2
|
-
//#region app/
|
|
2
|
+
//#region app/middlewares/api-sign-verify.ts
|
|
3
3
|
var t = 1e3 * 60 * 10, n = (n) => async (r, i) => {
|
|
4
4
|
let { path: a, method: o } = r;
|
|
5
5
|
if (a.includes(n.options.apiPrefix) && !n.config?.apiSignVerify?.whiteList?.includes(a)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region app/
|
|
1
|
+
//#region app/middlewares/project-handler.ts
|
|
2
2
|
var e = (e) => async (t, n) => {
|
|
3
3
|
let r = t.headers.projk, { path: i } = t;
|
|
4
4
|
if (i.indexOf(`${e.options.apiPrefix}/proj/`) !== -1 && (!r || ["undefined", "null"].includes(r))) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region app/
|
|
1
|
+
//#region app/middlewares/request-parameter-parsing.ts
|
|
2
2
|
var e = (e) => typeof e == "object" && !!e && !Array.isArray(e), t = ((t) => async (n, r) => {
|
|
3
3
|
if (n.path.includes(`${t.options.apiPrefix}/`)) {
|
|
4
4
|
let { query: t, headers: r } = n, i = n.request.body;
|
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
import e from "./
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { getAppPath as e } from "./paths.js";
|
|
2
|
+
import t from "./env.js";
|
|
3
|
+
import { resolve as n } from "../utils/path.js";
|
|
4
|
+
import { runFileFn as r } from "../utils/runFileFn.js";
|
|
4
5
|
import "../utils/index.js";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
6
|
+
import i from "./loader/config.js";
|
|
7
|
+
import a from "./loader/controller.js";
|
|
8
|
+
import o from "./loader/extend.js";
|
|
9
|
+
import s from "./loader/middleware.js";
|
|
10
|
+
import c from "./loader/model.js";
|
|
11
|
+
import l from "./loader/router.js";
|
|
12
|
+
import u from "./loader/router-schema.js";
|
|
13
|
+
import d from "./loader/service.js";
|
|
14
|
+
import f from "koa";
|
|
14
15
|
//#region packages/core/index.ts
|
|
15
|
-
var
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
let { frameBaseDir:
|
|
19
|
-
|
|
20
|
-
let g =
|
|
21
|
-
|
|
16
|
+
var p = c, m = async (e = {}) => {
|
|
17
|
+
let r = new f();
|
|
18
|
+
r.hooks = {}, r.hooks.initing?.();
|
|
19
|
+
let { frameBaseDir: c, baseDir: p, ...m } = e;
|
|
20
|
+
r.options = m, r.options.pageBasePage = r.options.pageBasePage ?? "/", r.options.apiPrefix = r.options.apiPrefix || "/api";
|
|
21
|
+
let g = r.baseDir = p ?? process.cwd(), _ = r.frameBaseDir = c ?? n(__dirname, "..");
|
|
22
|
+
r.businessAppPath = n(g, "app"), r.frameAppPath = n(_, "app"), r.paths = [{
|
|
22
23
|
type: "frame",
|
|
23
|
-
path:
|
|
24
|
+
path: r.frameAppPath
|
|
24
25
|
}, {
|
|
25
26
|
type: "business",
|
|
26
|
-
path:
|
|
27
|
-
}],
|
|
27
|
+
path: r.businessAppPath
|
|
28
|
+
}], r.envs = t(), console.log(`-- [init] env: ${r.envs.get()} --`), i(r), console.log("-- [init] loader config done --"), await o(r), console.log("-- [init] loader extend done --"), u(r), console.log("-- [init] loader routerSchema done --"), d(r), console.log("-- [init] loader service done --"), a(r), console.log("-- [init] loader controller done --"), s(r), console.log("-- [init] loader middleware done --"), h(r), l(r), console.log("-- [init] loader router done --");
|
|
28
29
|
try {
|
|
29
|
-
let
|
|
30
|
-
|
|
30
|
+
let e = r.config.port || process.env.PORT || "8080", n = parseInt(e + "", 10), i = process.env.IP || "0.0.0.0";
|
|
31
|
+
r.listen(n, i), console.log(`Server port: http://localhost:${n}`), console.log(`run env is ${t().get()}`);
|
|
31
32
|
} catch (e) {
|
|
32
33
|
console.error(e);
|
|
33
34
|
}
|
|
34
|
-
return
|
|
35
|
-
},
|
|
36
|
-
|
|
35
|
+
return r.hooks.inited?.(), r;
|
|
36
|
+
}, h = (t) => {
|
|
37
|
+
["frame", "business"].map((n) => ({
|
|
38
|
+
type: n,
|
|
39
|
+
path: e(t, n)
|
|
40
|
+
})).forEach((e) => {
|
|
37
41
|
for (let i of ["ts", "js"]) {
|
|
38
|
-
let a = `middleware.${i}`, o =
|
|
42
|
+
let a = `middleware.${i}`, o = n(e.path, a);
|
|
39
43
|
try {
|
|
40
|
-
|
|
44
|
+
r(o)?.(t), console.log(`-- [init] loader ${e.type} appMiddleware ${a} done -- (${e.path})`);
|
|
41
45
|
break;
|
|
42
|
-
} catch (
|
|
43
|
-
let
|
|
44
|
-
if (!(
|
|
46
|
+
} catch (t) {
|
|
47
|
+
let n = t;
|
|
48
|
+
if (!(n?.code === "MODULE_NOT_FOUND" && typeof n?.message == "string" && (n.message.includes(`Cannot find module '${o}'`) || n.message.includes(`Cannot find module "${o}"`)))) throw console.log(`-- [error] loader ${e.type} appMiddleware ${a} error -- (${o})`), t;
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
});
|
|
48
52
|
};
|
|
49
53
|
//#endregion
|
|
50
|
-
export {
|
|
54
|
+
export { p as loaderModel, m as start };
|
|
@@ -5,7 +5,7 @@ import "../../utils/index.js";
|
|
|
5
5
|
//#region packages/core/loader/middleware.ts
|
|
6
6
|
var r = (r) => {
|
|
7
7
|
r.middlewares = e(r).reduce((e, i) => {
|
|
8
|
-
let a = t(i, "
|
|
8
|
+
let a = t(i, "middlewares", { loadFileCallback: (e) => n(e)(r) });
|
|
9
9
|
return {
|
|
10
10
|
...e,
|
|
11
11
|
...a
|
|
@@ -96,7 +96,7 @@ var removeBaseurlFromURL = (config) => {
|
|
|
96
96
|
};
|
|
97
97
|
api.interceptors.request.use(async (config) => {
|
|
98
98
|
removeBaseurlFromURL(config);
|
|
99
|
-
const token =
|
|
99
|
+
const token = getAuthToken();
|
|
100
100
|
const aauto = getApiAuth();
|
|
101
101
|
const time = (/* @__PURE__ */ new Date()).getTime();
|
|
102
102
|
config.headers = {
|
|
@@ -1,24 +1,55 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.js";
|
|
2
2
|
import { useInputController } from "../hooks/useInputController.js";
|
|
3
|
-
import { forwardRef, useCallback, useMemo } from "react";
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { X } from "lucide-react";
|
|
5
|
+
import { Eye, EyeOff, X } from "lucide-react";
|
|
6
6
|
//#region packages/react/ui/components/Input/Input.tsx
|
|
7
7
|
var Input = forwardRef(({ className, inputClassName, type, value: controlledValue, onChange, defaultValue, allowClear, disabled, addonAfter, ...props }, ref) => {
|
|
8
|
+
const isFileInput = type === "file";
|
|
9
|
+
const isPasswordInput = type === "password";
|
|
8
10
|
const { setInternalValue, setIsFocused, setRefs, controllerRef, isControlledRef, isFocused, value, hasValue } = useInputController({
|
|
9
11
|
defaultValue,
|
|
10
12
|
controlledValue,
|
|
11
13
|
ref
|
|
12
14
|
});
|
|
13
|
-
const
|
|
15
|
+
const [innerType, setInnerType] = useState(type);
|
|
16
|
+
const isMountedRef = useRef(false);
|
|
17
|
+
const currentType = isPasswordInput ? innerType ?? "password" : type;
|
|
18
|
+
const isPasswordVisible = currentType === "text";
|
|
19
|
+
const removePasswordValueAttribute = useCallback(() => {
|
|
20
|
+
if (!isPasswordInput) return;
|
|
21
|
+
Promise.resolve().then(() => {
|
|
22
|
+
if (!isMountedRef.current) return;
|
|
23
|
+
const input = controllerRef.current;
|
|
24
|
+
if (input?.getAttribute("type") === "password" && input.hasAttribute("value")) input.removeAttribute("value");
|
|
25
|
+
});
|
|
26
|
+
}, [controllerRef, isPasswordInput]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
isMountedRef.current = true;
|
|
29
|
+
return () => {
|
|
30
|
+
isMountedRef.current = false;
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setInnerType(type);
|
|
35
|
+
}, [type]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
removePasswordValueAttribute();
|
|
38
|
+
}, [
|
|
39
|
+
currentType,
|
|
40
|
+
removePasswordValueAttribute,
|
|
41
|
+
value
|
|
42
|
+
]);
|
|
14
43
|
const handleChange = useCallback((e) => {
|
|
15
44
|
const newValue = e.target.value;
|
|
45
|
+
removePasswordValueAttribute();
|
|
16
46
|
if (!isFileInput && !isControlledRef.current) setInternalValue(newValue);
|
|
17
47
|
onChange?.(newValue, e);
|
|
18
48
|
}, [
|
|
19
49
|
isFileInput,
|
|
20
50
|
isControlledRef,
|
|
21
51
|
onChange,
|
|
52
|
+
removePasswordValueAttribute,
|
|
22
53
|
setInternalValue
|
|
23
54
|
]);
|
|
24
55
|
const handleClear = useCallback((e) => {
|
|
@@ -36,6 +67,12 @@ var Input = forwardRef(({ className, inputClassName, type, value: controlledValu
|
|
|
36
67
|
onChange,
|
|
37
68
|
setInternalValue
|
|
38
69
|
]);
|
|
70
|
+
const handleTogglePassword = useCallback((e) => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
if (isPasswordVisible) removePasswordValueAttribute();
|
|
74
|
+
setInnerType((current) => current === "password" ? "text" : "password");
|
|
75
|
+
}, [isPasswordVisible, removePasswordValueAttribute]);
|
|
39
76
|
const clearIcon = useMemo(() => {
|
|
40
77
|
if (allowClear) if (typeof allowClear === "boolean") return /* @__PURE__ */ jsx("span", {
|
|
41
78
|
className: cn("w-5 h-5 rounded-full", "bg-muted hover:bg-muted/80", "text-foreground", "transition-colors", "flex items-center justify-center", "focus:outline-none"),
|
|
@@ -43,44 +80,61 @@ var Input = forwardRef(({ className, inputClassName, type, value: controlledValu
|
|
|
43
80
|
});
|
|
44
81
|
else return allowClear.clearIcon;
|
|
45
82
|
}, [allowClear]);
|
|
46
|
-
const showClearIcon = hasValue && clearIcon && !disabled;
|
|
83
|
+
const showClearIcon = !isPasswordInput && hasValue && clearIcon && !disabled;
|
|
84
|
+
const showPasswordToggle = isPasswordInput && !disabled;
|
|
85
|
+
const rightAccessoryCount = Number(Boolean(showClearIcon)) + Number(showPasswordToggle) + Number(Boolean(addonAfter));
|
|
86
|
+
const hasRightAccessories = rightAccessoryCount > 0;
|
|
47
87
|
return /* @__PURE__ */ jsxs("div", {
|
|
48
88
|
className: cn("tc-ui-input relative w-full", className),
|
|
49
|
-
children: [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
90
|
+
type: currentType,
|
|
91
|
+
ref: setRefs,
|
|
92
|
+
...!isFileInput && { value },
|
|
93
|
+
onChange: handleChange,
|
|
94
|
+
className: cn("flex h-10 w-full rounded-lg px-3 py-0 text-sm", "border transition-colors duration-200", "placeholder:text-muted-foreground", "focus:outline-none", rightAccessoryCount === 1 && "pr-10", rightAccessoryCount === 2 && "pr-20", rightAccessoryCount >= 3 && "pr-28", disabled ? "cursor-not-allowed bg-muted border-border text-muted-foreground" : "bg-background", !disabled && !isFocused && "border-border", !disabled && isFocused && "border-theme ring-2 ring-theme/25", inputClassName),
|
|
95
|
+
onFocus: (e) => {
|
|
96
|
+
if (!disabled) {
|
|
97
|
+
removePasswordValueAttribute();
|
|
98
|
+
setIsFocused(true);
|
|
99
|
+
props.onFocus?.(e);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
onBlur: (e) => {
|
|
103
|
+
if (!disabled) {
|
|
104
|
+
removePasswordValueAttribute();
|
|
105
|
+
setIsFocused(false);
|
|
106
|
+
props.onBlur?.(e);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
disabled,
|
|
110
|
+
...props
|
|
111
|
+
}), hasRightAccessories && /* @__PURE__ */ jsxs("div", {
|
|
112
|
+
className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-1",
|
|
113
|
+
children: [
|
|
114
|
+
showClearIcon && /* @__PURE__ */ jsx("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
117
|
+
onClick: handleClear,
|
|
118
|
+
"aria-label": "Clear input",
|
|
119
|
+
title: "Clear input",
|
|
120
|
+
className: "flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-foreground/8",
|
|
121
|
+
children: clearIcon
|
|
122
|
+
}),
|
|
123
|
+
showPasswordToggle && /* @__PURE__ */ jsx("button", {
|
|
124
|
+
type: "button",
|
|
125
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
126
|
+
onClick: handleTogglePassword,
|
|
127
|
+
"aria-label": isPasswordVisible ? "Hide password" : "Show password",
|
|
128
|
+
title: isPasswordVisible ? "Hide password" : "Show password",
|
|
129
|
+
className: "flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-foreground/8 hover:text-foreground",
|
|
130
|
+
children: isPasswordVisible ? /* @__PURE__ */ jsx(EyeOff, { size: 16 }) : /* @__PURE__ */ jsx(Eye, { size: 16 })
|
|
131
|
+
}),
|
|
132
|
+
addonAfter && /* @__PURE__ */ jsx("div", {
|
|
133
|
+
className: "flex h-7 items-center text-muted-foreground",
|
|
134
|
+
children: addonAfter
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
})]
|
|
84
138
|
});
|
|
85
139
|
});
|
|
86
140
|
Input.displayName = "Input";
|
|
@@ -209,14 +209,40 @@ function InputDemo() {
|
|
|
209
209
|
return /* @__PURE__ */ jsxs(Section, {
|
|
210
210
|
title: "Input",
|
|
211
211
|
children: [
|
|
212
|
-
/* @__PURE__ */ jsx(Input, {
|
|
212
|
+
/* @__PURE__ */ jsx(Input, {
|
|
213
|
+
placeholder: "普通输入框",
|
|
214
|
+
className: "w-48"
|
|
215
|
+
}),
|
|
216
|
+
/* @__PURE__ */ jsx(Input, {
|
|
217
|
+
allowClear: true,
|
|
218
|
+
defaultValue: "可清空内容",
|
|
219
|
+
placeholder: "清空按钮",
|
|
220
|
+
className: "w-48"
|
|
221
|
+
}),
|
|
222
|
+
/* @__PURE__ */ jsx(Input, {
|
|
223
|
+
type: "password",
|
|
224
|
+
placeholder: "密码输入框",
|
|
225
|
+
className: "w-48"
|
|
226
|
+
}),
|
|
227
|
+
/* @__PURE__ */ jsx(Input, {
|
|
228
|
+
type: "password",
|
|
229
|
+
defaultValue: "template-core",
|
|
230
|
+
placeholder: "密码默认值",
|
|
231
|
+
className: "w-48"
|
|
232
|
+
}),
|
|
213
233
|
/* @__PURE__ */ jsx(Input, {
|
|
214
234
|
type: "password",
|
|
215
|
-
placeholder: "
|
|
235
|
+
placeholder: "密码 + addon",
|
|
236
|
+
addonAfter: /* @__PURE__ */ jsx("span", {
|
|
237
|
+
className: "text-xs text-muted-foreground",
|
|
238
|
+
children: "PWD"
|
|
239
|
+
}),
|
|
240
|
+
className: "w-56"
|
|
216
241
|
}),
|
|
217
242
|
/* @__PURE__ */ jsx(Input, {
|
|
218
243
|
disabled: true,
|
|
219
|
-
placeholder: "禁用状态"
|
|
244
|
+
placeholder: "禁用状态",
|
|
245
|
+
className: "w-48"
|
|
220
246
|
})
|
|
221
247
|
]
|
|
222
248
|
});
|
package/package.json
CHANGED
|
@@ -40,6 +40,32 @@ export interface DefaultAppConfig {
|
|
|
40
40
|
viewsPath?: string;
|
|
41
41
|
nunjucks?: import('nunjucks').ConfigureOptions;
|
|
42
42
|
notFoundRedirectCount?: number;
|
|
43
|
+
/**
|
|
44
|
+
* 登陆验证相关
|
|
45
|
+
*/
|
|
46
|
+
auth?: {
|
|
47
|
+
/**
|
|
48
|
+
* 短token 获取的key
|
|
49
|
+
* @default 'Authorization'
|
|
50
|
+
*/
|
|
51
|
+
ATKey?: string;
|
|
52
|
+
/**
|
|
53
|
+
* 长token 获取的key
|
|
54
|
+
* @default 'RT'
|
|
55
|
+
*/
|
|
56
|
+
RTKey?: string;
|
|
57
|
+
};
|
|
43
58
|
}
|
|
44
|
-
declare const _default:
|
|
59
|
+
declare const _default: {
|
|
60
|
+
frameName: string;
|
|
61
|
+
signKey: string;
|
|
62
|
+
apiSignVerify: {
|
|
63
|
+
whiteList: never[];
|
|
64
|
+
};
|
|
65
|
+
auth: {
|
|
66
|
+
ATKey: string;
|
|
67
|
+
RTKey: string;
|
|
68
|
+
};
|
|
69
|
+
notFoundRedirectCount: number;
|
|
70
|
+
};
|
|
45
71
|
export default _default;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|