feat: 移动端选人组件支持自定义字段 (#5967)

* 调整 saveAs  fileName 优先级

* feat: 移动端人员选择增加确定按钮

* Update UserSelect.tsx

* fix: 人员选择ts 类型错误

* fix: 城市选择组件移动端回显问题

* fix: 移动端级联选择器选中值bug修复

* Update Cascader.tsx

* fix: 城市选择香港、澳门不能选择市

* fix: 删除错误城市数据

* feat: 移动端人员选择支持字段配置

* feat: 人员选择组件支持字段配置

* Update UserSelect.tsx

Co-authored-by: zhangxulong <zhangxulong@baidu.com>
This commit is contained in:
ls 2022-12-23 14:40:42 +08:00 committed by GitHub
parent 39444b8df1
commit 5341f4caac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 388 additions and 49 deletions

View File

@ -1572,7 +1572,7 @@ $remFactor: 16px;
--UserSelect--role-bg: #0bc286;
--UserSelect--border-color: #f7f7f9;
--UserSelect--content-bg: #f5f7f8;
--UserSelect--bread-color: var(--colors-brand-5);
--UserSelect--bread-color: #5e626a;
// tag
--Tag-content-fontSize: var(--fontSizeSm);
--Tag-height: #{px2rem(24px)};

View File

@ -3,6 +3,10 @@
&-popup {
height: 100vh;
.#{$ns}PopUp-content {
overflow-x: hidden;
}
}
&-selectPopup {
@ -50,6 +54,10 @@
.#{$ns}Button {
width: 100%;
border-radius: px2rem(4px);
line-height: px2rem(44px);
font-size: px2rem(16px);
font-weight: 400;
}
}
@ -71,10 +79,12 @@
transform: translateX(-50%);
line-height: 44px;
text-align: center;
font-size: px2rem(18px);
font-weight: 500;
}
&-btnEdit {
color: var(--UserSelect--bread-color);
color: var(--primary);
font-size: px2rem(16px);
}
}
@ -90,10 +100,11 @@
&-item {
cursor: pointer;
color: var(--UserSelect--bread-color);
color: var(--primary);
font-size: px2rem(14px);
&:last-child {
color: inherit;
color: #5e626a;
}
}
@ -136,16 +147,16 @@
overflow-x: hidden;
overflow-y: auto;
background: var(--white);
border-radius: px2rem(4px);
li {
display: flex;
justify-content: space-between;
align-items: center;
height: px2rem(42px);
line-height: px2rem(42px);
height: px2rem(48px);
line-height: px2rem(48px);
cursor: pointer;
user-select: none;
padding: 0 px2rem(16px);
border-bottom: px2rem(1px) solid var(--UserSelect--border-color);
> span {
@ -172,10 +183,60 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&-label {
flex: 1;
.option-avatar-txt {
width: px2rem(28px);
height: px2rem(28px);
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
margin-right: px2rem(12px);
background-color: #528eff;
color: #fff !important;
border: 1px solid #eee;
&.avatar-2 {
width: px2rem(32px);
height: px2rem(32px);
}
}
.option-avatar-img {
width: px2rem(28px);
height: px2rem(28px);
margin-right: px2rem(12px);
border-radius: 100%;
&.avatar-2 {
width: px2rem(32px);
height: px2rem(32px);
}
}
.option-fields {
flex: 1;
line-height: px2rem(18px);
.option-item {
margin-right: px2rem(10px);
&:first-child {
color: var(--body-color);
font-size: var(--Form-fontSize);
}
&:nth-child(2n) {
margin-top: px2rem(4px);
}
&:last-child {
display: block;
font-size: 12px;
color: #84868c;
}
}
}
}
&-icon-box {
@ -254,6 +315,16 @@
overflow: hidden;
box-sizing: border-box;
background: var(--white);
position: relative;
}
&-resultBox-shadow {
position: absolute;
right: px2rem(50px);
top: px2rem(10px);
width: px2rem(10px);
height: px2rem(26px);
background: rgba(255, 255, 255, 0.5);
}
&-selectNum {
@ -295,21 +366,31 @@
}
&-selectSort-box {
margin-left: px2rem(10px);
// margin-left: px2rem(10px);
padding: px2rem(4px) px2rem(10px);
position: relative;
&::after {
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
filter: blur(5px);
}
}
&-noRecord {
width: 100vw;
height: 100%;
margin: 0 px2rem(16px);
margin-top: px2rem(16px);
display: flex;
align-items: center;
justify-content: center;
background: var(--white);
padding: px2rem(100px) 0;
box-sizing: border-box;
border-radius: px2rem(4px);
}
&-selectList-pop {
@ -329,7 +410,7 @@
&-del {
text-align: right;
flex: none !important;
padding: 0 10px;
padding: 0 10px 0 0;
}
&-dragBar {
@ -391,20 +472,61 @@
&-text {
font-size: px2rem(16px);
color: #151b26;
font-weight: 500;
}
&-btnClear {
color: var(--UserSelect--bread-color);
color: var(--primary);
font-size: px2rem(16px);
font-weight: 400;
cursor: pointer;
}
}
.#{$ns}ResultBox-valueLabel {
display: flex;
align-items: center;
}
.#{$ns}ResultBox-placeholder {
text-align: right;
}
}
.#{$ns}UserSelect-avatar-img {
width: px2rem(22px);
height: px2rem(22px);
border-radius: 50%;
margin-right: px2rem(10px);
}
.#{$ns}UserSelect-h2 {
height: px2rem(66px) !important;
line-height: px2rem(66px) !important;
}
.#{$ns}UserSelect-avatar-text {
display: flex;
font-size: px2rem(12px);
justify-content: center;
align-items: center;
width: px2rem(22px);
height: px2rem(22px);
border-radius: 50%;
background-color: #528eff;
color: #fff !important;
margin-right: px2rem(10px);
border: 1px solid #eee;
}
.#{$ns}UserTabSelect {
&-popup {
width: 100vw;
height: 100vh;
.#{$ns}PopUp-content {
overflow-x: hidden;
}
}
&-wrap {
@ -422,6 +544,10 @@
.#{$ns}Button {
width: 100%;
border-radius: px2rem(4px);
line-height: px2rem(44px);
font-size: px2rem(16px);
font-weight: 400;
}
}
@ -430,6 +556,38 @@
display: flex;
flex-direction: column;
.#{$ns}Tabs-links {
> li {
border-style: none !important;
a {
font-weight: 400 !important;
color: #303540 !important;
font-size: px2rem(16px) !important;
}
&.is-active {
position: relative;
a {
font-weight: 500 !important;
color: var(--primary) !important;
}
&::after {
content: '';
display: block;
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: px2rem(32px);
height: px2rem(2px);
background: var(--primary);
}
}
}
}
.#{$ns}Tabs-content {
background-color: var(--UserSelect--content-bg);
}

View File

@ -39,6 +39,8 @@ export interface UserSelectProps extends ThemeProps, LocaleProps {
placeholder?: string;
searchPlaceholder?: string;
controlled?: boolean;
displayFields: Array<string>;
isTab?: boolean;
fetcher?: (
api: Api,
data?: any,
@ -74,6 +76,12 @@ export interface UserSelectState {
isEdit: boolean;
}
const defaultIcons = [
'user-default-department',
'user-default-role',
'user-default-post'
];
export class UserSelect extends React.Component<
UserSelectProps,
UserSelectState
@ -288,8 +296,8 @@ export class UserSelect extends React.Component<
if (isRef) {
// 部门、人员一起加载
const res = await Promise.all([
deferLoad(option, false, deferParam),
deferLoad({...option, ref: option.value}, true, deferParam)
deferLoad(option, false, deferParam)
// deferLoad({...option, ref: option.value}, true, deferParam)
]);
option.children = flatten(res);
} else {
@ -382,6 +390,7 @@ export class UserSelect extends React.Component<
const {controlled} = this.props;
this.setState({
isSelectOpened: true,
isEdit: true,
tempSelection: controlled
? this.props.selection?.slice() || []
: this.state.selection.slice()
@ -433,7 +442,7 @@ export class UserSelect extends React.Component<
(item2: Option) => item2[valueField] === item[valueField]
);
if (res) {
res[labelField] = item[labelField];
res.label = item[labelField];
}
});
return _selection;
@ -510,9 +519,11 @@ export class UserSelect extends React.Component<
isDep,
isRef,
translate: __,
controlled
controlled,
displayFields,
isTab,
multiple
} = this.props;
let selection = controlled
? this.props.selection || []
: this.state.selection;
@ -533,9 +544,24 @@ export class UserSelect extends React.Component<
const userIcon = this.renderIcon(option);
const displays =
option.type === 'user' && displayFields
? displayFields
: ['label'];
const avatar = displays.find(i => i === 'avatar');
const first =
option.label?.substring(0, 1).toLocaleUpperCase() || 'A';
const restFiedls = displays.filter(i => i !== 'avatar');
if (option.type === 'post') {
restFiedls.push('desc');
}
return (
<li key={index}>
{checkVisible ? (
<li
key={index}
className={restFiedls.length === 2 ? cx(`UserSelect-h2`) : ''}
>
{(isTab || multiple) && checkVisible ? (
<Checkbox
size="sm"
checked={checkValues.includes(option[valueField])}
@ -551,15 +577,48 @@ export class UserSelect extends React.Component<
: hasChildren && this.handleExpand(option)
}
>
{userIcon ? (
{!avatar &&
userIcon &&
(isDep || defaultIcons.includes(option.icon)) ? (
<span className={cx('UserSelect-userPic-box')}>
{userIcon}
</span>
) : null}
<span className={cx('UserSelect-label')}>
{option[labelField]}
</span>
{!option.isRef ? (
<span className={cx('UserSelect-label')}>
{option.label}
</span>
) : null}
{avatar && option.isRef ? (
option.avatar ? (
<img
className={`option-avatar-img ${
restFiedls.length === 2 ? 'avatar-2' : ''
}`}
src={option.avatar}
/>
) : (
<span
className={`option-avatar-txt ${
restFiedls.length === 2 ? 'avatar-2' : ''
}`}
>
{first}
</span>
)
) : null}
{option.isRef ? (
<div className="option-fields">
{restFiedls.map(key => (
<span className={cx('option-item')} key={key}>
{option[key]}
</span>
))}
</div>
) : null}
</span>
{!isSearch && hasChildren ? (
@ -587,6 +646,8 @@ export class UserSelect extends React.Component<
classnames: cx,
labelField = 'label',
valueField = 'value',
displayFields,
isDep,
translate: __
} = this.props;
const {isEdit} = this.state;
@ -604,8 +665,24 @@ export class UserSelect extends React.Component<
options,
(item: Option) => item[valueField] === option[valueField]
);
const displays =
option.type === 'user' && displayFields
? displayFields
: ['label'];
const avatar = displays.find(i => i === 'avatar');
const first =
option.label?.substring(0, 1).toLocaleUpperCase() || 'A';
const restFiedls = displays.filter(i => i !== 'avatar');
if (option.type === 'post') {
restFiedls.push('desc');
}
return (
<li key={index}>
<li
key={index}
className={restFiedls.length === 2 ? cx(`UserSelect-h2`) : ''}
>
{isEdit ? (
<span
className={cx(`UserSelect-del`)}
@ -616,17 +693,66 @@ export class UserSelect extends React.Component<
) : null}
<span className={cx(`UserSelect-memberName`)}>
{userIcon ? (
{!avatar &&
userIcon &&
(isDep || defaultIcons.includes(option.icon)) ? (
<span className={cx('UserSelect-userPic-box')}>
{userIcon}
</span>
) : null}
<span className={cx('UserSelect-label')}>
{originOption
? originOption[labelField]
: option[labelField]}
</span>
{}
{!option.isRef ? (
labelField === 'avatar' ? (
option[labelField] ? (
<img
className={cx('UserSelect-avatar-img')}
src={option[labelField]}
alt=""
/>
) : (
<span className={cx('UserSelect-avatar-text')}>
{option[valueField].slice(0, 1).toLocaleUpperCase()}
</span>
)
) : (
<span className={cx('UserSelect-label')}>
{originOption
? originOption[labelField]
: option[labelField]}
</span>
)
) : null}
{avatar && option.isRef ? (
option.avatar ? (
<img
className={`option-avatar-img ${
restFiedls.length === 2 ? 'avatar-2' : ''
}`}
src={option.avatar}
/>
) : (
<span
className={`option-avatar-txt ${
restFiedls.length === 2 ? 'avatar-2' : ''
}`}
>
{first}
</span>
)
) : null}
{option.isRef ? (
<div className="option-fields">
{restFiedls.map(key => (
<span className={cx('option-item')} key={key}>
{option[key]}
</span>
))}
</div>
) : null}
</span>
{isEdit ? (
<a className={cx('UserSelect-dragBar')}>
@ -655,6 +781,7 @@ export class UserSelect extends React.Component<
labelField = 'label',
valueField = 'value',
classnames: cx,
multiple,
translate: __
} = this.props;
@ -704,7 +831,7 @@ export class UserSelect extends React.Component<
key={index}
onClick={() => this.handleBreadChange(item, index)}
>
{item[labelField]}
{item.label}
</span>
))
.reduce((prev, curr, index) => [
@ -721,6 +848,7 @@ export class UserSelect extends React.Component<
{selection?.length ? (
<div className={cx(`UserSelect-resultBox`)}>
<div className={cx(`UserSelect-resultBox-shadow`)}></div>
<ul className={cx(`UserSelect-selectList`)}>
{selection.map((item: Option, index) => {
const originOption = findTree(
@ -729,11 +857,26 @@ export class UserSelect extends React.Component<
);
return (
<li key={index} className={cx('UserSelect-selectList-item')}>
<span>
{originOption
? originOption[labelField]
: item[labelField]}
</span>
{labelField === 'avatar' ? (
item[labelField] ? (
<img
className={cx('UserSelect-avatar-img')}
src={item[labelField]}
alt=""
/>
) : (
<span className={cx('UserSelect-avatar-text')}>
{item[valueField].slice(0, 1).toLocaleUpperCase()}
</span>
)
) : (
<span>
{originOption
? originOption[labelField]
: item[labelField]}
</span>
)}
<span
className={cx('UserSelect-selectList-item-closeBox')}
onClick={() => this.onDelete(item)}
@ -744,15 +887,17 @@ export class UserSelect extends React.Component<
);
})}
</ul>
<span
className={cx('UserSelect-selectSort-box')}
onClick={this.handleSort}
>
<Icon
icon="menu"
className={cx('UserSelect-selectSort', 'icon')}
/>
</span>
{multiple ? (
<span
className={cx('UserSelect-selectSort-box')}
onClick={this.handleSort}
>
<Icon
icon="menu"
className={cx('UserSelect-selectSort', 'icon')}
/>
</span>
) : null}
</div>
) : null}
@ -817,7 +962,9 @@ export class UserSelect extends React.Component<
classnames: cx,
translate: __,
placeholder = '请选择',
showResultBox
showResultBox,
labelField = 'label',
valueField = 'value'
} = this.props;
const {isOpened, isEdit, isSelectOpened} = this.state;
@ -829,6 +976,28 @@ export class UserSelect extends React.Component<
className={cx('UserSelect-input', isOpened ? 'is-active' : '')}
allowInput={false}
result={this.getResult()}
itemRender={(option: any) => {
if (labelField !== 'avatar') {
return (
<span>{`${option.scopeLabel || ''}${option.label}`}</span>
);
} else {
if (option[labelField]) {
return (
<img
className={cx('UserSelect-avatar-img')}
src={option[labelField]}
alt=""
/>
);
}
return (
<span className={cx('UserSelect-avatar-text')}>
{option[valueField].slice(0, 1).toLocaleUpperCase()}
</span>
);
}
}}
onResultChange={value => this.handleSelectChange(value, true)}
onResultClick={this.onOpen}
placeholder={placeholder}

View File

@ -254,6 +254,7 @@ export class UserTabSelect extends React.Component<
className="TabsTransfer-tab"
>
<UserSelect
isTab={true}
selection={this.state.selection}
showResultBox={false}
{...item}

View File

@ -14,6 +14,7 @@ import find from 'lodash/find';
import {createObject, autobind} from 'amis-core';
import {PlainObject} from 'amis-core';
import {FormOptionsSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
/**
* UserSelect
@ -205,6 +206,12 @@ export default class UserSelectControl extends React.Component<
onChange(newValue);
}
renderStatic() {
const {selectedOptions, labelField = 'label'} = this.props;
return selectedOptions.map((item: Option) => item[labelField]).join(',');
}
@supportStatic()
render() {
let {
showNav,
@ -222,7 +229,9 @@ export default class UserSelectControl extends React.Component<
placeholder,
searchPlaceholder,
tabMode,
data
data,
displayFields,
labelField
} = this.props;
tabOptions?.forEach((item: any) => {
item.deferLoad = this.deferLoad;
@ -256,6 +265,8 @@ export default class UserSelectControl extends React.Component<
deferLoad={this.deferLoad}
onChange={this.changeValue}
onSearch={this.onSearch}
displayFields={displayFields}
labelField={labelField}
isDep={isDep}
isRef={isRef}
/>