添加 ListCheckboxes 和 TableCheckboxes

This commit is contained in:
2betop 2020-05-12 19:23:16 +08:00
parent b258bb4361
commit 9e89a14c46
8 changed files with 331 additions and 8 deletions

View File

@ -5,8 +5,10 @@
pointer-events: none; pointer-events: none;
input { input {
opacity: 0;
position: absolute; position: absolute;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
pointer-events: none;
} }
&:hover input:not(:disabled) + i { &:hover input:not(:disabled) + i {
@ -309,3 +311,48 @@
} }
} }
} }
.#{$ns}ListCheckboxes {
&-item {
display: flex;
height: $Form-input-height;
line-height: $Form-input-lineHeight;
font-size: $Form-input-fontSize;
padding: (
$Form-input-height - $Form-input-lineHeight * $Form-input-fontSize
)/2 $gap-sm;
flex-direction: row;
> .#{$ns}Checkbox {
margin-right: 0;
}
cursor: pointer;
user-select: none;
&.is-disabled {
pointer-events: none;
color: $text--muted-color;
}
}
&-itemLabel {
flex-grow: 1;
}
}
.#{$ns}TableCheckboxes {
.#{$ns}Table-table > thead > tr > th:first-child,
.#{$ns}Table-table > tbody > tr > td:first-child {
padding-left: px2rem(10px);
padding-right: 0;
}
.#{$ns}Table-table > thead > tr > th:last-child,
.#{$ns}Table-table > tbody > tr > td:last-child {
padding-right: px2rem(15px);
}
.#{$ns}Table-table > tbody > tr {
cursor: pointer;
}
}

View File

@ -7,6 +7,8 @@ import React from 'react';
import {ClassNamesFn, themeable} from '../theme'; import {ClassNamesFn, themeable} from '../theme';
import {autobind} from '../utils/helper'; import {autobind} from '../utils/helper';
const preventEvent = (e: any) => e.stopPropagation();
interface CheckboxProps { interface CheckboxProps {
type: 'checkbox' | 'radio'; type: 'checkbox' | 'radio';
size?: 'sm' | 'lg' | 'small' | 'large'; size?: 'sm' | 'lg' | 'small' | 'large';
@ -85,6 +87,9 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
: value == trueValue : value == trueValue
} }
onChange={this.handleCheck} onChange={this.handleCheck}
onClick={
preventEvent // 当点击 i 的时候,这个地方也会触发 click很奇怪干脆禁掉
}
disabled={disabled} disabled={disabled}
readOnly={readOnly} readOnly={readOnly}
name={name} name={name}

View File

@ -11,9 +11,10 @@ import chunk from 'lodash/chunk';
import {ClassNamesFn, themeable, ThemeProps} from '../theme'; import {ClassNamesFn, themeable, ThemeProps} from '../theme';
import {Option, value2array, Options} from './Select'; import {Option, value2array, Options} from './Select';
import find from 'lodash/find'; import find from 'lodash/find';
import { autobind } from '../utils/helper';
// import isPlainObject from 'lodash/isPlainObject'; // import isPlainObject from 'lodash/isPlainObject';
interface CheckboxesProps extends ThemeProps { export interface CheckboxesProps extends ThemeProps {
options: Options; options: Options;
className?: string; className?: string;
placeholder?: string; placeholder?: string;
@ -28,7 +29,7 @@ interface CheckboxesProps extends ThemeProps {
disabled?: boolean; disabled?: boolean;
} }
export class Checkboxes extends React.Component<CheckboxesProps, any> { export class Checkboxes<T extends CheckboxesProps = CheckboxesProps> extends React.Component<T, any> {
static defaultProps = { static defaultProps = {
placeholder: '暂无选项', placeholder: '暂无选项',
itemRender: (option: Option) => <span>{option.label}</span> itemRender: (option: Option) => <span>{option.label}</span>
@ -55,9 +56,14 @@ export class Checkboxes extends React.Component<CheckboxesProps, any> {
.filter((item: any) => item); .filter((item: any) => item);
} }
@autobind
toggleOption(option: Option) { toggleOption(option: Option) {
const {value, onChange, option2value, options} = this.props; const {value, onChange, option2value, options} = this.props;
if (option.disabled) {
return;
}
let valueArray = Checkboxes.value2array(value, options, option2value); let valueArray = Checkboxes.value2array(value, options, option2value);
let idx = valueArray.indexOf(option); let idx = valueArray.indexOf(option);
@ -74,6 +80,22 @@ export class Checkboxes extends React.Component<CheckboxesProps, any> {
onChange?.(newValue); onChange?.(newValue);
} }
@autobind
toggleAll() {
const {value, onChange, option2value, options} = this.props;
let valueArray:Array<Option> = [];
if (!Array.isArray(value) || !value.length) {
valueArray = options.filter(option => !option.disabled);
}
let newValue: string | Array<Option> = option2value
? valueArray.map(item => option2value(item))
: valueArray;
onChange?.(newValue);
}
render() { render() {
const { const {
value, value,

View File

@ -0,0 +1,63 @@
import {Checkboxes} from './Checkboxes';
import {themeable} from '../theme';
import React from 'react';
import uncontrollable from 'uncontrollable';
import Checkbox from './Checkbox';
export class ListCheckboxes extends Checkboxes {
render() {
const {
value,
options,
className,
placeholder,
labelClassName,
disabled,
classnames: cx,
option2value,
itemClassName,
itemRender
} = this.props;
let valueArray = Checkboxes.value2array(value, options, option2value);
let body: Array<React.ReactNode> = [];
if (Array.isArray(options) && options.length) {
body = options.map((option, key) => (
<div
key={key}
className={cx(
'ListCheckboxes-item',
itemClassName,
option.className,
disabled || option.disabled ? 'is-disabled' : ''
)}
onClick={() => this.toggleOption(option)}
>
<div className={cx('ListCheckboxes-itemLabel')}>
{itemRender(option)}
</div>
<Checkbox
checked={!!~valueArray.indexOf(option)}
disabled={disabled || option.disabled}
labelClassName={labelClassName}
description={option.description}
/>
</div>
));
}
return (
<div className={cx('ListCheckboxes', className)}>
{body && body.length ? body : <div>{placeholder}</div>}
</div>
);
}
}
export default themeable(
uncontrollable(ListCheckboxes, {
value: 'onChange'
})
);

View File

@ -0,0 +1,182 @@
import {Checkboxes, CheckboxesProps} from './Checkboxes';
import {themeable} from '../theme';
import React from 'react';
import uncontrollable from 'uncontrollable';
import Checkbox from './Checkbox';
import {Option} from './Select';
import {resolveVariable} from '../utils/tpl-builtin';
export interface TableCheckboxesProps extends CheckboxesProps {
columns: Array<{
name: string;
label: string;
[propName: string]: any;
}>;
cellRender: (
column: {
name: string;
label: string;
[propName: string]: any;
},
option: Option
) => JSX.Element;
}
export class TableCheckboxes extends Checkboxes<TableCheckboxesProps> {
static defaultProps = {
...Checkboxes.defaultProps,
cellRender: (
column: {
name: string;
label: string;
[propName: string]: any;
},
option: Option
) => <span>{resolveVariable(column.name, option)}</span>
};
getColumns() {
let columns = this.props.columns;
if (!Array.isArray(columns) || !columns.length) {
columns = [{label: 'Label', name: 'label'}];
}
return columns;
}
renderTHead() {
const {options, classnames: cx, value, option2value} = this.props;
let columns = this.getColumns();
let valueArray = Checkboxes.value2array(value, options, option2value);
const availableOptions = options.filter(option => !option.disabled);
let partialChecked = false;
let allChecked = !!availableOptions.length;
availableOptions.forEach(option => {
const isIn = !!~valueArray.indexOf(option);
if (isIn && !partialChecked) {
partialChecked = true;
} else if (!isIn && allChecked) {
allChecked = false;
}
});
return (
<thead>
<tr>
{Array.isArray(options) && options.length ? (
<th className={cx('Table-checkCell')}>
<Checkbox
onChange={this.toggleAll}
checked={partialChecked}
partial={partialChecked && !allChecked}
/>
</th>
) : null}
{columns.map((column, index) => (
<th key={index}>{column.label}</th>
))}
</tr>
</thead>
);
}
renderTBody() {
const {
options,
placeholder,
classnames: cx,
cellRender,
value,
option2value
} = this.props;
const columns = this.getColumns();
let valueArray = Checkboxes.value2array(value, options, option2value);
return (
<tbody>
{Array.isArray(options) && options.length ? (
options.map((option, rowIndex) => {
const checked = valueArray.indexOf(option) !== -1;
return (
<tr key={rowIndex} onClick={() => this.toggleOption(option)}>
<td className={cx('Table-checkCell')}>
<Checkbox checked={checked} />
</td>
{columns.map((column, colIndex) => (
<td key={colIndex}>{cellRender(column, option)}</td>
))}
</tr>
);
})
) : (
<tr>
<td colSpan={columns.length}>{placeholder}</td>
</tr>
)}
</tbody>
);
}
render() {
const {
value,
options,
className,
labelClassName,
disabled,
classnames: cx,
option2value,
itemClassName,
itemRender
} = this.props;
let valueArray = Checkboxes.value2array(value, options, option2value);
let body: Array<React.ReactNode> = [];
if (Array.isArray(options) && options.length) {
body = options.map((option, key) => (
<div
key={key}
className={cx(
'TableCheckboxes-item',
itemClassName,
option.className,
disabled || option.disabled ? 'is-disabled' : ''
)}
onClick={() => this.toggleOption(option)}
>
<div className={cx('TableCheckboxes-itemLabel')}>
{itemRender(option)}
</div>
<Checkbox
checked={!!~valueArray.indexOf(option)}
disabled={disabled || option.disabled}
labelClassName={labelClassName}
description={option.description}
/>
</div>
));
}
return (
<div className={cx('TableCheckboxes', className)}>
<div className={cx('Table-content')}>
<table className={cx('Table-table')}>
{this.renderTHead()}
{this.renderTBody()}
</table>
</div>
</div>
);
}
}
export default themeable(
uncontrollable(TableCheckboxes, {
value: 'onChange'
})
);

View File

@ -761,7 +761,7 @@ export default class Table extends React.Component<TableProps, object> {
'tr[data-index]' 'tr[data-index]'
) as HTMLElement; ) as HTMLElement;
if (!tr) { if (!tr || !this.props.itemActions) {
return; return;
} }

View File

@ -228,6 +228,8 @@ export const FormStore = ServiceStore.named('FormStore')
// 失败也同样修改数据,如果有数据的话。 // 失败也同样修改数据,如果有数据的话。
if (!isEmpty(json.data) || json.ok) { if (!isEmpty(json.data) || json.ok) {
self.updatedAt = Date.now();
setValues( setValues(
json.data, json.data,
{ {
@ -235,7 +237,6 @@ export const FormStore = ServiceStore.named('FormStore')
}, },
!!(api as ApiObject).replaceData !!(api as ApiObject).replaceData
); );
self.updatedAt = Date.now();
} }
if (!json.ok) { if (!json.ok) {

View File

@ -104,13 +104,13 @@ export const ServiceStore = iRendererStore
: undefined : undefined
); );
} else { } else {
self.updatedAt = Date.now();
let replace = !!(api as ApiObject).replaceData; let replace = !!(api as ApiObject).replaceData;
let data = { let data = {
...(replace ? {} : self.data), ...(replace ? {} : self.data),
...json.data ...json.data
}; };
reInitData(data, replace); reInitData(data, replace);
self.updatedAt = Date.now();
self.hasRemoteData = true; self.hasRemoteData = true;
if (options && options.onSuccess) { if (options && options.onSuccess) {
const ret = options.onSuccess(json); const ret = options.onSuccess(json);
@ -177,13 +177,15 @@ export const ServiceStore = iRendererStore
fetchCancel = null; fetchCancel = null;
if (!isEmpty(json.data) || json.ok) { if (!isEmpty(json.data) || json.ok) {
self.updatedAt = Date.now();
json.data && json.data &&
self.updateData( self.updateData(
json.data, json.data,
undefined, undefined,
!!(api as ApiObject).replaceData !!(api as ApiObject).replaceData
); );
self.updatedAt = Date.now();
self.hasRemoteData = true; self.hasRemoteData = true;
} }
@ -262,13 +264,14 @@ export const ServiceStore = iRendererStore
); );
if (!isEmpty(json.data) || json.ok) { if (!isEmpty(json.data) || json.ok) {
self.updatedAt = Date.now();
json.data && json.data &&
self.updateData( self.updateData(
json.data, json.data,
undefined, undefined,
!!(api as ApiObject).replaceData !!(api as ApiObject).replaceData
); );
self.updatedAt = Date.now();
} }
if (!json.ok) { if (!json.ok) {