暂存表单修改

This commit is contained in:
zhangxulong 2023-04-24 18:19:32 +08:00
parent 27a4083a1c
commit d68cda753b
103 changed files with 2587 additions and 443 deletions

View File

@ -678,7 +678,7 @@ order: 31
} }
], ],
searchable: true, searchable: true,
multiple: false, multiple: true,
joinValues: true, joinValues: true,
clearable: true clearable: true
} }

View File

@ -15,6 +15,7 @@ order: 68
```schema: scope="body" ```schema: scope="body"
{ {
"type": "tabs", "type": "tabs",
"swipeable": true,
"tabs": [ "tabs": [
{ {
"title": "Tab 1", "title": "Tab 1",
@ -23,6 +24,54 @@ order: 68
{ {
"title": "Tab 2", "title": "Tab 2",
"tab": "Content 2" "tab": "Content 2"
},
{
"title": "Tab 3",
"tab": "Content 2"
},
{
"title": "Tab 4",
"tab": "Content 2"
},
{
"title": "Tab 5",
"tab": "Content 2"
},
{
"title": "Tab 6",
"tab": "Content 2"
},
{
"title": "Tab 7",
"tab": "Content 2"
},
{
"title": "Tab 8",
"tab": "Content 2"
},
{
"title": "Tab 9",
"tab": "Content 2"
},
{
"title": "Tab 10",
"tab": "Content 2"
},
{
"title": "Tab 11",
"tab": "Content 2"
},
{
"title": "Tab 12",
"tab": "Content 2"
},
{
"title": "Tab 13",
"tab": "Content 2"
},
{
"title": "Tab 14",
"tab": "Content 2"
} }
] ]
} }
@ -789,6 +838,7 @@ order: 68
| sidePosition | `left` / `right` | `left` | `sidebar` 模式下,标签栏位置 | | sidePosition | `left` / `right` | `left` | `sidebar` 模式下,标签栏位置 |
| collapseOnExceed | `number` | | 当 tabs 超出多少个时开始折叠 | | collapseOnExceed | `number` | | 当 tabs 超出多少个时开始折叠 |
| collapseBtnLabel | `string` | `more` | 用来设置折叠按钮的文字 | | collapseBtnLabel | `string` | `more` | 用来设置折叠按钮的文字 |
| swipeable | `boolean` | false | 是否开启手势滑动切换(移动端生效) |
## 事件表 ## 事件表

View File

@ -1,24 +1,18 @@
export default { export default {
type: 'page', type: 'page',
title: '表单页面', body: {
body: [
{
type: 'form', type: 'form',
mode: 'horizontal',
api: '/api/mock2/form/saveForm',
body: [ body: [
{ {
label: 'Name', label: '多选',
type: 'input-text', type: 'select',
name: 'name' name: 'select2',
}, searchable: true,
{ checkAll: true,
label: 'Email', multiple: true,
type: 'input-email', clearable: true,
placeholder: '请输入邮箱地址', source: '/api/mock2/form/getOptions'
name: 'email'
} }
] ]
} }
]
}; };

View File

@ -1676,6 +1676,8 @@
--InputRange-track-onActive-bg: var(--colors-brand-5); --InputRange-track-onActive-bg: var(--colors-brand-5);
--InputRange-handle-height: var(--sizes-size-9); --InputRange-handle-height: var(--sizes-size-9);
--InputRange-handle-width: var(--sizes-size-9); --InputRange-handle-width: var(--sizes-size-9);
--InputRange-handle-mobile-height: var(--sizes-base-11);
--InputRange-handle-mobile-width: var(--sizes-base-11);
--InputRange-handle-bg: var(--colors-neutral-fill-11); --InputRange-handle-bg: var(--colors-neutral-fill-11);
--InputRange-handle-top-border-color: var(--colors-brand-5); --InputRange-handle-top-border-color: var(--colors-brand-5);
--InputRange-handle-top-border-width: 0.0625rem; --InputRange-handle-top-border-width: 0.0625rem;
@ -1730,6 +1732,7 @@
); );
--InputRange-label-position-bottom: calc(100% + 8px); --InputRange-label-position-bottom: calc(100% + 8px);
--InputRange-input-width: var(--sizes-base-40); --InputRange-input-width: var(--sizes-base-40);
--InputRange-input-mobile-width: var(--sizes-base-20);
--InputRange-input-marginTop: var(--sizes-size-0); --InputRange-input-marginTop: var(--sizes-size-0);
--InputRange-input-marginBottom: var(--sizes-size-0); --InputRange-input-marginBottom: var(--sizes-size-0);
--InputRange-input-marginLeft: var(--sizes-size-5); --InputRange-input-marginLeft: var(--sizes-size-5);
@ -2821,6 +2824,7 @@
--select-tree-active-bg-color: var(--colors-brand-10); --select-tree-active-bg-color: var(--colors-brand-10);
--Form-select-bg: var(--select-base-default-bg-color); --Form-select-bg: var(--select-base-default-bg-color);
--Form-select-mobile-icon-check-color: var(--colors-brand-5);
--Form-select-height: var(--Form-select-outer-top); --Form-select-height: var(--Form-select-outer-top);
--Form-select-borderColor: var(--select-base-default-top-border-color) --Form-select-borderColor: var(--select-base-default-top-border-color)
var(--select-base-default-right-border-color) var(--select-base-default-right-border-color)

View File

@ -527,6 +527,10 @@ $Table-strip-bg: transparent;
--ListMenu-item-bg: var(--colors-neutral-fill-11); --ListMenu-item-bg: var(--colors-neutral-fill-11);
--ListMenu-item-color: var(--colors-neutral-text-2); --ListMenu-item-color: var(--colors-neutral-text-2);
--ListMenu-item-height: var(--sizes-base-15); --ListMenu-item-height: var(--sizes-base-15);
--ListMenu-item-mobile-margin: #{px2rem(5px)};
--ListMenu-item-mobile-width: #{px2rem(90px)};
--ListMenu-item-mobile-bg: #f5f5f5;
--ListMenu-item-mobile-active-bg: #e7f1ff;
--Log-bg: #222; --Log-bg: #222;
--Log-padding: var(--gap-sm) 0; --Log-padding: var(--gap-sm) 0;
@ -768,6 +772,11 @@ $Table-strip-bg: transparent;
--UserSelect--border-color: #f7f7f9; --UserSelect--border-color: #f7f7f9;
--UserSelect--content-bg: #f5f7f8; --UserSelect--content-bg: #f5f7f8;
--UserSelect--bread-color: #5e626a; --UserSelect--bread-color: #5e626a;
--Cascader-border-color: #f7f7f9;
--Cascader-border-active-bg-color: #f7f7f9;
--Cascader-option-disable-color: #b8babf;
// tag // tag
--Tag-content-fontSize: var(--fontSizeSm); --Tag-content-fontSize: var(--fontSizeSm);
--Tag-height: #{px2rem(24px)}; --Tag-height: #{px2rem(24px)};

View File

@ -123,8 +123,15 @@
border-width: 0; border-width: 0;
&--time { &--time {
height: fit-content; height: px2rem(360px);
max-height: 90vh; max-height: 90vh;
.#{$ns}PopUp-content {
overflow: hidden;
}
}
&--years {
height: px2rem(360px);
} }
} }
@ -246,6 +253,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: px2rem(4px); border-radius: px2rem(4px);
padding: 0 px2rem(10px);
} }
} }
@ -282,11 +290,11 @@
line-height: px2rem(48px); line-height: px2rem(48px);
} }
.#{$ns}DateRangePicker-rangers { .#{$ns}DateRangePicker-rangers {
position: absolute; padding-left: revert;
white-space: nowrap; white-space: nowrap;
.#{$ns}DateRangePicker-ranger { line-height: inherit;
margin: 0 px2rem(25px); display: flex;
} justify-content: space-between;
} }
.#{$ns}DatePicker-shortcuts { .#{$ns}DatePicker-shortcuts {
width: auto; width: auto;

View File

@ -15,6 +15,9 @@
height: px2rem(260px); height: px2rem(260px);
overflow-y: auto; overflow-y: auto;
display: inline-block; display: inline-block;
padding-left: px2rem(10px);
border: 1px solid var(--Cascader-border-color);
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
@ -52,13 +55,18 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: px2rem(6px) 0; padding: px2rem(6px) 0 px2rem(6px) px2rem(10px);
font-size: var(--fontSizeMd); font-size: var(--fontSizeMd);
line-height: var(--Cascader-option-lineHeight); line-height: var(--Cascader-option-lineHeight);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
&.is-active {
background-color: var(--Cascader-border-active-bg-color);
}
&.selected { &.selected {
span { .#{$ns}Cascader-option--text {
color: var(--primary); color: var(--primary);
} }
} }
@ -68,11 +76,26 @@
} }
} }
&--text { &--text {
flex: 1;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
user-select: none; user-select: none;
&.disabled {
color: var(--Cascader-option-disable-color);
} }
}
&-arrow {
flex: 1;
text-align: right;
padding-right: px2rem(6px);
> svg {
transform: scale(0.8);
}
}
&-selectedNum { &-selectedNum {
min-width: px2rem(16px); min-width: px2rem(16px);
height: px2rem(16px); height: px2rem(16px);

View File

@ -24,7 +24,7 @@
} }
} }
.#{$ns}Collapse:not(:last-child) { .#{$ns}Collapse:not(:last-child):not(.is-mobile) {
border-bottom: none; border-bottom: none;
} }
@ -40,4 +40,14 @@
} }
} }
} }
&.is-mobile {
&.icon-position-right {
.#{$ns}Collapse-header {
.#{$ns}Collapse-arrow-wrap {
margin-right: px2rem(-18px);
}
}
}
}
} }

View File

@ -42,6 +42,17 @@
background: var(--Collapse-header-onHover-bg); background: var(--Collapse-header-onHover-bg);
color: var(--collapse-default-header-hover-color); color: var(--collapse-default-header-hover-color);
} }
&.is-mobile {
background: none;
border-radius: 0 !important;
position: relative;
padding-left: 0;
&:hover {
background: none;
}
}
} }
.Collapse-arrow { .Collapse-arrow {
@ -76,7 +87,7 @@
// display: inline-block; // display: inline-block;
// } // }
&.is-active > &-header > &-arrow-wrap > &-arrow { &.is-active > .#{$ns}Collapse-header > .#{$ns}Collapse-arrow-wrap > &-arrow {
transform: rotate(var(--collapse-icon-rotate)); transform: rotate(var(--collapse-icon-rotate));
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
@ -133,6 +144,28 @@
line-height: var(--collapse-default-content-lineHeight); line-height: var(--collapse-default-content-lineHeight);
background: var(--collapse-default-bg-color); background: var(--collapse-default-bg-color);
} }
&.is-mobile {
border-top: none;
border-left: none;
border-right: none;
&:last-child {
border-bottom: none;
}
.#{$ns}Collapse-icon-tranform,
.#{$ns}Collapse-arrow-wrap {
margin-right: px2rem(-18px);
float: right;
margin-top: px2rem(2px);
}
.#{$ns}Collapse-content {
padding-left: 0;
padding-right: 0;
}
}
} }
//FieldSet Form + Collapse //FieldSet Form + Collapse

View File

@ -64,9 +64,15 @@
height: #{px2rem(250px)}; height: #{px2rem(250px)};
margin-top: #{px2rem(10px)}; margin-top: #{px2rem(10px)};
& > div { &:not(.is-mobile) > div {
width: 0; width: 0;
} }
&.is-mobile {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
}
} }
&-settings.only-variable { &-settings.only-variable {
@ -389,6 +395,16 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
&-popup {
height: 80vh;
&-inner {
width: 100%;
box-sizing: border-box;
padding: 0 px2rem(16px);
}
}
&-input { &-input {
flex: 1; flex: 1;
margin-right: #{px2rem(10px)}; margin-right: #{px2rem(10px)};

View File

@ -72,4 +72,28 @@
&.is-active &-caret { &.is-active &-caret {
transform: rotate(180deg); transform: rotate(180deg);
} }
&.is-mobile {
border: none;
border-radius: 0;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
&.is-error,
.is-error > & {
border-bottom-color: var(--Form-input-onError-borderColor);
}
&.is-focused {
border-bottom-color: var(--Form-input-onFocused-borderColor);
}
&.is-error.is-focused {
border-bottom-color: var(--Form-input-onError-borderColor);
}
&.is-disabled {
border-bottom-color: var(--Form-input-onDisabled-borderColor);
}
}
} }

View File

@ -24,6 +24,15 @@
color: var(--Form-input-color); color: var(--Form-input-color);
} }
&.is-mobile {
& > span {
border-radius: 0;
border: none;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
}
}
} }
&-value { &-value {
@ -121,4 +130,12 @@
} }
} }
} }
&.is-mobile {
.#{$ns}Number {
border-radius: 0;
border-bottom: var(--Number-borderWidth) solid
var(--Form-input-borderColor);
}
}
} }

View File

@ -67,6 +67,42 @@
) )
var(--Form-select-paddingX); var(--Form-select-paddingX);
} }
&.is-mobile {
display: flex;
flex-wrap: wrap;
.#{$ns}ListMenu-item {
width: calc((100vw - var(--ListMenu-item-mobile-margin) * 7) / 3);
display: inline-flex;
background: var(--ListMenu-item-mobile-bg);
margin: var(--ListMenu-item-mobile-margin);
&.is-active {
background: var(--ListMenu-item-mobile-active-bg);
}
.#{$ns}ListMenu-itemLabel {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.#{$ns}ListMenu-add-wrap {
display: flex;
align-items: center;
justify-content: space-around;
margin: px2rem(20px) 0;
text-align: center;
input {
&::-webkit-input-placeholder {
text-align: center;
}
}
} }
.#{$ns}PopOver > .#{$ns}ListMenu { .#{$ns}PopOver > .#{$ns}ListMenu {

View File

@ -220,7 +220,7 @@
calc(var(--Panel-marginBottom) / 2); calc(var(--Panel-marginBottom) / 2);
.#{$ns}Panel-body { .#{$ns}Panel-body {
padding: 0 var(--gap-md) var(--gap-md); // padding: 0 var(--gap-md) var(--gap-md);
} }
> .#{$ns}Panel-heading { > .#{$ns}Panel-heading {

View File

@ -132,6 +132,12 @@
background: var(--SearchBox-enhonce-disabled-color); background: var(--SearchBox-enhonce-disabled-color);
color: var(--SearchBox-enhonce-disabled-search-color); color: var(--SearchBox-enhonce-disabled-search-color);
} }
&.is-mobile {
border-radius: 0;
border: none;
border-bottom: var(--borderWidth) solid var(--borderColor);
}
} }
.#{$ns}SearchBox-history { .#{$ns}SearchBox-history {

View File

@ -657,6 +657,21 @@
} }
} }
&.is-mobile {
.#{$ns}Table-table > tbody > tr {
&:hover,
&.is-hovered {
background: var(--Table-bg);
border-color: var(--Table-borderColor);
color: var(--Table-color);
& + tr {
border-color: var(--Table-borderColor);
}
}
}
}
&Cell-sortBtn, &Cell-sortBtn,
&Cell-searchBtn, &Cell-searchBtn,
&Cell-filterBtn { &Cell-filterBtn {

View File

@ -112,6 +112,18 @@
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
&.is-mobile {
overflow-x: auto;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
&::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
}
}
.#{$ns}Tabs-links-drag { .#{$ns}Tabs-links-drag {
position: absolute; position: absolute;
height: 100%; height: 100%;

View File

@ -11,7 +11,7 @@
border-radius: var(--borderRadius); border-radius: var(--borderRadius);
&-popup { &-popup {
height: px2rem(400px); height: px2rem(460px);
} }
&:not(.is-disabled) { &:not(.is-disabled) {
@ -122,3 +122,26 @@
border-radius: var(--borderRadius) !important; border-radius: var(--borderRadius) !important;
box-shadow: var(--ColorPicker-boxShadow) !important; box-shadow: var(--ColorPicker-boxShadow) !important;
} }
.#{$ns}ColorPicker-popup {
.sketch-picker {
width: 80% !important;
box-shadow: none !important;
input {
&:focus {
outline: none;
}
}
.flexbox-fix {
&:last-child {
> div {
width: 18px !important;
height: 18px !important;
margin: 0px 16px 10px 0px !important;
}
}
}
}
}

View File

@ -320,6 +320,34 @@
background: rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.1);
} }
} }
&.is-mobile {
.#{$ns}Form-item {
padding: px2rem(10px) 0 0 0;
.#{$ns}Form-rowInner {
flex: 1;
}
}
.#{$ns}Combo-delBtn {
height: px2rem(20px);
}
}
&.is-mobile:not(.#{$ns}Combo--ver) {
.#{$ns}TextareaControl > textarea,
.#{$ns}Form-control > .#{$ns}TextControl-input,
.#{$ns}TextControl.is-focused > .#{$ns}TextControl-input {
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
}
}
&.is-mobile:is(.#{$ns}Combo--hor) {
.#{$ns}Form-control {
padding-top: px2rem(10px);
}
}
} }
.#{$ns}ComboTabs > .#{$ns}Tabs-links { .#{$ns}ComboTabs > .#{$ns}Tabs-links {

View File

@ -185,10 +185,22 @@
width: auto; width: auto;
// padding-bottom: var(--gap-sm); // padding-bottom: var(--gap-sm);
padding: 0; padding: 0;
&.is-mobile {
flex: 1;
.#{$ns}DateRangePicker-end {
margin-top: 0;
}
}
} }
.#{$ns}DateRangePicker-picker-wrap { .#{$ns}DateRangePicker-picker-wrap {
display: flex; display: flex;
&.is-vertical {
flex-direction: column;
}
} }
.#{$ns}DateRangePicker-start, .#{$ns}DateRangePicker-start,
@ -274,6 +286,10 @@
border-color: var(--DatePicker-borderColor); border-color: var(--DatePicker-borderColor);
background: var(--DatePicker-bg); background: var(--DatePicker-bg);
border-radius: var(--DatePicker-borderRadius); border-radius: var(--DatePicker-borderRadius);
&.is-mobile {
display: block;
}
} }
// 移动端输入框样式 // 移动端输入框样式

View File

@ -456,7 +456,7 @@
color: var(--inputTime-active-color); color: var(--inputTime-active-color);
background: var(--inputTime-active-bg-color); background: var(--inputTime-active-bg-color);
} }
&:hover { &:not(.is-mobile):hover {
color: var(--inputTime-hover-color); color: var(--inputTime-hover-color);
background: var(--inputTime-hover-bg-color); background: var(--inputTime-hover-bg-color);
} }
@ -466,6 +466,7 @@
.#{$ns}TimeContentWrapper { .#{$ns}TimeContentWrapper {
display: flex; display: flex;
justify-content: center;
} }
.#{$ns}TimeRangeHeaderWrapper { .#{$ns}TimeRangeHeaderWrapper {
@ -473,6 +474,7 @@
padding-top: 10px; padding-top: 10px;
text-align: center; text-align: center;
border-bottom: 1px solid var(--Calendar-input-borderColor); border-bottom: 1px solid var(--Calendar-input-borderColor);
margin: 0 px2rem(4px);
} }
.#{$ns}TimeFooterWrapper { .#{$ns}TimeFooterWrapper {

View File

@ -25,6 +25,10 @@
border-left: 0 !important; border-left: 0 !important;
} }
> legend.#{$ns}Collapse-header.is-mobile {
position: absolute;
}
.collapse { .collapse {
position: relative; position: relative;
} }
@ -188,4 +192,13 @@ fieldset.#{$ns}Collapse {
top: calc(var(--fieldSet-size-lg-fontSize) / 2); top: calc(var(--fieldSet-size-lg-fontSize) / 2);
} }
} }
.#{$ns}Collapse-header.is-mobile {
padding-left: var(--gap-xs) !important;
padding-right: px2rem(18px);
&:hover {
background: var(--white);
}
}
} }

View File

@ -489,15 +489,21 @@
/* 移动端样式调整 */ /* 移动端样式调整 */
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
.#{$ns}Form { .#{$ns}Form {
// &::before {
// position: absolute;
// box-sizing: border-box;
// content: ' ';
// pointer-events: none;
// right: 0;
// top: 0;
// left: 0;
// border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
// }
}
.#{$ns}Combo-form {
&::before { &::before {
position: absolute; border-bottom: none;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
top: 0;
left: 0;
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
} }
} }
@ -612,6 +618,7 @@
flex: 1; flex: 1;
flex-wrap: wrap; flex-wrap: wrap;
font-size: var(--fontSizeLg); font-size: var(--fontSizeLg);
overflow-x: auto;
&.is-disabled > .#{$ns}TextControl-input { &.is-disabled > .#{$ns}TextControl-input {
background: transparent; background: transparent;
@ -686,6 +693,7 @@
border: none; border: none;
padding: 0 var(--Form-input-paddingX) 0 0; padding: 0 var(--Form-input-paddingX) 0 0;
box-shadow: none; box-shadow: none;
border-radius: 0;
&:hover, &:hover,
&:focus, &:focus,
@ -716,9 +724,9 @@
display: none; display: none;
} }
.#{$ns}Divider { // .#{$ns}Divider {
display: none; // display: none;
} // }
.#{$ns}Tabs-pane { .#{$ns}Tabs-pane {
padding: 0; padding: 0;
@ -727,4 +735,25 @@
.#{$ns}Form-item .#{$ns}Form-groupColumn > .#{$ns}Form-item { .#{$ns}Form-item .#{$ns}Form-groupColumn > .#{$ns}Form-item {
padding-bottom: var(--Form-input-paddingX); padding-bottom: var(--Form-input-paddingX);
} }
.#{$ns}Form-groupColumn {
position: relative;
&::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
bottom: 0;
left: 0;
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
}
}
.#{$ns}Form-groupColumn:nth-last-of-type(1) {
&::after {
display: none;
}
}
} }

View File

@ -151,6 +151,12 @@
.#{$ns}Form-static { .#{$ns}Form-static {
margin-right: var(--gap-xs); margin-right: var(--gap-xs);
} }
&.is-mobile {
.#{$ns}Form-control {
display: inline-flex;
}
}
} }
.#{$ns}InputGroup:not(.is-inline) { .#{$ns}InputGroup:not(.is-inline) {

View File

@ -39,6 +39,10 @@
} }
} }
&.is-mobile {
border: none;
}
&-placeholder { &-placeholder {
color: var(--colors-neutral-text-6); color: var(--colors-neutral-text-6);
user-select: none; user-select: none;
@ -72,6 +76,27 @@
top: 0; top: 0;
} }
} }
&-popup {
height: px2rem(460px);
&-inner {
flex: 1;
padding: 0 px2rem(16px);
.#{$ns}MapPicker-search {
padding-top: 0;
margin-bottom: px2rem(16px);
.#{$ns}TextControl-input {
border-radius: 0;
border-top: none;
border-right: none;
border-left: none;
}
}
}
}
} }
.#{$ns}LocationControl { .#{$ns}LocationControl {

View File

@ -1,4 +1,6 @@
.#{$ns}MatrixControl { .#{$ns}MatrixControl {
max-width: 100%;
&-error { &-error {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -71,6 +71,14 @@
box-shadow: var(--inputNumber-base-active-shadow); box-shadow: var(--inputNumber-base-active-shadow);
} }
&.is-mobile {
border: none;
&-focused {
border: none;
}
}
&-disabled { &-disabled {
border-width: var(--inputNumber-base-disabled-top-border-width) border-width: var(--inputNumber-base-disabled-top-border-width)
var(--inputNumber-base-disabled-right-border-width) var(--inputNumber-base-disabled-right-border-width)

View File

@ -206,3 +206,15 @@
min-width: px2rem(150px); min-width: px2rem(150px);
} }
} }
.#{$ns}PickerControl.is-mobile {
width: 100%;
.#{$ns}Form-item {
padding: 0;
}
.#{$ns}Picker-input {
border: none;
}
}

View File

@ -33,6 +33,24 @@
} }
} }
&.is-mobile {
padding: 0;
.#{$ns}InputRange-input {
width: var(--InputRange-input-mobile-width);
// margin-left: 0;
.#{$ns}Number-handler-wrap {
display: none !important;
}
input {
padding: 0 !important;
text-align: center;
}
}
}
&-clear { &-clear {
display: flex; display: flex;
align-items: center; align-items: center;
@ -97,6 +115,11 @@
width: var(--InputRange-handle-width); width: var(--InputRange-handle-width);
height: var(--InputRange-handle-height); height: var(--InputRange-handle-height);
&.is-mobile {
width: var(--InputRange-handle-mobile-width);
height: var(--InputRange-handle-mobile-height);
}
&-icon, &-icon,
&-drage { &-drage {
width: 100%; width: 100%;
@ -304,4 +327,16 @@
} }
} }
} }
&.is-mobile {
.#{$ns}InputRange-marks {
div {
top: px2rem(4px);
&:nth-child(2n) {
top: px2rem(-34px);
}
}
}
}
} }

View File

@ -436,6 +436,38 @@
// display: inline-block; // display: inline-block;
max-width: 100%; max-width: 100%;
} }
&.is-mobile {
position: relative;
& > a {
margin-right: calc(
20px + var(--select-base-default-option-paddingRight)
);
}
.#{$ns}Select-option-item-check {
flex: 1;
text-align: left;
border-bottom: px2rem(1px) solid var(--borderColor);
}
.#{$ns}Select-option-mcheck {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: var(--select-base-default-option-paddingRight);
flex: none;
width: px2rem(16px);
color: var(--Form-select-mobile-icon-check-color);
}
&:last-child {
.#{$ns}Select-option-item-check {
border-bottom: none;
}
}
}
} }
&-noResult { &-noResult {

View File

@ -237,6 +237,16 @@
color: var(--transfer-search-placeholder-font-color); color: var(--transfer-search-placeholder-font-color);
} }
} }
&.is-mobile {
.#{$ns}InputBox {
border: none;
border-bottom: var(--transfer-search-bottom-border-width)
var(--transfer-search-bottom-border-style)
var(--transfer-search-bottom-border-color);
border-radius: 0;
}
}
} }
&-mid { &-mid {
@ -363,6 +373,25 @@
height: auto; height: auto;
} }
} }
.#{$ns}Transfer-result {
.#{$ns}Transfer-title {
height: px2rem(40px);
line-height: px2rem(40px);
}
&.is-mobile {
.#{$ns}Transfer-search {
.#{$ns}InputBox {
border: none;
border-bottom: var(--transfer-search-bottom-border-width)
var(--transfer-search-bottom-border-style)
var(--transfer-search-bottom-border-color);
border-radius: 0;
}
}
}
}
} }
.#{$ns}TabsTransfer { .#{$ns}TabsTransfer {
@ -463,6 +492,16 @@
} }
} }
} }
.#{$ns}TabsTransfer-search {
&.is-mobile {
.#{$ns}InputBox {
border: none;
border-bottom: 1px solid var(--TabsTransfer-border-color);
border-radius: 0;
}
}
}
} }
} }
} }
@ -528,7 +567,9 @@
&.is-mobile { &.is-mobile {
width: 100%; width: 100%;
min-width: auto;
} }
& > .#{$ns}Transfer-selection { & > .#{$ns}Transfer-selection {
flex-grow: 1; flex-grow: 1;
max-height: var(--Transfer-selection-maxHeight); max-height: var(--Transfer-selection-maxHeight);

View File

@ -147,9 +147,10 @@
&:hover { &:hover {
.#{$ns}Tree { .#{$ns}Tree {
&-itemLabel-item { &-itemLabel-item:not(.is-mobile) {
background-color: var(--Tree-item-onHover-bg-pure); background-color: var(--Tree-item-onHover-bg-pure);
} }
&-item-icons { &-item-icons {
visibility: visible; visibility: visible;
} }
@ -255,6 +256,15 @@
background: var(--Form-input-onFocused-bg); background: var(--Form-input-onFocused-bg);
} }
} }
&.is-mobile {
> input {
border-radius: 0;
border: none;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
}
}
} }
&-addTopBtn { &-addTopBtn {
@ -398,3 +408,19 @@
} }
} }
} }
.#{$ns}PopUp {
.#{$ns}Tree {
flex: 1;
.#{$ns}Tree-itemLabel {
&:hover {
.#{$ns}Tree {
&-itemLabel-item {
background-color: none !important;
}
}
}
}
}
}

View File

@ -15,6 +15,14 @@
background: #fff; background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #f9f9f9; border: 1px solid #f9f9f9;
&.is-mobile-year {
width: px2rem(184px);
}
&.is-mobile-embed {
width: px2rem(240px);
}
} }
.rdtPickerNotDays { .rdtPickerNotDays {

View File

@ -165,6 +165,7 @@ export class BaiduMapPicker extends React.Component<
if (poiLength) { if (poiLength) {
for (let i = 0; i < poiLength; i++) { for (let i = 0; i < poiLength; i++) {
const poi = result.getPoi(i); const poi = result.getPoi(i);
if (poi) {
sugs.push( sugs.push(
[ [
poi.province, poi.province,
@ -175,6 +176,7 @@ export class BaiduMapPicker extends React.Component<
].join(' ') ].join(' ')
); );
} }
}
this.setState({ this.setState({
sugs sugs
}); });
@ -288,7 +290,8 @@ export class BaiduMapPicker extends React.Component<
if (this.props.coordinatesType == 'gcj02') { if (this.props.coordinatesType == 'gcj02') {
this.covertPoint(point, COORDINATES_BD09, COORDINATES_GCJ02).then( this.covertPoint(point, COORDINATES_BD09, COORDINATES_GCJ02).then(
(convertedPoint: any) => { (convertedPoint: any) => {
(typeof this.props?.onChange === 'function') && this.props.onChange({ typeof this.props?.onChange === 'function' &&
this.props.onChange({
address: loc.address.trim() || loc.title, address: loc.address.trim() || loc.title,
lat: convertedPoint.lat, lat: convertedPoint.lat,
lng: convertedPoint.lng, lng: convertedPoint.lng,
@ -297,7 +300,8 @@ export class BaiduMapPicker extends React.Component<
} }
); );
} else { } else {
(typeof this.props?.onChange === 'function') && this.props?.onChange({ typeof this.props?.onChange === 'function' &&
this.props?.onChange({
address: loc.address.trim() || loc.title, address: loc.address.trim() || loc.title,
lat: loc.lat, lat: loc.lat,
lng: loc.lng, lng: loc.lng,

View File

@ -11,6 +11,9 @@ import {themeable, ThemeProps} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core'; import {LocaleProps, localeable} from 'amis-core';
import {autobind} from 'amis-core'; import {autobind} from 'amis-core';
import Button from './Button'; import Button from './Button';
import {ShortCuts, ShortCutDateRange} from './DatePicker';
import {advancedRanges, availableRanges} from './DateRangePicker';
import {noop} from 'amis-core';
export interface CalendarMobileProps extends ThemeProps, LocaleProps { export interface CalendarMobileProps extends ThemeProps, LocaleProps {
className?: string; className?: string;
@ -49,6 +52,7 @@ export interface CalendarMobileProps extends ThemeProps, LocaleProps {
}; };
}; };
defaultDate?: moment.Moment; defaultDate?: moment.Moment;
ranges?: string | Array<ShortCuts>;
} }
export interface CalendarMobileState { export interface CalendarMobileState {
@ -281,6 +285,97 @@ export class CalendarMobile extends React.Component<
return dow; return dow;
} }
@autobind
renderRanges(ranges: string | Array<ShortCuts> | undefined) {
if (!ranges) {
return null;
}
const {
classPrefix: ns,
embed,
minDate,
maxDate,
confirm,
onChange
} = this.props;
let rangeArr: Array<string | ShortCuts>;
if (typeof ranges === 'string') {
rangeArr = ranges.split(',');
} else {
rangeArr = ranges;
}
const __ = this.props.translate;
return (
<ul className={`${ns}DateRangePicker-rangers`}>
{rangeArr.map(item => {
if (!item) {
return null;
}
let range: any = {};
if (typeof item === 'string') {
if (availableRanges[item]) {
range = availableRanges[item];
range.key = item;
} else {
// 通过正则尝试匹配
for (let i = 0, len = advancedRanges.length; i < len; i++) {
let value = advancedRanges[i];
const m = value.regexp.exec(item);
if (m) {
range = value.resolve.apply(item, [__, ...m]);
range.key = item;
}
}
}
} else if (
(item as ShortCutDateRange).startDate &&
(item as ShortCutDateRange).endDate
) {
range = {
...item,
startDate: () => (item as ShortCutDateRange).startDate,
endDate: () => (item as ShortCutDateRange).endDate
};
}
if (Object.keys(range).length) {
return (
<li
className={`${ns}DateRangePicker-ranger`}
onClick={() => {
const now = moment();
const startDate =
minDate && minDate.isValid()
? moment.max(range.startDate(now.clone()), minDate)
: range.startDate(now.clone());
const endDate =
maxDate && maxDate.isValid()
? moment.min(maxDate, range.endDate(now.clone()))
: range.endDate(now.clone());
this.setState({
startDate,
endDate
});
!embed && onChange && onChange({startDate, endDate}, noop);
// 内嵌模式
embed &&
onChange &&
onChange({startDate, endDate}, () => confirm && confirm());
}}
key={range.key || range.label}
>
<a>{__(range.label)}</a>
</li>
);
} else {
return null;
}
})}
</ul>
);
}
@autobind @autobind
handleCalendarClick(isDisabled: boolean) { handleCalendarClick(isDisabled: boolean) {
if (isDisabled) { if (isDisabled) {
@ -697,7 +792,8 @@ export class CalendarMobile extends React.Component<
footerExtra, footerExtra,
timeFormat, timeFormat,
showViewMode, showViewMode,
isDatePicker isDatePicker,
ranges
} = this.props; } = this.props;
const __ = this.props.translate; const __ = this.props.translate;
@ -755,7 +851,7 @@ export class CalendarMobile extends React.Component<
{timeFormat && startDate && this.renderMobileTimePicker()} {timeFormat && startDate && this.renderMobileTimePicker()}
<div className={cx('CalendarMobile-footer-toolbar')}> <div className={cx('CalendarMobile-footer-toolbar')}>
<div className={cx('CalendarMobile-footer-ranges')}> <div className={cx('CalendarMobile-footer-ranges')}>
{footerExtra} {this.renderRanges(ranges)}
</div> </div>
{confirm && !embed && ( {confirm && !embed && (
<Button <Button

View File

@ -12,12 +12,15 @@ import compact from 'lodash/compact';
import find from 'lodash/find'; import find from 'lodash/find';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import Button from './Button'; import Button from './Button';
import Checkbox from './Checkbox';
import {flattenTree, findTree, getTreeDepth} from 'amis-core'; import {flattenTree, findTree, getTreeDepth} from 'amis-core';
import type {OptionsControlProps} from 'amis-core'; import type {OptionsControlProps} from 'amis-core';
import {Icon} from './icons';
export type CascaderOption = { export type CascaderOption = {
text?: string; text?: string;
value?: string | number; value?: string | number;
valueField?: string;
color?: string; color?: string;
disabled?: boolean; disabled?: boolean;
children?: Options; children?: Options;
@ -50,12 +53,12 @@ export type CascaderTab = {
export interface CascaderState { export interface CascaderState {
selectedOptions: Options; selectedOptions: Options;
activeTab: number;
tabs: Array<{ tabs: Array<{
options: Options; options: Options;
}>; }>;
// 用于在只选择子节点模式的时候禁用按钮 // 用于在只选择子节点模式的时候禁用按钮
disableConfirm: boolean; disableConfirm: boolean;
activePaths: CascaderOption[];
} }
export class Cascader extends React.Component<CascaderProps, CascaderState> { export class Cascader extends React.Component<CascaderProps, CascaderState> {
@ -68,8 +71,8 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
constructor(props: CascaderProps) { constructor(props: CascaderProps) {
super(props); super(props);
this.state = { this.state = {
activePaths: [],
selectedOptions: this.props.selectedOptions || [], selectedOptions: this.props.selectedOptions || [],
activeTab: 0,
tabs: [ tabs: [
{ {
options: this.props.options.slice() || [] options: this.props.options.slice() || []
@ -109,17 +112,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
option.children.forEach((option: Option) => (option.disabled = true)); option.children.forEach((option: Option) => (option.disabled = true));
} }
} }
return multiple && !onlyLeaf return {
? {
options: [
{
...option,
isCheckAll: true
},
...(option.children ? option.children : [])
]
}
: {
options: option.children ? option.children : [] options: option.children ? option.children : []
}; };
}); });
@ -129,15 +122,6 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
}); });
} }
@autobind
handleTabSelect(index: number) {
const tabs = this.state.tabs.slice(0, index + 1);
this.setState({
activeTab: index,
tabs
});
}
@autobind @autobind
getOptionParent(option: Option) { getOptionParent(option: Option) {
const {options, valueField = 'value'} = this.props; const {options, valueField = 'value'} = this.props;
@ -202,6 +186,23 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
return loop(selectedOptions); return loop(selectedOptions);
} }
// 判断配置onlyChildren属性时节点选中情况
@autobind
getOnlyChildrenSelect(option: Option, selectedOptions?: Option[]) {
const {onlyChildren} = this.props;
selectedOptions = selectedOptions || this.state.selectedOptions;
return (
onlyChildren &&
option.children
?.filter(option => !option.children?.length)
.every(
option =>
!option.children?.length && selectedOptions?.includes(option)
)
);
}
@autobind @autobind
getSelectedChildNum(option: Option): number { getSelectedChildNum(option: Option): number {
let count = 0; let count = 0;
@ -267,9 +268,10 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
let index = selectedOptions.findIndex( let index = selectedOptions.findIndex(
(item: Option) => item[valueField] === option[valueField] (item: Option) => item[valueField] === option[valueField]
); );
let isSelect = this.getOnlyChildrenSelect(option, selectedOptions);
if (index !== -1) { if (index !== -1) {
selectedOptions.splice(index, 1); selectedOptions.splice(index, 1);
} else { } else if (!isSelect) {
if (!(onlyChildren && option.children?.length)) { if (!(onlyChildren && option.children?.length)) {
selectedOptions.push(option); selectedOptions.push(option);
} }
@ -279,7 +281,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
return; return;
} }
option.children.forEach((item: Option) => { option.children.forEach((item: Option) => {
if (index !== -1) { if (index !== -1 || isSelect) {
// 删除选中节点及其子节点 // 删除选中节点及其子节点
selectedOptions = selectedOptions.filter( selectedOptions = selectedOptions.filter(
(sItem: Option) => sItem[valueField] !== item[valueField] (sItem: Option) => sItem[valueField] !== item[valueField]
@ -313,22 +315,27 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
valueField = 'value', valueField = 'value',
cascade, cascade,
onlyLeaf, onlyLeaf,
onlyChildren onlyChildren,
withChildren
} = this.props; } = this.props;
let tabs = this.state.tabs.slice();
let {activeTab} = this.state;
let selectedOptions = this.state.selectedOptions; let selectedOptions = this.state.selectedOptions;
const isDisable = option.disabled; const isDisable = option.disabled;
if (!isDisable) { if (!isDisable) {
if (multiple) { if (multiple) {
// 父子级分离 // 父子级分离
if (cascade) { if (cascade) {
if ( let index = selectedOptions.findIndex(
option.isCheckAll || (item: Option) => item[valueField] === option[valueField]
!option.children || );
!option.children.length if (index !== -1) {
) { selectedOptions.splice(index, 1);
} else {
selectedOptions.push(option);
}
} else {
if (withChildren || onlyChildren) {
selectedOptions = this.dealChildrenSelect(option, selectedOptions);
} else {
let index = selectedOptions.findIndex( let index = selectedOptions.findIndex(
(item: Option) => item[valueField] === option[valueField] (item: Option) => item[valueField] === option[valueField]
); );
@ -338,29 +345,58 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
selectedOptions.push(option); selectedOptions.push(option);
} }
} }
} else {
if (
option.isCheckAll ||
!option.children ||
!option.children.length
) {
selectedOptions = this.dealChildrenSelect(option, selectedOptions);
if (!onlyChildren) {
selectedOptions = this.dealParentSelect(option, selectedOptions); selectedOptions = this.dealParentSelect(option, selectedOptions);
} }
}
}
} else { } else {
// 单选 // 单选
if (onlyLeaf) {
if (!option.children?.length) {
selectedOptions = [option]; selectedOptions = [option];
} }
} else {
selectedOptions = [option];
}
}
} }
this.dealOptionDisable(selectedOptions); this.dealOptionDisable(selectedOptions);
let disableConfirm = false;
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
disableConfirm = true;
}
this.setState({
selectedOptions,
disableConfirm
});
}
@autobind
handleExpand(option: Option, tabIndex: number) {
const activePaths = this.state.activePaths.slice();
if (option.children?.length) {
activePaths[tabIndex] = option;
} else {
activePaths.splice(tabIndex);
}
let tabs = this.state.tabs.slice();
if (tabs.length > tabIndex + 1) { if (tabs.length > tabIndex + 1) {
tabs = tabs.slice(0, tabIndex + 1); tabs = tabs.slice(0, tabIndex + 1);
} }
if (option?.children) {
const nextTab = {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
}
requestAnimationFrame(() => { requestAnimationFrame(() => {
const tabWidth = this.tabRef.current?.offsetWidth || 1; const tabWidth = this.tabRef.current?.offsetWidth || 1;
const parentTree = this.getParentTree(option, [option]); const parentTree = this.getParentTree(option, [option]);
@ -370,60 +406,9 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
} }
}); });
if (option?.children && !option.isCheckAll) {
const nextTab =
multiple && !onlyLeaf
? {
options: [
{
...option,
isCheckAll: true
},
...option.children
]
}
: {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
activeTab += 1;
}
let disableConfirm = false;
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
disableConfirm = true;
}
this.setState({ this.setState({
tabs, activePaths,
activeTab, tabs
selectedOptions,
disableConfirm
});
}
@autobind
onNextClick(option: CascaderOption, tabIndex: number) {
let {activeTab} = this.state;
let tabs = this.state.tabs.slice();
if (option.c)
if (option?.children) {
const nextTab = {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
activeTab += 1;
}
this.setState({
tabs,
activeTab
}); });
} }
@ -462,15 +447,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
@autobind @autobind
confirm() { confirm() {
const { const {onChange, onClose, onlyLeaf} = this.props;
onChange,
joinValues,
delimiter,
extractValue,
valueField,
onClose,
onlyLeaf
} = this.props;
const selectedOptions = this.getSelectedOptions(); const selectedOptions = this.getSelectedOptions();
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) { if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
return; return;
@ -490,15 +467,15 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
@autobind @autobind
renderOption(option: CascaderOption, tabIndex: number) { renderOption(option: CascaderOption, tabIndex: number) {
const { const {
onlyLeaf,
activeColor, activeColor,
optionRender, optionRender,
labelField, labelField,
valueField = 'value', valueField = 'value',
classnames: cx, multiple,
cascade, classnames: cx
multiple
} = this.props; } = this.props;
const {selectedOptions} = this.state; const {selectedOptions, activePaths} = this.state;
const selectedValueArr = selectedOptions.map(item => item[valueField]); const selectedValueArr = selectedOptions.map(item => item[valueField]);
let selfChecked = selectedValueArr.includes(option[valueField]); let selfChecked = selectedValueArr.includes(option[valueField]);
@ -508,21 +485,51 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
) : ( ) : (
<span>{option[labelField]}</span> <span>{option[labelField]}</span>
); );
return ( return (
<li <li
className={cx( className={cx(
'Cascader-option', 'Cascader-option',
{ {
selected: selfChecked, 'selected': selfChecked,
disabled: option.disabled 'disabled': option.disabled,
'is-active': activePaths.includes(option)
}, },
option.className option.className
)} )}
style={{color}} style={{color}}
onClick={() => this.onSelect(option, tabIndex)}
key={tabIndex + '-' + option[valueField]} key={tabIndex + '-' + option[valueField]}
onClick={() => {
!multiple && this.onSelect(option, tabIndex);
this.handleExpand(option, tabIndex);
}}
>
{multiple ? (
<Checkbox
disabled={option.disabled || (onlyLeaf && option.children?.length)}
checked={
selectedOptions.includes(option) ||
this.getOnlyChildrenSelect(option)
}
onChange={() => this.onSelect(option, tabIndex)}
> >
<span className={cx('Cascader-option--text')}>{Text}</span> <span className={cx('Cascader-option--text')}>{Text}</span>
</Checkbox>
) : (
<span
className={cx('Cascader-option--text', {
disabled: onlyLeaf && option.children?.length
})}
>
{Text}
</span>
)}
{option.children?.length ? (
<span className={cx('Cascader-option-arrow')}>
<Icon icon="right-arrow-bold" className="icon" />
</span>
) : null}
</li> </li>
); );
} }
@ -551,7 +558,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
const {options} = tab; const {options} = tab;
return ( return (
<div <div
className={cx(`Cascader-tab`)} className={cx(`Cascader-tab depth-${tabIndex}`)}
ref={this.tabRef} ref={this.tabRef}
key={tabIndex} key={tabIndex}
> >
@ -563,7 +570,10 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
? Array(getTreeDepth(options) - tabs.length) ? Array(getTreeDepth(options) - tabs.length)
.fill(1) .fill(1)
.map((item: number, index: number) => ( .map((item: number, index: number) => (
<div className={cx(`Cascader-tab`)} key={index}></div> <div
className={cx(`Cascader-tab depth-${index + 1}`)}
key={index}
></div>
)) ))
: null} : null}
</div> </div>

View File

@ -14,6 +14,7 @@ import Transition, {
import {autobind} from 'amis-core'; import {autobind} from 'amis-core';
import {isClickOnInput} from 'amis-core'; import {isClickOnInput} from 'amis-core';
import {TranslateFn} from 'amis-core'; import {TranslateFn} from 'amis-core';
import {isMobile} from 'amis-core';
import {Icon} from './icons'; import {Icon} from './icons';
const collapseStyles: { const collapseStyles: {
@ -54,6 +55,7 @@ export interface CollapseProps {
propsUpdate?: boolean; propsUpdate?: boolean;
partial?: boolean; partial?: boolean;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
} }
export interface CollapseState { export interface CollapseState {
@ -170,19 +172,25 @@ export class Collapse extends React.Component<CollapseProps, CollapseState> {
showArrow, showArrow,
expandIcon, expandIcon,
disabled, disabled,
children children,
useMobileUI
} = this.props; } = this.props;
const finalHeader = this.state.collapsed const finalHeader = this.state.collapsed
? header ? header
: collapseHeader || header; : collapseHeader || header;
const mobileUI = useMobileUI && isMobile();
let dom = [ let dom = [
finalHeader ? ( finalHeader ? (
<HeadingComponent <HeadingComponent
key="header" key="header"
onClick={this.toggleCollapsed} onClick={this.toggleCollapsed}
className={cx(`Collapse-header`, headingClassName)} className={cx(
`Collapse-header`,
{'is-mobile': mobileUI},
headingClassName
)}
> >
{showArrow && collapsable ? ( {showArrow && collapsable ? (
expandIcon ? ( expandIcon ? (
@ -249,6 +257,7 @@ export class Collapse extends React.Component<CollapseProps, CollapseState> {
className={cx( className={cx(
`Collapse`, `Collapse`,
{ {
'is-mobile': mobileUI,
'is-active': !this.state.collapsed, 'is-active': !this.state.collapsed,
[`Collapse--${size}`]: size, [`Collapse--${size}`]: size,
'Collapse--disabled': disabled, 'Collapse--disabled': disabled,

View File

@ -13,6 +13,7 @@ export interface CollapseItem {
import {ClassNamesFn, themeable, autobind} from 'amis-core'; import {ClassNamesFn, themeable, autobind} from 'amis-core';
import type {SchemaNode} from 'amis-core'; import type {SchemaNode} from 'amis-core';
import {isMobile} from 'amis-core';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
export interface CollapseGroupProps { export interface CollapseGroupProps {
@ -26,6 +27,7 @@ export interface CollapseGroupProps {
classnames: ClassNamesFn; classnames: ClassNamesFn;
classPrefix: string; classPrefix: string;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
} }
export interface CollapseGroupState { export interface CollapseGroupState {
@ -136,8 +138,10 @@ class CollapseGroup extends React.Component<
className, className,
style, style,
expandIconPosition, expandIconPosition,
children children,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
@ -146,6 +150,9 @@ class CollapseGroup extends React.Component<
{ {
'icon-position-right': expandIconPosition === 'right' 'icon-position-right': expandIconPosition === 'right'
}, },
{
'is-mobile': mobileUI
},
className className
)} )}
style={style} style={style}

View File

@ -40,6 +40,7 @@ export interface ColorControlState {
isOpened: boolean; isOpened: boolean;
isFocused: boolean; isFocused: boolean;
inputValue: string; inputValue: string;
tempValue: string;
} }
export class ColorControl extends React.PureComponent< export class ColorControl extends React.PureComponent<
@ -56,7 +57,8 @@ export class ColorControl extends React.PureComponent<
state = { state = {
isOpened: false, isOpened: false,
isFocused: false, isFocused: false,
inputValue: this.props.value || '' inputValue: this.props.value || '',
tempValue: this.props.value || ''
}; };
popover: any; popover: any;
closeTimer: number; closeTimer: number;
@ -70,6 +72,8 @@ export class ColorControl extends React.PureComponent<
this.focus = this.focus.bind(this); this.focus = this.focus.bind(this);
this.blur = this.blur.bind(this); this.blur = this.blur.bind(this);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleTempChange = this.handleTempChange.bind(this);
this.handleConfirm = this.handleConfirm.bind(this);
this.handleFocus = this.handleFocus.bind(this); this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this); this.handleBlur = this.handleBlur.bind(this);
this.clearValue = this.clearValue.bind(this); this.clearValue = this.clearValue.bind(this);
@ -206,6 +210,31 @@ export class ColorControl extends React.PureComponent<
// closeOnSelect && this.close(); // closeOnSelect && this.close();
} }
handleTempChange(color: ColorResult) {
let {tempValue} = this.state;
const {format} = this.props;
if (format === 'rgba') {
tempValue = `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`;
} else if (format === 'rgb') {
tempValue = `rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b})`;
} else if (format === 'hsl') {
tempValue = `hsl(${Math.round(color.hsl.h)}, ${Math.round(
color.hsl.s * 100
)}%, ${Math.round(color.hsl.l * 100)}%)`;
} else {
tempValue = color.hex;
}
this.setState({tempValue});
}
handleConfirm() {
const {onChange} = this.props;
const {tempValue} = this.state;
onChange(tempValue);
this.close();
}
render() { render() {
const { const {
classPrefix: ns, classPrefix: ns,
@ -227,6 +256,7 @@ export class ColorControl extends React.PureComponent<
const __ = this.props.translate; const __ = this.props.translate;
const isOpened = this.state.isOpened; const isOpened = this.state.isOpened;
const isFocused = this.state.isFocused; const isFocused = this.state.isFocused;
const tempValue = this.state.tempValue;
const mobileUI = useMobileUI && isMobile(); const mobileUI = useMobileUI && isMobile();
return ( return (
@ -328,18 +358,20 @@ export class ColorControl extends React.PureComponent<
container={popOverContainer} container={popOverContainer}
isShow={isOpened} isShow={isOpened}
onHide={this.handleClick} onHide={this.handleClick}
showConfirm
onConfirm={this.handleConfirm}
> >
{allowCustomColor ? ( {allowCustomColor ? (
<SketchPicker <SketchPicker
styles={{}} styles={{}}
disableAlpha={!!~['rgb', 'hex'].indexOf(format as string)} disableAlpha={!!~['rgb', 'hex'].indexOf(format as string)}
color={value} color={tempValue}
presetColors={presetColors} presetColors={presetColors}
onChangeComplete={this.handleChange} onChangeComplete={this.handleTempChange}
/> />
) : ( ) : (
<GithubPicker <GithubPicker
color={value} color={tempValue}
colors={ colors={
Array.isArray(presetColors) Array.isArray(presetColors)
? (presetColors ? (presetColors
@ -355,7 +387,7 @@ export class ColorControl extends React.PureComponent<
) as string[]) ) as string[])
: undefined : undefined
} }
onChangeComplete={this.handleChange} onChangeComplete={this.handleTempChange}
/> />
)} )}
</PopUp> </PopUp>

View File

@ -2,8 +2,15 @@ import React from 'react';
import Modal from './Modal'; import Modal from './Modal';
import Button from './Button'; import Button from './Button';
import Drawer from './Drawer'; import Drawer from './Drawer';
import {localeable, LocaleProps, themeable, ThemeProps} from 'amis-core'; import {
localeable,
LocaleProps,
themeable,
ThemeProps,
isMobile
} from 'amis-core';
import Spinner from './Spinner'; import Spinner from './Spinner';
import PopUp from './PopUp';
export interface ConfirmBoxProps extends LocaleProps, ThemeProps { export interface ConfirmBoxProps extends LocaleProps, ThemeProps {
show?: boolean; show?: boolean;
@ -34,6 +41,7 @@ export interface ConfirmBoxProps extends LocaleProps, ThemeProps {
headerClassName?: string; headerClassName?: string;
bodyClassName?: string; bodyClassName?: string;
footerClassName?: string; footerClassName?: string;
useMobileUI?: boolean;
} }
export function ConfirmBox({ export function ConfirmBox({
@ -56,7 +64,8 @@ export function ConfirmBox({
classnames: cx, classnames: cx,
className, className,
bodyClassName, bodyClassName,
footerClassName footerClassName,
useMobileUI
}: ConfirmBoxProps) { }: ConfirmBoxProps) {
const [loading, setLoading] = React.useState<boolean>(); const [loading, setLoading] = React.useState<boolean>();
const [error, setError] = React.useState<string>(); const [error, setError] = React.useState<string>();
@ -90,7 +99,22 @@ export function ConfirmBox({
}, [show]); }, [show]);
function renderDialog() { function renderDialog() {
return ( const mobileUI = useMobileUI && isMobile();
return mobileUI ? (
<PopUp
isShow={show}
showConfirm
onConfirm={handleConfirm}
onHide={onCancel}
>
{typeof children === 'function'
? children({
bodyRef: bodyRef,
loading
})
: children}
</PopUp>
) : (
<Modal <Modal
size={size} size={size}
closeOnEsc={closeOnEsc} closeOnEsc={closeOnEsc}

View File

@ -1035,7 +1035,9 @@ export class DateRangePicker extends React.Component<
} }
selectShortcut(shortcut: PlainObject) { selectShortcut(shortcut: PlainObject) {
const {closeOnSelect, minDateRaw, maxDateRaw, format, data} = this.props; const {closeOnSelect, minDateRaw, maxDateRaw, format, data, useMobileUI} =
this.props;
const mobileUI = useMobileUI && isMobile();
const now = moment(); const now = moment();
/** minDate和maxDate要实时计算因为用户可能设置为${NOW()},暂时不考虑毫秒级的时间差 */ /** minDate和maxDate要实时计算因为用户可能设置为${NOW()},暂时不考虑毫秒级的时间差 */
const minDate = minDateRaw const minDate = minDateRaw
@ -1056,7 +1058,7 @@ export class DateRangePicker extends React.Component<
endDate: endDate:
maxDate && maxDate?.isValid() ? moment.min(maxDate, endDate) : endDate maxDate && maxDate?.isValid() ? moment.min(maxDate, endDate) : endDate
}, },
closeOnSelect ? this.confirm : noop closeOnSelect && !mobileUI ? this.confirm : noop
); );
} }
@ -1382,7 +1384,9 @@ export class DateRangePicker extends React.Component<
locale, locale,
embed, embed,
type, type,
viewMode = 'days' viewMode = 'days',
label,
useMobileUI
} = this.props; } = this.props;
const __ = this.props.translate; const __ = this.props.translate;
const {startDate, endDate, editState} = this.state; const {startDate, endDate, editState} = this.state;
@ -1400,12 +1404,48 @@ export class DateRangePicker extends React.Component<
!endDate || !endDate ||
!startDate?.isValid() || !startDate?.isValid() ||
!endDate?.isValid())); !endDate?.isValid()));
const mobileUI = useMobileUI && isMobile();
return ( return (
<div className={cx(`${ns}DateRangePicker-wrap`)} ref={this.calendarRef}> <div
className={cx(`${ns}DateRangePicker-wrap`, {'is-mobile': mobileUI})}
ref={this.calendarRef}
>
{this.renderShortcuts(shortcuts ?? ranges)} {this.renderShortcuts(shortcuts ?? ranges)}
<div className={cx(`${ns}DateRangePicker-picker-wrap`)}> {mobileUI && !embed ? (
{(!isTimeRange || (editState === 'start' && !embed)) && ( <div className={cx('PickerColumns-header')}>
{
<Button
className="PickerColumns-cancel"
level="link"
onClick={() => this.close(false)}
>
{__('cancel')}
</Button>
}
{label && typeof label === 'string'
? label
: __('Calendar.datepicker')}
{
<Button
className="PickerColumns-confirm"
level="link"
disabled={isConfirmBtnDisbaled || !startDate || !endDate}
onClick={this.confirm}
>
{__('confirm')}
</Button>
}
</div>
) : null}
<div
className={cx(`${ns}DateRangePicker-picker-wrap`, {
'is-vertical': embed
})}
>
{(!isTimeRange ||
(editState === 'start' && !embed) ||
(mobileUI && isTimeRange)) && (
<Calendar <Calendar
className={`${ns}DateRangePicker-start`} className={`${ns}DateRangePicker-start`}
value={startDate} value={startDate}
@ -1432,9 +1472,12 @@ export class DateRangePicker extends React.Component<
renderYear={this.renderYear} renderYear={this.renderYear}
locale={locale} locale={locale}
timeRangeHeader="开始时间" timeRangeHeader="开始时间"
embed={embed}
/> />
)} )}
{(!isTimeRange || (editState === 'end' && !embed)) && ( {(!isTimeRange ||
(editState === 'end' && !embed) ||
(mobileUI && isTimeRange)) && (
<Calendar <Calendar
className={`${ns}DateRangePicker-end`} className={`${ns}DateRangePicker-end`}
value={endDate} value={endDate}
@ -1461,11 +1504,12 @@ export class DateRangePicker extends React.Component<
renderYear={this.renderYear} renderYear={this.renderYear}
locale={locale} locale={locale}
timeRangeHeader="结束时间" timeRangeHeader="结束时间"
embed={embed}
/> />
)} )}
</div> </div>
{embed ? null : ( {embed || mobileUI ? null : (
<div key="button" className={`${ns}DateRangePicker-actions`}> <div key="button" className={`${ns}DateRangePicker-actions`}>
{/* this.close 这里不可以传参 */} {/* this.close 这里不可以传参 */}
<Button size="sm" onClick={() => this.close()}> <Button size="sm" onClick={() => this.close()}>
@ -1631,11 +1675,16 @@ export class DateRangePicker extends React.Component<
/> />
); );
const mobileUI = useMobileUI && isMobile();
if (embed) { if (embed) {
return ( return (
<div <div
className={cx( className={cx(
`${ns}DateRangeCalendar`, `${ns}DateRangeCalendar`,
{
'is-mobile': mobileUI
},
{ {
'is-disabled': disabled 'is-disabled': disabled
}, },
@ -1739,7 +1788,8 @@ export class DateRangePicker extends React.Component<
`${ns}CalendarMobile-pop--${viewMode}` `${ns}CalendarMobile-pop--${viewMode}`
)} )}
onHide={this.close} onHide={this.close}
header={CalendarMobileTitle} showClose={false}
// header={CalendarMobileTitle}
> >
{useCalendarMobile ? calendarMobile : this.renderCalendar()} {useCalendarMobile ? calendarMobile : this.renderCalendar()}
</PopUp> </PopUp>

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {ThemeProps, themeable} from 'amis-core'; import {ThemeProps, themeable, isMobile} from 'amis-core';
import Input from './Input'; import Input from './Input';
import {autobind, ucFirst} from 'amis-core'; import {autobind, ucFirst} from 'amis-core';
import {Icon} from './icons'; import {Icon} from './icons';
@ -18,6 +18,7 @@ export interface InputBoxProps
prefix?: JSX.Element; prefix?: JSX.Element;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
borderMode?: 'full' | 'half' | 'none'; borderMode?: 'full' | 'half' | 'none';
useMobileUI?: boolean;
} }
export interface InputBoxState { export interface InputBoxState {
@ -83,13 +84,16 @@ export class InputBox extends React.Component<InputBoxProps, InputBoxState> {
children, children,
borderMode, borderMode,
onClick, onClick,
useMobileUI,
...rest ...rest
} = this.props; } = this.props;
const isFocused = this.state.isFocused; const isFocused = this.state.isFocused;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
className={cx('InputBox', className, { className={cx('InputBox', className, {
'is-mobile': mobileUI,
'is-focused': isFocused, 'is-focused': isFocused,
'is-disabled': disabled, 'is-disabled': disabled,
'is-error': hasError, 'is-error': hasError,

View File

@ -20,6 +20,7 @@ export interface InputBoxWithSuggestionProps extends ThemeProps, LocaleProps {
hasError?: boolean; hasError?: boolean;
placeholder?: string; placeholder?: string;
clearable?: boolean; clearable?: boolean;
useMobileUI?: boolean;
} }
const option2value = (item: any) => item.value; const option2value = (item: any) => item.value;
@ -65,7 +66,8 @@ export class InputBoxWithSuggestion extends React.Component<InputBoxWithSuggesti
searchable, searchable,
popOverContainer, popOverContainer,
clearable, clearable,
hasError hasError,
useMobileUI
} = this.props; } = this.props;
const options = this.filterOptions( const options = this.filterOptions(
Array.isArray(this.props.options) ? this.props.options : [] Array.isArray(this.props.options) ? this.props.options : []
@ -103,6 +105,7 @@ export class InputBoxWithSuggestion extends React.Component<InputBoxWithSuggesti
clearable={clearable} clearable={clearable}
onClick={onClick} onClick={onClick}
hasError={hasError} hasError={hasError}
useMobileUI={useMobileUI}
> >
<span className={cx('InputBox-caret')}> <span className={cx('InputBox-caret')}>
<Icon icon="caret" className="icon" /> <Icon icon="caret" className="icon" />

View File

@ -161,7 +161,7 @@ export function InputTable({
function renderBody() { function renderBody() {
return ( return (
<div className={cx(`Table`, className)}> <div className={cx(`Table`)}>
<div className={cx(`Table-contentWrap`)}> <div className={cx(`Table-contentWrap`)}>
<table className={cx(`Table-table`)}> <table className={cx(`Table-table`)}>
<thead> <thead>

View File

@ -2,6 +2,7 @@ import {ThemeProps, themeable} from 'amis-core';
import React from 'react'; import React from 'react';
import {Options, Option} from 'amis-core'; import {Options, Option} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core'; import {LocaleProps, localeable} from 'amis-core';
import {isMobile} from 'amis-core';
export interface ListMenuProps extends ThemeProps, LocaleProps { export interface ListMenuProps extends ThemeProps, LocaleProps {
options: Options; options: Options;
@ -14,6 +15,7 @@ export interface ListMenuProps extends ThemeProps, LocaleProps {
getItemProps: (props: {item: Option; index: number}) => any; getItemProps: (props: {item: Option; index: number}) => any;
prefix?: JSX.Element; prefix?: JSX.Element;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
} }
interface RenderResult { interface RenderResult {
@ -36,6 +38,7 @@ export class ListMenu extends React.Component<ListMenuProps> {
getItemProps, getItemProps,
highlightIndex, highlightIndex,
selectedOptions, selectedOptions,
useMobileUI,
onSelect onSelect
} = this.props; } = this.props;
@ -86,11 +89,20 @@ export class ListMenu extends React.Component<ListMenuProps> {
} }
render() { render() {
const {classnames: cx, options, placeholder, prefix, children} = this.props; const {
classnames: cx,
options,
placeholder,
prefix,
children,
useMobileUI,
selectedOptions
} = this.props;
const __ = this.props.translate; const __ = this.props.translate;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div className={cx('ListMenu')}> <div className={cx('ListMenu', {'is-mobile': mobileUI})}>
{prefix} {prefix}
{Array.isArray(options) && options.length ? ( {Array.isArray(options) && options.length ? (
options.reduce( options.reduce(

View File

@ -8,6 +8,8 @@ import Alert2 from './Alert2';
import BaiduMapPicker from './BaiduMapPicker'; import BaiduMapPicker from './BaiduMapPicker';
import GaodeMapPicker from './GaodeMapPicker'; import GaodeMapPicker from './GaodeMapPicker';
import {LocaleProps, localeable} from 'amis-core'; import {LocaleProps, localeable} from 'amis-core';
import {isMobile} from 'amis-core';
import PopUp from './PopUp';
export interface LocationProps extends ThemeProps, LocaleProps { export interface LocationProps extends ThemeProps, LocaleProps {
vendor: 'baidu' | 'gaode' | 'tenxun'; vendor: 'baidu' | 'gaode' | 'tenxun';
@ -26,6 +28,7 @@ export interface LocationProps extends ThemeProps, LocaleProps {
popoverClassName?: string; popoverClassName?: string;
onChange: (value: any) => void; onChange: (value: any) => void;
popOverContainer?: any; popOverContainer?: any;
useMobileUI?: boolean;
} }
export interface LocationState { export interface LocationState {
@ -42,6 +45,7 @@ export class LocationPicker extends React.Component<
clearable: false clearable: false
}; };
domRef: React.RefObject<HTMLDivElement> = React.createRef(); domRef: React.RefObject<HTMLDivElement> = React.createRef();
tempValue: any;
state = { state = {
isFocused: false, isFocused: false,
isOpened: false isOpened: false
@ -93,6 +97,7 @@ export class LocationPicker extends React.Component<
}, },
fn fn
); );
this.tempValue = this.props.value;
} }
@autobind @autobind
@ -127,6 +132,23 @@ export class LocationPicker extends React.Component<
this.props.onChange(value); this.props.onChange(value);
} }
@autobind
handleTempChange(value: any) {
if (value) {
value = {
...value,
vendor: this.props.vendor
};
}
this.tempValue = value;
}
@autobind
handleConfirm() {
this.props.onChange(this.tempValue);
this.close();
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -139,10 +161,12 @@ export class LocationPicker extends React.Component<
popOverContainer, popOverContainer,
vendor, vendor,
coordinatesType, coordinatesType,
ak ak,
useMobileUI
} = this.props; } = this.props;
const __ = this.props.translate; const __ = this.props.translate;
const {isFocused, isOpened} = this.state; const {isFocused, isOpened} = this.state;
const mobileUI = useMobileUI && isMobile();
const picker = (() => { const picker = (() => {
switch (vendor) { switch (vendor) {
@ -165,9 +189,9 @@ export class LocationPicker extends React.Component<
/> />
); );
default: default:
return (<Alert2>{__(`${vendor} 地图控件不支持`, {vendor})}</Alert2>); return <Alert2>{__(`${vendor} 地图控件不支持`, {vendor})}</Alert2>;
} }
})() })();
return ( return (
<div <div
@ -178,6 +202,7 @@ export class LocationPicker extends React.Component<
className={cx( className={cx(
`LocationPicker`, `LocationPicker`,
{ {
'is-mobile': mobileUI,
'is-disabled': disabled, 'is-disabled': disabled,
'is-focused': isFocused, 'is-focused': isFocused,
'is-active': isOpened 'is-active': isOpened
@ -205,6 +230,29 @@ export class LocationPicker extends React.Component<
<Icon icon="location" className="icon" /> <Icon icon="location" className="icon" />
</a> </a>
{mobileUI ? (
<PopUp
className={cx(`LocationPicker-popup`)}
container={popOverContainer || this.getParent}
isShow={isOpened}
onHide={this.close}
showConfirm
onConfirm={this.handleConfirm}
>
<div className={cx('LocationPicker-popup-inner')}>
{vendor === 'baidu' ? (
<BaiduMapPicker
ak={ak}
value={value}
coordinatesType={coordinatesType}
onChange={this.handleTempChange}
/>
) : (
<Alert2>{__('${vendor} 地图控件不支持', {vendor})}</Alert2>
)}
</div>
</PopUp>
) : (
<Overlay <Overlay
target={this.getTarget} target={this.getTarget}
container={popOverContainer || this.getParent} container={popOverContainer || this.getParent}
@ -218,9 +266,19 @@ export class LocationPicker extends React.Component<
onClick={this.handlePopOverClick} onClick={this.handlePopOverClick}
style={{width: this.getTarget()?.offsetWidth}} style={{width: this.getTarget()?.offsetWidth}}
> >
{picker} {vendor === 'baidu' ? (
<BaiduMapPicker
ak={ak}
value={value}
coordinatesType={coordinatesType}
onChange={this.handleChange}
/>
) : (
<Alert2>{__('${vendor} 地图控件不支持', {vendor})}</Alert2>
)}
</PopOver> </PopOver>
</Overlay> </Overlay>
)}
</div> </div>
); );
} }

View File

@ -264,6 +264,22 @@ export class Modal extends React.Component<ModalProps, ModalState> {
this.isRootClosed && !e.defaultPrevented && onHide(e); this.isRootClosed && !e.defaultPrevented && onHide(e);
} }
getZIndex() {
let defaultIndex = 1400;
const {classnames: cx} = this.props;
const modals = document.querySelectorAll('.' + cx('Modal'));
const popups = document.querySelectorAll('.' + cx('PopUp'));
[...modals, ...popups].forEach((node: HTMLDialogElement) => {
const style = getComputedStyle(node);
const curIndex = parseInt(style.zIndex, 10);
if (curIndex > defaultIndex) {
defaultIndex = curIndex;
}
});
return defaultIndex;
}
render() { render() {
const { const {
className, className,
@ -301,6 +317,7 @@ export class Modal extends React.Component<ModalProps, ModalState> {
}, },
className className
)} )}
style={{zIndex: this.getZIndex()}}
> >
{overlay ? ( {overlay ? (
<div className={cx(`Modal-overlay`, fadeStyles[status])} /> <div className={cx(`Modal-overlay`, fadeStyles[status])} />

View File

@ -10,7 +10,14 @@ import getMiniDecimal, {
} from '@rc-component/mini-decimal'; } from '@rc-component/mini-decimal';
import {Icon} from './icons'; import {Icon} from './icons';
import {ThemeProps, themeable, isNumeric, autobind, ucFirst} from 'amis-core'; import {
ThemeProps,
themeable,
isNumeric,
autobind,
ucFirst,
isMobile
} from 'amis-core';
export type ValueType = string | number; export type ValueType = string | number;
@ -74,6 +81,7 @@ export interface NumberProps extends ThemeProps {
* *
*/ */
inputControlClassName?: string; inputControlClassName?: string;
useMobileUI?: boolean;
} }
export interface NumberState { export interface NumberState {
@ -304,11 +312,13 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
displayMode, displayMode,
inputRef, inputRef,
keyboard, keyboard,
inputControlClassName inputControlClassName,
useMobileUI
} = this.props; } = this.props;
const precisionProps: any = { const precisionProps: any = {
precision: NumberInput.normalizePrecision(precision, step) precision: NumberInput.normalizePrecision(precision, step)
}; };
const mobileUI = useMobileUI && isMobile();
return ( return (
<InputNumber <InputNumber
@ -320,6 +330,9 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
: inputControlClassName, : inputControlClassName,
{ {
[`Number--border${ucFirst(borderMode)}`]: borderMode [`Number--border${ucFirst(borderMode)}`]: borderMode
},
{
'is-mobile': mobileUI
} }
)} )}
ref={inputRef} ref={inputRef}
@ -353,9 +366,11 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
borderMode, borderMode,
readOnly, readOnly,
displayMode, displayMode,
inputControlClassName inputControlClassName,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<> <>
{displayMode === 'enhance' ? ( {displayMode === 'enhance' ? (

View File

@ -28,7 +28,7 @@ export interface PickerColumnItem {
visibleItemCount?: number; visibleItemCount?: number;
itemHeight?: number; itemHeight?: number;
options?: PickerOption[]; options?: PickerOption[];
optionRender?: (option: string | object | PickerOption) => React.ReactNode; optionRender?: (...params: any) => React.ReactNode;
onChange?: ( onChange?: (
value?: PickerOption | string, value?: PickerOption | string,
index?: number, index?: number,
@ -319,7 +319,7 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
return ( return (
<li {...data} ref={menuItemRef}> <li {...data} ref={menuItemRef}>
{props.optionRender ? ( {props.optionRender ? (
props.optionRender(option) props.optionRender(option, {index, checked: state.index === index})
) : ( ) : (
<div {...childData} /> <div {...childData} />
)} )}

View File

@ -33,6 +33,7 @@ export interface PickerContainerProps
onPickerOpen?: (props: PickerContainerProps) => any; onPickerOpen?: (props: PickerContainerProps) => any;
popOverContainer?: any; popOverContainer?: any;
useMobileUI?: boolean;
} }
export interface PickerContainerState { export interface PickerContainerState {
@ -133,7 +134,8 @@ export class PickerContainer extends React.Component<
size, size,
showFooter, showFooter,
closeOnEsc, closeOnEsc,
popOverContainer popOverContainer,
useMobileUI
} = this.props; } = this.props;
return ( return (
<> <>
@ -156,6 +158,7 @@ export class PickerContainer extends React.Component<
showFooter={showFooter} showFooter={showFooter}
beforeConfirm={this.confirm} beforeConfirm={this.confirm}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
useMobileUI={useMobileUI}
> >
{() => {() =>
popOverRender({ popOverRender({

View File

@ -24,12 +24,14 @@ export interface PopOverContainerProps {
placement?: string; placement?: string;
overlayWidth?: number | string; overlayWidth?: number | string;
overlayWidthField?: 'minWidth' | 'width'; overlayWidthField?: 'minWidth' | 'width';
showConfirm?: boolean;
// 相当于 placement 的简化版 // 相当于 placement 的简化版
align?: OverlayAlignType; align?: OverlayAlignType;
/** Popover层隐藏前触发的事件 */ /** Popover层隐藏前触发的事件 */
onBeforeHide?: () => void; onBeforeHide?: () => void;
/** Popover层隐藏后触发的事件 */ /** Popover层隐藏后触发的事件 */
onAfterHide?: () => void; onAfterHide?: () => void;
onConfirm?: () => void;
} }
export interface PopOverContainerState { export interface PopOverContainerState {
@ -91,6 +93,12 @@ export class PopOverContainer extends React.Component<
return this.getTarget()?.parentElement; return this.getTarget()?.parentElement;
} }
@autobind
onConfirm() {
this.props.onConfirm?.();
this.close();
}
static calcOverlayWidth(overlay: PopOverOverlay, targetWidth: number) { static calcOverlayWidth(overlay: PopOverOverlay, targetWidth: number) {
const overlayWidth = overlay && overlay.width; const overlayWidth = overlay && overlay.width;
@ -142,7 +150,9 @@ export class PopOverContainer extends React.Component<
popOverClassName, popOverClassName,
popOverRender: dropdownRender, popOverRender: dropdownRender,
placement, placement,
align align,
showConfirm,
onConfirm
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile(); const mobileUI = useMobileUI && isMobile();
return ( return (
@ -157,7 +167,9 @@ export class PopOverContainer extends React.Component<
isShow={this.state.isOpened} isShow={this.state.isOpened}
container={popOverContainer} container={popOverContainer}
className={popOverClassName} className={popOverClassName}
showConfirm={showConfirm}
onHide={this.close} onHide={this.close}
onConfirm={this.onConfirm}
> >
{dropdownRender({onClose: this.close})} {dropdownRender({onClose: this.close})}
</PopUp> </PopUp>

View File

@ -71,6 +71,22 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
e.stopPropagation(); e.stopPropagation();
} }
getZIndex() {
let defaultIndex = 3000;
const {classnames: cx} = this.props;
const modals = document.querySelectorAll('.' + cx('Modal'));
const popups = document.querySelectorAll('.' + cx('PopUp'));
[...modals, ...popups].forEach((node: HTMLDialogElement) => {
const style = getComputedStyle(node);
const curIndex = parseInt(style.zIndex, 10);
if (curIndex > defaultIndex) {
defaultIndex = curIndex;
}
});
return defaultIndex;
}
render() { render() {
const { const {
style, style,
@ -93,7 +109,8 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
} = this.props; } = this.props;
const outerStyle: any = { const outerStyle: any = {
...style ...style,
zIndex: this.getZIndex()
}; };
delete outerStyle.top; delete outerStyle.top;
return ( return (

View File

@ -17,6 +17,7 @@ import type {ThemeProps} from 'amis-core';
import {themeable} from 'amis-core'; import {themeable} from 'amis-core';
import {autobind, camel} from 'amis-core'; import {autobind, camel} from 'amis-core';
import {stripNumber} from 'amis-core'; import {stripNumber} from 'amis-core';
import {isMobile} from 'amis-core';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {Icon} from './icons'; import {Icon} from './icons';
@ -54,6 +55,7 @@ interface HandleItemProps extends ThemeProps {
tipFormatter?: (value: Value) => boolean; tipFormatter?: (value: Value) => boolean;
unit?: string; unit?: string;
tooltipPlacement?: string; tooltipPlacement?: string;
useMobileUI?: boolean;
} }
interface LabelProps extends ThemeProps { interface LabelProps extends ThemeProps {
@ -166,6 +168,35 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
}); });
} }
@autobind
onTouchStart() {
this.setState({
isDrag: true,
labelActive: true
});
}
@autobind
onTouchMove(e: any) {
const {isDrag} = this.state;
const {type = 'min'} = this.props;
if (!isDrag) {
return;
}
this.props.onChange(e.touches[0].clientX, type);
}
@autobind
onTouchEnd() {
const {isDrag} = this.state;
if (isDrag) {
return;
}
this.setState({
labelActive: false
});
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -176,7 +207,8 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
tooltipVisible, tooltipVisible,
tipFormatter, tipFormatter,
unit, unit,
tooltipPlacement = 'auto' tooltipPlacement = 'auto',
useMobileUI
} = this.props; } = this.props;
const {isDrag, labelActive} = this.state; const {isDrag, labelActive} = this.state;
const style = { const style = {
@ -184,6 +216,8 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
zIndex: isDrag ? 2 : 1 zIndex: isDrag ? 2 : 1
}; };
const mobileUI = useMobileUI && isMobile();
return disabled ? ( return disabled ? (
<div className={cx('InputRange-handle')} style={style}> <div className={cx('InputRange-handle')} style={style}>
<div className={cx('InputRange-handle-icon')}> <div className={cx('InputRange-handle-icon')}>
@ -192,7 +226,9 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
</div> </div>
) : ( ) : (
<div <div
className={cx('InputRange-handle')} className={cx('InputRange-handle', {
'is-mobile': mobileUI
})}
style={style} style={style}
ref={this.handleRef} ref={this.handleRef}
> >
@ -203,6 +239,9 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
onMouseDown={this.onMouseDown} onMouseDown={this.onMouseDown}
onMouseEnter={this.onMouseEnter} onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
onTouchStart={this.onTouchStart}
onTouchMove={this.onTouchMove}
onTouchEnd={this.onTouchEnd}
> >
<Icon icon="slider-handle" className="icon" /> <Icon icon="slider-handle" className="icon" />
</div> </div>
@ -277,6 +316,7 @@ class Label extends React.Component<LabelProps, any> {
// @todo 丰富这个 // @todo 丰富这个
export interface RangeItemProps extends ThemeProps { export interface RangeItemProps extends ThemeProps {
[propName: string]: any; [propName: string]: any;
useMobileUI?: boolean;
} }
export class Range extends React.Component<RangeItemProps, any> { export class Range extends React.Component<RangeItemProps, any> {
@ -443,13 +483,15 @@ export class Range extends React.Component<RangeItemProps, any> {
* @returns * @returns
*/ */
@autobind @autobind
getOffsetLeft(value: number | string) { getOffsetLeft(value: number | string, getNumber?: boolean) {
const {max, min} = this.props; const {max, min} = this.props;
if (isString(value) && MARKS_REG.test(value)) { if (isString(value) && MARKS_REG.test(value)) {
return value; return getNumber ? parseFloat(value) : value;
} }
value = Math.min(Math.max(+value, min), max); value = Math.min(Math.max(+value, min), max);
return ((value - min) * 100) / (max - min) + '%'; return getNumber
? ((value - min) * 100) / (max - min)
: ((value - min) * 100) / (max - min) + '%';
} }
/** /**
@ -489,7 +531,8 @@ export class Range extends React.Component<RangeItemProps, any> {
unit, unit,
tooltipPlacement, tooltipPlacement,
tipFormatter, tipFormatter,
onAfterChange onAfterChange,
useMobileUI
} = this.props; } = this.props;
// trace // trace
@ -520,6 +563,14 @@ export class Range extends React.Component<RangeItemProps, any> {
}; };
} }
const sortMaks = marks
? keys(marks).sort(
(a: keyof MarksType, b: keyof MarksType) =>
(this.getOffsetLeft(a, true) as number) -
(this.getOffsetLeft(b, true) as number)
)
: [];
return ( return (
<div className={cx('InputRange-wrap')}> <div className={cx('InputRange-wrap')}>
<div <div
@ -550,6 +601,7 @@ export class Range extends React.Component<RangeItemProps, any> {
tooltipVisible={tooltipVisible} tooltipVisible={tooltipVisible}
tipFormatter={tipFormatter} tipFormatter={tipFormatter}
unit={unit} unit={unit}
useMobileUI={useMobileUI}
tooltipPlacement={tooltipPlacement} tooltipPlacement={tooltipPlacement}
onAfterChange={onAfterChange} onAfterChange={onAfterChange}
onChange={this.onGetChangeValue.bind(this)} onChange={this.onGetChangeValue.bind(this)}
@ -566,6 +618,7 @@ export class Range extends React.Component<RangeItemProps, any> {
tooltipVisible={tooltipVisible} tooltipVisible={tooltipVisible}
tipFormatter={tipFormatter} tipFormatter={tipFormatter}
unit={unit} unit={unit}
useMobileUI={useMobileUI}
tooltipPlacement={tooltipPlacement} tooltipPlacement={tooltipPlacement}
onAfterChange={onAfterChange} onAfterChange={onAfterChange}
onChange={this.onChange.bind(this)} onChange={this.onChange.bind(this)}
@ -575,9 +628,10 @@ export class Range extends React.Component<RangeItemProps, any> {
{/* 刻度标记 */} {/* 刻度标记 */}
{marks && ( {marks && (
<div className={cx('InputRange-marks')}> <div className={cx('InputRange-marks')}>
{keys(marks).map((key: keyof MarksType) => { {sortMaks.map((key: keyof MarksType) => {
const offsetLeft = this.getOffsetLeft(key); const offsetLeft = this.getOffsetLeft(key) as string;
const markMaxWidth = this.getMarkMaxWidth(key, marks); const markMaxWidth = this.getMarkMaxWidth(key, marks);
if (MARKS_REG.test(offsetLeft)) { if (MARKS_REG.test(offsetLeft)) {
return ( return (
<div <div

View File

@ -29,16 +29,22 @@ export interface ResultBoxProps
overflowTagPopover?: TooltipObject; overflowTagPopover?: TooltipObject;
actions?: JSX.Element | JSX.Element[]; actions?: JSX.Element | JSX.Element[];
showInvalidMatch?: boolean; showInvalidMatch?: boolean;
showArrow?: boolean;
} }
export class ResultBox extends React.Component<ResultBoxProps> { export class ResultBox extends React.Component<ResultBoxProps> {
static defaultProps: Pick< static defaultProps: Pick<
ResultBoxProps, ResultBoxProps,
'clearable' | 'placeholder' | 'itemRender' | 'inputPlaceholder' | 'clearable'
| 'placeholder'
| 'itemRender'
| 'inputPlaceholder'
| 'showArrow'
> = { > = {
clearable: false, clearable: false,
placeholder: 'placeholder.noData', placeholder: 'placeholder.noData',
inputPlaceholder: 'placeholder.enter', inputPlaceholder: 'placeholder.enter',
showArrow: true,
itemRender: (option: any) => ( itemRender: (option: any) => (
<span>{`${option.scopeLabel || ''}${option.label}`}</span> <span>{`${option.scopeLabel || ''}${option.label}`}</span>
) )
@ -68,6 +74,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
@autobind @autobind
handleFocus(e: any) { handleFocus(e: any) {
console.log('eeeeeeee', e);
const onFocus = this.props.onFocus; const onFocus = this.props.onFocus;
onFocus && onFocus(e); onFocus && onFocus(e);
this.setState({ this.setState({
@ -241,11 +248,14 @@ export class ResultBox extends React.Component<ResultBoxProps> {
onClear, onClear,
maxTagCount, maxTagCount,
overflowTagPopover, overflowTagPopover,
showArrow,
...rest ...rest
} = this.props; } = this.props;
const isFocused = this.state.isFocused; const isFocused = this.state.isFocused;
const mobileUI = useMobileUI && isMobile(); const mobileUI = useMobileUI && isMobile();
console.log('onResultClick-----', onResultClick);
return ( return (
<div <div
className={cx('ResultBox', className, { className={cx('ResultBox', className, {
@ -326,7 +336,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
<Icon icon="right-arrow-bold" className="icon" /> <Icon icon="right-arrow-bold" className="icon" />
</span> </span>
)} )}
{!allowInput && mobileUI ? ( {!allowInput && mobileUI && showArrow ? (
<span className={cx('ResultBox-arrow')}> <span className={cx('ResultBox-arrow')}>
<Icon icon="caret" className="icon" /> <Icon icon="caret" className="icon" />
</span> </span>

View File

@ -6,7 +6,7 @@ import moment from 'moment';
import {ThemeProps, themeable} from 'amis-core'; import {ThemeProps, themeable} from 'amis-core';
import {Icon} from './icons'; import {Icon} from './icons';
import {uncontrollable} from 'amis-core'; import {uncontrollable} from 'amis-core';
import {autobind} from 'amis-core'; import {autobind, isMobile} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core'; import {LocaleProps, localeable} from 'amis-core';
export interface HistoryRecord { export interface HistoryRecord {
@ -48,6 +48,7 @@ export interface SearchBoxProps extends ThemeProps, LocaleProps {
/** 历史记录配置 */ /** 历史记录配置 */
history?: SearchHistoryOptions; history?: SearchHistoryOptions;
clearAndSubmit?: boolean; clearAndSubmit?: boolean;
useMobileUI?: boolean;
} }
export interface SearchBoxState { export interface SearchBoxState {
@ -284,10 +285,12 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
mini, mini,
enhance, enhance,
clearable, clearable,
useMobileUI,
translate: __ translate: __
} = this.props; } = this.props;
const {isFocused, inputValue} = this.state; const {isFocused, inputValue} = this.state;
const {enable} = this.getHistoryOptions(); const {enable} = this.getHistoryOptions();
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
@ -298,7 +301,8 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
disabled ? 'is-disabled' : '', disabled ? 'is-disabled' : '',
isFocused ? 'is-focused' : '', isFocused ? 'is-focused' : '',
!mini || active ? 'is-active' : '', !mini || active ? 'is-active' : '',
{'is-history': enable} {'is-history': enable},
{'is-mobile': mobileUI}
)} )}
style={style} style={style}
> >

View File

@ -41,6 +41,7 @@ import {RemoteOptionsProps, withRemoteConfig} from './WithRemoteConfig';
import Picker from './Picker'; import Picker from './Picker';
import PopUp from './PopUp'; import PopUp from './PopUp';
import BasePopover, {PopOverOverlay} from './PopOverContainer'; import BasePopover, {PopOverOverlay} from './PopOverContainer';
import SelectMobile from './SelectMobile';
import type {TooltipObject} from '../components/TooltipWrapper'; import type {TooltipObject} from '../components/TooltipWrapper';
@ -303,7 +304,7 @@ export function normalizeOptions(
const DownshiftChangeTypes = Downshift.stateChangeTypes; const DownshiftChangeTypes = Downshift.stateChangeTypes;
interface SelectProps export interface SelectProps
extends OptionProps, extends OptionProps,
ThemeProps, ThemeProps,
LocaleProps, LocaleProps,
@ -1145,10 +1146,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
}; };
const mobileUI = isMobile() && useMobileUI; const mobileUI = isMobile() && useMobileUI;
const column = {
labelField: 'label',
options: filtedOptions
};
const menu = ( const menu = (
<div <div
ref={this.menu} ref={this.menu}
@ -1229,14 +1227,21 @@ export class Select extends React.Component<SelectProps, SelectState> {
</div> </div>
); );
return mobileUI ? ( return mobileUI ? (
<PopUp <SelectMobile
className={cx(`Select-popup`)} {...this.props}
container={popOverContainer} highlightedIndex={highlightedIndex}
isShow={this.state.isOpen} isOpen={isOpen}
onHide={this.close} getItemProps={getItemProps}
> getInputProps={getInputProps}
{menu} selectedItem={selectedItem}
</PopUp> onChange={selection => {
this.setState({
isOpen: false
});
this.props.onChange(selection);
}}
onClose={this.close}
/>
) : ( ) : (
<Overlay <Overlay
container={popOverContainer || this.getTarget} container={popOverContainer || this.getTarget}

View File

@ -0,0 +1,520 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import Picker from './Picker';
import PopUp from './PopUp';
import {autobind, highlight} from 'amis-core';
import merge from 'lodash/merge';
// @ts-ignore
import {matchSorter} from 'match-sorter';
import {Option, Options, value2array, SelectProps} from './Select';
import VirtualList from './virtual-list';
import Checkbox from './Checkbox';
import Input from './Input';
import {closeIcon, Icon} from './icons';
interface SelectState {
isFocused: boolean;
inputValue: string;
itemHeight: number;
selection: Array<Option>;
}
interface Props extends SelectProps {
isOpen: boolean;
highlightedIndex: any;
selectedItem: any;
getInputProps: (...params: any) => any;
getItemProps: (...params: any) => any;
onClose: () => void;
}
export default class SelectMobile extends React.Component<Props, SelectState> {
input: HTMLInputElement;
target: HTMLElement;
constructor(props: Props) {
super(props);
this.state = {
selection: value2array(props.value, props),
isFocused: false,
inputValue: '',
itemHeight: 32 /** Select选项高度保持一致 */
};
}
@autobind
handleChange([item]: any) {
const {onChange, multiple, simpleValue, valueField, options} = this.props;
let {selection} = this.state;
if (!multiple && item === undefined) {
console.log('执行了');
}
// 单选是字符串
const selectItem = options.find((option: Option) =>
multiple
? option[valueField] === item[valueField]
: option[valueField] === item
);
if (multiple) {
const selectionValues = selection.map(item => item[valueField]);
selection = selection.concat();
const idx = selectionValues.indexOf(selectItem?.[valueField]);
if (~idx) {
selection.splice(idx, 1);
} else {
selectItem && selection.push(selectItem);
}
this.setState({
selection
});
} else {
this.setState({
selection: selectItem ? [selectItem] : []
});
}
}
@autobind
handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
const {loadOptions} = this.props;
this.setState(
{
inputValue: evt.currentTarget.value
},
() => loadOptions && loadOptions(this.state.inputValue)
);
}
@autobind
getTarget() {
if (!this.target) {
this.target = findDOMNode(this) as HTMLElement;
}
return this.target as HTMLElement;
}
@autobind
inputRef(ref: HTMLInputElement) {
this.input = ref;
}
@autobind
toggleCheckAll() {
const {
options,
onChange,
simpleValue,
checkAllBySearch,
labelField,
valueField
} = this.props;
const inputValue = this.state.inputValue;
let {selection} = this.state;
let filtedOptions: Array<Option> =
inputValue && checkAllBySearch !== false
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat();
const optionsValues = filtedOptions.map(option => option.value);
const selectionValues = selection.map(select => select.value);
const checkedAll = optionsValues.every(
option => selectionValues.indexOf(option) > -1
);
selection = checkedAll ? [] : filtedOptions;
this.setState({selection});
}
@autobind
handleAddClick() {
const {onAdd} = this.props;
onAdd && onAdd();
}
@autobind
handleEditClick(e: Event, item: any) {
const {onEdit} = this.props;
e.preventDefault();
e.stopPropagation();
onEdit && onEdit(item);
}
@autobind
handleDeleteClick(e: Event, item: any) {
const {onDelete} = this.props;
e.preventDefault();
e.stopPropagation();
onDelete && onDelete(item);
}
@autobind
onFocus(e: any) {
const {simpleValue} = this.props;
const {selection} = this.state;
const value = simpleValue ? selection.map(item => item.value) : selection;
this.props.disabled ||
this.props.isOpen ||
this.setState(
{
isFocused: true
},
this.focus
);
this.props.onFocus &&
this.props.onFocus({
...e,
value
});
}
@autobind
onBlur(e: any) {
const {simpleValue} = this.props;
const {selection} = this.state;
const value = simpleValue ? selection.map(item => item.value) : selection;
this.setState({
isFocused: false
});
this.props.onBlur &&
this.props.onBlur({
...e,
value
});
}
@autobind
focus() {
this.input
? this.input.focus()
: this.getTarget() && this.getTarget().focus();
}
blur() {
this.input
? this.input.blur()
: this.getTarget() && this.getTarget().blur();
}
@autobind
clearSearchValue() {
const {loadOptions} = this.props;
this.setState(
{
inputValue: ''
},
() => loadOptions?.('')
);
}
@autobind
onConfirm() {
const {selection} = this.state;
const {
multiple,
onChange,
simpleValue,
valueField,
options,
loadOptions,
labelField
} = this.props;
if (multiple) {
onChange(
simpleValue ? selection.map(item => item[valueField]) : selection
);
} else {
const inputValue = this.state.inputValue;
let filtedOptions: Array<Option> = (
inputValue && !loadOptions
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat()
).filter((option: Option) => !option.hidden && option.visible !== false);
// picker 打开未滑动时选中第一项
if (!selection.length && filtedOptions.length) {
onChange(
simpleValue ? filtedOptions[0]?.[valueField] : filtedOptions[0]
);
} else {
onChange(simpleValue ? selection[0]?.[valueField] : selection[0]);
}
}
}
render() {
const {
popOverContainer,
options,
value,
valueField,
labelField,
noResultsText,
loadOptions,
creatable,
multiple,
valuesNoWrap,
classnames: cx,
popoverClassName,
popOverContainerSelector,
checkAll,
checkAllLabel,
checkAllBySearch,
searchable,
createBtnLabel,
disabled,
searchPromptText,
editable,
removable,
overlayPlacement,
translate: __,
hideSelected,
renderMenu,
mobileClassName,
virtualThreshold = 100,
useMobileUI = false,
overlay,
isOpen,
highlightedIndex,
onClose,
getInputProps,
getItemProps,
selectedItem
} = this.props;
const {selection} = this.state;
const inputValue = this.state.inputValue;
let checkedAll = false;
let checkedPartial = false;
let filtedOptions: Array<Option> = (
inputValue && isOpen && !loadOptions
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat()
).filter((option: Option) => !option.hidden && option.visible !== false);
const enableVirtualRender =
filtedOptions.length && filtedOptions.length > virtualThreshold;
const selectionValues = selection.map(select => select[valueField]);
if (multiple && checkAll) {
const optionsValues = (
checkAllBySearch !== false ? filtedOptions : options
).map(option => option[valueField]);
checkedAll = optionsValues.every(
option => selectionValues.indexOf(option) > -1
);
checkedPartial = optionsValues.some(
option => selectionValues.indexOf(option) > -1
);
}
// 用于虚拟渲染的每项高度
const virtualItemHeight = this.props.itemHeight || this.state.itemHeight;
// 渲染单个选项
const renderItem = ({index, style}: {index: number; style?: object}) => {
const item = filtedOptions[index];
if (!item) {
return null;
}
const checked =
selectedItem === item || !!~selectionValues.indexOf(item[valueField]);
if (hideSelected && checked) {
return null;
}
return (
<div
{...getItemProps({
key:
typeof item.value === 'string'
? `${item.label}-${item.value}`
: index,
index,
item,
disabled: item.disabled
})}
style={merge(style, enableVirtualRender ? {width: '100%'} : {})}
className={cx(`Select-option`, {
'is-disabled': item.disabled,
'is-active': checked,
'is-mobile': true
})}
>
{renderMenu ? (
multiple ? (
renderMenu(item, {
multiple,
checkAll,
checked,
onChange: () => this.handleChange(item),
inputValue: inputValue || '',
searchable,
index
})
) : (
renderMenu(item, {
multiple,
checkAll,
checked,
onChange: () => this.handleChange(item),
inputValue: inputValue || '',
searchable,
index
})
)
) : multiple ? (
<>
<div
title={item[labelField]}
className={cx('Select-option-item-check')}
onClick={() => !item.disabled && this.handleChange([item])}
>
{item.disabled
? item[labelField]
: highlight(
item[labelField],
inputValue as string,
cx('Select-option-hl')
)}
{item.tip}
</div>
{checked ? (
<Icon icon="check" className={cx('Select-option-mcheck')} />
) : null}
</>
) : (
<span
className={cx('Select-option-content')}
title={
typeof item[labelField] === 'string' ? item[labelField] : ''
}
>
{item.disabled
? item[labelField]
: highlight(
item[labelField],
inputValue as string,
cx('Select-option-hl')
)}
{item.tip}
</span>
)}
</div>
);
};
const menu = (
<div
className={cx('Select-menu', {
'Select--longlist': enableVirtualRender,
'is-mobile': true
})}
>
{searchable ? (
<div
className={cx(`Select-input`, {
'is-focused': this.state.isFocused
})}
>
<Icon icon="search" className="icon" />
<Input
{...getInputProps({
onFocus: this.onFocus,
onBlur: this.onBlur,
disabled: disabled,
placeholder: __(searchPromptText),
onChange: this.handleInputChange,
ref: this.inputRef
})}
/>
{inputValue?.length ? (
<a onClick={this.clearSearchValue} className={cx('Select-clear')}>
<Icon icon="close" className="icon" />
</a>
) : null}
</div>
) : null}
{multiple && valuesNoWrap ? (
<div className={cx('Select-option')}>
({selectionValues.length})
</div>
) : null}
{multiple && checkAll && filtedOptions.length ? (
<div className={cx('Select-option')}>
<Checkbox
checked={checkedPartial}
partial={checkedPartial && !checkedAll}
onChange={this.toggleCheckAll}
size="sm"
>
{__(checkAllLabel)}
</Checkbox>
</div>
) : null}
{filtedOptions.length ? (
filtedOptions.length > virtualThreshold ? ( // 较多数据时才启用 virtuallist避免滚动条问题
<VirtualList
height={
filtedOptions.length > 8
? 266
: filtedOptions.length * virtualItemHeight
}
itemCount={filtedOptions.length}
itemSize={virtualItemHeight}
renderItem={renderItem}
/>
) : (
filtedOptions.map((item, index) => {
return renderItem({index});
})
)
) : (
<div className={cx('Select-noResult')}>{__(noResultsText)}</div>
)}
</div>
);
return (
<PopUp
className={cx(`Select-popup`)}
container={popOverContainer}
isShow={isOpen}
showConfirm={true}
onConfirm={this.onConfirm}
onHide={onClose}
>
{multiple ? (
menu
) : (
<Picker
className={'Select-picker'}
columns={{
options: filtedOptions as Option[],
optionRender: renderMenu
}}
onChange={item => this.handleChange(item as any)}
showToolbar={false}
labelField={labelField}
valueField={valueField}
itemHeight={40}
value={[selection[0]?.[valueField]]}
/>
)}
</PopUp>
);
}
}

View File

@ -15,7 +15,7 @@ import {Icon} from './icons';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import TooltipWrapper from './TooltipWrapper'; import TooltipWrapper from './TooltipWrapper';
import {resizeSensor} from 'amis-core'; import {resizeSensor, isMobile} from 'amis-core';
import PopOverContainer from './PopOverContainer'; import PopOverContainer from './PopOverContainer';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
@ -45,6 +45,8 @@ export interface TabProps extends ThemeProps {
iconPosition?: 'left' | 'right'; iconPosition?: 'left' | 'right';
disabled?: boolean | string; disabled?: boolean | string;
eventKey: string | number; eventKey: string | number;
prevKey?: string | number;
nextKey?: string | number;
tab?: Schema; tab?: Schema;
className?: string; className?: string;
activeKey?: string | number; activeKey?: string | number;
@ -53,12 +55,52 @@ export interface TabProps extends ThemeProps {
unmountOnExit?: boolean; unmountOnExit?: boolean;
toolbar?: React.ReactNode; toolbar?: React.ReactNode;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
swipeable?: boolean;
onSelect?: (eventKey: string | number) => void;
} }
class TabComponent extends React.PureComponent<TabProps> { class TabComponent extends React.PureComponent<TabProps> {
contentDom: any; contentDom: any;
touch: any = {};
touchStartTime: number;
contentRef = (ref: any) => (this.contentDom = ref); contentRef = (ref: any) => (this.contentDom = ref);
@autobind
onTouchStart(event: TouchEvent) {
this.touch.startX = event.touches[0].clientX;
this.touch.startY = event.touches[0].clientY;
this.touchStartTime = Date.now();
}
@autobind
onTouchMove(event: TouchEvent) {
const touch = event.touches[0];
const newState = {...this.touch};
newState.deltaX = touch.clientX < 0 ? 0 : touch.clientX - newState.startX;
newState.deltaY = touch.clientY - newState.startY;
newState.offsetX = Math.abs(newState.deltaX);
newState.offsetY = Math.abs(newState.deltaY);
this.touch = newState;
}
@autobind
onTouchEnd() {
const duration = Date.now() - this.touchStartTime;
const speed = this.touch.deltaX / duration;
const shouldSwipe = Math.abs(speed) > 0.25;
const {prevKey, nextKey, onSelect} = this.props;
if (shouldSwipe) {
if (this.touch.deltaX > 0) {
prevKey !== undefined && onSelect?.(prevKey);
} else {
nextKey && onSelect?.(nextKey);
}
}
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -68,9 +110,13 @@ class TabComponent extends React.PureComponent<TabProps> {
eventKey, eventKey,
activeKey, activeKey,
children, children,
className className,
swipeable,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<Transition <Transition
in={activeKey === eventKey} in={activeKey === eventKey}
@ -91,6 +137,10 @@ class TabComponent extends React.PureComponent<TabProps> {
'Tabs-pane', 'Tabs-pane',
className className
)} )}
onTouchStart={swipeable && mobileUI && this.onTouchStart}
onTouchMove={swipeable && mobileUI && this.onTouchMove}
onTouchEnd={swipeable && mobileUI && this.onTouchEnd}
onTouchCancel={swipeable && mobileUI && this.onTouchEnd}
> >
{children} {children}
</div> </div>
@ -132,6 +182,7 @@ export interface TabsProps extends ThemeProps, LocaleProps {
collapseBtnLabel?: string; collapseBtnLabel?: string;
popOverContainer?: any; popOverContainer?: any;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
} }
export interface IDragInfo { export interface IDragInfo {
@ -271,6 +322,29 @@ export class Tabs extends React.Component<TabsProps, any> {
if (!this.scroll && !this.draging && isTabsModified) { if (!this.scroll && !this.draging && isTabsModified) {
this.computedWidth(); this.computedWidth();
} }
// 移动端取消箭头切换,改为滚动切换激活项居中
const {classPrefix: ns, activeKey, useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI && preProps.activeKey !== activeKey) {
const {classPrefix: ns} = this.props;
const dom = findDOMNode(this) as HTMLElement;
const activeTab = dom.querySelector(
`.${ns}Tabs-link.is-active`
) as HTMLElement;
const parentWidth = (activeTab.parentNode?.parentNode as any).offsetWidth;
const offsetLeft = activeTab.offsetLeft;
const offsetWidth = activeTab.offsetWidth;
if (activeTab.parentNode) {
(activeTab.parentNode as any).scrollLeft =
offsetLeft > parentWidth
? (offsetLeft / parentWidth) * parentWidth -
parentWidth / 2 +
offsetWidth / 2
: offsetLeft - parentWidth / 2 + offsetWidth / 2;
}
}
this.scroll = false; this.scroll = false;
} }
@ -760,9 +834,11 @@ export class Tabs extends React.Component<TabsProps, any> {
addable, addable,
draggable, draggable,
sidePosition, sidePosition,
addBtnText addBtnText,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
const {isOverflow} = this.state; const {isOverflow} = this.state;
if (!Array.isArray(children)) { if (!Array.isArray(children)) {
return null; return null;
@ -811,10 +887,12 @@ export class Tabs extends React.Component<TabsProps, any> {
isOverflow && 'Tabs-linksContainer--overflow' isOverflow && 'Tabs-linksContainer--overflow'
)} )}
> >
{this.renderArrow('left')} {!mobileUI ? this.renderArrow('left') : null}
<div className={cx('Tabs-linksContainer-main')}> <div className={cx('Tabs-linksContainer-main')}>
<ul <ul
className={cx('Tabs-links', linksClassName)} className={cx('Tabs-links', linksClassName, {
'is-mobile': mobileUI
})}
role="tablist" role="tablist"
ref={this.navMain} ref={this.navMain}
> >
@ -823,13 +901,18 @@ export class Tabs extends React.Component<TabsProps, any> {
{!isOverflow && toolButtons} {!isOverflow && toolButtons}
</ul> </ul>
</div> </div>
{this.renderArrow('right')} {!mobileUI ? this.renderArrow('right') : null}
</div> </div>
{isOverflow && toolButtons} {isOverflow && toolButtons}
</div> </div>
) : ( ) : (
<div className={cx('Tabs-linksWrapper')}> <div className={cx('Tabs-linksWrapper')}>
<ul className={cx('Tabs-links', linksClassName)} role="tablist"> <ul
className={cx('Tabs-links', linksClassName, {
'is-mobile': mobileUI
})}
role="tablist"
>
{this.renderNavs()} {this.renderNavs()}
{additionBtns} {additionBtns}
{toolbar} {toolbar}

View File

@ -15,6 +15,7 @@ import {ItemRenderStates} from './Selection';
import {Icon} from './icons'; import {Icon} from './icons';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import {SpinnerExtraProps} from './Spinner'; import {SpinnerExtraProps} from './Spinner';
import {isMobile} from 'amis-core';
export interface TabsTransferProps export interface TabsTransferProps
extends Omit< extends Omit<
@ -51,6 +52,7 @@ export interface TabsTransferProps
activeKey: number; activeKey: number;
onlyChildren?: boolean; onlyChildren?: boolean;
ctx?: Record<string, any>; ctx?: Record<string, any>;
useMobileUI?: boolean;
} }
export interface TabsTransferState { export interface TabsTransferState {
@ -266,9 +268,11 @@ export class TabsTransfer extends React.Component<
activeKey, activeKey,
classnames: cx, classnames: cx,
translate: __, translate: __,
ctx ctx,
useMobileUI
} = this.props; } = this.props;
const showOptions = options.filter(item => item.visible !== false); const showOptions = options.filter(item => item.visible !== false);
const mobileUI = useMobileUI && isMobile();
if (!Array.isArray(options) || !options.length) { if (!Array.isArray(options) || !options.length) {
return ( return (
@ -296,7 +300,9 @@ export class TabsTransfer extends React.Component<
className="TabsTransfer-tab" className="TabsTransfer-tab"
> >
{option.searchable ? ( {option.searchable ? (
<div className={cx('TabsTransfer-search')}> <div
className={cx('TabsTransfer-search', {'is-mobile': mobileUI})}
>
<InputBox <InputBox
value={this.state.inputValue} value={this.state.inputValue}
onChange={(text: string) => this.handleSearch(text, option)} onChange={(text: string) => this.handleSearch(text, option)}
@ -478,12 +484,14 @@ export class TabsTransfer extends React.Component<
classnames: cx, classnames: cx,
optionItemRender, optionItemRender,
onSearch, onSearch,
useMobileUI,
...reset ...reset
} = this.props; } = this.props;
return ( return (
<Transfer <Transfer
{...reset} {...reset}
useMobileUI={useMobileUI}
statistics={false} statistics={false}
classnames={cx} classnames={cx}
className={cx('TabsTransfer', className)} className={cx('TabsTransfer', className)}

View File

@ -1,4 +1,4 @@
import {localeable} from 'amis-core'; import {isMobile, localeable} from 'amis-core';
import {themeable} from 'amis-core'; import {themeable} from 'amis-core';
import {uncontrollable} from 'amis-core'; import {uncontrollable} from 'amis-core';
import React from 'react'; import React from 'react';
@ -14,6 +14,7 @@ export interface TabsTransferPickerProps
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
onFocus?: () => void; onFocus?: () => void;
onBlur?: () => void; onBlur?: () => void;
useMobileUI?: boolean;
} }
export class TransferPicker extends React.Component<TabsTransferPickerProps> { export class TransferPicker extends React.Component<TabsTransferPickerProps> {
@ -45,12 +46,15 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
onChange, onChange,
size, size,
labelField = 'label', labelField = 'label',
useMobileUI,
...rest ...rest
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<PickerContainer <PickerContainer
title={__('Select.placeholder')} title={__('Select.placeholder')}
useMobileUI={useMobileUI}
onFocus={this.onFoucs} onFocus={this.onFoucs}
onClose={this.onBlur} onClose={this.onBlur}
bodyRender={({onClose, value, onChange, setState, ...states}) => { bodyRender={({onClose, value, onChange, setState, ...states}) => {
@ -59,6 +63,7 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
{...rest} {...rest}
{...states} {...states}
value={value} value={value}
useMobileUI={useMobileUI}
onChange={(value: any, optionModified) => { onChange={(value: any, optionModified) => {
if (optionModified) { if (optionModified) {
let options = mapTree(rest.options, item => { let options = mapTree(rest.options, item => {
@ -96,10 +101,13 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
itemRender={option => ( itemRender={option => (
<span>{(option && option[labelField]) || 'undefiend'}</span> <span>{(option && option[labelField]) || 'undefiend'}</span>
)} )}
useMobileUI={useMobileUI}
> >
{!mobileUI ? (
<span className={cx('TransferPicker-icon')}> <span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" /> <Icon icon="pencil" className="icon" />
</span> </span>
) : null}
</ResultBox> </ResultBox>
)} )}
</PickerContainer> </PickerContainer>

View File

@ -60,6 +60,7 @@ export interface TextAreaProps extends ThemeProps, LocaleProps {
disabled?: boolean; disabled?: boolean;
forwardRef?: {current: HTMLTextAreaElement | null}; forwardRef?: {current: HTMLTextAreaElement | null};
useMobileUI?: boolean;
} }
export interface TextAreaState { export interface TextAreaState {

View File

@ -13,6 +13,7 @@ import {ThemeProps, themeable, findTree} from 'amis-core';
import {BaseSelectionProps, BaseSelection, ItemRenderStates} from './Selection'; import {BaseSelectionProps, BaseSelection, ItemRenderStates} from './Selection';
import {Options, Option} from './Select'; import {Options, Option} from './Select';
import {uncontrollable} from 'amis-core'; import {uncontrollable} from 'amis-core';
import {isMobile} from 'amis-core';
import ResultList from './ResultList'; import ResultList from './ResultList';
import TableSelection from './TableSelection'; import TableSelection from './TableSelection';
import {autobind, flattenTree} from 'amis-core'; import {autobind, flattenTree} from 'amis-core';
@ -115,9 +116,11 @@ export interface TransferProps
checkAllLabel?: string; checkAllLabel?: string;
/** 树形模式下,给 tree 的属性 */ /** 树形模式下,给 tree 的属性 */
onlyChildren?: boolean; onlyChildren?: boolean;
useMobileUI?: boolean;
} }
export interface TransferState { export interface TransferState {
tempValue?: Array<Option> | Option;
inputValue: string; inputValue: string;
searchResult: Options | null; searchResult: Options | null;
isTreeDeferLoad: boolean; isTreeDeferLoad: boolean;
@ -366,7 +369,8 @@ export class Transfer<
options, options,
statistics, statistics,
translate: __, translate: __,
searchPlaceholder = __('Transfer.searchKeyword') searchPlaceholder = __('Transfer.searchKeyword'),
useMobileUI
} = props; } = props;
if (selectRender) { if (selectRender) {
@ -395,6 +399,8 @@ export class Transfer<
isEqual isEqual
).length; ).length;
const mobileUI = useMobileUI && isMobile();
return ( return (
<> <>
<div <div
@ -437,7 +443,7 @@ export class Transfer<
</div> </div>
{onSearch ? ( {onSearch ? (
<div className={cx('Transfer-search')}> <div className={cx('Transfer-search', {'is-mobile': mobileUI})}>
<InputBox <InputBox
value={this.state.inputValue} value={this.state.inputValue}
onChange={this.handleSearch} onChange={this.handleSearch}
@ -818,7 +824,8 @@ export class Transfer<
resultListModeFollowSelect, resultListModeFollowSelect,
selectMode = 'list', selectMode = 'list',
translate: __, translate: __,
valueField = 'value' valueField = 'value',
useMobileUI
} = this.props as any; } = this.props as any;
const {searchResult} = this.state; const {searchResult} = this.state;
@ -832,6 +839,7 @@ export class Transfer<
); );
const tableType = resultListModeFollowSelect && selectMode === 'table'; const tableType = resultListModeFollowSelect && selectMode === 'table';
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
@ -847,7 +855,7 @@ export class Transfer<
</div> </div>
) : null} ) : null}
</div> </div>
<div className={cx('Transfer-result')}> <div className={cx('Transfer-result', {'is-mobile': mobileUI})}>
<div <div
className={cx( className={cx(
'Transfer-title', 'Transfer-title',

View File

@ -1,6 +1,6 @@
import {localeable} from 'amis-core'; import {localeable} from 'amis-core';
import {themeable} from 'amis-core'; import {themeable} from 'amis-core';
import {Transfer, TransferProps} from './Transfer'; import {Transfer, TransferProps, TransferState} from './Transfer';
import {uncontrollable, autobind} from 'amis-core'; import {uncontrollable, autobind} from 'amis-core';
import React from 'react'; import React from 'react';
import ResultBox from './ResultBox'; import ResultBox from './ResultBox';
@ -29,11 +29,51 @@ export interface TransferDropDownProps extends TransferProps {
} }
export class TransferDropDown extends Transfer<TransferDropDownProps> { export class TransferDropDown extends Transfer<TransferDropDownProps> {
constructor(props: TransferDropDownProps) {
super(props);
this.state = {
tempValue: props.value,
inputValue: '',
searchResult: null,
isTreeDeferLoad: false,
resultSelectMode: 'list'
};
}
componentDidUpdate(prevProps: TransferDropDownProps) {
if (this.props.value !== prevProps.value) {
this.setState({
tempValue: this.props.value
});
}
}
@autobind @autobind
handleAfterPopoverHide() { handleAfterPopoverHide() {
this.setState({inputValue: '', searchResult: null}); this.setState({inputValue: '', searchResult: null});
} }
@autobind
handleChange(value: any, onClose: () => void) {
const {multiple, onChange, useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
this.setState({tempValue: value});
} else {
onChange?.(value);
if (!multiple) {
onClose();
}
}
}
@autobind
onConfirm() {
const {onChange} = this.props;
onChange?.(this.state.tempValue as typeof Option[]);
}
render() { render() {
const { const {
classnames: cx, classnames: cx,
@ -68,6 +108,8 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
overlayWidth={overlay && overlay?.width} overlayWidth={overlay && overlay?.width}
align={overlay && overlay?.align} align={overlay && overlay?.align}
popOverClassName={cx('TransferDropDown-popover')} popOverClassName={cx('TransferDropDown-popover')}
showConfirm
onConfirm={this.onConfirm}
popOverRender={({onClose}) => ( popOverRender={({onClose}) => (
<div <div
className={cx('TransferDropDown-content', { className={cx('TransferDropDown-content', {
@ -96,24 +138,14 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
{searchResult !== null {searchResult !== null
? this.renderSearchResult({ ? this.renderSearchResult({
...this.props, ...this.props,
value, value: this.state.tempValue,
onChange: multiple onChange: value => this.handleChange(value, onClose),
? onChange
: (value: any) => {
onClose();
onChange?.(value);
},
multiple multiple
}) })
: this.renderOptions({ : this.renderOptions({
...this.props, ...this.props,
value, value: this.state.tempValue,
onChange: multiple onChange: value => this.handleChange(value, onClose),
? onChange
: (value: any) => {
onClose();
onChange?.(value);
},
multiple multiple
})} })}
</div> </div>

View File

@ -6,7 +6,7 @@ import React from 'react';
import ResultBox from './ResultBox'; import ResultBox from './ResultBox';
import {Icon} from './icons'; import {Icon} from './icons';
import PickerContainer from './PickerContainer'; import PickerContainer from './PickerContainer';
import {autobind, mapTree} from 'amis-core'; import {autobind, mapTree, isMobile} from 'amis-core';
export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> { export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
// 新的属性? // 新的属性?
@ -20,6 +20,7 @@ export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
onFocus?: () => void; onFocus?: () => void;
onBlur?: () => void; onBlur?: () => void;
useMobileUI?: boolean;
} }
export class TransferPicker extends React.Component<TransferPickerProps> { export class TransferPicker extends React.Component<TransferPickerProps> {
@ -51,17 +52,21 @@ export class TransferPicker extends React.Component<TransferPickerProps> {
size, size,
borderMode, borderMode,
labelField = 'label', labelField = 'label',
useMobileUI,
...rest ...rest
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<PickerContainer <PickerContainer
title={__('Select.placeholder')} title={__('Select.placeholder')}
onFocus={this.onFoucs} onFocus={this.onFoucs}
onClose={this.onBlur} onClose={this.onBlur}
useMobileUI={useMobileUI}
bodyRender={({onClose, value, onChange, setState, ...states}) => { bodyRender={({onClose, value, onChange, setState, ...states}) => {
return ( return (
<Transfer <Transfer
useMobileUI={useMobileUI}
{...rest} {...rest}
{...states} {...states}
value={value} value={value}
@ -103,10 +108,13 @@ export class TransferPicker extends React.Component<TransferPickerProps> {
itemRender={option => ( itemRender={option => (
<span>{(option && option[labelField]) || 'undefined'}</span> <span>{(option && option[labelField]) || 'undefined'}</span>
)} )}
useMobileUI={useMobileUI}
> >
{!mobileUI ? (
<span className={cx('TransferPicker-icon')}> <span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" /> <Icon icon="pencil" className="icon" />
</span> </span>
) : null}
</ResultBox> </ResultBox>
)} )}
</PickerContainer> </PickerContainer>

View File

@ -32,7 +32,7 @@ import {Option, Options, value2array} from './Select';
import {themeable, ThemeProps, highlight} from 'amis-core'; import {themeable, ThemeProps, highlight} from 'amis-core';
import {Icon, getIcon} from './icons'; import {Icon, getIcon} from './icons';
import Checkbox from './Checkbox'; import Checkbox from './Checkbox';
import {LocaleProps, localeable} from 'amis-core'; import {LocaleProps, localeable, isMobile} from 'amis-core';
import Spinner, {SpinnerExtraProps} from './Spinner'; import Spinner, {SpinnerExtraProps} from './Spinner';
import {ItemRenderStates} from './Selection'; import {ItemRenderStates} from './Selection';
import VirtualList from './virtual-list'; import VirtualList from './virtual-list';
@ -149,6 +149,7 @@ interface TreeSelectorProps extends ThemeProps, LocaleProps, SpinnerExtraProps {
// 全选按钮文案 // 全选按钮文案
checkAllLabel?: string; checkAllLabel?: string;
enableDefaultIcon?: boolean; enableDefaultIcon?: boolean;
useMobileUI?: boolean;
} }
interface TreeSelectorState { interface TreeSelectorState {
@ -716,12 +717,21 @@ export class TreeSelector extends React.Component<
} }
renderInput(prfix: JSX.Element | null = null) { renderInput(prfix: JSX.Element | null = null) {
const {classnames: cx, translate: __} = this.props; const {classnames: cx, useMobileUI, translate: __} = this.props;
const {inputValue} = this.state; const {inputValue} = this.state;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div className={cx('Tree-itemLabel')}> <div
<div className={cx('Tree-itemInput')}> className={cx('Tree-itemLabel', {
'is-mobile': mobileUI
})}
>
<div
className={cx('Tree-itemInput', {
'is-mobile': mobileUI
})}
>
{prfix} {prfix}
<input <input
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -1064,9 +1074,11 @@ export class TreeSelector extends React.Component<
draggable, draggable,
loadingConfig, loadingConfig,
enableDefaultIcon, enableDefaultIcon,
valueField valueField,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
const item = this.state.flattenedOptions[index]; const item = this.state.flattenedOptions[index];
if (!item) { if (!item) {
@ -1160,7 +1172,7 @@ export class TreeSelector extends React.Component<
{checkbox} {checkbox}
<div className={cx('Tree-itemLabel-item')}> <div className={cx('Tree-itemLabel-item', {'is-mobile': mobileUI})}>
{showIcon ? ( {showIcon ? (
<i <i
className={cx( className={cx(
@ -1308,7 +1320,8 @@ export class TreeSelector extends React.Component<
checkAllLabel, checkAllLabel,
classnames: cx, classnames: cx,
translate: __, translate: __,
disabled disabled,
useMobileUI
} = this.props; } = this.props;
if (!multiple || !checkAll) { if (!multiple || !checkAll) {
@ -1323,6 +1336,7 @@ export class TreeSelector extends React.Component<
const checkedPartial = availableOptions.some(option => const checkedPartial = availableOptions.some(option =>
this.isItemChecked(option) this.isItemChecked(option)
); );
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
@ -1336,7 +1350,11 @@ export class TreeSelector extends React.Component<
partial={checkedPartial && !checkedAll} partial={checkedPartial && !checkedAll}
/> />
<div className={cx('Tree-itemLabel-item')}> <div
className={cx('Tree-itemLabel-item', {
'is-mobile': mobileUI
})}
>
<span className={cx('Tree-itemText')}>{__(checkAllLabel)}</span> <span className={cx('Tree-itemText')}>{__(checkAllLabel)}</span>
</div> </div>
</div> </div>

View File

@ -14,6 +14,7 @@ import {
import {PickerOption} from '../PickerColumn'; import {PickerOption} from '../PickerColumn';
import 'moment/locale/zh-cn'; import 'moment/locale/zh-cn';
import 'moment/locale/de'; import 'moment/locale/de';
import {isMobile} from 'amis-core';
export type DateType = export type DateType =
| 'year' | 'year'
@ -690,6 +691,8 @@ class BaseDatePicker extends React.Component<
key="dt" key="dt"
className={cx( className={cx(
'rdtPicker', 'rdtPicker',
{'is-mobile-year': isMobile() && viewMode === 'years'},
{'is-mobile-embed': isMobile() && viewProps.embed},
timeFormat && !dateFormat timeFormat && !dateFormat
? 'rdtPickerTimeWithoutD' ? 'rdtPickerTimeWithoutD'
: timeFormat && dateFormat : timeFormat && dateFormat

View File

@ -683,9 +683,11 @@ export class CustomTimeView extends React.Component<
<div <div
key={option.value} key={option.value}
className={cx('CalendarInput-sugsItem', { className={cx('CalendarInput-sugsItem', {
'is-mobile': isMobile(),
'is-highlight': selectedDate 'is-highlight': selectedDate
? option.value === date.format(formatMap[type]) ? option.value === date.format(formatMap[type])
: option.value === options?.[0]?.value : option.value === options?.[0]?.value &&
!isMobile()
})} })}
onClick={() => { onClick={() => {
this.setTime(type, parseInt(option.value, 10)); this.setTime(type, parseInt(option.value, 10));

View File

@ -22,6 +22,7 @@ import FuncList from './FuncList';
import VariableList from './VariableList'; import VariableList from './VariableList';
import CodeMirrorEditor from '../CodeMirror'; import CodeMirrorEditor from '../CodeMirror';
import {toast} from '../Toast'; import {toast} from '../Toast';
import {isMobile} from 'amis-core';
export interface VariableItem { export interface VariableItem {
label: string; label: string;
@ -358,7 +359,9 @@ export class FormulaEditor extends React.Component<
/> />
</section> </section>
<section className={cx('FormulaEditor-settings')}> <section
className={cx('FormulaEditor-settings', {'is-mobile': isMobile()})}
>
<div className={cx('FormulaEditor-panel')}> <div className={cx('FormulaEditor-panel')}>
{variableMode !== 'tabs' ? ( {variableMode !== 'tabs' ? (
<div className={cx('FormulaEditor-panel-header')}> <div className={cx('FormulaEditor-panel-header')}>

View File

@ -44,7 +44,7 @@ export function FuncList(props: FuncListProps) {
<div className={cx('FormulaEditor-panel-header')}>{title}</div> <div className={cx('FormulaEditor-panel-header')}>{title}</div>
<div className={cx('FormulaEditor-panel-body')}> <div className={cx('FormulaEditor-panel-body')}>
<div className={cx('FormulaEditor-FuncList-searchBox')}> <div className={cx('FormulaEditor-FuncList-searchBox')}>
<SearchBox mini={false} onSearch={onSearch} /> <SearchBox mini={false} onSearch={onSearch} useMobileUI />
</div> </div>
<div className={cx('FormulaEditor-FuncList-body', bodyClassName)}> <div className={cx('FormulaEditor-FuncList-body', bodyClassName)}>
<CollapseGroup <CollapseGroup

View File

@ -20,7 +20,8 @@ import ResultBox from '../ResultBox';
import Button from '../Button'; import Button from '../Button';
import {Icon} from '../icons'; import {Icon} from '../icons';
import Modal from '../Modal'; import Modal from '../Modal';
import Input from '../Input'; import PopUp from '../PopUp';
import {isMobile} from 'amis-core';
export interface FormulaPickerProps extends FormulaEditorProps { export interface FormulaPickerProps extends FormulaEditorProps {
// 新的属性? // 新的属性?
@ -123,6 +124,7 @@ export interface FormulaPickerProps extends FormulaEditorProps {
onRef?: (node: any) => void; onRef?: (node: any) => void;
popOverContainer?: any; popOverContainer?: any;
useMobileUI?: boolean;
} }
export interface FormulaPickerState { export interface FormulaPickerState {
@ -333,10 +335,12 @@ export class FormulaPicker extends React.Component<
mixedMode, mixedMode,
evalMode, evalMode,
popOverContainer, popOverContainer,
useMobileUI,
...rest ...rest
} = this.props; } = this.props;
const {isOpened, value, editorValue, isError} = this.state; const {isOpened, value, editorValue, isError} = this.state;
const iconElement = generateIcon(cx, icon, 'Icon'); const iconElement = generateIcon(cx, icon, 'Icon');
const mobileUI = useMobileUI && isMobile();
return ( return (
<> <>
@ -411,6 +415,8 @@ export class FormulaPicker extends React.Component<
disabled={disabled} disabled={disabled}
borderMode={borderMode} borderMode={borderMode}
placeholder={placeholder} placeholder={placeholder}
useMobileUI={useMobileUI}
showArrow={false}
/> />
<Button <Button
@ -452,6 +458,8 @@ export class FormulaPicker extends React.Component<
disabled={disabled} disabled={disabled}
borderMode={borderMode} borderMode={borderMode}
placeholder={placeholder} placeholder={placeholder}
useMobileUI={useMobileUI}
showArrow={false}
/> />
<a <a
@ -464,6 +472,35 @@ export class FormulaPicker extends React.Component<
)} )}
</div> </div>
)} )}
{mobileUI ? (
<PopUp
className={cx(`FormulaPicker-popup`)}
isShow={this.state.isOpened}
showConfirm
onHide={this.close}
onConfirm={this.handleEditorConfirm}
>
<div className={cx('FormulaPicker-popup-inner')}>
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}
onChange={this.handleEditorChange}
selfVariableName={this.props.selfVariableName}
/>
{!!isError ? (
<div className={cx('Dialog-info')} key="info">
<span className={cx('Dialog-error')}>
{__('FormulaEditor.invalidData', {err: isError})}
</span>
</div>
) : null}
</div>
</PopUp>
) : (
<Modal <Modal
size="md" size="md"
closeOnEsc closeOnEsc
@ -500,6 +537,7 @@ export class FormulaPicker extends React.Component<
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>
)}
</> </>
); );
} }

View File

@ -82,7 +82,7 @@ function VariableList(props: VariableListProps) {
function renderSearchBox() { function renderSearchBox() {
return ( return (
<div className={cx('FormulaEditor-VariableList-searchBox')}> <div className={cx('FormulaEditor-VariableList-searchBox')}>
<SearchBox mini={false} onSearch={onSearch} /> <SearchBox mini={false} onSearch={onSearch} useMobileUI />
</div> </div>
); );
} }

View File

@ -4,6 +4,7 @@ import Button from '../Button';
import {Icon} from '../icons'; import {Icon} from '../icons';
import type {InputJSONSchemaItemProps} from './index'; import type {InputJSONSchemaItemProps} from './index';
import {InputJSONSchemaItem} from './Item'; import {InputJSONSchemaItem} from './Item';
import {isMobile} from 'amis-core';
type JSONSchemaArrayMember = { type JSONSchemaArrayMember = {
key: string; key: string;
@ -19,7 +20,8 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
disabled, disabled,
translate: __, translate: __,
collapsable, collapsable,
renderValue renderValue,
useMobileUI
} = props; } = props;
const buildMembers = React.useCallback((schema: any, value: any) => { const buildMembers = React.useCallback((schema: any, value: any) => {
const members: Array<JSONSchemaArrayMember> = []; const members: Array<JSONSchemaArrayMember> = [];
@ -137,6 +139,7 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
// todo additionalProperties 还有其他格式 // todo additionalProperties 还有其他格式
const allowAdd = !maxContains || maxContains > members.length; const allowAdd = !maxContains || maxContains > members.length;
const allowDelete = !minContains || minContains < members.length; const allowDelete = !minContains || minContains < members.length;
const mobileUI = useMobileUI && isMobile();
return ( return (
<> <>
@ -153,6 +156,7 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
<div <div
className={cx('JSONSchemaObject', { className={cx('JSONSchemaObject', {
'is-mobile': mobileUI,
'is-expanded': collapsable && !collapsed 'is-expanded': collapsable && !collapsed
})} })}
> >

View File

@ -20,6 +20,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
value={props.value} value={props.value}
onChange={props.onChange} onChange={props.onChange}
placeholder={props.placeholder} placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/> />
); );
} else if (schema.type == 'integer') { } else if (schema.type == 'integer') {
@ -29,6 +30,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
onChange={props.onChange} onChange={props.onChange}
precision={0} precision={0}
placeholder={props.placeholder} placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/> />
); );
} else if (schema.type == 'boolean') { } else if (schema.type == 'boolean') {
@ -42,6 +44,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
value={props.value} value={props.value}
onChange={props.onChange} onChange={props.onChange}
placeholder={props.placeholder} placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/> />
); );
} }

View File

@ -7,6 +7,7 @@ import InputBoxWithSuggestion from '../InputBoxWithSuggestion';
import Select from '../Select'; import Select from '../Select';
import type {InputJSONSchemaItemProps} from './index'; import type {InputJSONSchemaItemProps} from './index';
import {InputJSONSchemaItem} from './Item'; import {InputJSONSchemaItem} from './Item';
import {isMobile} from 'amis-core';
type JSONSchemaObjectMember = { type JSONSchemaObjectMember = {
key: string; key: string;
@ -25,7 +26,8 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
translate: __, translate: __,
renderKey, renderKey,
collapsable, collapsable,
renderValue renderValue,
useMobileUI
} = props; } = props;
const buildMembers = React.useCallback((schema: any, value: any) => { const buildMembers = React.useCallback((schema: any, value: any) => {
const members: Array<JSONSchemaObjectMember> = []; const members: Array<JSONSchemaObjectMember> = [];
@ -189,6 +191,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
options.every(o => members.find(m => m.name === o.value)) options.every(o => members.find(m => m.name === o.value))
); );
const allowInput = props.schema.additionalProperties !== false; const allowInput = props.schema.additionalProperties !== false;
const mobileUI = useMobileUI && isMobile();
return ( return (
<> <>
@ -205,6 +208,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
<div <div
className={cx('JSONSchemaObject', { className={cx('JSONSchemaObject', {
'is-mobile': mobileUI,
'is-expanded': collapsable && !collapsed 'is-expanded': collapsable && !collapsed
})} })}
> >
@ -228,7 +232,11 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
return ( return (
<div key={member.key} className={cx('JSONSchemaMember')}> <div key={member.key} className={cx('JSONSchemaMember')}>
<div className={cx('JSONSchemaMember-key')}> <div
className={cx('JSONSchemaMember-key', {
'is-mobile': mobileUI
})}
>
{member.nameMutable ? ( {member.nameMutable ? (
<> <>
{renderKey ? ( {renderKey ? (
@ -247,6 +255,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
clearable={false} clearable={false}
placeholder={__('JSONSchema.key')} placeholder={__('JSONSchema.key')}
options={filtedOptions} options={filtedOptions}
useMobileUI={useMobileUI}
/> />
) : ( ) : (
<Select <Select
@ -258,6 +267,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
clearable={false} clearable={false}
placeholder={__('JSONSchema.key')} placeholder={__('JSONSchema.key')}
options={filtedOptions} options={filtedOptions}
useMobileUI={useMobileUI}
/> />
) )
) : ( ) : (
@ -267,6 +277,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
onChange={onMemberKeyChange.bind(null, member)} onChange={onMemberKeyChange.bind(null, member)}
clearable={false} clearable={false}
placeholder={__('JSONSchema.key')} placeholder={__('JSONSchema.key')}
useMobileUI={useMobileUI}
/> />
)} )}
</> </>

View File

@ -29,6 +29,7 @@ export interface InputJSONSchemaItemProps extends ThemeProps, LocaleProps {
) => JSX.Element; ) => JSX.Element;
collapsable?: boolean; collapsable?: boolean;
placeholder?: string; placeholder?: string;
useMobileUI?: boolean;
} }
export interface InputJSONSchemaProps export interface InputJSONSchemaProps

View File

@ -41,7 +41,8 @@ export class SchemaEditorItemArray extends SchemaEditorItemCommon {
onTypeChange, onTypeChange,
enableAdvancedSetting, enableAdvancedSetting,
popOverContainer, popOverContainer,
placeholder placeholder,
useMobileUI
} = this.props; } = this.props;
const items = value?.items || { const items = value?.items || {
type: 'string' type: 'string'
@ -73,6 +74,7 @@ export class SchemaEditorItemArray extends SchemaEditorItemCommon {
enableAdvancedSetting={enableAdvancedSetting} enableAdvancedSetting={enableAdvancedSetting}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
placeholder={placeholder} placeholder={placeholder}
useMobileUI={useMobileUI}
/> />
</div> </div>
); );

View File

@ -54,6 +54,7 @@ export interface SchemaEditorItemCommonProps extends LocaleProps, ThemeProps {
/** 各属性输入控件的placeholder */ /** 各属性输入控件的placeholder */
placeholder?: SchemaEditorItemPlaceholder; placeholder?: SchemaEditorItemPlaceholder;
popOverContainer?: any; popOverContainer?: any;
useMobileUI?: boolean;
} }
export class SchemaEditorItemCommon< export class SchemaEditorItemCommon<
@ -102,7 +103,8 @@ export class SchemaEditorItemCommon<
prefix, prefix,
affix, affix,
types, types,
placeholder placeholder,
useMobileUI
} = this.props; } = this.props;
return ( return (
@ -118,6 +120,7 @@ export class SchemaEditorItemCommon<
clearable={false} clearable={false}
disabled={disabled || typeMutable === false} disabled={disabled || typeMutable === false}
simpleValue simpleValue
useMobileUI={useMobileUI}
/> />
) : null} ) : null}
@ -135,6 +138,7 @@ export class SchemaEditorItemCommon<
{enableAdvancedSetting ? ( {enableAdvancedSetting ? (
<PickerContainer <PickerContainer
useMobileUI={useMobileUI}
value={value} value={value}
bodyRender={({isOpened, value, onChange, ref}) => { bodyRender={({isOpened, value, onChange, ref}) => {
return isOpened ? ( return isOpened ? (
@ -152,6 +156,7 @@ export class SchemaEditorItemCommon<
{...field} {...field}
disabled={disabled} disabled={disabled}
placeholder={__(placeholder?.title ?? '')} placeholder={__(placeholder?.title ?? '')}
useMobileUI={useMobileUI}
/> />
)} )}
/> />
@ -164,6 +169,7 @@ export class SchemaEditorItemCommon<
<Textarea <Textarea
{...field} {...field}
disabled={disabled} disabled={disabled}
useMobileUI={useMobileUI}
placeholder={__(placeholder?.description ?? '')} placeholder={__(placeholder?.description ?? '')}
/> />
)} )}
@ -178,6 +184,7 @@ export class SchemaEditorItemCommon<
{...field} {...field}
disabled={disabled} disabled={disabled}
placeholder={__(placeholder?.default ?? '')} placeholder={__(placeholder?.default ?? '')}
useMobileUI={useMobileUI}
/> />
)} )}
/> />

View File

@ -7,7 +7,9 @@ import {SchemaEditorItemObject} from './Object';
export interface SchemaEditorItemProps export interface SchemaEditorItemProps
extends SchemaEditorItemCommonProps, extends SchemaEditorItemCommonProps,
LocaleProps, LocaleProps,
ThemeProps {} ThemeProps {
useMobileUI?: boolean;
}
export class SchemaEditorItem extends React.Component<SchemaEditorItemProps> { export class SchemaEditorItem extends React.Component<SchemaEditorItemProps> {
render() { render() {

View File

@ -199,7 +199,8 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onTypeChange, onTypeChange,
enableAdvancedSetting, enableAdvancedSetting,
popOverContainer, popOverContainer,
placeholder placeholder,
useMobileUI
} = this.props; } = this.props;
const members = this.state.members; const members = this.state.members;
@ -212,6 +213,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
{members.length ? ( {members.length ? (
members.map((member, index) => ( members.map((member, index) => (
<SchemaEditorItem <SchemaEditorItem
useMobileUI={useMobileUI}
key={member.id} key={member.id}
types={types} types={types}
onTypeChange={onTypeChange} onTypeChange={onTypeChange}
@ -226,6 +228,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onChange={this.handlePropKeyChange.bind(this, index)} onChange={this.handlePropKeyChange.bind(this, index)}
placeholder={__(placeholder?.key ?? '')} placeholder={__(placeholder?.key ?? '')}
disabled={disabled || !!value?.$ref} disabled={disabled || !!value?.$ref}
useMobileUI={useMobileUI}
/> />
<InputBox <InputBox
@ -234,6 +237,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onChange={this.handlePropTitleChange.bind(this, index)} onChange={this.handlePropTitleChange.bind(this, index)}
placeholder={__(placeholder?.title ?? '')} placeholder={__(placeholder?.title ?? '')}
disabled={disabled || !!value?.$ref} disabled={disabled || !!value?.$ref}
useMobileUI={useMobileUI}
/> />
</> </>
} }

View File

@ -72,6 +72,7 @@ export interface SchemaEditorProps extends LocaleProps, ThemeProps {
* *
*/ */
placeholder?: SchemaEditorItemPlaceholder; placeholder?: SchemaEditorItemPlaceholder;
useMobileUI?: boolean;
} }
export class SchemaEditor extends React.Component<SchemaEditorProps> { export class SchemaEditor extends React.Component<SchemaEditorProps> {
@ -171,7 +172,8 @@ export class SchemaEditor extends React.Component<SchemaEditorProps> {
definitions, definitions,
enableAdvancedSetting, enableAdvancedSetting,
popOverContainer, popOverContainer,
placeholder placeholder,
useMobileUI
} = this.props; } = this.props;
const value: JSONSchema = this.props.value || { const value: JSONSchema = this.props.value || {
type: defaultType || 'object' type: defaultType || 'object'
@ -231,6 +233,7 @@ export class SchemaEditor extends React.Component<SchemaEditorProps> {
enableAdvancedSetting={enableAdvancedSetting} enableAdvancedSetting={enableAdvancedSetting}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
placeholder={placeholder} placeholder={placeholder}
useMobileUI={useMobileUI}
/> />
</div> </div>
); );

View File

@ -92,6 +92,10 @@ export interface CollapseSchema extends BaseSchema {
* *
*/ */
unmountOnExit?: boolean; unmountOnExit?: boolean;
/**
* 线
*/
divideLine?: boolean;
} }
export interface CollapseProps export interface CollapseProps
@ -147,9 +151,13 @@ export default class Collapse extends React.Component<CollapseProps, {}> {
disabled, disabled,
collapsed, collapsed,
propsUpdate, propsUpdate,
onCollapse onCollapse,
useMobileUI,
divideLine
} = this.props; } = this.props;
console.log('cooooooo', divideLine);
return ( return (
<BasicCollapse <BasicCollapse
id={id} id={id}
@ -194,7 +202,9 @@ export default class Collapse extends React.Component<CollapseProps, {}> {
? render('body', body) ? render('body', body)
: null : null
} }
useMobileUI={useMobileUI}
onCollapse={onCollapse} onCollapse={onCollapse}
divideLine={divideLine}
></BasicCollapse> ></BasicCollapse>
); );
} }

View File

@ -60,7 +60,8 @@ export class CollapseGroupRender extends React.Component<
body, body,
className, className,
style, style,
render render,
useMobileUI
} = this.props; } = this.props;
return ( return (
<CollapseGroup <CollapseGroup
@ -70,6 +71,7 @@ export class CollapseGroupRender extends React.Component<
expandIconPosition={expandIconPosition} expandIconPosition={expandIconPosition}
className={className} className={className}
style={style} style={style}
useMobileUI={useMobileUI}
> >
{render('body', body || '')} {render('body', body || '')}
</CollapseGroup> </CollapseGroup>

View File

@ -47,6 +47,7 @@ import {
import {ListenerAction} from 'amis-core'; import {ListenerAction} from 'amis-core';
import type {SchemaTokenizeableString} from '../../Schema'; import type {SchemaTokenizeableString} from '../../Schema';
import isPlainObject from 'lodash/isPlainObject'; import isPlainObject from 'lodash/isPlainObject';
import {isMobile} from 'amis-core';
export type ComboCondition = { export type ComboCondition = {
test: string; test: string;
@ -1285,9 +1286,12 @@ export default class ComboControl extends React.Component<ComboProps> {
itemRemovableOn, itemRemovableOn,
disabled, disabled,
removable, removable,
deleteBtn deleteBtn,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
const finnalRemovable = const finnalRemovable =
store.removable !== false && // minLength ? store.removable !== false && // minLength ?
!disabled && // 控件自身是否禁用 !disabled && // 控件自身是否禁用
@ -1355,7 +1359,7 @@ export default class ComboControl extends React.Component<ComboProps> {
onClick={this.deleteItem.bind(this, index)} onClick={this.deleteItem.bind(this, index)}
key="delete" key="delete"
className={cx(`Combo-delBtn ${!store.removable ? 'is-disabled' : ''}`)} className={cx(`Combo-delBtn ${!store.removable ? 'is-disabled' : ''}`)}
data-tooltip={__('delete')} data-tooltip={!mobileUI ? __('delete') : null}
data-position="bottom" data-position="bottom"
> >
{deleteIcon ? ( {deleteIcon ? (
@ -1460,11 +1464,13 @@ export default class ComboControl extends React.Component<ComboProps> {
translate: __, translate: __,
itemClassName, itemClassName,
itemsWrapperClassName, itemsWrapperClassName,
static: isStatic static: isStatic,
useMobileUI
} = this.props; } = this.props;
let items = this.props.items; let items = this.props.items;
let value = this.props.value; let value = this.props.value;
const mobileUI = useMobileUI && isMobile();
if (flat && typeof value === 'string') { if (flat && typeof value === 'string') {
value = value.split(delimiter || ','); value = value.split(delimiter || ',');
@ -1474,6 +1480,9 @@ export default class ComboControl extends React.Component<ComboProps> {
<div <div
className={cx( className={cx(
`Combo Combo--multi`, `Combo Combo--multi`,
{
'is-mobile': mobileUI
},
multiLine ? `Combo--ver` : `Combo--hor`, multiLine ? `Combo--ver` : `Combo--hor`,
noBorder ? `Combo--noBorder` : '', noBorder ? `Combo--noBorder` : '',
disabled ? 'is-disabled' : '', disabled ? 'is-disabled' : '',
@ -1589,9 +1598,11 @@ export default class ComboControl extends React.Component<ComboProps> {
typeSwitchable, typeSwitchable,
nullable, nullable,
translate: __, translate: __,
itemClassName itemClassName,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
let items = this.props.items; let items = this.props.items;
const data = isObject(value) ? this.formatValue(value) : this.defaultValue; const data = isObject(value) ? this.formatValue(value) : this.defaultValue;
let condition: ComboCondition | null = null; let condition: ComboCondition | null = null;
@ -1605,6 +1616,9 @@ export default class ComboControl extends React.Component<ComboProps> {
<div <div
className={cx( className={cx(
`Combo Combo--single`, `Combo Combo--single`,
{
'is-mobile': mobileUI
},
multiLine ? `Combo--ver` : `Combo--hor`, multiLine ? `Combo--ver` : `Combo--hor`,
noBorder ? `Combo--noBorder` : '', noBorder ? `Combo--noBorder` : '',
disabled ? 'is-disabled' : '' disabled ? 'is-disabled' : ''

View File

@ -197,7 +197,8 @@ export class InputFormulaRenderer extends React.Component<InputFormulaProps> {
data, data,
onPickerOpen, onPickerOpen,
selfVariableName, selfVariableName,
env env,
useMobileUI
} = this.props; } = this.props;
let {variables, functions} = this.props; let {variables, functions} = this.props;
@ -240,6 +241,7 @@ export class InputFormulaRenderer extends React.Component<InputFormulaProps> {
onPickerOpen={onPickerOpen} onPickerOpen={onPickerOpen}
selfVariableName={selfVariableName} selfVariableName={selfVariableName}
mixedMode={mixedMode} mixedMode={mixedMode}
useMobileUI={useMobileUI}
/> />
); );
} }

View File

@ -9,6 +9,7 @@ import {
anyChanged anyChanged
} from 'amis-core'; } from 'amis-core';
import {FormBaseControlSchema, SchemaCollection} from '../../Schema'; import {FormBaseControlSchema, SchemaCollection} from '../../Schema';
import {isMobile} from 'amis-core';
/** /**
* InputGroup * InputGroup
@ -190,7 +191,8 @@ export class InputGroup extends React.Component<
data, data,
classnames: cx, classnames: cx,
static: isStatic, static: isStatic,
disabled disabled,
useMobileUI
} = this.props; } = this.props;
const {errorMode} = this.getValidationConfig(); const {errorMode} = this.getValidationConfig();
@ -218,6 +220,8 @@ export class InputGroup extends React.Component<
(formHorizontal (formHorizontal
? makeHorizontalDeeper(formHorizontal as any, inputs.length) ? makeHorizontalDeeper(formHorizontal as any, inputs.length)
: undefined); : undefined);
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
className={cx( className={cx(
@ -226,6 +230,9 @@ export class InputGroup extends React.Component<
className, className,
{ {
'is-focused': this.state.isFocused 'is-focused': this.state.isFocused
},
{
'is-mobile': mobileUI
} }
)} )}
> >

View File

@ -11,7 +11,8 @@ import {
autobind, autobind,
stripNumber, stripNumber,
filter, filter,
ActionObject ActionObject,
isMobile
} from 'amis-core'; } from 'amis-core';
import {Range as InputRange, NumberInput, Icon} from 'amis-ui'; import {Range as InputRange, NumberInput, Icon} from 'amis-ui';
import {FormBaseControlSchema, SchemaObject} from '../../Schema'; import {FormBaseControlSchema, SchemaObject} from '../../Schema';
@ -436,7 +437,8 @@ export class Input extends React.Component<RangeItemProps, any> {
classPrefix: ns, classPrefix: ns,
disabled, disabled,
max, max,
min min,
useMobileUI
} = this.props; } = this.props;
const _value = multiple const _value = multiple
? type === 'min' ? type === 'min'
@ -454,6 +456,7 @@ export class Input extends React.Component<RangeItemProps, any> {
disabled={disabled} disabled={disabled}
onBlur={this.onBlur} onBlur={this.onBlur}
onFocus={this.onFocus} onFocus={this.onFocus}
useMobileUI={useMobileUI}
/> />
</div> </div>
); );
@ -638,7 +641,8 @@ export default class RangeControl extends React.PureComponent<
max, max,
render, render,
marks, marks,
region region,
useMobileUI
} = props; } = props;
// 处理自定义json配置 // 处理自定义json配置
@ -653,6 +657,7 @@ export default class RangeControl extends React.PureComponent<
(renderMarks[key] = render(region, item as SchemaObject)); (renderMarks[key] = render(region, item as SchemaObject));
} }
}); });
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
@ -660,6 +665,7 @@ export default class RangeControl extends React.PureComponent<
'RangeControl', 'RangeControl',
`${ns}InputRange`, `${ns}InputRange`,
{'is-disabled': disabled}, {'is-disabled': disabled},
{'is-mobile': mobileUI},
className className
)} )}
> >

View File

@ -74,6 +74,7 @@ export default class RepeatControl extends React.Component<RepeatProps, any> {
placeholder, placeholder,
disabled, disabled,
classPrefix: ns, classPrefix: ns,
useMobileUI,
translate: __ translate: __
} = this.props; } = this.props;
@ -216,6 +217,7 @@ export default class RepeatControl extends React.Component<RepeatProps, any> {
searchable={false} searchable={false}
disabled={disabled} disabled={disabled}
joinValues={false} joinValues={false}
useMobileUI={useMobileUI}
/> />
</div> </div>
</div> </div>

View File

@ -8,6 +8,9 @@ import {Icon} from 'amis-ui';
import {FormBaseControlSchema, FormSchema, SchemaClassName} from '../../Schema'; import {FormBaseControlSchema, FormSchema, SchemaClassName} from '../../Schema';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {isMobile} from 'amis-core';
import {PopUp} from 'amis-ui';
import {autobind} from 'amis-core';
/** /**
* SubForm * SubForm
@ -152,6 +155,7 @@ export default class SubFormControl extends React.PureComponent<
dragTip?: HTMLElement; dragTip?: HTMLElement;
sortable?: Sortable; sortable?: Sortable;
id: string = guid(); id: string = guid();
tempValue: any;
constructor(props: SubFormProps) { constructor(props: SubFormProps) {
super(props); super(props);
@ -211,6 +215,7 @@ export default class SubFormControl extends React.PureComponent<
return; return;
} }
this.tempValue = value[index];
this.setState({ this.setState({
dialogData: createObject(this.props.data, value[index]), dialogData: createObject(this.props.data, value[index]),
dialogCtx: { dialogCtx: {
@ -255,6 +260,36 @@ export default class SubFormControl extends React.PureComponent<
this.close(); this.close();
} }
@autobind
handlePopupConfirm() {
const values = this.tempValue;
const {multiple, onChange, value} = this.props;
const ctx = this.state.dialogCtx;
if (multiple) {
let newValue = Array.isArray(value) ? value.concat() : [];
if (ctx?.mode === 'add') {
newValue.push({
...values
});
} else {
newValue[ctx!.index!] = {
...newValue[ctx!.index!],
...values
};
}
onChange(newValue);
} else {
onChange({
...value,
...values
});
}
this.close();
}
dragTipRef(ref: any) { dragTipRef(ref: any) {
if (!this.dragTip && ref) { if (!this.dragTip && ref) {
this.initDragging(); this.initDragging();
@ -332,6 +367,28 @@ export default class SubFormControl extends React.PureComponent<
}; };
} }
buildFormSchema() {
let {form} = this.props;
const dialogProps = [
'title',
'actions',
'name',
'size',
'closeOnEsc',
'closeOnOutside',
'showErrorMsg',
'showCloseButton',
'bodyClassName',
'type'
];
return {
type: 'form',
...omit(form, dialogProps)
};
}
renderMultipe() { renderMultipe() {
const { const {
addButtonClassName, addButtonClassName,
@ -502,21 +559,49 @@ export default class SubFormControl extends React.PureComponent<
} }
render() { render() {
const {multiple, classPrefix: ns, className, style, render} = this.props; const {
multiple,
classPrefix: ns,
className,
style,
render,
useMobileUI
} = this.props;
const dialogData = this.state.dialogData; const dialogData = this.state.dialogData;
const dialogCtx = this.state.dialogCtx; const dialogCtx = this.state.dialogCtx;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div className={cx(`${ns}SubFormControl`, className)}> <div className={cx(`${ns}SubFormControl`, className)}>
{multiple ? this.renderMultipe() : this.renderSingle()} {multiple ? this.renderMultipe() : this.renderSingle()}
{render(`modal`, this.buildDialogSchema(), { {!mobileUI ? (
render(`modal`, this.buildDialogSchema(), {
show: !!dialogCtx, show: !!dialogCtx,
onClose: this.close, onClose: this.close,
onConfirm: this.handleDialogConfirm, onConfirm: this.handleDialogConfirm,
data: dialogData, data: dialogData,
formStore: undefined formStore: undefined
})
) : (
<PopUp
isShow={!!dialogCtx}
showConfirm
onConfirm={this.handlePopupConfirm}
onHide={this.close}
>
<div className="flex-1 pl-10 pr-10">
{render('form', this.buildFormSchema(), {
data: dialogData,
formStore: undefined,
wrapWithPanel: false,
onChange: (val: any) => {
this.tempValue = val;
}
})} })}
</div> </div>
</PopUp>
)}
</div>
); );
} }
} }

View File

@ -10,13 +10,14 @@ import find from 'lodash/find';
import isInteger from 'lodash/isInteger'; import isInteger from 'lodash/isInteger';
import unionWith from 'lodash/unionWith'; import unionWith from 'lodash/unionWith';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {ResultBox, SpinnerExtraProps} from 'amis-ui'; import {PopUp, ResultBox, SpinnerExtraProps} from 'amis-ui';
import {autobind, filterTree, createObject} from 'amis-core'; import {autobind, filterTree, createObject} from 'amis-core';
import {Spinner} from 'amis-ui'; import {Spinner} from 'amis-ui';
import {Overlay} from 'amis-core'; import {Overlay} from 'amis-core';
import {PopOver} from 'amis-core'; import {PopOver} from 'amis-core';
import {ListMenu} from 'amis-ui'; import {ListMenu, Button} from 'amis-ui';
import {ActionObject} from 'amis-core'; import {ActionObject} from 'amis-core';
import {isMobile} from 'amis-core';
import {FormOptionsSchema} from '../../Schema'; import {FormOptionsSchema} from '../../Schema';
import {supportStatic} from './StaticHoc'; import {supportStatic} from './StaticHoc';
import {TooltipWrapperSchema} from '../TooltipWrapper'; import {TooltipWrapperSchema} from '../TooltipWrapper';
@ -98,6 +99,8 @@ export interface TagState {
inputValue: string; inputValue: string;
isFocused?: boolean; isFocused?: boolean;
isOpened?: boolean; isOpened?: boolean;
selectedOptions: Option[];
cacheOptions: Option[];
} }
export default class TagControl extends React.PureComponent< export default class TagControl extends React.PureComponent<
@ -116,11 +119,16 @@ export default class TagControl extends React.PureComponent<
separator: '-' separator: '-'
}; };
state = { constructor(props: TagProps) {
super(props);
this.state = {
isOpened: false, isOpened: false,
inputValue: '', inputValue: '',
isFocused: false isFocused: false,
selectedOptions: props.selectedOptions,
cacheOptions: []
}; };
}
componentDidUpdate(prevProps: TagProps) { componentDidUpdate(prevProps: TagProps) {
const props = this.props; const props = this.props;
@ -242,9 +250,15 @@ export default class TagControl extends React.PureComponent<
} }
@autobind @autobind
getValue(type: 'push' | 'pop' | 'normal' = 'normal', option: any = {}) { getValue(
const {selectedOptions, joinValues, extractValue, delimiter, valueField} = type: 'push' | 'pop' | 'normal' = 'normal',
this.props; option: any = {},
selectedOptions?: Option[]
) {
const {joinValues, extractValue, delimiter, valueField} = this.props;
selectedOptions = selectedOptions
? selectedOptions
: this.props.selectedOptions;
const newValue = selectedOptions.concat(); const newValue = selectedOptions.concat();
if (type === 'push') { if (type === 'push') {
@ -286,11 +300,84 @@ export default class TagControl extends React.PureComponent<
isPrevented || onChange(newValueRes); isPrevented || onChange(newValueRes);
} }
// 移动端特殊处理
addItem2(option: Option) {
const {useMobileUI, valueField = 'value'} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
const selectedOptions = this.state.selectedOptions.concat();
let index = selectedOptions.findIndex(
item => item[valueField] === option[valueField]
);
if (~index) {
selectedOptions.splice(index, 1);
} else if (!this.isReachMaxFromState()) {
selectedOptions.push(option);
}
this.setState({
selectedOptions
});
}
}
// 手机端校验
isExist(inputValue: string) {
const {options, valueField = 'value'} = this.props;
const {cacheOptions} = this.state;
return (
options.some(item => item[valueField] === inputValue) ||
cacheOptions.some(item => item[valueField] === inputValue)
);
}
@autobind
addSelection() {
let {inputValue} = this.state;
const {maxTagLength} = this.props;
const selectedOptions = this.state.selectedOptions.slice();
const cacheOptions = this.state.cacheOptions.slice();
if (maxTagLength !== undefined) {
inputValue = inputValue.trim();
inputValue = inputValue.slice(0, maxTagLength);
}
if (this.isExist(inputValue)) {
return;
}
if (inputValue && !this.isReachMaxFromState()) {
const addedValues = this.normalizeInputValue(inputValue);
selectedOptions.push(addedValues[0]);
cacheOptions.push(addedValues[0]);
this.setState({
inputValue: '',
selectedOptions,
cacheOptions
});
}
}
@autobind
async onConfirm() {
const {selectedOptions} = this.state;
const {onChange} = this.props;
const newValueRes = this.getValue('normal', {}, selectedOptions);
const isPrevented = await this.dispatchEvent('change', {
value: newValueRes,
selectedItems: selectedOptions
});
isPrevented || onChange(newValueRes);
this.close();
}
@autobind @autobind
async handleFocus(e: any) { async handleFocus(e: any) {
this.setState({ this.setState({
isFocused: true, isFocused: true,
isOpened: true isOpened: true,
selectedOptions: this.props.selectedOptions
}); });
const newValueRes = this.getValue('normal'); const newValueRes = this.getValue('normal');
@ -303,8 +390,11 @@ export default class TagControl extends React.PureComponent<
@autobind @autobind
async handleBlur(e: any) { async handleBlur(e: any) {
const {selectedOptions, onChange} = this.props; const {selectedOptions, onChange, useMobileUI, options} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI && options.length) {
return;
}
const value = this.state.inputValue.trim(); const value = this.state.inputValue.trim();
if (!this.validateInputValue(value)) { if (!this.validateInputValue(value)) {
@ -420,6 +510,12 @@ export default class TagControl extends React.PureComponent<
@autobind @autobind
handleOptionChange(option: Option) { handleOptionChange(option: Option) {
const {useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
this.addItem2(option);
return;
}
if (this.isReachMax() || this.state.inputValue || !option) { if (this.isReachMax() || this.state.inputValue || !option) {
return; return;
} }
@ -448,6 +544,13 @@ export default class TagControl extends React.PureComponent<
return max != null && isInteger(max) && selectedOptions.length >= max; return max != null && isInteger(max) && selectedOptions.length >= max;
} }
@autobind
isReachMaxFromState() {
const {selectedOptions} = this.state;
const {max} = this.props;
return max != null && isInteger(max) && selectedOptions.length >= max;
}
@supportStatic() @supportStatic()
render() { render() {
const { const {
@ -468,16 +571,17 @@ export default class TagControl extends React.PureComponent<
overflowTagPopover, overflowTagPopover,
translate: __, translate: __,
loadingConfig, loadingConfig,
valueField valueField,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
const finnalOptions = Array.isArray(options) const finnalOptions = Array.isArray(options)
? filterTree( ? filterTree(
options, options,
item => item =>
(Array.isArray(item.children) && !!item.children.length) || (Array.isArray(item.children) && !!item.children.length) ||
(item[valueField || 'value'] !== undefined && (item[valueField || 'value'] !== undefined &&
!~selectedOptions.indexOf(item)), (mobileUI || !~selectedOptions.indexOf(item))),
0, 0,
true true
) )
@ -488,7 +592,7 @@ export default class TagControl extends React.PureComponent<
return ( return (
<Downshift <Downshift
selectedItem={selectedOptions} selectedItem={selectedOptions}
isOpen={this.state.isFocused} isOpen={mobileUI ? this.state.isOpened : this.state.isFocused}
inputValue={this.state.inputValue} inputValue={this.state.inputValue}
onChange={this.handleOptionChange} onChange={this.handleOptionChange}
itemToString={this.renderItem} itemToString={this.renderItem}
@ -517,7 +621,8 @@ export default class TagControl extends React.PureComponent<
clearable={clearable} clearable={clearable}
maxTagCount={maxTagCount} maxTagCount={maxTagCount}
overflowTagPopover={overflowTagPopover} overflowTagPopover={overflowTagPopover}
allowInput allowInput={!mobileUI || (mobileUI && !options?.length)}
useMobileUI={useMobileUI}
> >
{loading ? ( {loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" /> <Spinner loadingConfig={loadingConfig} size="sm" />
@ -525,6 +630,61 @@ export default class TagControl extends React.PureComponent<
</ResultBox> </ResultBox>
{dropdown !== false ? ( {dropdown !== false ? (
mobileUI ? (
<PopUp
className={cx(`Tag-popup`)}
container={popOverContainer}
isShow={isOpen && !!finnalOptions.length}
showConfirm={true}
onConfirm={this.onConfirm}
onHide={this.close}
>
<div>
<ListMenu
selectedOptions={selectedOptions}
useMobileUI={useMobileUI}
options={finnalOptions.concat(this.state.cacheOptions)}
itemRender={this.renderItem}
highlightIndex={highlightedIndex}
getItemProps={({
item,
index
}: {
item: Option;
index: number;
}) => ({
...getItemProps({
index,
item,
className: cx('ListMenu-item', {
'is-active': ~(
this.state.selectedOptions.map(
item => item[valueField]
) || []
).indexOf(item[valueField])
})
})
})}
/>
{mobileUI && !this.isReachMaxFromState() ? (
<div className={cx('ListMenu-add-wrap')}>
<ResultBox
placeholder={__('SubForm.add')}
allowInput
value={this.state.inputValue}
useMobileUI={useMobileUI}
clearable
maxTagCount={maxTagCount}
onChange={value => {
this.setState({inputValue: value});
}}
onBlur={this.addSelection}
></ResultBox>
</div>
) : null}
</div>
</PopUp>
) : (
<Overlay <Overlay
container={popOverContainer || this.getParent} container={popOverContainer || this.getParent}
target={this.getTarget} target={this.getTarget}
@ -559,6 +719,7 @@ export default class TagControl extends React.PureComponent<
/> />
</PopOver> </PopOver>
</Overlay> </Overlay>
)
) : ( ) : (
// 保留原来的展现方式,不推荐 // 保留原来的展现方式,不推荐
<div className={cx('TagControl-sug')}> <div className={cx('TagControl-sug')}>

View File

@ -173,6 +173,7 @@ export interface TreeProps
SpinnerExtraProps { SpinnerExtraProps {
enableNodePath?: boolean; enableNodePath?: boolean;
pathSeparator?: string; pathSeparator?: string;
useMobileUI?: boolean;
} }
interface TreeState { interface TreeState {
@ -377,7 +378,8 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
enableDefaultIcon, enableDefaultIcon,
searchable, searchable,
searchConfig = {}, searchConfig = {},
heightAuto heightAuto,
useMobileUI
} = this.props; } = this.props;
let {highlightTxt} = this.props; let {highlightTxt} = this.props;
const {filteredOptions, keyword} = this.state; const {filteredOptions, keyword} = this.state;
@ -439,6 +441,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined} itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined}
itemRender={menuTpl ? this.renderOptionItem : undefined} itemRender={menuTpl ? this.renderOptionItem : undefined}
enableDefaultIcon={enableDefaultIcon} enableDefaultIcon={enableDefaultIcon}
useMobileUI={useMobileUI}
/> />
); );
@ -467,6 +470,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
clearable={true} clearable={true}
{...omit(searchConfig, 'className', 'sticky')} {...omit(searchConfig, 'className', 'sticky')}
onSearch={this.handleSearch} onSearch={this.handleSearch}
useMobileUI={useMobileUI}
/> />
{TreeCmpt} {TreeCmpt}
</> </>

View File

@ -138,11 +138,12 @@ export default class JSONSchemaEditorControl extends React.PureComponent<JSONSch
} }
render() { render() {
const {enableAdvancedSetting, env, ...rest} = this.props; const {enableAdvancedSetting, useMobileUI, env, ...rest} = this.props;
return ( return (
<JSONSchemaEditor <JSONSchemaEditor
{...rest} {...rest}
useMobileUI={useMobileUI}
placeholder={this.normalizePlaceholder()} placeholder={this.normalizePlaceholder()}
enableAdvancedSetting={enableAdvancedSetting} enableAdvancedSetting={enableAdvancedSetting}
renderModalProps={this.renderModalProps} renderModalProps={this.renderModalProps}

View File

@ -956,7 +956,7 @@ export default class NestedSelectControl extends React.Component<
onKeyDown={this.handleInputKeyDown} onKeyDown={this.handleInputKeyDown}
clearable={clearable} clearable={clearable}
hasDropDownArrow={true} hasDropDownArrow={true}
allowInput={searchable} allowInput={searchable && !mobileUI}
> >
{loading ? ( {loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" /> <Spinner loadingConfig={loadingConfig} size="sm" />

View File

@ -23,8 +23,10 @@ import {
resolveEventData resolveEventData
} from 'amis-core'; } from 'amis-core';
import {Html, Icon} from 'amis-ui'; import {Html, Icon} from 'amis-ui';
import {isMobile} from 'amis-core';
import {FormOptionsSchema, SchemaTpl} from '../../Schema'; import {FormOptionsSchema, SchemaTpl} from '../../Schema';
import intersectionWith from 'lodash/intersectionWith'; import intersectionWith from 'lodash/intersectionWith';
import {PopUp} from 'amis-ui';
/** /**
* Picker * Picker
@ -521,11 +523,14 @@ export default class PickerControl extends React.PureComponent<
translate: __, translate: __,
popOverContainer, popOverContainer,
modalTitle, modalTitle,
data data,
useMobileUI
} = this.props; } = this.props;
const mobileUI = useMobileUI && isMobile();
return ( return (
<div className={cx(`PickerControl`, className)}> <div className={cx(`PickerControl`, {'is-mobile': mobileUI}, className)}>
{embed ? ( {embed ? (
<div className={cx('Picker')}> <div className={cx('Picker')}>
{this.renderBody({popOverContainer})} {this.renderBody({popOverContainer})}
@ -556,6 +561,7 @@ export default class PickerControl extends React.PureComponent<
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus} onFocus={this.handleFocus}
onBlur={this.handleBlur} onBlur={this.handleBlur}
readOnly={mobileUI}
/> />
</div> </div>

View File

@ -301,7 +301,8 @@ export class TabsTransferRenderer extends BaseTabsTransferRenderer<TabsTransferP
loadingConfig, loadingConfig,
valueField = 'value', valueField = 'value',
labelField = 'label', labelField = 'label',
data data,
useMobileUI
} = this.props; } = this.props;
return ( return (
@ -332,6 +333,7 @@ export class TabsTransferRenderer extends BaseTabsTransferRenderer<TabsTransferP
labelField={labelField} labelField={labelField}
valueField={valueField} valueField={valueField}
ctx={data} ctx={data}
useMobileUI={useMobileUI}
/> />
<Spinner <Spinner

View File

@ -106,7 +106,8 @@ export class TabsTransferPickerRenderer extends BaseTabsTransferRenderer<TabsTra
virtualThreshold, virtualThreshold,
loadingConfig, loadingConfig,
labelField = 'label', labelField = 'label',
valueField = 'value' valueField = 'value',
useMobileUI
} = this.props; } = this.props;
return ( return (
@ -139,6 +140,7 @@ export class TabsTransferPickerRenderer extends BaseTabsTransferRenderer<TabsTra
virtualThreshold={virtualThreshold} virtualThreshold={virtualThreshold}
labelField={labelField} labelField={labelField}
valueField={valueField} valueField={valueField}
useMobileUI={useMobileUI}
/> />
<Spinner <Spinner

View File

@ -532,7 +532,8 @@ export class BaseTransferRenderer<
itemHeight, itemHeight,
loadingConfig, loadingConfig,
showInvalidMatch, showInvalidMatch,
onlyChildren onlyChildren,
useMobileUI
} = this.props; } = this.props;
// 目前 LeftOptions 没有接口可以动态加载 // 目前 LeftOptions 没有接口可以动态加载
@ -593,6 +594,7 @@ export class BaseTransferRenderer<
} }
loadingConfig={loadingConfig} loadingConfig={loadingConfig}
showInvalidMatch={showInvalidMatch} showInvalidMatch={showInvalidMatch}
useMobileUI={useMobileUI}
/> />
<Spinner <Spinner

View File

@ -85,7 +85,8 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
virtualThreshold, virtualThreshold,
loadingConfig, loadingConfig,
labelField = 'label', labelField = 'label',
valueField = 'value' valueField = 'value',
useMobileUI
} = this.props; } = this.props;
// 目前 LeftOptions 没有接口可以动态加载 // 目前 LeftOptions 没有接口可以动态加载
@ -135,6 +136,7 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined
} }
virtualThreshold={virtualThreshold} virtualThreshold={virtualThreshold}
useMobileUI={useMobileUI}
/> />
<Spinner <Spinner

View File

@ -135,6 +135,7 @@ export interface TreeSelectProps
export interface TreeSelectState { export interface TreeSelectState {
isOpened: boolean; isOpened: boolean;
inputValue: string; inputValue: string;
tempValue: string;
} }
export default class TreeSelectControl extends React.Component< export default class TreeSelectControl extends React.Component<
@ -182,12 +183,15 @@ export default class TreeSelectControl extends React.Component<
this.state = { this.state = {
inputValue: '', inputValue: '',
tempValue: '',
isOpened: false isOpened: false
}; };
this.open = this.open.bind(this); this.open = this.open.bind(this);
this.close = this.close.bind(this); this.close = this.close.bind(this);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleTempChange = this.handleTempChange.bind(this);
this.handleConfirm = this.handleConfirm.bind(this);
this.clearValue = this.clearValue.bind(this); this.clearValue = this.clearValue.bind(this);
this.handleFocus = this.handleFocus.bind(this); this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this); this.handleBlur = this.handleBlur.bind(this);
@ -316,6 +320,22 @@ export default class TreeSelectControl extends React.Component<
); );
} }
handleTempChange(value: any) {
this.setState({
tempValue: value
});
}
handleConfirm() {
this.close();
this.setState(
{
inputValue: ''
},
() => this.resultChangeEvent(this.state.tempValue)
);
}
handleInputChange(value: string) { handleInputChange(value: string) {
const {autoComplete, data} = this.props; const {autoComplete, data} = this.props;
@ -582,13 +602,15 @@ export default class TreeSelectControl extends React.Component<
virtualThreshold, virtualThreshold,
itemHeight, itemHeight,
menuTpl, menuTpl,
enableDefaultIcon enableDefaultIcon,
useMobileUI
} = this.props; } = this.props;
let filtedOptions = let filtedOptions =
!isEffectiveApi(autoComplete) && searchable && this.state.inputValue !isEffectiveApi(autoComplete) && searchable && this.state.inputValue
? this.filterOptions(options, this.state.inputValue) ? this.filterOptions(options, this.state.inputValue)
: options; : options;
const mobileUI = useMobileUI && isMobile();
return ( return (
<TreeSelector <TreeSelector
@ -599,7 +621,7 @@ export default class TreeSelectControl extends React.Component<
labelField={labelField} labelField={labelField}
valueField={valueField} valueField={valueField}
disabled={disabled} disabled={disabled}
onChange={this.handleChange} onChange={mobileUI ? this.handleTempChange : this.handleChange}
joinValues={joinValues} joinValues={joinValues}
extractValue={extractValue} extractValue={extractValue}
delimiter={delimiter} delimiter={delimiter}
@ -644,6 +666,7 @@ export default class TreeSelectControl extends React.Component<
itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined} itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined}
itemRender={menuTpl ? this.renderOptionItem : undefined} itemRender={menuTpl ? this.renderOptionItem : undefined}
enableDefaultIcon={enableDefaultIcon} enableDefaultIcon={enableDefaultIcon}
useMobileUI={useMobileUI}
/> />
); );
} }
@ -711,8 +734,10 @@ export default class TreeSelectControl extends React.Component<
onBlur={this.handleBlur} onBlur={this.handleBlur}
onKeyDown={this.handleInputKeyDown} onKeyDown={this.handleInputKeyDown}
clearable={clearable} clearable={clearable}
allowInput={searchable || isEffectiveApi(autoComplete)} allowInput={!mobileUI && (searchable || isEffectiveApi(autoComplete))}
hasDropDownArrow hasDropDownArrow
readOnly={mobileUI}
useMobileUI
> >
{loading ? ( {loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" /> <Spinner loadingConfig={loadingConfig} size="sm" />
@ -745,6 +770,8 @@ export default class TreeSelectControl extends React.Component<
className={cx(`${ns}TreeSelect-popup`)} className={cx(`${ns}TreeSelect-popup`)}
isShow={isOpened} isShow={isOpened}
onHide={this.close} onHide={this.close}
showConfirm
onConfirm={this.handleConfirm}
> >
{this.renderOuter()} {this.renderOuter()}
</PopUp> </PopUp>

View File

@ -196,7 +196,8 @@ export class SearchBoxRenderer extends React.Component<
placeholder, placeholder,
onChange, onChange,
className, className,
style style,
useMobileUI
} = this.props; } = this.props;
const value = this.state.value; const value = this.state.value;
@ -221,6 +222,7 @@ export class SearchBoxRenderer extends React.Component<
onChange={this.handleChange} onChange={this.handleChange}
onFocus={() => this.dispatchEvent('focus')} onFocus={() => this.dispatchEvent('focus')}
onBlur={() => this.dispatchEvent('blur')} onBlur={() => this.dispatchEvent('blur')}
useMobileUI={useMobileUI}
/> />
); );
} }

View File

@ -63,6 +63,7 @@ import {getStyleNumber} from 'amis-core';
import {exportExcel} from './exportExcel'; import {exportExcel} from './exportExcel';
import type {IColumn, IRow} from 'amis-core'; import type {IColumn, IRow} from 'amis-core';
import intersection from 'lodash/intersection'; import intersection from 'lodash/intersection';
import {isMobile} from 'amis-core';
/** /**
* *
@ -3040,7 +3041,8 @@ export default class Table extends React.Component<TableProps, object> {
classnames: cx, classnames: cx,
affixColumns, affixColumns,
autoFillHeight, autoFillHeight,
autoGenerateFilter autoGenerateFilter,
useMobileUI
} = this.props; } = this.props;
this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。 this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。
@ -3050,10 +3052,11 @@ export default class Table extends React.Component<TableProps, object> {
const tableClassName = cx('Table-table', this.props.tableClassName, { const tableClassName = cx('Table-table', this.props.tableClassName, {
'Table-table--withCombine': store.combineNum > 0 'Table-table--withCombine': store.combineNum > 0
}); });
const mobileUI = useMobileUI && isMobile();
return ( return (
<div <div
className={cx('Table', className, { className={cx('Table', {'is-mobile': mobileUI}, className, {
'Table--unsaved': !!store.modified || !!store.moved, 'Table--unsaved': !!store.modified || !!store.moved,
'Table--autoFillHeight': autoFillHeight 'Table--autoFillHeight': autoFillHeight
})} })}

Some files were not shown because too many files have changed in this diff Show More