feat: Picker组件已选项支持限制最大展示数量

This commit is contained in:
lurunze1226 2023-08-21 17:28:20 +08:00
parent 866d3412b6
commit 3efadbbfb6
11 changed files with 1021 additions and 197 deletions

View File

@ -684,12 +684,520 @@ order: 35
}
```
## 限制标签最大展示数量
设置`overflowConfig`后可以限制标签的最大展示数量,该属性仅在多选模式开启后生效,包含以下几个配置项:
- `maxTagCount`最大展示数量是范围为0 - 选项总数量的整数,超出数量的部分会收纳到 Popover 中。
- `displayPosition`:收纳标签生效的位置,类型为字符串数组,未开启内嵌模式默认为**选择器**, 开启后默认为**选择器**和**CRUD 顶部**,可选值为`'select'`(选择器)、`'crud'`(增删改查)。
- `overflowTagPopover`配置收纳标签 Popover 相关[属性](../tooltip#属性表)。
- `overflowTagPopoverInCRUD`可以配置**CRUD 顶部**收纳标签的 Popover相关[属性](../tooltip#属性表)。
> `3.4.0` 及以上版本
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"debug": true,
"body": [
{
"type": "picker",
"overflowConfig": {
"maxTagCount": 3,
},
"name": "maxTagCount1",
"joinValues": true,
"valueField": "id",
"labelField": "engine",
"label": "多选",
"source": "/api/mock2/sample",
"size": "lg",
"value": "1,2,3,4,5,6,7",
"multiple": true,
"pickerSchema": {
"mode": "table",
"name": "thelist",
"quickSaveApi": "/api/mock2/sample/bulkUpdate",
"quickSaveItemApi": "/api/mock2/sample/$id",
"draggable": true,
"headerToolbar": {
"wrapWithPanel": false,
"type": "form",
"className": "text-right",
"target": "thelist",
"mode": "inline",
"body": [
{
"type": "input-text",
"name": "keywords",
"addOn": {
"type": "submit",
"label": "搜索",
"level": "primary",
"icon": "fa fa-search pull-left"
}
}
]
},
"footerToolbar": [
"statistics",
{
"type": "pagination",
"showPageInput": true,
"layout": "perPage,pager,go"
}
],
"columns": [
{
"name": "engine",
"label": "Rendering engine",
"sortable": true,
"searchable": true,
"type": "text",
"toggled": true
},
{
"name": "browser",
"label": "Browser",
"sortable": true,
"type": "text",
"toggled": true
},
{
"name": "platform",
"label": "Platform(s)",
"sortable": true,
"type": "text",
"toggled": true
},
{
"name": "version",
"label": "Engine version",
"quickEdit": true,
"type": "text",
"toggled": true
},
{
"name": "grade",
"label": "CSS grade",
"quickEdit": {
"mode": "inline",
"type": "select",
"options": [
"A",
"B",
"C",
"D",
"X"
],
"saveImmediately": true
},
"type": "text",
"toggled": true
},
{
"type": "operation",
"label": "操作",
"width": 100,
"buttons": [
{
"type": "button",
"icon": "fa fa-eye",
"actionType": "dialog",
"dialog": {
"title": "查看",
"body": {
"type": "form",
"body": [
{
"type": "static",
"name": "engine",
"label": "Engine"
},
{
"type": "divider"
},
{
"type": "static",
"name": "browser",
"label": "Browser"
},
{
"type": "divider"
},
{
"type": "static",
"name": "platform",
"label": "Platform(s)"
},
{
"type": "divider"
},
{
"type": "static",
"name": "version",
"label": "Engine version"
},
{
"type": "divider"
},
{
"type": "static",
"name": "grade",
"label": "CSS grade"
},
{
"type": "divider"
},
{
"type": "html",
"html": "<p>添加其他 <span>Html 片段</span> 需要支持变量替换todo.</p>"
}
]
}
}
},
{
"type": "button",
"icon": "fa fa-pencil",
"actionType": "dialog",
"dialog": {
"position": "left",
"size": "lg",
"title": "编辑",
"body": {
"type": "form",
"name": "sample-edit-form",
"api": "/api/mock2/sample/$id",
"body": [
{
"type": "input-text",
"name": "engine",
"label": "Engine",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "browser",
"label": "Browser",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "platform",
"label": "Platform(s)",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "version",
"label": "Engine version"
},
{
"type": "divider"
},
{
"type": "select",
"name": "grade",
"label": "CSS grade",
"options": [
"A",
"B",
"C",
"D",
"X"
]
}
]
}
}
},
{
"type": "button",
"icon": "fa fa-times text-danger",
"actionType": "ajax",
"confirmText": "您确认要删除?",
"api": "delete:/api/mock2/sample/$id"
}
],
"toggled": true
}
]
}
}
]
}
```
内嵌模式下
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"debug": true,
"body": [
{
"type": "picker",
"overflowConfig": {
"maxTagCount": 3,
"overflowTagPopoverInCRUD": {
"placement": "top"
}
},
"embed": true,
"name": "maxTagCount2",
"joinValues": true,
"valueField": "id",
"labelField": "engine",
"label": "多选",
"source": "/api/mock2/sample",
"size": "lg",
"value": "1,2,3,4,5,6,7",
"multiple": true,
"pickerSchema": {
"mode": "table",
"name": "thelist",
"quickSaveApi": "/api/mock2/sample/bulkUpdate",
"quickSaveItemApi": "/api/mock2/sample/$id",
"draggable": true,
"headerToolbar": {
"wrapWithPanel": false,
"type": "form",
"className": "text-right",
"target": "thelist",
"mode": "inline",
"body": [
{
"type": "input-text",
"name": "keywords",
"addOn": {
"type": "submit",
"label": "搜索",
"level": "primary",
"icon": "fa fa-search pull-left"
}
}
]
},
"footerToolbar": [
"statistics",
{
"type": "pagination",
"showPageInput": true,
"layout": "perPage,pager,go"
}
],
"columns": [
{
"name": "engine",
"label": "Rendering engine",
"sortable": true,
"searchable": true,
"type": "text",
"toggled": true
},
{
"name": "browser",
"label": "Browser",
"sortable": true,
"type": "text",
"toggled": true
},
{
"name": "platform",
"label": "Platform(s)",
"sortable": true,
"type": "text",
"toggled": true
},
{
"name": "version",
"label": "Engine version",
"quickEdit": true,
"type": "text",
"toggled": true
},
{
"name": "grade",
"label": "CSS grade",
"quickEdit": {
"mode": "inline",
"type": "select",
"options": [
"A",
"B",
"C",
"D",
"X"
],
"saveImmediately": true
},
"type": "text",
"toggled": true
},
{
"type": "operation",
"label": "操作",
"width": 100,
"buttons": [
{
"type": "button",
"icon": "fa fa-eye",
"actionType": "dialog",
"dialog": {
"title": "查看",
"body": {
"type": "form",
"body": [
{
"type": "static",
"name": "engine",
"label": "Engine"
},
{
"type": "divider"
},
{
"type": "static",
"name": "browser",
"label": "Browser"
},
{
"type": "divider"
},
{
"type": "static",
"name": "platform",
"label": "Platform(s)"
},
{
"type": "divider"
},
{
"type": "static",
"name": "version",
"label": "Engine version"
},
{
"type": "divider"
},
{
"type": "static",
"name": "grade",
"label": "CSS grade"
},
{
"type": "divider"
},
{
"type": "html",
"html": "<p>添加其他 <span>Html 片段</span> 需要支持变量替换todo.</p>"
}
]
}
}
},
{
"type": "button",
"icon": "fa fa-pencil",
"actionType": "dialog",
"dialog": {
"position": "left",
"size": "lg",
"title": "编辑",
"body": {
"type": "form",
"name": "sample-edit-form",
"api": "/api/mock2/sample/$id",
"body": [
{
"type": "input-text",
"name": "engine",
"label": "Engine",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "browser",
"label": "Browser",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "platform",
"label": "Platform(s)",
"required": true
},
{
"type": "divider"
},
{
"type": "input-text",
"name": "version",
"label": "Engine version"
},
{
"type": "divider"
},
{
"type": "select",
"name": "grade",
"label": "CSS grade",
"options": [
"A",
"B",
"C",
"D",
"X"
]
}
]
}
}
},
{
"type": "button",
"icon": "fa fa-times text-danger",
"actionType": "ajax",
"confirmText": "您确认要删除?",
"api": "delete:/api/mock2/sample/$id"
}
],
"toggled": true
}
]
}
}
]
}
```
## 属性表
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------- |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| ------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------- | --- |
| options | `Array<object>`或`Array<string>` | | [选项组](./options#%E9%9D%99%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-options) |
| source | `string`或 [API](../../../docs/types/api) 或 [数据映射](../../../docs/concepts/data-mapping) | | [动态选项组](./options#%E5%8A%A8%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-source) |
| multiple | `boolean` | | 是否为多选。 |
@ -703,6 +1211,17 @@ order: 35
| modalMode | `string` | `"dialog"` | 设置 `dialog` 或者 `drawer`,用来配置弹出方式。 |
| pickerSchema | `string` | `{mode: 'list', listItem: {title: '${label}'}}` | 即用 List 类型的渲染,来展示列表信息。更多配置参考 [CRUD](../crud) |
| embed | `boolean` | `false` | 是否使用内嵌模式 |
| overflowConfig | `OverflowConfig` | 参考[OverflowConfig](./#overflowconfig) | 开启最大标签展示数量的相关配置 | `3.4.0` |
### OverflowConfig
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------- |
| maxTagCount | `number` | `-1` | 标签的最大展示数量,超出数量后以收纳浮层的方式展示,仅在多选模式开启后生效,默认为`-1` 不开启 | `3.4.0` |
| displayPosition | `('select' \| 'crud')[]` | `['select', 'crud']` | 收纳标签生效的位置,未开启内嵌模式默认为选择器, 开启后默认为选择器和CRUD 顶部,可选值为`'select'`(选择器)、`'crud'`(增删改查) | `3.4.0` |
| overflowTagPopover | `TooltipObject` | `{"placement": "top", "trigger": "hover", "showArrow": false, "offset": [0, -10]}` | 选择器内收纳标签的Popover配置详细配置参考[Tooltip](../tooltip#属性表) | `3.4.0` |
| overflowTagPopoverInCRUD | `TooltipObject` | `{"placement": "bottom", "trigger": "hover", "showArrow": false, "offset": [0, 10]}` | CRUD顶部内收纳标签的Popover配置详细配置参考[Tooltip](../tooltip#属性表) | `3.4.0` |
## 事件表

View File

@ -40,3 +40,40 @@ export function numberFormatter(num: number | string, precision: number = 0) {
}
return ZERO.toFixed(precision);
}
/**
*
*
* @param num
* @param options startendleftright
* @param options.start
* @param options.end
* @param options.left 'inclusive' 'inclusive'() 'exclusive'()
* @param options.right 'inclusive' 'inclusive'() 'exclusive'()
* @returns true false
*/
export function isIntegerInRange(
num: number,
options: {
start: number;
end: number;
left: 'inclusive' | 'exclusive';
right: 'inclusive' | 'exclusive';
}
) {
const {start, end, left = 'inclusive', right = 'inclusive'} = options || {};
if (num == null || typeof num !== 'number' || !Number.isSafeInteger(num)) {
return false;
}
if (left === 'exclusive' && right === 'exclusive') {
return num > start && num < end;
} else if (left === 'inclusive' && right === 'exclusive') {
return num >= start && num < end;
} else if (left === 'exclusive' && right === 'inclusive') {
return num > start && num <= end;
} else {
return num >= start && num <= end;
}
}

View File

@ -3620,6 +3620,8 @@
--Pick-base-value-icon-color: var(--colors-other-5);
--Picker-iconColor: var(--Pick-base-icon-color);
--Picker-onHover-iconColor: var(--icon-onHover-color);
--Picker-tag-height: #{px2rem(24px)};
--Picker-tag-marginBottom: var(--select-multiple-marginBottom);
--Pick-status-hover-top-border-color: var(--colors-other-5);
--Pick-status-hover-top-border-width: var(--borders-width-2);
--Pick-status-hover-top-border-style: var(--borders-style-2);

View File

@ -616,3 +616,63 @@
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin tag-item($component-prefix) {
.#{$ns}#{$component-prefix}-value {
cursor: pointer;
user-select: none;
white-space: nowrap;
vertical-align: middle;
line-height: calc(
var(--Form-input-lineHeight) * var(--Form-input-fontSize) - #{px2rem(2px)}
);
display: inline-block;
font-size: var(--Pick-base-value-fontSize);
color: var(--Pick-base-value-color);
font-weight: var(--Pick-base-value-fontWeight);
background: var(--Pick-base-value-bgColor);
border-width: var(--Pick-base-value-top-border-width)
var(--Pick-base-value-right-border-width)
var(--Pick-base-value-bottom-border-width)
var(--Pick-base-value-left-border-width);
border-style: var(--Pick-base-value-top-border-style)
var(--Pick-base-value-right-border-style)
var(--Pick-base-value-bottom-border-style)
var(--Pick-base-value-left-border-style);
border-color: var(--Pick-base-value-top-border-color)
var(--Pick-base-value-right-border-color)
var(--Pick-base-value-bottom-border-color)
var(--Pick-base-value-left-border-color);
border-radius: var(--Pick-base-top-left-border-radius)
var(--Pick-base-top-right-border-radius)
var(--Pick-base-bottom-right-border-radius)
var(--Pick-base-bottom-left-border-radius);
margin-right: var(--gap-xs);
margin-bottom: var(--gap-xs);
margin-top: var(--gap-xs);
&:hover {
background: var(--Form-selectValue-onHover-bg);
}
&.is-disabled {
pointer-events: none;
opacity: var(--Button-onDisabled-opacity);
}
}
.#{$ns}#{$component-prefix}-valueIcon {
color: var(--Pick-base-value-icon-color);
cursor: pointer;
border-right: px2rem(1px) solid var(--Form-selectValue-borderColor);
padding: 1px 5px;
&:hover {
background: var(--Pick-base-value-hover-icon-color);
}
}
.#{$ns}#{$component-prefix}-valueLabel {
padding: 0 var(--gap-xs);
}
}

View File

@ -9,6 +9,25 @@
&-selection {
margin-bottom: var(--gap-base);
&-overflow {
&-wrapper {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: flex-start;
overflow-x: hidden;
overflow-y: auto;
height: calc(
(var(--Picker-tag-height) + var(--Picker-tag-marginBottom) * 4) * 3
);
max-height: calc(
(var(--Picker-tag-height) + var(--Picker-tag-marginBottom)) * 5
);
@include tag-item(Crud);
}
}
}
&-selectionLabel {
@ -17,61 +36,8 @@
margin-top: var(--gap-xs);
}
&-value {
cursor: pointer;
vertical-align: middle;
user-select: none;
line-height: calc(
var(--Form-input-lineHeight) * var(--Form-input-fontSize) - #{px2rem(2px)}
);
display: inline-block;
font-size: var(--Pick-base-value-fontSize);
color: var(--Pick-base-value-color);
font-weight: var(--Pick-base-value-fontWeight);
background: var(--Pick-base-value-bgColor);
border-width: var(--Pick-base-value-top-border-width)
var(--Pick-base-value-right-border-width)
var(--Pick-base-value-bottom-border-width)
var(--Pick-base-value-left-border-width);
border-style: var(--Pick-base-value-top-border-style)
var(--Pick-base-value-right-border-style)
var(--Pick-base-value-bottom-border-style)
var(--Pick-base-value-left-border-style);
border-color: var(--Pick-base-value-top-border-color)
var(--Pick-base-value-right-border-color)
var(--Pick-base-value-bottom-border-color)
var(--Pick-base-value-left-border-color);
border-radius: var(--Pick-base-top-left-border-radius)
var(--Pick-base-top-right-border-radius)
var(--Pick-base-bottom-right-border-radius)
var(--Pick-base-bottom-left-border-radius);
margin-right: var(--gap-xs);
margin-top: var(--gap-xs);
&:hover {
background: var(--Form-selectValue-onHover-bg);
}
&.is-disabled {
pointer-events: none;
opacity: var(--Button-onDisabled-opacity);
}
}
&-valueIcon {
color: var(--Pick-base-value-icon-color);
cursor: pointer;
border-right: px2rem(1px) solid var(--Form-selectValue-borderColor);
padding: 1px 5px;
&:hover {
background: var(--Form-selectValue-onHover-bg);
}
}
&-valueLabel {
padding: 0 var(--gap-xs);
}
/* tag 样式 */
@include tag-item(Crud);
&-selectionClear {
display: inline-block;

View File

@ -115,53 +115,8 @@
line-height: 1;
}
.#{$ns}Picker-value {
cursor: pointer;
user-select: none;
white-space: nowrap;
vertical-align: middle;
line-height: calc(
var(--Form-input-lineHeight) * var(--Form-input-fontSize) - #{px2rem(2px)}
);
display: inline-block;
font-size: var(--Pick-base-value-fontSize);
color: var(--Pick-base-value-color);
font-weight: var(--Pick-base-value-fontWeight);
background: var(--Pick-base-value-bgColor);
border-width: var(--Pick-base-value-top-border-width)
var(--Pick-base-value-right-border-width)
var(--Pick-base-value-bottom-border-width)
var(--Pick-base-value-left-border-width);
border-style: var(--Pick-base-value-top-border-style)
var(--Pick-base-value-right-border-style)
var(--Pick-base-value-bottom-border-style)
var(--Pick-base-value-left-border-style);
border-color: var(--Pick-base-value-top-border-color)
var(--Pick-base-value-right-border-color)
var(--Pick-base-value-bottom-border-color)
var(--Pick-base-value-left-border-color);
border-radius: var(--Pick-base-top-left-border-radius)
var(--Pick-base-top-right-border-radius)
var(--Pick-base-bottom-right-border-radius)
var(--Pick-base-bottom-left-border-radius);
margin-right: var(--gap-xs);
margin-bottom: var(--gap-xs);
}
.#{$ns}Picker-valueIcon {
color: var(--Pick-base-value-icon-color);
cursor: pointer;
border-right: px2rem(1px) solid var(--Form-selectValue-borderColor);
padding: 1px 5px;
&:hover {
background: var(--Pick-base-value-hover-icon-color);
}
}
.#{$ns}Picker-valueLabel {
padding: 0 var(--gap-xs);
}
/* tag 样式 */
@include tag-item(Picker);
&-btn {
cursor: pointer;
@ -198,6 +153,25 @@
top: 0;
}
}
&-overflow {
&-wrapper {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: flex-start;
overflow-x: hidden;
overflow-y: auto;
height: calc(
(var(--Picker-tag-height) + var(--Picker-tag-marginBottom) * 4) * 3
);
max-height: calc(
(var(--Picker-tag-height) + var(--Picker-tag-marginBottom)) * 5
);
@include tag-item(Picker);
}
}
}
.#{$ns}PickerControl.is-inline {

View File

@ -12,8 +12,7 @@ import {
render,
fireEvent,
cleanup,
waitFor,
getByText
screen
} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
@ -25,7 +24,7 @@ afterEach(() => {
clearStoresCache();
});
test('Renderer:Picker base', async () => {
test('1. Renderer:Picker base', async () => {
const {container, rerender, getByText, getByPlaceholderText, baseElement} =
render(
amisRender({
@ -73,7 +72,7 @@ test('Renderer:Picker base', async () => {
expect(container).toMatchSnapshot();
});
test('Renderer:Picker with pickerSchema & valueField & labelField & multiple & value & size', async () => {
test('2. Renderer:Picker with pickerSchema & valueField & labelField & multiple & value & size', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
@ -168,7 +167,7 @@ test('Renderer:Picker with pickerSchema & valueField & labelField & multiple & v
});
});
test('Renderer:Picker with embed', async () => {
test('3. Renderer:Picker with embed', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
@ -241,7 +240,7 @@ test('Renderer:Picker with embed', async () => {
).toBeInTheDocument();
});
test('Renderer:Picker with drawer modalMode', async () => {
test('4. Renderer:Picker with drawer modalMode', async () => {
const {container, rerender, getByText, getByPlaceholderText, baseElement} =
render(
amisRender({
@ -274,3 +273,72 @@ test('Renderer:Picker with drawer modalMode', async () => {
baseElement.querySelector('.cxd-Drawer .cxd-Crud')!
).toBeInTheDocument();
});
describe('5. Renderer:Picker with overflowConfig', () => {
test('5-1. Renderer:Picker select', async () => {
const {container, rerender, getByText, getByPlaceholderText, baseElement} =
render(
amisRender({
type: 'picker',
name: 'picker',
label: 'picker',
modalMode: 'dialog',
placeholder: 'picker-placeholder',
multiple: true,
overflowConfig: {
maxTagCount: 2
},
value: 'a,b,c',
options: [
{label: 'A', value: 'a'},
{label: 'B', value: 'b'},
{label: 'C', value: 'c'},
{label: 'D', value: 'd'}
]
})
);
await wait(500);
const tags = container.querySelector('.cxd-Picker-values');
expect(tags).toBeInTheDocument();
/** tag 元素数量正确 */
expect(tags?.childElementCount).toEqual(3);
/** 收纳标签文案正确 */
expect(tags?.lastElementChild).toHaveTextContent('+ 1 ...');
});
test('5-2. Renderer:Picker embeded', async () => {
const {container, rerender, getByText, getByPlaceholderText, baseElement} =
render(
amisRender({
type: 'picker',
name: 'picker',
label: 'picker',
modalMode: 'dialog',
placeholder: 'picker-placeholder',
embed: true,
multiple: true,
overflowConfig: {
maxTagCount: 2
},
value: 'a,b,c',
options: [
{label: 'A', value: 'a'},
{label: 'B', value: 'b'},
{label: 'C', value: 'c'},
{label: 'D', value: 'd'}
]
})
);
await wait(500);
const tags = container.querySelectorAll('.cxd-Crud-selection .cxd-Crud-value');
/** tag 元素数量正确 */
expect(tags?.length).toEqual(3);
/** 收纳标签文案正确 */
expect(tags[tags?.length - 1]).toHaveTextContent('+ 1 ...');
});
});

View File

@ -2721,8 +2721,6 @@ exports[`13. enderer: crud keepItemSelectionOnPageChange & maxKeepItemSelectionL
>
<span
class="cxd-Crud-valueIcon"
data-position="bottom"
data-tooltip="删除"
>
×
</span>
@ -2741,8 +2739,6 @@ exports[`13. enderer: crud keepItemSelectionOnPageChange & maxKeepItemSelectionL
>
<span
class="cxd-Crud-valueIcon"
data-position="bottom"
data-tooltip="删除"
>
×
</span>
@ -2761,8 +2757,6 @@ exports[`13. enderer: crud keepItemSelectionOnPageChange & maxKeepItemSelectionL
>
<span
class="cxd-Crud-valueIcon"
data-position="bottom"
data-tooltip="删除"
>
×
</span>
@ -2781,8 +2775,6 @@ exports[`13. enderer: crud keepItemSelectionOnPageChange & maxKeepItemSelectionL
>
<span
class="cxd-Crud-valueIcon"
data-position="bottom"
data-tooltip="删除"
>
×
</span>

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renderer:Picker base 1`] = `
exports[`1. Renderer:Picker base 1`] = `
<body
class="is-modalOpened"
style="width: calc(100% - 0px);"
@ -222,7 +222,7 @@ exports[`Renderer:Picker base 1`] = `
</body>
`;
exports[`Renderer:Picker base 2`] = `
exports[`1. Renderer:Picker base 2`] = `
<div>
<div
class="cxd-Form-item cxd-Form-item--normal"
@ -261,8 +261,6 @@ exports[`Renderer:Picker base 2`] = `
>
<span
class="cxd-Picker-valueIcon"
data-position="bottom"
data-tooltip="删除"
>
×
</span>

View File

@ -15,10 +15,11 @@ import {
getVariable,
qsstringify,
qsparse,
isArrayChildrenModified
isArrayChildrenModified,
isIntegerInRange
} from 'amis-core';
import {ScopedContext, IScopedContext} from 'amis-core';
import {Button, SpinnerExtraProps} from 'amis-ui';
import {Button, SpinnerExtraProps, TooltipWrapper} from 'amis-ui';
import {Select} from 'amis-ui';
import {getExprProperties} from 'amis-core';
import pick from 'lodash/pick';
@ -441,7 +442,9 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'onSave',
'onQuery',
'formStore',
'autoFillHeight'
'autoFillHeight',
'maxTagCount',
'overflowTagPopover'
];
static defaultProps = {
toolbarInline: true,
@ -2221,9 +2224,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
return this.renderToolbar(footerToolbar, 0, childProps, toolbarRenderer);
}
renderSelection(): React.ReactNode {
renderTag(item: any, index: number) {
const {
store,
classnames: cx,
labelField,
labelTpl,
@ -2233,20 +2235,9 @@ export default class CRUD extends React.Component<CRUDProps, any> {
env
} = this.props;
if (!store.selectedItems.length) {
return null;
}
return (
<div className={cx('Crud-selection')}>
<div className={cx('Crud-selectionLabel')}>
{__('CRUD.selected', {total: store.selectedItems.length})}
</div>
{store.selectedItems.map((item, index) => (
<div key={index} className={cx(`Crud-value`)}>
<span
data-tooltip={__('delete')}
data-position="bottom"
className={cx('Crud-valueIcon')}
onClick={this.unSelectItem.bind(this, item, index)}
>
@ -2254,17 +2245,107 @@ export default class CRUD extends React.Component<CRUDProps, any> {
</span>
<span className={cx('Crud-valueLabel')}>
{labelTpl ? (
<Html
html={filter(labelTpl, item)}
filterHtml={env.filterHtml}
/>
<Html html={filter(labelTpl, item)} filterHtml={env.filterHtml} />
) : (
getVariable(item, labelField || 'label') ||
getVariable(item, valueField || primaryField || 'id')
)}
</span>
</div>
))}
);
}
renderSelection(): React.ReactNode {
const {
store,
classPrefix: ns,
classnames: cx,
labelField,
labelTpl,
primaryField,
valueField,
translate: __,
env,
popOverContainer,
multiple,
maxTagCount,
overflowTagPopover
} = this.props;
if (!store.selectedItems.length) {
return null;
}
const totalCount = store.selectedItems.length;
let tags: any[] = store.selectedItems;
const enableOverflow =
multiple !== false &&
isIntegerInRange(maxTagCount, {
start: 0,
end: totalCount,
left: 'inclusive',
right: 'exclusive'
});
if (enableOverflow) {
tags = [
...store.selectedItems.slice(0, maxTagCount),
{label: `+ ${totalCount - maxTagCount} ...`, value: '__overflow_tag__'}
];
}
return (
<div className={cx('Crud-selection')}>
<div className={cx('Crud-selectionLabel')}>
{__('CRUD.selected', {total: store.selectedItems.length})}
</div>
{tags.map((item, index) => {
if (enableOverflow && index === maxTagCount) {
return (
<TooltipWrapper
key={index}
container={popOverContainer}
tooltip={{
placement: 'top',
trigger: 'hover',
showArrow: false,
offset: [0, -10],
tooltipClassName: cx(
'Crud-selection-overflow',
overflowTagPopover?.tooltipClassName
),
title: __('已选项'),
...omit(overflowTagPopover, [
'children',
'content',
'tooltipClassName'
]),
children: () => {
return (
<div
className={cx(`${ns}Crud-selection-overflow-wrapper`)}
>
{store.selectedItems
.slice(maxTagCount, totalCount)
.map((overflowItem, rawIndex) => {
const key = rawIndex + maxTagCount;
return this.renderTag(overflowItem, key);
})}
</div>
);
}
}}
>
<div key={index} className={cx(`Crud-value`)}>
<span className={cx('Crud-valueLabel')}>{item.label}</span>
</div>
</TooltipWrapper>
);
}
return this.renderTag(item, index);
})}
<a onClick={this.clearSelection} className={cx('Crud-selectionClear')}>
{__('clear')}
</a>

View File

@ -4,6 +4,7 @@ import omit from 'lodash/omit';
import find from 'lodash/find';
import isEqual from 'lodash/isEqual';
import findIndex from 'lodash/findIndex';
import merge from 'lodash/merge';
import {
OptionsControl,
OptionsControlProps,
@ -20,13 +21,14 @@ import {
resolveVariableAndFilter,
isApiOutdated,
isEffectiveApi,
resolveEventData
resolveEventData,
isIntegerInRange
} from 'amis-core';
import {Html, Icon} from 'amis-ui';
import {isMobile} from 'amis-core';
import {Html, Icon, TooltipWrapper} from 'amis-ui';
import {FormOptionsSchema, SchemaTpl} from '../../Schema';
import intersectionWith from 'lodash/intersectionWith';
import {PopUp} from 'amis-ui';
import type {TooltipWrapperSchema} from '../TooltipWrapper';
import type {Option} from 'amis-core';
/**
* Picker
@ -70,6 +72,31 @@ export interface PickerControlSchema extends FormOptionsSchema {
*
*/
embed?: boolean;
/**
*
*/
overflowConfig: {
/**
*
*/
maxTagCount?: number;
/**
* , + 'select'()'crud'()
*/
displayPosition?: ('select' | 'crud')[];
/**
* Popover配置
*/
overflowTagPopover?: TooltipWrapperSchema;
/**
* CRUD顶部内收纳标签的Popover配置
*/
overflowTagPopoverInCRUD?: TooltipWrapperSchema;
};
}
export interface PickerProps extends OptionsControlProps {
@ -115,7 +142,24 @@ export default class PickerControl extends React.PureComponent<
title: '${label|raw}'
}
},
embed: false
embed: false,
overflowConfig: {
/** 默认值为-1不开启 */
maxTagCount: -1,
displayPosition: ['select', 'crud'],
overflowTagPopover: {
placement: 'top',
trigger: 'hover',
showArrow: false,
offset: [0, -10]
},
overflowTagPopoverInCRUD: {
placement: 'bottom',
trigger: 'hover',
showArrow: false,
offset: [0, 10]
}
}
};
state: PickerState = {
@ -398,10 +442,15 @@ export default class PickerControl extends React.PureComponent<
onChange(resetValue !== void 0 ? resetValue : '');
}
renderValues() {
getOverflowConfig() {
const {overflowConfig} = this.props;
return merge(PickerControl.defaultProps.overflowConfig, overflowConfig);
}
renderTag(item: Option, index: number) {
const {
classPrefix: ns,
selectedOptions,
labelField,
labelTpl,
translate: __,
@ -410,8 +459,6 @@ export default class PickerControl extends React.PureComponent<
} = this.props;
return (
<div className={`${ns}Picker-values`}>
{selectedOptions.map((item, index) => (
<div
key={index}
className={cx(`${ns}Picker-value`, {
@ -419,8 +466,6 @@ export default class PickerControl extends React.PureComponent<
})}
>
<span
data-tooltip={__('delete')}
data-position="bottom"
className={`${ns}Picker-valueIcon`}
onClick={e => {
e.stopPropagation();
@ -437,10 +482,7 @@ export default class PickerControl extends React.PureComponent<
}}
>
{labelTpl ? (
<Html
html={filter(labelTpl, item)}
filterHtml={env.filterHtml}
/>
<Html html={filter(labelTpl, item)} filterHtml={env.filterHtml} />
) : (
`${
getVariable(item, labelField || 'label') ||
@ -449,7 +491,86 @@ export default class PickerControl extends React.PureComponent<
)}
</span>
</div>
))}
);
}
renderValues() {
const {
classPrefix: ns,
selectedOptions,
translate: __,
disabled,
multiple,
popOverContainer
} = this.props;
const {maxTagCount, overflowTagPopover} = this.getOverflowConfig();
const totalCount = selectedOptions.length;
let tags = selectedOptions;
const enableOverflow =
multiple !== false &&
isIntegerInRange(maxTagCount, {
start: 0,
end: totalCount,
left: 'inclusive',
right: 'exclusive'
});
/** 多选且开启限制标签数量 */
if (enableOverflow) {
tags = [
...selectedOptions.slice(0, maxTagCount),
{label: `+ ${totalCount - maxTagCount} ...`, value: '__overflow_tag__'}
];
}
return (
<div className={`${ns}Picker-values`}>
{tags.map((item, index) => {
if (enableOverflow && index === maxTagCount) {
return (
<TooltipWrapper
key={index}
container={popOverContainer}
tooltip={{
tooltipClassName: cx(
'Picker-overflow',
overflowTagPopover?.tooltipClassName
),
title: __('已选项'),
...omit(overflowTagPopover, [
'children',
'content',
'tooltipClassName'
]),
children: () => {
return (
<div className={cx(`${ns}Picker-overflow-wrapper`)}>
{selectedOptions
.slice(maxTagCount, totalCount)
.map((overflowItem, rawIndex) => {
const key = rawIndex + maxTagCount;
return this.renderTag(overflowItem, key);
})}
</div>
);
}
}}
>
<div
key={index}
className={cx(`${ns}Picker-value`, {
'is-disabled': disabled
})}
>
<span className={`${ns}Picker-valueLabel`}>{item.label}</span>
</div>
</TooltipWrapper>
);
}
return this.renderTag(item, index);
})}
</div>
);
}
@ -466,6 +587,8 @@ export default class PickerControl extends React.PureComponent<
source,
strictMode
} = this.props;
const {maxTagCount, overflowTagPopoverInCRUD, displayPosition} =
this.getOverflowConfig();
return render('modal-body', this.state.schema, {
value: selectedOptions,
@ -512,7 +635,11 @@ export default class PickerControl extends React.PureComponent<
}
: undefined,
ref: this.crudRef,
popOverContainer
popOverContainer,
...(embed ||
(Array.isArray(displayPosition) && displayPosition.includes('crud'))
? {maxTagCount, overflowTagPopover: overflowTagPopoverInCRUD}
: {})
}) as JSX.Element;
}
render() {