@yoka-ui/ui 1.0.5 → 1.0.6
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/@Docs-yoka/exports.generated.md +6 -9
- package/README.md +39 -12
- package/dist/es/business/YkLoginModule/SmsLoginForm.d.ts +25 -0
- package/dist/es/business/YkLoginModule/SmsLoginForm.js +178 -0
- package/dist/es/business/YkLoginModule/SmsLoginForm.js.map +7 -0
- package/dist/es/business/YkLoginModule/index.d.ts +48 -0
- package/dist/es/business/YkLoginModule/index.js +198 -0
- package/dist/es/business/YkLoginModule/index.js.map +7 -0
- package/dist/es/business/YkLoginModule/styles.module.less +169 -0
- package/dist/es/business/YkSqlEdit/code-mirror-custom.module.less +154 -0
- package/dist/es/business/YkSqlEdit/index.d.ts +20 -0
- package/dist/es/business/YkSqlEdit/index.js +180 -0
- package/dist/es/business/YkSqlEdit/index.js.map +7 -0
- package/dist/es/business/YkSqlEdit/sql-language.d.ts +11 -0
- package/dist/es/business/YkSqlEdit/sql-language.js +1460 -0
- package/dist/es/business/YkSqlEdit/sql-language.js.map +7 -0
- package/dist/es/creative/GlassSegmentedRadio/index.d.ts +1 -1
- package/dist/es/index.d.ts +4 -2
- package/dist/es/index.js +80 -76
- package/dist/es/index.js.map +2 -2
- package/dist/es/index.less +2 -0
- package/dist/lib/business/YkLoginModule/SmsLoginForm.d.ts +25 -0
- package/dist/lib/business/YkLoginModule/SmsLoginForm.js +171 -0
- package/dist/lib/business/YkLoginModule/SmsLoginForm.js.map +7 -0
- package/dist/lib/business/YkLoginModule/index.d.ts +48 -0
- package/dist/lib/business/YkLoginModule/index.js +206 -0
- package/dist/lib/business/YkLoginModule/index.js.map +7 -0
- package/dist/lib/business/YkLoginModule/styles.module.less +169 -0
- package/dist/lib/business/YkSqlEdit/code-mirror-custom.module.less +154 -0
- package/dist/lib/business/YkSqlEdit/index.d.ts +20 -0
- package/dist/lib/business/YkSqlEdit/index.js +202 -0
- package/dist/lib/business/YkSqlEdit/index.js.map +7 -0
- package/dist/lib/business/YkSqlEdit/sql-language.d.ts +11 -0
- package/dist/lib/business/YkSqlEdit/sql-language.js +1493 -0
- package/dist/lib/business/YkSqlEdit/sql-language.js.map +7 -0
- package/dist/lib/creative/GlassSegmentedRadio/index.d.ts +1 -1
- package/dist/lib/index.d.ts +4 -2
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.js.map +2 -2
- package/dist/lib/index.less +2 -0
- package/package.json +143 -138
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @yoka-ui/ui 具名导出表
|
|
2
2
|
|
|
3
3
|
> **自动生成**:`pnpm run generate:yoka-llms`(源文件 `src/index.tsx`)
|
|
4
|
-
> **包版本**:`1.0.
|
|
5
|
-
> **生成时间**:`2026-04-
|
|
4
|
+
> **包版本**:`1.0.6`
|
|
5
|
+
> **生成时间**:`2026-04-20T11:13:15.402Z`
|
|
6
6
|
> 请勿手动编辑本文件;修改导出请改 `src/index.tsx` 后重新运行上述命令。
|
|
7
7
|
|
|
8
8
|
## 使用约定(给 LLM / 业务开发者)
|
|
9
9
|
|
|
10
|
-
- **包名**:`@yoka-ui/ui`(npm 公网);可选 Git 安装:`@yoka-ui/ui`: `git+http://gitlab.sh.com/web/yoka-ui.git#v1.0.
|
|
10
|
+
- **包名**:`@yoka-ui/ui`(npm 公网);可选 Git 安装:`@yoka-ui/ui`: `git+http://gitlab.sh.com/web/yoka-ui.git#v1.0.6` 等,以实际 tag 为准
|
|
11
11
|
- **导入**:仅使用下表中的**具名**导出(组件/值与 `export type` 类型),与发布产物 `dist/es/index.d.ts` 一致;勿臆造未列出符号。
|
|
12
12
|
- **全局样式(常用)**:业务入口引入一行,例如 `import '@yoka-ui/ui/dist/index.less'`(若以 README / 实际产物路径为准)。
|
|
13
13
|
- **技术栈**:React 18、Ant Design 5;复杂表单/表格优先使用库内封装(`InputTheme`、`TableTheme`、`ModCommonFilter` 等),需要原生 antd API 时查阅 [Ant Design 文档](https://ant.design) 或本仓库 `@Docs/llms.txt`(克隆源码时可用)。
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
| `Editor` | 组件 | `./business/Editor` |
|
|
24
24
|
| `Empty` | 组件 | `./business/Empty` |
|
|
25
25
|
| `ModCommonFilter` | 组件 | `./business/ModCommonFilter` |
|
|
26
|
+
| `YkLoginModule` | 组件 | `./business/YkLoginModule` |
|
|
26
27
|
| `YkPorjectSelect` | 组件 | `./business/YkPorjectSelect` |
|
|
28
|
+
| `YkSqlEdit` | 组件 | `./business/YkSqlEdit` |
|
|
27
29
|
|
|
28
30
|
### Export common components
|
|
29
31
|
|
|
@@ -38,9 +40,6 @@
|
|
|
38
40
|
| `TextWithInput` | 组件 | `./components/TextWithInput` |
|
|
39
41
|
| `TextWithToolTip` | 组件 | `./components/TextWithToolTip` |
|
|
40
42
|
| `TreeTransfer` | 组件 | `./components/TreeTransfer` |
|
|
41
|
-
| `DateRangeValue` | 类型 | `./components/YkDateRangePicker` |
|
|
42
|
-
| `YkDateRangePickerProps` | 类型 | `./components/YkDateRangePicker` |
|
|
43
|
-
| `YkDateRangePickerRef` | 类型 | `./components/YkDateRangePicker` |
|
|
44
43
|
| `YkDateRangePicker` | 组件 | `./components/YkDateRangePicker` |
|
|
45
44
|
| `YkRangeDateWithVS` | 组件 | `./components/YkRangeDateWithVS` |
|
|
46
45
|
| `YkRangeTimeWithRecent` | 组件 | `./components/YkRangeTimeWithRecent` |
|
|
@@ -53,8 +52,6 @@
|
|
|
53
52
|
| `ArcCheckbox` | 组件 | `./creative/ArcCheckbox` |
|
|
54
53
|
| `ButtonRadioWithInfo` | 组件 | `./creative/ButtonRadioWithInfo` |
|
|
55
54
|
| `ButtonWithProgress` | 组件 | `./creative/ButtonWithProgress` |
|
|
56
|
-
| `GlassSegmentedRadioProps` | 类型 | `./creative/GlassSegmentedRadio` |
|
|
57
|
-
| `GlassSegmentOption` | 类型 | `./creative/GlassSegmentedRadio` |
|
|
58
55
|
| `GlassSegmentedRadio` | 组件 | `./creative/GlassSegmentedRadio` |
|
|
59
56
|
|
|
60
57
|
### Export layout
|
|
@@ -96,6 +93,6 @@
|
|
|
96
93
|
## 纯符号列表(便于检索)
|
|
97
94
|
|
|
98
95
|
```
|
|
99
|
-
AiChat, DrawerPageInfo, Editor, Empty, ModCommonFilter, YkPorjectSelect, Clock, DebounceInput, MultipleSelect, NumericInput, RefreshButton, SearchWithHistory, TextWithInput, TextWithToolTip, TreeTransfer,
|
|
96
|
+
AiChat, DrawerPageInfo, Editor, Empty, ModCommonFilter, YkLoginModule, YkPorjectSelect, YkSqlEdit, Clock, DebounceInput, MultipleSelect, NumericInput, RefreshButton, SearchWithHistory, TextWithInput, TextWithToolTip, TreeTransfer, YkDateRangePicker, YkRangeDateWithVS, YkRangeTimeWithRecent, ArcCheckboxProps, ArcCheckbox, ButtonRadioWithInfo, ButtonWithProgress, GlassSegmentedRadio, FlexGrid, YkContainer, YkDrawer, InputTheme, TableTheme, LabelSelect, LogicOperator, YkButton, YkCard, YkCheckbox, YkDescriptions, YkPagination, YkRadio, YkRadioBtnSpecial, YkSegmented, YkSelect, YkSpin, YkStatistic, YkSwitch, YkTabs, YkTooltip
|
|
100
97
|
```
|
|
101
98
|
|
package/README.md
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
##
|
|
1
|
+
## 安装与使用
|
|
2
2
|
|
|
3
3
|
### 安装组件库
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
+
# npm
|
|
7
|
+
npm i @yoka-ui/ui
|
|
8
|
+
|
|
9
|
+
# 或 pnpm
|
|
6
10
|
pnpm add @yoka-ui/ui
|
|
7
11
|
```
|
|
8
12
|
|
|
13
|
+
### (推荐)配置 Cursor / IDE 规则
|
|
14
|
+
|
|
15
|
+
在项目的 Cursor rules 中创建文件 `yoka-ui.mdc`,用于约束导入方式与文档入口(导出清单、LLM 索引等):
|
|
16
|
+
|
|
17
|
+
```md
|
|
18
|
+
---
|
|
19
|
+
description: 使用 @yoka/ui 时的导入与文档约定
|
|
20
|
+
globs: "**/*.{ts,tsx}"
|
|
21
|
+
alwaysApply: false
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# @yoka/ui
|
|
25
|
+
|
|
26
|
+
- **导入**:仅从 `@yoka/ui` 做**具名** `import`,勿使用未在导出表中出现的符号。
|
|
27
|
+
- **导出清单(权威)**:以安装包内 `node_modules/@yoka/ui/@Docs-yoka/exports.generated.md` 为准,与发布产物类型一致;该文件由 yoka-ui 在导出变更后重新生成。
|
|
28
|
+
- **LLM / 助手入口**:同目录 `node_modules/@yoka/ui/@Docs-yoka/llms.txt`(索引、外链与使用规则摘要)。
|
|
29
|
+
- **API**:不确定 props 时查组件库 Storybook / 源码示例或 README,勿臆造 API;原生 antd 行为对照 [Ant Design 文档](https://ant.design)。
|
|
30
|
+
- **全局样式**:业务入口按需引入,常见为 `import '@yoka/ui/dist/index.less'`(以 `@yoka/ui` 的 README 与当前产物路径为准)。
|
|
31
|
+
```
|
|
32
|
+
|
|
9
33
|
### 基础用法
|
|
10
34
|
|
|
11
35
|
```tsx
|
|
@@ -21,13 +45,14 @@ function App() {
|
|
|
21
45
|
}
|
|
22
46
|
```
|
|
23
47
|
|
|
24
|
-
###
|
|
48
|
+
### 导入约定(重要)
|
|
25
49
|
|
|
26
50
|
```tsx
|
|
27
|
-
//
|
|
28
|
-
import {
|
|
51
|
+
// 仅从包入口做“具名导入”
|
|
52
|
+
import { YkPorjectSelect } from "@yoka-ui/ui";
|
|
29
53
|
|
|
30
|
-
//
|
|
54
|
+
// 具体有哪些符号可用,以 @Docs-yoka/exports.generated.md 为准
|
|
55
|
+
// node_modules/@yoka-ui/ui/@Docs-yoka/exports.generated.md
|
|
31
56
|
```
|
|
32
57
|
|
|
33
58
|
## 组件列表
|
|
@@ -96,13 +121,15 @@ import { YkButton } from "@yoka-ui/ui";
|
|
|
96
121
|
|
|
97
122
|
### 业务组件 (`src/business/`)
|
|
98
123
|
|
|
99
|
-
| 组件 | 说明
|
|
100
|
-
| --------------- |
|
|
101
|
-
| DrawerPageInfo | 抽屉页面信息
|
|
102
|
-
| Editor | 富文本编辑器
|
|
103
|
-
| Empty | 空状态
|
|
104
|
-
| ModCommonFilter | 通用筛选器
|
|
105
|
-
|
|
|
124
|
+
| 组件 | 说明 |
|
|
125
|
+
| --------------- | -------------------------------------------------------------------- |
|
|
126
|
+
| DrawerPageInfo | 抽屉页面信息 |
|
|
127
|
+
| Editor | 富文本编辑器 |
|
|
128
|
+
| Empty | 空状态 |
|
|
129
|
+
| ModCommonFilter | 通用筛选器 |
|
|
130
|
+
| YkLoginModule | 登录组件(当前支持:扫码 / 短信验证码;Tabs 由 `tabs` 配置) |
|
|
131
|
+
| YkPorjectSelect | 项目选择器(搜索、关注、在营/关服 Tab、最近访问等) |
|
|
132
|
+
| YkSqlEdit | SQL 编辑器(CodeMirror 6:动态参数 `{{...}}` 高亮、关键词/函数补全) |
|
|
106
133
|
|
|
107
134
|
**导出说明**:业务项目里 `import { ... } from '@yoka-ui/ui'` 可用的符号以 `[src/index.tsx](src/index.tsx)` 为准。若某组件已在源码目录中实现但尚未在入口文件中 `export`,需先在组件库补齐导出并发布后,再在业务侧引用。
|
|
108
135
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type SmsLoginFormApi = {
|
|
3
|
+
sendSms: (payload: Record<string, unknown>) => Promise<{
|
|
4
|
+
code: number;
|
|
5
|
+
msg: string;
|
|
6
|
+
}>;
|
|
7
|
+
};
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
sliderVerify: (element: 'pop' | HTMLElement, success: (res: {
|
|
11
|
+
token: string;
|
|
12
|
+
captchaVerification: string;
|
|
13
|
+
}) => void, options: {
|
|
14
|
+
appKey: string;
|
|
15
|
+
locale?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export type SmsLoginFormProps = {
|
|
20
|
+
inputClassName: string;
|
|
21
|
+
api: SmsLoginFormApi;
|
|
22
|
+
appKey: string;
|
|
23
|
+
};
|
|
24
|
+
declare const SmsLogin: React.FC<SmsLoginFormProps>;
|
|
25
|
+
export default SmsLogin;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __async = (__this, __arguments, generator) => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
var fulfilled = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
step(generator.next(value));
|
|
22
|
+
} catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var rejected = (value) => {
|
|
27
|
+
try {
|
|
28
|
+
step(generator.throw(value));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
reject(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
34
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/business/YkLoginModule/SmsLoginForm.tsx
|
|
39
|
+
import { LockOutlined, MobileOutlined } from "@ant-design/icons";
|
|
40
|
+
import { ProFormText } from "@ant-design/pro-form";
|
|
41
|
+
import { Button, message } from "antd";
|
|
42
|
+
import React, { useEffect, useState } from "react";
|
|
43
|
+
import styles from "./styles.module.less";
|
|
44
|
+
var SmsLogin = ({ inputClassName, api, appKey }) => {
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const link = document.createElement("link");
|
|
47
|
+
link.href = "https://captchaoss.sanguosha.com/library/captcha/0.0.5/lib/index.css";
|
|
48
|
+
link.rel = "stylesheet";
|
|
49
|
+
document.head.appendChild(link);
|
|
50
|
+
const script = document.createElement("script");
|
|
51
|
+
script.src = "https://captchaoss.sanguosha.com/library/captcha/0.0.5/lib/index.js";
|
|
52
|
+
document.body.appendChild(script);
|
|
53
|
+
return () => {
|
|
54
|
+
document.head.removeChild(link);
|
|
55
|
+
document.body.removeChild(script);
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
const [phone, setPhone] = useState("");
|
|
59
|
+
const [countdown, setCountdown] = useState(0);
|
|
60
|
+
const [sendStatus, setSendStatus] = useState(false);
|
|
61
|
+
const disableBtn = countdown > 0 || phone.length !== 11;
|
|
62
|
+
const sendVerificationCode = () => __async(void 0, null, function* () {
|
|
63
|
+
try {
|
|
64
|
+
const phoneNumber = phone;
|
|
65
|
+
if (!phoneNumber) {
|
|
66
|
+
message.error("请输入手机号");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!/^1\d{10}$/.test(phoneNumber)) {
|
|
70
|
+
message.error("请输入正确的手机号格式");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!appKey) {
|
|
74
|
+
message.error("验证码配置缺失,请联系管理员(appKey)");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
window.sliderVerify(
|
|
78
|
+
"pop",
|
|
79
|
+
(captchaRes) => {
|
|
80
|
+
void api.sendSms(__spreadValues({ phone: phoneNumber }, captchaRes)).then((sendRes) => {
|
|
81
|
+
if (sendRes.code !== 200) {
|
|
82
|
+
message.error(sendRes.msg);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setSendStatus(true);
|
|
86
|
+
message.success("验证码发送成功");
|
|
87
|
+
setCountdown(60);
|
|
88
|
+
const timer = setInterval(() => {
|
|
89
|
+
setCountdown((prevCountdown) => {
|
|
90
|
+
if (prevCountdown <= 1) {
|
|
91
|
+
clearInterval(timer);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
return prevCountdown - 1;
|
|
95
|
+
});
|
|
96
|
+
}, 1e3);
|
|
97
|
+
}).catch(() => {
|
|
98
|
+
message.error("验证码发送失败,请稍后再试");
|
|
99
|
+
return;
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
appKey
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
message.error("发送验证码失败,请稍后再试");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
111
|
+
ProFormText,
|
|
112
|
+
{
|
|
113
|
+
className: inputClassName,
|
|
114
|
+
name: "phone",
|
|
115
|
+
fieldProps: {
|
|
116
|
+
size: "large",
|
|
117
|
+
prefix: /* @__PURE__ */ React.createElement(MobileOutlined, null),
|
|
118
|
+
onChange: (e) => {
|
|
119
|
+
setPhone(e.target.value);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
placeholder: "请输入手机号",
|
|
123
|
+
validateTrigger: "onBlur",
|
|
124
|
+
rules: [
|
|
125
|
+
{
|
|
126
|
+
required: true,
|
|
127
|
+
validator(rule, value, _callback) {
|
|
128
|
+
if (!value) {
|
|
129
|
+
return Promise.reject("手机号是必填项!");
|
|
130
|
+
}
|
|
131
|
+
if (!/^1\d{10}$/.test(value)) {
|
|
132
|
+
return Promise.reject("请输入正确的手机号格式");
|
|
133
|
+
}
|
|
134
|
+
return Promise.resolve();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
), /* @__PURE__ */ React.createElement(
|
|
140
|
+
ProFormText,
|
|
141
|
+
{
|
|
142
|
+
className: inputClassName,
|
|
143
|
+
name: "code",
|
|
144
|
+
disabled: !sendStatus,
|
|
145
|
+
fieldProps: {
|
|
146
|
+
size: "large",
|
|
147
|
+
prefix: /* @__PURE__ */ React.createElement(LockOutlined, null),
|
|
148
|
+
suffix: /* @__PURE__ */ React.createElement(
|
|
149
|
+
Button,
|
|
150
|
+
{
|
|
151
|
+
className: styles.captchaBtn,
|
|
152
|
+
style: { fontSize: 16 },
|
|
153
|
+
onClick: (e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
countdown === 0 && void sendVerificationCode();
|
|
157
|
+
},
|
|
158
|
+
type: "link",
|
|
159
|
+
disabled: disableBtn
|
|
160
|
+
},
|
|
161
|
+
countdown > 0 ? `${countdown}秒后重新获取` : "获取验证码"
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
placeholder: "请输入验证码",
|
|
165
|
+
rules: [
|
|
166
|
+
{
|
|
167
|
+
required: true,
|
|
168
|
+
message: "验证码是必填项!"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
));
|
|
173
|
+
};
|
|
174
|
+
var SmsLoginForm_default = SmsLogin;
|
|
175
|
+
export {
|
|
176
|
+
SmsLoginForm_default as default
|
|
177
|
+
};
|
|
178
|
+
//# sourceMappingURL=SmsLoginForm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/business/YkLoginModule/SmsLoginForm.tsx"],
|
|
4
|
+
"sourcesContent": ["import { LockOutlined, MobileOutlined } from '@ant-design/icons';\nimport { ProFormText } from '@ant-design/pro-form';\nimport { Button, message } from 'antd';\nimport React, { useEffect, useState } from 'react';\nimport styles from './styles.module.less';\n\nexport type SmsLoginFormApi = {\n sendSms: (payload: Record<string, unknown>) => Promise<{ code: number; msg: string }>;\n};\n\ndeclare global {\n interface Window {\n sliderVerify: (\n element: 'pop' | HTMLElement,\n success: (res: { token: string; captchaVerification: string }) => void,\n options: { appKey: string; locale?: string },\n ) => void;\n }\n}\n\nexport type SmsLoginFormProps = {\n inputClassName: string;\n api: SmsLoginFormApi;\n appKey: string;\n};\n\nconst SmsLogin: React.FC<SmsLoginFormProps> = ({ inputClassName, api, appKey }) => {\n useEffect(() => {\n const link = document.createElement('link');\n link.href = 'https://captchaoss.sanguosha.com/library/captcha/0.0.5/lib/index.css';\n link.rel = 'stylesheet';\n document.head.appendChild(link);\n\n const script = document.createElement('script');\n script.src = 'https://captchaoss.sanguosha.com/library/captcha/0.0.5/lib/index.js';\n document.body.appendChild(script);\n return () => {\n document.head.removeChild(link);\n document.body.removeChild(script);\n };\n }, []);\n\n const [phone, setPhone] = useState<string>('');\n const [countdown, setCountdown] = useState<number>(0);\n const [sendStatus, setSendStatus] = useState<boolean>(false);\n const disableBtn = countdown > 0 || phone.length !== 11;\n\n const sendVerificationCode = async () => {\n try {\n const phoneNumber = phone;\n if (!phoneNumber) {\n message.error('请输入手机号');\n return;\n }\n\n if (!/^1\\d{10}$/.test(phoneNumber)) {\n message.error('请输入正确的手机号格式');\n return;\n }\n if (!appKey) {\n message.error('验证码配置缺失,请联系管理员(appKey)');\n return;\n }\n window.sliderVerify(\n 'pop',\n (captchaRes: Record<string, unknown>) => {\n void api\n .sendSms({ phone: phoneNumber, ...captchaRes })\n .then((sendRes) => {\n if (sendRes.code !== 200) {\n message.error(sendRes.msg);\n return;\n }\n setSendStatus(true);\n message.success('验证码发送成功');\n setCountdown(60);\n const timer = setInterval(() => {\n setCountdown((prevCountdown) => {\n if (prevCountdown <= 1) {\n clearInterval(timer);\n return 0;\n }\n return prevCountdown - 1;\n });\n }, 1000);\n })\n .catch(() => {\n message.error('验证码发送失败,请稍后再试');\n return;\n });\n },\n {\n appKey,\n },\n );\n } catch {\n message.error('发送验证码失败,请稍后再试');\n }\n };\n\n return (\n <>\n <ProFormText\n className={inputClassName}\n name='phone'\n fieldProps={{\n size: 'large',\n prefix: <MobileOutlined />,\n onChange: (e) => {\n setPhone(e.target.value);\n },\n }}\n placeholder='请输入手机号'\n validateTrigger='onBlur'\n rules={[\n {\n required: true,\n validator(rule, value, _callback) {\n if (!value) {\n return Promise.reject('手机号是必填项!');\n }\n if (!/^1\\d{10}$/.test(value)) {\n return Promise.reject('请输入正确的手机号格式');\n }\n return Promise.resolve();\n },\n },\n ]}\n />\n <ProFormText\n className={inputClassName}\n name='code'\n disabled={!sendStatus}\n fieldProps={{\n size: 'large',\n prefix: <LockOutlined />,\n suffix: (\n <Button\n className={styles.captchaBtn}\n style={{ fontSize: 16 }}\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n countdown === 0 && void sendVerificationCode();\n }}\n type='link'\n disabled={disableBtn}\n >\n {countdown > 0 ? `${countdown}秒后重新获取` : '获取验证码'}\n </Button>\n ),\n }}\n placeholder='请输入验证码'\n rules={[\n {\n required: true,\n message: '验证码是必填项!',\n },\n ]}\n />\n </>\n );\n};\n\nexport default SmsLogin;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type SmsLoginFormApi } from './SmsLoginForm';
|
|
3
|
+
export type LoginType = 'qrcode' | 'sms';
|
|
4
|
+
export type LoginTabItem = {
|
|
5
|
+
key: LoginType;
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export type QrcodeApi = {
|
|
9
|
+
getQrcode: () => Promise<{
|
|
10
|
+
qrcode: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}>;
|
|
13
|
+
scanLogin: (qrcode: string) => Promise<{
|
|
14
|
+
code: number;
|
|
15
|
+
data?: {
|
|
16
|
+
status?: number;
|
|
17
|
+
msg?: string;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
export type LoginModuleProps = {
|
|
22
|
+
title: React.ReactNode;
|
|
23
|
+
tabs: LoginTabItem[];
|
|
24
|
+
defaultLoginType: LoginType;
|
|
25
|
+
containerStyle?: React.CSSProperties;
|
|
26
|
+
actions?: React.ReactNode | ((loginType: LoginType) => React.ReactNode);
|
|
27
|
+
className?: string;
|
|
28
|
+
formClassName?: string;
|
|
29
|
+
tabsClassName?: string;
|
|
30
|
+
inputClassName?: string;
|
|
31
|
+
qrcodeClassName?: string;
|
|
32
|
+
qrcodeBoxClassName?: string;
|
|
33
|
+
onFinish: (values: Record<string, unknown>) => Promise<void>;
|
|
34
|
+
qrcode?: {
|
|
35
|
+
api: QrcodeApi;
|
|
36
|
+
icon: string;
|
|
37
|
+
iconSize?: number;
|
|
38
|
+
size?: number;
|
|
39
|
+
wrapStyle?: React.CSSProperties;
|
|
40
|
+
onSuccess: () => void | Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
sms?: {
|
|
43
|
+
api: SmsLoginFormApi;
|
|
44
|
+
appKey: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
declare const LoginModule: React.FC<LoginModuleProps>;
|
|
48
|
+
export default LoginModule;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
var __async = (__this, __arguments, generator) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
var fulfilled = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
step(generator.next(value));
|
|
6
|
+
} catch (e) {
|
|
7
|
+
reject(e);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var rejected = (value) => {
|
|
11
|
+
try {
|
|
12
|
+
step(generator.throw(value));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
reject(e);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
18
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/business/YkLoginModule/index.tsx
|
|
23
|
+
import { LoginForm } from "@ant-design/pro-form";
|
|
24
|
+
import { Flex, message, QRCode, Tabs } from "antd";
|
|
25
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
26
|
+
import SmsLoginForm from "./SmsLoginForm";
|
|
27
|
+
import styles from "./styles.module.less";
|
|
28
|
+
var DEFAULT_QRCODE_WRAP_STYLE = {
|
|
29
|
+
padding: 10,
|
|
30
|
+
display: "flex",
|
|
31
|
+
justifyContent: "center"
|
|
32
|
+
};
|
|
33
|
+
var LoginModule = (props) => {
|
|
34
|
+
var _a, _b, _c;
|
|
35
|
+
const {
|
|
36
|
+
title,
|
|
37
|
+
tabs,
|
|
38
|
+
defaultLoginType,
|
|
39
|
+
containerStyle,
|
|
40
|
+
actions,
|
|
41
|
+
onFinish,
|
|
42
|
+
qrcode,
|
|
43
|
+
className = styles.root,
|
|
44
|
+
formClassName = styles.form,
|
|
45
|
+
tabsClassName = styles.tabs,
|
|
46
|
+
inputClassName,
|
|
47
|
+
qrcodeClassName = styles.qrcode,
|
|
48
|
+
qrcodeBoxClassName = styles.qrcodeBox,
|
|
49
|
+
sms
|
|
50
|
+
} = props;
|
|
51
|
+
const [loginType, setLoginType] = useState(defaultLoginType);
|
|
52
|
+
const [qrcodeStatus, setQrcodeStatus] = useState("active");
|
|
53
|
+
const [qrcodeUrl, setQrcodeUrl] = useState("-");
|
|
54
|
+
const [qrcodeValue, setQrcodeValue] = useState("");
|
|
55
|
+
const pollIntervalRef = useRef(null);
|
|
56
|
+
const pollTimeoutRef = useRef(null);
|
|
57
|
+
const shouldShowSubmitter = loginType !== "qrcode";
|
|
58
|
+
const resolvedActions = useMemo(() => {
|
|
59
|
+
if (!actions)
|
|
60
|
+
return void 0;
|
|
61
|
+
return typeof actions === "function" ? actions(loginType) : actions;
|
|
62
|
+
}, [actions, loginType]);
|
|
63
|
+
const safeClearTimers = useCallback(() => {
|
|
64
|
+
if (pollIntervalRef.current)
|
|
65
|
+
clearInterval(pollIntervalRef.current);
|
|
66
|
+
if (pollTimeoutRef.current)
|
|
67
|
+
clearTimeout(pollTimeoutRef.current);
|
|
68
|
+
pollIntervalRef.current = null;
|
|
69
|
+
pollTimeoutRef.current = null;
|
|
70
|
+
}, []);
|
|
71
|
+
const handleQrcodeRefresh = useCallback(() => __async(void 0, null, function* () {
|
|
72
|
+
if (!qrcode)
|
|
73
|
+
return;
|
|
74
|
+
setQrcodeStatus("loading");
|
|
75
|
+
try {
|
|
76
|
+
const res = yield qrcode.api.getQrcode();
|
|
77
|
+
setQrcodeUrl(res.url);
|
|
78
|
+
setQrcodeValue(res.qrcode);
|
|
79
|
+
setQrcodeStatus("active");
|
|
80
|
+
} catch (e) {
|
|
81
|
+
setQrcodeStatus("expired");
|
|
82
|
+
message.error("获取二维码失败,请稍后重试");
|
|
83
|
+
}
|
|
84
|
+
}), [qrcode]);
|
|
85
|
+
const enabledTabKeys = useMemo(() => new Set(tabs.map((t) => t.key)), [tabs]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
var _a2;
|
|
88
|
+
if (enabledTabKeys.has(defaultLoginType)) {
|
|
89
|
+
setLoginType(defaultLoginType);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const fallback = (_a2 = tabs[0]) == null ? void 0 : _a2.key;
|
|
93
|
+
if (fallback)
|
|
94
|
+
setLoginType(fallback);
|
|
95
|
+
}, [defaultLoginType, enabledTabKeys, tabs]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
var _a2;
|
|
98
|
+
if (enabledTabKeys.has(loginType))
|
|
99
|
+
return;
|
|
100
|
+
const fallback = (_a2 = tabs[0]) == null ? void 0 : _a2.key;
|
|
101
|
+
if (fallback)
|
|
102
|
+
setLoginType(fallback);
|
|
103
|
+
}, [enabledTabKeys, loginType, tabs]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
safeClearTimers();
|
|
106
|
+
if (loginType !== "qrcode") {
|
|
107
|
+
setQrcodeStatus("expired");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!qrcode) {
|
|
111
|
+
setQrcodeStatus("expired");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
void handleQrcodeRefresh();
|
|
115
|
+
return safeClearTimers;
|
|
116
|
+
}, [handleQrcodeRefresh, loginType, qrcode, safeClearTimers]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
safeClearTimers();
|
|
119
|
+
if (!qrcode)
|
|
120
|
+
return;
|
|
121
|
+
if (loginType !== "qrcode")
|
|
122
|
+
return;
|
|
123
|
+
if (qrcodeStatus !== "active")
|
|
124
|
+
return;
|
|
125
|
+
if (!qrcodeValue)
|
|
126
|
+
return;
|
|
127
|
+
pollIntervalRef.current = setInterval(() => {
|
|
128
|
+
void qrcode.api.scanLogin(qrcodeValue).then((res) => {
|
|
129
|
+
var _a2, _b2, _c2;
|
|
130
|
+
if (res.code !== 200)
|
|
131
|
+
return;
|
|
132
|
+
const status = (_a2 = res.data) == null ? void 0 : _a2.status;
|
|
133
|
+
const msg = (_c2 = (_b2 = res.data) == null ? void 0 : _b2.msg) != null ? _c2 : "扫码遇到问题,稍后重试";
|
|
134
|
+
if (status === -1)
|
|
135
|
+
return;
|
|
136
|
+
if (status !== 1) {
|
|
137
|
+
safeClearTimers();
|
|
138
|
+
setQrcodeStatus("expired");
|
|
139
|
+
message.error(msg, 3);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
safeClearTimers();
|
|
143
|
+
void qrcode.onSuccess();
|
|
144
|
+
}).catch(() => {
|
|
145
|
+
});
|
|
146
|
+
}, 1e3);
|
|
147
|
+
pollTimeoutRef.current = setTimeout(() => {
|
|
148
|
+
setQrcodeStatus("expired");
|
|
149
|
+
safeClearTimers();
|
|
150
|
+
}, 3e4);
|
|
151
|
+
return safeClearTimers;
|
|
152
|
+
}, [loginType, qrcode, qrcodeStatus, qrcodeValue, safeClearTimers]);
|
|
153
|
+
const tabItems = useMemo(() => tabs.map((t) => ({ key: t.key, label: t.label })), [tabs]);
|
|
154
|
+
return /* @__PURE__ */ React.createElement("div", { className, style: { maxWidth: "500px" } }, /* @__PURE__ */ React.createElement(
|
|
155
|
+
LoginForm,
|
|
156
|
+
{
|
|
157
|
+
className: formClassName,
|
|
158
|
+
onFinish: (values) => __async(void 0, null, function* () {
|
|
159
|
+
yield onFinish(values);
|
|
160
|
+
}),
|
|
161
|
+
title: typeof title === "string" ? /* @__PURE__ */ React.createElement("div", { className: styles.title }, title) : title,
|
|
162
|
+
containerStyle,
|
|
163
|
+
submitter: shouldShowSubmitter ? void 0 : false,
|
|
164
|
+
actions: resolvedActions
|
|
165
|
+
},
|
|
166
|
+
/* @__PURE__ */ React.createElement(
|
|
167
|
+
Tabs,
|
|
168
|
+
{
|
|
169
|
+
className: tabsClassName,
|
|
170
|
+
centered: true,
|
|
171
|
+
activeKey: loginType,
|
|
172
|
+
onChange: (activeKey) => {
|
|
173
|
+
const next = activeKey;
|
|
174
|
+
setLoginType(next);
|
|
175
|
+
},
|
|
176
|
+
items: tabItems
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
enabledTabKeys.has("qrcode") && loginType === "qrcode" && qrcode && /* @__PURE__ */ React.createElement("div", { style: (_a = qrcode.wrapStyle) != null ? _a : DEFAULT_QRCODE_WRAP_STYLE }, /* @__PURE__ */ React.createElement("div", { className: qrcodeBoxClassName }, /* @__PURE__ */ React.createElement(Flex, { justify: "center", align: "center" }, /* @__PURE__ */ React.createElement("i", { className: "iconfont icon-saoyisao", style: { color: "#999999", marginRight: 10, fontSize: 14 } }), "请使用", /* @__PURE__ */ React.createElement("span", { style: { color: "#0A6BFD" } }, "小闪扫码"), "登录"), /* @__PURE__ */ React.createElement(
|
|
180
|
+
QRCode,
|
|
181
|
+
{
|
|
182
|
+
icon: qrcode.icon,
|
|
183
|
+
iconSize: (_b = qrcode.iconSize) != null ? _b : 50,
|
|
184
|
+
className: qrcodeClassName,
|
|
185
|
+
size: (_c = qrcode.size) != null ? _c : 240,
|
|
186
|
+
value: qrcodeUrl,
|
|
187
|
+
status: qrcodeStatus,
|
|
188
|
+
onRefresh: handleQrcodeRefresh
|
|
189
|
+
}
|
|
190
|
+
))),
|
|
191
|
+
enabledTabKeys.has("sms") && loginType === "sms" && sms && /* @__PURE__ */ React.createElement(SmsLoginForm, { inputClassName: inputClassName != null ? inputClassName : "", api: sms.api, appKey: sms.appKey })
|
|
192
|
+
));
|
|
193
|
+
};
|
|
194
|
+
var YkLoginModule_default = LoginModule;
|
|
195
|
+
export {
|
|
196
|
+
YkLoginModule_default as default
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/business/YkLoginModule/index.tsx"],
|
|
4
|
+
"sourcesContent": ["import { LoginForm } from '@ant-design/pro-form';\nimport { Flex, message, QRCode, Tabs } from 'antd';\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport SmsLoginForm, { type SmsLoginFormApi } from './SmsLoginForm';\nimport styles from './styles.module.less';\n\nexport type LoginType = 'qrcode' | 'sms';\n\nexport type LoginTabItem = {\n key: LoginType;\n label: React.ReactNode;\n};\n\nexport type QrcodeApi = {\n getQrcode: () => Promise<{ qrcode: string; url: string }>;\n scanLogin: (qrcode: string) => Promise<{\n code: number;\n data?: {\n status?: number;\n msg?: string;\n };\n }>;\n};\n\nexport type LoginModuleProps = {\n title: React.ReactNode;\n tabs: LoginTabItem[];\n defaultLoginType: LoginType;\n containerStyle?: React.CSSProperties;\n actions?: React.ReactNode | ((loginType: LoginType) => React.ReactNode);\n className?: string;\n formClassName?: string;\n tabsClassName?: string;\n inputClassName?: string;\n qrcodeClassName?: string;\n qrcodeBoxClassName?: string;\n\n onFinish: (values: Record<string, unknown>) => Promise<void>;\n\n qrcode?: {\n api: QrcodeApi;\n icon: string;\n iconSize?: number;\n size?: number;\n wrapStyle?: React.CSSProperties;\n onSuccess: () => void | Promise<void>;\n };\n\n sms?: {\n api: SmsLoginFormApi;\n appKey: string;\n };\n};\n\nconst DEFAULT_QRCODE_WRAP_STYLE: React.CSSProperties = {\n padding: 10,\n display: 'flex',\n justifyContent: 'center',\n};\n\nconst LoginModule: React.FC<LoginModuleProps> = (props) => {\n const {\n title,\n tabs,\n defaultLoginType,\n containerStyle,\n actions,\n onFinish,\n qrcode,\n className = styles.root,\n formClassName = styles.form,\n tabsClassName = styles.tabs,\n inputClassName,\n qrcodeClassName = styles.qrcode,\n qrcodeBoxClassName = styles.qrcodeBox,\n sms,\n } = props;\n\n const [loginType, setLoginType] = useState<LoginType>(defaultLoginType);\n\n const [qrcodeStatus, setQrcodeStatus] = useState<'active' | 'expired' | 'loading'>('active');\n const [qrcodeUrl, setQrcodeUrl] = useState<string>('-');\n const [qrcodeValue, setQrcodeValue] = useState<string>('');\n const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const shouldShowSubmitter = loginType !== 'qrcode';\n\n const resolvedActions = useMemo(() => {\n if (!actions) return undefined;\n return typeof actions === 'function' ? actions(loginType) : actions;\n }, [actions, loginType]);\n\n const safeClearTimers = useCallback(() => {\n if (pollIntervalRef.current) clearInterval(pollIntervalRef.current);\n if (pollTimeoutRef.current) clearTimeout(pollTimeoutRef.current);\n pollIntervalRef.current = null;\n pollTimeoutRef.current = null;\n }, []);\n\n const handleQrcodeRefresh = useCallback(async () => {\n if (!qrcode) return;\n setQrcodeStatus('loading');\n try {\n const res = await qrcode.api.getQrcode();\n setQrcodeUrl(res.url);\n setQrcodeValue(res.qrcode);\n setQrcodeStatus('active');\n } catch {\n setQrcodeStatus('expired');\n message.error('获取二维码失败,请稍后重试');\n }\n }, [qrcode]);\n\n const enabledTabKeys = useMemo(() => new Set(tabs.map((t) => t.key)), [tabs]);\n\n useEffect(() => {\n if (enabledTabKeys.has(defaultLoginType)) {\n setLoginType(defaultLoginType);\n return;\n }\n const fallback = tabs[0]?.key;\n if (fallback) setLoginType(fallback);\n }, [defaultLoginType, enabledTabKeys, tabs]);\n\n useEffect(() => {\n if (enabledTabKeys.has(loginType)) return;\n const fallback = tabs[0]?.key;\n if (fallback) setLoginType(fallback);\n }, [enabledTabKeys, loginType, tabs]);\n\n // 切到扫码登录时刷新二维码;离开扫码登录时停止轮询并标记过期\n useEffect(() => {\n safeClearTimers();\n if (loginType !== 'qrcode') {\n setQrcodeStatus('expired');\n return;\n }\n if (!qrcode) {\n setQrcodeStatus('expired');\n return;\n }\n void handleQrcodeRefresh();\n return safeClearTimers;\n }, [handleQrcodeRefresh, loginType, qrcode, safeClearTimers]);\n\n // 扫码轮询:仅在二维码“激活 + 有值 + 在扫码 Tab”时启动\n useEffect(() => {\n safeClearTimers();\n if (!qrcode) return;\n if (loginType !== 'qrcode') return;\n if (qrcodeStatus !== 'active') return;\n if (!qrcodeValue) return;\n\n pollIntervalRef.current = setInterval(() => {\n void qrcode.api\n .scanLogin(qrcodeValue)\n .then((res) => {\n if (res.code !== 200) return;\n\n const status = res.data?.status;\n const msg = res.data?.msg ?? '扫码遇到问题,稍后重试';\n\n if (status === -1) return;\n if (status !== 1) {\n safeClearTimers();\n setQrcodeStatus('expired');\n message.error(msg, 3);\n return;\n }\n safeClearTimers();\n void qrcode.onSuccess();\n })\n .catch(() => {\n // 忽略轮询中的偶发网络错误,保持继续轮询直到超时/成功\n });\n }, 1000);\n\n pollTimeoutRef.current = setTimeout(() => {\n setQrcodeStatus('expired');\n safeClearTimers();\n }, 30000);\n\n return safeClearTimers;\n }, [loginType, qrcode, qrcodeStatus, qrcodeValue, safeClearTimers]);\n\n const tabItems = useMemo(() => tabs.map((t) => ({ key: t.key, label: t.label })), [tabs]);\n\n return (\n <div className={className} style={{ maxWidth: '500px' }}>\n <LoginForm\n className={formClassName}\n onFinish={async (values) => {\n await onFinish(values);\n }}\n title={typeof title === 'string' ? <div className={styles.title}>{title}</div> : title}\n containerStyle={containerStyle}\n submitter={shouldShowSubmitter ? undefined : (false as const)}\n actions={resolvedActions}\n >\n <Tabs\n className={tabsClassName}\n centered\n activeKey={loginType}\n onChange={(activeKey) => {\n const next = activeKey as LoginType;\n setLoginType(next);\n }}\n items={tabItems}\n />\n\n {enabledTabKeys.has('qrcode') && loginType === 'qrcode' && qrcode && (\n <div style={qrcode.wrapStyle ?? DEFAULT_QRCODE_WRAP_STYLE}>\n <div className={qrcodeBoxClassName}>\n <Flex justify='center' align='center'>\n <i className='iconfont icon-saoyisao' style={{ color: '#999999', marginRight: 10, fontSize: 14 }}></i>\n 请使用<span style={{ color: '#0A6BFD' }}>小闪扫码</span>登录\n </Flex>\n <QRCode\n icon={qrcode.icon}\n iconSize={qrcode.iconSize ?? 50}\n className={qrcodeClassName}\n size={qrcode.size ?? 240}\n value={qrcodeUrl}\n status={qrcodeStatus}\n onRefresh={handleQrcodeRefresh}\n />\n </div>\n </div>\n )}\n\n {enabledTabKeys.has('sms') && loginType === 'sms' && sms && (\n <SmsLoginForm inputClassName={inputClassName ?? ''} api={sms.api} appKey={sms.appKey} />\n )}\n </LoginForm>\n </div>\n );\n};\n\nexport default LoginModule;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["_a", "_b", "_c"]
|
|
7
|
+
}
|