tango-ui-cw 0.0.1
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/README.md +1 -0
- package/dist/index.js +30 -0
- package/dist/index.mjs +661 -0
- package/package.json +37 -0
- package/src/component/CSSFab/useTangoStyle.jsx +182 -0
- package/src/component/MaterialButton/MaterialButton.css +64 -0
- package/src/component/MaterialButton/index.jsx +58 -0
- package/src/component/MaterialInput/MaterialInput.css +33 -0
- package/src/component/MaterialInput/index.jsx +29 -0
- package/src/component/TButton/TButton.css +270 -0
- package/src/component/TButton/index.jsx +74 -0
- package/src/component/TColorPicker/TColorPicker.css +24 -0
- package/src/component/TColorPicker/index.jsx +106 -0
- package/src/component/TDate/TDate.css +0 -0
- package/src/component/TDate/index.jsx +148 -0
- package/src/component/TDatePicker/TDatePicker.css +13 -0
- package/src/component/TDatePicker/index.jsx +60 -0
- package/src/component/TDrawer/TDrawer.css +202 -0
- package/src/component/TDrawer/index.jsx +74 -0
- package/src/component/TInput/TInput.css +80 -0
- package/src/component/TInput/index.jsx +102 -0
- package/src/component/TLayout/TLayout.css +88 -0
- package/src/component/TLayout/index.jsx +77 -0
- package/src/component/TLine/TLine.css +54 -0
- package/src/component/TLine/index.jsx +57 -0
- package/src/component/TMark/TMark.css +6 -0
- package/src/component/TMark/index.jsx +78 -0
- package/src/component/TModal/TModal.css +108 -0
- package/src/component/TModal/index.jsx +69 -0
- package/src/component/TNotice/TNotice.css +52 -0
- package/src/component/TNotice/index.jsx +38 -0
- package/src/component/TNotice/useNotice.jsx +54 -0
- package/src/component/TSearch/TSearch.css +90 -0
- package/src/component/TSearch/index.jsx +100 -0
- package/src/component/TSpace/TSpace.css +43 -0
- package/src/component/TSpace/index.jsx +60 -0
- package/src/component/TTable/TTable.css +26 -0
- package/src/component/TTable/index.jsx +77 -0
- package/src/component/TTooltip/TTooltip.css +105 -0
- package/src/component/TTooltip/index.jsx +25 -0
- package/src/component/Tango/store.js +105 -0
- package/src/component/Tools/WaterMark/WaterMark.jsx +78 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import PropTypes from "prop-types";
|
3
|
+
import "./TInput.css"; // 引入样式文件
|
4
|
+
import { useTangoStyle } from "../CSSFab/useTangoStyle"; // 导入 useTangoStyle 函数
|
5
|
+
|
6
|
+
export default function Input(props) {
|
7
|
+
const {
|
8
|
+
type = "default",
|
9
|
+
size = "medium",
|
10
|
+
sx = {},
|
11
|
+
style: userStyle = {},
|
12
|
+
className: userClassName = "", // 允许用户自定义 className
|
13
|
+
onClick,
|
14
|
+
onChange,
|
15
|
+
value,
|
16
|
+
defaultValue = "",
|
17
|
+
disabled = false,
|
18
|
+
placeholder = "",
|
19
|
+
maxlength
|
20
|
+
} = props;
|
21
|
+
|
22
|
+
// 使用类名控制输入框的样式
|
23
|
+
const className = `input input-${type} input-${size} ${
|
24
|
+
disabled ? "input-disabled" : ""
|
25
|
+
} ${userClassName}` ;
|
26
|
+
|
27
|
+
// 用于接收 sx 属性设置的属性样式
|
28
|
+
const sxStyle = useTangoStyle(sx);
|
29
|
+
|
30
|
+
// 合并 sx 和 style,确保 style 优先级更高
|
31
|
+
const combinedStyle = { ...sxStyle, ...userStyle };
|
32
|
+
|
33
|
+
// 定义 HTML 的 input type 属性
|
34
|
+
const inputType = type === "password" ? "password" : "text";
|
35
|
+
|
36
|
+
// 判断是否走受控模式
|
37
|
+
const isControlled = value !== undefined && onChange !== undefined;
|
38
|
+
|
39
|
+
// 定义属性类型
|
40
|
+
Input.propTypes = {
|
41
|
+
type: PropTypes.oneOf(["default", "textarea", "password"]),
|
42
|
+
size: PropTypes.oneOf(["small", "medium", "large", "huge"]),
|
43
|
+
sx: PropTypes.object, // 自定义样式对象
|
44
|
+
style: PropTypes.object, // 用户传入的 style 属性,保证 style 属性优先级最高
|
45
|
+
onClick: PropTypes.func, // 点击事件
|
46
|
+
onChange: PropTypes.func, // 输入值变化事件
|
47
|
+
className: PropTypes.string, // 添加 className PropTypes
|
48
|
+
value: PropTypes.string, // 输入框的值
|
49
|
+
defaultValue: PropTypes.string, // 输入框的初始值(非受控模式)
|
50
|
+
disabled: PropTypes.bool, // 是否禁用
|
51
|
+
placeholder: PropTypes.string, // 占位符
|
52
|
+
maxlength: PropTypes.number,
|
53
|
+
};
|
54
|
+
|
55
|
+
// 默认属性
|
56
|
+
Input.defaultProps = {
|
57
|
+
type: "default",
|
58
|
+
size: "medium",
|
59
|
+
sx: {},
|
60
|
+
style: {},
|
61
|
+
onClick: () => {},
|
62
|
+
className: "", // 默认值为空字符串
|
63
|
+
onChange: undefined,
|
64
|
+
value: undefined,
|
65
|
+
defaultValue: "",
|
66
|
+
disabled: false,
|
67
|
+
placeholder: "",
|
68
|
+
maxlength: 524288,
|
69
|
+
};
|
70
|
+
|
71
|
+
return (
|
72
|
+
<div>
|
73
|
+
{type === "textarea" ? (
|
74
|
+
<textarea
|
75
|
+
className={className}
|
76
|
+
style={combinedStyle} // 应用解析后的内联样式
|
77
|
+
onClick={onClick}
|
78
|
+
onChange={onChange && ((e) => onChange(e.target.value))} // 如果提供 onChange,则绑定事件
|
79
|
+
value={isControlled ? value : undefined} // 受控模式
|
80
|
+
defaultValue={!isControlled ? defaultValue : undefined} // 非受控模式
|
81
|
+
disabled={disabled}
|
82
|
+
placeholder={placeholder}
|
83
|
+
maxLength={maxlength}
|
84
|
+
/>
|
85
|
+
) : (
|
86
|
+
<input
|
87
|
+
className={className}
|
88
|
+
style={combinedStyle} // 应用解析后的内联样式
|
89
|
+
type={inputType}
|
90
|
+
onClick={onClick}
|
91
|
+
onChange={onChange && ((e) => onChange(e.target.value))} // 如果提供 onChange,则绑定事件
|
92
|
+
value={isControlled ? value : undefined} // 受控模式
|
93
|
+
defaultValue={!isControlled ? defaultValue : undefined} // 非受控模式
|
94
|
+
disabled={disabled}
|
95
|
+
placeholder={placeholder}
|
96
|
+
maxLength={maxlength}
|
97
|
+
/>
|
98
|
+
)}
|
99
|
+
</div>
|
100
|
+
);
|
101
|
+
};
|
102
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
/* 公共样式 */
|
2
|
+
html,
|
3
|
+
body {
|
4
|
+
height: 100%;
|
5
|
+
margin: 0;
|
6
|
+
padding: 0;
|
7
|
+
box-sizing: border-box;
|
8
|
+
}
|
9
|
+
|
10
|
+
.ultra {
|
11
|
+
display: flex;
|
12
|
+
flex-direction: column;
|
13
|
+
width: 100%;
|
14
|
+
height: 100%;
|
15
|
+
background-color: #f0f2f5;
|
16
|
+
border-radius: 8px;
|
17
|
+
overflow: hidden;
|
18
|
+
}
|
19
|
+
|
20
|
+
.clay-header {
|
21
|
+
background-color: #96d498;
|
22
|
+
color: #fff;
|
23
|
+
text-align: center;
|
24
|
+
height: 64px;
|
25
|
+
line-height: 64px;
|
26
|
+
}
|
27
|
+
|
28
|
+
.clay-main {
|
29
|
+
display: flex;
|
30
|
+
flex: 1; /* 占据剩余高度 */
|
31
|
+
width: 100%; /* 确保子元素水平分布 */
|
32
|
+
min-height: 120px;
|
33
|
+
display: flex;
|
34
|
+
align-items: center;
|
35
|
+
justify-content: center;
|
36
|
+
background-color: #4caf50;
|
37
|
+
color: #fff;
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
.clay-aside {
|
42
|
+
background-color: #6ccb6f;
|
43
|
+
color: #fff;
|
44
|
+
text-align: center;
|
45
|
+
padding: 16px;
|
46
|
+
min-width: 120px;
|
47
|
+
min-height: 120px;
|
48
|
+
display: flex;
|
49
|
+
align-items: center;
|
50
|
+
justify-content: center;
|
51
|
+
}
|
52
|
+
|
53
|
+
.clay-right {
|
54
|
+
flex: 3;
|
55
|
+
display: flex;
|
56
|
+
flex-direction: column; /* 确保右侧是上下布局 */
|
57
|
+
}
|
58
|
+
|
59
|
+
.clay-content {
|
60
|
+
background-color: #4caf50;
|
61
|
+
color: #fff;
|
62
|
+
flex: 1;
|
63
|
+
text-align: center;
|
64
|
+
padding: 16px;
|
65
|
+
min-height: 120px;
|
66
|
+
display: flex;
|
67
|
+
align-items: center;
|
68
|
+
justify-content: center;
|
69
|
+
|
70
|
+
}
|
71
|
+
|
72
|
+
.clay-footer {
|
73
|
+
background-color: #96d498;
|
74
|
+
color: #fff;
|
75
|
+
text-align: center;
|
76
|
+
height: 64px;
|
77
|
+
line-height: 64px;
|
78
|
+
}
|
79
|
+
|
80
|
+
/* 支持自适应布局 */
|
81
|
+
@media (max-width: 768px) {
|
82
|
+
.ultra {
|
83
|
+
flex-direction: column;
|
84
|
+
}
|
85
|
+
.clay-main {
|
86
|
+
flex-direction: column; /* 主内容区域改为竖向排列 */
|
87
|
+
}
|
88
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import PropTypes from "prop-types";
|
3
|
+
import "./TLayout.css";
|
4
|
+
|
5
|
+
// 定义基本组件
|
6
|
+
export const Header = ({ children }) => <header>{children}</header>;
|
7
|
+
export const Aside = ({ children }) => <aside>{children}</aside>;
|
8
|
+
export const Content = ({ children }) => <div>{children}</div>;
|
9
|
+
export const Footer = ({ children }) => <footer>{children}</footer>;
|
10
|
+
|
11
|
+
export default function Layout(props) {
|
12
|
+
const { children, type, className: userClassName = "" } = props;
|
13
|
+
const className = `clay-layout clay-layout-${type} ${userClassName}`;
|
14
|
+
|
15
|
+
return (
|
16
|
+
<div className={className}>
|
17
|
+
{/* 第一种:上中下布局 */}
|
18
|
+
{type === "top-middle-bottom" && (
|
19
|
+
<div className="ultra">
|
20
|
+
<header className="clay-header">{children[0]}</header>
|
21
|
+
<main className="clay-main">{children[1]}</main>
|
22
|
+
<footer className="clay-footer">{children[2]}</footer>
|
23
|
+
</div>
|
24
|
+
)}
|
25
|
+
|
26
|
+
{/* 第二种:上下布局,下部分是左右布局 */}
|
27
|
+
{type === "top-bottom-split" && (
|
28
|
+
<div className="ultra">
|
29
|
+
<header className="clay-header">{children[0]}</header>
|
30
|
+
<main className="clay-main">
|
31
|
+
<aside className="clay-aside">{children[1]}</aside>
|
32
|
+
<div className="clay-content">{children[2]}</div>
|
33
|
+
</main>
|
34
|
+
</div>
|
35
|
+
)}
|
36
|
+
|
37
|
+
{/* 第三种:左右布局,右侧分为上下布局 */}
|
38
|
+
{type === "left-right-top-bottom" && (
|
39
|
+
<>
|
40
|
+
<header className="clay-header">{children[0]}</header>
|
41
|
+
<div className="clay-main">
|
42
|
+
<aside className="clay-aside">{children[1]}</aside>
|
43
|
+
<div className="clay-right">
|
44
|
+
<header className="clay-header">{children[2]}</header>
|
45
|
+
<div className="clay-content">{children[3]}</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</>
|
49
|
+
)}
|
50
|
+
|
51
|
+
{/* 第四种:左右布局,右侧分为上中下布局 */}
|
52
|
+
{type === "left-right-three-parts" && (
|
53
|
+
<div className="ultra">
|
54
|
+
<aside className="clay-aside">{children[0]}</aside>
|
55
|
+
<div className="clay-right">
|
56
|
+
<header className="clay-header">{children[1]}</header>
|
57
|
+
<main className="clay-main">{children[2]}</main>
|
58
|
+
<footer className="clay-footer">{children[3]}</footer>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
)}
|
62
|
+
</div>
|
63
|
+
);
|
64
|
+
};
|
65
|
+
|
66
|
+
Layout.propTypes = {
|
67
|
+
children: PropTypes.node.isRequired,
|
68
|
+
className: PropTypes.string, // 添加 className PropTypes
|
69
|
+
|
70
|
+
type: PropTypes.oneOf([
|
71
|
+
"top-middle-bottom",
|
72
|
+
"top-bottom-split",
|
73
|
+
"left-right-top-bottom",
|
74
|
+
"left-right-three-parts",
|
75
|
+
]).isRequired,
|
76
|
+
};
|
77
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
.line {
|
2
|
+
display: flex;
|
3
|
+
align-items: center;
|
4
|
+
justify-content: center;
|
5
|
+
position: relative;
|
6
|
+
width: 100%;
|
7
|
+
}
|
8
|
+
|
9
|
+
.line-small {
|
10
|
+
margin: 2px 0;
|
11
|
+
}
|
12
|
+
|
13
|
+
.line-medium {
|
14
|
+
margin: 6px 0;
|
15
|
+
}
|
16
|
+
|
17
|
+
.line-large {
|
18
|
+
margin: 10px 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
.line-huge {
|
22
|
+
margin: 15px 0;
|
23
|
+
}
|
24
|
+
|
25
|
+
.line::before,
|
26
|
+
.line::after {
|
27
|
+
content: "";
|
28
|
+
flex: 1;
|
29
|
+
height: 1px;
|
30
|
+
background-color: #b2b2b2;
|
31
|
+
}
|
32
|
+
|
33
|
+
.line::before {
|
34
|
+
margin-right: 10px;
|
35
|
+
}
|
36
|
+
|
37
|
+
.line::after {
|
38
|
+
margin-left: 10px;
|
39
|
+
}
|
40
|
+
|
41
|
+
/* 如果没有文字,移除伪元素的间隔 */
|
42
|
+
.line:not(:has(.line-text))::before,
|
43
|
+
.line:not(:has(.line-text))::after {
|
44
|
+
margin: 0;
|
45
|
+
}
|
46
|
+
|
47
|
+
.line-text {
|
48
|
+
padding: 0 5px;
|
49
|
+
color: inherit; /* 继承文本颜色 */
|
50
|
+
background-color: transparent;
|
51
|
+
white-space: nowrap;
|
52
|
+
z-index: 1;
|
53
|
+
}
|
54
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
// 封装的hr组件
|
2
|
+
import React from "react";
|
3
|
+
import PropTypes from "prop-types";
|
4
|
+
import "./TLine.css"; // 引入样式文件
|
5
|
+
import { useTangoStyle } from "../CSSFab/useTangoStyle"; // 导入 useTangoStyle 函数
|
6
|
+
|
7
|
+
export default function Line(props) {
|
8
|
+
// 接收属性参数,调整Space样式
|
9
|
+
const {
|
10
|
+
sx = {},
|
11
|
+
style: userStyle = {},
|
12
|
+
className: userClassName = "",
|
13
|
+
onClick,
|
14
|
+
children,
|
15
|
+
size = "medium",
|
16
|
+
} = props;
|
17
|
+
|
18
|
+
const className = `line line-${size} ${
|
19
|
+
children ? "line-has-text" : "line-no-text"
|
20
|
+
} ${userClassName}`;
|
21
|
+
|
22
|
+
// 用于接收 sx 属性设置的属性样式
|
23
|
+
const sxStyle = useTangoStyle(sx);
|
24
|
+
|
25
|
+
// 合并 sx 和 style,确保 style 优先级更高
|
26
|
+
const combinedStyle = { ...sxStyle, ...userStyle };
|
27
|
+
|
28
|
+
// 定义属性类型
|
29
|
+
Line.propTypes = {
|
30
|
+
sx: PropTypes.object, // 自定义样式对象
|
31
|
+
size: PropTypes.oneOf(["small", "medium", "large", "huge"]), // 间隔间隙
|
32
|
+
style: PropTypes.object, // 用户传入的 style 属性,保证style属性优先级最高
|
33
|
+
onClick: PropTypes.func, // 点击事件
|
34
|
+
className: PropTypes.string, // 添加 className PropTypes
|
35
|
+
children: PropTypes.node.isRequired, // 按钮内容
|
36
|
+
};
|
37
|
+
|
38
|
+
// 默认属性
|
39
|
+
Line.defaultProps = {
|
40
|
+
sx: {},
|
41
|
+
size: "medium",
|
42
|
+
style: {},
|
43
|
+
onClick: () => {},
|
44
|
+
className: "", // 默认值为空字符串
|
45
|
+
};
|
46
|
+
|
47
|
+
return (
|
48
|
+
<div
|
49
|
+
style={combinedStyle} // 应用合并后的内联样式
|
50
|
+
onClick={onClick}
|
51
|
+
className={className}
|
52
|
+
>
|
53
|
+
{children && <div className="line-text">{children}</div>}
|
54
|
+
</div>
|
55
|
+
);
|
56
|
+
};
|
57
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import PropTypes from "prop-types";
|
3
|
+
import "./TMark.css"; // 引入样式文件
|
4
|
+
import { useTangoStyle } from "../CSSFab/useTangoStyle"; // 导入 useTStyle 函数
|
5
|
+
|
6
|
+
export default function Mark(props) {
|
7
|
+
const {
|
8
|
+
sx = {},
|
9
|
+
style: userStyle = {},
|
10
|
+
className: userClassName = "",
|
11
|
+
onClick,
|
12
|
+
children,
|
13
|
+
color = "default",
|
14
|
+
beauty = false, // 是否使用新样式
|
15
|
+
} = props;
|
16
|
+
|
17
|
+
// 用于接收 sx 属性设置的属性样式
|
18
|
+
const sxStyle = useTangoStyle(sx);
|
19
|
+
|
20
|
+
// 计算背景色
|
21
|
+
const backgroundColor = color === "default" ? "#ffe28d" : color;
|
22
|
+
|
23
|
+
// 默认样式
|
24
|
+
const defaultStyle = {
|
25
|
+
width: "auto",
|
26
|
+
height: "auto",
|
27
|
+
display: "inline-block",
|
28
|
+
};
|
29
|
+
|
30
|
+
// 新样式
|
31
|
+
const newStyle = beauty
|
32
|
+
? {
|
33
|
+
padding: "4px 8px",
|
34
|
+
borderRadius: "4px",
|
35
|
+
}
|
36
|
+
: {};
|
37
|
+
|
38
|
+
// 合并样式,确保 style 最高优先级
|
39
|
+
const combinedStyle = {
|
40
|
+
backgroundColor,
|
41
|
+
...defaultStyle,
|
42
|
+
...newStyle,
|
43
|
+
...sxStyle,
|
44
|
+
...userStyle,
|
45
|
+
};
|
46
|
+
|
47
|
+
return (
|
48
|
+
<div
|
49
|
+
style={combinedStyle}
|
50
|
+
onClick={onClick}
|
51
|
+
className={`mark ${userClassName}`} // 仅保留通用 className
|
52
|
+
>
|
53
|
+
{children}
|
54
|
+
</div>
|
55
|
+
);
|
56
|
+
};
|
57
|
+
|
58
|
+
// 定义属性类型
|
59
|
+
Mark.propTypes = {
|
60
|
+
color: PropTypes.string, // color 支持颜色单词、#999、rgb
|
61
|
+
sx: PropTypes.object, // 自定义样式对象
|
62
|
+
style: PropTypes.object, // 用户传入的 style,保证 style 最高优先级
|
63
|
+
onClick: PropTypes.func, // 点击事件
|
64
|
+
className: PropTypes.string, // 自定义 className
|
65
|
+
children: PropTypes.node.isRequired, // 内容
|
66
|
+
beauty: PropTypes.bool, // 是否应用新样式
|
67
|
+
};
|
68
|
+
|
69
|
+
// 默认属性
|
70
|
+
Mark.defaultProps = {
|
71
|
+
color: "default",
|
72
|
+
sx: {},
|
73
|
+
style: {},
|
74
|
+
onClick: () => {},
|
75
|
+
className: "",
|
76
|
+
beauty: false,
|
77
|
+
};
|
78
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
.modal-overlay {
|
2
|
+
position: fixed;
|
3
|
+
top: 0;
|
4
|
+
left: 0;
|
5
|
+
width: 100vw;
|
6
|
+
height: 100vh;
|
7
|
+
background-color: rgba(0, 0, 0, 0.5);
|
8
|
+
z-index: 999;
|
9
|
+
display: flex;
|
10
|
+
justify-content: center;
|
11
|
+
align-items: center;
|
12
|
+
}
|
13
|
+
|
14
|
+
.modal-container {
|
15
|
+
background-color: #fff;
|
16
|
+
padding: 20px;
|
17
|
+
border-radius: 8px;
|
18
|
+
width: 520px;
|
19
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
20
|
+
position: relative;
|
21
|
+
margin-top: -350px;
|
22
|
+
}
|
23
|
+
|
24
|
+
.modal-header {
|
25
|
+
font-size: 18px;
|
26
|
+
font-weight: bold;
|
27
|
+
margin-bottom: 16px;
|
28
|
+
display: flex;
|
29
|
+
justify-content: space-between;
|
30
|
+
}
|
31
|
+
|
32
|
+
.modal-content {
|
33
|
+
margin-bottom: 24px;
|
34
|
+
}
|
35
|
+
|
36
|
+
.modal-footer {
|
37
|
+
display: flex;
|
38
|
+
justify-content: flex-end;
|
39
|
+
gap: 10px;
|
40
|
+
}
|
41
|
+
|
42
|
+
.modal-closeBtn {
|
43
|
+
background-image: url('../../assets/关闭.png');
|
44
|
+
width: 15px;
|
45
|
+
height: 15px;
|
46
|
+
background-size: 100% 100%;
|
47
|
+
cursor: pointer;
|
48
|
+
}
|
49
|
+
/* 过渡动画 */
|
50
|
+
@keyframes fade-in {
|
51
|
+
from {
|
52
|
+
opacity: 0;
|
53
|
+
}
|
54
|
+
to {
|
55
|
+
opacity: 1;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
@keyframes fade-out {
|
60
|
+
from {
|
61
|
+
opacity: 1;
|
62
|
+
}
|
63
|
+
to {
|
64
|
+
opacity: 0;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
@keyframes scale-in {
|
69
|
+
from {
|
70
|
+
transform: scale(0.8);
|
71
|
+
opacity: 0;
|
72
|
+
}
|
73
|
+
to {
|
74
|
+
transform: scale(1);
|
75
|
+
opacity: 1;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
@keyframes scale-out {
|
80
|
+
from {
|
81
|
+
transform: scale(1);
|
82
|
+
opacity: 1;
|
83
|
+
}
|
84
|
+
to {
|
85
|
+
transform: scale(0.8);
|
86
|
+
opacity: 0;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
.fade-in {
|
91
|
+
animation: fade-in 0.3s ease-in-out;
|
92
|
+
-webkit-animation: fade-in 0.3s ease-in-out;
|
93
|
+
}
|
94
|
+
|
95
|
+
.fade-out {
|
96
|
+
animation: fade-out 0.3s ease-in-out;
|
97
|
+
-webkit-animation: fade-out 0.3s ease-in-out;
|
98
|
+
}
|
99
|
+
|
100
|
+
.scale-in {
|
101
|
+
animation: scale-in 0.3s ease-in-out;
|
102
|
+
-webkit-animation: scale-in 0.3s ease-in-out;
|
103
|
+
}
|
104
|
+
|
105
|
+
.scale-out {
|
106
|
+
animation: scale-out 0.3s ease-in-out;
|
107
|
+
-webkit-animation: scale-out 0.3s ease-in-out;
|
108
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
|
+
import PropTypes from "prop-types";
|
3
|
+
import "./TModal.css";
|
4
|
+
import TButton from "../TButton/index";
|
5
|
+
|
6
|
+
export default function Modal(props) {
|
7
|
+
const { children, title, onText, cancelText, open, onClose } = props;
|
8
|
+
const [showAnimation, setShowAnimation] = useState(false); // 控制是否展示 Modal
|
9
|
+
const [closing, setClosing] = useState(false); // 标志是否在关闭动画中
|
10
|
+
|
11
|
+
Modal.propTypes = {
|
12
|
+
children: PropTypes.node.isRequired,
|
13
|
+
title: PropTypes.string,
|
14
|
+
onText: PropTypes.string,
|
15
|
+
cancelText: PropTypes.string,
|
16
|
+
open: PropTypes.bool,
|
17
|
+
onClose: PropTypes.func,
|
18
|
+
};
|
19
|
+
|
20
|
+
Modal.defaultProps = {
|
21
|
+
title: "基础标题",
|
22
|
+
onText: "确定",
|
23
|
+
cancelText: "取消",
|
24
|
+
open: false,
|
25
|
+
onClose: () => {},
|
26
|
+
};
|
27
|
+
|
28
|
+
// 监听 open 状态的变化
|
29
|
+
useEffect(() => {
|
30
|
+
if (open) {
|
31
|
+
setShowAnimation(true);
|
32
|
+
setClosing(false); // 取消关闭状态
|
33
|
+
} else if (showAnimation) {
|
34
|
+
setClosing(true); // 触发关闭动画
|
35
|
+
setTimeout(() => {
|
36
|
+
setShowAnimation(false); // 关闭动画完成后隐藏
|
37
|
+
setClosing(false);
|
38
|
+
}, 300); // 动画时长 300ms
|
39
|
+
}
|
40
|
+
}, [open]);
|
41
|
+
|
42
|
+
function ok() {
|
43
|
+
console.log("确定按钮");
|
44
|
+
if (onClose) onClose();
|
45
|
+
}
|
46
|
+
|
47
|
+
return (
|
48
|
+
<>
|
49
|
+
{showAnimation && (
|
50
|
+
<div className={`modal-overlay ${closing ? "fade-out" : "fade-in"}`}>
|
51
|
+
<div className={`modal-container ${closing ? "scale-out" : "scale-in"}`}>
|
52
|
+
<div className="modal-header">
|
53
|
+
<h3>{title}</h3>
|
54
|
+
<div className="modal-closeBtn" onClick={onClose}></div>
|
55
|
+
</div>
|
56
|
+
<div className="modal-content">{children}</div>
|
57
|
+
<div className="modal-footer">
|
58
|
+
<TButton type="success" onClick={ok}>
|
59
|
+
{onText}
|
60
|
+
</TButton>
|
61
|
+
<TButton onClick={onClose}>{cancelText}</TButton>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
)}
|
66
|
+
</>
|
67
|
+
);
|
68
|
+
};
|
69
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
.notice {
|
2
|
+
display: inline-flex;
|
3
|
+
align-items: center;
|
4
|
+
justify-content: start;
|
5
|
+
padding: 8px 10px 8px 11px;
|
6
|
+
text-align: left;
|
7
|
+
border-radius: 8px;
|
8
|
+
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
9
|
+
0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
10
|
+
background-color: white;
|
11
|
+
color: black;
|
12
|
+
position: absolute;
|
13
|
+
top: 20px;
|
14
|
+
left: 50%;
|
15
|
+
transform: translateX(-50%);
|
16
|
+
animation: slide-down 0.1s ease-out; /* 保留进入动画 */
|
17
|
+
transition: opacity 0.3s ease-out, transform 0.5s ease-out; /* 用于退出动画 */
|
18
|
+
}
|
19
|
+
|
20
|
+
.notice img {
|
21
|
+
width: 20px;
|
22
|
+
height: 20px;
|
23
|
+
margin-right: 8px;
|
24
|
+
flex-shrink: 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
.notice span {
|
28
|
+
display: inline-block;
|
29
|
+
vertical-align: middle;
|
30
|
+
}
|
31
|
+
|
32
|
+
/* 淡出动画 */
|
33
|
+
.notice.fade-out {
|
34
|
+
opacity: 0;
|
35
|
+
transform: translateX(-50%) translateY(-20px);
|
36
|
+
}
|
37
|
+
|
38
|
+
/* 定义从上到下的进入动画 */
|
39
|
+
@keyframes slide-down {
|
40
|
+
0% {
|
41
|
+
transform: translateX(-50%) translateY(-50px);
|
42
|
+
opacity: 0;
|
43
|
+
}
|
44
|
+
90% {
|
45
|
+
transform: translateX(-50%) translateY(8px);
|
46
|
+
opacity: 1;
|
47
|
+
}
|
48
|
+
100% {
|
49
|
+
transform: translateX(-50%) translateY(0);
|
50
|
+
opacity: 1;
|
51
|
+
}
|
52
|
+
}
|