feat:调整列宽问题修复;新增sticky属性;列搜索;单元格支持编辑等

This commit is contained in:
wanglinfang 2021-12-31 17:33:11 +08:00
parent 422376dec7
commit 34f73bf4ca
22 changed files with 6085 additions and 874 deletions

File diff suppressed because it is too large Load Diff

View File

@ -775,14 +775,13 @@ export const components = [
import('../../docs/zh-CN/components/table.md').then(wrapDoc)
)
},
{
label: 'Table v2 表格',
path: '/zh-CN/components/table-v2',
getComponent: () =>
import('../../docs/zh-CN/components/table-v2.md').then(
makeMarkdownRenderer
)
},
// {
// label: 'Table v2 表格',
// path: '/zh-CN/components/table-v2',
// component: React.lazy(() =>
// import('../../docs/zh-CN/components/table-v2.md').then(wrapDoc)
// )
// },
{
label: 'Table View 表格视图',
path: '/zh-CN/components/table-view',

View File

@ -1043,6 +1043,7 @@ body.dark {
flex-direction: column;
flex-grow: 1;
// overflow: hidden;
width: calc(100% - 350px);
> div {
flex: 1;

View File

@ -1309,22 +1309,43 @@
--Table-searchableForm-borderRadius: #{px2rem(4px)};
--TableCell--edge-paddingX: var(--gap-md);
--TableCell--edge-paddingX-default: var(--gap-base);
--TableCell-filterBtn--onActive-color: var(--primary);
--TableCell-filterBtn-width: #{px2rem(16px)};
--TableCell-filterPopOver-dropDownItem-height: #{px2rem(34px)};
--TableCell-filterPopOver-dropDownItem-padding: 0 #{px2rem(12px)};
--TableCell-line-height-large: #{px2rem(40px)};
--TableCell-line-height-middle: #{px2rem(30px)};
--TableCell-height: #{px2rem(40px)};
--TableCell-height-default: #{px2rem(41px)};
--TableCell-height-large: #{px2rem(61px)};
--TableCell-height-small: #{px2rem(33px)};
--TableCell-paddingX: var(--gap-sm);
--TableCell-paddingX-large: var(--gap-base);
--TableCell-paddingX-small: var(--gap-xs);
--TableCell-paddingY: calc(
(var(--TableCell-height) - var(--Table-fontSize) * var(--Table-lineHeight)) /
2
);
--TableCell-paddingY-default: calc(
(var(--TableCell-height-default) - var(--Table-fontSize) * var(--Table-lineHeight)) /
2
);
--TableCell-paddingY-large: calc(
(var(--TableCell-height-large) - var(--Table-fontSize) * var(--Table-lineHeight)) /
2
);
--TableCell-paddingY-small: calc(
(var(--TableCell-height-small) - var(--Table-fontSize) * var(--Table-lineHeight)) /
2
);
--TableCell-searchBtn--onActive-color: var(--primary);
--TableCell-searchBtn-width: #{px2rem(16px)};
--TableCell-sortBtn--default-onActive-opacity: 1;
--TableCell-sortBtn--default-opacity: 0;
--TableCell-sortBtn--onActive-color: var(--primary);
--TableCell-sortBtn-width: #{px2rem(8px)};
--TableCell-icon-gap: var(--gap-sm);
--Table-fixedLeftLast-boxShadow: inset 10px 0 8px -8px #00000026;
--Table-fixedRightFirst-boxShadow: inset -10px 0 8px -8px #00000026;

View File

@ -0,0 +1,912 @@
.#{$ns}Table-v2 {
position: relative;
border-radius: var(--Table-borderRadius);
margin-bottom: var(--gap-md);
&.#{$ns}Table-bordered {
border-width: var(--Table-borderWidth) var(--Table-borderWidth) 0 var(--Table-borderWidth);
border-style: solid;
border-color: var(--Table-borderColor);
border-collapse: inherit;
.#{$ns}Table-table {
> thead > tr > th,
> tbody > tr > td,
> tfoot > tr > td {
border-right: var(--Table-borderWidth) solid var(--Table-borderColor);
&:last-child {
border-right: none;
}
}
}
.#{$ns}Table-footer {
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
}
.#{$ns}Table-title {
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
}
}
&.#{$ns}Table-large {
.#{$ns}Table-table {
> thead > tr {
> th {
padding: var(--TableCell-paddingY-large) var(--TableCell-paddingX-large);
}
}
> tbody > tr {
> td,
> th {
padding: var(--TableCell-paddingY-large) var(--TableCell-paddingX-large);
}
}
> tfoot > tr {
> td {
padding: var(--TableCell-paddingY-large) var(--TableCell-paddingX-large);
}
}
}
.#{$ns}TableCell-filterBtn {
right: calc(
var(--TableCell-paddingX-large) - var(--TableCell-filterBtn-width) / 2
);
}
}
&.#{$ns}Table-small {
.#{$ns}Table-table {
> thead > tr {
> th {
padding: var(--TableCell-paddingY-small) var(--TableCell-paddingX-small);
}
}
> tbody > tr {
> td,
> th {
padding: var(--TableCell-paddingY-small) var(--TableCell-paddingX-small);
}
}
> tfoot > tr {
> td {
padding: var(--TableCell-paddingY-small) var(--TableCell-paddingX-small);
}
}
}
.#{$ns}TableCell-filterBtn {
right: calc(
var(--TableCell-paddingX-small) - var(--TableCell-filterBtn-width) / 2
);
}
}
.#{$ns}Table-title,
.#{$ns}Table-footer {
background: var(--Table-heading-bg);
padding: calc(
(
var(--Table-heading-height) - var(--Table-fontSize) *
var(--lineHeightBase)
) / 2
)
var(--gap-sm);
}
.#{$ns}Table-header {
padding: var(--Table-toolbar-marginY) var(--Table-toolbar-marginX);
&.#{$ns}Table-sticky-holder {
position: sticky;
z-index: 3;
background: var(--Table-bg);
}
}
.#{$ns}Table-toolbar {
@include clearfix();
display: flex;
margin: 0 var(--Table-toolbar-marginX) var(--Table-toolbar-marginY);
flex-wrap: wrap;
.#{$ns}DropDown {
&-menuItem {
height: auto;
.#{$ns}Checkbox {
display: flex;
align-items: center;
}
}
}
}
.#{$ns}Table-header + .#{$ns}Table-toolbar {
padding-top: 0;
}
.#{$ns}Table-content {
min-height: 0.01%;
overflow-x: auto;
transform: translateZ(0);
th {
position: relative;
}
}
.#{$ns}Table-table {
width: 100%;
min-width: 100%;
margin-bottom: 0;
font-size: var(--Table-fontSize);
color: var(--Table-color);
background: var(--Table-bg);
border-spacing: 0;
border-collapse: collapse;
border: none;
& th,
& td {
text-align: left;
}
& th.text-center,
& td.text-center,
& th[colspan],
& td[colspan] {
text-align: center;
}
& th.text-right,
& td.text-right {
text-align: right;
}
> thead > tr {
> th {
background: var(--Table-thead-bg);
padding: var(--TableCell-paddingY-default) var(--TableCell-paddingX);
&:first-child {
padding-left: var(--TableCell--edge-paddingX-default);
}
&.#{$ns}Table-cell-last {
padding-right: var(--TableCell--edge-paddingX-default);
}
&:not(.#{$ns}Table-cell-last) {
border-right: var(--Table-thead-borderWidth) solid
var(--Table-thead-borderColor);
}
&.#{$ns}Table-row-expand-icon-cell {
border-right: 0;
}
font-size: var(--Table-thead-fontSize);
color: var(--Table-thead-color);
font-weight: var(--fontWeightNormal);
white-space: nowrap;
.#{$ns}Remark {
margin-left: var(--gap-xs);
position: relative;
top: 2px;
}
.#{$ns}Table-head-cell-wrapper {
display: flex;
}
}
}
> thead > tr:not(:last-child) {
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
}
> tbody > tr {
position: relative;
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
&.#{$ns}Table-summary-row {
> td {
background: var(--Table-thead-bg);
}
}
> th {
background: var(--Table-thead-bg);
color: var(--Table-thead-color);
font-weight: var(--fontWeightNormal);
white-space: nowrap;
border-right: var(--Table-thead-borderWidth) solid
var(--Table-thead-borderColor);
}
> td,
> th {
padding: var(--TableCell-paddingY-default) var(--TableCell-paddingX);
&:first-child {
padding-left: var(--TableCell--edge-paddingX-default);
}
&:last-child {
padding-right: var(--TableCell--edge-paddingX-default);
}
}
.#{$ns}Table-cell-wrapper-prefix {
display: flex;
.#{$ns}Table-expandBtn {
margin-right: 5px;
}
}
.#{$ns}Table-cell-height-large {
height: var(--TableCell-line-height-large);
line-height: var(--TableCell-line-height-large);
overflow: hidden;
}
.#{$ns}Table-cell-height-middle {
height: var(--TableCell-line-height-middle);
line-height: var(--TableCell-line-height-middle);
overflow: hidden;
}
@if var(--Table-strip-bg) !=transparent {
background: transparent;
&.#{$ns}Table-tr--odd {
background: var(--Table-strip-bg);
}
}
&.#{$ns}Table-tr--hasItemAction:hover {
cursor: pointer;
}
&:hover,
&.is-hovered {
background: var(--Table-onHover-bg);
border-color: var(--Table-onHover-borderColor);
color: var(--Table-onHover-color);
& + tr {
border-color: var(--Table-onHover-borderColor);
}
}
&.is-checked {
background: var(--Table-onChecked-bg);
border-color: var(--Table-onChecked-borderColor);
color: var(--Table-onChecked-color);
& + tr {
border-color: var(--Table-onChecked-borderColor);
}
}
&.is-moved,
&.is-modified {
background: var(--Table-onModified-bg);
border-color: var(--Table-onModified-borderColor);
color: var(--Table-onModified-color);
& + tr {
border-color: var(--Table-onModified-borderColor);
}
}
&.is-summary {
background: var(--Table-thead-bg);
color: var(--Table-thead-color);
font-weight: var(--fontWeightNormal);
}
&.bg-light {
@include color-variant($light, 2%, 3%, 3%, 5%);
color: $text-color;
}
&.bg-dark {
@include color-variant($dark, 5%, 10%, 5%, 10%);
@include font-variant($dark);
}
&.bg-black {
@include color-variant($black, 5%, 10%, 5%, 10%);
@include font-variant($black);
}
&.bg-primary {
@include color-variant($primary, 5%, 10%, 5%, 10%);
@include font-variant($primary);
}
&.bg-success {
@include color-variant($success, 5%, 10%, 5%, 10%);
@include font-variant($success);
}
&.bg-info {
@include color-variant($info, 5%, 10%, 5%, 10%);
@include font-variant($info);
}
&.bg-warning {
@include color-variant($warning, 5%, 10%, 5%, 10%);
@include font-variant($warning);
}
&.bg-danger {
@include color-variant($danger, 5%, 10%, 5%, 10%);
@include font-variant($danger);
}
&.is-dragging {
opacity: var(--Table-onDragging-opacity);
}
}
@for $i from 2 through 10 {
tr.#{$ns}Table-tr--#{$i}th.is-expanded {
.#{$ns}Table-expandCell:before {
right: px2rem(7px) + px2rem(-18px) * ($i - 1);
}
}
tr.#{$ns}Table-tr--#{$i}th {
.#{$ns}Table-expandBtn {
position: relative;
right: -(px2rem(18px)) * ($i - 1);
}
.#{$ns}Table-expandCell + td {
position: relative;
&::before {
content: '';
position: absolute;
width: px2rem(1px);
top: 0;
bottom: 0;
left: px2rem(-8px) + px2rem(18px) * ($i - 2);
height: auto;
background: var(--Table-tree-borderColor);
}
&::after {
content: '';
position: absolute;
height: px2rem(1px);
top: 50%;
left: px2rem(-8px) + px2rem(18px) * ($i - 2);
width: px2rem(10px);
background: var(--Table-tree-borderColor);
}
padding-left: px2rem(18px) * $i - px2rem(18px);
}
}
tr.#{$ns}Table-tr--#{$i}th.is-expandable {
.#{$ns}Table-expandCell + td {
padding-left: px2rem(18px) * ($i - 1);
}
}
tr.#{$ns}Table-tr--#{$i}th.is-last:not(.is-expanded) {
.#{$ns}Table-expandCell + td {
&::before {
height: 50%;
bottom: auto;
}
}
}
}
> thead > tr > th.#{$ns}Table-checkCell,
> tbody > tr > td.#{$ns}Table-checkCell {
border-right: 0;
white-space: nowrap;
.#{$ns}Checkbox {
margin: 0;
}
}
> thead > tr > th.#{$ns}Table-expandCell,
> tbody > tr > td.#{$ns}Table-expandCell {
border-right: 0;
width: px2rem(1px);
padding-right: 0;
}
> thead > tr > th.#{$ns}Table-dragCell,
> tbody > tr > td.#{$ns}Table-dragCell {
border-right: 0;
width: px2rem(1px);
padding-right: 0;
cursor: move;
> svg {
vertical-align: middle;
}
}
> tbody > tr > td.#{$ns}Table-expandCell {
position: relative;
@for $i from 1 through 7 {
.#{$ns}Table-divider-#{$i} {
position: absolute;
width: px2rem(1px);
top: 0;
bottom: 0;
height: 100%;
background: var(--Table-tree-borderColor);
right: px2rem(7px) + px2rem(-18px) * ($i - 1);
}
}
}
> tbody > tr.is-expanded > td.#{$ns}Table-expandCell {
&::before {
content: '';
position: absolute;
width: px2rem(1px);
top: 50%;
bottom: 0;
right: px2rem(7px);
height: auto;
background: var(--Table-tree-borderColor);
}
}
> thead > tr > th.#{$ns}TableCell--sortable {
padding-right: calc(
var(--TableCell-paddingX) + var(--TableCell-sortBtn-width)
);
position: relative;
}
> thead > tr > th.#{$ns}TableCell--searchable {
padding-right: calc(
var(--TableCell-paddingX) + var(--TableCell-searchBtn-width)
);
position: relative;
}
> thead > tr > th.#{$ns}TableCell--filterable {
padding-right: calc(
var(--TableCell-paddingX) + var(--TableCell-filterBtn-width)
);
position: relative;
}
> tbody > tr.#{$ns}Table-row-disabled {
background: var(--TableRow-onDisabled-bg);
color: var(--TableRow-onDisabled-color);
}
> tbody > tr:not(.#{$ns}Table-row-disabled) > td.#{$ns}Table-cell-row-hover {
background: var(--Table-onHover-bg);
border-color: var(--Table-onHover-borderColor);
color: var(--Table-onHover-color);
}
> thead > tr > th.#{$ns}Table-cell-fix-left-last,
> tbody > tr > td.#{$ns}Table-cell-fix-left-last,
> tfoot > tr > td.#{$ns}Table-cell-fix-left-last {
&:after {
position: absolute;
top: 0;
right: 0;
bottom: -1px;
width: 30px;
transform: translate(100%);
transition: box-shadow .3s;
content: "";
pointer-events: none;
}
}
> thead > tr > th.#{$ns}Table-cell-fix-right-first,
> tbody > tr > td.#{$ns}Table-cell-fix-right-first,
> tfoot > tr > td.#{$ns}Table-cell-fix-right-last {
&:after {
position: absolute;
top: 0;
bottom: -1px;
left: 0;
width: 30px;
transform: translate(-100%);
transition: box-shadow .3s;
content: "";
pointer-events: none;
}
}
> tbody > tr > td.#{$ns}Table-cell-expand-icon-cell {
text-align: center;
.#{$ns}Table-row-indent {
height: 1px;
}
}
> tbody > tr.#{$ns}Table-expanded-row > td {
background: var(--Table-onHover-bg);
}
> tfoot > tr {
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
> td {
padding: var(--TableCell-paddingY-default) var(--TableCell-paddingX);
background: var(--Table-thead-bg);
}
}
}
.#{$ns}Table-container {
.#{$ns}Table-header {
padding: 0;
}
}
&.#{$ns}Table-ping-left {
.#{$ns}Table-table {
> thead > tr > th.#{$ns}Table-cell-fix-left-last,
> tbody > tr > td.#{$ns}Table-cell-fix-left-last,
> tfoot > tr > td.#{$ns}Table-cell-fix-left-last {
&:after {
box-shadow: var(--Table-fixedLeftLast-boxShadow);
}
}
> tbody > tr:not(.#{$ns}Table-row-disabled) {
> td.#{$ns}Table-cell-fix-left {
border-right: none;
}
> td.#{$ns}Table-cell-fix-left:not(.#{$ns}Table-cell-row-hover) {
background: #FFF;
}
}
> tfoot > tr > td:not(:last-child) {
&.#{$ns}Table-cell-fix-left-last {
border-right: none;
}
}
> thead > tr > th:not(:last-child):not(:first-child) {
&.#{$ns}Table-cell-fix-left-last {
border-right: none;
}
}
}
}
&.#{$ns}Table-ping-right {
.#{$ns}Table-table {
> thead > tr > th.#{$ns}Table-cell-fix-right-first,
> tbody > tr > td.#{$ns}Table-cell-fix-right-first,
> tfoot > tr > td.#{$ns}Table-cell-fix-right-first {
&:after {
box-shadow: var(--Table-fixedRightFirst-boxShadow);
}
}
> tbody > tr:not(.#{$ns}Table-row-disabled) {
> td.#{$ns}Table-cell-fix-right {
border-right: none;
}
> td.#{$ns}Table-cell-fix-right:not(.#{$ns}Table-cell-row-hover) {
background: #FFF;
}
}
}
&:not(.#{$ns}Table-bordered) {
.#{$ns}Table-table {
> thead > tr > th.#{$ns}Table-cell-fix-right-first-prev {
border-right: none;
}
> thead > tr > th:not(:last-child) {
&.#{$ns}Table-cell-fix-right-first {
border-right: none;
}
}
}
}
}
&.#{$ns}Table-resizable {
.#{$ns}Table-table {
> thead > tr > th {
position: relative;
.#{$ns}Table-thead-resizable {
position: absolute;
width: 1px;
right: 0;
top: 0;
bottom: 0;
cursor: col-resize;
}
}
}
}
.#{$ns}Table-loading {
padding: var(--Table-loading-padding);
text-align: center;
}
.#{$ns}TableCell-sortBtn {
cursor: pointer;
width: var(--TableCell-sortBtn-width);
height: var(--gap-md);
position: static;
display: inline-block;
transform: none;
color: var(--icon-color);
margin-left: var(--TableCell-icon-gap);
&:hover {
color: var(--icon-onHover-color);
}
&--up > svg,
&--down > svg,
&--default > svg {
color: inherit;
width: 13px;
height: 13px;
}
&--up,
&--down,
&--default {
display: none;
z-index: 2;
font-style: normal;
&.is-active {
display: inline-block;
}
}
&--default {
&.is-active {
color: var(--text--muted-color);
&:hover {
color: var(--text-color);
}
}
}
&--up,
&--down {
&.is-active {
color: var(--TableCell-sortBtn--onActive-color);
}
}
}
.#{$ns}TableCell-searchBtn {
cursor: pointer;
position: static;
transform: translateY(-50%);
color: var(--text--muted-color);
margin-left: var(--TableCell-icon-gap);
svg.icon {
width: 12px;
height: 12px;
}
&:hover {
color: var(--text-color);
}
&.is-active {
color: var(--TableCell-searchBtn--onActive-color);
}
}
.#{$ns}TableCell-searchPopOver {
border: none;
min-width: px2rem(320px);
max-width: px2rem(640px);
.#{$ns}Panel {
margin: 0;
}
}
.#{$ns}TableCell-filterBtn {
cursor: pointer;
width: var(--TableCell-filterBtn-width);
position: static;
display: inline-block;
transform: none;
color: var(--text--muted-color);
margin-left: var(--TableCell-icon-gap);
svg.icon {
width: 13px;
height: 13px;
}
&:hover {
color: var(--text-color);
}
&.is-active {
color: var(--TableCell-filterBtn--onActive-color);
}
.#{$ns}Remark {
display: inline;
}
}
.#{$ns}TableCell-filterPopOver {
border: none;
width: px2rem(160px);
.#{$ns}DropDown-menu {
margin: 0;
padding: 0;
.#{$ns}DropDown-divider {
height: var(--TableCell-filterPopOver-dropDownItem-height);
line-height: var(--TableCell-filterPopOver-dropDownItem-height);
padding: var(--TableCell-filterPopOver-dropDownItem-padding);
background: var(--white);
margin: 0;
&:hover {
background: var(--light);
color: var(--primary);
}
&.is-selected {
background: var(--light);
color: var(--primary);
}
.#{$ns}Checkbox {
width: 100%;
margin: 0;
}
}
}
.#{$ns}DropDown-multiple-menu {
text-align: center;
border-top: 1px solid var(--Table-borderColor);
.#{$ns}Button {
margin: 0 5px;
padding: 0 10px;
}
&:hover {
background: none;
}
}
}
.#{$ns}TableCell-selectionBtn {
cursor: pointer;
margin-left: 4px;
svg.icon {
transform: rotate(270deg);
font-size: 12px;
}
}
.#{$ns}TableCell-selectionPopOver {
.#{$ns}DropDown-menu {
margin: 0;
padding: 0;
}
}
&.#{$ns}Table-expandBtn {
position: relative;
z-index: 1;
color: var(--Table-expandBtn-color);
display: inline-flex;
justify-content: center;
align-items: center;
width: px2rem(14px);
line-height: 1;
height: 16px;
> svg {
display: inline-block;
text-align: center;
cursor: pointer;
transition: transform ease-in-out var(--animation-duration),
top ease-in-out var(--animation-duration);
position: relative;
transform-origin: 50% 50%;
width: px2rem(10px);
height: px2rem(10px);
top: 0;
}
&.is-active > svg {
transform: rotate(90deg);
}
&:hover {
text-decoration: none;
}
}
.#{$ns}Table-table > tbody > tr:hover .#{$ns}Table-dragBtn,
.#{$ns}Table-table > tbody > tr.is-dragging .#{$ns}Table-dragBtn,
.#{$ns}Table-table > tbody > tr.is-drop-allowed .#{$ns}Table-dragBtn {
visibility: visible;
}
.fake-hide {
visibility: hidden;
position: absolute;
}
.#{$ns}Table-badge {
position: absolute;
top: 0;
left: 0;
}
}
.#{$ns}InputTable-toolbar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.#{$ns}InputTable-pager {
margin-left: auto;
}
.#{$ns}OperationField {
margin: px2rem(-3px);
> .#{$ns}Button,
> .#{$ns}Button--disabled-wrap > .#{$ns}Button {
margin: px2rem(3px);
}
> .#{$ns}Button--disabled-wrap > .#{$ns}Button--link {
padding: 0;
}
> .#{$ns}Button--link {
padding: 0;
margin-right: px2rem(10px);
}
}

View File

@ -8,34 +8,6 @@
margin-bottom: var(--gap-sm);
}
&-bordered {
border: var(--Table-borderWidth) solid var(--Table-borderColor);
border-collapse: inherit;
.#{$ns}Table-container {
border-top: var(--Table-borderWidth) solid var(--Table-borderColor);
}
.#{$ns}Table-table {
> thead > tr > th,
> tbody > tr > td,
> tfoot > tr > td {
border-right: var(--Table-borderWidth) solid var(--Table-borderColor);
&:last-child {
border-right: none;
}
}
}
.#{$ns}Table-footer {
border-top: var(--Table-borderWidth) solid var(--Table-borderColor);
}
.#{$ns}Table-title {
border-bottom: var(--Table-borderWidth) solid var(--Table-borderColor);
}
}
&-fixedLeft,
&-fixedRight {
position: absolute;
@ -127,9 +99,7 @@
}
}
&-heading,
&-title,
&-footer {
&-heading {
background: var(--Table-heading-bg);
padding: calc(
(
@ -261,6 +231,7 @@
background: var(--Table-bg);
border-spacing: 0;
border-collapse: collapse;
border: var(--Table-borderWidth) solid var(--Table-borderColor);
& th,
& td {
@ -632,124 +603,6 @@
);
position: relative;
}
> tbody > tr > td.#{$ns}Table-cell-fix-left,
> tbody > tr > td.#{$ns}Table-cell-fix-right,
> tfoot > tr > td.#{$ns}Table-cell-fix-left,
> tfoot > tr > td.#{$ns}Table-cell-fix-right {
background: #FFF;
}
> tbody > tr > td.#{$ns}Table-cell-row-hover {
background: var(--Table-onHover-bg);
border-color: var(--Table-onHover-borderColor);
color: var(--Table-onHover-color);
}
> thead > tr > th.#{$ns}Table-cell-fix-left-last,
> tbody > tr > td.#{$ns}Table-cell-fix-left-last,
> tfoot > tr > td.#{$ns}Table-cell-fix-left-last {
&:after {
position: absolute;
top: 0;
right: 0;
bottom: -1px;
width: 30px;
transform: translate(100%);
transition: box-shadow .3s;
content: "";
pointer-events: none;
}
}
> thead > tr > th.#{$ns}Table-cell-fix-right-first,
> tbody > tr > td.#{$ns}Table-cell-fix-right-first,
> tfoot > tr > td.#{$ns}Table-cell-fix-right-last {
&:after {
position: absolute;
top: 0;
bottom: -1px;
left: 0;
width: 30px;
transform: translate(-100%);
transition: box-shadow .3s;
content: "";
pointer-events: none;
}
}
> tbody > tr > td.#{$ns}Table-cell-expand-icon-cell {
text-align: center;
.fa-minus-square,
.fa-plus-square {
cursor: pointer;
}
}
> tbody > tr.#{$ns}Table-expanded-row > td {
background: var(--Table-onHover-bg);
}
> tfoot > tr > td {
padding: var(--TableCell-paddingY) var(--TableCell-paddingX);
}
}
&-container {
.#{$ns}Table-header {
padding: 0;
}
}
&.#{$ns}Table-ping-left {
.#{$ns}Table-table {
> thead > tr > th.#{$ns}Table-cell-fix-left-last,
> tbody > tr > td.#{$ns}Table-cell-fix-left-last,
> tfoot > tr > td.#{$ns}Table-cell-fix-left-last {
&:after {
box-shadow: var(--Table-fixedLeftLast-boxShadow);
}
}
}
}
&.#{$ns}Table-ping-right {
.#{$ns}Table-table {
> thead > tr > th.#{$ns}Table-cell-fix-right-first,
> tbody > tr > td.#{$ns}Table-cell-fix-right-first,
> tfoot > tr > td.#{$ns}Table-cell-fix-right-first {
&:after {
box-shadow: var(--Table-fixedRightFirst-boxShadow);
}
}
> thead > tr > th.#{$ns}Table-cell-fix-right-first {
border-right: var(--Table-thead-borderWidth) solid var(--Table-thead-borderColor);
}
}
}
&.#{$ns}Table-resizable {
.#{$ns}Table-table {
> thead > tr > th {
position: relative;
.#{$ns}Table-thead-resizable {
position: absolute;
width: 1px;
right: 0;
top: 0;
bottom: 0;
cursor: col-resize;
}
}
}
}
.#{$ns}Table-loading {
padding: var(--Table-loading-padding);
text-align: center;
}
&Cell-sortBtn {
@ -805,9 +658,6 @@
color: var(--TableCell-sortBtn--onActive-color);
}
}
&:not(:last-child) {
right: calc(var(--TableCell-paddingX) - var(--TableCell-sortBtn-width) / 2 + 15px);
}
}
&Cell-searchBtn {
@ -903,20 +753,6 @@
}
}
}
.#{$ns}DropDown-multiple-menu {
text-align: center;
border-top: 1px solid var(--Table-borderColor);
.#{$ns}Button {
margin: 0 5px;
padding: 0 10px;
}
&:hover {
background: none;
}
}
}
&-itemActions-wrap {

View File

@ -49,6 +49,7 @@
@import '../components/wizard';
@import '../components/crud';
@import '../components/table';
@import '../components/table-v2';
@import '../components/column-toggler';
@import '../components/list';
@import '../components/cards';

View File

@ -369,13 +369,13 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
--Table-color: #333;
--Table-thead-color: #333;
--Table-lineHeight: 20 / 12;
--Table-borderColor: #f5f5f5;
--Table-borderColor: #{$G8};
--Table-tree-borderColor: #{darken(#f5f5f5, 10%)};
--Table-thead-bg: #f5f5f5;
--Table-thead-bg: #{$G10};
--Table-thead-borderColor: #fff;
--Table-thead-iconColor: #999;
--Table-strip-bg: transparent;
--Table-onHover-bg: #f5f5f5;
--Table-onHover-bg: #{$B1};
--Table-onHover-bg-rgb: 245, 251, 255;
--Table-onHover-borderColor: #eceff8;
--Table-onChecked-bg: #f0faff;
@ -391,6 +391,9 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06),
--TableCell-filterPopOver-dropDownItem-height: #{px2rem(30px)};
--TableCell-filterPopOver-dropDownItem-padding: 0 #{px2rem(10px)};
--TableRow-onDisabled-bg: #{$G10};
--TableRow-onDisabled-color: #{$G6};
// listControl
--ListControl-item-borderWidth: #{px2rem(1px)};
--ListControl-item-borderRadius: #{$R3};

View File

@ -118,6 +118,7 @@ import {FormControlSchema} from './renderers/Form/Control';
import {TransferPickerControlSchema} from './renderers/Form/TransferPicker';
import {TabsTransferPickerControlSchema} from './renderers/Form/TabsTransferPicker';
import {JSONSchemaEditorControlSchema} from './renderers/Form/JSONSchemaEditor';
import {TableSchemaV2} from './renderers/Table-v2';
// 每加个类型,这补充一下。
export type SchemaType =
@ -392,6 +393,7 @@ export type SchemaObject =
| StatusSchema
| SpinnerSchema
| TableSchema
| TableSchemaV2
| TabsSchema
| TasksSchema
| VBoxSchema

View File

@ -20,12 +20,16 @@ export interface Props extends ThemeProps, LocaleProps {
children?: any;
tagName?: string;
style?: Object;
column?: ColumnProps
column?: ColumnProps;
wrapperComponent: any;
groupId?: string; // 表头分组随机生成的id
depth?: number; // 表头分组
}
export class BodyCell extends React.Component<Props> {
static defaultProps = {
fixed: '',
wrapperComponent: 'td',
rowSpan: null,
colSpan: null
};
@ -38,28 +42,16 @@ export class BodyCell extends React.Component<Props> {
key,
children,
className,
tagName,
style,
column,
style,
groupId,
depth,
wrapperComponent: Component,
classnames: cx
} = this.props;
if (tagName === 'TH') {
return (
<th
key={key || null}
rowSpan={rowSpan && rowSpan > 1 ? rowSpan : null}
colSpan={colSpan && colSpan > 1 ? colSpan : null}
className={cx('Table-cell', className, {
[cx(`Table-cell-fix-${fixed}`)] : fixed
})}
style={fixed ? {position: 'sticky', zIndex} : style}
>{children}</th>
);
}
return (
<td
<Component
key={key || null}
rowSpan={rowSpan && rowSpan > 1 ? rowSpan : null}
colSpan={colSpan && colSpan > 1 ? colSpan : null}
@ -67,8 +59,10 @@ export class BodyCell extends React.Component<Props> {
[cx(`Table-cell-fix-${fixed}`)] : fixed,
[`text-${column?.align}`] : column?.align
})}
style={fixed ? {position: 'sticky', zIndex} : {}}
>{children}</td>
style={fixed ? {position: 'sticky', zIndex, ...style} : {...style}}
data-group-id={groupId || null}
data-depth={depth || null}
>{children}</Component>
);
}
}

View File

@ -0,0 +1,115 @@
/**
* @file table/HeadCellDropDown
* @author fex
*/
import React from 'react';
import {findDOMNode} from 'react-dom';
import {themeable, ThemeProps} from '../../theme';
import {LocaleProps, localeable} from '../../locale';
import Overlay from '../Overlay';
import PopOver from '../PopOver';
export interface FilterPayload {
closeDropdown?: boolean;
}
export interface FilterDropdownProps {
setSelectedKeys?: (keys: Array<string | number> | string) => void,
selectedKeys?: Array<string | number> | string,
confirm: (payload: FilterPayload) => void,
clearFilters?: () => void
}
export interface Props extends ThemeProps, LocaleProps {
filterIcon: Function | React.ReactNode; // 图标方法 返回ReactNode
className: string; // 图标样式
layerClassName: string; // 展开层样式
active: boolean; // 图标是否高亮
popOverContainer?: () => Element | Text | null;
filterDropdown: (payload: FilterDropdownProps) => JSX.Element | null ; // 菜单内容
selectedKeys?: Array<string | number> | string;
setSelectedKeys?: (keys: Array<string | number> | string) => void;
}
export interface State {
isOpened: boolean;
}
export class HeadCellDropDown extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isOpened: false
}
this.openLayer = this.openLayer.bind(this);
this.closeLayer = this.closeLayer.bind(this);
}
render() {
const {isOpened} = this.state;
const {
popOverContainer,
active,
className,
layerClassName,
filterIcon,
filterDropdown,
classnames: cx,
classPrefix: ns
} = this.props;
return (
<span
className={cx(
className,
active ? 'is-active' : ''
)}
>
<span onClick={this.openLayer}>
{filterIcon && typeof filterIcon === 'function'
? filterIcon(active) : (filterIcon || null)}
</span>
{
isOpened ? (
<Overlay
container={popOverContainer || (() => findDOMNode(this))}
placement="left-bottom-left-top right-bottom-right-top"
target={
popOverContainer ? () => findDOMNode(this)!.parentNode : null
}
show
>
<PopOver
classPrefix={ns}
onHide={this.closeLayer}
className={cx(layerClassName)}
overlay
>
{filterDropdown && typeof filterDropdown === 'function'
? filterDropdown({...this.props, confirm: (payload: FilterPayload) => {
if (!(payload && payload.closeDropdown === false)) {
this.closeLayer();
}
}}) : (filterDropdown || null)}
</PopOver>
</Overlay>)
: null
}
</span>
);
}
openLayer() {
this.setState({isOpened: true});
}
closeLayer() {
this.setState({isOpened: false});
}
}
export default themeable(localeable(HeadCellDropDown));

View File

@ -9,19 +9,17 @@ import isEqual from 'lodash/isEqual';
import {themeable, ThemeProps} from '../../theme';
import {LocaleProps, localeable} from '../../locale';
import Overlay from '../Overlay';
import PopOver from '../PopOver';
import HeadCellDropDown, {FilterDropdownProps, FilterPayload} from './HeadCellDropDown';
import CheckBox from '../Checkbox';
import Button from '../Button';
import {Icon} from '../icons';
export interface Props extends ThemeProps, LocaleProps {
column: any;
popOverContainer?: () => Element | Text | null;
onFilter?: Function;
onQuery?: Function;
filteredValue?: Array<string>;
filterMultiple?: boolean;
popOverContainer?: () => Element | Text | null;
}
export interface OptionProps {
@ -33,7 +31,6 @@ export interface OptionProps {
export interface State {
options: Array<OptionProps>;
isOpened: boolean;
filteredValue: Array<string>;
}
@ -48,17 +45,11 @@ export class HeadCellFilter extends React.Component<Props, State> {
this.state = {
options: [],
isOpened: false,
filteredValue: props.filteredValue || []
}
this.openLayer = this.openLayer.bind(this);
this.closeLayer = this.closeLayer.bind(this);
}
alterOptions(options: Array<any>) {
const {column} = this.props;
options = options.map(option => ({
...option,
selected: this.state.filteredValue.indexOf(option.value) > -1
@ -83,7 +74,7 @@ export class HeadCellFilter extends React.Component<Props, State> {
}
render() {
const {isOpened, options} = this.state;
const {options} = this.state;
const {
column,
popOverContainer,
@ -91,35 +82,10 @@ export class HeadCellFilter extends React.Component<Props, State> {
classPrefix: ns
} = this.props;
return (
<span
className={cx(
`${ns}TableCell-filterBtn`,
column.filtered || options && options.some((item: any) => item.selected) ? 'is-active' : ''
)}
>
<span onClick={this.openLayer}>
<Icon icon="column-filter" className="icon" />
</span>
{
isOpened ? (
<Overlay
container={popOverContainer || (() => findDOMNode(this))}
placement="left-bottom-left-top right-bottom-right-top"
target={
popOverContainer ? () => findDOMNode(this) : null
}
show
>
<PopOver
classPrefix={ns}
onHide={this.closeLayer}
className={cx(
`${ns}TableCell-filterPopOver`
)}
overlay
>
{options && options.length > 0 ? (
const filterProps = {
filterDropdown: (payload: FilterDropdownProps) => {
const {setSelectedKeys, selectedKeys, confirm, clearFilters} = payload
return options && options.length > 0 ? (
<ul className={cx('DropDown-menu')}>
{!column.filterMultiple
? options.map((option: any, index) => (
@ -128,7 +94,7 @@ export class HeadCellFilter extends React.Component<Props, State> {
className={cx({
'is-active': option.selected
})}
onClick={this.handleClick.bind(this, option.value)}
onClick={() => this.handleClick(confirm, setSelectedKeys, [option.value])}
>
{option.text}
</li>
@ -137,7 +103,9 @@ export class HeadCellFilter extends React.Component<Props, State> {
<li key={index}>
<CheckBox
classPrefix={ns}
onChange={this.handleCheck.bind(this, option.value)}
onChange={e =>
this.handleCheck(confirm, setSelectedKeys, e
? [option.value] : option.value)}
checked={option.selected}
>
{option.text}
@ -146,65 +114,79 @@ export class HeadCellFilter extends React.Component<Props, State> {
))}
{column.filterMultiple ? (
<li
key="DropDown-multiple-menu"
key="dropDown-multiple-menu"
className={cx('DropDown-multiple-menu')}
>
<Button
size={'xs'}
level={'primary'}
onClick={this.handleConfirmClick.bind(this)}
onClick={() => this.handleConfirmClick(confirm)}
></Button>
<Button
size={'xs'}
onClick={this.handleCancelClick.bind(this)}
onClick={() => this.handleCancelClick(confirm, setSelectedKeys)}
></Button>
</li>
) : null}
</ul>
) : null}
</PopOver>
</Overlay>)
: null
}
</span>
) : null
},
setSelectedKeys: (keys: Array<string>) => this.setState({filteredValue: keys})
};
return (
<HeadCellDropDown
className={`${ns}TableCell-filterBtn`}
layerClassName={`${ns}TableCell-filterPopOver`}
filterIcon={<Icon icon="column-filter" className="icon" />}
active={column.filtered || options && options.some((item: any) => item.selected)}
popOverContainer={popOverContainer ? popOverContainer : () => findDOMNode(this)}
selectedKeys={this.state.filteredValue}
{...filterProps}
>
</HeadCellDropDown>
);
}
openLayer() {
this.setState({isOpened: true});
handleClick(
confirm: (payload?: FilterPayload) => void,
setSelectedKeys?: (keys?: (string | Array<string | number>)) => void,
selectedKeys?: Array<string>
) {
const {onFilter, column} = this.props;
setSelectedKeys && setSelectedKeys(selectedKeys);
onFilter && onFilter({[column.key] : selectedKeys});
confirm();
}
closeLayer() {
this.setState({isOpened: false});
}
handleClick(value: string) {
const {onQuery, column} = this.props;
this.setState({filteredValue: [value]});
onQuery && onQuery({[column.key] : value});
this.closeLayer();
}
handleCheck(value: string) {
handleCheck(
confirm: (payload?: FilterPayload) => void,
setSelectedKeys?: ((keys: (string | Array<string | number>)) => void | undefined),
selectedKeys?: Array<string>
) {
const filteredValue = this.state.filteredValue;
if (value) {
this.setState({filteredValue: [...filteredValue, value]});
} else {
this.setState({filteredValue: filteredValue.filter(v => v !== value)});
// 选中
if (Array.isArray(selectedKeys)) {
setSelectedKeys && setSelectedKeys([...filteredValue, ...selectedKeys]);
} else { // 取消选中
setSelectedKeys && setSelectedKeys(filteredValue.filter(v => v !== selectedKeys));
}
}
handleConfirmClick() {
const {onQuery, column} = this.props;
onQuery && onQuery({[column.key] : this.state.filteredValue});
this.closeLayer();
handleConfirmClick(confirm: (payload?: FilterPayload) => void) {
const {onFilter, column} = this.props;
onFilter && onFilter({[column.key] : this.state.filteredValue});
confirm();
}
handleCancelClick() {
this.setState({filteredValue: []});
this.closeLayer();
handleCancelClick(
confirm: (payload?: FilterPayload) => void,
setSelectedKeys?: ((keys: (string | Array<string | number>)) => void | undefined)
) {
setSelectedKeys && setSelectedKeys([]);
confirm();
}
}

View File

@ -0,0 +1,86 @@
/**
* @file table/HeadCellSelect
* @author fex
*/
import React from 'react';
import {findDOMNode} from 'react-dom';
import {themeable, ThemeProps} from '../../theme';
import {LocaleProps, localeable} from '../../locale';
import HeadCellDropDown, {FilterPayload} from './HeadCellDropDown';
import {RowSelectionOptionProps} from './index';
import {Icon} from '../icons';
export interface Props extends ThemeProps, LocaleProps {
selections: Array<RowSelectionOptionProps>;
keys: Array<string | number> | string;
popOverContainer?: () => Element | Text | null;
}
export interface State {
key: Array<string | number> | string;
}
export class HeadCellSelect extends React.Component<Props, State> {
static defaultProps = {
selections: []
};
constructor(props: Props) {
super(props);
this.state = {
key: ''
}
}
render() {
const {
selections,
keys: allKeys,
popOverContainer,
classnames: cx,
classPrefix: ns
} = this.props;
return (
<HeadCellDropDown
className={`${ns}TableCell-selectionBtn`}
layerClassName={`${ns}TableCell-selectionPopOver`}
filterIcon={<Icon icon="left-arrow" className="icon" />}
active={false}
popOverContainer={popOverContainer ? popOverContainer : () => findDOMNode(this)}
filterDropdown={({setSelectedKeys, selectedKeys, confirm, clearFilters}) => {
return <ul className={cx('DropDown-menu')}>
{selections.map((item, index) => (
<li
key={index}
onClick={() => {
item.onSelect && item.onSelect(allKeys);
this.handleClick(confirm, setSelectedKeys, item.key);
}}>
{item.text}
</li>
))}
</ul> ;
}}
setSelectedKeys={keys => this.setState({key: keys})}
selectedKeys={this.state.key}
>
</HeadCellDropDown>
);
}
handleClick(
confirm: (payload?: FilterPayload) => void,
setSelectedKeys?: (keys?: Array<string | number> | string) => void | undefined,
selectedKeys?: Array<string> | string
) {
setSelectedKeys && setSelectedKeys(selectedKeys);
confirm();
}
}
export default themeable(localeable(HeadCellSelect));

File diff suppressed because it is too large Load Diff

View File

@ -322,14 +322,11 @@ export const HocQuickEdit =
);
}
openQuickEdit(e) {
openQuickEdit() {
currentOpened = this;
this.setState({
isOpened: true
});
// QuickEdit在table中使用时如果table配置了checkOnItemClick会同时触发行选中
// 所以这里阻止冒泡一下
e.stopPropagation && e.stopPropagation();
}
closeQuickEdit() {

View File

@ -0,0 +1,244 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import {RendererProps} from '../../factory';
import {Action} from '../../types';
import {Icon} from '../../components/icons';
import {setVariable} from '../../utils/helper';
import {ITableStore} from '../../store/table-v2';
import HeadCellDropDown from '../../components/table/HeadCellDropDown';
export interface QuickSearchConfig {
type?: string;
controls?: any;
tabs?: any;
fieldSet?: any;
[propName: string]: any;
}
export interface HeadCellSearchProps extends RendererProps {
name: string;
searchable: boolean | QuickSearchConfig;
classPrefix: string;
onFilter?: (values: object) => void;
onAction?: Function;
store: ITableStore;
}
export class HeadCellSearchDropDown extends React.Component<
HeadCellSearchProps,
any
> {
formItems: Array<string> = [];
constructor(props: HeadCellSearchProps) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleAction = this.handleAction.bind(this);
}
buildSchema() {
const {searchable, sortable, name, label, translate: __} = this.props;
let schema: any;
if (searchable === true) {
schema = {
title: '',
controls: [
{
type: 'text',
name,
placeholder: label,
clearable: true
}
]
};
} else if (searchable) {
if (searchable.controls || searchable.tabs || searchable.fieldSet) {
schema = {
title: '',
...searchable,
controls: Array.isArray(searchable.controls)
? searchable.controls.concat()
: undefined
};
} else {
schema = {
title: '',
className: searchable.formClassName,
controls: [
{
type: searchable.type || 'text',
name: searchable.name || name,
placeholder: label,
...searchable
}
]
};
}
}
if (schema && schema.controls && sortable) {
schema.controls.unshift(
{
type: 'hidden',
name: 'orderBy',
value: name
},
{
type: 'button-group',
name: 'order',
label: __('sort'),
options: [
{
label: __('asc'),
value: 'asc'
},
{
label: __('desc'),
value: 'desc'
}
]
}
);
}
if (schema) {
const formItems: Array<string> = [];
schema.controls?.forEach(
(item: any) =>
item.name &&
item.name !== 'orderBy' &&
item.name !== 'order' &&
formItems.push(item.name)
);
this.formItems = formItems;
schema = {
...schema,
type: 'form',
wrapperComponent: 'div',
actions: [
{
type: 'button',
label: __('reset'),
actionType: 'clear-and-submit'
},
{
type: 'button',
label: __('cancel'),
actionType: 'cancel'
},
{
label: __('search'),
type: 'submit',
primary: true
}
]
};
}
return schema || 'error';
}
handleAction(e: any, action: Action, ctx: object, confirm: Function) {
const {onAction} = this.props;
if (action.actionType === 'cancel' || action.actionType === 'close') {
confirm();
return;
}
if (action.actionType === 'reset') {
confirm();
this.handleReset();
return;
}
onAction && onAction(e, action, ctx);
}
handleReset() {
const {onFilter, data, name, store} = this.props;
const values = {...data};
this.formItems.forEach(key => setVariable(values, key, undefined));
if (values.orderBy === name) {
values.orderBy = '';
values.order = 'asc';
}
store.updateQuery(values);
onFilter && onFilter(values);
}
handleSubmit(values: any, confirm: Function) {
const {onFilter, name, store} = this.props;
if (values.order) {
values = {
...values,
orderBy: name
};
}
store.updateQuery(values);
onFilter && onFilter(values);
confirm();
}
isActive() {
const {data, name, orderBy} = this.props;
return (orderBy && orderBy === name) || this.formItems.some(key => data?.[key]);
}
render() {
const {
render,
name,
data,
searchable,
store,
orderBy,
popOverContainer,
classPrefix: ns,
classnames: cx
} = this.props;
const formSchema = this.buildSchema();
const isActive = this.isActive();
return (
<HeadCellDropDown
className={`${ns}TableCell-searchBtn`}
layerClassName={cx(
`${ns}TableCell-searchPopOver`,
(searchable as any).className
)}
active={isActive}
filterIcon={<Icon icon="search" className="icon" />}
popOverContainer={popOverContainer ? popOverContainer : () => findDOMNode(this)}
filterDropdown={({setSelectedKeys, selectedKeys, confirm, clearFilters}) => {
return render('quick-search-form', formSchema, {
data: {
...data,
orderBy,
order: orderBy && orderBy === name ? (store as ITableStore).order : ''
},
onSubmit: (values: object) => this.handleSubmit(values, confirm),
onAction: (e: any, action: Action, ctx: object) => {
this.handleAction(e, action, ctx, confirm);
}
}) as JSX.Element;
}}>
</HeadCellDropDown>
);
}
}

View File

@ -0,0 +1,19 @@
import {Renderer} from '../../factory';
import {TableCell} from '../Table';
import QuickEdit from '../QuickEdit';
import Copyable from '../Copyable';
import PopOverable from '../PopOver';
@Renderer({
type: 'cell-field',
name: 'cell-field'
})
@PopOverable()
@Copyable()
@QuickEdit()
export class CellFieldRenderer extends TableCell {
static defaultProps = {
...TableCell.defaultProps,
wrapperComponent: 'div'
};
}

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,6 @@ export class HeadCellSearchDropDown extends React.Component<
this.open = this.open.bind(this);
this.close = this.close.bind(this);
this.close = this.close.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleAction = this.handleAction.bind(this);
}

View File

@ -156,16 +156,14 @@ export class TableBody extends React.Component<TableBodyProps> {
classnames: cx,
rows,
prefixRowClassName,
affixRowClassName,
footable
affixRowClassName
} = this.props;
if (!(Array.isArray(items) && items.length)) {
return null;
}
// 开启了footable不需要考虑设置了breakpoint的列了
const filterColumns = columns.filter(item => item.toggable && !(footable && item.breakpoint));
const filterColumns = columns.filter(item => item.toggable);
const result: any[] = [];
for (let index = 0; index < filterColumns.length; index++) {
@ -184,16 +182,14 @@ export class TableBody extends React.Component<TableBodyProps> {
}
// 缺少的单元格补齐
// 考虑是否设置了
// 开启了footable不需要考虑设置了breakpoint的列了
const appendLen =
(footable ? columns.filter(item => !item.breakpoint).length : columns.length) - result.reduce((p, c) => p + (c.colSpan || 1), 0);
columns.length - result.reduce((p, c) => p + (c.colSpan || 1), 0);
if (appendLen) {
const item = result.pop();
result.push({
...item,
colSpan: (item?.colSpan || 1) + appendLen
colSpan: (item.colSpan || 1) + appendLen
});
}
const ctx = createObject(data, {

View File

@ -3,11 +3,35 @@ import {
getParent,
Instance,
SnapshotIn,
isAlive
isAlive,
IAnyModelType,
flow,
getEnv
} from 'mobx-state-tree';
import find from 'lodash/find';
import isEqual from 'lodash/isEqual';
import {isVisible, hasVisibleExpression} from '../utils/helper';
import {iRendererStore} from './iRenderer';
import {
isVisible,
hasVisibleExpression,
isObjectShallowModified,
qsstringify,
guid,
eachTree,
createObject,
flattenTree,
isObject,
immutableExtends,
isEmpty,
extendObject
} from '../utils/helper';
import {normalizeApiResponseData} from '../utils/api';
import {Api, Payload, fetchOptions, ApiObject} from '../types';
import {ServiceStore} from './service';
class ServerError extends Error {
type = 'ServerError';
}
export const Column = types
.model('Column', {
@ -18,7 +42,11 @@ export const Column = types
pristine: types.optional(types.frozen(), undefined),
toggable: true,
index: 0,
type: ''
type: '',
children: types.optional(
types.array(types.late((): IAnyModelType => Column)),
[]
)
})
.actions(self => ({
toggleToggle() {
@ -41,29 +69,133 @@ export type SColumn = SnapshotIn<typeof Column>;
export const Row = types
.model('Row', {
data: types.frozen({} as any)
storeType: 'Row',
id: types.identifier,
key: types.string,
pristine: types.frozen({} as any), // 原始数据
data: types.frozen({} as any),
index: types.number,
newIndex: types.number,
depth: types.number, // 当前children位于第几层便于使用getParent获取最顶层TableStore
children: types.optional(
types.array(types.late((): IAnyModelType => Row)),
[]
),
path: '' // 行数据的位置
})
.views(self => ({
get checked(): boolean {
return (getParent(self, self.depth * 2) as ITableStore).isSelected(
self as IRow
);
},
get modified() {
if (!self.data) {
return false;
}
return Object.keys(self.data).some(
key => !isEqual(self.data[key], self.pristine[key])
);
},
get locals(): any {
let children: Array<any> | null = null;
if (self.children.length) {
children = self.children.map(item => item.locals);
}
const parent = getParent(self, 2) as ITableStore;
return createObject(
extendObject((getParent(self, self.depth * 2) as ITableStore).data, {
index: self.index,
// todo 以后再支持多层,目前先一层
parent: parent.storeType === Row.name ? parent.data : undefined
}),
children
? {
...self.data,
children
}
: self.data
);
}
}))
.actions(self => ({
replaceWith(data: any) {
Object.keys(data).forEach(key => {
if (key !== 'id') {
(self as any)[key] = data[key];
}
});
if (Array.isArray(data.children)) {
const arr = data.children;
const pool = arr.concat();
// 把多的删了先
if (self.children.length > arr.length) {
self.children.splice(arr.length, self.children.length - arr.length);
}
let index = 0;
const len = self.children.length;
while (pool.length) {
// 因为父级id未更新所以需要将子级的parentId正确指向父级id
const item = {
...pool.shift(),
parentId: self.id
}!;
if (index < len) {
self.children[index].replaceWith(item);
} else {
const row = Row.create(item);
self.children.push(row);
}
index++;
}
}
},
change(values: object, savePristine?: boolean) {
self.data = immutableExtends(self.data, values);
savePristine && (self.pristine = self.data);
}
}));
export type IRow = Instance<typeof Row>;
export type SRow = SnapshotIn<typeof Row>;
export const TableStoreV2 = iRendererStore
export const TableStoreV2 = ServiceStore
.named('TableStoreV2')
.props({
columns: types.array(Column),
rows: types.array(Row),
columnsToggable: types.optional(
selectedRowKeys: types.array(types.frozen()),
selectedRows: types.array(types.reference(Row)),
expandedRowKeys: types.array(types.frozen()),
columnsTogglable: types.optional(
types.union(types.boolean, types.literal('auto')),
'auto'
)
),
orderBy: '',
order: types.optional(
types.union(types.literal('asc'), types.literal('desc')),
'asc'
),
query: types.optional(types.frozen(), {}),
pageNo: 1,
pageSize: 10,
dragging: false
})
.views(self => {
function getToggable() {
if (self.columnsToggable === 'auto') {
if (self.columnsTogglable === 'auto') {
return self.columns.filter.length > 10;
}
return self.columnsToggable;
return self.columnsTogglable;
}
function hasColumnHidden() {
@ -80,8 +212,9 @@ export const TableStoreV2 = iRendererStore
return getToggableColumns().filter(item => item.toggled);
}
function getFilteredColumns() {
return self.columns.filter(
function getAllFilteredColumns(columns?: Array<SColumn>): Array<any> {
if (columns) {
return columns.filter(
item =>
item &&
isVisible(
@ -89,7 +222,33 @@ export const TableStoreV2 = iRendererStore
hasVisibleExpression(item.pristine) ? self.data : {}
) &&
(item.toggled || !item.toggable)
).map(item => ({...item.pristine, type: item.type}));
).map(item => ({
...item.pristine,
type: item.type,
children: item.children ? getAllFilteredColumns(item.children) : undefined
}));
}
return [];
}
function getFilteredColumns() {
return getAllFilteredColumns(self.columns);
}
function getUnSelectedRows() {
return flattenTree<IRow>(self.rows).filter((item: IRow) => !item.checked);
}
function getData(superData: any): any {
return createObject(superData, {
items: self.rows.map(item => item.data),
selectedItems: self.selectedRows.map(item => item.data),
unSelectedItems: getUnSelectedRows().map(item => item.data)
});
}
function isSelected(row: IRow): boolean {
return !!~self.selectedRows.indexOf(row);
}
return {
@ -113,32 +272,63 @@ export const TableStoreV2 = iRendererStore
return getActiveToggableColumns();
},
get dataSource() {
return self.rows.map(item => item.data);
},
get currentSelectedRowKeys() {
return self.selectedRowKeys.map(item => item);
},
get currentExpandedKeys() {
return self.expandedRowKeys.map(item => item);
},
// 是否隐藏了某列
hasColumnHidden() {
return hasColumnHidden();
}
},
getData,
isSelected
}
})
.actions(self => {
function update(config: Partial<STableStore>) {
config.columnsToggable !== void 0 &&
(self.columnsToggable = config.columnsToggable);
if (config.columns && Array.isArray(config.columns)) {
let columns: Array<SColumn> = config.columns
function updateColumns(columns: Array<SColumn>) {
if (columns && Array.isArray(columns)) {
let cols: Array<SColumn> = columns
.filter(column => column)
.concat();
columns = columns.map((item, index) => ({
cols = cols.map((item, index) => ({
...item,
index,
type: item.type || 'plain',
pristine: item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint
breakpoint: item.breakpoint,
children: item.children ? updateColumns(item.children) : []
}));
self.columns.replace(columns as any);
return cols;
}
return;
}
function update(config: Partial<STableStore>) {
config.columnsTogglable !== void 0 &&
(self.columnsTogglable = config.columnsTogglable);
if (typeof config.orderBy === 'string') {
setOrderByInfo(
config.orderBy,
config.order === 'desc' ? 'desc' : 'asc'
);
}
if (config.columns && Array.isArray(config.columns)) {
self.columns.replace(updateColumns(config.columns) as any);
}
}
@ -153,9 +343,275 @@ export const TableStoreV2 = iRendererStore
);
}
function setOrderByInfo(key: string, direction: 'asc' | 'desc') {
self.orderBy = key;
self.order = direction;
}
function updateQuery(
values: object,
updater?: Function,
pageNoField: string = 'pageNo',
pageSizeField: string = 'pageSize',
replace: boolean = false
) {
const originQuery = self.query;
self.query = replace
? {
...values
}
: {
...self.query,
...values
};
if (self.query[pageNoField || 'pageNo']) {
self.pageNo = parseInt(self.query[pageNoField || 'pageNo'], 10);
}
if (self.query[pageSizeField || 'pageSize']) {
self.pageSize = parseInt(self.query[pageSizeField || 'pageSize'], 10);
}
updater &&
isObjectShallowModified(originQuery, self.query, false) &&
setTimeout(updater.bind(null, `?${qsstringify(self.query)}`), 4);
}
function updateSelectedRows(rows: Array<any>, selectedKeys: Array<any>, keyField?: string) {
eachTree(rows, item => {
if (~selectedKeys.indexOf(item.pristine[keyField || 'key'])) {
self.selectedRows.push(item.id);
self.selectedRowKeys.push(item.pristine[keyField || 'key']);
} else if (
find(
selectedKeys,
a =>
a &&
a == item.pristine[keyField || 'key']
)
) {
self.selectedRows.push(item.id);
self.selectedRowKeys.push(item.pristine[keyField || 'key']);
} else if (item.children) {
updateSelectedRows(item.children, selectedKeys, keyField);
}
});
}
function updateSelected(selectedKeys: Array<any>, keyField?: string) {
self.selectedRows.clear();
self.selectedRowKeys.clear();
updateSelectedRows(self.rows, selectedKeys, keyField);
}
function updateExpanded(expandedRowKeys: Array<any>, keyField?: string) {
self.expandedRowKeys.clear();
eachTree(self.rows, item => {
if (~expandedRowKeys.indexOf(item.pristine[keyField || 'key'])) {
self.expandedRowKeys.push(item.pristine[keyField || 'key']);
} else if (
find(
expandedRowKeys,
a =>
a &&
a == item.pristine[keyField || 'key']
)
) {
self.expandedRowKeys.push(item.pristine[keyField || 'key']);
}
});
}
// 尽可能的复用 row
function replaceRow(arr: Array<SRow>, reUseRow?: boolean) {
if (reUseRow === false) {
self.rows.replace(arr.map(item => Row.create(item)));
return;
}
const pool = arr.concat();
// 把多的删了先
if (self.rows.length > arr.length) {
self.rows.splice(arr.length, self.rows.length - arr.length);
}
let index = 0;
const len = self.rows.length;
while (pool.length) {
const item = pool.shift()!;
if (index < len) {
self.rows[index].replaceWith(item);
} else {
const row = Row.create(item);
self.rows.push(row);
}
index++;
}
}
function initChildren(
children: Array<any>,
depth: number,
pindex: number,
parentId: string,
path: string = '',
keyField?: string
): any {
const key = keyField || 'children';
depth += 1;
return children.map((item, index) => {
item = isObject(item)
? item
: {
item
};
const id = guid();
return {
id: id,
parentId,
key: String(`${pindex}-${depth}-${index}`),
path: `${path}${index}`,
depth: depth,
index: index,
newIndex: index,
pristine: item,
data: item,
rowSpans: {},
children:
item && Array.isArray(item[key])
? initChildren(
item[key],
depth,
index,
id,
`${path}${index}.`,
)
: []
};
});
}
function initRows(
rows: Array<any>,
getEntryId?: (entry: any, index: number) => string,
reUseRow?: boolean,
keyField?: string
) {
self.selectedRows.clear();
const key = keyField || 'children';
let arr: Array<SRow> = rows.map((item, index) => {
let id = getEntryId ? getEntryId(item, index) : guid();
return {
id: id,
key: String(`${index}-1-${index}`),
index: index,
newIndex: index,
pristine: item,
path: `${index}`,
data: item,
depth: 1, // 最大父节点默认为第一层,逐层叠加
children:
item && Array.isArray(item[key])
? initChildren(item[key], 1, index, id, `${index}.`, key)
: []
};
});
replaceRow(arr, reUseRow);
}
const saveRemote: (
api: Api,
data?: object,
options?: fetchOptions
) => Promise<any> = flow(function* saveRemote(
api: Api,
data: object,
options: fetchOptions = {}
) {
try {
options = {
method: 'post', // 默认走 post
...options
};
self.markSaving(true);
const json: Payload = yield getEnv(self).fetcher(api, data, options);
self.markSaving(false);
if (!isEmpty(json.data) || json.ok) {
self.updateData(
normalizeApiResponseData(json.data),
{
__saved: Date.now()
},
!!api && (api as ApiObject).replaceData
);
self.updatedAt = Date.now();
}
if (!json.ok) {
self.updateMessage(
json.msg ?? options.errorMessage ?? self.__('saveFailed'),
true
);
getEnv(self).notify(
'error',
self.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
throw new ServerError(self.msg);
} else {
self.updateMessage(json.msg ?? options.successMessage);
self.msg &&
getEnv(self).notify(
'success',
self.msg,
json.msgTimeout !== undefined
? {
closeButton: true,
timeout: json.msgTimeout
}
: undefined
);
}
return json.data;
} catch (e) {
self.markSaving(false);
if (!isAlive(self) || self.disposed) {
return;
}
e.type !== 'ServerError' && getEnv(self).notify('error', e.message);
throw e;
}
});
return {
update,
persistSaveToggledColumns,
setOrderByInfo,
updateQuery,
initRows,
updateSelected,
updateExpanded,
// events
afterCreate() {
@ -177,6 +633,11 @@ export const TableStoreV2 = iRendererStore
);
}
}, 200);
},
saveRemote,
getRowByIndex(rowIndex: number) {
return self.rows[rowIndex];
}
};
});

View File

@ -639,8 +639,7 @@ export const TableStore = iRendererStore
},
get disabledHeadCheckbox() {
// 设置为multiple 默认没选择会报错
const selectedLength = self.data?.selectedItems?.length;
const selectedLength = self.data?.selectedItems.length;
const maxLength = self.maxKeepItemSelectionLength;
if (!self.data || !self.keepItemSelectionOnPageChange || !maxLength) {