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 | 10 | "license": "MIT", | 
| 11 | 11 | "dependencies": { | 
| 12 | 12 | "@ant-design/icons": "^5.2.5", | 
| 13 | + "@qx/icon-btn": "^0.0.13", | |
| 13 | 14 | "@qx/ui": "0.0.3-beta.1", | 
| 14 | 15 | "ahooks": "^3.7.5", | 
| 15 | 16 | "classnames": "^2.3.2", | 
| ... | ... | @@ -2883,10 +2884,9 @@ | 
| 2883 | 2884 | } | 
| 2884 | 2885 | }, | 
| 2885 | 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 | 2890 | "license": "MIT" | 
| 2891 | 2891 | }, | 
| 2892 | 2892 | "node_modules/@qx/ui": { | 
| ... | ... | @@ -2936,6 +2936,13 @@ | 
| 2936 | 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 | 2946 | "node_modules/@rc-component/color-picker": { | 
| 2940 | 2947 | "version": "1.4.1", | 
| 2941 | 2948 | "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz", | 
| ... | ... | @@ -22525,10 +22532,9 @@ | 
| 22525 | 22532 | } | 
| 22526 | 22533 | }, | 
| 22527 | 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 | 22539 | "@qx/ui": { | 
| 22534 | 22540 | "version": "0.0.3-beta.1", | 
| ... | ... | @@ -22559,6 +22565,14 @@ | 
| 22559 | 22565 | "qiankun": "^2.2.1", | 
| 22560 | 22566 | "umi-request": "^1.4.0", | 
| 22561 | 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 | 22578 | "@rc-component/color-picker": { | ... | ... | 
| ... | ... | @@ -10,5 +10,9 @@ export * from './qx-app-selector'; | 
| 10 | 10 | export * from './utils'; | 
| 11 | 11 | export * from './qx-field'; | 
| 12 | 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 | +}; | ... | ... |