Commit 914531b047ccf888d2ec26014567b5d178b4756d
1 parent
82127a72
feat:增加qx-btn按钮,qx-progress进度条,搜索框,导出功能。
Showing
16 changed files
with
1155 additions
and
9 deletions
@@ -10,6 +10,7 @@ | @@ -10,6 +10,7 @@ | ||
10 | "license": "MIT", | 10 | "license": "MIT", |
11 | "dependencies": { | 11 | "dependencies": { |
12 | "@ant-design/icons": "^5.2.5", | 12 | "@ant-design/icons": "^5.2.5", |
13 | + "@qx/icon-btn": "^0.0.13", | ||
13 | "@qx/ui": "0.0.3-beta.1", | 14 | "@qx/ui": "0.0.3-beta.1", |
14 | "ahooks": "^3.7.5", | 15 | "ahooks": "^3.7.5", |
15 | "classnames": "^2.3.2", | 16 | "classnames": "^2.3.2", |
@@ -2883,10 +2884,9 @@ | @@ -2883,10 +2884,9 @@ | ||
2883 | } | 2884 | } |
2884 | }, | 2885 | }, |
2885 | "node_modules/@qx/icon-btn": { | 2886 | "node_modules/@qx/icon-btn": { |
2886 | - "version": "0.0.1", | ||
2887 | - "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.1.tgz", | ||
2888 | - "integrity": "sha1-pW/sqh+v5hyHoxsFkjmpMsumyMk=", | ||
2889 | - "dev": true, | 2887 | + "version": "0.0.13", |
2888 | + "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.13.tgz", | ||
2889 | + "integrity": "sha512-y3i0ngFuO22I4jv5ovgOR5nDI9X+64xuie22cKfLeNjI9IZhmNBn+BtDaG7wE9DPXMyxDH0sFBhlsya22hTaqA==", | ||
2890 | "license": "MIT" | 2890 | "license": "MIT" |
2891 | }, | 2891 | }, |
2892 | "node_modules/@qx/ui": { | 2892 | "node_modules/@qx/ui": { |
@@ -2936,6 +2936,13 @@ | @@ -2936,6 +2936,13 @@ | ||
2936 | "react-cookies": ">=0.1.1" | 2936 | "react-cookies": ">=0.1.1" |
2937 | } | 2937 | } |
2938 | }, | 2938 | }, |
2939 | + "node_modules/@qx/utils/node_modules/@qx/icon-btn": { | ||
2940 | + "version": "0.0.1", | ||
2941 | + "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.1.tgz", | ||
2942 | + "integrity": "sha1-pW/sqh+v5hyHoxsFkjmpMsumyMk=", | ||
2943 | + "dev": true, | ||
2944 | + "license": "MIT" | ||
2945 | + }, | ||
2939 | "node_modules/@rc-component/color-picker": { | 2946 | "node_modules/@rc-component/color-picker": { |
2940 | "version": "1.4.1", | 2947 | "version": "1.4.1", |
2941 | "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz", | 2948 | "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz", |
@@ -22525,10 +22532,9 @@ | @@ -22525,10 +22532,9 @@ | ||
22525 | } | 22532 | } |
22526 | }, | 22533 | }, |
22527 | "@qx/icon-btn": { | 22534 | "@qx/icon-btn": { |
22528 | - "version": "0.0.1", | ||
22529 | - "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.1.tgz", | ||
22530 | - "integrity": "sha1-pW/sqh+v5hyHoxsFkjmpMsumyMk=", | ||
22531 | - "dev": true | 22535 | + "version": "0.0.13", |
22536 | + "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.13.tgz", | ||
22537 | + "integrity": "sha512-y3i0ngFuO22I4jv5ovgOR5nDI9X+64xuie22cKfLeNjI9IZhmNBn+BtDaG7wE9DPXMyxDH0sFBhlsya22hTaqA==" | ||
22532 | }, | 22538 | }, |
22533 | "@qx/ui": { | 22539 | "@qx/ui": { |
22534 | "version": "0.0.3-beta.1", | 22540 | "version": "0.0.3-beta.1", |
@@ -22559,6 +22565,14 @@ | @@ -22559,6 +22565,14 @@ | ||
22559 | "qiankun": "^2.2.1", | 22565 | "qiankun": "^2.2.1", |
22560 | "umi-request": "^1.4.0", | 22566 | "umi-request": "^1.4.0", |
22561 | "yet-another-abortcontroller-polyfill": "^0.0.4" | 22567 | "yet-another-abortcontroller-polyfill": "^0.0.4" |
22568 | + }, | ||
22569 | + "dependencies": { | ||
22570 | + "@qx/icon-btn": { | ||
22571 | + "version": "0.0.1", | ||
22572 | + "resolved": "http://registry.npm.qgutech.com/@qx/icon-btn/download/@qx/icon-btn-0.0.1.tgz", | ||
22573 | + "integrity": "sha1-pW/sqh+v5hyHoxsFkjmpMsumyMk=", | ||
22574 | + "dev": true | ||
22575 | + } | ||
22562 | } | 22576 | } |
22563 | }, | 22577 | }, |
22564 | "@rc-component/color-picker": { | 22578 | "@rc-component/color-picker": { |
@@ -44,6 +44,7 @@ | @@ -44,6 +44,7 @@ | ||
44 | }, | 44 | }, |
45 | "dependencies": { | 45 | "dependencies": { |
46 | "@ant-design/icons": "^5.2.5", | 46 | "@ant-design/icons": "^5.2.5", |
47 | + "@qx/icon-btn": "^0.0.13", | ||
47 | "@qx/ui": "0.0.3-beta.1", | 48 | "@qx/ui": "0.0.3-beta.1", |
48 | "ahooks": "^3.7.5", | 49 | "ahooks": "^3.7.5", |
49 | "classnames": "^2.3.2", | 50 | "classnames": "^2.3.2", |
@@ -10,5 +10,9 @@ export * from './qx-app-selector'; | @@ -10,5 +10,9 @@ export * from './qx-app-selector'; | ||
10 | export * from './utils'; | 10 | export * from './utils'; |
11 | export * from './qx-field'; | 11 | export * from './qx-field'; |
12 | export * from './qx-field-setter'; | 12 | export * from './qx-field-setter'; |
13 | +export * from './qx-btn'; | ||
14 | +export * from './qx-progress'; | ||
15 | +export * from './qx-search-input'; | ||
16 | +export * from './qx-dynamic-component'; | ||
17 | + | ||
13 | 18 | ||
14 | -// export * from './qx-btn'; |
src/qx-btn/btn-icon.js
0 → 100644
1 | +import { createFromIconfontCN } from '@ant-design/icons/lib'; | ||
2 | + | ||
3 | +/** | ||
4 | + * 使用: | ||
5 | + * import QxIcon from '@/packages/qx-icon'; | ||
6 | + * <QxIcon type="xxx"/> | ||
7 | + * 说明: | ||
8 | + * `xxx`为图标唯一标识,`iconfont.cn`对应图标下的“复制代码”所得,建议命名为“icon-xxx_yyy”, | ||
9 | + * “icon-”为固定前缀,xxx为模块类型,yyy为图标名。公共类的xxx命名为“comm” | ||
10 | + * eg: <QxIcon type="icon-flow_eye"/> | ||
11 | + * | ||
12 | + * @type {React.FC<IconFontProps<string>>} | ||
13 | + */ | ||
14 | +const QxIcon = createFromIconfontCN({ | ||
15 | + scriptUrl: [ | ||
16 | + // require('./svg/icon-font-btn.js'),//功能按钮 专用图标 | ||
17 | + // '//at.alicdn.com/t/font_2917361_i07tun4416.js' | ||
18 | + ], | ||
19 | +}); | ||
20 | + | ||
21 | +export default QxIcon; |
src/qx-btn/icon.js
0 → 100644
1 | +import { createFromIconfontCN } from '@ant-design/icons/lib'; | ||
2 | + | ||
3 | +/** | ||
4 | + * 使用: | ||
5 | + * import QxIcon from '@/packages/qx-icon'; | ||
6 | + * <QxIcon type="xxx"/> | ||
7 | + * 说明: | ||
8 | + * `xxx`为图标唯一标识,`iconfont.cn`对应图标下的“复制代码”所得,建议命名为“icon-xxx_yyy”, | ||
9 | + * “icon-”为固定前缀,xxx为模块类型,yyy为图标名。公共类的xxx命名为“comm” | ||
10 | + * eg: <QxIcon type="icon-flow_eye"/> | ||
11 | + * | ||
12 | + * @type {React.FC<IconFontProps<string>>} | ||
13 | + */ | ||
14 | +const QxIcon = createFromIconfontCN({ | ||
15 | + scriptUrl: [require('@qx/icon-btn')], | ||
16 | +}); | ||
17 | + | ||
18 | +export default QxIcon; |
src/qx-btn/index.less
0 → 100644
1 | +.qx-btn(@btn-color) { | ||
2 | + color: @btn-color; | ||
3 | + background-color: #fff; | ||
4 | + border-color: @btn-color; | ||
5 | + | ||
6 | + &:hover, | ||
7 | + &:focus { | ||
8 | + color: @btn-color; | ||
9 | + background-color: #fff; | ||
10 | + border-color: @btn-color; | ||
11 | + } | ||
12 | + | ||
13 | + &:active { | ||
14 | + color: @btn-color; | ||
15 | + background-color: #fff; | ||
16 | + border-color: @btn-color; | ||
17 | + } | ||
18 | + &.ant-btn-primary { | ||
19 | + color: #fff; | ||
20 | + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); | ||
21 | + background: @btn-color; | ||
22 | + border-color: @btn-color; | ||
23 | + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); | ||
24 | + | ||
25 | + &:hover, | ||
26 | + &:focus { | ||
27 | + color: #fff; | ||
28 | + background: lighten(@btn-color, 10); | ||
29 | + border-color: lighten(@btn-color, 10); | ||
30 | + } | ||
31 | + | ||
32 | + &:active { | ||
33 | + color: #fff; | ||
34 | + background: darken(@btn-color, 7); | ||
35 | + border-color: darken(@btn-color, 7); | ||
36 | + } | ||
37 | + } | ||
38 | + | ||
39 | + &.ant-tooltip-disabled-compatible-wrapper, | ||
40 | + &.ant-btn-link, | ||
41 | + &.ant-btn-text { | ||
42 | + color: @btn-color; | ||
43 | + background: transparent; | ||
44 | + border-color: transparent; | ||
45 | + box-shadow: none; | ||
46 | + | ||
47 | + &:hover, | ||
48 | + &:focus { | ||
49 | + color: darken(@btn-color, 5); | ||
50 | + background: transparent; | ||
51 | + border-color: transparent; | ||
52 | + } | ||
53 | + | ||
54 | + &:active { | ||
55 | + color: darken(@btn-color, 7); | ||
56 | + background: transparent; | ||
57 | + border-color: transparent; | ||
58 | + } | ||
59 | + } | ||
60 | + | ||
61 | + &.ant-btn-dashed { | ||
62 | + color: @btn-color; | ||
63 | + border-color: @btn-color; | ||
64 | + } | ||
65 | +} | ||
66 | + | ||
67 | +@qx_btn_blue: #1764ff; | ||
68 | +@qx_btn_green: #52c41a; | ||
69 | +@qx_btn_orange: #faad14; | ||
70 | +@qx_btn_red: #f5222d; | ||
71 | +@qx_btn_purple: #722ed1; | ||
72 | +@qx_btn_cyan: #13c2c2; | ||
73 | +.qx-btn-blue { | ||
74 | + .qx-btn(@qx_btn_blue); | ||
75 | +} | ||
76 | + | ||
77 | +.qx-btn-green { | ||
78 | + .qx-btn(@qx_btn_green); | ||
79 | +} | ||
80 | + | ||
81 | +.qx-btn-orange { | ||
82 | + .qx-btn(@qx_btn_orange); | ||
83 | +} | ||
84 | + | ||
85 | +.qx-btn-red { | ||
86 | + .qx-btn(@qx_btn_red); | ||
87 | +} | ||
88 | + | ||
89 | +.qx-btn-purple { | ||
90 | + .qx-btn(@qx_btn_purple); | ||
91 | +} | ||
92 | + | ||
93 | +.qx-btn-cyan { | ||
94 | + .qx-btn(@qx_btn_cyan); | ||
95 | +} | ||
96 | + | ||
97 | +.qx-core-drop_down { | ||
98 | + .ant-dropdown-menu-item-icon { | ||
99 | + min-width: 0; | ||
100 | + } | ||
101 | +} | ||
102 | + | ||
103 | +// 更多操作样式 迭代 | ||
104 | +.ant-btn.ant-dropdown-trigger { | ||
105 | + .qx-core-dropdown-arrow.anticon { | ||
106 | + margin-right: 4px; | ||
107 | + } | ||
108 | + | ||
109 | + &.ant-dropdown-open { | ||
110 | + .qx-core-dropdown-arrow.anticon { | ||
111 | + transform: rotate(180deg); | ||
112 | + } | ||
113 | + } | ||
114 | +} |
src/qx-btn/index.tsx
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { Button, Tooltip, Dropdown, Menu } from 'antd'; | ||
3 | +import './index.less'; | ||
4 | +import { | ||
5 | + CloseCircleOutlined, | ||
6 | + CloseOutlined, | ||
7 | + DeleteOutlined, | ||
8 | + EditOutlined, | ||
9 | + EllipsisOutlined, | ||
10 | + MinusCircleOutlined, | ||
11 | + MinusOutlined, | ||
12 | + PlusCircleOutlined, | ||
13 | + PlusOutlined, | ||
14 | + ToolOutlined, | ||
15 | + SettingOutlined, | ||
16 | + CheckOutlined, | ||
17 | + CopyOutlined, | ||
18 | + ImportOutlined, | ||
19 | + ExportOutlined, | ||
20 | + FileTextOutlined, | ||
21 | + PlayCircleOutlined, | ||
22 | + PauseCircleOutlined, | ||
23 | + AppstoreOutlined, | ||
24 | + BarsOutlined, | ||
25 | + AlertOutlined, | ||
26 | + EyeOutlined, | ||
27 | + // DownOutlined, | ||
28 | + PrinterOutlined, | ||
29 | +} from '@ant-design/icons'; | ||
30 | +import QxIcon from './icon'; | ||
31 | +import type { DropDownProps as DropdownProps, ButtonProps, TooltipProps } from 'antd/lib'; | ||
32 | + | ||
33 | +export const BUTTON_ICONS_MAP: { [index: string]: React.ReactElement } = { | ||
34 | + PlusOutlined: <PlusOutlined />, | ||
35 | + MinusOutlined: <MinusOutlined />, | ||
36 | + PlusCircleOutlined: <PlusCircleOutlined />, | ||
37 | + MinusCircleOutlined: <MinusCircleOutlined />, | ||
38 | + CloseCircleOutlined: <CloseCircleOutlined />, | ||
39 | + CloseOutlined: <CloseOutlined />, | ||
40 | + CheckOutlined: <CheckOutlined />, | ||
41 | + DeleteOutlined: <DeleteOutlined />, | ||
42 | + EyeOutlined: <EyeOutlined />, | ||
43 | + EditOutlined: <EditOutlined />, | ||
44 | + FileTextOutlined: <FileTextOutlined />, | ||
45 | + EllipsisOutlined: <EllipsisOutlined />, | ||
46 | + SettingOutlined: <SettingOutlined />, | ||
47 | + ToolOutlined: <ToolOutlined />, | ||
48 | + CopyOutlined: <CopyOutlined />, | ||
49 | + ImportOutlined: <ImportOutlined />, | ||
50 | + ExportOutlined: <ExportOutlined />, | ||
51 | + PlayCircleOutlined: <PlayCircleOutlined />, | ||
52 | + PauseCircleOutlined: <PauseCircleOutlined />, | ||
53 | + AppstoreOutlined: <AppstoreOutlined />, | ||
54 | + BarsOutlined: <BarsOutlined />, | ||
55 | + AlertOutlined: <AlertOutlined />, | ||
56 | + PrinterOutlined: <PrinterOutlined />, | ||
57 | +}; | ||
58 | +export const BUTTON_COLORS = { | ||
59 | + blue: '#1764FF', | ||
60 | + green: '#52c41a', | ||
61 | + orange: '#faad14', | ||
62 | + red: '#f5222d', | ||
63 | + purple: '#722ed1', | ||
64 | + cyan: '#13c2c2', | ||
65 | +}; | ||
66 | + | ||
67 | +//'#1890ff' primary | ||
68 | +//'#52c41a' success | ||
69 | +//'#faad14' Warning | ||
70 | +// #f5222d error | ||
71 | +/*@purple-base: #722ed1; | ||
72 | +@cyan-base: #13c2c2;*/ | ||
73 | + | ||
74 | +export interface DropdownMenuItem { | ||
75 | + code?: string; | ||
76 | + name?: string; | ||
77 | + icon?: string; | ||
78 | +} | ||
79 | + | ||
80 | +export interface QxButtonProps extends ButtonProps { | ||
81 | + name?: string; | ||
82 | + color?: string; | ||
83 | + batch?: boolean; //冗余属性 | ||
84 | + flag?: string; //冗余属性 | ||
85 | + action?: string; //冗余属性 | ||
86 | + line?: boolean; //冗余属性 | ||
87 | + code?: string; //冗余属性 | ||
88 | + needConfirm?: boolean; //冗余属性 | ||
89 | + tooltip?: string; | ||
90 | + /** | ||
91 | + * 下拉菜单选项 | ||
92 | + */ | ||
93 | + dropdownMenuItems?: DropdownMenuItem[]; | ||
94 | + /** | ||
95 | + * 下拉菜单点击事件 | ||
96 | + */ | ||
97 | + handleMenuClick?: (...args: any) => void; | ||
98 | + /** | ||
99 | + * 下拉菜单参数 | ||
100 | + */ | ||
101 | + dropdownProps?: DropdownProps; | ||
102 | + /** | ||
103 | + * 是否显示下拉菜单,默认 true | ||
104 | + */ | ||
105 | + showDropdown?: boolean; | ||
106 | + // 提示框相关参数 | ||
107 | + tooltipProps?: TooltipProps | undefined; | ||
108 | + extraDom?: React.ReactNode | undefined; // 额外标签 | ||
109 | +} | ||
110 | + | ||
111 | +const Icon = ({ name }) => { | ||
112 | + if (name?.$$typeof) return name; | ||
113 | + return name ? ( | ||
114 | + String(name).indexOf('http') > -1 ? ( | ||
115 | + <img | ||
116 | + src={String(name)} | ||
117 | + style={{ | ||
118 | + width: '16px', | ||
119 | + height: '16px', | ||
120 | + marginRight: '4px', | ||
121 | + verticalAlign: 'text-bottom', | ||
122 | + borderRadius: '50%', | ||
123 | + }} | ||
124 | + alt="" | ||
125 | + /> | ||
126 | + ) : String(name).indexOf('-') > -1 ? ( | ||
127 | + <QxIcon type={String(name)} /> | ||
128 | + ) : ( | ||
129 | + BUTTON_ICONS_MAP[name + ''] || <PlusOutlined /> | ||
130 | + ) | ||
131 | + ) : null; | ||
132 | +}; | ||
133 | + | ||
134 | +export const QxButton: React.FC<QxButtonProps> = (props) => { | ||
135 | + const { | ||
136 | + name, | ||
137 | + style, | ||
138 | + icon, | ||
139 | + batch, | ||
140 | + line, | ||
141 | + needConfirm, | ||
142 | + className = '', | ||
143 | + flag, | ||
144 | + color, | ||
145 | + tooltip = '', | ||
146 | + dropdownMenuItems = [], | ||
147 | + handleMenuClick, | ||
148 | + dropdownProps = {}, | ||
149 | + showDropdown = true, | ||
150 | + tooltipProps, | ||
151 | + extraDom, | ||
152 | + ...rest | ||
153 | + } = props; | ||
154 | + const showMenu = Array.isArray(dropdownMenuItems) && dropdownMenuItems?.length && showDropdown; | ||
155 | + | ||
156 | + const getMenuItem = (_menuItems: any) => | ||
157 | + _menuItems?.map((menu) => { | ||
158 | + return { | ||
159 | + ...menu, | ||
160 | + label: menu?.label || menu?.name, | ||
161 | + key: menu?.key || menu?.code, | ||
162 | + children: getMenuItem(menu?.dropdownMenuItems), | ||
163 | + }; | ||
164 | + }); | ||
165 | + | ||
166 | + const generateMenu = (items: any) => { | ||
167 | + return getMenuItem(items)?.map((item) => { | ||
168 | + if (item.children?.length) { | ||
169 | + return ( | ||
170 | + <Menu.SubMenu | ||
171 | + icon={ | ||
172 | + <span> | ||
173 | + <Icon name={item.icon} /> | ||
174 | + </span> | ||
175 | + } | ||
176 | + title={item.label} | ||
177 | + key={item.key} | ||
178 | + disabled={!!item?.disabled} | ||
179 | + > | ||
180 | + {generateMenu(item.children)} | ||
181 | + </Menu.SubMenu> | ||
182 | + ); | ||
183 | + } else { | ||
184 | + return ( | ||
185 | + <Menu.Item | ||
186 | + icon={ | ||
187 | + <span> | ||
188 | + <Icon name={item.icon} /> | ||
189 | + </span> | ||
190 | + } | ||
191 | + key={item.key} | ||
192 | + onClick={() => handleMenuClick(item)} | ||
193 | + disabled={!!item?.disabled} | ||
194 | + > | ||
195 | + {item.label} | ||
196 | + </Menu.Item> | ||
197 | + ); | ||
198 | + } | ||
199 | + }); | ||
200 | + }; | ||
201 | + | ||
202 | + const menu = () => <Menu>{generateMenu(dropdownMenuItems)}</Menu>; | ||
203 | + const btnChildren = name ? name : props.children; | ||
204 | + | ||
205 | + const button = !!extraDom ? ( | ||
206 | + <Button | ||
207 | + className={'qx-btn ' + className + ' ' + (color ? `qx-btn-${color}` : '')} | ||
208 | + {...rest} | ||
209 | + icon={<Icon name={icon} />} | ||
210 | + > | ||
211 | + {showMenu && btnChildren && props?.type !== 'link' ? ( | ||
212 | + <> | ||
213 | + <QxIcon type={'icon-common-arrow'} className={'qx-core-dropdown-arrow'} /> | ||
214 | + {btnChildren} | ||
215 | + </> | ||
216 | + ) : ( | ||
217 | + btnChildren | ||
218 | + )} | ||
219 | + {extraDom} | ||
220 | + </Button> | ||
221 | + ) : ( | ||
222 | + <Button | ||
223 | + className={'qx-btn ' + className + ' ' + (color ? `qx-btn-${color}` : '')} | ||
224 | + {...rest} | ||
225 | + icon={<Icon name={icon} />} | ||
226 | + > | ||
227 | + {showMenu && btnChildren && props?.type !== 'link' ? ( | ||
228 | + <> | ||
229 | + <QxIcon type={'icon-common-arrow'} className={'qx-core-dropdown-arrow'} /> | ||
230 | + {btnChildren} | ||
231 | + </> | ||
232 | + ) : ( | ||
233 | + btnChildren | ||
234 | + )} | ||
235 | + </Button> | ||
236 | + ); | ||
237 | + | ||
238 | + const _dropdownProps: Omit<DropdownProps, 'overlay'> = { | ||
239 | + overlayClassName: 'qx-core-drop_down', | ||
240 | + ...dropdownProps, | ||
241 | + }; | ||
242 | + | ||
243 | + const content = showMenu ? ( | ||
244 | + <Dropdown overlay={menu} {..._dropdownProps}> | ||
245 | + {button} | ||
246 | + </Dropdown> | ||
247 | + ) : ( | ||
248 | + button | ||
249 | + ); | ||
250 | + | ||
251 | + // extraDom 有额外标签时 tooltip不生效 | ||
252 | + if (tooltip && !extraDom) { | ||
253 | + return ( | ||
254 | + <Tooltip {...(tooltipProps || {})} title={tooltip}> | ||
255 | + {content} | ||
256 | + </Tooltip> | ||
257 | + ); | ||
258 | + } | ||
259 | + | ||
260 | + return content; | ||
261 | +}; |
src/qx-dynamic-component/fetchComponent.ts
0 → 100644
1 | +import type { ReactNode } from 'react'; | ||
2 | + | ||
3 | +const cache = {}; | ||
4 | +/** | ||
5 | + * | ||
6 | + * @param url 组件地址 | ||
7 | + * @param packages 依赖 | ||
8 | + * @param name 组件名称 | ||
9 | + * @param fileServer | ||
10 | + * @returns | ||
11 | + */ | ||
12 | +export async function fetchComponent( | ||
13 | + url: string, | ||
14 | + name: string, | ||
15 | + packages: Record<string, any>, | ||
16 | + fileServer: boolean, | ||
17 | + request?: any, | ||
18 | +) { | ||
19 | + if (cache[url]) { | ||
20 | + const cacheModule = await cache[url]; | ||
21 | + | ||
22 | + if (name && !cacheModule[name]) { | ||
23 | + throw new Error('component not found'); | ||
24 | + } | ||
25 | + | ||
26 | + return { | ||
27 | + default: cacheModule[name || 'default'], | ||
28 | + }; | ||
29 | + } | ||
30 | + | ||
31 | + let resolve; | ||
32 | + let reject; | ||
33 | + | ||
34 | + cache[url] = new Promise((_resolve, _reject) => { | ||
35 | + resolve = _resolve; | ||
36 | + reject = _reject; | ||
37 | + }); | ||
38 | + | ||
39 | + try { | ||
40 | + let _url = url; | ||
41 | + if (fileServer && !url.startsWith('http')) { | ||
42 | + const data = await getFileServerPath(request); | ||
43 | + if (data) { | ||
44 | + _url = data?.endpoint + url; | ||
45 | + } | ||
46 | + } | ||
47 | + | ||
48 | + const text = await fetch(_url).then((a) => { | ||
49 | + if (!a.ok) { | ||
50 | + throw new Error('Network response was not ok'); | ||
51 | + } | ||
52 | + return a.text(); | ||
53 | + }); | ||
54 | + | ||
55 | + const module = getParsedModule(text, packages); | ||
56 | + | ||
57 | + const result = { default: {} }; | ||
58 | + | ||
59 | + if (module.exports?.$$typeof || typeof module.exports === 'function') { | ||
60 | + module.exports.default = module.exports as ReactNode; | ||
61 | + } | ||
62 | + | ||
63 | + if (!module.exports?.default && !name) { | ||
64 | + throw new Error('module default is undefined'); | ||
65 | + } | ||
66 | + | ||
67 | + result.default = module.exports[name || 'default']; | ||
68 | + | ||
69 | + if (!result.default) { | ||
70 | + throw new Error('component not found'); | ||
71 | + } | ||
72 | + | ||
73 | + resolve(module.exports); | ||
74 | + | ||
75 | + return result; | ||
76 | + } catch (error) { | ||
77 | + reject(error); | ||
78 | + throw new Error(error); | ||
79 | + } | ||
80 | +} | ||
81 | + | ||
82 | +function getParsedModule(code: string, packages: Record<string, any>) { | ||
83 | + const module: { | ||
84 | + exports: { | ||
85 | + default?: React.ReactNode; | ||
86 | + $$typeof?: any; | ||
87 | + }; | ||
88 | + } = { | ||
89 | + exports: {}, | ||
90 | + }; | ||
91 | + | ||
92 | + const require = (name: string) => { | ||
93 | + return packages[name]; | ||
94 | + }; | ||
95 | + | ||
96 | + try { | ||
97 | + Function('require, exports, module, process', code)( | ||
98 | + require, | ||
99 | + module.exports, | ||
100 | + module, | ||
101 | + window.process || process, | ||
102 | + ); | ||
103 | + } catch (error) { | ||
104 | + throw new Error(error.message); | ||
105 | + } | ||
106 | + | ||
107 | + return module; | ||
108 | +} | ||
109 | + | ||
110 | +function getFileServerPath(request?) { | ||
111 | + return ((window as any)?.qx?.sdk?.request || request)?.get?.( | ||
112 | + '/qgyun-service-fs-manager/fileSetting/get', | ||
113 | + { | ||
114 | + headers: { | ||
115 | + businessCode: 'design', | ||
116 | + }, | ||
117 | + }, | ||
118 | + ); | ||
119 | +} | ||
120 | + | ||
121 | +export function isMobile() { | ||
122 | + return 'ontouchstart' in document.documentElement; | ||
123 | +} |
src/qx-dynamic-component/index.md
0 → 100644
1 | +--- | ||
2 | +nav: | ||
3 | + path: /component | ||
4 | + title: 组件 | ||
5 | + order: 1 | ||
6 | +group: | ||
7 | + path: /view | ||
8 | + title: 业务组件 | ||
9 | + order: 0 | ||
10 | +--- | ||
11 | + | ||
12 | +## QxDynamicComponent | ||
13 | + | ||
14 | +### 默认导出 | ||
15 | + | ||
16 | +```tsx | ||
17 | +import React from 'react'; | ||
18 | +import { QxDynamicComponent } from '@qx/common'; | ||
19 | + | ||
20 | +export default () => ( | ||
21 | + <QxDynamicComponent url="http://10.9.1.180/qx-apaaspublic/widgets/default/@qx-cw/demo1/index.umd.min.js" /> | ||
22 | +); | ||
23 | +``` | ||
24 | + | ||
25 | +### 导出多个组件 | ||
26 | + | ||
27 | +```tsx | ||
28 | +import React from 'react'; | ||
29 | +import { QxDynamicComponent } from '@qx/common'; | ||
30 | + | ||
31 | +export default () => ( | ||
32 | + <> | ||
33 | + <QxDynamicComponent | ||
34 | + url="http://192.168.1.182/qx-apaaspublic/widgets/default/@qx-cw/demo2/index.umd.min.js" | ||
35 | + component="QxDemo21" | ||
36 | + /> | ||
37 | + <QxDynamicComponent | ||
38 | + url="http://192.168.1.182/qx-apaaspublic/widgets/default/@qx-cw/demo2/index.umd.min.js" | ||
39 | + component="QxDemo22" | ||
40 | + /> | ||
41 | + </> | ||
42 | +); | ||
43 | +``` | ||
44 | + | ||
45 | +<API></API> |
src/qx-dynamic-component/index.tsx
0 → 100644
1 | +import React, { useMemo } from 'react'; | ||
2 | +import ReactDOM from 'react-dom'; | ||
3 | +import { Skeleton } from 'antd'; | ||
4 | +import { fetchComponent, isMobile } from './fetchComponent'; | ||
5 | + | ||
6 | +const packages = { | ||
7 | + react: React, | ||
8 | + 'react-dom': ReactDOM, | ||
9 | +}; | ||
10 | + | ||
11 | +export const QxDynamicComponent: React.FC<QxDynamicComponentProps> = (props) => { | ||
12 | + const { config = {} } = props; | ||
13 | + | ||
14 | + const { | ||
15 | + profile: _profile = {}, | ||
16 | + skeleton = true, | ||
17 | + component, | ||
18 | + fileServer = false, | ||
19 | + request, | ||
20 | + url, | ||
21 | + ...rest | ||
22 | + } = getComponentConfig(); | ||
23 | + | ||
24 | + const profile = useMemo(() => ({ ...rest, ..._profile }), [_profile, rest]); | ||
25 | + | ||
26 | + const Comp = useMemo<React.FC<typeof profile>>(() => { | ||
27 | + return React.lazy(async () => { | ||
28 | + try { | ||
29 | + return await fetchComponent(url, component, packages, fileServer, request); | ||
30 | + } catch (error) { | ||
31 | + console.warn(error); | ||
32 | + return { | ||
33 | + default: () => <span style={{ color: 'red' }}>加载错误: {error.message}</span>, | ||
34 | + }; | ||
35 | + } | ||
36 | + }); | ||
37 | + }, [url]); | ||
38 | + | ||
39 | + function getComponentConfig(key?: string) { | ||
40 | + const mergedConfig: QxDynamicComponentProps = Object.assign( | ||
41 | + {}, | ||
42 | + props, | ||
43 | + config[isMobile() ? 'h5' : 'pc'] || {}, | ||
44 | + { | ||
45 | + url: isMobile() ? props.h5Url : props.url, | ||
46 | + }, | ||
47 | + ); | ||
48 | + return key ? mergedConfig[key] : mergedConfig; | ||
49 | + } | ||
50 | + | ||
51 | + return ( | ||
52 | + <React.Suspense | ||
53 | + fallback={ | ||
54 | + skeleton ? typeof skeleton === 'boolean' ? <Skeleton.Input active /> : skeleton : null | ||
55 | + } | ||
56 | + > | ||
57 | + <Comp {...profile} /> | ||
58 | + </React.Suspense> | ||
59 | + ); | ||
60 | +}; | ||
61 | + | ||
62 | +export interface QxDynamicComponentProps extends Record<string, any> { | ||
63 | + /** | ||
64 | + * 组件地址 | ||
65 | + */ | ||
66 | + url: string; | ||
67 | + /** | ||
68 | + * 移动端组件地址 | ||
69 | + */ | ||
70 | + h5Url: string; | ||
71 | + /** | ||
72 | + * 组件 props | ||
73 | + */ | ||
74 | + profile?: Record<string, any>; | ||
75 | + /** | ||
76 | + * 是否显示加载骨架 | ||
77 | + */ | ||
78 | + skeleton?: boolean | React.ReactNode; | ||
79 | + /** | ||
80 | + * 组件名称 | ||
81 | + */ | ||
82 | + component: string; | ||
83 | + /** | ||
84 | + * 文件服务,未 true 时会自动拼接文件服务器地址 | ||
85 | + */ | ||
86 | + fileServer?: boolean; | ||
87 | + /** | ||
88 | + * 组件生效范围 | ||
89 | + */ | ||
90 | + scope: Record<'form' | 'search' | 'table' | 'card', boolean>; | ||
91 | + /** | ||
92 | + * 请求方法 | ||
93 | + */ | ||
94 | + request?: any; | ||
95 | + /** | ||
96 | + * 配置项 | ||
97 | + */ | ||
98 | + config?: Partial<Record<PlatformType, QxDynamicComponentProps>>; | ||
99 | +} | ||
100 | + | ||
101 | +export type PlatformType = 'h5' | 'pc'; |
src/qx-progress/index.less
0 → 100644
1 | +@import '~@qx/ui/src/style/variable.less'; | ||
2 | + | ||
3 | +.qx-progress__horizontal { | ||
4 | + width: 100%; | ||
5 | + min-width: 128px; | ||
6 | + max-width: 208px; | ||
7 | + height: 32px; | ||
8 | + | ||
9 | + .ant-progress-line { | ||
10 | + display: flex; | ||
11 | + align-items: center; | ||
12 | + justify-content: space-between; | ||
13 | + width: 100%; | ||
14 | + } | ||
15 | + .ant-progress-show-info .ant-progress-outer { | ||
16 | + margin-right: unset; | ||
17 | + padding-right: unset; | ||
18 | + } | ||
19 | + .ant-progress-outer { | ||
20 | + flex: 1; | ||
21 | + } | ||
22 | + | ||
23 | + .ant-progress-text { | ||
24 | + width: 40px; | ||
25 | + overflow: hidden; | ||
26 | + color: @N9; | ||
27 | + white-space: nowrap; | ||
28 | + text-overflow: ellipsis; | ||
29 | + } | ||
30 | + | ||
31 | + .ant-progress-bg { | ||
32 | + background-color: @B8; | ||
33 | + } | ||
34 | +} | ||
35 | + | ||
36 | +.qx-progress__horizontal--look { | ||
37 | + min-width: 146px; | ||
38 | + max-width: 226px; | ||
39 | +} | ||
40 | +.qx-progress__annular { | ||
41 | + .ant-progress-inner { | ||
42 | + svg { | ||
43 | + color: @B8; | ||
44 | + } | ||
45 | + } | ||
46 | + .ant-progress-status-success | ||
47 | + .ant-progress-inner:not(.ant-progress-circle-gradient) | ||
48 | + .ant-progress-circle-path { | ||
49 | + stroke: @B8; | ||
50 | + } | ||
51 | + .ant-progress-circle.ant-progress-status-success .ant-progress-text { | ||
52 | + color: @N9; | ||
53 | + } | ||
54 | + | ||
55 | + .qx-pa--main .ant-progress-text { | ||
56 | + display: block; | ||
57 | + overflow: hidden; | ||
58 | + white-space: nowrap; | ||
59 | + text-align: center; | ||
60 | + text-overflow: ellipsis; | ||
61 | + } | ||
62 | + .qx-pa--default { | ||
63 | + .ant-progress-text { | ||
64 | + width: 40px; | ||
65 | + padding: 0 2px; | ||
66 | + font-size: 12px; | ||
67 | + } | ||
68 | + } | ||
69 | + .qx-pa--middle { | ||
70 | + .ant-progress-text { | ||
71 | + width: 64px; | ||
72 | + margin-top: -5px; | ||
73 | + margin-left: -16px; | ||
74 | + padding: 0 4px; | ||
75 | + font-size: 20px; | ||
76 | + text-align: center; | ||
77 | + scale: 0.5; | ||
78 | + } | ||
79 | + } | ||
80 | + .qx-pa--small { | ||
81 | + .ant-progress-text { | ||
82 | + width: 48px; | ||
83 | + margin-top: -4px; | ||
84 | + margin-left: -12px; | ||
85 | + padding: 0 4px; | ||
86 | + font-size: 16px; | ||
87 | + text-align: center; | ||
88 | + scale: 0.5; | ||
89 | + } | ||
90 | + } | ||
91 | +} | ||
92 | +.qx-progress__horizontal, | ||
93 | +.qx-progress__annular { | ||
94 | + display: flex; | ||
95 | + align-items: center; | ||
96 | +} | ||
97 | + | ||
98 | +.qx-progress__look { | ||
99 | + margin-left: 4px; | ||
100 | + color: @N7; | ||
101 | + cursor: pointer; | ||
102 | + | ||
103 | + &:hover { | ||
104 | + color: @B8; | ||
105 | + } | ||
106 | +} | ||
107 | + | ||
108 | +.qx-pa-tooltip, | ||
109 | +.qx-ph-tooltip { | ||
110 | + .ant-tooltip-inner { | ||
111 | + color: @N9; | ||
112 | + } | ||
113 | +} |
src/qx-progress/index.md
0 → 100644
1 | +--- | ||
2 | +nav: | ||
3 | + path: /component | ||
4 | + title: 组件 | ||
5 | + order: 1 | ||
6 | +group: | ||
7 | + path: /common | ||
8 | + title: 百分比进度条 | ||
9 | + order: 0 | ||
10 | +--- | ||
11 | + | ||
12 | +## QxProgress 百分比进度条 | ||
13 | + | ||
14 | +### 水平进度条 | ||
15 | + | ||
16 | +```tsx | ||
17 | +import React from 'react'; | ||
18 | +import { QxProgress } from '@qx/common'; | ||
19 | + | ||
20 | +export default () => { | ||
21 | + return <QxProgress value={34} formatValue={'这是34%'} type={'bar'} />; | ||
22 | +}; | ||
23 | +``` | ||
24 | + | ||
25 | +### 环状进度条 | ||
26 | + | ||
27 | +```tsx | ||
28 | +import React from 'react'; | ||
29 | +import { QxProgress } from '@qx/common'; | ||
30 | + | ||
31 | +export default () => { | ||
32 | + return <QxProgress value={34} type={'circle'} />; | ||
33 | +}; | ||
34 | +``` | ||
35 | + | ||
36 | +### 环状进度条小中大 | ||
37 | + | ||
38 | +```tsx | ||
39 | +import React from 'react'; | ||
40 | +import { QxProgress } from '@qx/common'; | ||
41 | + | ||
42 | +export default () => { | ||
43 | + return ( | ||
44 | + <div style={{ display: 'flex', alignItems: 'center' }}> | ||
45 | + <QxProgress value={34} formatValue={'34%'} type={'circle'} size={'small'} /> | ||
46 | + <QxProgress value={34} formatValue={'34%'} type={'circle'} size={'middle'} /> | ||
47 | + <QxProgress value={34} formatValue={'34%'} type={'circle'} size={'default'} /> | ||
48 | + </div> | ||
49 | + ); | ||
50 | +}; | ||
51 | +``` | ||
52 | + | ||
53 | +### 隐私保护条形 | ||
54 | + | ||
55 | +```tsx | ||
56 | +import React, { useState } from 'react'; | ||
57 | +import { QxProgress } from '@qx/common'; | ||
58 | + | ||
59 | +export default () => { | ||
60 | + return ( | ||
61 | + <QxProgress | ||
62 | + value={34} | ||
63 | + formatValue={'34%'} | ||
64 | + type={'bar'} | ||
65 | + controlPrivacy={true} | ||
66 | + privacy={true} | ||
67 | + size={'default'} | ||
68 | + /> | ||
69 | + ); | ||
70 | +}; | ||
71 | +``` | ||
72 | + | ||
73 | +### 隐私保护环状 | ||
74 | + | ||
75 | +```tsx | ||
76 | +import React, { useState } from 'react'; | ||
77 | +import { QxProgress } from '@qx/common'; | ||
78 | + | ||
79 | +export default () => { | ||
80 | + const [privacy, setPrivacy] = useState(true); | ||
81 | + return ( | ||
82 | + <QxProgress | ||
83 | + value={34} | ||
84 | + formatValue={'34%'} | ||
85 | + type={'circle'} | ||
86 | + controlPrivacy={true} | ||
87 | + privacy={true} | ||
88 | + size={'default'} | ||
89 | + /> | ||
90 | + ); | ||
91 | +}; | ||
92 | +``` | ||
93 | + | ||
94 | +<API></API> |
src/qx-progress/index.tsx
0 → 100644
1 | +import React, { useState } from 'react'; | ||
2 | +import { Progress, Tooltip } from 'antd'; | ||
3 | +import './index.less'; | ||
4 | +import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; | ||
5 | + | ||
6 | +interface QxProgressProps { | ||
7 | + /** | ||
8 | + * @description 值 | ||
9 | + */ | ||
10 | + value: number; | ||
11 | + /** | ||
12 | + * @description 进度条类型 | ||
13 | + * @default "circle" | ||
14 | + */ | ||
15 | + type?: 'bar' | 'circle'; | ||
16 | + /** | ||
17 | + * @description 环状进度条规格 | ||
18 | + * @default "small" | ||
19 | + */ | ||
20 | + size?: 'small' | 'middle' | 'default' | string | undefined; | ||
21 | + /** | ||
22 | + * @description 是否开启隐私保护 | ||
23 | + * @default "false" | ||
24 | + */ | ||
25 | + privacy?: boolean; | ||
26 | + /** | ||
27 | + * @description 自定义显示的内容 | ||
28 | + */ | ||
29 | + formatValue?: string | number; | ||
30 | + /** | ||
31 | + * @description 是否需要控制隐私保护的按钮 | ||
32 | + * @default "false" | ||
33 | + */ | ||
34 | + controlPrivacy?: boolean; | ||
35 | + /** | ||
36 | + * @description 位置 | ||
37 | + * @default "left" | ||
38 | + */ | ||
39 | + align?: string; | ||
40 | +} | ||
41 | + | ||
42 | +export const QxProgress: React.FC<QxProgressProps> = (props) => { | ||
43 | + const { value, type, size, privacy, formatValue, controlPrivacy, align } = props; | ||
44 | + const [privacyLocal, setPrivacyLocal] = useState<boolean>(privacy); | ||
45 | + | ||
46 | + return ( | ||
47 | + <> | ||
48 | + {value || typeof value === 'number' ? ( | ||
49 | + <> | ||
50 | + {type === 'bar' ? ( | ||
51 | + <div | ||
52 | + className={`qx-progress__horizontal ${ | ||
53 | + controlPrivacy && privacy ? 'qx-progress__horizontal--look' : '' | ||
54 | + }`} | ||
55 | + style={{ justifyContent: align || 'left' }} | ||
56 | + > | ||
57 | + <Progress | ||
58 | + percent={ | ||
59 | + (!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) ? 0 : value | ||
60 | + } | ||
61 | + format={() => ( | ||
62 | + <Tooltip | ||
63 | + overlayClassName={'qx-ph-tooltip'} | ||
64 | + title={ | ||
65 | + (!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) | ||
66 | + ? '***%' | ||
67 | + : formatValue || `${value}%` | ||
68 | + } | ||
69 | + color={'#fff'} | ||
70 | + > | ||
71 | + {(!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) | ||
72 | + ? '***%' | ||
73 | + : formatValue || `${value}%`} | ||
74 | + </Tooltip> | ||
75 | + )} | ||
76 | + /> | ||
77 | + {controlPrivacy && privacy ? ( | ||
78 | + <span | ||
79 | + className={'qx-progress__look'} | ||
80 | + onClick={() => { | ||
81 | + setPrivacyLocal(!privacyLocal); | ||
82 | + }} | ||
83 | + > | ||
84 | + {privacyLocal ? <EyeInvisibleOutlined /> : <EyeOutlined />} | ||
85 | + </span> | ||
86 | + ) : null} | ||
87 | + </div> | ||
88 | + ) : ( | ||
89 | + <div className={'qx-progress__annular'} style={{ justifyContent: align || 'left' }}> | ||
90 | + <Progress | ||
91 | + strokeWidth={10} | ||
92 | + type="circle" | ||
93 | + format={() => ( | ||
94 | + <Tooltip | ||
95 | + overlayClassName={'qx-pa-tooltip'} | ||
96 | + title={ | ||
97 | + (!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) | ||
98 | + ? '***%' | ||
99 | + : formatValue || `${value}%` | ||
100 | + } | ||
101 | + color={'#fff'} | ||
102 | + > | ||
103 | + {(!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) | ||
104 | + ? '***%' | ||
105 | + : formatValue || `${value}%`} | ||
106 | + </Tooltip> | ||
107 | + )} | ||
108 | + percent={ | ||
109 | + (!controlPrivacy && privacy) || (controlPrivacy && privacyLocal) ? 0 : value | ||
110 | + } | ||
111 | + className={`qx-pa--main ${ | ||
112 | + size === 'default' | ||
113 | + ? 'qx-pa--default' | ||
114 | + : size === 'middle' | ||
115 | + ? 'qx-pa--middle' | ||
116 | + : 'qx-pa--small' | ||
117 | + }`} | ||
118 | + width={size === 'default' ? 40 : size === 'middle' ? 32 : 24} | ||
119 | + /> | ||
120 | + {controlPrivacy && privacy ? ( | ||
121 | + <span | ||
122 | + className={'qx-progress__look'} | ||
123 | + onClick={() => { | ||
124 | + setPrivacyLocal(!privacyLocal); | ||
125 | + }} | ||
126 | + > | ||
127 | + {privacyLocal ? <EyeInvisibleOutlined /> : <EyeOutlined />} | ||
128 | + </span> | ||
129 | + ) : null} | ||
130 | + </div> | ||
131 | + )} | ||
132 | + </> | ||
133 | + ) : null} | ||
134 | + </> | ||
135 | + ); | ||
136 | +}; |
src/qx-search-input/index.less
0 → 100644
src/qx-search-input/index.md
0 → 100644
1 | +--- | ||
2 | +nav: | ||
3 | + path: /component | ||
4 | + title: 组件 | ||
5 | + order: 1 | ||
6 | +group: | ||
7 | + path: /common | ||
8 | + title: 公共组件方法 | ||
9 | + order: 0 | ||
10 | +--- | ||
11 | + | ||
12 | +## QxSearchInput 实时搜索框 | ||
13 | + | ||
14 | +Demo: | ||
15 | + | ||
16 | +```tsx | ||
17 | +import { QxSearchInput } from '@qx/common'; | ||
18 | +import type { InputRef } from 'antd'; | ||
19 | +import { Button } from 'antd'; | ||
20 | +import { useRef, useState } from 'react'; | ||
21 | + | ||
22 | +export default () => { | ||
23 | + // 输入框内容 | ||
24 | +const [keyValue, setKeyValue] = useState(''); | ||
25 | +const searchInputRef = useRef<InputRef>(null); | ||
26 | + | ||
27 | +const handleChange = (val: string) => { | ||
28 | + // 输入框内容变化时的回调、按下回车的回调 | ||
29 | + setKeyValue(val); | ||
30 | +}; | ||
31 | + return ( | ||
32 | + <div> | ||
33 | + <Button | ||
34 | + type="primary" | ||
35 | + onClick={() => { | ||
36 | + // 自动聚焦 | ||
37 | + searchInputRef?.current!.focus({ | ||
38 | + cursor: 'end', | ||
39 | + }); | ||
40 | + }} | ||
41 | + > | ||
42 | + 聚焦 | ||
43 | + </Button> | ||
44 | + <QxSearchInput | ||
45 | + searchInputRef={searchInputRef} | ||
46 | + value={keyValue} | ||
47 | + onChange={handleChange} | ||
48 | + onPressEnter={handleChange} | ||
49 | + // prefix={prefixIcon} | ||
50 | + /> | ||
51 | + </div> | ||
52 | + ); | ||
53 | +}; | ||
54 | +``` | ||
55 | + | ||
56 | +<API></API> | ||
57 | + | ||
58 | +More skills for writing demo: https://d.umijs.org/guide/basic#write-component-demo |
src/qx-search-input/index.tsx
0 → 100644
1 | +import React, { CSSProperties, Ref } from 'react'; | ||
2 | +import { Input } from 'antd'; | ||
3 | +import type { InputProps, InputRef } from 'antd'; | ||
4 | +import { SearchOutlined } from '@ant-design/icons'; | ||
5 | +import './index.less'; | ||
6 | + | ||
7 | +const prefixCls = 'qx-search-input'; | ||
8 | + | ||
9 | +type QxSearchInputProps = { | ||
10 | + // searchInputRef.current 获取当前input Dom 元素 自动聚焦 | ||
11 | + searchInputRef?: Ref<InputRef>; | ||
12 | + // 自定义样式 style | ||
13 | + style?: CSSProperties; | ||
14 | + // 轻提示 | ||
15 | + placeholder?: string; | ||
16 | + // 支持 antd-Input 自身API | ||
17 | +} & InputProps; | ||
18 | + | ||
19 | +export const QxSearchInput: React.FC<QxSearchInputProps> = (props) => { | ||
20 | + const defaultPrefixIcon: React.ReactNode = <SearchOutlined />; | ||
21 | + | ||
22 | + return ( | ||
23 | + <Input | ||
24 | + ref={props.searchInputRef} | ||
25 | + className={prefixCls} | ||
26 | + placeholder={props?.placeholder || '请输入搜索内容'} | ||
27 | + prefix={props?.prefix || defaultPrefixIcon} | ||
28 | + {...props} | ||
29 | + /> | ||
30 | + ); | ||
31 | +}; |