Commit 914531b047ccf888d2ec26014567b5d178b4756d

Authored by 邱嘉伟
1 parent 82127a72

feat:增加qx-btn按钮,qx-progress进度条,搜索框,导出功能。

@@ -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';  
  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;
  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;
  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 +}
  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 +};
  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 +}
  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>
  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';
  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 +}
  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>
  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 +};
  1 +.qx-search-input.ant-input-affix-wrapper {
  2 + height: 32px;
  3 + margin: 0;
  4 + padding: 0 16px 0 0;
  5 + background-color: #f4f4f5;
  6 + border: 1px solid #f4f4f5;
  7 + border-radius: 16px !important;
  8 +
  9 + .ant-input {
  10 + background-color: transparent;
  11 + }
  12 +}
  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
  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 +};