Commit 184fa4a31f530d2e9e2964eea9c1b465626bb66b

Authored by 邱嘉伟
1 parent 5ccc9c43

feat :增加角色选择器

... ... @@ -6,6 +6,8 @@ export * from './qx-tags-input';
6 6 export * from './qx-user-selector';
7 7 export * from './qx-org-selector';
8 8 export * from './qx-form-select';
  9 +export * from './qx-pos-selector';
  10 +export * from './qx-role-selector';
9 11 export * from './qx-app-selector';
10 12 export * from './utils';
11 13 export * from './qx-field';
... ...
  1 +import QxRoleSelectorInput from './src/input';
  2 +import QxRoleSelectorDialog from './src/dialog';
  3 +import React from 'react';
  4 +
  5 +interface QxRoleSelectorType extends React.FC {
  6 + Dialog: typeof QxRoleSelectorDialog;
  7 +}
  8 +
  9 +export const QxRoleSelector = QxRoleSelectorInput as QxRoleSelectorType;
  10 +
  11 +QxRoleSelector.Dialog = QxRoleSelectorDialog;
... ...
  1 +import * as React from 'react';
  2 +import { useCallback, useEffect, useImperativeHandle, useState } from 'react';
  3 +import { Checkbox, Empty, Input, Radio, Spin } from 'antd';
  4 +import Menu from 'antd/es/menu';
  5 +import { SearchOutlined } from '@ant-design/icons';
  6 +import { getRoles, getRolesByAppCode } from './service';
  7 +import { cloneDeep } from "lodash-es";
  8 +// import _ from 'lodash';
  9 +
  10 +type PosCoreProps = {
  11 + appId?: string;
  12 + cRef?: any;
  13 + multiple?: boolean;
  14 + placeholder?: string;
  15 + params?: any;
  16 + onSelect?: (selectedKeys: string[], selectedData: RoleModel[]) => void;
  17 + request: any;
  18 + isRole?: boolean; // 是否为设计时角色权限
  19 + appCode?: string;
  20 +};
  21 +
  22 +export interface RoleModel {
  23 + id: string;
  24 + name: string;
  25 + appId?: string;
  26 + code: string;
  27 + visible?: boolean;
  28 + disabled?: boolean;
  29 + child: RoleModel[];
  30 +}
  31 +
  32 +const RoleSelCore: React.FC<PosCoreProps> = ({
  33 + appId,
  34 + cRef,
  35 + multiple,
  36 + placeholder,
  37 + onSelect,
  38 + isRole,
  39 + appCode,
  40 + ...props
  41 +}) => {
  42 + const [loading, setLoading] = useState<boolean>(true);
  43 +
  44 + const [data, setData] = useState<RoleModel[]>([]);
  45 +
  46 + const [selectedData, setSelectedData] = useState<RoleModel[]>([]);
  47 + const [selectedKeys, setSelectedKeys] = useState<string[]>();
  48 + const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
  49 + const [keywords, setKeywords] = useState<string>('');
  50 +
  51 + const requestData = useCallback(() => {
  52 + setLoading(true);
  53 + let _getRole = getRoles;
  54 + if (isRole) {
  55 + _getRole = getRolesByAppCode;
  56 + }
  57 + // todo 当前为指定appCode,待接口修改为appId传入(不用appId了)
  58 + _getRole(props.request, appCode || '').then((res) => {
  59 + const roles: RoleModel[] = res.child || [];
  60 + if (roles) {
  61 + const _expandedKeys: string[] = [];
  62 + roles.forEach((item) => {
  63 + _expandedKeys.push(item.id);
  64 + });
  65 + setExpandedKeys(_expandedKeys);
  66 + setData(roles);
  67 + setLoading(false);
  68 + }
  69 + });
  70 + }, []);
  71 +
  72 + useEffect(() => {
  73 + requestData();
  74 + }, [requestData]);
  75 +
  76 + useEffect(() => {
  77 + const isOnSelect = onSelect ? onSelect : () => { };
  78 + if (selectedKeys) {
  79 + isOnSelect(selectedKeys, selectedData);
  80 + }
  81 + }, [onSelect, selectedKeys, selectedData]);
  82 +
  83 + const handleSelect = (selectData: { selectedKeys: string[] }) => {
  84 + //单选走这里
  85 + if (!multiple) {
  86 + setSelectedKeys(selectData.selectedKeys);
  87 + }
  88 + };
  89 + useImperativeHandle(cRef, () => ({
  90 + // 暴露给父组件
  91 + remove: (index: number) => {
  92 + let _selectedKeys: string[] = [];
  93 + let _selectedData: RoleModel[] = [];
  94 + if (selectedKeys && selectedKeys.length > 0) {
  95 + _selectedKeys = [...selectedKeys];
  96 + _selectedData = [...selectedData];
  97 + _selectedKeys.splice(index, 1);
  98 + _selectedData.splice(index, 1);
  99 + setSelectedData(_selectedData);
  100 + setSelectedKeys(_selectedKeys);
  101 + }
  102 + },
  103 + emptySelect: () => {
  104 + setSelectedData([]);
  105 + setSelectedKeys([]);
  106 + },
  107 + }));
  108 + const handleMultiSelect = (checked: boolean, item: RoleModel) => {
  109 + let _selectedKeys: string[] = [];
  110 + let _selectedData: RoleModel[] = [];
  111 + if (selectedKeys) {
  112 + _selectedKeys = [...selectedKeys];
  113 + _selectedData = [...selectedData];
  114 + }
  115 + // console.log(checked, item.id, _selectedKeys);
  116 +
  117 + if (checked) {
  118 + _selectedKeys.push(item.id);
  119 + _selectedData.push(item);
  120 + } else {
  121 + const index = _selectedKeys.indexOf(item.id);
  122 + if (index > -1) {
  123 + _selectedKeys.splice(index, 1);
  124 + _selectedData.splice(index, 1);
  125 + }
  126 + }
  127 +
  128 + setSelectedData(_selectedData);
  129 + setSelectedKeys(_selectedKeys);
  130 + };
  131 +
  132 + //多选走这里
  133 + const filter = (word: string) => {
  134 + setKeywords(word);
  135 + const traverse = function (node: any) {
  136 + const childNodes = node.child || [];
  137 +
  138 + childNodes.forEach((child) => {
  139 + child.visible = child.name.indexOf(word) > -1;
  140 +
  141 + traverse(child);
  142 + });
  143 +
  144 + if (!node.visible && childNodes.length) {
  145 + node.visible = childNodes.some((child) => child.visible);
  146 + }
  147 + };
  148 +
  149 + if (data) {
  150 + const _data = cloneDeep(data);
  151 + _data.forEach((item) => {
  152 + traverse(item);
  153 + });
  154 + setData(_data);
  155 + }
  156 + };
  157 +
  158 + const handleSearch = (e: React.KeyboardEvent<HTMLInputElement>) => {
  159 + e.stopPropagation();
  160 + // @ts-ignore
  161 + filter(e.target.value.trim());
  162 + };
  163 + const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  164 + // @ts-ignore
  165 + if (e.type === 'click' && e.target.value === '' && data) {
  166 + //如果是清空
  167 + filter('');
  168 + }
  169 + };
  170 +
  171 + const renderText = (text: string) => {
  172 + let title = <> {text}</>;
  173 + if (keywords) {
  174 + const index = text.indexOf(keywords);
  175 + if (index > -1) {
  176 + title = (
  177 + <>
  178 + {text.substr(0, index)}
  179 + <span className={'qx-keywords-highlight'}>{keywords}</span>
  180 + {text.substr(index + keywords.length)}
  181 + </>
  182 + );
  183 + }
  184 + }
  185 + return title;
  186 + };
  187 +
  188 + return (
  189 + <div className={'qx-search-menus__wrap'}>
  190 + <Input
  191 + // style={{ display: 'none' }}
  192 + className={'qx-selector-sub-search'}
  193 + placeholder={placeholder || '请输入角色名称,按回车键搜索'}
  194 + allowClear
  195 + prefix={<SearchOutlined />}
  196 + onChange={(e) => {
  197 + handleChange(e);
  198 + }}
  199 + onPressEnter={(e) => {
  200 + handleSearch(e);
  201 + }}
  202 + />
  203 + <div className="qx-search-menus">
  204 + {expandedKeys.length ? (
  205 + <Menu
  206 + mode={'inline'}
  207 + onSelect={handleSelect}
  208 + selectedKeys={multiple ? [] : selectedKeys}
  209 + multiple={!!multiple}
  210 + defaultOpenKeys={expandedKeys}
  211 + >
  212 + {data.map((item) => {
  213 + if (typeof item.visible === 'boolean' && !item.visible) {
  214 + return null;
  215 + }
  216 + if (item.child) {
  217 + return (
  218 + <Menu.SubMenu key={item.id} title={item.name}>
  219 + {item.child.map((child) => {
  220 + return typeof child.visible === 'boolean' && !child.visible ? null : (
  221 + <Menu.Item key={child.id} disabled={child.disabled}>
  222 + {multiple ? (
  223 + <Checkbox
  224 + checked={selectedKeys && selectedKeys.indexOf(child.id) > -1}
  225 + disabled={child.disabled}
  226 + onChange={(e) => {
  227 + handleMultiSelect(e.target.checked, child);
  228 + }}
  229 + >
  230 + {renderText(child.name)}
  231 + </Checkbox>
  232 + ) : isRole ? (
  233 + <Radio
  234 + checked={
  235 + selectedKeys?.length && selectedKeys[0].indexOf(child.id) > -1
  236 + }
  237 + disabled={child.disabled}
  238 + >
  239 + {renderText(child.name)}
  240 + </Radio>
  241 + ) : (
  242 + renderText(child.name)
  243 + )}
  244 + </Menu.Item>
  245 + );
  246 + })}
  247 + </Menu.SubMenu>
  248 + );
  249 + }
  250 + return null;
  251 + })}
  252 + </Menu>
  253 + ) : null}
  254 +
  255 + <Spin
  256 + spinning={loading}
  257 + style={{ width: '100%', marginTop: '40px' }}
  258 + // indicator={<LoadingOutlined style={{ fontSize: 24, marginTop: '40px' }} spin />}
  259 + />
  260 + {!loading && data.length === 0 ? <Empty style={{ paddingTop: '30px' }} /> : null}
  261 + </div>
  262 + </div>
  263 + );
  264 +};
  265 +
  266 +export default RoleSelCore;
... ...
  1 +import * as React from 'react';
  2 +import { useRef, useState } from 'react';
  3 +import { Modal, Tag } from 'antd';
  4 +import './style.less';
  5 +import type { RoleModel } from './core';
  6 +import RoleSelCore from './core';
  7 +// import { RequestMethod } from 'umi-request';
  8 +
  9 +type RoleSelectorDialogProps = {
  10 + appId?: string;
  11 + title?: string;
  12 + visible: boolean;
  13 + onCancel: () => void;
  14 + data?: [];
  15 + multiple?: boolean;
  16 + onOk: (selectedKeys: string[], selectedData: RoleModel[]) => void;
  17 + request: AnalyserOptions;
  18 + isRole?: boolean; // 是否为设计时角色权限
  19 + appCode?: string;
  20 +};
  21 +
  22 +const RoleSelectorDialog: React.FC<RoleSelectorDialogProps> = (props) => {
  23 + const [selectedData, setSelectedData] = useState<RoleModel[]>([]);
  24 + const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
  25 + const coreRef = useRef({
  26 + // eslint-disable-next-line @typescript-eslint/no-unused-vars
  27 + remove: (index: number) => {
  28 + // console.log(index)
  29 + },
  30 + emptySelect: () => {},
  31 + });
  32 +
  33 + const handleOk = () => {
  34 + props.onOk(selectedKeys, selectedData);
  35 + coreRef.current.emptySelect();
  36 + setSelectedKeys([]);
  37 + setSelectedData([]);
  38 + };
  39 + const handleCancel = () => {
  40 + props.onCancel();
  41 + coreRef.current.emptySelect();
  42 + setSelectedKeys([]);
  43 + setSelectedData([]);
  44 + };
  45 +
  46 + const handleSelectPos = (keys: string[], datas: RoleModel[]) => {
  47 + setSelectedKeys(keys);
  48 + setSelectedData(datas);
  49 + };
  50 +
  51 + const handleRemove = (index: number) => {
  52 + coreRef.current.remove(index);
  53 + };
  54 +
  55 + return (
  56 + <Modal
  57 + title={props.title || '选择角色'}
  58 + width={560}
  59 + visible={props.visible}
  60 + className={'qx-role-selector__dialog'}
  61 + onOk={handleOk}
  62 + onCancel={handleCancel}
  63 + >
  64 + {props.multiple ? (
  65 + <div className={'qx-role-selected__temp'}>
  66 + {(selectedData || []).map((item: RoleModel, index: number) => (
  67 + <Tag closable color={'blue'} key={item.id} onClose={() => handleRemove(index)}>
  68 + {item.name}
  69 + </Tag>
  70 + ))}
  71 + </div>
  72 + ) : null}
  73 + <div className={'qx-role-selector__content'}>
  74 + {props.visible ? (
  75 + <RoleSelCore
  76 + request={props.request}
  77 + cRef={coreRef}
  78 + // multiple
  79 + multiple={!props?.isRole}
  80 + onSelect={handleSelectPos}
  81 + appId={props.appId}
  82 + isRole={props?.isRole}
  83 + appCode={props?.appCode}
  84 + />
  85 + ) : null}
  86 + </div>
  87 + </Modal>
  88 + );
  89 +};
  90 +
  91 +export default RoleSelectorDialog;
... ...
  1 +import React, { useEffect, useState } from 'react';
  2 +import { Checkbox, Input, Tag } from 'antd';
  3 +import { ApartmentOutlined } from '@ant-design/icons';
  4 +import PosSelectorDialog from './dialog';
  5 +// import { RequestMethod } from 'umi-request';
  6 +
  7 +export type QxRoleSelectorProps = {
  8 + onChange?: (data: any) => void;
  9 + defaultValue?: any;
  10 + disabled?: boolean;
  11 + multiple?: boolean;
  12 + readOnly?: boolean;
  13 + name?: string;
  14 + data?: [];
  15 + request: any;
  16 +};
  17 +
  18 +/**
  19 + * 表单设计器(XRender)
  20 + * @constructor
  21 + */
  22 +const QxRoleSelector: React.FC<QxRoleSelectorProps> = (props) => {
  23 + const [selectOrgs, setSelectOrgs] = useState([]);
  24 + const [visible, setVisible] = useState(false);
  25 + const [value, setValue] = useState<string | string[]>();
  26 +
  27 + useEffect(() => {
  28 + setValue(props.defaultValue);
  29 + }, []);
  30 +
  31 + // getUserList()
  32 + const handleOk = (keys: [], data: []) => {
  33 + let _value: string[] | string = keys;
  34 + if (!props.multiple && keys && keys.length > 0) {
  35 + // @ts-ignore
  36 + _value = keys[0];
  37 + }
  38 + setValue(_value);
  39 + setSelectOrgs(data);
  40 + setVisible(false);
  41 + if (props.onChange) {
  42 + props.onChange(_value);
  43 + }
  44 + };
  45 +
  46 + const handleCancel = () => {
  47 + setVisible(false);
  48 + };
  49 + const handleRemove = (index: number) => {
  50 + let _value: string | string[] = '';
  51 + let _selected: string[] = [];
  52 + if (props.multiple) {
  53 + _value = [...value];
  54 + _selected = [...selectOrgs];
  55 + _value.splice(index, 1);
  56 + _selected.splice(index, 1);
  57 + }
  58 +
  59 + setValue(_value);
  60 + setSelectOrgs(_selected);
  61 +
  62 + if (props.onChange) {
  63 + props.onChange(_value);
  64 + }
  65 + };
  66 +
  67 + return (
  68 + <>
  69 + {props.name ? (
  70 + props.multiple && typeof value !== 'string' ? (
  71 + <Checkbox.Group name={props.name} value={value} style={{ display: 'none' }} />
  72 + ) : (
  73 + <Input style={{ display: 'none' }} value={value} />
  74 + )
  75 + ) : null}
  76 + <div
  77 + className={'qx-user-selector ant-input'}
  78 + style={{ minHeight: '33px' }}
  79 + onClick={() => setVisible(true)}
  80 + >
  81 + <ApartmentOutlined style={{ paddingRight: '5px', color: '#999' }} />
  82 + {selectOrgs.map((org: { title: string; key: string }, index) => (
  83 + <Tag closable color={'blue'} key={org.key} onClose={() => handleRemove(index)}>
  84 + {org.title}
  85 + </Tag>
  86 + ))}
  87 + </div>
  88 + {!props.readOnly && (
  89 + <PosSelectorDialog
  90 + visible={visible}
  91 + multiple={props.multiple}
  92 + data={props.data}
  93 + onOk={handleOk}
  94 + request={props.request}
  95 + onCancel={handleCancel}
  96 + />
  97 + )}
  98 + </>
  99 + );
  100 +};
  101 +
  102 +export default QxRoleSelector;
... ...
  1 +/**
  2 + * 获取选人组件中的角色
  3 + */
  4 +export function getRoles(request: any, appCode: string) {
  5 + return request.get(`/qx-apaas-uc/role/authTree`);
  6 +}
  7 +
  8 +/**
  9 + * 获取选人组件中的角色(设计时角色控制)
  10 + */
  11 +export function getRolesByAppCode(request: any, appCode: string) {
  12 + return request.get(`/qx-apaas-lowcode/appRole/${appCode}/authTree`);
  13 +}
... ...
  1 +.qx-role-selector__dialog {
  2 + .ant-modal-body {
  3 + display: flex;
  4 + flex-direction: column;
  5 + padding: 0;
  6 +
  7 + > .ant-row {
  8 + flex: 1;
  9 + }
  10 + }
  11 +
  12 + .ant-checkbox-wrapper {
  13 + width: 100%;
  14 + }
  15 +}
  16 +
  17 +.qx-role-selected__temp {
  18 + display: flex;
  19 + flex-wrap: wrap;
  20 + align-items: center;
  21 + height: 60px;
  22 + padding: 5px;
  23 + overflow: auto;
  24 + border-bottom: 1px solid #f0f0f0;
  25 +
  26 + .ant-tag {
  27 + margin: 1px 2px;
  28 + }
  29 +}
  30 +
  31 +.qx-role-selector__content {
  32 + height: 300px;
  33 + overflow: auto;
  34 +}
... ...