mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
chore: 优化 Table 渲染性能
This commit is contained in:
parent
df59d92a4d
commit
fd93dedf2f
@ -1845,6 +1845,7 @@ popOver 的其它配置请参考 [popover](./popover)
|
|||||||
| resizable | `boolean` | `true` | 列宽度是否支持调整 | |
|
| resizable | `boolean` | `true` | 列宽度是否支持调整 | |
|
||||||
| selectable | `boolean` | `false` | 支持勾选 | |
|
| selectable | `boolean` | `false` | 支持勾选 | |
|
||||||
| multiple | `boolean` | `false` | 勾选 icon 是否为多选样式`checkbox`, 默认为`radio` | |
|
| multiple | `boolean` | `false` | 勾选 icon 是否为多选样式`checkbox`, 默认为`radio` | |
|
||||||
|
| lazyRenderAfter | `number` | `100` | 用来控制从第几行开始懒渲染行,用来渲染大表格时有用 | |
|
||||||
|
|
||||||
### 列配置属性表
|
### 列配置属性表
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = [
|
const list = [
|
||||||
{
|
{
|
||||||
engine: 'Trident',
|
engine: 'Trident',
|
||||||
browser: 'Internet Explorer 4.0',
|
browser: 'Internet Explorer 4.0',
|
||||||
@ -1202,8 +1202,21 @@ module.exports = [
|
|||||||
version: '-',
|
version: '-',
|
||||||
grade: 'U'
|
grade: 'U'
|
||||||
}
|
}
|
||||||
].map(function (item, index) {
|
];
|
||||||
return Object.assign({}, item, {
|
|
||||||
id: index + 1
|
// 多来点测试数据
|
||||||
|
module.exports = list
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.concat(list)
|
||||||
|
.map(function (item, index) {
|
||||||
|
return Object.assign({}, item, {
|
||||||
|
id: index + 1
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
@ -57,3 +57,22 @@ export const createMockMediaMatcher =
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mock IntersectionObserver
|
||||||
|
class IntersectionObserver {
|
||||||
|
observe = jest.fn();
|
||||||
|
disconnect = jest.fn();
|
||||||
|
unobserve = jest.fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'IntersectionObserver', {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: IntersectionObserver
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(global, 'IntersectionObserver', {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: IntersectionObserver
|
||||||
|
});
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
"esm"
|
"esm"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"amis-formula": "^3.4.0",
|
"amis-formula": "*",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
@ -58,8 +58,8 @@
|
|||||||
"moment": "^2.19.4",
|
"moment": "^2.19.4",
|
||||||
"papaparse": "^5.3.0",
|
"papaparse": "^5.3.0",
|
||||||
"qs": "6.9.7",
|
"qs": "6.9.7",
|
||||||
|
"react-intersection-observer": "9.5.2",
|
||||||
"react-json-view": "1.21.3",
|
"react-json-view": "1.21.3",
|
||||||
"react-visibility-sensor": "5.1.1",
|
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"uncontrollable": "7.2.1"
|
"uncontrollable": "7.2.1"
|
||||||
},
|
},
|
||||||
|
@ -440,14 +440,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
exprProps = {};
|
exprProps = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// style 支持公式
|
|
||||||
if (schema.style) {
|
|
||||||
// schema.style是readonly属性
|
|
||||||
schema = {...schema, style: buildStyle(schema.style, detectData)};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isClassComponent = Component.prototype?.isReactComponent;
|
const isClassComponent = Component.prototype?.isReactComponent;
|
||||||
const $schema = {...schema, ...exprProps};
|
|
||||||
let props = {
|
let props = {
|
||||||
...theme.getRendererConfig(renderer.name),
|
...theme.getRendererConfig(renderer.name),
|
||||||
...restSchema,
|
...restSchema,
|
||||||
@ -459,7 +452,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
defaultActiveKey: defaultActiveKey,
|
defaultActiveKey: defaultActiveKey,
|
||||||
propKey: propKey,
|
propKey: propKey,
|
||||||
$path: $path,
|
$path: $path,
|
||||||
$schema: $schema,
|
$schema: schema,
|
||||||
ref: this.refFn,
|
ref: this.refFn,
|
||||||
render: this.renderChild,
|
render: this.renderChild,
|
||||||
rootStore,
|
rootStore,
|
||||||
@ -468,6 +461,11 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
mobileUI: schema.useMobileUI === false ? false : rest.mobileUI
|
mobileUI: schema.useMobileUI === false ? false : rest.mobileUI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// style 支持公式
|
||||||
|
if (schema.style) {
|
||||||
|
(props as any).style = buildStyle(schema.style, detectData);
|
||||||
|
}
|
||||||
|
|
||||||
if (disable !== undefined) {
|
if (disable !== undefined) {
|
||||||
(props as any).disabled = disable;
|
(props as any).disabled = disable;
|
||||||
}
|
}
|
||||||
@ -478,7 +476,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
|
|
||||||
// 自动解析变量模式,主要是方便直接引入第三方组件库,无需为了支持变量封装一层
|
// 自动解析变量模式,主要是方便直接引入第三方组件库,无需为了支持变量封装一层
|
||||||
if (renderer.autoVar) {
|
if (renderer.autoVar) {
|
||||||
for (const key of Object.keys($schema)) {
|
for (const key of Object.keys(schema)) {
|
||||||
if (typeof props[key] === 'string') {
|
if (typeof props[key] === 'string') {
|
||||||
props[key] = resolveVariableAndFilter(
|
props[key] = resolveVariableAndFilter(
|
||||||
props[key],
|
props[key],
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
import {InView} from 'react-intersection-observer';
|
||||||
|
|
||||||
export interface LazyComponentProps {
|
export interface LazyComponentProps {
|
||||||
component?: React.ElementType;
|
component?: React.ElementType;
|
||||||
@ -13,7 +13,6 @@ export interface LazyComponentProps {
|
|||||||
placeholder?: React.ReactNode;
|
placeholder?: React.ReactNode;
|
||||||
unMountOnHidden?: boolean;
|
unMountOnHidden?: boolean;
|
||||||
childProps?: object;
|
childProps?: object;
|
||||||
visiblilityProps?: object;
|
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +55,7 @@ export default class LazyComponent extends React.Component<
|
|||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleVisibleChange(visible: boolean) {
|
handleVisibleChange(visible: boolean, entry?: any) {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: visible
|
visible: visible
|
||||||
});
|
});
|
||||||
@ -91,7 +90,6 @@ export default class LazyComponent extends React.Component<
|
|||||||
placeholder,
|
placeholder,
|
||||||
unMountOnHidden,
|
unMountOnHidden,
|
||||||
childProps,
|
childProps,
|
||||||
visiblilityProps,
|
|
||||||
partialVisibility,
|
partialVisibility,
|
||||||
children,
|
children,
|
||||||
...rest
|
...rest
|
||||||
@ -102,33 +100,42 @@ export default class LazyComponent extends React.Component<
|
|||||||
// 需要监听从可见到不可见。
|
// 需要监听从可见到不可见。
|
||||||
if (unMountOnHidden) {
|
if (unMountOnHidden) {
|
||||||
return (
|
return (
|
||||||
<VisibilitySensor
|
<InView
|
||||||
{...visiblilityProps}
|
|
||||||
partialVisibility={partialVisibility}
|
|
||||||
onChange={this.handleVisibleChange}
|
onChange={this.handleVisibleChange}
|
||||||
|
threshold={partialVisibility ? 0 : 1}
|
||||||
>
|
>
|
||||||
<div className="visibility-sensor">
|
{({ref}) => {
|
||||||
{Component && visible ? (
|
return (
|
||||||
<Component {...rest} {...childProps} />
|
<div
|
||||||
) : children && visible ? (
|
ref={ref}
|
||||||
children
|
className={`visibility-sensor ${visible ? 'in' : ''}`}
|
||||||
) : (
|
>
|
||||||
placeholder
|
{Component && visible ? (
|
||||||
)}
|
<Component {...rest} {...childProps} />
|
||||||
</div>
|
) : children && visible ? (
|
||||||
</VisibilitySensor>
|
children
|
||||||
|
) : (
|
||||||
|
placeholder
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</InView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return (
|
return (
|
||||||
<VisibilitySensor
|
<InView
|
||||||
{...visiblilityProps}
|
|
||||||
partialVisibility={partialVisibility}
|
|
||||||
onChange={this.handleVisibleChange}
|
onChange={this.handleVisibleChange}
|
||||||
|
threshold={partialVisibility ? 0 : 1}
|
||||||
>
|
>
|
||||||
<div className="visibility-sensor">{placeholder}</div>
|
{({ref}) => (
|
||||||
</VisibilitySensor>
|
<div ref={ref} className="visibility-sensor">
|
||||||
|
{placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</InView>
|
||||||
);
|
);
|
||||||
} else if (Component) {
|
} else if (Component) {
|
||||||
// 只监听不可见到可见,一旦可见了,就销毁检查。
|
// 只监听不可见到可见,一旦可见了,就销毁检查。
|
||||||
|
@ -3,7 +3,7 @@ import {IFormStore, IFormItemStore} from '../store/form';
|
|||||||
import debouce from 'lodash/debounce';
|
import debouce from 'lodash/debounce';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
import {RendererProps, Renderer} from '../factory';
|
import {RendererProps, Renderer, getRendererByName} from '../factory';
|
||||||
import {ComboStore, IComboStore, IUniqueGroup} from '../store/combo';
|
import {ComboStore, IComboStore, IUniqueGroup} from '../store/combo';
|
||||||
import {
|
import {
|
||||||
anyChanged,
|
anyChanged,
|
||||||
@ -32,7 +32,7 @@ import {FormBaseControl, FormItemWrap} from './Item';
|
|||||||
import {Api} from '../types';
|
import {Api} from '../types';
|
||||||
import {TableStore} from '../store/table';
|
import {TableStore} from '../store/table';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import {callStrFunction} from '../utils';
|
import {callStrFunction, changedEffect} from '../utils';
|
||||||
|
|
||||||
export interface ControlOutterProps extends RendererProps {
|
export interface ControlOutterProps extends RendererProps {
|
||||||
formStore?: IFormStore;
|
formStore?: IFormStore;
|
||||||
@ -121,6 +121,8 @@ export function wrapControl<
|
|||||||
onChange,
|
onChange,
|
||||||
data,
|
data,
|
||||||
inputGroupControl,
|
inputGroupControl,
|
||||||
|
colIndex,
|
||||||
|
rowIndex,
|
||||||
$schema: {
|
$schema: {
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
@ -154,17 +156,14 @@ export function wrapControl<
|
|||||||
this.setPrinstineValue = this.setPrinstineValue.bind(this);
|
this.setPrinstineValue = this.setPrinstineValue.bind(this);
|
||||||
this.controlRef = this.controlRef.bind(this);
|
this.controlRef = this.controlRef.bind(this);
|
||||||
this.handleBlur = this.handleBlur.bind(this);
|
this.handleBlur = this.handleBlur.bind(this);
|
||||||
|
this.validate = this.validate.bind(this);
|
||||||
|
this.flushChange = this.flushChange.bind(this);
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
// 一般情况下这些表单项都是需要 name 的,提示一下
|
// 一般情况下这些表单项都是需要 name 的,提示一下
|
||||||
if (
|
if (
|
||||||
typeof type === 'string' &&
|
typeof type === 'string' &&
|
||||||
(type.startsWith('input-') ||
|
getRendererByName(type)?.isFormItem
|
||||||
type.endsWith('select') ||
|
|
||||||
type === 'switch' ||
|
|
||||||
type === 'textarea' ||
|
|
||||||
type === 'radios') &&
|
|
||||||
type !== 'input-group'
|
|
||||||
) {
|
) {
|
||||||
console.warn('name is required', this.props.$schema);
|
console.warn('name is required', this.props.$schema);
|
||||||
}
|
}
|
||||||
@ -178,7 +177,9 @@ export function wrapControl<
|
|||||||
path: this.props.$path,
|
path: this.props.$path,
|
||||||
storeType: FormItemStore.name,
|
storeType: FormItemStore.name,
|
||||||
parentId: store?.id,
|
parentId: store?.id,
|
||||||
name
|
name,
|
||||||
|
colIndex: colIndex !== undefined ? colIndex : undefined,
|
||||||
|
rowIndex: rowIndex !== undefined ? rowIndex : undefined
|
||||||
}) as IFormItemStore;
|
}) as IFormItemStore;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
// @issue 打算干掉这个
|
// @issue 打算干掉这个
|
||||||
@ -226,6 +227,7 @@ export function wrapControl<
|
|||||||
if (propValue !== undefined && propValue !== null) {
|
if (propValue !== undefined && propValue !== null) {
|
||||||
// 同步 value: 优先使用 props 中的 value
|
// 同步 value: 优先使用 props 中的 value
|
||||||
model.changeTmpValue(propValue, 'controlled');
|
model.changeTmpValue(propValue, 'controlled');
|
||||||
|
model.setIsControlled(true);
|
||||||
} else {
|
} else {
|
||||||
const isExp = isExpression(value);
|
const isExp = isExpression(value);
|
||||||
|
|
||||||
@ -332,12 +334,10 @@ export function wrapControl<
|
|||||||
|
|
||||||
componentDidUpdate(prevProps: OuterProps) {
|
componentDidUpdate(prevProps: OuterProps) {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
const form = props.formStore;
|
|
||||||
const model = this.model;
|
const model = this.model;
|
||||||
|
|
||||||
if (
|
model &&
|
||||||
model &&
|
changedEffect(
|
||||||
anyChanged(
|
|
||||||
[
|
[
|
||||||
'id',
|
'id',
|
||||||
'validations',
|
'validations',
|
||||||
@ -362,34 +362,17 @@ export function wrapControl<
|
|||||||
'extraName'
|
'extraName'
|
||||||
],
|
],
|
||||||
prevProps.$schema,
|
prevProps.$schema,
|
||||||
props.$schema
|
props.$schema,
|
||||||
)
|
changes => {
|
||||||
) {
|
model.config({
|
||||||
model.config({
|
...changes,
|
||||||
required: props.$schema.required,
|
|
||||||
id: props.$schema.id,
|
// todo 优化后面两个
|
||||||
unique: props.$schema.unique,
|
isValueSchemaExp: isExpression(props.$schema.value),
|
||||||
value: props.$schema.value,
|
inputGroupControl: props?.inputGroupControl
|
||||||
isValueSchemaExp: isExpression(props.$schema.value),
|
} as any);
|
||||||
rules: props.$schema.validations,
|
}
|
||||||
multiple: props.$schema.multiple,
|
);
|
||||||
delimiter: props.$schema.delimiter,
|
|
||||||
valueField: props.$schema.valueField,
|
|
||||||
labelField: props.$schema.labelField,
|
|
||||||
joinValues: props.$schema.joinValues,
|
|
||||||
extractValue: props.$schema.extractValue,
|
|
||||||
messages: props.$schema.validationErrors,
|
|
||||||
selectFirst: props.$schema.selectFirst,
|
|
||||||
autoFill: props.$schema.autoFill,
|
|
||||||
clearValueOnHidden: props.$schema.clearValueOnHidden,
|
|
||||||
validateApi: props.$schema.validateApi,
|
|
||||||
minLength: props.$schema.minLength,
|
|
||||||
maxLength: props.$schema.maxLength,
|
|
||||||
label: props.$schema.label,
|
|
||||||
inputGroupControl: props?.inputGroupControl,
|
|
||||||
extraName: props.$schema.extraName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 此处需要同时考虑 defaultValue 和 value
|
// 此处需要同时考虑 defaultValue 和 value
|
||||||
if (model && typeof props.value !== 'undefined') {
|
if (model && typeof props.value !== 'undefined') {
|
||||||
@ -605,13 +588,16 @@ export function wrapControl<
|
|||||||
result = [await this.model.validate(data)];
|
result = [await this.model.validate(data)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result && result.length) {
|
const valid = !result.some(item => item === false);
|
||||||
if (result.indexOf(false) > -1) {
|
formItemDispatchEvent?.(
|
||||||
formItemDispatchEvent('formItemValidateError', data);
|
valid ? 'formItemValidateSucc' : 'formItemValidateError',
|
||||||
} else {
|
data
|
||||||
formItemDispatchEvent('formItemValidateSucc', data);
|
);
|
||||||
}
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flushChange() {
|
||||||
|
this.lazyEmitChange.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(
|
handleChange(
|
||||||
@ -862,6 +848,8 @@ export function wrapControl<
|
|||||||
getValue: this.getValue,
|
getValue: this.getValue,
|
||||||
prinstine: model ? model.prinstine : undefined,
|
prinstine: model ? model.prinstine : undefined,
|
||||||
setPrinstineValue: this.setPrinstineValue,
|
setPrinstineValue: this.setPrinstineValue,
|
||||||
|
onValidate: this.validate,
|
||||||
|
onFlushChange: this.flushChange,
|
||||||
// !没了这个, tree 里的 options 渲染会出问题
|
// !没了这个, tree 里的 options 渲染会出问题
|
||||||
_filteredOptions: this.model?.filteredOptions
|
_filteredOptions: this.model?.filteredOptions
|
||||||
};
|
};
|
||||||
|
@ -117,26 +117,27 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
|||||||
replace: boolean = false
|
replace: boolean = false
|
||||||
) {
|
) {
|
||||||
const originQuery = self.query;
|
const originQuery = self.query;
|
||||||
self.query = replace
|
const query: any = replace
|
||||||
? {
|
? {
|
||||||
...values
|
...values
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...self.query,
|
...originQuery,
|
||||||
...values
|
...values
|
||||||
};
|
};
|
||||||
|
|
||||||
if (self.query[pageField || 'page']) {
|
if (isObjectShallowModified(originQuery, query, false)) {
|
||||||
self.page = parseInt(self.query[pageField || 'page'], 10);
|
if (query[pageField || 'page']) {
|
||||||
}
|
self.page = parseInt(query[pageField || 'page'], 10);
|
||||||
|
}
|
||||||
|
|
||||||
if (self.query[perPageField || 'perPage']) {
|
if (query[perPageField || 'perPage']) {
|
||||||
self.perPage = parseInt(self.query[perPageField || 'perPage'], 10);
|
self.perPage = parseInt(query[perPageField || 'perPage'], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
updater &&
|
self.query = query;
|
||||||
isObjectShallowModified(originQuery, self.query, false) &&
|
updater && setTimeout(updater.bind(null, `?${qsstringify(query)}`), 4);
|
||||||
setTimeout(updater.bind(null, `?${qsstringify(self.query)}`), 4);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchInitData: (
|
const fetchInitData: (
|
||||||
|
@ -49,7 +49,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
|||||||
while (pool.length) {
|
while (pool.length) {
|
||||||
const current = pool.shift()!;
|
const current = pool.shift()!;
|
||||||
|
|
||||||
if (current.storeType === 'FormItemStore') {
|
if (current.storeType === 'FormItemStore' && !current.isControlled) {
|
||||||
formItems.push(current);
|
formItems.push(current);
|
||||||
} else if (
|
} else if (
|
||||||
!['ComboStore', 'TableStore', 'FormStore'].includes(current.storeType)
|
!['ComboStore', 'TableStore', 'FormStore'].includes(current.storeType)
|
||||||
|
@ -67,6 +67,7 @@ const getSelectedOptionsCache: any = {
|
|||||||
export const FormItemStore = StoreNode.named('FormItemStore')
|
export const FormItemStore = StoreNode.named('FormItemStore')
|
||||||
.props({
|
.props({
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
|
isControlled: false, // 是否是受控表单项,通常是用在别的组件里面
|
||||||
type: '',
|
type: '',
|
||||||
label: '',
|
label: '',
|
||||||
unique: false,
|
unique: false,
|
||||||
@ -107,7 +108,9 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
|||||||
resetValue: types.optional(types.frozen(), ''),
|
resetValue: types.optional(types.frozen(), ''),
|
||||||
validateOnChange: false,
|
validateOnChange: false,
|
||||||
/** 当前表单项所属的InputGroup父元素, 用于收集InputGroup的子元素 */
|
/** 当前表单项所属的InputGroup父元素, 用于收集InputGroup的子元素 */
|
||||||
inputGroupControl: types.optional(types.frozen(), {})
|
inputGroupControl: types.optional(types.frozen(), {}),
|
||||||
|
colIndex: types.frozen(),
|
||||||
|
rowIndex: types.frozen()
|
||||||
})
|
})
|
||||||
.views(self => {
|
.views(self => {
|
||||||
function getForm(): any {
|
function getForm(): any {
|
||||||
@ -358,28 +361,30 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
|||||||
inputGroupControl?.name != null &&
|
inputGroupControl?.name != null &&
|
||||||
(self.inputGroupControl = inputGroupControl);
|
(self.inputGroupControl = inputGroupControl);
|
||||||
|
|
||||||
rules = {
|
if (typeof rules !== 'undefined' || self.required) {
|
||||||
...rules,
|
rules = {
|
||||||
isRequired: self.required || rules?.isRequired
|
...rules,
|
||||||
};
|
isRequired: self.required || rules?.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
// todo 这个弄个配置由渲染器自己来决定
|
// todo 这个弄个配置由渲染器自己来决定
|
||||||
// 暂时先这样
|
// 暂时先这样
|
||||||
if (~['input-text', 'textarea'].indexOf(self.type)) {
|
if (~['input-text', 'textarea'].indexOf(self.type)) {
|
||||||
if (typeof minLength === 'number') {
|
if (typeof minLength === 'number') {
|
||||||
rules.minLength = minLength;
|
rules.minLength = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof maxLength === 'number') {
|
||||||
|
rules.maxLength = maxLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof maxLength === 'number') {
|
if (isObjectShallowModified(rules, self.rules)) {
|
||||||
rules.maxLength = maxLength;
|
self.rules = rules;
|
||||||
|
clearError('builtin');
|
||||||
|
self.validated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isObjectShallowModified(rules, self.rules)) {
|
|
||||||
self.rules = rules;
|
|
||||||
clearError('builtin');
|
|
||||||
self.validated = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
@ -1334,6 +1339,10 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setIsControlled(value: any) {
|
||||||
|
self.isControlled = !!value;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
focus,
|
focus,
|
||||||
blur,
|
blur,
|
||||||
@ -1359,7 +1368,8 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
|||||||
changeEmitedValue,
|
changeEmitedValue,
|
||||||
addSubFormItem,
|
addSubFormItem,
|
||||||
removeSubFormItem,
|
removeSubFormItem,
|
||||||
loadAutoUpdateData
|
loadAutoUpdateData,
|
||||||
|
setIsControlled
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import {Instance, types} from 'mobx-state-tree';
|
import {Instance, types} from 'mobx-state-tree';
|
||||||
import {parseQuery} from '../utils/helper';
|
import {parseQuery} from '../utils/helper';
|
||||||
import {ServiceStore} from './service';
|
import {ServiceStore} from './service';
|
||||||
import {createObjectFromChain, extractObjectChain} from '../utils';
|
import {
|
||||||
|
createObjectFromChain,
|
||||||
|
extractObjectChain,
|
||||||
|
isObjectShallowModified
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
export const RootStore = ServiceStore.named('RootStore')
|
export const RootStore = ServiceStore.named('RootStore')
|
||||||
.props({
|
.props({
|
||||||
@ -42,7 +46,10 @@ export const RootStore = ServiceStore.named('RootStore')
|
|||||||
self.runtimeErrorStack = errorStack;
|
self.runtimeErrorStack = errorStack;
|
||||||
},
|
},
|
||||||
updateLocation(location?: any, parseFn?: Function) {
|
updateLocation(location?: any, parseFn?: Function) {
|
||||||
self.query = parseFn ? parseFn(location) : parseQuery(location);
|
const query = parseFn ? parseFn(location) : parseQuery(location);
|
||||||
|
if (isObjectShallowModified(query, self.query, false)) {
|
||||||
|
self.query = query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ export const Row = types
|
|||||||
rowSpans: types.frozen({} as any),
|
rowSpans: types.frozen({} as any),
|
||||||
index: types.number,
|
index: types.number,
|
||||||
newIndex: types.number,
|
newIndex: types.number,
|
||||||
|
nth: 0,
|
||||||
path: '', // 行数据的位置
|
path: '', // 行数据的位置
|
||||||
expandable: false,
|
expandable: false,
|
||||||
checkdisable: false,
|
checkdisable: false,
|
||||||
@ -119,7 +120,9 @@ export const Row = types
|
|||||||
types.array(types.late((): IAnyModelType => Row)),
|
types.array(types.late((): IAnyModelType => Row)),
|
||||||
[]
|
[]
|
||||||
),
|
),
|
||||||
depth: types.number // 当前children位于第几层,便于使用getParent获取最顶层TableStore
|
depth: types.number, // 当前children位于第几层,便于使用getParent获取最顶层TableStore
|
||||||
|
appeared: true,
|
||||||
|
lazyRender: false
|
||||||
})
|
})
|
||||||
.views(self => ({
|
.views(self => ({
|
||||||
get checked(): boolean {
|
get checked(): boolean {
|
||||||
@ -321,6 +324,10 @@ export const Row = types
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
markAppeared(value: any) {
|
||||||
|
value && (self.appeared = !!value);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -344,6 +351,8 @@ export const TableStore = iRendererStore
|
|||||||
),
|
),
|
||||||
'asc'
|
'asc'
|
||||||
),
|
),
|
||||||
|
loading: false,
|
||||||
|
canAccessSuperData: false,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
@ -365,7 +374,8 @@ export const TableStore = iRendererStore
|
|||||||
keepItemSelectionOnPageChange: false,
|
keepItemSelectionOnPageChange: false,
|
||||||
// 导出 Excel 按钮的 loading 状态
|
// 导出 Excel 按钮的 loading 状态
|
||||||
exportExcelLoading: false,
|
exportExcelLoading: false,
|
||||||
searchFormExpanded: false // 用来控制搜索框是否展开了,那个自动根据 searchable 生成的表单 autoGenerateFilter
|
searchFormExpanded: false, // 用来控制搜索框是否展开了,那个自动根据 searchable 生成的表单 autoGenerateFilter
|
||||||
|
lazyRenderAfter: 100
|
||||||
})
|
})
|
||||||
.views(self => {
|
.views(self => {
|
||||||
function getColumnsExceptBuiltinTypes() {
|
function getColumnsExceptBuiltinTypes() {
|
||||||
@ -688,10 +698,12 @@ export const TableStore = iRendererStore
|
|||||||
return getUnSelectedRows();
|
return getUnSelectedRows();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get falttenedRows() {
|
||||||
|
return flattenTree<IRow>(self.rows);
|
||||||
|
},
|
||||||
|
|
||||||
get checkableRows() {
|
get checkableRows() {
|
||||||
return flattenTree<IRow>(self.rows).filter(
|
return this.falttenedRows.filter((item: IRow) => item.checkable);
|
||||||
(item: IRow) => item.checkable
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get expandableRows() {
|
get expandableRows() {
|
||||||
@ -821,6 +833,10 @@ export const TableStore = iRendererStore
|
|||||||
style.right = right;
|
style.right = right;
|
||||||
}
|
}
|
||||||
return [style, stickyClassName];
|
return [style, stickyClassName];
|
||||||
|
},
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
return self.rows.concat();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -832,46 +848,60 @@ export const TableStore = iRendererStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
function update(config: Partial<STableStore>) {
|
function update(config: Partial<STableStore>) {
|
||||||
config.primaryField !== void 0 &&
|
config.primaryField !== undefined &&
|
||||||
(self.primaryField = config.primaryField);
|
(self.primaryField = config.primaryField);
|
||||||
config.selectable !== void 0 && (self.selectable = config.selectable);
|
config.selectable !== undefined && (self.selectable = config.selectable);
|
||||||
config.columnsTogglable !== void 0 &&
|
config.columnsTogglable !== undefined &&
|
||||||
(self.columnsTogglable = config.columnsTogglable);
|
(self.columnsTogglable = config.columnsTogglable);
|
||||||
config.draggable !== void 0 && (self.draggable = config.draggable);
|
config.draggable !== undefined && (self.draggable = config.draggable);
|
||||||
|
|
||||||
if (typeof config.orderBy === 'string') {
|
if (
|
||||||
|
typeof config.orderBy === 'string' ||
|
||||||
|
typeof config.orderDir === 'string'
|
||||||
|
) {
|
||||||
setOrderByInfo(
|
setOrderByInfo(
|
||||||
config.orderBy,
|
config.orderBy ?? self.orderBy,
|
||||||
config.orderDir === 'desc' ? 'desc' : 'asc'
|
config.orderDir !== undefined
|
||||||
|
? config.orderDir === 'desc'
|
||||||
|
? 'desc'
|
||||||
|
: 'asc'
|
||||||
|
: self.orderDir
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
config.multiple !== void 0 && (self.multiple = config.multiple);
|
config.multiple !== undefined && (self.multiple = config.multiple);
|
||||||
config.footable !== void 0 && (self.footable = config.footable);
|
config.footable !== undefined && (self.footable = config.footable);
|
||||||
config.expandConfig !== void 0 &&
|
config.expandConfig !== undefined &&
|
||||||
(self.expandConfig = config.expandConfig);
|
(self.expandConfig = config.expandConfig);
|
||||||
config.itemCheckableOn !== void 0 &&
|
config.itemCheckableOn !== undefined &&
|
||||||
(self.itemCheckableOn = config.itemCheckableOn);
|
(self.itemCheckableOn = config.itemCheckableOn);
|
||||||
config.itemDraggableOn !== void 0 &&
|
config.itemDraggableOn !== undefined &&
|
||||||
(self.itemDraggableOn = config.itemDraggableOn);
|
(self.itemDraggableOn = config.itemDraggableOn);
|
||||||
config.hideCheckToggler !== void 0 &&
|
config.hideCheckToggler !== undefined &&
|
||||||
(self.hideCheckToggler = !!config.hideCheckToggler);
|
(self.hideCheckToggler = !!config.hideCheckToggler);
|
||||||
|
|
||||||
config.combineNum !== void 0 &&
|
config.combineNum !== undefined &&
|
||||||
(self.combineNum = parseInt(config.combineNum as any, 10) || 0);
|
(self.combineNum = parseInt(config.combineNum as any, 10) || 0);
|
||||||
config.combineFromIndex !== void 0 &&
|
config.combineFromIndex !== undefined &&
|
||||||
(self.combineFromIndex =
|
(self.combineFromIndex =
|
||||||
parseInt(config.combineFromIndex as any, 10) || 0);
|
parseInt(config.combineFromIndex as any, 10) || 0);
|
||||||
|
|
||||||
config.maxKeepItemSelectionLength !== void 0 &&
|
config.maxKeepItemSelectionLength !== undefined &&
|
||||||
(self.maxKeepItemSelectionLength = config.maxKeepItemSelectionLength);
|
(self.maxKeepItemSelectionLength = config.maxKeepItemSelectionLength);
|
||||||
config.keepItemSelectionOnPageChange !== void 0 &&
|
config.keepItemSelectionOnPageChange !== undefined &&
|
||||||
(self.keepItemSelectionOnPageChange =
|
(self.keepItemSelectionOnPageChange =
|
||||||
config.keepItemSelectionOnPageChange);
|
config.keepItemSelectionOnPageChange);
|
||||||
|
|
||||||
config.exportExcelLoading !== undefined &&
|
config.exportExcelLoading !== undefined &&
|
||||||
(self.exportExcelLoading = config.exportExcelLoading);
|
(self.exportExcelLoading = config.exportExcelLoading);
|
||||||
|
|
||||||
|
config.loading !== undefined && (self.loading = config.loading);
|
||||||
|
config.canAccessSuperData !== undefined &&
|
||||||
|
(self.canAccessSuperData = !!config.canAccessSuperData);
|
||||||
|
|
||||||
|
typeof config.lazyRenderAfter === 'number' &&
|
||||||
|
self.lazyRenderAfter === config.lazyRenderAfter;
|
||||||
|
|
||||||
if (config.columns && Array.isArray(config.columns)) {
|
if (config.columns && Array.isArray(config.columns)) {
|
||||||
let columns: Array<SColumn> = config.columns
|
let columns: Array<SColumn> = config.columns
|
||||||
.filter(column => column)
|
.filter(column => column)
|
||||||
@ -1112,7 +1142,8 @@ export const TableStore = iRendererStore
|
|||||||
depth: number,
|
depth: number,
|
||||||
pindex: number,
|
pindex: number,
|
||||||
parentId: string,
|
parentId: string,
|
||||||
path: string = ''
|
path: string = '',
|
||||||
|
nThRef: {index: number}
|
||||||
): any {
|
): any {
|
||||||
depth += 1;
|
depth += 1;
|
||||||
return children.map((item, index) => {
|
return children.map((item, index) => {
|
||||||
@ -1131,6 +1162,7 @@ export const TableStore = iRendererStore
|
|||||||
path: `${path}${index}`,
|
path: `${path}${index}`,
|
||||||
depth: depth,
|
depth: depth,
|
||||||
index: index,
|
index: index,
|
||||||
|
nth: nThRef.index++,
|
||||||
newIndex: index,
|
newIndex: index,
|
||||||
pristine: item,
|
pristine: item,
|
||||||
data: item,
|
data: item,
|
||||||
@ -1142,7 +1174,8 @@ export const TableStore = iRendererStore
|
|||||||
depth,
|
depth,
|
||||||
index,
|
index,
|
||||||
id,
|
id,
|
||||||
`${path}${index}.`
|
`${path}${index}.`,
|
||||||
|
nThRef
|
||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
expandable: !!(
|
expandable: !!(
|
||||||
@ -1164,6 +1197,7 @@ export const TableStore = iRendererStore
|
|||||||
/* 避免输入内容为非数组挂掉 */
|
/* 避免输入内容为非数组挂掉 */
|
||||||
rows = !Array.isArray(rows) ? [] : rows;
|
rows = !Array.isArray(rows) ? [] : rows;
|
||||||
|
|
||||||
|
const nThRef = {index: 0};
|
||||||
let arr: Array<SRow> = rows.map((item, index) => {
|
let arr: Array<SRow> = rows.map((item, index) => {
|
||||||
if (!isObject(item)) {
|
if (!isObject(item)) {
|
||||||
item = {
|
item = {
|
||||||
@ -1180,6 +1214,7 @@ export const TableStore = iRendererStore
|
|||||||
key: String(`${index}-1-${index}`),
|
key: String(`${index}-1-${index}`),
|
||||||
depth: 1, // 最大父节点默认为第一层,逐层叠加
|
depth: 1, // 最大父节点默认为第一层,逐层叠加
|
||||||
index: index,
|
index: index,
|
||||||
|
nth: nThRef.index++,
|
||||||
newIndex: index,
|
newIndex: index,
|
||||||
pristine: item,
|
pristine: item,
|
||||||
path: `${index}`,
|
path: `${index}`,
|
||||||
@ -1187,7 +1222,7 @@ export const TableStore = iRendererStore
|
|||||||
rowSpans: {},
|
rowSpans: {},
|
||||||
children:
|
children:
|
||||||
item && Array.isArray(item.children)
|
item && Array.isArray(item.children)
|
||||||
? initChildren(item.children, 1, index, id, `${index}.`)
|
? initChildren(item.children, 1, index, id, `${index}.`, nThRef)
|
||||||
: [],
|
: [],
|
||||||
expandable: !!(
|
expandable: !!(
|
||||||
(item && Array.isArray(item.children) && item.children.length) ||
|
(item && Array.isArray(item.children) && item.children.length) ||
|
||||||
@ -1208,6 +1243,21 @@ export const TableStore = iRendererStore
|
|||||||
replaceRow(arr, reUseRow);
|
replaceRow(arr, reUseRow);
|
||||||
self.isNested = self.rows.some(item => item.children.length);
|
self.isNested = self.rows.some(item => item.children.length);
|
||||||
|
|
||||||
|
// 前 20 个直接渲染,后面的按需渲染
|
||||||
|
if (
|
||||||
|
self.lazyRenderAfter &&
|
||||||
|
self.falttenedRows.length > self.lazyRenderAfter
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
let i = self.lazyRenderAfter, len = self.falttenedRows.length;
|
||||||
|
i < len;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
self.falttenedRows[i].appeared = false;
|
||||||
|
self.falttenedRows[i].lazyRender = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const expand = self.footable && self.footable.expand;
|
const expand = self.footable && self.footable.expand;
|
||||||
if (
|
if (
|
||||||
expand === 'first' ||
|
expand === 'first' ||
|
||||||
|
@ -502,3 +502,30 @@ export function warning(cat: Category, msg: string, ext?: object) {
|
|||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
store.logs.push(log);
|
store.logs.push(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助定位是因为什么属性变化导致了组件更新
|
||||||
|
export function traceProps(props: any, prevProps: any, componentName: string) {
|
||||||
|
console.log(
|
||||||
|
componentName,
|
||||||
|
Object.keys(props)
|
||||||
|
.map(key => {
|
||||||
|
if (props[key] !== prevProps[key]) {
|
||||||
|
if (key === 'data') {
|
||||||
|
return `data[${Object.keys(props[key])
|
||||||
|
.map(item => {
|
||||||
|
if (props[key][item] !== prevProps[key][item]) {
|
||||||
|
return `data.${item}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.filter(item => item)
|
||||||
|
.join(', ')}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.filter(item => item)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {isObservable, isObservableArray} from 'mobx';
|
import {isObservable, isObservableArray} from 'mobx';
|
||||||
import uniq from 'lodash/uniq'
|
import uniq from 'lodash/uniq';
|
||||||
import last from 'lodash/last'
|
import last from 'lodash/last';
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge';
|
||||||
import isPlainObject from 'lodash/isPlainObject'
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual';
|
||||||
import isNaN from 'lodash/isNaN'
|
import isNaN from 'lodash/isNaN';
|
||||||
import isNumber from 'lodash/isNumber'
|
import isNumber from 'lodash/isNumber';
|
||||||
import isString from 'lodash/isString'
|
import isString from 'lodash/isString';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
|
|
||||||
import type {Schema, PlainObject, FunctionPropertyNames} from '../types';
|
import type {Schema, PlainObject, FunctionPropertyNames} from '../types';
|
||||||
@ -196,9 +196,37 @@ export function anyChanged(
|
|||||||
to: {[propName: string]: any},
|
to: {[propName: string]: any},
|
||||||
strictMode: boolean = true
|
strictMode: boolean = true
|
||||||
): boolean {
|
): boolean {
|
||||||
return (typeof attrs === 'string' ? attrs.split(/\s*,\s*/) : attrs).some(
|
return (
|
||||||
key => (strictMode ? from[key] !== to[key] : from[key] != to[key])
|
typeof attrs === 'string'
|
||||||
);
|
? attrs.split(',').map(item => item.trim())
|
||||||
|
: attrs
|
||||||
|
).some(key => (strictMode ? from[key] !== to[key] : from[key] != to[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutable<T> = {
|
||||||
|
-readonly [k in keyof T]: T[k];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function changedEffect<T extends Record<string, any>>(
|
||||||
|
attrs: string | Array<string>,
|
||||||
|
origin: T,
|
||||||
|
data: T,
|
||||||
|
effect: (changes: Partial<Mutable<T>>) => void,
|
||||||
|
strictMode: boolean = true
|
||||||
|
) {
|
||||||
|
const changes: Partial<T> = {};
|
||||||
|
const keys =
|
||||||
|
typeof attrs === 'string'
|
||||||
|
? attrs.split(',').map(item => item.trim())
|
||||||
|
: attrs;
|
||||||
|
|
||||||
|
keys.forEach(key => {
|
||||||
|
if (strictMode ? origin[key] !== data[key] : origin[key] != data[key]) {
|
||||||
|
(changes as any)[key] = data[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(changes).length && effect(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rmUndefined(obj: PlainObject) {
|
export function rmUndefined(obj: PlainObject) {
|
||||||
|
@ -15,4 +15,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .visibility-sensor {
|
||||||
|
height: 100%; // 修复图表高度为 0 时,visibility-sensor 无法触发的问题
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1027,6 +1027,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table 骨架样式
|
||||||
|
&-emptyBlock {
|
||||||
|
background-color: #eaebed;
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}InputTable {
|
.#{$ns}InputTable {
|
||||||
@ -1074,6 +1081,7 @@
|
|||||||
> .#{$ns}Button,
|
> .#{$ns}Button,
|
||||||
> .#{$ns}Button--disabled-wrap > .#{$ns}Button {
|
> .#{$ns}Button--disabled-wrap > .#{$ns}Button {
|
||||||
margin: px2rem(3px);
|
margin: px2rem(3px);
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .#{$ns}Button--disabled-wrap > .#{$ns}Button--link {
|
> .#{$ns}Button--disabled-wrap > .#{$ns}Button--link {
|
||||||
|
@ -440,7 +440,7 @@ exports[`Renderer:input table add 1`] = `
|
|||||||
/>
|
/>
|
||||||
<col
|
<col
|
||||||
data-index="5"
|
data-index="5"
|
||||||
style="width: 100px;"
|
style="width: 150px;"
|
||||||
/>
|
/>
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
@ -520,34 +520,24 @@ exports[`Renderer:input table add 1`] = `
|
|||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form cxd-Form--normal cxd-Form--quickEdit"
|
class="cxd-Form-item cxd-Form-item--normal"
|
||||||
novalidate=""
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
style="display: none;"
|
|
||||||
type="submit"
|
|
||||||
value="aa"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--normal"
|
class="cxd-Form-control cxd-TextControl"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-control cxd-TextControl"
|
class="cxd-TextControl-input"
|
||||||
>
|
>
|
||||||
<div
|
<input
|
||||||
class="cxd-TextControl-input"
|
autocomplete="off"
|
||||||
>
|
class=""
|
||||||
<input
|
name="a"
|
||||||
autocomplete="off"
|
placeholder=""
|
||||||
class=""
|
size="10"
|
||||||
name="a"
|
type="text"
|
||||||
placeholder=""
|
value="aa"
|
||||||
size="10"
|
/>
|
||||||
type="text"
|
|
||||||
value="bb"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -556,33 +546,24 @@ exports[`Renderer:input table add 1`] = `
|
|||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form cxd-Form--normal cxd-Form--quickEdit"
|
class="cxd-Form-item cxd-Form-item--normal"
|
||||||
novalidate=""
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
style="display: none;"
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--normal"
|
class="cxd-Form-control cxd-TextControl"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-control cxd-TextControl"
|
class="cxd-TextControl-input"
|
||||||
>
|
>
|
||||||
<div
|
<input
|
||||||
class="cxd-TextControl-input"
|
autocomplete="off"
|
||||||
>
|
class=""
|
||||||
<input
|
name="b"
|
||||||
autocomplete="off"
|
placeholder=""
|
||||||
class=""
|
size="10"
|
||||||
name="b"
|
type="text"
|
||||||
placeholder=""
|
value="bb"
|
||||||
size="10"
|
/>
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -867,74 +848,65 @@ exports[`Renderer:input-table cell selects delete 1`] = `
|
|||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form cxd-Form--normal cxd-Form--quickEdit"
|
class="cxd-Form-item cxd-Form-item--normal"
|
||||||
novalidate=""
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
style="display: none;"
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--normal"
|
class="cxd-SelectControl cxd-Form-control"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-SelectControl cxd-Form-control"
|
aria-expanded="false"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
class="cxd-Select cxd-Select--multi"
|
||||||
|
role="combobox"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-expanded="false"
|
class="cxd-Select-valueWrap"
|
||||||
aria-haspopup="listbox"
|
|
||||||
class="cxd-Select cxd-Select--multi"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Select-valueWrap"
|
class="cxd-Select-value"
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="cxd-Select-value"
|
class="cxd-Select-valueLabel"
|
||||||
>
|
>
|
||||||
<span
|
s2
|
||||||
class="cxd-Select-valueLabel"
|
</span>
|
||||||
>
|
<span
|
||||||
s2
|
class="cxd-Select-valueIcon"
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="cxd-Select-valueIcon"
|
|
||||||
>
|
|
||||||
<icon-mock
|
|
||||||
classname="icon icon-close"
|
|
||||||
icon="close"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="cxd-Select-value"
|
|
||||||
>
|
>
|
||||||
<span
|
<icon-mock
|
||||||
class="cxd-Select-valueLabel"
|
classname="icon icon-close"
|
||||||
>
|
icon="close"
|
||||||
s3
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
class="cxd-Select-valueIcon"
|
|
||||||
>
|
|
||||||
<icon-mock
|
|
||||||
classname="icon icon-close"
|
|
||||||
icon="close"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<span
|
<div
|
||||||
class="cxd-Select-arrow"
|
class="cxd-Select-value"
|
||||||
>
|
>
|
||||||
<icon-mock
|
<span
|
||||||
classname="icon icon-right-arrow-bold"
|
class="cxd-Select-valueLabel"
|
||||||
icon="right-arrow-bold"
|
>
|
||||||
/>
|
s3
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
class="cxd-Select-valueIcon"
|
||||||
|
>
|
||||||
|
<icon-mock
|
||||||
|
classname="icon icon-close"
|
||||||
|
icon="close"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
class="cxd-Select-arrow"
|
||||||
|
>
|
||||||
|
<icon-mock
|
||||||
|
classname="icon icon-right-arrow-bold"
|
||||||
|
icon="right-arrow-bold"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1265,128 +1237,119 @@ exports[`Renderer:input-table with combo column 1`] = `
|
|||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form cxd-Form--normal cxd-Form--quickEdit"
|
class="cxd-Form-item cxd-Form-item--normal"
|
||||||
novalidate=""
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
style="display: none;"
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--normal"
|
class="cxd-ComboControl cxd-Form-control"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-ComboControl cxd-Form-control"
|
class="cxd-Combo cxd-Combo--single cxd-Combo--hor"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Combo cxd-Combo--single cxd-Combo--hor"
|
class="cxd-Combo-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Combo-item"
|
class="cxd-Combo-itemInner"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Combo-itemInner"
|
class="cxd-Form cxd-Form--row cxd-Combo-form"
|
||||||
|
novalidate=""
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
style="display: none;"
|
||||||
|
type="submit"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form cxd-Form--row cxd-Combo-form"
|
class="cxd-Form-row"
|
||||||
novalidate=""
|
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
style="display: none;"
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-row"
|
class="cxd-Form-col"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-col"
|
class="cxd-Form-item cxd-Form-item--row"
|
||||||
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--row"
|
class="cxd-Form-rowInner"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-rowInner"
|
class="cxd-NumberControl cxd-Form-control"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-NumberControl cxd-Form-control"
|
class="cxd-Number cxd-Number--borderFull"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Number cxd-Number--borderFull"
|
class="cxd-Number-handler-wrap"
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="cxd-Number-handler-wrap"
|
aria-disabled="false"
|
||||||
|
aria-label="Increase Value"
|
||||||
|
class="cxd-Number-handler cxd-Number-handler-up"
|
||||||
|
role="button"
|
||||||
|
unselectable="on"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-disabled="false"
|
class="cxd-Number-handler-up-inner"
|
||||||
aria-label="Increase Value"
|
|
||||||
class="cxd-Number-handler cxd-Number-handler-up"
|
|
||||||
role="button"
|
|
||||||
unselectable="on"
|
unselectable="on"
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="cxd-Number-handler-up-inner"
|
|
||||||
unselectable="on"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-label="Decrease Value"
|
|
||||||
class="cxd-Number-handler cxd-Number-handler-down"
|
|
||||||
role="button"
|
|
||||||
unselectable="on"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="cxd-Number-handler-down-inner"
|
|
||||||
unselectable="on"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="cxd-Number-input-wrap"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-valuenow="88"
|
|
||||||
autocomplete="off"
|
|
||||||
class="cxd-Number-input"
|
|
||||||
placeholder="请手动输入分数"
|
|
||||||
role="spinbutton"
|
|
||||||
step="1"
|
|
||||||
value="88"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</span>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-label="Decrease Value"
|
||||||
|
class="cxd-Number-handler cxd-Number-handler-down"
|
||||||
|
role="button"
|
||||||
|
unselectable="on"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="cxd-Number-handler-down-inner"
|
||||||
|
unselectable="on"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="cxd-Number-input-wrap"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-valuenow="88"
|
||||||
|
autocomplete="off"
|
||||||
|
class="cxd-Number-input"
|
||||||
|
placeholder="请手动输入分数"
|
||||||
|
role="spinbutton"
|
||||||
|
step="1"
|
||||||
|
value="88"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="cxd-Form-col"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-col"
|
class="cxd-Form-item cxd-Form-item--row"
|
||||||
|
data-role="form-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-item cxd-Form-item--row"
|
class="cxd-Form-rowInner"
|
||||||
data-role="form-item"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-rowInner"
|
class="cxd-Form-control cxd-TextControl"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cxd-Form-control cxd-TextControl"
|
class="cxd-TextControl-input"
|
||||||
>
|
>
|
||||||
<div
|
<input
|
||||||
class="cxd-TextControl-input"
|
autocomplete="off"
|
||||||
>
|
class=""
|
||||||
<input
|
name="comment"
|
||||||
autocomplete="off"
|
placeholder="请手动输入意见"
|
||||||
class=""
|
size="10"
|
||||||
name="comment"
|
type="text"
|
||||||
placeholder="请手动输入意见"
|
value="this is comment msg!!"
|
||||||
size="10"
|
/>
|
||||||
type="text"
|
|
||||||
value="this is comment msg!!"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +72,8 @@
|
|||||||
"sortablejs": "1.15.0",
|
"sortablejs": "1.15.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"video-react": "0.15.0",
|
"video-react": "0.15.0",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5",
|
||||||
|
"react-intersection-observer": "9.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||||
|
@ -58,6 +58,7 @@ import {
|
|||||||
import type {PaginationProps} from './Pagination';
|
import type {PaginationProps} from './Pagination';
|
||||||
import {isAlive} from 'mobx-state-tree';
|
import {isAlive} from 'mobx-state-tree';
|
||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
|
import memoize from 'lodash/memoize';
|
||||||
|
|
||||||
export type CRUDBultinToolbarType =
|
export type CRUDBultinToolbarType =
|
||||||
| 'columns-toggler'
|
| 'columns-toggler'
|
||||||
@ -473,6 +474,10 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
/** 父容器, 主要用于定位CRUD内部popover的挂载点 */
|
/** 父容器, 主要用于定位CRUD内部popover的挂载点 */
|
||||||
parentContainer: Element | null;
|
parentContainer: Element | null;
|
||||||
|
|
||||||
|
filterOnEvent = memoize(onEvent =>
|
||||||
|
omitBy(onEvent, (event, key: any) => !INNER_EVENTS.includes(key))
|
||||||
|
);
|
||||||
|
|
||||||
constructor(props: CRUDProps) {
|
constructor(props: CRUDProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@ -548,8 +553,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {store, autoGenerateFilter, columns} = this.props;
|
const {store, autoGenerateFilter, perPageField, columns} = this.props;
|
||||||
if (this.props.perPage) {
|
if (this.props.perPage && !store.query[perPageField || 'perPage']) {
|
||||||
store.changePage(store.page, this.props.perPage);
|
store.changePage(store.page, this.props.perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -662,6 +667,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
|
this.filterOnEvent.cache.clear?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查找CRUD最近层级的父窗口 */
|
/** 查找CRUD最近层级的父窗口 */
|
||||||
@ -2451,10 +2457,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
...rest,
|
...rest,
|
||||||
// 通用事件 例如cus-event 如果直接透传给table 则会被触发2次
|
// 通用事件 例如cus-event 如果直接透传给table 则会被触发2次
|
||||||
// 因此只将下层组件table、cards中自定义事件透传下去 否则通过crud配置了也不会执行
|
// 因此只将下层组件table、cards中自定义事件透传下去 否则通过crud配置了也不会执行
|
||||||
onEvent: omitBy(
|
onEvent: this.filterOnEvent(onEvent),
|
||||||
onEvent,
|
|
||||||
(event, key: any) => !INNER_EVENTS.includes(key)
|
|
||||||
),
|
|
||||||
columns: store.columns ?? rest.columns,
|
columns: store.columns ?? rest.columns,
|
||||||
type: mode || 'table'
|
type: mode || 'table'
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
ApiObject,
|
ApiObject,
|
||||||
autobind,
|
autobind,
|
||||||
isExpression,
|
isExpression,
|
||||||
ITableStore,
|
|
||||||
isPureVariable,
|
isPureVariable,
|
||||||
resolveVariableAndFilter,
|
resolveVariableAndFilter,
|
||||||
getRendererByName,
|
getRendererByName,
|
||||||
@ -283,9 +282,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
entries: SimpleMap<any, number>;
|
entries: SimpleMap<any, number>;
|
||||||
entityId: number = 1;
|
entityId: number = 1;
|
||||||
subForms: any = {};
|
subForms: any = {};
|
||||||
|
subFormItems: any = {};
|
||||||
rowPrinstine: Array<any> = [];
|
rowPrinstine: Array<any> = [];
|
||||||
editting: any = {};
|
editting: any = {};
|
||||||
tableStore?: ITableStore;
|
|
||||||
|
|
||||||
constructor(props: TableProps) {
|
constructor(props: TableProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -305,6 +304,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
this.handleRadioChange = this.handleRadioChange.bind(this);
|
this.handleRadioChange = this.handleRadioChange.bind(this);
|
||||||
this.getEntryId = this.getEntryId.bind(this);
|
this.getEntryId = this.getEntryId.bind(this);
|
||||||
this.subFormRef = this.subFormRef.bind(this);
|
this.subFormRef = this.subFormRef.bind(this);
|
||||||
|
this.subFormItemRef = this.subFormItemRef.bind(this);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
this.emitValue = this.emitValue.bind(this);
|
this.emitValue = this.emitValue.bind(this);
|
||||||
}
|
}
|
||||||
@ -382,6 +382,10 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
this.subForms[`${x}-${y}`] = form;
|
this.subForms[`${x}-${y}`] = form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subFormItemRef(form: any, x: number, y: number) {
|
||||||
|
this.subFormItems[`${x}-${y}`] = form;
|
||||||
|
}
|
||||||
|
|
||||||
async validate(): Promise<string | void> {
|
async validate(): Promise<string | void> {
|
||||||
const {value, translate: __, columns} = this.props;
|
const {value, translate: __, columns} = this.props;
|
||||||
const minLength = this.resolveVariableProps(this.props, 'minLength');
|
const minLength = this.resolveVariableProps(this.props, 'minLength');
|
||||||
@ -442,16 +446,18 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.tableStore) return;
|
|
||||||
|
|
||||||
// 校验子项
|
// 校验子项
|
||||||
const children = this.tableStore.children.filter(
|
const subFormItemss: Array<any> = [];
|
||||||
item => item?.storeType === 'FormItemStore'
|
Object.keys(this.subFormItems).forEach(
|
||||||
|
key =>
|
||||||
|
this.subFormItems[key] && subFormItemss.push(this.subFormItems[key])
|
||||||
);
|
);
|
||||||
|
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
children.map(item => item.validate(this.props.value))
|
subFormItemss.map(item => item.props.onValidate())
|
||||||
);
|
);
|
||||||
|
let msg = ~results.indexOf(false) ? __('Form.validateFailed') : '';
|
||||||
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
async emitValue() {
|
async emitValue() {
|
||||||
@ -728,6 +734,12 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
);
|
);
|
||||||
subForms.forEach(form => form.flush());
|
subForms.forEach(form => form.flush());
|
||||||
|
|
||||||
|
const subFormItems: Array<any> = [];
|
||||||
|
Object.keys(this.subFormItems).forEach(
|
||||||
|
key => this.subFormItems[key] && subFormItems.push(this.subFormItems[key])
|
||||||
|
);
|
||||||
|
subFormItems.forEach(item => item.props.onFlushChange?.());
|
||||||
|
|
||||||
const validateForms: Array<any> = [];
|
const validateForms: Array<any> = [];
|
||||||
Object.keys(this.subForms).forEach(key => {
|
Object.keys(this.subForms).forEach(key => {
|
||||||
const arr = key.split('-');
|
const arr = key.split('-');
|
||||||
@ -1229,7 +1241,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
label: __('Table.operation'),
|
label: __('Table.operation'),
|
||||||
className: 'v-middle nowrap',
|
className: 'v-middle nowrap',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 100,
|
width: 150,
|
||||||
innerClassName: 'm-n'
|
innerClassName: 'm-n'
|
||||||
};
|
};
|
||||||
columns.push(operation);
|
columns.push(operation);
|
||||||
@ -1468,7 +1480,6 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
while (ref && ref.getWrappedInstance) {
|
while (ref && ref.getWrappedInstance) {
|
||||||
ref = ref.getWrappedInstance();
|
ref = ref.getWrappedInstance();
|
||||||
}
|
}
|
||||||
this.tableStore = ref?.props?.store;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
computedAddBtnDisabled() {
|
computedAddBtnDisabled() {
|
||||||
@ -1553,6 +1564,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
|||||||
onSaveOrder: this.handleSaveTableOrder,
|
onSaveOrder: this.handleSaveTableOrder,
|
||||||
buildItemProps: this.buildItemProps,
|
buildItemProps: this.buildItemProps,
|
||||||
quickEditFormRef: this.subFormRef,
|
quickEditFormRef: this.subFormRef,
|
||||||
|
quickEditFormItemRef: this.subFormItemRef,
|
||||||
columnsTogglable: columnsTogglable,
|
columnsTogglable: columnsTogglable,
|
||||||
combineNum: combineNum,
|
combineNum: combineNum,
|
||||||
combineFromIndex: combineFromIndex,
|
combineFromIndex: combineFromIndex,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {findDOMNode} from 'react-dom';
|
import {findDOMNode} from 'react-dom';
|
||||||
import {RendererProps, noop} from 'amis-core';
|
import {RendererProps, getRendererByName, noop} from 'amis-core';
|
||||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||||
import {ActionObject} from 'amis-core';
|
import {ActionObject} from 'amis-core';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
@ -121,8 +121,10 @@ export const HocQuickEdit =
|
|||||||
this.handleWindowKeyPress = this.handleWindowKeyPress.bind(this);
|
this.handleWindowKeyPress = this.handleWindowKeyPress.bind(this);
|
||||||
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
|
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
|
||||||
this.formRef = this.formRef.bind(this);
|
this.formRef = this.formRef.bind(this);
|
||||||
|
this.formItemRef = this.formItemRef.bind(this);
|
||||||
this.handleInit = this.handleInit.bind(this);
|
this.handleInit = this.handleInit.bind(this);
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleFormItemChange = this.handleFormItemChange.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpened: false
|
isOpened: false
|
||||||
@ -152,6 +154,17 @@ export const HocQuickEdit =
|
|||||||
quickEditFormRef(ref, colIndex, rowIndex);
|
quickEditFormRef(ref, colIndex, rowIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
formItemRef(ref: any) {
|
||||||
|
const {quickEditFormItemRef, rowIndex, colIndex} = this.props;
|
||||||
|
|
||||||
|
if (quickEditFormItemRef) {
|
||||||
|
while (ref && ref.getWrappedInstance) {
|
||||||
|
ref = ref.getWrappedInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
quickEditFormItemRef(ref, colIndex, rowIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleWindowKeyPress(e: Event) {
|
handleWindowKeyPress(e: Event) {
|
||||||
const ns = this.props.classPrefix;
|
const ns = this.props.classPrefix;
|
||||||
@ -337,6 +350,17 @@ export const HocQuickEdit =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFormItemChange(value: any) {
|
||||||
|
const {onQuickChange, quickEdit, name} = this.props;
|
||||||
|
|
||||||
|
onQuickChange(
|
||||||
|
{[name!]: value},
|
||||||
|
(quickEdit as QuickEditConfig).saveImmediately,
|
||||||
|
false,
|
||||||
|
quickEdit as QuickEditConfig
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
openQuickEdit() {
|
openQuickEdit() {
|
||||||
currentOpened = this;
|
currentOpened = this;
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -526,6 +550,51 @@ export const HocQuickEdit =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInlineForm() {
|
||||||
|
const {
|
||||||
|
render,
|
||||||
|
classnames: cx,
|
||||||
|
canAccessSuperData,
|
||||||
|
disabled,
|
||||||
|
value,
|
||||||
|
name
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const schema: any = this.buildSchema();
|
||||||
|
|
||||||
|
// 有且只有一个表单项时,直接渲染表单项
|
||||||
|
if (
|
||||||
|
Array.isArray(schema.body) &&
|
||||||
|
schema.body.length === 1 &&
|
||||||
|
!schema.body[0].unique && // 唯一模式还不支持
|
||||||
|
!schema.body[0].value && // 不能有默认值表达式什么的情况
|
||||||
|
schema.body[0].name &&
|
||||||
|
schema.body[0].name === name &&
|
||||||
|
schema.body[0].type &&
|
||||||
|
getRendererByName(schema.body[0].type)?.isFormItem
|
||||||
|
) {
|
||||||
|
return render('inline-form-item', schema.body[0], {
|
||||||
|
mode: 'normal',
|
||||||
|
value: value || '',
|
||||||
|
onChange: this.handleFormItemChange,
|
||||||
|
ref: this.formItemRef
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return render('inline-form', schema, {
|
||||||
|
value: undefined,
|
||||||
|
wrapperComponent: 'div',
|
||||||
|
className: cx('Form--quickEdit'),
|
||||||
|
ref: this.formRef,
|
||||||
|
simpleMode: true,
|
||||||
|
onInit: this.handleInit,
|
||||||
|
onChange: this.handleChange,
|
||||||
|
formLazyChange: false,
|
||||||
|
canAccessSuperData,
|
||||||
|
disabled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
onQuickChange,
|
onQuickChange,
|
||||||
@ -556,20 +625,7 @@ export const HocQuickEdit =
|
|||||||
(quickEdit as QuickEditConfig).isFormMode
|
(quickEdit as QuickEditConfig).isFormMode
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Component {...this.props}>
|
<Component {...this.props}>{this.renderInlineForm()}</Component>
|
||||||
{render('inline-form', this.buildSchema(), {
|
|
||||||
value: undefined,
|
|
||||||
wrapperComponent: 'div',
|
|
||||||
className: cx('Form--quickEdit'),
|
|
||||||
ref: this.formRef,
|
|
||||||
simpleMode: true,
|
|
||||||
onInit: this.handleInit,
|
|
||||||
onChange: this.handleChange,
|
|
||||||
formLazyChange: false,
|
|
||||||
canAccessSuperData,
|
|
||||||
disabled
|
|
||||||
})}
|
|
||||||
</Component>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import {ClassNamesFn, RendererEvent} from 'amis-core';
|
import {ClassNamesFn, RendererEvent} from 'amis-core';
|
||||||
|
|
||||||
import {SchemaNode, ActionObject} from 'amis-core';
|
import {SchemaNode, ActionObject} from 'amis-core';
|
||||||
import {TableRow} from './TableRow';
|
import TableRow from './TableRow';
|
||||||
import {filter} from 'amis-core';
|
import {filter} from 'amis-core';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {trace, reaction} from 'mobx';
|
import {trace, reaction} from 'mobx';
|
||||||
@ -91,7 +91,8 @@ export class TableBody extends React.Component<TableBodyProps> {
|
|||||||
onRowClick,
|
onRowClick,
|
||||||
onRowDbClick,
|
onRowDbClick,
|
||||||
onRowMouseEnter,
|
onRowMouseEnter,
|
||||||
onRowMouseLeave
|
onRowMouseLeave,
|
||||||
|
store
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return rows.map((item: IRow, rowIndex: number) => {
|
return rows.map((item: IRow, rowIndex: number) => {
|
||||||
@ -99,6 +100,7 @@ export class TableBody extends React.Component<TableBodyProps> {
|
|||||||
const doms = [
|
const doms = [
|
||||||
<TableRow
|
<TableRow
|
||||||
{...itemProps}
|
{...itemProps}
|
||||||
|
store={store}
|
||||||
itemAction={itemAction}
|
itemAction={itemAction}
|
||||||
classnames={cx}
|
classnames={cx}
|
||||||
checkOnItemClick={checkOnItemClick}
|
checkOnItemClick={checkOnItemClick}
|
||||||
@ -136,6 +138,7 @@ export class TableBody extends React.Component<TableBodyProps> {
|
|||||||
doms.push(
|
doms.push(
|
||||||
<TableRow
|
<TableRow
|
||||||
{...itemProps}
|
{...itemProps}
|
||||||
|
store={store}
|
||||||
itemAction={itemAction}
|
itemAction={itemAction}
|
||||||
classnames={cx}
|
classnames={cx}
|
||||||
checkOnItemClick={checkOnItemClick}
|
checkOnItemClick={checkOnItemClick}
|
||||||
|
@ -81,53 +81,60 @@ export interface TableContentProps extends LocaleProps {
|
|||||||
dispatchEvent?: Function;
|
dispatchEvent?: Function;
|
||||||
onEvent?: OnEventProps;
|
onEvent?: OnEventProps;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
columnWidthReady?: boolean;
|
||||||
|
|
||||||
|
// 以下纯粹是为了监控
|
||||||
|
someChecked?: boolean;
|
||||||
|
allChecked?: boolean;
|
||||||
|
isSelectionThresholdReached?: boolean;
|
||||||
|
orderBy?: string;
|
||||||
|
orderDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
export function renderItemActions(
|
||||||
export class TableContent extends React.Component<TableContentProps> {
|
props: Pick<
|
||||||
static renderItemActions(
|
TableContentProps,
|
||||||
props: Pick<
|
'itemActions' | 'render' | 'store' | 'classnames'
|
||||||
TableContentProps,
|
>
|
||||||
'itemActions' | 'render' | 'store' | 'classnames'
|
) {
|
||||||
>
|
const {itemActions, render, store, classnames: cx} = props;
|
||||||
) {
|
|
||||||
const {itemActions, render, store, classnames: cx} = props;
|
|
||||||
|
|
||||||
if (!store.hoverRow) {
|
if (!store.hoverRow) {
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
const finalActions = Array.isArray(itemActions)
|
|
||||||
? itemActions.filter(action => !action.hiddenOnHover)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (!finalActions.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ItemActionsWrapper store={store} classnames={cx}>
|
|
||||||
<div className={cx('Table-itemActions')}>
|
|
||||||
{finalActions.map((action, index) =>
|
|
||||||
render(
|
|
||||||
`itemAction/${index}`,
|
|
||||||
{
|
|
||||||
...(action as any),
|
|
||||||
isMenuItem: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: index,
|
|
||||||
item: store.hoverRow,
|
|
||||||
data: store.hoverRow!.locals,
|
|
||||||
rowIndex: store.hoverRow!.index
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ItemActionsWrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalActions = Array.isArray(itemActions)
|
||||||
|
? itemActions.filter(action => !action.hiddenOnHover)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!finalActions.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemActionsWrapper store={store} classnames={cx}>
|
||||||
|
<div className={cx('Table-itemActions')}>
|
||||||
|
{finalActions.map((action, index) =>
|
||||||
|
render(
|
||||||
|
`itemAction/${index}`,
|
||||||
|
{
|
||||||
|
...(action as any),
|
||||||
|
isMenuItem: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: index,
|
||||||
|
item: store.hoverRow,
|
||||||
|
data: store.hoverRow!.locals,
|
||||||
|
rowIndex: store.hoverRow!.index
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ItemActionsWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TableContent extends React.PureComponent<TableContentProps> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
placeholder,
|
placeholder,
|
||||||
@ -166,7 +173,8 @@ export class TableContent extends React.Component<TableContentProps> {
|
|||||||
store,
|
store,
|
||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
onEvent,
|
onEvent,
|
||||||
loading
|
loading,
|
||||||
|
columnWidthReady
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const tableClassName = cx('Table-table', this.props.tableClassName);
|
const tableClassName = cx('Table-table', this.props.tableClassName);
|
||||||
@ -182,7 +190,7 @@ export class TableContent extends React.Component<TableContentProps> {
|
|||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
className={cx(
|
className={cx(
|
||||||
tableClassName,
|
tableClassName,
|
||||||
store.columnWidthReady ? 'is-layout-fixed' : undefined
|
columnWidthReady ? 'is-layout-fixed' : undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ColGroup columns={columns} store={store} />
|
<ColGroup columns={columns} store={store} />
|
||||||
@ -293,7 +301,6 @@ export class TableContent extends React.Component<TableContentProps> {
|
|||||||
affixRow={affixRow}
|
affixRow={affixRow}
|
||||||
data={data}
|
data={data}
|
||||||
rowsProps={{
|
rowsProps={{
|
||||||
data,
|
|
||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
onEvent
|
onEvent
|
||||||
}}
|
}}
|
||||||
@ -304,3 +311,27 @@ export class TableContent extends React.Component<TableContentProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default observer((props: TableContentProps) => {
|
||||||
|
const store = props.store;
|
||||||
|
|
||||||
|
// 分析 table/index.tsx 中的 renderHeadCell 依赖了以下属性
|
||||||
|
// store.someChecked;
|
||||||
|
// store.allChecked;
|
||||||
|
// store.isSelectionThresholdReached;
|
||||||
|
// store.allExpanded;
|
||||||
|
// store.orderBy
|
||||||
|
// store.orderDir
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContent
|
||||||
|
{...props}
|
||||||
|
columnWidthReady={store.columnWidthReady}
|
||||||
|
someChecked={store.someChecked}
|
||||||
|
allChecked={store.allChecked}
|
||||||
|
isSelectionThresholdReached={store.isSelectionThresholdReached}
|
||||||
|
orderBy={store.orderBy}
|
||||||
|
orderDir={store.orderDir}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {IColumn, IRow} from 'amis-core/lib/store/table';
|
import type {IColumn, IRow} from 'amis-core/lib/store/table';
|
||||||
import {RendererEvent, RendererProps} from 'amis-core';
|
import {RendererEvent, RendererProps, autobind, traceProps} from 'amis-core';
|
||||||
import {Action} from '../Action';
|
import {Action} from '../Action';
|
||||||
import {isClickOnInput, createObject} from 'amis-core';
|
import {isClickOnInput} from 'amis-core';
|
||||||
|
import {useInView} from 'react-intersection-observer';
|
||||||
|
|
||||||
interface TableRowProps extends Pick<RendererProps, 'render'> {
|
interface TableRowProps extends Pick<RendererProps, 'render'> {
|
||||||
onCheck: (item: IRow, value: boolean, shift?: boolean) => Promise<void>;
|
onCheck: (item: IRow, value: boolean, shift?: boolean) => Promise<void>;
|
||||||
@ -38,31 +39,36 @@ interface TableRowProps extends Pick<RendererProps, 'render'> {
|
|||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
export class TableRow extends React.PureComponent<
|
||||||
export class TableRow extends React.Component<TableRowProps> {
|
TableRowProps & {
|
||||||
// reaction?: () => void;
|
expanded: boolean;
|
||||||
constructor(props: TableRowProps) {
|
id: string;
|
||||||
super(props);
|
newIndex: number;
|
||||||
this.handleAction = this.handleAction.bind(this);
|
isHover: boolean;
|
||||||
this.handleQuickChange = this.handleQuickChange.bind(this);
|
checked: boolean;
|
||||||
this.handleChange = this.handleChange.bind(this);
|
modified: boolean;
|
||||||
this.handleItemClick = this.handleItemClick.bind(this);
|
moved: boolean;
|
||||||
this.handleDbClick = this.handleDbClick.bind(this);
|
depth: number;
|
||||||
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
expandable: boolean;
|
||||||
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
appeard?: boolean;
|
||||||
|
checkdisable: boolean;
|
||||||
|
trRef?: React.Ref<any>;
|
||||||
}
|
}
|
||||||
|
> {
|
||||||
|
@autobind
|
||||||
handleMouseEnter(e: React.MouseEvent<HTMLTableRowElement>) {
|
handleMouseEnter(e: React.MouseEvent<HTMLTableRowElement>) {
|
||||||
const {item, itemIndex, onRowMouseEnter} = this.props;
|
const {item, itemIndex, onRowMouseEnter} = this.props;
|
||||||
onRowMouseEnter?.(item?.data, itemIndex);
|
onRowMouseEnter?.(item?.data, itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleMouseLeave(e: React.MouseEvent<HTMLTableRowElement>) {
|
handleMouseLeave(e: React.MouseEvent<HTMLTableRowElement>) {
|
||||||
const {item, itemIndex, onRowMouseLeave} = this.props;
|
const {item, itemIndex, onRowMouseLeave} = this.props;
|
||||||
onRowMouseLeave?.(item?.data, itemIndex);
|
onRowMouseLeave?.(item?.data, itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义点击一行的行为,通过 itemAction配置
|
// 定义点击一行的行为,通过 itemAction配置
|
||||||
|
@autobind
|
||||||
async handleItemClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
async handleItemClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
||||||
if (isClickOnInput(e)) {
|
if (isClickOnInput(e)) {
|
||||||
return;
|
return;
|
||||||
@ -92,16 +98,19 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleDbClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
handleDbClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
||||||
const {item, itemIndex, onRowDbClick} = this.props;
|
const {item, itemIndex, onRowDbClick} = this.props;
|
||||||
onRowDbClick?.(item?.data, itemIndex);
|
onRowDbClick?.(item?.data, itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleAction(e: React.UIEvent<any>, action: Action, ctx: any) {
|
handleAction(e: React.UIEvent<any>, action: Action, ctx: any) {
|
||||||
const {onAction, item} = this.props;
|
const {onAction, item} = this.props;
|
||||||
onAction && onAction(e, action, ctx || item.locals);
|
onAction && onAction(e, action, ctx || item.locals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleQuickChange(
|
handleQuickChange(
|
||||||
values: object,
|
values: object,
|
||||||
saveImmediately?: boolean,
|
saveImmediately?: boolean,
|
||||||
@ -116,6 +125,7 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
onQuickChange(item, values, saveImmediately, savePristine, options);
|
onQuickChange(item, values, saveImmediately, savePristine, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleChange(
|
handleChange(
|
||||||
value: any,
|
value: any,
|
||||||
name: string,
|
name: string,
|
||||||
@ -157,18 +167,32 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
parent,
|
parent,
|
||||||
itemAction,
|
itemAction,
|
||||||
onEvent,
|
onEvent,
|
||||||
|
|
||||||
|
expanded,
|
||||||
|
id,
|
||||||
|
newIndex,
|
||||||
|
isHover,
|
||||||
|
checked,
|
||||||
|
modified,
|
||||||
|
moved,
|
||||||
|
depth,
|
||||||
|
expandable,
|
||||||
|
appeard,
|
||||||
|
trRef,
|
||||||
|
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (footableMode) {
|
if (footableMode) {
|
||||||
if (!item.expanded) {
|
if (!expanded) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
data-id={item.id}
|
ref={trRef}
|
||||||
data-index={item.newIndex}
|
data-id={id}
|
||||||
|
data-index={newIndex}
|
||||||
onClick={
|
onClick={
|
||||||
checkOnItemClick || itemAction || onEvent?.rowClick
|
checkOnItemClick || itemAction || onEvent?.rowClick
|
||||||
? this.handleItemClick
|
? this.handleItemClick
|
||||||
@ -178,10 +202,10 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
onMouseLeave={this.handleMouseLeave}
|
onMouseLeave={this.handleMouseLeave}
|
||||||
className={cx(itemClassName, {
|
className={cx(itemClassName, {
|
||||||
'is-hovered': item.isHover,
|
'is-hovered': isHover,
|
||||||
'is-checked': item.checked,
|
'is-checked': checked,
|
||||||
'is-modified': item.modified,
|
'is-modified': modified,
|
||||||
'is-moved': item.moved,
|
'is-moved': moved,
|
||||||
[`Table-tr--hasItemAction`]: itemAction, // 就是为了加鼠标效果
|
[`Table-tr--hasItemAction`]: itemAction, // 就是为了加鼠标效果
|
||||||
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
||||||
[`Table-tr--even`]: itemIndex % 2 === 1
|
[`Table-tr--even`]: itemIndex % 2 === 1
|
||||||
@ -208,20 +232,26 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
</th>
|
</th>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{renderCell(
|
{appeard ? (
|
||||||
`${regionPrefix}${itemIndex}/${column.index}`,
|
renderCell(
|
||||||
column,
|
`${regionPrefix}${itemIndex}/${column.index}`,
|
||||||
item,
|
column,
|
||||||
{
|
item,
|
||||||
...rest,
|
{
|
||||||
width: null,
|
...rest,
|
||||||
rowIndex: itemIndex,
|
width: null,
|
||||||
colIndex: column.index,
|
rowIndex: itemIndex,
|
||||||
key: column.index,
|
colIndex: column.index,
|
||||||
onAction: this.handleAction,
|
key: column.index,
|
||||||
onQuickChange: this.handleQuickChange,
|
onAction: this.handleAction,
|
||||||
onChange: this.handleChange
|
onQuickChange: this.handleQuickChange,
|
||||||
}
|
onChange: this.handleChange
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<td key={column.index}>
|
||||||
|
<div className={cx('Table-emptyBlock')}> </div>
|
||||||
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@ -238,6 +268,7 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
|
ref={trRef}
|
||||||
onClick={
|
onClick={
|
||||||
checkOnItemClick || itemAction || onEvent?.rowClick
|
checkOnItemClick || itemAction || onEvent?.rowClick
|
||||||
? this.handleItemClick
|
? this.handleItemClick
|
||||||
@ -246,36 +277,79 @@ export class TableRow extends React.Component<TableRowProps> {
|
|||||||
onDoubleClick={this.handleDbClick}
|
onDoubleClick={this.handleDbClick}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
onMouseLeave={this.handleMouseLeave}
|
onMouseLeave={this.handleMouseLeave}
|
||||||
data-index={item.depth === 1 ? item.newIndex : undefined}
|
data-index={depth === 1 ? newIndex : undefined}
|
||||||
data-id={item.id}
|
data-id={id}
|
||||||
className={cx(
|
className={cx(
|
||||||
itemClassName,
|
itemClassName,
|
||||||
{
|
{
|
||||||
'is-hovered': item.isHover,
|
'is-hovered': isHover,
|
||||||
'is-checked': item.checked,
|
'is-checked': checked,
|
||||||
'is-modified': item.modified,
|
'is-modified': modified,
|
||||||
'is-moved': item.moved,
|
'is-moved': moved,
|
||||||
'is-expanded': item.expanded && item.expandable,
|
'is-expanded': expanded && expandable,
|
||||||
'is-expandable': item.expandable,
|
'is-expandable': expandable,
|
||||||
[`Table-tr--hasItemAction`]: itemAction,
|
[`Table-tr--hasItemAction`]: itemAction,
|
||||||
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
||||||
[`Table-tr--even`]: itemIndex % 2 === 1
|
[`Table-tr--even`]: itemIndex % 2 === 1
|
||||||
},
|
},
|
||||||
`Table-tr--${item.depth}th`
|
`Table-tr--${depth}th`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{columns.map(column =>
|
{columns.map(column =>
|
||||||
renderCell(`${itemIndex}/${column.index}`, column, item, {
|
appeard ? (
|
||||||
...rest,
|
renderCell(`${itemIndex}/${column.index}`, column, item, {
|
||||||
rowIndex: itemIndex,
|
...rest,
|
||||||
colIndex: column.index,
|
rowIndex: itemIndex,
|
||||||
key: column.index,
|
colIndex: column.index,
|
||||||
onAction: this.handleAction,
|
key: column.index,
|
||||||
onQuickChange: this.handleQuickChange,
|
onAction: this.handleAction,
|
||||||
onChange: this.handleChange
|
onQuickChange: this.handleQuickChange,
|
||||||
})
|
onChange: this.handleChange
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<td key={column.index}>
|
||||||
|
<div className={cx('Table-emptyBlock')}> </div>
|
||||||
|
</td>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 换成 mobx-react-lite 模式
|
||||||
|
export default observer((props: TableRowProps) => {
|
||||||
|
const item = props.item;
|
||||||
|
const store = props.store;
|
||||||
|
const columns = props.columns;
|
||||||
|
const canAccessSuperData =
|
||||||
|
store.canAccessSuperData ||
|
||||||
|
columns.some(item => item.pristine.canAccessSuperData);
|
||||||
|
|
||||||
|
const {ref, inView} = useInView({
|
||||||
|
threshold: 0,
|
||||||
|
onChange: item.markAppeared,
|
||||||
|
skip: !item.lazyRender
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
{...props}
|
||||||
|
trRef={ref}
|
||||||
|
expanded={item.expanded}
|
||||||
|
id={item.id}
|
||||||
|
newIndex={item.newIndex}
|
||||||
|
isHover={item.isHover}
|
||||||
|
checked={item.checked}
|
||||||
|
modified={item.modified}
|
||||||
|
moved={item.moved}
|
||||||
|
depth={item.depth}
|
||||||
|
expandable={item.expandable}
|
||||||
|
checkdisable={item.checkdisable}
|
||||||
|
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
|
||||||
|
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
|
||||||
|
data={canAccessSuperData ? item.locals : item.data}
|
||||||
|
appeard={item.lazyRender ? item.appeared || inView : true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -17,6 +17,7 @@ import {Button} from 'amis-ui';
|
|||||||
import {TableStore, ITableStore, padArr} from 'amis-core';
|
import {TableStore, ITableStore, padArr} from 'amis-core';
|
||||||
import {
|
import {
|
||||||
anyChanged,
|
anyChanged,
|
||||||
|
changedEffect,
|
||||||
getScrollParent,
|
getScrollParent,
|
||||||
difference,
|
difference,
|
||||||
autobind,
|
autobind,
|
||||||
@ -38,7 +39,7 @@ import {TableCell} from './TableCell';
|
|||||||
import type {AutoGenerateFilterObject} from '../CRUD';
|
import type {AutoGenerateFilterObject} from '../CRUD';
|
||||||
import {HeadCellFilterDropDown} from './HeadCellFilterDropdown';
|
import {HeadCellFilterDropDown} from './HeadCellFilterDropdown';
|
||||||
import {HeadCellSearchDropDown} from './HeadCellSearchDropdown';
|
import {HeadCellSearchDropDown} from './HeadCellSearchDropdown';
|
||||||
import {TableContent} from './TableContent';
|
import TableContent, {renderItemActions} from './TableContent';
|
||||||
import {
|
import {
|
||||||
BaseSchema,
|
BaseSchema,
|
||||||
SchemaApi,
|
SchemaApi,
|
||||||
@ -183,6 +184,12 @@ export type TableColumnObject = {
|
|||||||
*/
|
*/
|
||||||
canAccessSuperData?: boolean;
|
canAccessSuperData?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当一次性渲染太多列上有用,默认为 100,可以用来提升表格渲染性能
|
||||||
|
* @default 100
|
||||||
|
*/
|
||||||
|
lazyRenderAfter?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单元格内部组件自定义样式 style作为单元格自定义样式的配置
|
* 单元格内部组件自定义样式 style作为单元格自定义样式的配置
|
||||||
*/
|
*/
|
||||||
@ -569,7 +576,10 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
keepItemSelectionOnPageChange,
|
keepItemSelectionOnPageChange,
|
||||||
maxKeepItemSelectionLength,
|
maxKeepItemSelectionLength,
|
||||||
onQuery,
|
onQuery,
|
||||||
autoGenerateFilter
|
autoGenerateFilter,
|
||||||
|
loading,
|
||||||
|
canAccessSuperData,
|
||||||
|
lazyRenderAfter
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
let combineNum = props.combineNum;
|
let combineNum = props.combineNum;
|
||||||
@ -597,7 +607,10 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
combineNum,
|
combineNum,
|
||||||
combineFromIndex,
|
combineFromIndex,
|
||||||
keepItemSelectionOnPageChange,
|
keepItemSelectionOnPageChange,
|
||||||
maxKeepItemSelectionLength
|
maxKeepItemSelectionLength,
|
||||||
|
loading,
|
||||||
|
canAccessSuperData,
|
||||||
|
lazyRenderAfter
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -770,58 +783,49 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
const props = this.props;
|
const props = this.props;
|
||||||
const store = props.store;
|
const store = props.store;
|
||||||
|
|
||||||
if (
|
changedEffect(
|
||||||
anyChanged(
|
[
|
||||||
[
|
'selectable',
|
||||||
'selectable',
|
'columnsTogglable',
|
||||||
'columnsTogglable',
|
'draggable',
|
||||||
'draggable',
|
'orderBy',
|
||||||
'orderBy',
|
'orderDir',
|
||||||
'orderDir',
|
'multiple',
|
||||||
'multiple',
|
'footable',
|
||||||
'footable',
|
'primaryField',
|
||||||
'primaryField',
|
'itemCheckableOn',
|
||||||
'itemCheckableOn',
|
'itemDraggableOn',
|
||||||
'itemDraggableOn',
|
'hideCheckToggler',
|
||||||
'hideCheckToggler',
|
'combineNum',
|
||||||
'combineNum',
|
'combineFromIndex',
|
||||||
'combineFromIndex',
|
'expandConfig',
|
||||||
'expandConfig'
|
'columns',
|
||||||
],
|
'loading',
|
||||||
prevProps,
|
'canAccessSuperData',
|
||||||
props
|
'lazyRenderAfter'
|
||||||
)
|
],
|
||||||
) {
|
prevProps,
|
||||||
let combineNum = props.combineNum;
|
props,
|
||||||
if (typeof combineNum === 'string') {
|
changes => {
|
||||||
combineNum = parseInt(
|
if (
|
||||||
resolveVariableAndFilter(combineNum, props.data, '| raw'),
|
changes.hasOwnProperty('combineNum') &&
|
||||||
10
|
typeof changes.combineNum === 'string'
|
||||||
);
|
) {
|
||||||
|
changes.combineNum = parseInt(
|
||||||
|
resolveVariableAndFilter(
|
||||||
|
changes.combineNum as string,
|
||||||
|
props.data,
|
||||||
|
'| raw'
|
||||||
|
),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (changes.orderBy && !props.onQuery) {
|
||||||
|
delete changes.orderBy;
|
||||||
|
}
|
||||||
|
store.update(changes as any);
|
||||||
}
|
}
|
||||||
store.update({
|
);
|
||||||
selectable: props.selectable,
|
|
||||||
columnsTogglable: props.columnsTogglable,
|
|
||||||
draggable: props.draggable,
|
|
||||||
orderBy: props.onQuery ? props.orderBy : undefined,
|
|
||||||
orderDir: props.orderDir,
|
|
||||||
multiple: props.multiple,
|
|
||||||
primaryField: props.primaryField,
|
|
||||||
footable: props.footable,
|
|
||||||
itemCheckableOn: props.itemCheckableOn,
|
|
||||||
itemDraggableOn: props.itemDraggableOn,
|
|
||||||
hideCheckToggler: props.hideCheckToggler,
|
|
||||||
combineNum: combineNum,
|
|
||||||
combineFromIndex: props.combineFromIndex,
|
|
||||||
expandConfig: props.expandConfig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevProps.columns !== props.columns) {
|
|
||||||
store.update({
|
|
||||||
columns: props.columns
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
anyChanged(['source', 'value', 'items'], prevProps, props) ||
|
anyChanged(['source', 'value', 'items'], prevProps, props) ||
|
||||||
@ -1091,6 +1095,27 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验直接放在单元格里面的表单项
|
||||||
|
const subFormItems = store.children.filter(
|
||||||
|
item => item?.storeType === 'FormItemStore'
|
||||||
|
);
|
||||||
|
if (subFormItems.length) {
|
||||||
|
const result = await Promise.all(
|
||||||
|
subFormItems.map(item => {
|
||||||
|
let ctx = {};
|
||||||
|
|
||||||
|
if (item.rowIndex && store.rows[item.rowIndex]) {
|
||||||
|
ctx = store.rows[item.rowIndex].data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.validate(ctx);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (~result.indexOf(false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rows = store.modifiedRows.map(item => item.data);
|
const rows = store.modifiedRows.map(item => item.data);
|
||||||
const rowIndexes = store.modifiedRows.map(item => item.path);
|
const rowIndexes = store.modifiedRows.map(item => item.path);
|
||||||
const diff = store.modifiedRows.map(item =>
|
const diff = store.modifiedRows.map(item =>
|
||||||
@ -1149,6 +1174,14 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
key => this.subForms[key] && subForms.push(this.subForms[key])
|
key => this.subForms[key] && subForms.push(this.subForms[key])
|
||||||
);
|
);
|
||||||
subForms.forEach(item => item.clearErrors());
|
subForms.forEach(item => item.clearErrors());
|
||||||
|
|
||||||
|
// 去掉错误提示
|
||||||
|
const subFormItems = store.children.filter(
|
||||||
|
item => item?.storeType === 'FormItemStore'
|
||||||
|
);
|
||||||
|
if (subFormItems.length) {
|
||||||
|
subFormItems.map(item => item.reset());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bulkUpdate(value: any, items: Array<object>) {
|
bulkUpdate(value: any, items: Array<object>) {
|
||||||
@ -1824,6 +1857,9 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
data
|
data
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
// 注意,这里用关了哪些 store 里面的东西,TableContent 里面得也用一下
|
||||||
|
// 因为 renderHeadCell 是 TableContent 回调的,tableContent 不重新渲染,这里面也不会重新渲染
|
||||||
|
|
||||||
const style = {...props.style};
|
const style = {...props.style};
|
||||||
const [stickyStyle, stickyClassName] = store.getStickyStyles(
|
const [stickyStyle, stickyClassName] = store.getStickyStyles(
|
||||||
column,
|
column,
|
||||||
@ -2713,7 +2749,6 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
itemActions,
|
itemActions,
|
||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
onEvent,
|
onEvent,
|
||||||
loading = false,
|
|
||||||
loadingConfig
|
loadingConfig
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -2723,7 +2758,7 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{TableContent.renderItemActions({
|
{renderItemActions({
|
||||||
store,
|
store,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
render,
|
render,
|
||||||
@ -2746,7 +2781,7 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
classnames={cx}
|
classnames={cx}
|
||||||
columns={store.filteredColumns}
|
columns={store.filteredColumns}
|
||||||
columnsGroup={store.columnGroup}
|
columnsGroup={store.columnGroup}
|
||||||
rows={store.rows}
|
rows={store.items} // store.rows 是没有变更的,所以不会触发更新
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
render={render}
|
render={render}
|
||||||
onMouseMove={
|
onMouseMove={
|
||||||
@ -2781,10 +2816,10 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
translate={translate}
|
translate={translate}
|
||||||
dispatchEvent={dispatchEvent}
|
dispatchEvent={dispatchEvent}
|
||||||
onEvent={onEvent}
|
onEvent={onEvent}
|
||||||
loading={loading}
|
loading={store.loading} // store 的同步较慢,所以统一用 store 来下发,否则会出现 props 和 store 变化触发子节点两次 re-rerender
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Spinner loadingConfig={loadingConfig} overlay show={loading} />
|
<Spinner loadingConfig={loadingConfig} overlay show={store.loading} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user