forked from p96170835/amis
添加 ListCheckboxes 和 TableCheckboxes
This commit is contained in:
parent
b258bb4361
commit
9e89a14c46
|
@ -5,8 +5,10 @@
|
|||
pointer-events: none;
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&: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 {autobind} from '../utils/helper';
|
||||
|
||||
const preventEvent = (e: any) => e.stopPropagation();
|
||||
|
||||
interface CheckboxProps {
|
||||
type: 'checkbox' | 'radio';
|
||||
size?: 'sm' | 'lg' | 'small' | 'large';
|
||||
|
@ -85,6 +87,9 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
|
|||
: value == trueValue
|
||||
}
|
||||
onChange={this.handleCheck}
|
||||
onClick={
|
||||
preventEvent // 当点击 i 的时候,这个地方也会触发 click,很奇怪,干脆禁掉
|
||||
}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
name={name}
|
||||
|
|
|
@ -11,9 +11,10 @@ import chunk from 'lodash/chunk';
|
|||
import {ClassNamesFn, themeable, ThemeProps} from '../theme';
|
||||
import {Option, value2array, Options} from './Select';
|
||||
import find from 'lodash/find';
|
||||
import { autobind } from '../utils/helper';
|
||||
// import isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
interface CheckboxesProps extends ThemeProps {
|
||||
export interface CheckboxesProps extends ThemeProps {
|
||||
options: Options;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
|
@ -28,7 +29,7 @@ interface CheckboxesProps extends ThemeProps {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class Checkboxes extends React.Component<CheckboxesProps, any> {
|
||||
export class Checkboxes<T extends CheckboxesProps = CheckboxesProps> extends React.Component<T, any> {
|
||||
static defaultProps = {
|
||||
placeholder: '暂无选项',
|
||||
itemRender: (option: Option) => <span>{option.label}</span>
|
||||
|
@ -55,9 +56,14 @@ export class Checkboxes extends React.Component<CheckboxesProps, any> {
|
|||
.filter((item: any) => item);
|
||||
}
|
||||
|
||||
@autobind
|
||||
toggleOption(option: Option) {
|
||||
const {value, onChange, option2value, options} = this.props;
|
||||
|
||||
if (option.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let valueArray = Checkboxes.value2array(value, options, option2value);
|
||||
let idx = valueArray.indexOf(option);
|
||||
|
||||
|
@ -74,6 +80,22 @@ export class Checkboxes extends React.Component<CheckboxesProps, any> {
|
|||
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() {
|
||||
const {
|
||||
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]'
|
||||
) as HTMLElement;
|
||||
|
||||
if (!tr) {
|
||||
if (!tr || !this.props.itemActions) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,8 @@ export const FormStore = ServiceStore.named('FormStore')
|
|||
|
||||
// 失败也同样修改数据,如果有数据的话。
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
self.updatedAt = Date.now();
|
||||
|
||||
setValues(
|
||||
json.data,
|
||||
{
|
||||
|
@ -235,7 +237,6 @@ export const FormStore = ServiceStore.named('FormStore')
|
|||
},
|
||||
!!(api as ApiObject).replaceData
|
||||
);
|
||||
self.updatedAt = Date.now();
|
||||
}
|
||||
|
||||
if (!json.ok) {
|
||||
|
|
|
@ -104,13 +104,13 @@ export const ServiceStore = iRendererStore
|
|||
: undefined
|
||||
);
|
||||
} else {
|
||||
self.updatedAt = Date.now();
|
||||
let replace = !!(api as ApiObject).replaceData;
|
||||
let data = {
|
||||
...(replace ? {} : self.data),
|
||||
...json.data
|
||||
};
|
||||
reInitData(data, replace);
|
||||
self.updatedAt = Date.now();
|
||||
self.hasRemoteData = true;
|
||||
if (options && options.onSuccess) {
|
||||
const ret = options.onSuccess(json);
|
||||
|
@ -177,13 +177,15 @@ export const ServiceStore = iRendererStore
|
|||
fetchCancel = null;
|
||||
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
self.updatedAt = Date.now();
|
||||
|
||||
json.data &&
|
||||
self.updateData(
|
||||
json.data,
|
||||
undefined,
|
||||
!!(api as ApiObject).replaceData
|
||||
);
|
||||
self.updatedAt = Date.now();
|
||||
|
||||
self.hasRemoteData = true;
|
||||
}
|
||||
|
||||
|
@ -262,13 +264,14 @@ export const ServiceStore = iRendererStore
|
|||
);
|
||||
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
self.updatedAt = Date.now();
|
||||
|
||||
json.data &&
|
||||
self.updateData(
|
||||
json.data,
|
||||
undefined,
|
||||
!!(api as ApiObject).replaceData
|
||||
);
|
||||
self.updatedAt = Date.now();
|
||||
}
|
||||
|
||||
if (!json.ok) {
|
||||
|
|
Loading…
Reference in New Issue