forked from p96170835/amis
添加 ListCheckboxes 和 TableCheckboxes
This commit is contained in:
parent
b258bb4361
commit
9e89a14c46
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'
|
||||||
|
})
|
||||||
|
);
|
|
@ -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'
|
||||||
|
})
|
||||||
|
);
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue