Commit 97f7ab959fb505bc07e515195e884363ce6410b4

Authored by qiang.tian
1 parent 671dee50

feat: 添加路由页面

Showing 57 changed files with 4573 additions and 15 deletions

Too many changes to show.

To preserve performance only 57 of 63 files are displayed.

1   -const { defineConfig } = require('vite')
  1 +const { defineConfig } = require('vite');
2 2
3   -module.exports = defineConfig({
  3 +const process = require('process');
  4 +const path = require('path');
  5 +
  6 +const cwd = process.cwd();
4 7
5   -})
  8 +module.exports = defineConfig({
  9 + resolve: {
  10 + extensions: ['.json', '.jsx', '.js', '.ts', '.tsx'],
  11 + alias: {
  12 + '@': path.resolve(cwd, './src/'),
  13 + '@/src': path.resolve(cwd, './src/'),
  14 + },
  15 + },
  16 + css: {
  17 + preprocessorOptions: {
  18 + less: {
  19 + javascriptEnabled: true,
  20 + additionalData: `@import '@qx/ui/dist/src/style/variable.less';`,
  21 + },
  22 + },
  23 + },
  24 +});
... ...
... ... @@ -52,6 +52,7 @@ module.exports = {
52 52 extensions: ['.json', '.jsx', '.js', '.ts', '.tsx'],
53 53 alias: {
54 54 '@': path.resolve(cwd, './src/'),
  55 + '@/src': path.resolve(cwd, './src/'),
55 56 },
56 57 },
57 58 plugins: [
... ...
... ... @@ -23,19 +23,36 @@
23 23 "author": "",
24 24 "license": "ISC",
25 25 "dependencies": {
  26 + "@ant-design/icons": "^5.2.5",
  27 + "@qx/flow": "1.0.0-alpha.10",
  28 + "@qx/icon-btn": "^0.0.13",
  29 + "@qx/ui": "0.0.3-beta.1",
  30 + "@qx/utils": "0.0.56-alpha.4",
  31 + "ahooks": "^3.7.8",
  32 + "antd": "^5.8.4",
  33 + "classnames": "^2.3.2",
26 34 "hox": "^2.1.1",
  35 + "lodash-es": "^4.17.21",
  36 + "normalize.css": "^8.0.1",
27 37 "react": "^18.2.0",
  38 + "react-cookies": "^0.1.1",
28 39 "react-dom": "^18.2.0",
29 40 "react-router": "^6.15.0",
30   - "react-router-dom": "^6.15.0"
  41 + "react-router-dom": "^6.15.0",
  42 + "react-sortablejs": "^6.1.4",
  43 + "sortablejs": "^1.15.0",
  44 + "uuid": "^9.0.0"
31 45 },
32 46 "devDependencies": {
33 47 "@babel/core": "^7.22.9",
34 48 "@babel/preset-env": "^7.22.9",
35 49 "@babel/preset-react": "^7.22.5",
  50 + "@qx/types": "^0.0.2",
36 51 "@svgr/webpack": "^8.1.0",
  52 + "@types/lodash-es": "^4.17.8",
37 53 "@types/react": "^18.2.21",
38 54 "@types/react-dom": "^18.2.7",
  55 + "@types/uuid": "^9.0.2",
39 56 "babel-loader": "^9.1.3",
40 57 "copy-webpack-plugin": "^11.0.0",
41 58 "css-loader": "^6.8.1",
... ...
  1 +//抽屉 蒙层样式全局覆盖
  2 +.ant-drawer .ant-drawer-mask {
  3 + background-color: transparent;
  4 + transition: none;
  5 + animation: none;
  6 +}
  7 +
  8 +//开始节点 配置 抽屉页面 样式覆盖
  9 +.qx-df-start-setting-drawer {
  10 + .ant-drawer-body {
  11 + padding: 0;
  12 + }
  13 +
  14 + .ant-form.ant-form-vertical {
  15 + height: calc(100% - 57px);
  16 + padding: 24px 18px 24px 24px;
  17 + font-size: 14px;
  18 + background-color: #fff;
  19 + overflow-y: hidden;
  20 + scrollbar-gutter: stable;
  21 +
  22 + &:hover {
  23 + overflow-y: auto;
  24 + }
  25 +
  26 + .qx-data-flow-tooltip-opt {
  27 + margin-left: 4px;
  28 + color: @N6;
  29 + }
  30 +
  31 + .qx-flow-form__label-title {
  32 + margin-bottom: 16px;
  33 + line-height: 22px;
  34 + font-size: 14px;
  35 + font-weight: 600;
  36 + color: @N9;
  37 + }
  38 +
  39 + .qx-flow-form__label-tips {
  40 + margin-bottom: 8px;
  41 + line-height: 22px;
  42 + font-size: 14px;
  43 + font-weight: 400;
  44 + color: @N8;
  45 + }
  46 +
  47 + .ant-row.ant-form-item:not(:first-child) {
  48 + margin-bottom: 16px;
  49 + }
  50 +
  51 + .ant-col.ant-form-item-control {
  52 + color: @N9;
  53 +
  54 + .ant-checkbox-wrapper,
  55 + .ant-radio-wrapper {
  56 + color: @N9;
  57 + }
  58 + }
  59 +
  60 + .ant-col ant-form-item-label {
  61 + color: @N8;
  62 + }
  63 + }
  64 +}
... ...
  1 +import React, { memo, useRef, useContext, useState } from 'react';
  2 +import { Drawer, Button } from 'antd';
  3 +import {
  4 + IDrawerComponent,
  5 + BuilderContext,
  6 + useHistory,
  7 + useDrawer,
  8 +} from '@qx/flow';
  9 +import { QxIcon } from '@/components';
  10 +import './index.less';
  11 +import cls from 'classnames';
  12 +import commonStyles from '@/components/header/index.module.less';
  13 +// 头部相关
  14 +import FlowHeader from '../header';
  15 +import FlowNameEditor from '../name-editor';
  16 +import { merge } from 'lodash-es';
  17 +
  18 +const ConfigDrawerContainer: React.FC<ConfigDrawerContainerProps> = (props) => {
  19 + const configRef = useRef<any>();
  20 +
  21 + const { selectedNode, setSelectedNode, onChange, nodes } =
  22 + useContext(BuilderContext);
  23 +
  24 + const [showEdit, setShowEdit] = useState<boolean>(false);
  25 +
  26 + const { pushHistory } = useHistory();
  27 +
  28 + const { saveDrawer } = useDrawer();
  29 +
  30 + const handleChange = async (values: any, extra: Record<string, any>) => {
  31 + if (selectedNode) {
  32 + selectedNode.data = values;
  33 + try {
  34 + await configRef.current?.validateFields?.();
  35 + selectedNode.validateStatusError = false;
  36 + } catch (error) {
  37 + selectedNode.validateStatusError = true;
  38 + }
  39 +
  40 + merge(setSelectedNode, extra);
  41 + onChange([...nodes], 'config-change');
  42 + }
  43 + };
  44 +
  45 + const handleClose = () => {
  46 + if (selectedNode) {
  47 + selectedNode.configuring = false;
  48 + }
  49 + setSelectedNode(undefined);
  50 + pushHistory();
  51 + onChange([...nodes], 'close-drawer');
  52 + };
  53 + // console.log('数据展示 nodes ===', nodes);
  54 + // console.log('数据展示 selectedNode ===', selectedNode);
  55 +
  56 + return (
  57 + <Drawer
  58 + {...props}
  59 + title={null}
  60 + closable={false}
  61 + maskClosable={true}
  62 + drawerStyle={{
  63 + boxShadow: '-4px 0 16px 0 rgba(36, 40, 53, 0.1)',
  64 + }}
  65 + width={480}
  66 + destroyOnClose={true}
  67 + className={'qx-df-start-setting-drawer'}
  68 + onClose={handleClose}
  69 + >
  70 + <FlowHeader
  71 + extraStyles={{ padding: '0 24px' }}
  72 + renderLeft={
  73 + <FlowNameEditor
  74 + nodeName={selectedNode?.name || ''}
  75 + nodeIcon={<QxIcon type={'icon-two-dimensional-code'} />}
  76 + editTitle={showEdit}
  77 + setEditTitle={setShowEdit}
  78 + onChange={(val) => {
  79 + if (selectedNode) {
  80 + selectedNode.name = val;
  81 + onChange([...nodes], 'config-change');
  82 + }
  83 + }}
  84 + />
  85 + }
  86 + renderRight={
  87 + <Button
  88 + className={cls(commonStyles['qx-data-flow__modal-close'])}
  89 + type="text"
  90 + onClick={handleClose}
  91 + icon={<QxIcon type={'icon-close'} />}
  92 + />
  93 + }
  94 + />
  95 + {props.children &&
  96 + React.cloneElement(props.children as React.ReactElement, {
  97 + ref: configRef,
  98 + onChange: handleChange,
  99 + })}
  100 + </Drawer>
  101 + );
  102 +};
  103 +
  104 +interface ConfigDrawerContainerProps extends IDrawerComponent {}
  105 +
  106 +export default memo(ConfigDrawerContainer);
... ...
  1 +import React, { memo } from 'react';
  2 +
  3 +const DynamicComponent: React.FC<DynamicComponentProps> = (props) => {
  4 + return <div>DynamicComponent</div>;
  5 +};
  6 +
  7 +interface DynamicComponentProps {
  8 + loader: (path: string) => any;
  9 +}
  10 +
  11 +export default memo(DynamicComponent);
... ...
  1 +@prefix-cls: ~'qx-flow-version';
  2 +
  3 +.@{prefix-cls} {
  4 + width: auto;
  5 + height: 24px;
  6 + display: inline-flex;
  7 + align-items: center;
  8 + justify-content: center;
  9 + padding: 0 8px;
  10 + font-size: 14px;
  11 + font-weight: 400;
  12 + color: @N9;
  13 + background-color: @N3;
  14 + border-radius: 4px;
  15 + border: 1px solid @N3;
  16 +
  17 + &.operational {
  18 + cursor: pointer;
  19 +
  20 + &:hover {
  21 + color: @B8;
  22 + border-color: @B8;
  23 + }
  24 + }
  25 +
  26 + &.published {
  27 + color: #00b42a;
  28 + background-color: #e8ffea;
  29 + border-color: #e8ffea;
  30 +
  31 + &.operational:hover {
  32 + color: #00b42a;
  33 + background-color: #aff0b5;
  34 + border-color: #e8ffea;
  35 + }
  36 + }
  37 +
  38 + &.edit {
  39 + color: #ff7d00;
  40 + background-color: #fff7e8;
  41 + border-color: #e8ffea;
  42 +
  43 + &.operational:hover {
  44 + color: #ff7d00;
  45 + background-color: #ffe4ba;
  46 + border-color: #ffe4ba;
  47 + }
  48 + }
  49 +}
... ...
  1 +// 数据流名称编辑通用组件
  2 +import React from 'react';
  3 +
  4 +import {
  5 + FlowStatusEnums,
  6 + FlowStatusProps,
  7 + FlowVersionStatusProps,
  8 +} from '@/interface';
  9 +
  10 +// 样式
  11 +import cls from 'classnames';
  12 +import styles from './index.module.less';
  13 +
  14 +const prefix = 'qx-flow-version';
  15 +
  16 +interface FlowVersionProps {
  17 + versionText?: string | number;
  18 + status?: FlowVersionStatusProps | FlowStatusProps;
  19 + statusEnums?: Record<string, string>; // 状态枚举
  20 + handleClick?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; // 调用方法
  21 +}
  22 +
  23 +const FlowVersion: React.FC<FlowVersionProps> = (props) => {
  24 + const { handleClick, status, versionText, statusEnums } = props;
  25 +
  26 + return (
  27 + <>
  28 + {versionText ||
  29 + (statusEnums || FlowStatusEnums)[status || 'UNPUBLISHED'] ? (
  30 + <div
  31 + className={cls(
  32 + styles[`${prefix}`],
  33 + styles[`${(status || 'DRAFT').toLowerCase()}`],
  34 + !!handleClick && versionText && Number(versionText) > 1
  35 + ? styles['operational']
  36 + : ' ',
  37 + !!handleClick && versionText && Number(versionText) > 1
  38 + ? ' qx-flow-version__default-operational'
  39 + : ' qx-flow-version__default',
  40 + )}
  41 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  42 + if (handleClick && versionText && Number(versionText) > 1)
  43 + handleClick(e);
  44 + }}
  45 + style={{
  46 + minWidth:
  47 + (!!versionText ? (versionText + '').length + 1 : 0) * 14 +
  48 + (!!status ? 3 : 0) * 14 +
  49 + 'px',
  50 + }}
  51 + >
  52 + {!!versionText ? <span>V&nbsp;{versionText}</span> : null}
  53 + {!!status ? (
  54 + <span>{(statusEnums || FlowStatusEnums)[status]}</span>
  55 + ) : null}
  56 + </div>
  57 + ) : null}
  58 + </>
  59 + );
  60 +};
  61 +
  62 +export default FlowVersion;
... ...
  1 +@prefix-cls: ~'qx-data-flow__header';
  2 +
  3 +.qx-data-flow__container {
  4 + height: 100%;
  5 +
  6 + :global {
  7 + .qx-flow-wrap {
  8 + height: calc(100% - 57px);
  9 +
  10 + &.is-full {
  11 + height: 100%;
  12 + }
  13 + }
  14 + }
  15 +}
  16 +
  17 +.@{prefix-cls} {
  18 + display: flex;
  19 + align-items: center;
  20 + justify-content: space-between;
  21 + padding: 0 16px;
  22 + width: 100%;
  23 + height: 57px;
  24 + background-color: #fff;
  25 + border-bottom: 1px solid @N4;
  26 + box-sizing: border-box;
  27 +}
  28 +
  29 +.@{prefix-cls}__left {
  30 + height: 100%;
  31 + display: flex;
  32 + align-items: center;
  33 + justify-content: flex-start;
  34 + font-size: 16px;
  35 + color: @N9;
  36 + cursor: pointer;
  37 +
  38 + :global {
  39 + .anticon {
  40 + font-size: 16px;
  41 + }
  42 + }
  43 +}
  44 +
  45 +.@{prefix-cls}__left-return {
  46 + width: 24px;
  47 + height: 24px;
  48 + display: flex;
  49 + align-items: center;
  50 + justify-content: center;
  51 + font-size: 18px;
  52 + cursor: pointer;
  53 + transform: rotate(90deg);
  54 + color: @N9;
  55 + cursor: pointer;
  56 +}
  57 +
  58 +.@{prefix-cls}__left-title {
  59 + margin: 0 8px;
  60 + font-size: 16px;
  61 + color: @N9;
  62 + font-weight: 600;
  63 +}
  64 +
  65 +.@{prefix-cls}__left-custom {
  66 + height: 100%;
  67 + display: flex;
  68 + align-items: center;
  69 + justify-content: flex-start;
  70 + font-size: 16px;
  71 +}
  72 +
  73 +.@{prefix-cls}__left-custom__opt {
  74 + margin-right: 16px;
  75 +
  76 + :global {
  77 + .ant-btn-icon-only {
  78 + width: 24px;
  79 + height: 24px;
  80 + padding: 0;
  81 + display: inline-flex;
  82 + align-items: center;
  83 + justify-content: center;
  84 + }
  85 +
  86 + .anticon {
  87 + font-size: 16px;
  88 + color: @N7;
  89 + cursor: pointer;
  90 +
  91 + &:hover {
  92 + color: @B8;
  93 + }
  94 + }
  95 + }
  96 +}
  97 +
  98 +.@{prefix-cls}__right {
  99 + :global {
  100 + .ant-btn-text {
  101 + &:focus,
  102 + &:hover {
  103 + background-color: transparent;
  104 + }
  105 + }
  106 +
  107 + .ant-btn-default {
  108 + color: @N8;
  109 + border-color: @N4;
  110 +
  111 + &:focus,
  112 + &:hover {
  113 + color: @N8;
  114 + }
  115 +
  116 + &:hover {
  117 + color: @B8;
  118 + border-color: @B8;
  119 + }
  120 + }
  121 +
  122 + .ant-btn-primary {
  123 + &:focus,
  124 + &:hover {
  125 + background-color: transparent;
  126 + }
  127 +
  128 + &:hover {
  129 + background-color: @B7;
  130 + }
  131 + }
  132 + }
  133 +}
  134 +
  135 +.qx-data-flow__modal-close {
  136 + width: 24px;
  137 + height: 24px;
  138 + display: flex;
  139 + align-items: center;
  140 + justify-content: center;
  141 +
  142 + :global {
  143 + .anticon {
  144 + font-size: 12px !important;
  145 + color: @N7;
  146 + }
  147 + }
  148 +
  149 + &:hover {
  150 + :global {
  151 + .anticon {
  152 + color: @N9;
  153 + }
  154 + }
  155 + }
  156 +}
... ...
  1 +// 数据流 头部组件
  2 +import React, { useEffect, useState } from 'react';
  3 +import { Button, Space } from 'antd';
  4 +// import { useLocation } from 'umi';
  5 +import { useLocation } from 'react-router-dom';
  6 +import { QxIcon } from '@/components';
  7 +
  8 +// 样式
  9 +import cls from 'classnames';
  10 +import styles from './index.module.less';
  11 +
  12 +const prefix = 'qx-data-flow__header';
  13 +
  14 +interface FlowHeaderProps {
  15 + handleClick?: (type: string) => void;
  16 + type?: 'FLOW' | 'VERSION'; // 数据流 | 数据流详情页 | 流程版本
  17 + renderLeft?: React.ReactNode;
  18 + title?: string;
  19 + renderRight?: React.ReactNode;
  20 + extraStyles?: React.CSSProperties;
  21 +}
  22 +
  23 +const FlowHeader: React.FC<FlowHeaderProps> = (props) => {
  24 + const {
  25 + handleClick,
  26 + type = 'FLOW',
  27 + renderLeft,
  28 + title,
  29 + renderRight,
  30 + extraStyles,
  31 + } = props;
  32 +
  33 + // @ts-ignore
  34 + const { query } = useLocation();
  35 +
  36 + return (
  37 + <header className={cls(styles[`${prefix}`])} style={extraStyles}>
  38 + {!!renderLeft ? (
  39 + renderLeft
  40 + ) : (
  41 + <div
  42 + className={cls(styles[`${prefix}__left`])}
  43 + onClick={() => {
  44 + handleClick && handleClick('RETURN');
  45 + }}
  46 + >
  47 + <QxIcon
  48 + className={cls(styles[`${prefix}__left-return`])}
  49 + type={'icon-common-arrow'}
  50 + />
  51 + <div className={cls(styles[`${prefix}__left-title`])}>
  52 + {title || '数据流'}
  53 + </div>
  54 + </div>
  55 + )}
  56 + <div className={cls(styles[`${prefix}__right`])}>
  57 + {!!renderRight ? (
  58 + renderRight
  59 + ) : (
  60 + <>
  61 + {type === 'FLOW' ? (
  62 + <Button
  63 + type="primary"
  64 + // loading={false}
  65 + onClick={() => {
  66 + handleClick && handleClick('ADD');
  67 + }}
  68 + >
  69 + 新建数据流
  70 + </Button>
  71 + ) : null}
  72 + </>
  73 + )}
  74 + </div>
  75 + </header>
  76 + );
  77 +};
  78 +
  79 +export default FlowHeader;
... ...
  1 +export { default as FlowHeader } from './header';
  2 +export { default as FlowVersion } from './flow-version';
  3 +export { default as NameDescriptionSetting } from './name-description-setting';
  4 +export { default as ConfigDrawer } from './config-drawer';
  5 +export { default as NodesPanel } from './nodes-panel';
  6 +export { default as NameEditor } from './name-editor';
  7 +export { default as QxIcon } from './qx-icon';
... ...
  1 +.qx-name-description-modal {
  2 + .qx-data-flow-input--clear {
  3 + .fr-item-wrapper .fr-item-status-error input[class~='ant-input'],
  4 + .fr-item-wrapper .fr-item-status-error textarea[class~='ant-input'] {
  5 + &:hover,
  6 + &:focus {
  7 + border: none !important; // 由于该样式使用了!important,所以覆盖还是使用!important
  8 + box-shadow: none !important;
  9 + }
  10 + }
  11 +
  12 + .fr-item-wrapper textarea[class~='ant-input'] + span.ant-input-clear-icon {
  13 + top: unset;
  14 + bottom: 8px;
  15 + }
  16 + }
  17 +
  18 + .ant-modal-body {
  19 + .fr-container .field-block,
  20 + .fr-container .error-message {
  21 + min-height: 24px !important;
  22 + }
  23 + }
  24 +}
... ...
  1 +// 数据流名称编辑通用组件
  2 +import React, { useImperativeHandle, useState } from 'react';
  3 +import { Button, Modal, Space } from 'antd';
  4 +// import { useForm } from '@qx/form-render';
  5 +import { QxIcon } from '@/components';
  6 +
  7 +import _ from 'lodash-es';
  8 +import '@/styles/modal-reset.less';
  9 +import './index.less';
  10 +import { Form } from 'antd';
  11 +// import QxFormRender from '@/packages/qx-form-generator/src/form-render';
  12 +
  13 +const FORM_SCHEMA: object = {
  14 + type: 'object',
  15 + properties: {
  16 + id: {
  17 + type: 'string',
  18 + hidden: true,
  19 + },
  20 + name: {
  21 + title: '名称',
  22 + type: 'string',
  23 + max: 20,
  24 + placeholder: '请输入名称',
  25 + required: true,
  26 + props: {
  27 + allowClear: true,
  28 + },
  29 + className: 'qx-data-flow-input--clear',
  30 + },
  31 + remark: {
  32 + type: 'string',
  33 + widget: 'textarea',
  34 + title: '说明',
  35 + placeholder: '请输入说明',
  36 + max: 200,
  37 + props: {
  38 + allowClear: true,
  39 + // showCount: true, // 显示字数提示
  40 + },
  41 + className: 'qx-data-flow-input--clear',
  42 + },
  43 + },
  44 +};
  45 +
  46 +interface NameDescriptionSettingProps {
  47 + cRef: any;
  48 + handleClick: (type: string, data?: any) => void; // 名称变化调用方法
  49 +}
  50 +
  51 +const NameDescriptionSetting: React.FC<NameDescriptionSettingProps> = (
  52 + props,
  53 +) => {
  54 + const { cRef, handleClick } = props;
  55 + const [form] = Form.useForm();
  56 + const [visible, setVisible] = useState(false);
  57 +
  58 + useImperativeHandle(cRef, () => ({
  59 + // 暴露给父组件
  60 + open: (values: any) => {
  61 + setTimeout(() => {
  62 + if (_.isEmpty(values)) {
  63 + form.resetFields();
  64 + } else {
  65 + form.setValues(values);
  66 + }
  67 + }, 100);
  68 + setVisible(true);
  69 + },
  70 + }));
  71 +
  72 + const onFinish = async (data: any, error: any) => {
  73 + // console.log('表单数据:',data);
  74 + if (error.length > 0) {
  75 + return;
  76 + }
  77 + // 校验成功 保存名称、说明
  78 + handleClick('SAVE_NAME', data);
  79 + setVisible(false);
  80 + };
  81 +
  82 + return (
  83 + <div>
  84 + <Modal
  85 + width={480}
  86 + title="编辑名称与说明"
  87 + onCancel={() => setVisible(false)}
  88 + keyboard={false}
  89 + maskClosable={false}
  90 + visible={visible}
  91 + footer={
  92 + <Space size={8}>
  93 + <Button onClick={() => setVisible(false)}>取消</Button>
  94 + <Button
  95 + type={'primary'}
  96 + onClick={() => {
  97 + form.submit();
  98 + }}
  99 + >
  100 + 保存
  101 + </Button>
  102 + </Space>
  103 + }
  104 + wrapClassName={'qx-data-flow__modal qx-name-description-modal'}
  105 + closeIcon={
  106 + <QxIcon
  107 + className={'qx-data-flow__modal__close'}
  108 + type={'icon-close'}
  109 + />
  110 + }
  111 + destroyOnClose
  112 + >
  113 + <Form
  114 + form={form}
  115 + schema={FORM_SCHEMA}
  116 + onMount={() => {}}
  117 + onFinish={onFinish}
  118 + watch={{}}
  119 + />
  120 + </Modal>
  121 + </div>
  122 + );
  123 +};
  124 +
  125 +export default NameDescriptionSetting;
... ...
  1 +@prefix-cls: ~'qx-name-editor';
  2 +
  3 +.@{prefix-cls} {
  4 + width: auto;
  5 + height: 32px;
  6 + display: flex;
  7 + align-items: center;
  8 + justify-content: flex-start;
  9 + font-size: 16px;
  10 +
  11 + :global {
  12 + .anticon {
  13 + font-size: 16px;
  14 + color: @N7;
  15 + }
  16 + }
  17 +
  18 + .@{prefix-cls}__title--edit {
  19 + cursor: pointer;
  20 +
  21 + &:hover {
  22 + color: @B8;
  23 + }
  24 + }
  25 +
  26 + .@{prefix-cls}__input {
  27 + margin-left: 8px !important;
  28 + }
  29 +}
  30 +
  31 +.@{prefix-cls}__title {
  32 + height: 100%;
  33 + display: inline-flex;
  34 + align-items: center;
  35 + justify-content: flex-start;
  36 +
  37 + :global {
  38 + .ant-typography {
  39 + margin: 0 8px;
  40 + font-size: 16px;
  41 + line-height: 1.5;
  42 + color: @N9;
  43 + font-weight: 600;
  44 + }
  45 + }
  46 +}
... ...
  1 +// 数据流名称编辑通用组件
  2 +import React, { useEffect, useState } from 'react';
  3 +import { Input, Typography } from 'antd';
  4 +import { QxIcon } from '@/components';
  5 +
  6 +const { Text } = Typography;
  7 +// 样式
  8 +import cls from 'classnames';
  9 +import styles from './index.module.less';
  10 +
  11 +const prefix = 'qx-name-editor';
  12 +
  13 +interface FlowNameEditorProps {
  14 + noValueView?: string; // 无名称 展示文本
  15 + nodeName: string; // 节点名称
  16 + nodeIcon?: React.ReactNode; // 节点 icon
  17 + editTitle: boolean; // 可编辑 参数
  18 + setEditTitle: (val: boolean) => void; // 可编辑 参数
  19 + onChange: (data: string) => void; // 名称变化调用方法
  20 + nameMaxLength?: number; // 名称最长多少 展示省略号 默认 8
  21 +}
  22 +
  23 +const FlowNameEditor: React.FC<FlowNameEditorProps> = (props) => {
  24 + const {
  25 + noValueView = '未命名',
  26 + nodeName,
  27 + nodeIcon,
  28 + editTitle,
  29 + setEditTitle,
  30 + onChange,
  31 + nameMaxLength,
  32 + } = props;
  33 +
  34 + const [nameLocal, setNameLocal] = useState<string>('');
  35 +
  36 + const inputRef = React.useRef<any>(null);
  37 +
  38 + useEffect(() => {
  39 + setNameLocal(nodeName);
  40 + }, [nodeName]);
  41 +
  42 + useEffect(() => {
  43 + if (Boolean(editTitle)) {
  44 + inputRef.current!.focus({
  45 + cursor: 'end',
  46 + });
  47 + }
  48 + }, [editTitle]);
  49 +
  50 + return (
  51 + <div className={cls(styles[`${prefix}`])}>
  52 + {!!nodeIcon ? nodeIcon : null}
  53 + {editTitle ? (
  54 + <Input
  55 + ref={inputRef}
  56 + className={cls(styles[`${prefix}__input`])}
  57 + style={{
  58 + maxWidth: (nameMaxLength || 8) * 16 * 1.2 + 20 + 'px',
  59 + }}
  60 + value={nameLocal}
  61 + maxLength={20}
  62 + onChange={(e) => setNameLocal(e.target.value)}
  63 + onPressEnter={() => {
  64 + setEditTitle(false);
  65 + if (!nameLocal) {
  66 + setNameLocal(nodeName);
  67 + }
  68 + onChange(nameLocal || nodeName);
  69 + }}
  70 + onBlur={() => {
  71 + setEditTitle(false);
  72 + if (!nameLocal) {
  73 + setNameLocal(nodeName);
  74 + }
  75 + onChange(nameLocal || nodeName);
  76 + }}
  77 + />
  78 + ) : (
  79 + <>
  80 + <div
  81 + className={cls(styles[`${prefix}__title`])}
  82 + onClick={() => setEditTitle(true)}
  83 + >
  84 + <Text
  85 + style={{ maxWidth: (nameMaxLength || 8) * 16 * 1.2 + 'px' }}
  86 + ellipsis={{
  87 + tooltip: nameLocal ? nameLocal : noValueView ? noValueView : '',
  88 + }}
  89 + >
  90 + {nameLocal ? nameLocal : noValueView ? noValueView : ''}
  91 + </Text>
  92 + <QxIcon
  93 + className={cls(styles[`${prefix}__title--edit`])}
  94 + type={'icon-frame-edit'}
  95 + />
  96 + </div>
  97 + </>
  98 + )}
  99 + </div>
  100 + );
  101 +};
  102 +
  103 +export default FlowNameEditor;
... ...
  1 +import React from 'react';
  2 +import { NodeProps, NodeItemsProps } from './node-list';
  3 +import { QxIcon } from '@/components';
  4 +import { CloseOutlined } from '@ant-design/icons';
  5 +import { useAction } from '@qx/flow';
  6 +
  7 +const prefix = 'qx-flow-node-panel';
  8 +const NodeItem: React.FC<NodeItemProps> = (props) => {
  9 + const { nodeItem, isManage, setNodeList, index, parentId } = props;
  10 +
  11 + const { addNode } = useAction();
  12 + const handleAdd = (nodeType: string) => {
  13 + addNode(nodeType);
  14 + };
  15 +
  16 + const deleteNode = () => {
  17 + setNodeList((nodeItems: NodeItemsProps[]) => {
  18 + const tempList = [...nodeItems];
  19 + tempList.forEach((el) => {
  20 + if (el.id === parentId) {
  21 + el.nodes?.splice(index, 1);
  22 + }
  23 + });
  24 + return tempList;
  25 + });
  26 + };
  27 +
  28 + return (
  29 + <div
  30 + className={`${prefix}__item`}
  31 + onClick={() => {
  32 + if (isManage) return;
  33 + console.log('点击我了,lllll');
  34 + handleAdd(nodeItem.nodeType);
  35 + }}
  36 + >
  37 + <QxIcon className={`${prefix}__item-icon`} type={'icon-frame-todolist'} />
  38 + <span className={`${prefix}__item-name`}>{nodeItem.nodeName}</span>
  39 + {isManage ? (
  40 + <CloseOutlined
  41 + className={`${prefix}__item--close`}
  42 + onClick={() => deleteNode()}
  43 + />
  44 + ) : null}
  45 + </div>
  46 + );
  47 +};
  48 +
  49 +interface NodeItemProps {
  50 + nodeItem: NodeProps;
  51 + isManage?: boolean;
  52 + setNodeList?: any;
  53 + index: number;
  54 + parentId?: string;
  55 +}
  56 +
  57 +export default NodeItem;
... ...
  1 +import React, { useEffect, useState } from 'react';
  2 +import NodeItem from './node-item';
  3 +import { QxIcon } from '@/components';
  4 +import { ReactSortable } from 'react-sortablejs';
  5 +import { Input } from 'antd';
  6 +
  7 +import empty from '../img/empty.png';
  8 +
  9 +const prefix = 'qx-flow-node-panel';
  10 +
  11 +const NodeList: React.FC<NodeListProps> = (props) => {
  12 + const { listItem, isManage, setNodeList, nodeIndex, setNodeEdit } = props;
  13 + const [open, setOpen] = useState(true);
  14 + const [name, setName] = useState(listItem.name);
  15 + const [edit, setEdit] = useState(listItem?.edit || false);
  16 +
  17 + useEffect(() => {
  18 + setNodeEdit(edit);
  19 + }, [edit]);
  20 +
  21 + const sortableOptionsComponent = {
  22 + disabled: !isManage || edit,
  23 + animation: 150,
  24 + fallbackOnBody: true,
  25 + swapThreshold: 0.65,
  26 + ghostClass: 'qx-node-panel--drag',
  27 + group: 'component',
  28 + onSort: () => {
  29 + if (!open) {
  30 + setOpen(!open);
  31 + }
  32 + },
  33 + };
  34 +
  35 + const editName = (_value: string) => {
  36 + if (_value) {
  37 + setNodeList((nodeItems: NodeItemsProps[]) => {
  38 + const tempList = [...nodeItems];
  39 + tempList.forEach((el) => {
  40 + if (el.id === listItem.id) {
  41 + el.name = _value;
  42 + }
  43 + if (el.edit) delete el.edit;
  44 + });
  45 + return tempList;
  46 + });
  47 + }
  48 + setEdit(false);
  49 + };
  50 +
  51 + const deleteGroup = () => {
  52 + setNodeList((nodeItems: NodeItemsProps[]) => {
  53 + const tempList = [...nodeItems];
  54 + tempList.splice(nodeIndex, 1);
  55 + return tempList;
  56 + });
  57 + };
  58 +
  59 + return (
  60 + <div className={`${prefix}__list`}>
  61 + <div className={`${prefix}__list-title`}>
  62 + <div className={`${prefix}__list-title-left`}>
  63 + <QxIcon
  64 + className={`${prefix}__left-icon`}
  65 + type={open ? 'icon-down' : 'icon-right'}
  66 + onClick={() => setOpen(!open)}
  67 + />
  68 + {edit ? (
  69 + <Input
  70 + defaultValue={name}
  71 + value={name}
  72 + autoFocus
  73 + onChange={(e) => setName(e.target.value)}
  74 + onBlur={(e) => {
  75 + const _value = e.target.value;
  76 + editName(_value);
  77 + }}
  78 + onPressEnter={(e: any) => {
  79 + const _value = e.target.value;
  80 + editName(_value);
  81 + }}
  82 + />
  83 + ) : (
  84 + <span
  85 + className={`${prefix}__left-span`}
  86 + onClick={() => {
  87 + if (!isManage) return;
  88 + setEdit(true);
  89 + }}
  90 + >
  91 + {listItem.name}
  92 + </span>
  93 + )}
  94 + </div>
  95 + {!listItem.nodes?.length ? (
  96 + <QxIcon
  97 + className={`${prefix}__list-title-right`}
  98 + type={'icon-frame-delete'}
  99 + onClick={() => deleteGroup()}
  100 + />
  101 + ) : null}
  102 + </div>
  103 + <ReactSortable
  104 + className={`${prefix}__list-box`}
  105 + list={listItem?.nodes || []}
  106 + setList={(list) => {
  107 + setNodeList((nodeItems: NodeItemsProps[]) => {
  108 + const tempList = [...nodeItems];
  109 + tempList[nodeIndex]['nodes'] = list;
  110 + return tempList;
  111 + });
  112 + }}
  113 + {...sortableOptionsComponent}
  114 + >
  115 + {!open ? null : (
  116 + <>
  117 + {listItem?.nodes?.length ? (
  118 + <>
  119 + {(listItem?.nodes || []).map((item, index) => {
  120 + return (
  121 + <NodeItem
  122 + key={item.id}
  123 + nodeItem={item || []}
  124 + isManage={isManage}
  125 + setNodeList={setNodeList}
  126 + index={index}
  127 + parentId={listItem.id}
  128 + />
  129 + );
  130 + })}
  131 + </>
  132 + ) : (
  133 + <div className={`${prefix}--empty`}>
  134 + <img src={empty} alt={'暂无数据'} />
  135 + <span>暂无数据</span>
  136 + </div>
  137 + )}
  138 + </>
  139 + )}
  140 + </ReactSortable>
  141 + </div>
  142 + );
  143 +};
  144 +export interface NodeProps {
  145 + id: string;
  146 + groupId: string;
  147 + nodeMode: string;
  148 + nodeType: string;
  149 + nodeName: string;
  150 + configJson: any; // 待规划
  151 +}
  152 +export interface NodeItemsProps {
  153 + id: string;
  154 + name: string;
  155 + nodes?: NodeProps[];
  156 + disabled: boolean;
  157 + edit?: boolean;
  158 +}
  159 +interface NodeListProps {
  160 + listItem: NodeItemsProps;
  161 + isManage?: boolean;
  162 + setNodeList?: any;
  163 + nodeIndex: number;
  164 + setNodeEdit: (status: boolean) => void;
  165 +}
  166 +
  167 +export default NodeList;
... ...
  1 +.qx-flow-node-panel {
  2 + .ant-drawer-body {
  3 + padding: 0;
  4 + overflow: hidden;
  5 + }
  6 + &__title {
  7 + height: 56px;
  8 + padding: 16px;
  9 + display: flex;
  10 + align-items: center;
  11 + justify-content: space-between;
  12 + color: @N9;
  13 + border-bottom: 1px solid @N4;
  14 + .qx-flow-node-panel__title-left {
  15 + font-size: 16px;
  16 + font-weight: 600;
  17 + }
  18 + .qx-flow-node-panel__title-right {
  19 + font-size: 14px;
  20 + span {
  21 + cursor: pointer;
  22 + margin-left: 12px;
  23 + transition: all 0.3s;
  24 + }
  25 + }
  26 + }
  27 +
  28 + &--save {
  29 + color: @B8;
  30 + &:hover {
  31 + color: @B7;
  32 + }
  33 + }
  34 +
  35 + &--manage {
  36 + &:hover {
  37 + color: @B8;
  38 + }
  39 + }
  40 +
  41 + &__tabs {
  42 + .ant-tabs-nav {
  43 + margin: 0 !important;
  44 + &::before {
  45 + border: none;
  46 + }
  47 + .ant-tabs-nav-list {
  48 + width: 100%;
  49 +
  50 + .ant-tabs-tab {
  51 + flex: 1;
  52 + display: flex;
  53 + align-items: center;
  54 + justify-content: center;
  55 + font-weight: 600;
  56 + color: @N8;
  57 + padding: 9px 0;
  58 + .ant-tabs-tab-btn {
  59 + }
  60 + }
  61 + .ant-tabs-tab-active {
  62 + position: relative;
  63 + &::before {
  64 + content: '';
  65 + position: absolute;
  66 + left: 50%;
  67 + bottom: 0;
  68 + width: 88px;
  69 + height: 2px;
  70 + margin-left: -44px;
  71 + border-radius: 2px;
  72 + background-color: @B8;
  73 + transition:
  74 + width 0.3s,
  75 + left 0.3s,
  76 + right 0.3s;
  77 + }
  78 + }
  79 +
  80 + .ant-tabs-ink-bar {
  81 + display: none;
  82 + }
  83 + }
  84 + }
  85 + }
  86 +
  87 + &__content {
  88 + height: calc(100vh - 56px - 40px);
  89 + overflow-y: auto;
  90 + padding: 16px;
  91 +
  92 + &--manage {
  93 + height: calc(100vh - 56px - 40px - 44px);
  94 + }
  95 + }
  96 +
  97 + &__list {
  98 + margin-bottom: 16px;
  99 + width: 100%;
  100 +
  101 + .qx-flow-node-panel__list-title {
  102 + height: 32px;
  103 + display: flex;
  104 + align-items: center;
  105 + justify-content: space-between;
  106 + color: @N7;
  107 + font-size: 14px;
  108 + width: 100%;
  109 + }
  110 +
  111 + .qx-flow-node-panel__list-title-left {
  112 + display: flex;
  113 + align-items: center;
  114 + flex: 1;
  115 +
  116 + input {
  117 + flex: 1;
  118 + }
  119 + .qx-flow-node-panel__left-icon {
  120 + margin-right: 4px;
  121 + width: 14px;
  122 + height: 14px;
  123 + }
  124 +
  125 + .qx-flow-node-panel__left-span {
  126 + cursor: pointer;
  127 + font-weight: 600;
  128 + flex: 1;
  129 + overflow: hidden;
  130 + text-overflow: ellipsis;
  131 + white-space: nowrap;
  132 + width: 300px;
  133 + }
  134 + }
  135 +
  136 + .qx-flow-node-panel__list-title-right {
  137 + margin-left: 18px;
  138 + cursor: pointer;
  139 + }
  140 +
  141 + .qx-flow-node-panel__list-box {
  142 + display: flex;
  143 + flex-wrap: wrap;
  144 + align-items: center;
  145 + transition: all 0.3s;
  146 + }
  147 + }
  148 + &__item {
  149 + margin: 8px 8px 0 0;
  150 + width: 30%;
  151 + height: 40px;
  152 + border-radius: 4px;
  153 + border: 1px solid @N4;
  154 + background: @N2;
  155 + padding: 0 8px;
  156 + display: flex;
  157 + align-items: center;
  158 + max-width: 142px;
  159 + flex: 1;
  160 + position: relative;
  161 + cursor: pointer;
  162 +
  163 + &:nth-child(3n) {
  164 + margin-right: 0;
  165 + }
  166 +
  167 + .qx-flow-node-panel__item-icon {
  168 + width: 28px;
  169 + height: 28px;
  170 + border-radius: 14px;
  171 + background-color: pink;
  172 + color: #fff;
  173 + font-size: 16px;
  174 + display: inline-flex;
  175 + align-items: center;
  176 + justify-content: center;
  177 + }
  178 +
  179 + .qx-flow-node-panel__item-name {
  180 + margin-left: 8px;
  181 + flex: 1;
  182 + overflow: hidden;
  183 + text-overflow: ellipsis;
  184 + white-space: nowrap;
  185 + color: @N9;
  186 + font-size: 14px;
  187 + }
  188 + }
  189 +
  190 + .qx-flow-node-panel__item--close {
  191 + position: absolute;
  192 + width: 16px;
  193 + height: 16px;
  194 + border-radius: 8px;
  195 + color: #fff;
  196 + background-color: @E3;
  197 + top: -8px;
  198 + right: -8px;
  199 + font-size: 8px;
  200 + display: flex;
  201 + align-items: center;
  202 + justify-content: center;
  203 + cursor: pointer;
  204 + }
  205 +
  206 + &--empty {
  207 + width: 100%;
  208 + display: flex;
  209 + align-items: center;
  210 + justify-content: center;
  211 + flex-direction: column;
  212 + margin-top: 8px;
  213 + img {
  214 + width: 190px;
  215 + height: 126px;
  216 + }
  217 + span {
  218 + margin-top: 4px;
  219 + line-height: 22px;
  220 + font-weight: 600;
  221 + color: @N7;
  222 + }
  223 +
  224 + &-max {
  225 + margin-top: 24px;
  226 + }
  227 + }
  228 +
  229 + .qx-flow-node-panel__loading {
  230 + width: 100%;
  231 + margin-top: 100px;
  232 + }
  233 +
  234 + &__opt {
  235 + position: static;
  236 + padding: 16px 16px 0;
  237 + height: 44px;
  238 + background-color: #fff;
  239 + color: @B8;
  240 + display: flex;
  241 + align-items: center;
  242 +
  243 + &-icon {
  244 + margin-right: 16px;
  245 + cursor: pointer;
  246 + &:hover {
  247 + color: @B7;
  248 + }
  249 + > span {
  250 + margin-right: 4px;
  251 + }
  252 + }
  253 + }
  254 +}
... ...
  1 +import React, { memo, useContext, useEffect, useState } from 'react';
  2 +import { BuilderContext } from '@qx/flow';
  3 +import { Drawer, Spin, Tabs } from 'antd';
  4 +import NodeList, { NodeItemsProps } from './components/node-list';
  5 +import { useLocation, useSearchParams } from 'react-router-dom';
  6 +import { getListGroupNode } from '@/services';
  7 +import { QxIcon } from '@/components';
  8 +import { ReactSortable } from 'react-sortablejs';
  9 +import { cloneDeep } from 'lodash-es';
  10 +import { v4 as uuid } from 'uuid';
  11 +import type { INodePanelProps } from '@qx/flow';
  12 +import Empty from './img/empty.png';
  13 +
  14 +import './index.less';
  15 +
  16 +const prefix = 'qx-flow-node-panel';
  17 +
  18 +const panelTabs = [
  19 + {
  20 + name: '系统节点',
  21 + key: 'SYSTEM',
  22 + },
  23 + {
  24 + name: '扩展节点',
  25 + key: 'EXTEND',
  26 + },
  27 + {
  28 + name: '开发节点',
  29 + key: 'DEVELOP',
  30 + },
  31 +];
  32 +
  33 +const NodePanel: React.FC<NodePanelProps> = (props) => {
  34 + const { visible, onClose } = props;
  35 + // @ts-ignore
  36 + const [searchParams] = useSearchParams();
  37 + const appId = searchParams.get('appId');
  38 + const { registerNodes } = useContext(BuilderContext);
  39 + const [loading, setLoading] = useState(false);
  40 + const [activeKey, setActiveKey] = useState('SYSTEM');
  41 + const [isManage, setIsManage] = useState<boolean>(false);
  42 + const [nodeList, setNodeList] = useState<NodeItemsProps[]>([]);
  43 + const [originalNodeList, setOriginalNodeList] = useState<NodeItemsProps[]>(
  44 + [],
  45 + );
  46 + const [nodeEdit, setNodeEdit] = useState(false);
  47 + const [nameList, setNameList] = useState<string[]>([]);
  48 + // console.log('registerNodes', registerNodes, props);
  49 +
  50 + useEffect(() => {
  51 + if (!isManage) return;
  52 + if (!nodeList?.length) {
  53 + setNameList([]);
  54 + } else {
  55 + const _list: string[] = [];
  56 + nodeList.forEach((el) => {
  57 + if (el.name.includes('未命名分组(')) {
  58 + _list.push(el.name);
  59 + }
  60 + });
  61 + setNameList(_list);
  62 + }
  63 + }, [JSON.stringify(nodeList), isManage]);
  64 +
  65 + useEffect(() => {
  66 + setIsManage(false);
  67 + setNameList([]);
  68 + if (!appId || !visible) return;
  69 + setLoading(true);
  70 + // TODO
  71 + getListGroupNode(appId, {
  72 + type: activeKey,
  73 + })
  74 + .then((data: NodeItemsProps[]) => {
  75 + setLoading(false);
  76 + if (data?.length) {
  77 + setOriginalNodeList(cloneDeep(data));
  78 + setNodeList(data.filter((el) => !el.disabled) || []);
  79 + return;
  80 + }
  81 + setNodeList([]);
  82 + setOriginalNodeList([]);
  83 + })
  84 + .catch(() => {
  85 + setLoading(false);
  86 + setNodeList([]);
  87 + setOriginalNodeList([]);
  88 + });
  89 + }, [activeKey, visible]);
  90 +
  91 + useEffect(() => {
  92 + if (!visible) {
  93 + setActiveKey('SYSTEM');
  94 + }
  95 + }, [visible]);
  96 +
  97 + // const detailNodeList = (_isManage: boolean) => {
  98 + // return nodeList.filter((el) => _isManage || !el.disabled) || [];
  99 + // };
  100 +
  101 + const sortableOptionsPage = {
  102 + disabled: !isManage || nodeEdit,
  103 + animation: 150,
  104 + fallbackOnBody: true,
  105 + swapThreshold: 0.65,
  106 + ghostClass: 'qx-node-panel--drag',
  107 + group: 'page',
  108 + };
  109 +
  110 + const onExit = () => {
  111 + setIsManage(false);
  112 + setNodeList(cloneDeep(originalNodeList));
  113 + };
  114 +
  115 + useEffect(() => {
  116 + const _data = cloneDeep(originalNodeList);
  117 + setNodeList(_data.filter((el) => isManage || !el.disabled) || []);
  118 + }, [isManage]);
  119 + const generateUniqueName = () => {
  120 + if (nameList?.length) {
  121 + let numberList: number[] = [];
  122 + nameList.forEach((el) => {
  123 + const a = /未命名分组\((.*?)\)/gi;
  124 + const b = el.match(a);
  125 + const c = el.split(b?.[0] || ''); // 判断前后是否还有字符
  126 + const _firstStr = b?.[0].replace(a, '$1');
  127 + if (!isNaN(Number(_firstStr)) && !c[1] && !c[0]) {
  128 + numberList.push(Number(_firstStr));
  129 + }
  130 + });
  131 + if (numberList.length) {
  132 + numberList = numberList.sort();
  133 + const _length = numberList.length;
  134 + for (let i = 1; i <= numberList[_length - 1]; i++) {
  135 + if (!numberList.includes(i)) {
  136 + return `未命名分组(${i})`;
  137 + }
  138 + }
  139 + return `未命名分组(${_length + 1})`;
  140 + }
  141 + return '未命名分组(1)';
  142 + }
  143 + return '未命名分组(1)';
  144 + };
  145 +
  146 + // 新增分组
  147 + const addGroup = () => {
  148 + setNodeList((list) => {
  149 + const _list = cloneDeep(list || []);
  150 + _list.push({
  151 + id: uuid(),
  152 + name: generateUniqueName(),
  153 + disabled: false,
  154 + nodes: [],
  155 + edit: true,
  156 + });
  157 + return _list;
  158 + });
  159 + };
  160 +
  161 + const saveData = () => {
  162 + console.log(nodeList, 'llllllnodeList');
  163 + };
  164 +
  165 + return (
  166 + <Drawer
  167 + visible={visible}
  168 + onClose={onClose}
  169 + width={475}
  170 + className={`${prefix}`}
  171 + title={null}
  172 + closable={false}
  173 + >
  174 + <div className={`${prefix}__title`}>
  175 + <div className={`${prefix}__title-left`}>节点类型</div>
  176 + <div className={`${prefix}__title-right`}>
  177 + {activeKey !== 'SYSTEM' && !isManage ? (
  178 + <span
  179 + className={`${prefix}--manage`}
  180 + onClick={() => setIsManage(true)}
  181 + >
  182 + 管理
  183 + </span>
  184 + ) : null}
  185 + {isManage ? (
  186 + <>
  187 + <span onClick={() => onExit()}>退出</span>
  188 + <span className={`${prefix}--save`} onClick={() => saveData()}>
  189 + 保存
  190 + </span>
  191 + </>
  192 + ) : null}
  193 + </div>
  194 + </div>
  195 + <Tabs
  196 + activeKey={activeKey}
  197 + className={`${prefix}__tabs`}
  198 + onChange={(key) => setActiveKey(key)}
  199 + >
  200 + {panelTabs.map((item) => {
  201 + return (
  202 + <Tabs.TabPane tab={item.name} key={item.key}>
  203 + {isManage && !loading ? (
  204 + <div className={`${prefix}__opt`}>
  205 + <span
  206 + className={`${prefix}__opt-icon`}
  207 + onClick={() => addGroup()}
  208 + >
  209 + <QxIcon
  210 + className={`${prefix}__left-icon`}
  211 + type={'icon-frame-todolist'}
  212 + />
  213 + 分组
  214 + </span>
  215 + {activeKey === 'DEVELOP' ? (
  216 + <span className={`${prefix}__opt-icon`} onClick={() => {}}>
  217 + <QxIcon
  218 + className={`${prefix}__left-icon`}
  219 + type={'icon-frame-todolist'}
  220 + />
  221 + 开发节点管理
  222 + </span>
  223 + ) : null}
  224 + </div>
  225 + ) : null}
  226 + <div
  227 + className={`${prefix}__content ${
  228 + isManage ? `${prefix}__content--manage` : ''
  229 + }`}
  230 + >
  231 + {loading ? (
  232 + <Spin className={`${prefix}__loading`} />
  233 + ) : (
  234 + <>
  235 + {nodeList?.length ? (
  236 + <ReactSortable
  237 + list={nodeList}
  238 + setList={setNodeList}
  239 + {...sortableOptionsPage}
  240 + >
  241 + {nodeList.map((bl, index) => {
  242 + return (
  243 + <NodeList
  244 + key={bl.id}
  245 + listItem={bl}
  246 + isManage={isManage}
  247 + setNodeList={setNodeList}
  248 + nodeIndex={index}
  249 + setNodeEdit={setNodeEdit}
  250 + />
  251 + );
  252 + })}
  253 + </ReactSortable>
  254 + ) : (
  255 + <div className={`${prefix}--empty ${prefix}--empty-max`}>
  256 + <img src={Empty} alt={'暂无数据'} />
  257 + <span>暂无数据</span>
  258 + </div>
  259 + )}
  260 + </>
  261 + )}
  262 + </div>
  263 + </Tabs.TabPane>
  264 + );
  265 + })}
  266 + </Tabs>
  267 + </Drawer>
  268 + );
  269 +};
  270 +
  271 +interface NodePanelProps extends INodePanelProps {}
  272 +
  273 +export default memo(NodePanel);
... ...
  1 +import { createFromIconfontCN } from '@ant-design/icons';
  2 +// @ts-ignore
  3 +import '@qx/icon-btn/svg/icon-park';
  4 +
  5 +const QxIcon = createFromIconfontCN({
  6 + scriptUrl: [],
  7 +});
  8 +
  9 +export default QxIcon;
... ...
  1 +export * from './useTheme';
... ...
  1 +import { useEffect } from 'react';
  2 +import { ConfigProvider } from 'antd';
  3 +import { Outlet } from 'react-router-dom';
  4 +import { defaultTheme, setTheme } from '@qx/ui';
  5 +
  6 +export const useTheme = () => {
  7 + const userTheme = JSON.parse(
  8 + window.localStorage.getItem('qx-system-theme') || 'null',
  9 + );
  10 +
  11 + const theme = {
  12 + token: {
  13 + colorPrimary: userTheme?.themeColor || defaultTheme.themeColor,
  14 + borderRadius: 4,
  15 + },
  16 + };
  17 +
  18 + const handleGenerateThemeColors = () => {
  19 + if (!userTheme) {
  20 + setTheme(defaultTheme.themeColor);
  21 + }
  22 + };
  23 +
  24 + useEffect(() => {
  25 + handleGenerateThemeColors();
  26 + ConfigProvider.config({
  27 + theme,
  28 + });
  29 + }, []);
  30 +
  31 + return (
  32 + <ConfigProvider theme={theme}>
  33 + <Outlet />
  34 + </ConfigProvider>
  35 + );
  36 +};
... ...
  1 +export type FlowVersionStatusProps =
  2 + | 'DRAFT'
  3 + | 'DISABLED'
  4 + | 'PUBLISHED'
  5 + | 'EDIT'
  6 + | 'DELETED';
  7 +export type FlowStatusProps =
  8 + | 'UNPUBLISHED'
  9 + | 'DISABLED'
  10 + | 'PUBLISHED'
  11 + | 'MODIFY'
  12 + | 'DELETED';
  13 +
  14 +export const FlowVersionStatusEnums = {
  15 + DRAFT: '未发布',
  16 + DISABLED: '已停用',
  17 + PUBLISHED: '已发布',
  18 + EDIT: '有修改',
  19 + DELETED: '已删除',
  20 +};
  21 +
  22 +export const FlowStatusEnums = {
  23 + UNPUBLISHED: '未发布',
  24 + DISABLED: '已停用',
  25 + PUBLISHED: '已发布',
  26 + MODIFY: '有修改',
  27 + DELETED: '已删除',
  28 +};
  29 +
  30 +export enum NodeTypes {
  31 + /**
  32 + * 开始
  33 + */
  34 + START = 'DF_START',
  35 + /**
  36 + * 结束
  37 + */
  38 + END = 'DF_END',
  39 + /**
  40 + * 查询单条记录
  41 + */
  42 + DQ_MODEL = 'DF_DQ_MODEL',
  43 + /**
  44 + * 查询多条记录
  45 + */
  46 + DQ_MODEL_MORE = 'DF_DQ_MODEL_MORE',
  47 + /**
  48 + * 新增记录
  49 + */
  50 + DO_C = 'DF_DO_C',
  51 + /**
  52 + * 查询部门
  53 + */
  54 + DQ_ORG = 'DF_DQ_ORG',
  55 + /**
  56 + * 查询人员
  57 + */
  58 + DQ_USER = 'DF_DQ_USER',
  59 + /**
  60 + * 填写记录
  61 + */
  62 + DO_FILL = 'DF_DO_FILL',
  63 + /**
  64 + * 延迟执行
  65 + */
  66 + APRV_DLAY = 'DF_APRV_DLAY',
  67 + /**
  68 + * 服务调用
  69 + */
  70 + WEB_HOOK = 'DF_WEB_HOOK',
  71 + /**
  72 + * 脚本节点
  73 + */
  74 + CODE = 'DF_CODE',
  75 + /**
  76 + * 删除记录
  77 + */
  78 + DO_D = 'DF_DO_D',
  79 + /**
  80 + * 更新记录
  81 + */
  82 + DO_U = 'DF_DO_U',
  83 + /**
  84 + * 设置参数
  85 + */
  86 + DO_PARAM = 'DF_DO_PARAM',
  87 + /**
  88 + * 公式运算
  89 + */
  90 + DO_CAL = 'DF_DO_CAL',
  91 +}
  92 +
  93 +export interface ProcessItemProps {
  94 + name?: string;
  95 + appId?: string;
  96 + id?: string;
  97 + funCode?: string;
  98 + processJson?: string;
  99 + remark?: string;
  100 + funId?: string;
  101 + latestConfigStatus?: FlowStatusProps;
  102 + curVersion?: string | number;
  103 +}
... ...
1 1 import React, { memo } from 'react';
2   -import './styles.less';
3 2
4 3 const About: React.FC<AboutProps> = (props) => {
5   - return <div className="about">About</div>;
  4 + return <div>About</div>;
6 5 };
7 6
8 7 interface AboutProps {}
... ...
1   -.about {
2   - background-color: #ccc;
3   -}
  1 +import React, { useEffect, useRef, useState, useContext } from 'react';
  2 +import FlowBuilder, { IQxNode, dataConversion, BuilderContext } from '@qx/flow';
  3 +import {
  4 + Button,
  5 + Drawer,
  6 + message,
  7 + Popconfirm,
  8 + Popover,
  9 + Space,
  10 + Tooltip,
  11 + Typography,
  12 + Dropdown,
  13 + Menu,
  14 + Modal,
  15 +} from 'antd';
  16 +import { Start, End } from './nodes';
  17 +import { NodeTypes, ProcessItemProps, FlowStatusEnums } from '@/interface';
  18 +import { handleWindowOpen } from '@qx/utils';
  19 +// import { history } from '@@/core/history';
  20 +import { useLocation, useSearchParams } from 'react-router-dom';
  21 +import { useTitle } from 'ahooks';
  22 +// 头部相关
  23 +import {
  24 + FlowHeader,
  25 + FlowVersion,
  26 + NameDescriptionSetting,
  27 + NodesPanel,
  28 + ConfigDrawer,
  29 +} from '@/components';
  30 +import cls from 'classnames';
  31 +import styles from '@/components/header/index.module.less';
  32 +import { getIcon } from '@/utils';
  33 +import { QxIcon } from '@/components';
  34 +import {
  35 + getProcessDetailsById,
  36 + publishProcessById,
  37 + saveProcessSetting,
  38 +} from '@/services';
  39 +import type { IRegisterNode, INode, IRegisterRemoteNode } from '@qx/flow';
  40 +
  41 +const prefix = 'qx-data-flow';
  42 +
  43 +const { Text } = Typography;
  44 +
  45 +const nameMaxLength = 20; // 数据流名称 最长文字 限制
  46 +
  47 +// TODO: 后续 systemNodes 要删掉,所有节点都是 registerRemoteNode
  48 +const systemNodes: IRegisterNode[] = [
  49 + {
  50 + type: NodeTypes.START,
  51 + name: '开始节点',
  52 + // displayComponent: Start.DisplayComponent,
  53 + // configComponent: Start.ConfigComponent,
  54 + isStart: true,
  55 + },
  56 + {
  57 + type: NodeTypes.END,
  58 + name: '结束节点',
  59 + // displayComponent: End.DisplayComponent,
  60 + // configComponent: End.ConfigComponent,
  61 + isEnd: true,
  62 + },
  63 + // {
  64 + // type: 'condition',
  65 + // name: '条件节点',
  66 + // displayComponent: Condition.DisplayComponent,
  67 + // configComponent: Condition.ConfigComponent,
  68 + // },
  69 +];
  70 +
  71 +export default () => {
  72 + const [nodes, setNodes] = useState<INode[]>([]);
  73 + const [remoteNodes, setRemoteNodes] = useState<IRegisterRemoteNode[]>([]);
  74 + // @ts-ignore
  75 + const [searchParams] = useSearchParams();
  76 + const [showEditTitle, setShowEditTitle] = useState(false);
  77 + const {
  78 + appId, // 应用id
  79 + funCode, // 表单id
  80 + returnUrl, // 来自页面跳转返回可跳转
  81 + processId, // 流程Id preview
  82 + type, // 流程类型 edit 编辑 preview 预览
  83 + } = {
  84 + appId: searchParams.get('appId'),
  85 + funCode: searchParams.get('funCode'),
  86 + returnUrl: searchParams.get('returnUrl'),
  87 + processId: searchParams.get('processId'),
  88 + type: searchParams.get('type'),
  89 + };
  90 +
  91 + const [processDetails, setProcessDetails] = useState<ProcessItemProps>({
  92 + id: '',
  93 + appId: '',
  94 + name: '',
  95 + remark: '',
  96 + }); // 数据流 详细数据
  97 +
  98 + const [showExtendModal, setShowExtendModal] = useState(false);
  99 + const [showSimple, setShowSimple] = useState<boolean>(false);
  100 +
  101 + useTitle(processDetails?.name || '未命名流程'); // 动态变化 文档标题
  102 +
  103 + const formModalRef = useRef({
  104 + open: (data?: any) => {},
  105 + });
  106 +
  107 + // 获取详情
  108 + const getDetailsData = (_id: string, _data?: ProcessItemProps) => {
  109 + getProcessDetailsById(_id)
  110 + .then((res: any) => {
  111 + setProcessDetails({
  112 + ..._data,
  113 + ...(res || {}),
  114 + });
  115 + })
  116 + .catch((err: { msg?: string }) => {
  117 + message.warning(err?.msg || '流程详情数据请求失败', 5);
  118 + setProcessDetails(
  119 + _data || {
  120 + id: _id,
  121 + name: '未命名流程',
  122 + appId,
  123 + },
  124 + );
  125 + });
  126 + };
  127 +
  128 + // 点击事件
  129 + const handleClick = (
  130 + type: string,
  131 + data?: any,
  132 + e?: React.MouseEvent<HTMLElement, MouseEvent>,
  133 + ) => {
  134 + const { host, pathname, protocol, hash } = location;
  135 + let _params: ProcessItemProps = {}; // 接口调用参数
  136 +
  137 + if (!!e) {
  138 + e.stopPropagation();
  139 + }
  140 +
  141 + _params = {
  142 + appId: processDetails?.appId,
  143 + };
  144 + if (!!processDetails?.id) {
  145 + _params.id = processDetails?.id;
  146 + }
  147 + if (!!processDetails?.funCode) {
  148 + _params.funCode = processDetails?.funCode;
  149 + }
  150 +
  151 + switch (type) {
  152 + case 'RETURN':
  153 + // 返回上一页
  154 + if (!!returnUrl) {
  155 + handleWindowOpen(
  156 + `${protocol}//${host}${pathname}${returnUrl}`,
  157 + '_self',
  158 + );
  159 + } else {
  160 + window.history.back();
  161 + }
  162 + break;
  163 + case 'SAVE':
  164 + // TODO 保存草稿 保存节点相关 参数 processJson
  165 + console.log('SAVE === ', dataConversion(nodes));
  166 + _params.processJson = JSON.stringify({
  167 + dataJson: [...dataConversion(nodes)],
  168 + });
  169 + saveProcessSetting(_params)
  170 + .then((res: string) => {
  171 + console.log('res ===', res);
  172 + if (!!res) {
  173 + // setProcessDetails({
  174 + // ...processDetails,
  175 + // id: processDetails?.id || res,
  176 + // name: data.name,
  177 + // remark: data.remark,
  178 + // });
  179 + // if (!processDetails?.id) {
  180 + // history.replace({
  181 + // query: {
  182 + // ...query,
  183 + // processId: res,
  184 + // }
  185 + // });
  186 + // }
  187 + }
  188 + })
  189 + .catch((err: { msg?: string }) => {
  190 + message.warning(err?.msg || '节点配置不完善', 5);
  191 + });
  192 + break;
  193 + case 'DEBUG':
  194 + // 调试
  195 + console.log('DEBUG === ');
  196 + break;
  197 + case 'RELEASE':
  198 + // 发布流程
  199 + // TODO 要先校验是否保存 才能发布
  200 + publishProcessById(processDetails.id || '')
  201 + .then((res) => {
  202 + console.log('res ====', res);
  203 + })
  204 + .catch((err: { msg?: string }) => {
  205 + message.warning(err?.msg || '节点配置不完善', 5);
  206 + });
  207 + break;
  208 + case 'EDIT':
  209 + // 编辑数据流名称
  210 + const obj: ProcessItemProps = {
  211 + id: processDetails.id || '',
  212 + name: processDetails.name || '',
  213 + remark: processDetails.remark || '',
  214 + };
  215 + formModalRef.current.open(obj);
  216 + break;
  217 + case 'VERSION':
  218 + // 版本管理跳转
  219 + handleWindowOpen(
  220 + `${protocol}//${host}${pathname}#/design/data-flow-view/version?processId=${
  221 + data?.id || ''
  222 + }&name=${data?.name || ''}&returnUrl=${hash}`,
  223 + '_self',
  224 + );
  225 + break;
  226 + case 'SAVE_NAME':
  227 + _params.name = data.name;
  228 + _params.remark = data?.remark || '';
  229 + // 保存 名称、说明
  230 + saveProcessSetting(_params)
  231 + .then((res: string) => {
  232 + if (!!res) {
  233 + setProcessDetails({
  234 + ...processDetails,
  235 + id: processDetails?.id || res,
  236 + name: data.name,
  237 + remark: data.remark,
  238 + });
  239 + if (!processDetails?.id) {
  240 + history.replace({
  241 + query: {
  242 + ...query,
  243 + processId: res,
  244 + },
  245 + });
  246 + }
  247 + }
  248 + })
  249 + .catch((err: { msg?: string }) => {
  250 + message.warning(err?.msg || '当前保存操作失败', 5);
  251 + });
  252 + break;
  253 + case 'SIMPLE':
  254 + // 精简模式
  255 + setShowSimple(!showSimple);
  256 + break;
  257 + default:
  258 + break;
  259 + }
  260 + };
  261 +
  262 + // 头部 左侧 自定义
  263 + const renderLeft = () => {
  264 + return (
  265 + <section className={cls(styles[`${prefix}__header__left-custom`])}>
  266 + <div
  267 + className={cls(styles[`${prefix}__header__left`])}
  268 + onClick={() => {
  269 + handleClick('RETURN');
  270 + }}
  271 + >
  272 + <QxIcon
  273 + className={cls(styles[`${prefix}__header__left-return`])}
  274 + type={'icon-common-arrow'}
  275 + />
  276 + <Text
  277 + className={cls(styles[`${prefix}__header__left-title`])}
  278 + style={{ maxWidth: nameMaxLength * 16 * 1.2 + 'px' }}
  279 + ellipsis={{ tooltip: processDetails?.name || '' }}
  280 + >
  281 + {processDetails?.name || ''}
  282 + </Text>
  283 + </div>
  284 + <div className={cls(styles[`${prefix}__header__left-custom__opt`])}>
  285 + {!!processDetails?.remark ? (
  286 + <Tooltip placement="bottom" title={processDetails?.remark}>
  287 + <Button type="link" icon={<QxIcon type={'icon-frame-help'} />} />
  288 + </Tooltip>
  289 + ) : null}
  290 + {type === 'preview' ? null : (
  291 + <Tooltip placement="bottom" title={'编辑名称与说明'}>
  292 + <Button
  293 + type="link"
  294 + icon={<QxIcon type={'icon-frame-edit'} />}
  295 + onClick={() => {
  296 + handleClick('EDIT');
  297 + }}
  298 + />
  299 + </Tooltip>
  300 + )}
  301 + </div>
  302 + <Space size={8}>
  303 + {type === 'preview' ? (
  304 + <FlowVersion versionText={processDetails?.curVersion || 1} />
  305 + ) : (
  306 + <FlowVersion
  307 + versionText={processDetails?.curVersion || 1}
  308 + handleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  309 + handleClick(
  310 + 'VERSION',
  311 + {
  312 + id: processDetails?.id,
  313 + name: processDetails?.name,
  314 + },
  315 + e,
  316 + );
  317 + }}
  318 + />
  319 + )}
  320 + <FlowVersion
  321 + status={processDetails?.latestConfigStatus || 'DRAFT'}
  322 + statusEnums={FlowStatusEnums}
  323 + />
  324 + </Space>
  325 + </section>
  326 + );
  327 + };
  328 +
  329 + const renderRight = () => {
  330 + return (
  331 + <Space size={12}>
  332 + <Button
  333 + onClick={() => {
  334 + handleClick && handleClick('SIMPLE');
  335 + }}
  336 + >
  337 + {!showSimple ? '精简模式' : '退出精简'}
  338 + </Button>
  339 + <Button
  340 + disabled={false} // 需要校验 是否禁用
  341 + onClick={() => {
  342 + handleClick && handleClick('DEBUG');
  343 + }}
  344 + >
  345 + 调试
  346 + </Button>
  347 + <Button
  348 + disabled={false} // 需要校验 是否禁用
  349 + onClick={() => {
  350 + handleClick && handleClick('SAVE');
  351 + }}
  352 + >
  353 + 保存
  354 + </Button>
  355 + <Button
  356 + disabled={false} // 需要校验 是否禁用
  357 + type="primary"
  358 + // loading={false}
  359 + onClick={() => {
  360 + handleClick && handleClick('RELEASE');
  361 + }}
  362 + >
  363 + 发布流程
  364 + </Button>
  365 + </Space>
  366 + );
  367 + };
  368 +
  369 + const handleChange = (nodes: INode[]) => {
  370 + setNodes(nodes);
  371 + };
  372 +
  373 + const handleGetRemoteNodes = () => {
  374 + setRemoteNodes([
  375 + {
  376 + url: 'https://unpkg.com/react-flow-builder-pkg-demo@1.2.0/dist/index.umd.js',
  377 + },
  378 + ]);
  379 + };
  380 +
  381 + useEffect(() => {
  382 + // setNodes(dataConversion(defaultNodes as IQxNode[], true));
  383 + handleGetRemoteNodes();
  384 + }, []);
  385 +
  386 + useEffect(() => {
  387 + console.log('nodes ==== ', nodes);
  388 + }, [nodes]);
  389 + useEffect(() => {
  390 + let _data: ProcessItemProps = {};
  391 + _data.name = '未命名流程';
  392 + _data.appId = appId;
  393 + if (!!funCode) {
  394 + _data.funCode = funCode;
  395 + } else {
  396 + if (!!_data.funCode) delete _data.funCode;
  397 + }
  398 + if (!processId) {
  399 + setProcessDetails(_data);
  400 + } else {
  401 + getDetailsData(processId, _data);
  402 + }
  403 + }, [appId, funCode, processId]);
  404 +
  405 + const handleOpenExtendModal = () => {
  406 + setShowExtendModal(true);
  407 + };
  408 +
  409 + const handleCloseExtendModal = () => {
  410 + setShowExtendModal(false);
  411 + };
  412 +
  413 + const getNodeTool = (
  414 + node: INode,
  415 + nodes: INode[],
  416 + defaultTools: INode[],
  417 + actions: any,
  418 + ) => {
  419 + const { copyNodeAndChildren } = actions;
  420 + switch (node.type) {
  421 + case 'condition':
  422 + return [
  423 + ...defaultTools,
  424 + {
  425 + name: '复制分支',
  426 + icon: '复制分支',
  427 + onClick: () => copyNodeAndChildren(),
  428 + },
  429 + ];
  430 + default:
  431 + return [
  432 + ...defaultTools,
  433 + {
  434 + name: '封装节点',
  435 + icon: '封装节点',
  436 + onClick: () => handleOpenExtendModal(),
  437 + },
  438 + ];
  439 + }
  440 + };
  441 +
  442 + return (
  443 + <div className={cls(styles[`${prefix}__container`])}>
  444 + <FlowHeader
  445 + renderLeft={renderLeft()}
  446 + handleClick={handleClick}
  447 + renderRight={type === 'preview' ? null : renderRight()}
  448 + />
  449 + <FlowBuilder
  450 + readonly={type === 'preview'}
  451 + nodes={nodes}
  452 + registerNodes={systemNodes}
  453 + historyTool
  454 + zoomTool
  455 + expandAllTool
  456 + DrawerComponent={Drawer}
  457 + PopoverComponent={Popover}
  458 + PopconfirmComponent={Popconfirm}
  459 + DropdownComponent={Dropdown}
  460 + MenuComponent={Menu}
  461 + // CommonDisplay={CommonDisplay}
  462 + onChange={handleChange}
  463 + registerRemoteNodes={remoteNodes}
  464 + ConfigDrawerContainer={ConfigDrawer}
  465 + NodePanel={NodesPanel}
  466 + getNodeTool={getNodeTool}
  467 + />
  468 + <NameDescriptionSetting cRef={formModalRef} handleClick={handleClick} />
  469 + <Modal
  470 + title="扩展节点"
  471 + visible={showExtendModal}
  472 + onCancel={handleCloseExtendModal}
  473 + />
  474 + </div>
  475 + );
  476 +};
... ...
  1 +import React, { useContext, memo, useImperativeHandle } from 'react';
  2 +import { useForm } from '@qx/form-render';
  3 +import QxFormRender from '@/packages/qx-form-generator/src/form-render';
  4 +import { conditionSetting } from '../../setting-configs';
  5 +import styles from './index.less';
  6 +import type { IConfigComponent } from '@qx/flow';
  7 +
  8 +const StartConfigComponent = React.forwardRef<any, StartConfigComponentProps>(
  9 + (props, ref) => {
  10 + const form = useForm();
  11 +
  12 + const handleValuesChange = (values: any) => {
  13 + props?.onChange(values);
  14 + };
  15 +
  16 + useImperativeHandle(ref, () => ({
  17 + validateFields: form.validateFields,
  18 + }));
  19 +
  20 + return (
  21 + <div className={styles['qx-flow-single-records-config']}>
  22 + <QxFormRender
  23 + form={form}
  24 + schema={conditionSetting}
  25 + widgets={{}}
  26 + watch={{}}
  27 + onValuesChange={handleValuesChange}
  28 + />
  29 + </div>
  30 + );
  31 + },
  32 +);
  33 +
  34 +interface StartConfigComponentProps extends IConfigComponent {
  35 + onChange: (values: any) => void;
  36 +}
  37 +
  38 +export default memo(StartConfigComponent);
... ...
  1 +import React, { memo, useContext } from 'react';
  2 +import { useAction } from '@qx/flow';
  3 +import type { IDisplayComponent } from '@qx/flow';
  4 +
  5 +const ConditionDisplayComponent: React.FC<ConditionDisplayComponentProps> = (
  6 + props,
  7 +) => {
  8 + return <>条件节点内容</>;
  9 +};
  10 +
  11 +interface ConditionDisplayComponentProps extends IDisplayComponent {}
  12 +
  13 +export default memo(ConditionDisplayComponent);
... ...
  1 +import ConfigComponent from './configComponent';
  2 +import DisplayComponent from './displayComponent';
  3 +
  4 +export default {
  5 + DisplayComponent,
  6 + ConfigComponent,
  7 +};
... ...
  1 +import React, { useContext, memo } from 'react';
  2 +import { useForm } from '@qx/form-render';
  3 +import { useDrawer } from '@qx/flow';
  4 +import { ConfigComponentFooter } from '../../components';
  5 +import QxFormRender from '@/packages/qx-form-generator/src/form-render';
  6 +import { startSetting } from '../../setting-configs';
  7 +import styles from './index.less';
  8 +import type { IConfigComponent } from '@qx/flow';
  9 +
  10 +const EndConfigComponent: React.FC<EndConfigComponentProps> = (props) => {
  11 + const form = useForm();
  12 + const { closeDrawer: cancel, saveDrawer: save } = useDrawer();
  13 +
  14 + const onSave = async () => {
  15 + try {
  16 + await form.submit();
  17 + const values = form.getValues();
  18 + console.log('values', values);
  19 + save(values);
  20 + } catch (error) {
  21 + console.log(error);
  22 + }
  23 + };
  24 +
  25 + return (
  26 + <div className={styles['qx-flow-single-records-config']}>
  27 + <QxFormRender form={form} schema={startSetting} widgets={{}} watch={{}} />
  28 + <ConfigComponentFooter onSave={onSave} onCancel={cancel} />
  29 + </div>
  30 + );
  31 +};
  32 +
  33 +interface EndConfigComponentProps extends IConfigComponent {}
  34 +
  35 +export default memo(EndConfigComponent);
... ...
  1 +import React, { memo } from 'react';
  2 +// import { ReactComponent as Icon } from "../../public/icons/outlined.svg";
  3 +import type { IDisplayComponent, INode } from '@qx/flow';
  4 +import './index.less';
  5 +
  6 +const DisplayComponent: React.FC<DisplayComponentProps> = (props) => {
  7 + const { node, nodes } = props;
  8 +
  9 + return (
  10 + <div className="qx-flow-default-node">
  11 + <div className="qx-flow-default-node__header">
  12 + <span className="qx-flow-default-node__title">
  13 + <span className="qx-flow-default-node__title--icon">
  14 + {/* <Icon /> */}
  15 + </span>
  16 + <span className="qx-flow-default-node__title--name">{node.name}</span>
  17 + </span>
  18 + </div>
  19 + <div className="qx-flow-default-node__content">
  20 + {!node.data ? (
  21 + <span className="qx-flow-start-node__button">设置入参</span>
  22 + ) : (
  23 + <span>节点配置内容</span>
  24 + )}
  25 + </div>
  26 + </div>
  27 + );
  28 +};
  29 +
  30 +interface DisplayComponentProps extends IDisplayComponent {
  31 + onChange?: (nodes: INode[]) => void;
  32 +}
  33 +
  34 +export default memo(DisplayComponent);
... ...
  1 +import ConfigComponent from './configComponent';
  2 +import DisplayComponent from './displayComponent';
  3 +
  4 +export default {
  5 + DisplayComponent,
  6 + // ConfigComponent,
  7 +};
... ...
  1 +export { default as Start } from './start';
  2 +export { default as End } from './end';
  3 +// export { default as Condition } from './condition';
  4 +export { default as Loop } from './loop';
... ...
  1 +import React, { useContext, memo } from 'react';
  2 +import { useForm } from '@qx/form-render';
  3 +import { useDrawer } from '@qx/flow';
  4 +import { ConfigComponentFooter } from '../../components';
  5 +import QxFormRender from '@/packages/qx-form-generator/src/form-render';
  6 +import { loopSetting } from '../../setting-configs';
  7 +import styles from './index.less';
  8 +import type { IConfigComponent } from '@qx/flow';
  9 +
  10 +const LoopConfigComponent: React.FC<LoopConfigComponentProps> = (props) => {
  11 + const form = useForm();
  12 + const { closeDrawer: cancel, saveDrawer: save } = useDrawer();
  13 +
  14 + const onSave = async () => {
  15 + try {
  16 + await form.submit();
  17 + const values = form.getValues();
  18 + console.log('values', values);
  19 + save(values);
  20 + } catch (error) {
  21 + console.log(error);
  22 + }
  23 + };
  24 +
  25 + return (
  26 + <div className={styles['qx-flow-single-records-config']}>
  27 + <QxFormRender form={form} schema={LoopSetting} widgets={{}} watch={{}} />
  28 + <ConfigComponentFooter onSave={onSave} onCancel={cancel} />
  29 + </div>
  30 + );
  31 +};
  32 +
  33 +interface LoopConfigComponentProps extends IConfigComponent {}
  34 +
  35 +export default memo(LoopConfigComponent);
... ...
  1 +import React, { memo, useContext } from 'react';
  2 +import { useAction } from '@qx/flow';
  3 +import type { IDisplayComponent } from '@qx/flow';
  4 +
  5 +const ConditionDisplayComponent: React.FC<ConditionDisplayComponentProps> = (
  6 + props,
  7 +) => {
  8 + return <>条件节点内容</>;
  9 +};
  10 +
  11 +interface ConditionDisplayComponentProps extends IDisplayComponent {}
  12 +
  13 +export default memo(ConditionDisplayComponent);
... ...
  1 +import ConfigComponent from './configComponent';
  2 +import DisplayComponent from './displayComponent';
  3 +
  4 +export default {
  5 + // DisplayComponent,
  6 + // ConfigComponent,
  7 +};
... ...
  1 +import React, {
  2 + useContext,
  3 + memo,
  4 + useState,
  5 + useEffect,
  6 + useImperativeHandle,
  7 +} from 'react';
  8 +import { Checkbox, Form, Input, Radio, Space, Tooltip } from 'antd';
  9 +import type { CheckboxChangeEvent, RadioChangeEvent } from 'antd/es/checkbox';
  10 +
  11 +import type { IConfigComponent } from '@qx/flow';
  12 +import { QxParameterSetting } from '@qx/common';
  13 +import { QxIcon } from '@/components';
  14 +import _ from 'lodash';
  15 +
  16 +import { isolationList, propagationList } from '../../setting-configs/start';
  17 +interface PropagationSettingProps {
  18 + value: string;
  19 + onChange: (val: string) => void;
  20 + optionsList: {
  21 + value: string;
  22 + label: string;
  23 + tips: string;
  24 + }[];
  25 +}
  26 +const PropagationSetting: React.FC<PropagationSettingProps> = (props) => {
  27 + const { value, onChange, optionsList } = props;
  28 +
  29 + return (
  30 + <Radio.Group
  31 + onChange={(e: RadioChangeEvent) => {
  32 + onChange(e.target.value);
  33 + }}
  34 + value={value}
  35 + >
  36 + <Space direction="vertical">
  37 + {(optionsList || []).map(
  38 + (_options: { value: string; label: string; tips: string }) => {
  39 + return (
  40 + <Radio value={_options.value}>
  41 + <span>{_options.label}</span>
  42 + <Tooltip placement="right" title={_options.tips}>
  43 + <QxIcon
  44 + className={'qx-data-flow-tooltip-opt'}
  45 + type={'icon-frame-help'}
  46 + />
  47 + </Tooltip>
  48 + </Radio>
  49 + );
  50 + },
  51 + )}
  52 + </Space>
  53 + </Radio.Group>
  54 + );
  55 +};
  56 +
  57 +const StartConfigComponent: React.FC<StartConfigComponentProps> =
  58 + React.forwardRef((props, ref) => {
  59 + const [form] = Form.useForm();
  60 +
  61 + useImperativeHandle(ref, () => ({
  62 + validateFields: form.validateFields,
  63 + }));
  64 +
  65 + const [values, setValues] = useState<{
  66 + params: any[];
  67 + enablePropagation: boolean;
  68 + propagation: string;
  69 + isolation: string;
  70 + }>({
  71 + params: [],
  72 + enablePropagation: false,
  73 + propagation: 'REQUIRED',
  74 + isolation: 'REPEATABLE_READ',
  75 + });
  76 +
  77 + useEffect(() => {
  78 + if (_.isEmpty(props?.node?.data)) {
  79 + return;
  80 + }
  81 + if (_.isEqual(props?.node?.data, values)) {
  82 + return;
  83 + }
  84 + setValues(props?.node?.data);
  85 + }, [props?.node?.data]);
  86 +
  87 + const onCheckedChange = (e: CheckboxChangeEvent) => {
  88 + const _cloneVal = _.cloneDeep(values);
  89 + setValues({
  90 + ..._cloneVal,
  91 + enablePropagation: e.target.checked,
  92 + });
  93 + };
  94 +
  95 + useEffect(() => {
  96 + if (_.isEqual(props?.node?.data, values)) {
  97 + return;
  98 + }
  99 + props.onChange(values);
  100 + }, [values]);
  101 +
  102 + return (
  103 + <Form
  104 + form={form}
  105 + layout="vertical"
  106 + initialValues={{
  107 + enablePropagation: false,
  108 + propagation: 'REQUIRED',
  109 + isolation: 'REPEATABLE_READ',
  110 + }}
  111 + >
  112 + <Form.Item name="params">
  113 + <h3 className={'qx-flow-form__label-title'}>入参设置</h3>
  114 + <p className={'qx-flow-form__label-tips'}>
  115 + 参数可以被后面的所有数据流节点使用
  116 + </p>
  117 + <QxParameterSetting
  118 + value={values.params}
  119 + onChange={(_v: any) => {
  120 + console.log('参数 === ', _v);
  121 + }}
  122 + />
  123 + </Form.Item>
  124 + <Form.Item name="enablePropagation">
  125 + <h3 className={'qx-flow-form__label-title'}>事务控制</h3>
  126 + <Checkbox
  127 + checked={values.enablePropagation}
  128 + onChange={onCheckedChange}
  129 + >
  130 + 设置事务的传播行为和隔离级别
  131 + </Checkbox>
  132 + </Form.Item>
  133 + <Form.Item
  134 + hidden={!values.enablePropagation}
  135 + label="事务传播行为"
  136 + name="propagation"
  137 + >
  138 + <PropagationSetting
  139 + optionsList={propagationList}
  140 + value={'REQUIRED'}
  141 + onChange={(_v: string) => {
  142 + const _cloneVal = _.cloneDeep(values);
  143 + setValues({
  144 + ..._cloneVal,
  145 + propagation: _v,
  146 + });
  147 + }}
  148 + />
  149 + </Form.Item>
  150 + <Form.Item
  151 + hidden={!values.enablePropagation}
  152 + label="事务隔离级别"
  153 + name="isolation"
  154 + >
  155 + <PropagationSetting
  156 + optionsList={isolationList}
  157 + value={'REPEATABLE_READ'}
  158 + onChange={(_v: string) => {
  159 + const _cloneVal = _.cloneDeep(values);
  160 + setValues({
  161 + ..._cloneVal,
  162 + isolation: _v,
  163 + });
  164 + }}
  165 + />
  166 + </Form.Item>
  167 + </Form>
  168 + );
  169 + });
  170 +
  171 +interface StartConfigComponentProps extends IConfigComponent {
  172 + onChange: (values: any) => void;
  173 +}
  174 +
  175 +export default memo(StartConfigComponent);
... ...
  1 +import React, { memo } from 'react';
  2 +// import { ReactComponent as Icon } from '../../public/icons/outlined.svg';
  3 +import type { IDisplayComponent, INode } from '@qx/flow';
  4 +// import './index.less';
  5 +
  6 +const DisplayComponent: React.FC<DisplayComponentProps> = (props) => {
  7 + const { node, nodes } = props;
  8 +
  9 + const handleChangeName = (newValue: string) => {
  10 + node.name = newValue;
  11 + props.onChange?.([...nodes]);
  12 + };
  13 +
  14 + return (
  15 + <div className="qx-flow-default-node">
  16 + <div className="qx-flow-default-node__header">
  17 + <span className="qx-flow-default-node__title">
  18 + <span className="qx-flow-default-node__title--icon">
  19 + {/* <Icon /> */}
  20 + </span>
  21 + <span className="qx-flow-default-node__title--name">{node.name}</span>
  22 + </span>
  23 + </div>
  24 + <div className="qx-flow-default-node__content">
  25 + {!node.data ? (
  26 + <span className="qx-flow-end-node__button">设置入参</span>
  27 + ) : (
  28 + <span>节点配置内容</span>
  29 + )}
  30 + </div>
  31 + </div>
  32 + );
  33 +};
  34 +
  35 +interface DisplayComponentProps extends IDisplayComponent {
  36 + onChange?: (nodes: INode[]) => void;
  37 +}
  38 +
  39 +export default memo(DisplayComponent);
... ...
  1 +import ConfigComponent from './configComponent';
  2 +import DisplayComponent from './displayComponent';
  3 +
  4 +export default {
  5 + DisplayComponent,
  6 + // ConfigComponent,
  7 +};
... ...
1 1 import React, { memo } from 'react';
2   -import { Outlet } from 'react-router-dom';
  2 +import { useTheme } from '@/hooks';
  3 +import '@/utils/actions';
  4 +import '@/styles/normalize.less';
3 5
4 6 const Root: React.FC = (props) => {
5   - return (
6   - <div>
7   - <Outlet />
8   - </div>
9   - );
  7 + const ThemeConfigProvider = useTheme();
  8 + return <>{ThemeConfigProvider}</>;
10 9 };
11 10
12 11 export default memo(Root);
... ...
  1 +.qx-flow-quote-container {
  2 + .ant-modal {
  3 + .ant-modal-body {
  4 + padding-top: 16px;
  5 + padding-bottom: 16px;
  6 +
  7 + .qx-flow-quote__header {
  8 + font-size: 16px;
  9 + line-height: 32px;
  10 + padding-bottom: 12px;
  11 + color: @N8;
  12 + }
  13 + }
  14 + }
  15 +}
... ...
  1 +import React, {
  2 + useEffect,
  3 + useImperativeHandle,
  4 + useMemo,
  5 + useState,
  6 +} from 'react';
  7 +import { Button, Modal, Space, Table, Typography } from 'antd';
  8 +import { QxIcon } from '@/components';
  9 +import { isEmpty } from 'lodash-es';
  10 +import { handleWindowOpen } from '@qx/utils';
  11 +// import {} from '@/src/services/data-flow';
  12 +import { getIcon, useFlowTableScrollY } from '@/utils';
  13 +import { useDebounceEffect } from 'ahooks';
  14 +
  15 +const { Text } = Typography;
  16 +
  17 +import '@/styles/modal-reset.less';
  18 +import cls from 'classnames';
  19 +import commonStyles from '@/page/view/index.module.less';
  20 +import './index.less';
  21 +
  22 +const prefix = 'qx-flow-setting';
  23 +
  24 +interface FlowQuoteSettingProps {
  25 + cRef: any;
  26 +}
  27 +
  28 +interface ParamsDataProps {
  29 + id?: string;
  30 + name?: string;
  31 + pageSize?: number;
  32 + pageNum?: number;
  33 +}
  34 +
  35 +const FlowQuoteSetting: React.FC<FlowQuoteSettingProps> = (props) => {
  36 + const { cRef } = props;
  37 + const [visible, setVisible] = useState(false);
  38 + const [paramsData, setParamsData] = useState<ParamsDataProps>({
  39 + id: '',
  40 + name: '',
  41 + pageSize: 10,
  42 + pageNum: 1,
  43 + });
  44 + const [pageData, setPageData] = useState<any[]>([]);
  45 + // 表格是否添加scroll 属性
  46 + const [isScrollY, setIsScrollY] = useState<boolean>(false);
  47 + const [tableLoading, setTableLoading] = useState<boolean>(true); // 表格loading
  48 +
  49 + useImperativeHandle(cRef, () => ({
  50 + // 暴露给父组件
  51 + open: (values: ParamsDataProps) => {
  52 + if (isEmpty(values)) {
  53 + return;
  54 + }
  55 + // 打开弹框 传参
  56 + setParamsData({
  57 + id: values?.id || '',
  58 + name: values?.name || '',
  59 + });
  60 + setVisible(true);
  61 + },
  62 + }));
  63 +
  64 + // list 操作
  65 + const handleClick = (
  66 + type: string,
  67 + data?: any,
  68 + e?: React.MouseEvent<HTMLElement, MouseEvent>,
  69 + ) => {
  70 + const { host, pathname, protocol, hash } = location;
  71 + if (!!e) {
  72 + e.stopPropagation();
  73 + }
  74 + console.log('type ==== ', type, data);
  75 +
  76 + switch (type) {
  77 + case 'JUMP':
  78 + // 功能名称 跳转 详情
  79 + // handleWindowOpen(
  80 + // `${protocol}//${host}${pathname}#/design/data-flow?appId=${data?.appId || ''}&funCode=${
  81 + // data?.funCode || ''
  82 + // }&funId=${data?.funId || ''}&type=edit&processId=${data.id}&returnUrl=${hash}`,
  83 + // '_self',
  84 + // );
  85 + break;
  86 + default:
  87 + break;
  88 + }
  89 + };
  90 +
  91 + const cols = useMemo(() => {
  92 + return [
  93 + {
  94 + title: () => {
  95 + return (
  96 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  97 + 应用名称
  98 + </span>
  99 + );
  100 + },
  101 + dataIndex: 'name',
  102 + width: 230,
  103 + className: 'qx-flow-table-cell',
  104 + render: (text: any, row: any) => {
  105 + return (
  106 + <span
  107 + className={cls(commonStyles[`${prefix}__colspan`])}
  108 + style={{ width: '100%' }}
  109 + >
  110 + <Text
  111 + style={{
  112 + marginRight: 4,
  113 + maxWidth: '100%',
  114 + }}
  115 + ellipsis={{ tooltip: text || '' }}
  116 + >
  117 + {text}
  118 + </Text>
  119 + </span>
  120 + );
  121 + },
  122 + },
  123 + {
  124 + title: () => {
  125 + return (
  126 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  127 + 表单名称
  128 + </span>
  129 + );
  130 + },
  131 + dataIndex: 'curVersion',
  132 + render: (text: any, row: any) => {
  133 + return (
  134 + <span
  135 + className={cls(commonStyles[`${prefix}__colspan`])}
  136 + style={{ width: '100%' }}
  137 + >
  138 + <Text
  139 + style={{
  140 + marginRight: 4,
  141 + maxWidth: '100%',
  142 + }}
  143 + ellipsis={{ tooltip: text || '' }}
  144 + >
  145 + {text}
  146 + </Text>
  147 + </span>
  148 + );
  149 + },
  150 + width: 160,
  151 + className: 'qx-flow-table-cell',
  152 + },
  153 + {
  154 + title: () => {
  155 + return (
  156 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  157 + 使用功能
  158 + </span>
  159 + );
  160 + },
  161 + dataIndex: 'latestConfigStatus',
  162 + className: 'qx-flow-table-cell',
  163 + render: (text: any, row: any) => {
  164 + return (
  165 + <span
  166 + className={cls(commonStyles[`${prefix}__colspan`])}
  167 + style={{ width: '100%' }}
  168 + >
  169 + <Text
  170 + style={{
  171 + marginRight: 4,
  172 + maxWidth: '100%',
  173 + }}
  174 + ellipsis={{ tooltip: text || '' }}
  175 + >
  176 + {text}
  177 + </Text>
  178 + </span>
  179 + );
  180 + },
  181 + width: 120,
  182 + },
  183 + {
  184 + title: () => {
  185 + return (
  186 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  187 + 功能名称
  188 + </span>
  189 + );
  190 + },
  191 + dataIndex: 'updatedAt',
  192 + width: 140,
  193 + className: 'qx-flow-table-cell',
  194 + render: (text: any, row: any) => {
  195 + // console.log('流程使用情况 === 1', text);
  196 + // console.log('流程使用情况 === 2', row);
  197 +
  198 + return (
  199 + <span
  200 + className={cls(commonStyles[`${prefix}__colspan`])}
  201 + style={{ width: '100%' }}
  202 + >
  203 + <Button
  204 + type="link"
  205 + size="small"
  206 + icon={getIcon('icon-field-rel')}
  207 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) =>
  208 + handleClick('FLOW_QUOTE', row, e)
  209 + }
  210 + >
  211 + 等待提供字段
  212 + </Button>
  213 + </span>
  214 + );
  215 + },
  216 + },
  217 + ];
  218 + }, [JSON.stringify(paramsData), JSON.stringify(pageData)]);
  219 +
  220 + // 查询list
  221 + const handleSearch = (params?: ParamsDataProps) => {
  222 + setTableLoading(true);
  223 + let _cloneParams: ParamsDataProps = {};
  224 + if (isEmpty(params)) {
  225 + _cloneParams = { ...paramsData };
  226 + } else {
  227 + _cloneParams = { ...paramsData, ...params };
  228 + }
  229 + setParamsData(_cloneParams);
  230 + // @ts-ignore
  231 + // window.qx.actions.cancelToken(); // 多次请求 取最新请求 其他取消
  232 + /*getListProcess(_cloneParams)
  233 + .then((res: any) => {
  234 + // 处理数据 符合表格渲染格式
  235 + const _cloneList: any[] = [];
  236 + const getColumnList = (data: any[]) => {
  237 + data.forEach((item: any) => {
  238 + _cloneList.push(_.cloneDeep(item));
  239 + if (!!item?.processes?.length) {
  240 + getColumnList(item?.processes);
  241 + }
  242 + });
  243 + };
  244 + getColumnList(_.cloneDeep(res || []));
  245 + setPageData(_.cloneDeep(_cloneList));
  246 + // setExpandedKeys(_cloneList.map((item: any) => item.key));
  247 + setTableLoading(false);
  248 + })
  249 + .catch(() => {
  250 + console.warn('数据列表获取失败');
  251 + setPageData([]);
  252 + setTableLoading(false);
  253 + });*/
  254 + };
  255 +
  256 + // 表格滚动高度获取
  257 + const [scrollY] = useFlowTableScrollY({
  258 + columns: cols,
  259 + listProps: {
  260 + paramsData,
  261 + pageData,
  262 + },
  263 + hasPagination: true,
  264 + }) as any[];
  265 +
  266 + useEffect(() => {
  267 + if (!paramsData?.id) {
  268 + return;
  269 + }
  270 + setPageData([]);
  271 + setTableLoading(false);
  272 +
  273 + // handleSearch({id: paramsData?.id});
  274 + }, [paramsData]);
  275 +
  276 + useDebounceEffect(
  277 + () => {
  278 + if (scrollY >= 100 && !!pageData?.length) {
  279 + const itemHeaderHeight = 41;
  280 + setIsScrollY(pageData.length * itemHeaderHeight >= scrollY);
  281 + }
  282 + },
  283 + [pageData, scrollY],
  284 + { wait: 200 },
  285 + );
  286 +
  287 + return (
  288 + <div>
  289 + <Modal
  290 + width={720}
  291 + centered
  292 + title="流程使用情况"
  293 + onCancel={() => setVisible(false)}
  294 + visible={visible}
  295 + footer={null}
  296 + closeIcon={
  297 + <QxIcon
  298 + className={'qx-data-flow__modal__close'}
  299 + type={'icon-close'}
  300 + />
  301 + }
  302 + destroyOnClose
  303 + wrapClassName={'qx-data-flow__modal qx-flow-quote-container'}
  304 + >
  305 + <section className={'qx-flow-quote__header'}>
  306 + 【{paramsData?.name || ''}】正在使用中
  307 + </section>
  308 + <div
  309 + className={cls(
  310 + commonStyles[`${prefix}-container`],
  311 + 'qx-data-flow-table',
  312 + )}
  313 + >
  314 + <Table
  315 + loading={tableLoading}
  316 + columns={cols}
  317 + dataSource={pageData}
  318 + scroll={
  319 + isScrollY
  320 + ? {
  321 + x: '100%',
  322 + y: scrollY,
  323 + }
  324 + : { x: '100%' }
  325 + }
  326 + size={'small'}
  327 + bordered
  328 + rowKey={(record: any) => {
  329 + return `${record?.type || ''}${record?.id || ''}${Math.random()
  330 + .toString(36)
  331 + .replaceAll(/[0-9]/g, '')
  332 + .slice(2, 4)}`;
  333 + }}
  334 + />
  335 + </div>
  336 + </Modal>
  337 + </div>
  338 + );
  339 +};
  340 +
  341 +export default FlowQuoteSetting;
... ...
  1 +.qx-flow-setting {
  2 + &__top {
  3 + height: 32px;
  4 + margin-bottom: 16px;
  5 + display: flex;
  6 + align-items: center;
  7 + justify-content: space-between;
  8 + }
  9 +
  10 + &__top-tip {
  11 + height: 32px;
  12 + line-height: 32px;
  13 + color: @N8;
  14 + }
  15 +
  16 + &__top-tip__opt {
  17 + display: inline-block;
  18 + position: relative;
  19 + padding-left: 20px;
  20 + margin-left: 8px;
  21 + color: @N8;
  22 + height: 32px;
  23 + line-height: 32px;
  24 + cursor: pointer;
  25 +
  26 + :global {
  27 + .qx-data-flow__icon {
  28 + position: absolute;
  29 + top: 50%;
  30 + left: 0;
  31 + margin-top: -8px;
  32 + width: 16px;
  33 + height: 16px;
  34 + font-size: 16px;
  35 + color: @N6;
  36 + }
  37 + }
  38 +
  39 + &:hover {
  40 + color: @N9;
  41 +
  42 + :global {
  43 + .qx-data-flow__icon {
  44 + color: @N8;
  45 + }
  46 + }
  47 + }
  48 + }
  49 +
  50 + &__top-search {
  51 + }
  52 +
  53 + &__top-search__input {
  54 + :global {
  55 + .qx-data-flow__icon {
  56 + color: @N6;
  57 + }
  58 + }
  59 + }
  60 +}
... ...
  1 +import React, { useEffect, useMemo, useRef, useState } from 'react';
  2 +import {
  3 + Button,
  4 + Input,
  5 + message,
  6 + Modal,
  7 + Space,
  8 + Table,
  9 + Tooltip,
  10 + Typography,
  11 + Avatar,
  12 +} from 'antd';
  13 +import { useDebounceEffect, useDebounceFn } from 'ahooks';
  14 +// TODO: 去掉 view-render
  15 +// import { QxIconAppImg } from '@qx/view-render';
  16 +import { isEmpty, cloneDeep } from 'lodash-es';
  17 +
  18 +import { FlowSettingProps } from '@/page/view/constant';
  19 +import { getIcon, useFlowTableScrollY } from '@/utils';
  20 +import actions from '@/utils/actions';
  21 +
  22 +import { copyProcessById, deleteProcessById, getListProcess } from '@/services';
  23 +import { handleWindowOpen } from '@qx/utils';
  24 +import { FlowVersion } from '@/components';
  25 +import FlowQuoteSetting from '../flow-quote-setting';
  26 +import cls from 'classnames';
  27 +import styles from './index.module.less';
  28 +import commonStyles from '../../index.module.less';
  29 +import { FlowStatusEnums } from '@/interface';
  30 +const prefix = 'qx-flow-setting';
  31 +const { Text } = Typography;
  32 +
  33 +interface ListParamsProps {
  34 + appId?: string; // 所属应用 必需
  35 + funId?: string; // 表单 funId
  36 + funCode?: string; // 表单 funCode
  37 + name?: string; //流程名称 模糊查询
  38 +}
  39 +
  40 +// 表格 可收起 使用该方法
  41 +const getColumnListExpand = (data: any[]) => {
  42 + const _cloneData: any[] = [];
  43 + data.forEach((item: any) => {
  44 + if (!!item?.processes?.length) {
  45 + _cloneData.push({
  46 + key: item?.id || new Date().getTime().toString(),
  47 + children: getColumnListExpand(item?.processes),
  48 + ...item,
  49 + });
  50 + } else {
  51 + _cloneData.push({
  52 + key: item?.id || new Date().getTime().toString(),
  53 + ...item,
  54 + });
  55 + }
  56 + });
  57 + return _cloneData;
  58 +};
  59 +
  60 +const FlowSetting: React.FC<FlowSettingProps> = (props) => {
  61 + const { params, type } = props;
  62 + const [queryParams, setQueryParams] = useState<ListParamsProps>({
  63 + appId: '',
  64 + });
  65 +
  66 + const [pageData, setPageData] = useState<any[]>([]);
  67 + // 表格是否添加scroll 属性
  68 + const [isScrollY, setIsScrollY] = useState<boolean>(false);
  69 + const [expandedKeys, setExpandedKeys] = useState<readonly React.Key[]>([]); // 表格 多级展开
  70 + const [tableLoading, setTableLoading] = useState<boolean>(true); // 表格loading
  71 + const [keyword, setKeyword] = useState(''); // 搜索框字段
  72 + // 流程引用弹框
  73 + const quoteModalRef = useRef({
  74 + open: (data?: any) => {},
  75 + });
  76 +
  77 + // list 操作
  78 + const handleClick = (
  79 + type: string,
  80 + data?: any,
  81 + e?: React.MouseEvent<HTMLElement, MouseEvent>,
  82 + ) => {
  83 + const { host, pathname, protocol, hash } = location;
  84 + if (!!e) {
  85 + e.stopPropagation();
  86 + }
  87 + console.log('type ==== ', type, data);
  88 +
  89 + switch (type) {
  90 + case 'EDIT':
  91 + // 编辑数据流 跳转
  92 + handleWindowOpen(
  93 + `${protocol}//${host}${pathname}#/design/data-flow?appId=${
  94 + params?.appId || ''
  95 + }&funCode=${params?.funCode || ''}&funId=${
  96 + data?.funId || ''
  97 + }&type=edit&processId=${data.id}&returnUrl=${hash}`,
  98 + '_self',
  99 + );
  100 + break;
  101 + case 'FLOW_QUOTE':
  102 + // 流程使用 弹框打开
  103 + quoteModalRef.current.open(data || {});
  104 + break;
  105 + case 'VERSION':
  106 + // 版本管理跳转
  107 + handleWindowOpen(
  108 + `${protocol}//${host}${pathname}#/design/data-flow-view/version?processId=${
  109 + data?.id || ''
  110 + }&name=${data?.name || ''}&returnUrl=${hash}`,
  111 + '_self',
  112 + );
  113 + break;
  114 + case 'DELETE':
  115 + // 删除
  116 + if (!!data?.id) {
  117 + Modal.confirm({
  118 + title: '删除',
  119 + content: `确定删除【${data?.name || ''}】?`,
  120 + cancelText: '取消',
  121 + okText: '确定',
  122 + onOk: () => {
  123 + deleteProcessById(data?.id)
  124 + .then(() => {
  125 + message.success('删除成功');
  126 + handleSearch();
  127 + })
  128 + .catch((err: any) => {
  129 + console.log('err ====', err);
  130 + message.success('删除失败');
  131 + });
  132 + },
  133 + });
  134 + }
  135 + break;
  136 + case 'COPY':
  137 + // 复制
  138 + if (!!data?.id) {
  139 + Modal.confirm({
  140 + title: '复制',
  141 + content: `确定复制【${data?.name || ''}】的所有节点与配置?`,
  142 + cancelText: '取消',
  143 + okText: '确定',
  144 + onOk: () => {
  145 + copyProcessById(data?.id)
  146 + .then(() => {
  147 + message.success('复制成功');
  148 + handleSearch();
  149 + })
  150 + .catch((err: any) => {
  151 + console.log('err ====', err);
  152 + message.success('复制失败');
  153 + });
  154 + },
  155 + });
  156 + }
  157 + break;
  158 + default:
  159 + break;
  160 + }
  161 + };
  162 +
  163 + const cols = useMemo(() => {
  164 + return [
  165 + {
  166 + title: () => {
  167 + return (
  168 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  169 + 流程名称
  170 + </span>
  171 + );
  172 + },
  173 + dataIndex: 'name',
  174 + width: 230,
  175 + className: 'qx-flow-table-cell',
  176 + render: (text: any, row: any) => {
  177 + let _maxWidth: string = '';
  178 + let _classNameStr: string = '';
  179 + if (['APP', 'FUN'].includes(row?.type || '')) {
  180 + _maxWidth = 'calc(100% - 24px)';
  181 + _classNameStr = ' ';
  182 + } else {
  183 + _classNameStr = `${prefix}__colspan-flow`;
  184 + if (!!row?.remark) {
  185 + _maxWidth = 'calc(100% - 20px)';
  186 + } else {
  187 + _maxWidth = '100%';
  188 + }
  189 + }
  190 +
  191 + return (
  192 + <span
  193 + className={cls(
  194 + commonStyles[`${prefix}__colspan`],
  195 + commonStyles[_classNameStr],
  196 + )}
  197 + style={{ width: '100%' }}
  198 + >
  199 + {['APP', 'FUN'].includes(row?.type || '') ? (
  200 + <span className={cls(commonStyles[`${prefix}__colspan-icon`])}>
  201 + {/* <QxIconAppImg
  202 + item={{
  203 + icon: row?.icon || '',
  204 + iconColor: row?.iconColor || '',
  205 + }}
  206 + /> */}
  207 + </span>
  208 + ) : null}
  209 + <Text
  210 + style={{
  211 + marginRight: 4,
  212 + maxWidth: _maxWidth,
  213 + }}
  214 + ellipsis={{ tooltip: text || '' }}
  215 + >
  216 + {text}
  217 + </Text>
  218 + {row?.remark ? (
  219 + <Tooltip placement="top" title={row?.remark}>
  220 + <span>{getIcon('icon-frame-help')}</span>
  221 + </Tooltip>
  222 + ) : null}
  223 + </span>
  224 + );
  225 + },
  226 + onCell: (record: any) => {
  227 + return {
  228 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 7 : 1,
  229 + };
  230 + },
  231 + },
  232 + {
  233 + title: () => {
  234 + return (
  235 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  236 + 当前版本
  237 + </span>
  238 + );
  239 + },
  240 + dataIndex: 'curVersion',
  241 + render: (text: any, row: any) => {
  242 + return (
  243 + <span
  244 + className={cls(commonStyles[`${prefix}__colspan`])}
  245 + style={{ width: '100%' }}
  246 + >
  247 + <FlowVersion
  248 + versionText={text}
  249 + handleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  250 + handleClick('VERSION', row, e);
  251 + }}
  252 + />
  253 + </span>
  254 + );
  255 + },
  256 + width: 160,
  257 + className: 'qx-flow-table-cell',
  258 + onCell: (record: any) => {
  259 + return {
  260 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  261 + };
  262 + },
  263 + },
  264 + {
  265 + title: () => {
  266 + return (
  267 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  268 + 状态
  269 + </span>
  270 + );
  271 + },
  272 + dataIndex: 'latestConfigStatus',
  273 + className: 'qx-flow-table-cell',
  274 + render: (text: any) => {
  275 + return (
  276 + <span
  277 + className={cls(commonStyles[`${prefix}__colspan`])}
  278 + style={{ width: '100%' }}
  279 + >
  280 + <FlowVersion status={text} statusEnums={FlowStatusEnums} />
  281 + </span>
  282 + );
  283 + },
  284 + width: 120,
  285 + onCell: (record: any) => {
  286 + return {
  287 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  288 + };
  289 + },
  290 + },
  291 + {
  292 + title: () => {
  293 + return (
  294 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  295 + 流程使用情况
  296 + </span>
  297 + );
  298 + },
  299 + dataIndex: 'updatedAt',
  300 + width: 140,
  301 + className: 'qx-flow-table-cell',
  302 + render: (text: any, row: any) => {
  303 + // console.log('流程使用情况 === 1', text);
  304 + // console.log('流程使用情况 === 2', row);
  305 +
  306 + return (
  307 + <span
  308 + className={cls(commonStyles[`${prefix}__colspan`])}
  309 + style={{ width: '100%' }}
  310 + >
  311 + <Button
  312 + type="link"
  313 + size="small"
  314 + icon={getIcon('icon-field-rel')}
  315 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) =>
  316 + handleClick('FLOW_QUOTE', row, e)
  317 + }
  318 + >
  319 + 等待提供字段
  320 + </Button>
  321 + </span>
  322 + );
  323 + },
  324 + onCell: (record: any) => {
  325 + return {
  326 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  327 + };
  328 + },
  329 + },
  330 + {
  331 + title: () => {
  332 + return (
  333 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  334 + 更新时间
  335 + </span>
  336 + );
  337 + },
  338 + dataIndex: 'updatedAt',
  339 + width: 200,
  340 + className: 'qx-flow-table-cell',
  341 + render: (text: any) => {
  342 + return (
  343 + <span
  344 + className={cls(commonStyles[`${prefix}__colspan`])}
  345 + style={{ width: '100%' }}
  346 + >
  347 + {text}
  348 + </span>
  349 + );
  350 + },
  351 + onCell: (record: any) => {
  352 + return {
  353 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  354 + };
  355 + },
  356 + },
  357 + {
  358 + title: () => {
  359 + return (
  360 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  361 + 创建人
  362 + </span>
  363 + );
  364 + },
  365 + dataIndex: 'createdByUser',
  366 + className: 'qx-flow-table-cell qx-flow-table-cell-user',
  367 + render: (text: any, row: any) => {
  368 + if (isEmpty(text)) {
  369 + return '--';
  370 + }
  371 + let _imgError = false;
  372 + return (
  373 + <span
  374 + className={cls(commonStyles[`${prefix}__colspan-user`])}
  375 + style={{ width: '100%' }}
  376 + >
  377 + <Avatar
  378 + className={'qx-flow-table-cell__avatar'}
  379 + size={24}
  380 + alt="avatar"
  381 + src={row?.createdByUser?.faceUrl || ''}
  382 + onError={() => {
  383 + _imgError = true;
  384 + return true;
  385 + }}
  386 + >
  387 + {(!!row?.createdByUser?.faceUrl && _imgError) ||
  388 + !row?.createdByUser?.faceUrl
  389 + ? !!row?.createdByUser?.name?.length
  390 + ? row?.createdByUser?.name[
  391 + row?.createdByUser?.name?.length - 1
  392 + ]
  393 + : null
  394 + : null}
  395 + </Avatar>
  396 + <Text
  397 + className={'qx-flow-table-cell__avatar-name'}
  398 + ellipsis={{ tooltip: row?.createdByUser?.name || '' }}
  399 + >
  400 + {row?.createdByUser?.name || ''}
  401 + </Text>
  402 + </span>
  403 + );
  404 + },
  405 + width: 200,
  406 + onCell: (record: any) => {
  407 + return {
  408 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  409 + };
  410 + },
  411 + },
  412 + {
  413 + title: () => {
  414 + return (
  415 + <span className={cls(commonStyles[`${prefix}__col-title`])}>
  416 + 操作
  417 + </span>
  418 + );
  419 + },
  420 + dataIndex: 'control',
  421 + render: (text: any, row: any) => (
  422 + <Space
  423 + size={8}
  424 + className={cls(commonStyles[`${prefix}__colspan`])}
  425 + style={{ width: '100%' }}
  426 + >
  427 + <Button
  428 + type="link"
  429 + size="small"
  430 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  431 + e.stopPropagation();
  432 + handleClick('EDIT', row);
  433 + }}
  434 + >
  435 + 编辑
  436 + </Button>
  437 + <Button
  438 + type="link"
  439 + size="small"
  440 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) =>
  441 + handleClick('COPY', row, e)
  442 + }
  443 + >
  444 + 复制
  445 + </Button>
  446 + {/* TODO 字段待确定 有流程引用 不能删除*/}
  447 + <Button
  448 + type="link"
  449 + size="small"
  450 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) =>
  451 + handleClick('DELETE', row, e)
  452 + }
  453 + >
  454 + 删除
  455 + </Button>
  456 + </Space>
  457 + ),
  458 + width: 140,
  459 + className: 'qx-flow-table-cell',
  460 + onCell: (record: any) => {
  461 + return {
  462 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  463 + };
  464 + },
  465 + },
  466 + ];
  467 + }, [JSON.stringify(queryParams), JSON.stringify(pageData)]);
  468 +
  469 + // 查询list
  470 + const handleSearch = (params?: ListParamsProps) => {
  471 + setTableLoading(true);
  472 + let _cloneParams: ListParamsProps = {};
  473 + if (isEmpty(params)) {
  474 + _cloneParams = { ...queryParams };
  475 + } else {
  476 + _cloneParams = { ...queryParams, ...params };
  477 + }
  478 + setQueryParams(_cloneParams);
  479 +
  480 + actions.cancelToken(); // 多次请求 取最新请求 其他取消
  481 +
  482 + getListProcess(_cloneParams)
  483 + .then((res: any) => {
  484 + // 处理数据 符合表格渲染格式
  485 + const _cloneList: any[] = [];
  486 + const getColumnList = (data: any[]) => {
  487 + data.forEach((item: any) => {
  488 + _cloneList.push(cloneDeep(item));
  489 + if (!!item?.processes?.length) {
  490 + getColumnList(item?.processes);
  491 + }
  492 + });
  493 + };
  494 + getColumnList(cloneDeep(res || []));
  495 + setPageData(cloneDeep(_cloneList));
  496 + // setExpandedKeys(_cloneList.map((item: any) => item.key));
  497 + setTableLoading(false);
  498 + })
  499 + .catch(() => {
  500 + console.warn('数据列表获取失败');
  501 + setPageData([]);
  502 + setTableLoading(false);
  503 + });
  504 + };
  505 +
  506 + // 表格滚动高度获取
  507 + const [scrollY] = useFlowTableScrollY({
  508 + columns: cols,
  509 + listProps: {
  510 + queryParams,
  511 + pageData,
  512 + },
  513 + }) as any[];
  514 +
  515 + // 搜索框查询
  516 + const { run: changeSearch } = useDebounceFn(
  517 + (keyword) => {
  518 + handleSearch({ name: keyword });
  519 + },
  520 + {
  521 + wait: 300,
  522 + },
  523 + );
  524 +
  525 + useEffect(() => {
  526 + if (!params?.appId) {
  527 + return;
  528 + }
  529 + if (!!params?.appId) {
  530 + if (!!params?.funCode) {
  531 + handleSearch({ appId: params?.appId, funCode: params?.funCode });
  532 + } else {
  533 + handleSearch({ appId: params?.appId });
  534 + }
  535 + }
  536 + }, [params]);
  537 +
  538 + useDebounceEffect(
  539 + () => {
  540 + if (scrollY >= 100 && !!pageData?.length) {
  541 + const itemHeaderHeight = 41;
  542 + setIsScrollY(pageData.length * itemHeaderHeight >= scrollY);
  543 + }
  544 + },
  545 + [pageData, scrollY],
  546 + { wait: 200 },
  547 + );
  548 +
  549 + return (
  550 + <>
  551 + <header className={cls(styles[`${prefix}__top`])}>
  552 + <section className={cls(styles[`${prefix}__top-tip`])}>
  553 + <span>
  554 + 数据流是一个强大的数据处理编排器,接受约定的参数传入,流程执行后输出结果参数。
  555 + </span>
  556 + <span
  557 + className={cls(styles[`${prefix}__top-tip__opt`])}
  558 + onClick={() => {
  559 + // 点击跳转
  560 + }}
  561 + >
  562 + {getIcon('icon-frame-help')}帮助
  563 + </span>
  564 + </section>
  565 + <section className={cls(styles[`${prefix}__top-search`])}>
  566 + {type === 'form' ? (
  567 + <Button
  568 + type="primary"
  569 + // loading={false}
  570 + onClick={() => {
  571 + const { host, pathname, protocol, hash } = location;
  572 + handleWindowOpen(
  573 + `${protocol}//${host}${pathname}#/design/data-flow?appId=${
  574 + params?.appId || ''
  575 + }&type=edit&funCode=${
  576 + params?.funCode || ''
  577 + }&returnUrl=${hash}`,
  578 + '_self',
  579 + );
  580 + }}
  581 + >
  582 + 新建数据流
  583 + </Button>
  584 + ) : (
  585 + <Input
  586 + className={cls(styles[`${prefix}__top-search__input`])}
  587 + placeholder={'流程名称搜索'}
  588 + value={keyword}
  589 + allowClear
  590 + prefix={getIcon('icon-search')}
  591 + onChange={(e) => {
  592 + let _v = e.target.value;
  593 + if (_v) {
  594 + _v = _v.trim();
  595 + }
  596 + setKeyword(_v);
  597 + changeSearch(_v);
  598 + }}
  599 + />
  600 + )}
  601 + </section>
  602 + </header>
  603 +
  604 + <div
  605 + className={cls(
  606 + commonStyles[`${prefix}-container`],
  607 + 'qx-data-flow-table',
  608 + )}
  609 + >
  610 + <Table
  611 + loading={tableLoading}
  612 + columns={cols}
  613 + dataSource={pageData}
  614 + scroll={
  615 + isScrollY
  616 + ? {
  617 + x: '100%',
  618 + y: scrollY,
  619 + }
  620 + : { x: '100%' }
  621 + }
  622 + pagination={false}
  623 + size={'small'}
  624 + bordered
  625 + expandable={{
  626 + indentSize: 0,
  627 + // defaultExpandAllRows: true,
  628 + // expandedRowKeys: expandedKeys,
  629 + // onExpandedRowsChange: (expandedRows: readonly React.Key[]) => {
  630 + // setExpandedKeys(expandedRows);
  631 + // },
  632 + }}
  633 + rowKey={(record: any) => {
  634 + return `${record?.type || ''}${record?.id || ''}${Math.random()
  635 + .toString(36)
  636 + .replaceAll(/[0-9]/g, '')
  637 + .slice(2, 4)}`;
  638 + }}
  639 + />
  640 + </div>
  641 + <FlowQuoteSetting cRef={quoteModalRef} />
  642 + </>
  643 + );
  644 +};
  645 +
  646 +export default FlowSetting;
... ...
  1 +interface ParamsProps {
  2 + name?: string;
  3 + funCode?: string;
  4 + appId: string;
  5 + type?: string;
  6 +}
  7 +
  8 +export interface FlowSettingProps {
  9 + params: ParamsProps;
  10 + type?: 'form';
  11 +}
... ...
  1 +.qx-data-flow {
  2 + width: 100%;
  3 + height: 100%;
  4 + background-color: @body-bg;
  5 +}
  6 +
  7 +.qx-data-flow__img.ant-image,
  8 +.qx-data-flow__icon {
  9 + display: flex;
  10 + align-items: center;
  11 + justify-content: center;
  12 + min-width: 16px;
  13 + height: 100%;
  14 + font-size: 16px;
  15 + line-height: 1;
  16 + color: inherit;
  17 +}
  18 +
  19 +.qx-data-flow__main {
  20 + display: flex;
  21 + padding: 16px;
  22 + width: 100%;
  23 + height: calc(100% - 57px);
  24 +
  25 + /*.main-aside {
  26 + width: 240px;
  27 + border-radius: 8px;
  28 + background-color: #fff;
  29 + }
  30 +
  31 + .main-aside-menus {
  32 + padding-top: 12px;
  33 + height: 100%;
  34 + border-radius: 8px;
  35 +
  36 + // 菜单覆盖
  37 + :global {
  38 + .ant-menu-item {
  39 + width: calc(100% - 24px);
  40 + margin: 0 6px 0 12px;
  41 + padding-top: 0;
  42 + padding-bottom: 0;
  43 + border-radius: 4px;
  44 +
  45 + &:hover {
  46 + color: @N8;
  47 + background-color: @N3;
  48 + }
  49 +
  50 + &:not(:last-child) {
  51 + margin-bottom: 4px;
  52 + }
  53 +
  54 + &.ant-menu-item-selected {
  55 + color: @B8;
  56 + background-color: @B3;
  57 +
  58 + a {
  59 + color: @B8;
  60 + }
  61 +
  62 + &:hover {
  63 + color: @B8;
  64 + background-color: @B3;
  65 + }
  66 + }
  67 + }
  68 + }
  69 + }*/
  70 +
  71 + .main-content {
  72 + flex: 1;
  73 + width: 100%;
  74 + //width: calc(100% - 240px - 16px);
  75 + height: 100%;
  76 + padding: 16px;
  77 + min-height: 0;
  78 + //margin-left: 16px;
  79 + background-color: #fff;
  80 + border-radius: 8px;
  81 + overflow: hidden;
  82 + }
  83 +}
  84 +
  85 +.qx-flow-setting {
  86 + &-container {
  87 + width: 100%;
  88 + min-width: 100%;
  89 + max-width: 100%;
  90 + height: calc(100% - 48px);
  91 +
  92 + :global {
  93 + // 创建人 单元格 样式迭代
  94 + td.ant-table-cell.qx-flow-table-cell {
  95 + height: 40px;
  96 + line-height: initial !important;
  97 +
  98 + &.qx-flow-table-cell-user {
  99 + padding: 0 8px !important;
  100 + height: 100% !important;
  101 + }
  102 +
  103 + .anticon.qx-data-flow__icon {
  104 + font-size: 16px;
  105 + color: @N7;
  106 +
  107 + &:hover {
  108 + color: @N9;
  109 + }
  110 + }
  111 +
  112 + .ant-btn.ant-btn-link.ant-btn-sm {
  113 + flex: 1;
  114 + display: inline-flex;
  115 + align-items: center;
  116 + justify-content: flex-start;
  117 + height: 24px !important;
  118 + padding: 0 !important;
  119 + color: @B8;
  120 +
  121 + span {
  122 + height: 100%;
  123 + display: inline-flex;
  124 + align-items: center;
  125 + justify-content: flex-start;
  126 + }
  127 +
  128 + &:hover {
  129 + color: @B7;
  130 +
  131 + .anticon.qx-data-flow__icon {
  132 + color: @B7;
  133 + }
  134 + }
  135 +
  136 + .anticon.qx-data-flow__icon {
  137 + color: @B8;
  138 +
  139 + &:hover {
  140 + color: @B7;
  141 + }
  142 + }
  143 + }
  144 + }
  145 +
  146 + // 覆盖单元格 hover样式变化
  147 + td.qx-flow-table-cell.ant-table-cell-row-hover {
  148 + //cursor: pointer;
  149 +
  150 + .qx-flow-version__default,
  151 + .qx-flow-version__default-operational {
  152 + background-color: @N4 !important;
  153 + border-color: @N4 !important;
  154 + }
  155 +
  156 + .qx-flow-version__default-operational:hover {
  157 + border-color: @B8 !important;
  158 + }
  159 +
  160 + .qx-flow-table-cell__avatar-name.ant-typography {
  161 + background-color: @N4 !important;
  162 + }
  163 + }
  164 + }
  165 + }
  166 +
  167 + &__col-title,
  168 + &__colspan,
  169 + &__colspan-user {
  170 + height: 100%;
  171 + padding: 0 8px;
  172 + display: inline-flex;
  173 + align-items: center;
  174 + justify-content: flex-start;
  175 + }
  176 +
  177 + &__colspan {
  178 + :global {
  179 + .ant-typography {
  180 + color: @N8;
  181 + }
  182 + }
  183 + }
  184 +
  185 + &__colspan-flow {
  186 + :global {
  187 + .ant-typography {
  188 + color: @N9;
  189 + }
  190 + }
  191 + }
  192 +
  193 + &__colspan-icon {
  194 + width: 20px;
  195 + height: 100%;
  196 + display: inline-flex;
  197 + align-items: center;
  198 + justify-content: center;
  199 + margin-right: 4px;
  200 +
  201 + :global {
  202 + .qx-icon-aggregate-img,
  203 + .anticon.qx-icon-aggregate {
  204 + width: 20px;
  205 + height: 20px;
  206 + border-radius: 4px;
  207 + font-size: 14px;
  208 + overflow: hidden;
  209 + line-height: 1;
  210 + }
  211 + }
  212 + }
  213 +
  214 + &__colspan-user {
  215 + display: inline-block;
  216 + width: 100%;
  217 + max-width: 100%;
  218 + min-width: 100%;
  219 + position: relative;
  220 +
  221 + :global {
  222 + //覆盖头像样式
  223 + .qx-flow-table-cell__avatar.ant-avatar {
  224 + position: absolute;
  225 + font-size: 12px !important;
  226 + top: 50%;
  227 + margin-top: -12px;
  228 + left: 8px;
  229 + background-color: @B8;
  230 + }
  231 +
  232 + .qx-flow-table-cell__avatar-name.ant-typography {
  233 + padding-left: 28px;
  234 + padding-right: 8px;
  235 + max-width: 144px;
  236 + height: 100%;
  237 + line-height: 24px;
  238 + font-size: 12px;
  239 + color: @N9;
  240 + background-color: @N3;
  241 + border-radius: 100px;
  242 + }
  243 + }
  244 + }
  245 +}
... ...
  1 +import React from 'react';
  2 +// import { history } from '@@/core/history';
  3 +import { useSearchParams } from 'react-router-dom';
  4 +import { useTitle } from 'ahooks';
  5 +// @ts-ignore;
  6 +import { handleWindowOpen } from '@qx/utils';
  7 +import { FlowHeader, QxIcon } from '@/components';
  8 +import FlowSetting from '../components/flow-setting';
  9 +
  10 +// 样式
  11 +import cls from 'classnames';
  12 +import styles from '@/page/view/index.module.less';
  13 +const prefix = 'qx-data-flow';
  14 +
  15 +const DataFlowView: React.FC = ({}) => {
  16 + const [searchParams] = useSearchParams();
  17 + const {
  18 + appId, // 应用id
  19 + funCode, // 表单id
  20 + returnUrl, // 来自页面跳转返回可跳转
  21 + } = {
  22 + appId: searchParams.get('appId'),
  23 + funCode: searchParams.get('funCode'),
  24 + returnUrl: searchParams.get('returnUrl'),
  25 + };
  26 +
  27 + // 添加标题
  28 + if (!!appId && !funCode) {
  29 + useTitle('数据流');
  30 + }
  31 +
  32 + const handleClick = (type: string) => {
  33 + const { host, pathname, protocol, hash } = location;
  34 +
  35 + switch (type) {
  36 + case 'RETURN':
  37 + // 返回上一页
  38 + if (!!returnUrl) {
  39 + const { host, pathname, protocol } = location;
  40 + // 返回上一页
  41 + handleWindowOpen(
  42 + `${protocol}//${host}${pathname}${returnUrl}`,
  43 + '_self',
  44 + );
  45 + } else {
  46 + // history.goBack();
  47 + window.history.back();
  48 + }
  49 + break;
  50 + case 'ADD':
  51 + // 新建数据流
  52 + handleWindowOpen(
  53 + `${protocol}//${host}${pathname}#/designer?appId=${
  54 + appId || ''
  55 + }&type=edit&returnUrl=${hash}`,
  56 + '_self',
  57 + );
  58 + break;
  59 + default:
  60 + break;
  61 + }
  62 + };
  63 +
  64 + return (
  65 + <div className={cls(styles[prefix])}>
  66 + <FlowHeader handleClick={handleClick} type={'FLOW'} />
  67 + <section className={cls(styles[`${prefix}__main`])}>
  68 + <section className={cls(styles['main-content'])}>
  69 + <FlowSetting
  70 + params={{
  71 + appId: appId || '',
  72 + funCode: funCode || '',
  73 + }}
  74 + />
  75 + </section>
  76 + </section>
  77 + </div>
  78 + );
  79 +};
  80 +
  81 +export default DataFlowView;
... ...
  1 +import React, { useEffect, useMemo, useRef, useState } from 'react';
  2 +
  3 +import { Button, Image, message, Modal, Tooltip, Upload } from 'antd';
  4 +import { QxIcon } from '@/components';
  5 +
  6 +import defaultImg from '@/components/default/default_cover.png';
  7 +
  8 +const defaultIcon: Record<string, string> = {
  9 + form: 'icon-app-file-fill',
  10 + group: 'icon-app-folder-2-fill',
  11 + report: 'icon-app-performance-fill',
  12 + url: 'icon-app-share-forward-fill',
  13 + page: 'icon-app-tablicon-app-fill',
  14 + dataset: 'icon-app-presentation-2-fill',
  15 + datasetCube: 'icon-app-box-3-fill',
  16 + app: 'icon-app-grid-2-fill',
  17 +};
  18 +
  19 +// 数据流 图标展示
  20 +export const getIcon = (icon: string, type?: string) => {
  21 + if ((icon as string)?.startsWith('http')) {
  22 + return (
  23 + <Image
  24 + className={'qx-data-flow__img'}
  25 + src={icon}
  26 + fallback={defaultImg}
  27 + preview={false}
  28 + width={'16px'}
  29 + height={'16px'}
  30 + style={{
  31 + width: '16px',
  32 + height: '16px',
  33 + borderRadius: '4px',
  34 + }}
  35 + alt=""
  36 + />
  37 + );
  38 + } else {
  39 + return (
  40 + <QxIcon
  41 + className={'qx-data-flow__icon'}
  42 + type={icon || defaultIcon[type || 'form']}
  43 + />
  44 + );
  45 + }
  46 +};
  47 +
  48 +// 表格 Scroll 高度计算
  49 +export const useFlowTableScrollY = (
  50 + deps: {
  51 + columns: any[];
  52 + size?: 'small' | 'middle' | 'default';
  53 + tableBoxClassName?: string;
  54 + classNames?: string[]; // 表格DOM内嵌元素 列名 添加在此,
  55 + hasPagination?: boolean;
  56 + listProps?: any;
  57 + } = {
  58 + size: 'small',
  59 + tableBoxClassName: '',
  60 + classNames: [],
  61 + hasPagination: false,
  62 + columns: [],
  63 + listProps: {},
  64 + },
  65 +) => {
  66 + const itemHeaderHeight =
  67 + (deps?.size === 'middle' ? 48 : deps?.size === 'default' ? 56 : 40) + 1;
  68 + const TABLE_VIEW_LIST = deps?.tableBoxClassName || 'qx-data-flow-table';
  69 + let tableHeader = itemHeaderHeight;
  70 + const tableViewListDefaultHeight = 400;
  71 + const defaultPagePadding = 0;
  72 +
  73 + const $ = (selectors: string) =>
  74 + document.querySelector<HTMLElement>(selectors);
  75 +
  76 + const defaultScrollY = useMemo(
  77 + () => document.body.clientHeight,
  78 + [tableHeader],
  79 + );
  80 +
  81 + const [scrollY, setScrollY] = useState<number>(defaultScrollY ?? 200);
  82 +
  83 + const timer = useRef<NodeJS.Timer>();
  84 +
  85 + const getScrollY = () => {
  86 + const tableViewList = $(`.${TABLE_VIEW_LIST}`);
  87 +
  88 + const tableViewListClientHeight =
  89 + tableViewList?.clientHeight || tableViewListDefaultHeight;
  90 + // 分页大小 目前已经固定 所以直接用56px
  91 + const antPaginationHeight = deps?.hasPagination ? 56 : 0;
  92 +
  93 + const _domHeightByClassName = (deps?.classNames || []).map(
  94 + (_v: string) => $(`.${_v}`)?.clientHeight ?? 0,
  95 + );
  96 + let result =
  97 + tableViewListClientHeight -
  98 + tableHeader -
  99 + defaultPagePadding -
  100 + antPaginationHeight;
  101 + if (!!_domHeightByClassName?.length) {
  102 + result = result - eval(_domHeightByClassName.join('+'));
  103 + }
  104 +
  105 + if (tableViewList) {
  106 + tableViewList.style.overflowY = result < 200 ? 'auto' : 'hidden';
  107 + }
  108 +
  109 + setScrollY(result < 100 ? 100 : result);
  110 + };
  111 +
  112 + const observer = () => {
  113 + window.addEventListener('resize', getScrollY);
  114 + };
  115 +
  116 + const removeObserver = () => {
  117 + if (timer.current) window.clearTimeout(timer.current);
  118 + window.removeEventListener('resize', getScrollY);
  119 + };
  120 +
  121 + useEffect(() => {
  122 + observer();
  123 + return () => {
  124 + removeObserver();
  125 + };
  126 + }, [tableHeader]);
  127 +
  128 + useEffect(() => {
  129 + if (deps) {
  130 + timer.current = setTimeout(() => {
  131 + getScrollY();
  132 + }, 100);
  133 + }
  134 + }, [JSON.stringify(deps)]);
  135 +
  136 + return [scrollY, getScrollY];
  137 +};
... ...
  1 +import React, { useEffect, useMemo, useState } from 'react';
  2 +import {
  3 + Button,
  4 + Input,
  5 + message,
  6 + Modal,
  7 + Space,
  8 + Table,
  9 + Tooltip,
  10 + Typography,
  11 + Avatar,
  12 +} from 'antd';
  13 +
  14 +import { SettingOutlined } from '@ant-design/icons/lib';
  15 +import { useSearchParams } from 'react-router-dom';
  16 +import { useTitle } from 'ahooks';
  17 +import { QxIcon } from '@/components';
  18 +import { handleWindowOpen } from '@qx/utils';
  19 +import { FlowHeader, FlowVersion } from '@/components';
  20 +
  21 +import { useFlowTableScrollY } from '@/utils';
  22 +import actions from '@/utils/actions';
  23 +
  24 +import { isEmpty, cloneDeep } from 'lodash-es';
  25 +
  26 +const { Text } = Typography;
  27 +
  28 +// 样式
  29 +import cls from 'classnames';
  30 +import styles from '../index.module.less';
  31 +import { getProcessVersions } from '@/services';
  32 +import { FlowVersionStatusEnums } from '@/interface';
  33 +const prefix = 'qx-flow-setting';
  34 +
  35 +const DataFlowVersion: React.FC = ({}) => {
  36 + // @ts-ignore
  37 + const [searchParams] = useSearchParams();
  38 + const {
  39 + processId, // 流程ID
  40 + name, // 流程名称
  41 + returnUrl, // 来自页面跳转返回可跳转
  42 + } = {
  43 + processId: searchParams.get('processId'),
  44 + name: searchParams.get('name'),
  45 + returnUrl: searchParams.get('returnUrl'),
  46 + };
  47 + const [pageData, setPageData] = useState<any[]>([]);
  48 + const [tableLoading, setTableLoading] = useState<boolean>(true); // 表格loading
  49 + // 表格是否添加scroll 属性
  50 + const [isScrollY, setIsScrollY] = useState<boolean>(false);
  51 + // 添加标题
  52 + useTitle(name || '未命名流程');
  53 +
  54 + const handleClick = (
  55 + type: string,
  56 + data?: any,
  57 + e?: React.MouseEvent<HTMLElement, MouseEvent>,
  58 + ) => {
  59 + const { host, pathname, protocol, hash } = location;
  60 +
  61 + if (!!e) {
  62 + e.stopPropagation();
  63 + }
  64 +
  65 + switch (type) {
  66 + case 'RETURN':
  67 + // 返回上一页
  68 + if (!!returnUrl) {
  69 + // 返回上一页
  70 + handleWindowOpen(
  71 + `${protocol}//${host}${pathname}${returnUrl}`,
  72 + '_self',
  73 + );
  74 + } else {
  75 + // history.goBack();
  76 + window.history.back();
  77 + }
  78 + break;
  79 + case 'VIEW':
  80 + // 查看
  81 + console.log('VIEW === ', data);
  82 + break;
  83 + case 'RESTORE':
  84 + // 恢复
  85 + console.log('RESTORE === ', data);
  86 + break;
  87 + default:
  88 + break;
  89 + }
  90 + };
  91 +
  92 + const cols = useMemo(() => {
  93 + return [
  94 + {
  95 + title: () => {
  96 + return (
  97 + <span className={cls(styles[`${prefix}__col-title`])}>
  98 + 流程版本
  99 + </span>
  100 + );
  101 + },
  102 + dataIndex: 'version',
  103 + render: (text: any, row: any) => {
  104 + return (
  105 + <span
  106 + className={cls(styles[`${prefix}__colspan`])}
  107 + style={{ width: '100%' }}
  108 + >
  109 + <FlowVersion versionText={text} />
  110 + </span>
  111 + );
  112 + },
  113 + width: 170,
  114 + className: 'qx-flow-table-cell',
  115 + },
  116 + {
  117 + title: () => {
  118 + return (
  119 + <span className={cls(styles[`${prefix}__col-title`])}>状态</span>
  120 + );
  121 + },
  122 + dataIndex: 'status',
  123 + className: 'qx-flow-table-cell',
  124 + render: (text: any) => {
  125 + return (
  126 + <span
  127 + className={cls(styles[`${prefix}__colspan`])}
  128 + style={{ width: '100%' }}
  129 + >
  130 + <FlowVersion status={text} statusEnums={FlowVersionStatusEnums} />
  131 + </span>
  132 + );
  133 + },
  134 + width: 170,
  135 + },
  136 + {
  137 + title: () => {
  138 + return (
  139 + <span className={cls(styles[`${prefix}__col-title`])}>
  140 + 更新时间
  141 + </span>
  142 + );
  143 + },
  144 + dataIndex: 'updatedAt',
  145 + width: 200,
  146 + className: 'qx-flow-table-cell',
  147 + render: (text: any) => {
  148 + return (
  149 + <span
  150 + className={cls(styles[`${prefix}__colspan`])}
  151 + style={{ width: '100%' }}
  152 + >
  153 + {text}
  154 + </span>
  155 + );
  156 + },
  157 + onCell: (record: any) => {
  158 + return {
  159 + colSpan: ['APP', 'FUN'].includes(record?.type || '') ? 0 : 1,
  160 + };
  161 + },
  162 + },
  163 + {
  164 + title: () => {
  165 + return (
  166 + <span className={cls(styles[`${prefix}__col-title`])}>更新人</span>
  167 + );
  168 + },
  169 + dataIndex: 'updatedByUser',
  170 + className: 'qx-flow-table-cell qx-flow-table-cell-user',
  171 + render: (text: any, row: any) => {
  172 + if (isEmpty(text)) {
  173 + return '--';
  174 + }
  175 + let _imgError = false;
  176 + return (
  177 + <span
  178 + className={cls(styles[`${prefix}__colspan-user`])}
  179 + style={{ width: '100%' }}
  180 + >
  181 + <Avatar
  182 + className={'qx-flow-table-cell__avatar'}
  183 + size={24}
  184 + alt="avatar"
  185 + src={row?.updatedByUser?.faceUrl || ''}
  186 + onError={() => {
  187 + _imgError = true;
  188 + return true;
  189 + }}
  190 + >
  191 + {(!!row?.updatedByUser?.faceUrl && _imgError) ||
  192 + !row?.updatedByUser?.faceUrl
  193 + ? !!row?.updatedByUser?.name?.length
  194 + ? row?.updatedByUser?.name[
  195 + row?.updatedByUser?.name?.length - 1
  196 + ]
  197 + : null
  198 + : null}
  199 + </Avatar>
  200 + <Text
  201 + className={'qx-flow-table-cell__avatar-name'}
  202 + ellipsis={{ tooltip: row?.updatedByUser?.name || '' }}
  203 + >
  204 + {row?.updatedByUser?.name || ''}
  205 + </Text>
  206 + </span>
  207 + );
  208 + },
  209 + width: 200,
  210 + },
  211 + {
  212 + title: () => {
  213 + return (
  214 + <span className={cls(styles[`${prefix}__col-title`])}>操作</span>
  215 + );
  216 + },
  217 + dataIndex: 'control',
  218 + render: (text: any, row: any) => (
  219 + <Space
  220 + size={8}
  221 + className={cls(styles[`${prefix}__colspan`])}
  222 + style={{ width: '100%' }}
  223 + >
  224 + <Button
  225 + type="link"
  226 + size="small"
  227 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  228 + e.stopPropagation();
  229 + handleClick('VIEW', row);
  230 + }}
  231 + >
  232 + 查看
  233 + </Button>
  234 + <Button
  235 + type="link"
  236 + size="small"
  237 + onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) =>
  238 + handleClick('RESTORE', row, e)
  239 + }
  240 + >
  241 + 恢复
  242 + </Button>
  243 + </Space>
  244 + ),
  245 + width: 140,
  246 + className: 'qx-flow-table-cell',
  247 + },
  248 + ];
  249 + }, [JSON.stringify(pageData)]);
  250 +
  251 + // 查询list
  252 + const handleSearch = () => {
  253 + setTableLoading(true);
  254 +
  255 + actions.cancelToken(); // 多次请求 取最新请求 其他取消
  256 +
  257 + getProcessVersions('')
  258 + .then((res: any) => {
  259 + // 处理数据 符合表格渲染格式
  260 + const _cloneList: any[] = [];
  261 + const getColumnList = (data: any[]) => {
  262 + data.forEach((item: any) => {
  263 + _cloneList.push(cloneDeep(item));
  264 + if (!!item?.processes?.length) {
  265 + getColumnList(item?.processes);
  266 + }
  267 + });
  268 + };
  269 + getColumnList(cloneDeep(res || []));
  270 + setPageData(cloneDeep(_cloneList));
  271 + // setExpandedKeys(_cloneList.map((item: any) => item.key));
  272 + setTableLoading(false);
  273 + })
  274 + .catch(() => {
  275 + console.warn('数据列表获取失败');
  276 + setPageData([]);
  277 + setTableLoading(false);
  278 + });
  279 + };
  280 +
  281 + // 表格滚动高度获取
  282 + const [scrollY] = useFlowTableScrollY({
  283 + columns: cols,
  284 + listProps: {
  285 + pageData,
  286 + },
  287 + }) as any[];
  288 +
  289 + return (
  290 + <div className={cls(styles['qx-data-flow'])}>
  291 + <FlowHeader
  292 + title={name || '未命名流程'}
  293 + handleClick={handleClick}
  294 + type={'VERSION'}
  295 + />
  296 + <section className={cls(styles['qx-data-flow__main'])}>
  297 + <section className={cls(styles['main-content'])}>
  298 + <div
  299 + className={cls(styles[`${prefix}-container`], 'qx-data-flow-table')}
  300 + >
  301 + <Table
  302 + loading={tableLoading}
  303 + columns={cols}
  304 + dataSource={pageData}
  305 + scroll={
  306 + isScrollY
  307 + ? {
  308 + x: '100%',
  309 + y: scrollY,
  310 + }
  311 + : { x: '100%' }
  312 + }
  313 + pagination={false}
  314 + size={'small'}
  315 + bordered
  316 + rowKey={(record: any) => {
  317 + return `${record?.type || ''}${record?.id || ''}${Math.random()
  318 + .toString(36)
  319 + .replaceAll(/[0-9]/g, '')
  320 + .slice(2, 4)}`;
  321 + }}
  322 + />
  323 + </div>
  324 + </section>
  325 + </section>
  326 + </div>
  327 + );
  328 +};
  329 +
  330 +export default DataFlowVersion;
... ...
  1 +import { createRequest } from '@qx/utils';
  2 +
  3 +export default createRequest();
... ...
1 1 import { createHashRouter, RouterProvider } from 'react-router-dom';
2 2 import Root from '../page/root';
3 3 import About from '../page/about';
  4 +import Designer from '../page/designer';
  5 +import List from '../page/view/list';
  6 +import Version from '../page/view/version';
4 7
5 8 export default () => (
6 9 <RouterProvider
... ... @@ -13,6 +16,18 @@ export default () => (
13 16 path: '/about',
14 17 element: <About />,
15 18 },
  19 + {
  20 + path: '/designer',
  21 + element: <Designer />,
  22 + },
  23 + {
  24 + path: '/list',
  25 + element: <List />,
  26 + },
  27 + {
  28 + path: '/version',
  29 + element: <Version />,
  30 + },
16 31 ],
17 32 },
18 33 ])}
... ...
  1 +import { lazy } from 'react';
  2 +import { QxSdk } from '@qx/utils';
  3 +import request from '@/request';
  4 +const qxSdk = new QxSdk();
  5 +
  6 +qxSdk.registerSdk({
  7 + getFormFields(appCode: string, funCode: string, viewCode: string) {
  8 + return request.get(
  9 + `/qx-apaas-lowcode/app/${appCode}/form/${funCode}/field/option/${viewCode}?withRel=true`,
  10 + );
  11 + },
  12 +
  13 + getCommonComponent() {
  14 + return {
  15 + // QxAppSelector: lazy(async () => (await import(/* webpackChunkName: "qx-common" */ '@qx/common')).QxOrgSelector)
  16 + };
  17 + },
  18 +});
  19 +
  20 +export default qxSdk;
... ...