Merge pull request #6796 from hsm-lv/feat-context

feat:CRUD、Table上下文优化
This commit is contained in:
hsm-lv 2023-05-09 12:58:11 +08:00 committed by GitHub
commit 38d26ae414
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 485 additions and 136 deletions

View File

@ -183,13 +183,14 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
}) })
]; ];
}); });
items = items.filter((item: any) => arrItems.find(a => a === item)); items = items.filter((item: any) =>
arrItems.find(a => a === item)
);
} }
} } else {
else {
items = matchSorter(items, value, { items = matchSorter(items, value, {
keys: [key] keys: [key]
}) });
} }
} }
} }
@ -349,11 +350,11 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
}) })
]; ];
}); });
filteredItems = filteredItems.filter( filteredItems = filteredItems.filter(item =>
item => arrItems.find(a => a === item)); arrItems.find(a => a === item)
);
} }
} } else {
else {
filteredItems = matchSorter(filteredItems, value, { filteredItems = matchSorter(filteredItems, value, {
keys: [key] keys: [key]
}); });

View File

@ -97,28 +97,97 @@ export class DataScope {
return false; return false;
} }
assignSchema(target: any, schema: any): any {
// key相同type也相同
if (target.type && target.type === schema.type) {
if (target.type === 'array') {
// 先只考虑items不考虑contains
if (target.items) {
if (Array.isArray(target.items)) {
if (schema.items) {
if (Array.isArray(schema.items)) {
// 如果都是数组,就后者覆盖前者
return schema.items;
} else {
// 否则,追加
return {
...target,
items: [...target.items, schema.items]
};
}
} else {
return {
...target,
...schema
};
}
} else {
// 非数组则merge
return {
...target,
items: this.assignSchema(target.items, schema.items)
};
}
} else {
return schema;
}
} else if (target.type === 'object' && target.properties) {
let properties: any = {};
// 合并属性
for (let key of Array.from(
new Set([
...Object.keys(target.properties),
...Object.keys(schema.properties)
])
)) {
const value = target.properties[key];
if (value) {
properties[key] = schema.properties[key]
? this.assignSchema(value, schema.properties[key])
: value;
} else {
properties[key] = schema.properties[key];
}
}
return {
...target,
properties
};
} else {
return schema;
}
} else {
// key相同、type不同
if (Array.isArray(target.oneOf)) {
return {
...target, // 先做个显示过度因formula还没支持oneOf
oneOf: [...target.oneOf, schema]
};
} else {
return {
...target, // 先做个显示过度因formula还没支持oneOf
oneOf: [target, schema]
};
}
}
}
getMergedSchema() { getMergedSchema() {
const mergedSchema: any = { const mergedSchema: any = {
type: 'object', type: 'object',
properties: {} properties: {}
}; };
// todo 以后再来细化这一块,先粗略的写个大概
this.schemas.forEach(schema => { this.schemas.forEach(schema => {
const properties: any = schema.properties || {}; const properties: any = schema.properties || {};
Object.keys(properties).forEach(key => { Object.keys(properties).forEach(key => {
const value = properties[key]; const value = properties[key];
if (mergedSchema.properties[key]) { if (mergedSchema.properties[key]) {
if (Array.isArray(mergedSchema.properties[key].oneOf)) { mergedSchema.properties[key] = this.assignSchema(
mergedSchema.properties[key].oneOf.push(); mergedSchema.properties[key],
} else if ( value
mergedSchema.properties[key].type && );
mergedSchema.properties[key].type !== value.type
) {
mergedSchema.properties[key] = {
oneOf: [mergedSchema.properties[key], value]
};
}
} else { } else {
mergedSchema.properties[key] = value; mergedSchema.properties[key] = value;
} }
@ -132,19 +201,14 @@ export class DataScope {
options: Array<any>, options: Array<any>,
schema: JSONSchema, schema: JSONSchema,
path: string = '', path: string = '',
key: string = '', key: string = ''
/** 是否数组元素,数组元素的内容将获取每个成员的对应值 */
isArrayItem = false,
/** 不是所有的都可以选择,但不影响子元素 */
disabled?: boolean
) { ) {
// todo 支持 oneOf, anyOf // todo 支持 oneOf, anyOf
const option: any = { const option: any = {
label: schema.title || key, label: schema.title || key,
value: path, value: path,
type: schema.type, type: schema.type,
tag: schema.description ?? schema.type, tag: schema.description ?? schema.type
disabled
}; };
options.push(option); options.push(option);
@ -155,15 +219,12 @@ export class DataScope {
keys.forEach(key => { keys.forEach(key => {
const child: any = schema.properties![key]; const child: any = schema.properties![key];
const newPath = isArrayItem ? `ARRAYMAP(${path}, item => item.${key})` : (path + (path ? '.' : '') + key);
this.buildOptions( this.buildOptions(
option.children, option.children,
child, child,
newPath, path + (path ? '.' : '') + key,
key, key
isArrayItem,
false
); );
}); });
} else if (schema.type === 'array' && schema.items) { } else if (schema.type === 'array' && schema.items) {
@ -173,25 +234,17 @@ export class DataScope {
option.children, option.children,
{ {
title: '成员', title: '成员',
...(schema.items as any) ...(schema.items as any),
disabled: true
}, },
path, path,
'items', 'items'
true,
true
); );
this.buildOptions( option.children = mapTree(option.children, item => ({
option.children, ...item,
{ disabled: true
title: '总数', }));
type: 'number'
},
path + (path ? '.' : '') + 'length',
'length',
true,
isArrayItem
);
} }
} }
@ -219,4 +272,8 @@ export class DataScope {
} }
return null; return null;
} }
getSchemaById(id: string) {
return this.schemas?.find(item => item.$id === id);
}
} }

View File

@ -931,32 +931,40 @@ export function isObject(curObj: any) {
return isObject; return isObject;
} }
export function jsonToJsonSchema(json: any = {}) { export function jsonToJsonSchema(
json: any = {},
titleBuilder?: (type: string, key: string) => string
) {
const jsonschema: any = { const jsonschema: any = {
type: 'object', type: 'object',
properties: {} properties: {}
}; };
Object.keys(json).forEach(key => { Object.keys(json).forEach(key => {
const value = json[key]; const value = json[key];
const type = typeof value; const type = Array.isArray(value) ? 'array' : typeof value;
if (~['string', 'number'].indexOf(type)) { if (~['string', 'number'].indexOf(type)) {
jsonschema.properties[key] = { jsonschema.properties[key] = {
type: type, type,
title: key title: titleBuilder?.(type, key) || key
}; };
} else if (type === 'object' && value) { } else if (~['object', 'array'].indexOf(type) && value) {
jsonschema.properties[key] = { jsonschema.properties[key] = {
type: 'object', type,
title: key title: titleBuilder?.(type, key) || key,
...(type === 'object'
? jsonToJsonSchema(value, titleBuilder)
: {items: jsonToJsonSchema(value[0], titleBuilder)})
}; };
} else { } else {
jsonschema.properties[key] = { jsonschema.properties[key] = {
type: '', type: '',
title: key title: titleBuilder?.(type, key) || key
}; };
} }
}); });
return jsonschema; return jsonschema;
} }

View File

@ -5,6 +5,7 @@ import React from 'react';
import { import {
getI18nEnabled, getI18nEnabled,
jsonToJsonSchema,
registerEditorPlugin, registerEditorPlugin,
tipedLabel tipedLabel
} from 'amis-editor-core'; } from 'amis-editor-core';
@ -23,13 +24,14 @@ import {
} from 'amis-editor-core'; } from 'amis-editor-core';
import {defaultValue, getSchemaTpl} from 'amis-editor-core'; import {defaultValue, getSchemaTpl} from 'amis-editor-core';
import {isObject, JSONPipeIn} from 'amis-editor-core'; import {isObject, JSONPipeIn} from 'amis-editor-core';
import {setVariable} from 'amis-core'; import {setVariable, someTree} from 'amis-core';
import type {ActionSchema} from 'amis'; import type {ActionSchema} from 'amis';
import type {CRUDCommonSchema} from 'amis'; import type {CRUDCommonSchema} from 'amis';
import {getEnv} from 'mobx-state-tree'; import {getEnv} from 'mobx-state-tree';
import {EditorNodeType, RendererPluginAction} from 'amis-editor-core'; import type {EditorNodeType, RendererPluginAction} from 'amis-editor-core';
import {normalizeApi} from 'amis-core'; import {normalizeApi} from 'amis-core';
import isPlainObject from 'lodash/isPlainObject'; import isPlainObject from 'lodash/isPlainObject';
import omit from 'lodash/omit';
interface ColumnItem { interface ColumnItem {
label: string; label: string;
@ -1745,7 +1747,92 @@ export class CRUDPlugin extends BasePlugin {
return; return;
} }
return child.info.plugin.buildDataSchemas(child, undefined, trigger); let childSchame = await child.info.plugin.buildDataSchemas(
child,
undefined,
trigger
);
// 兼容table的rows并自行merged异步数据
if (child.type === 'table') {
let cellProperties = {};
const columns: EditorNodeType = child.children.find(
item => item.isRegion && item.region === 'columns'
);
if (trigger) {
const isColumnChild = someTree(
columns?.children,
item => item.id === trigger.id
);
// merge异步数据中的单列成员因为rendererBeforeDispatchEvent无法区分是否需要单列成员
if (isColumnChild) {
const scope = this.manager.dataSchema.getScope(
`${node.id}-${node.type}`
);
const menberProps = (
scope.getSchemaById('crudFetchInitedData')?.properties?.items as any
)?.items?.properties;
cellProperties = {
...menberProps,
...omit(
childSchame.properties,
'rows',
'selectedItems',
'unSelectedItems'
)
};
}
}
childSchame = {
$id: childSchame.$id,
type: childSchame.type,
properties: {
...cellProperties,
items: childSchame.properties.rows,
selectedItems: childSchame.properties.selectedItems,
unSelectedItems: childSchame.properties.unSelectedItems,
count: {
type: 'number',
title: '总行数'
},
page: {
type: 'number',
title: '当前页码'
}
}
};
}
return childSchame;
}
rendererBeforeDispatchEvent(node: EditorNodeType, e: any, data: any) {
if (e === 'fetchInited') {
const scope = this.manager.dataSchema.getScope(`${node.id}-${node.type}`);
const jsonschema: any = {
$id: 'crudFetchInitedData',
type: 'object',
...jsonToJsonSchema(
omit(data, 'selectedItems', 'unSelectedItems'),
(type: string, key: string) => {
if (type === 'array' && key === 'items') {
return '数据列表';
}
if (type === 'number' && key === 'count') {
return '总行数';
}
return key;
}
)
};
scope?.removeSchema(jsonschema.$id);
scope?.addSchema(jsonschema);
}
} }
/** crud 不同 mode 之间转换时候,主体的转换 */ /** crud 不同 mode 之间转换时候,主体的转换 */

View File

@ -68,13 +68,13 @@ export class ServicePlugin extends BasePlugin {
}, },
{ {
eventName: 'fetchInited', eventName: 'fetchInited',
eventLabel: '初始化数据接口请求', eventLabel: '初始化数据接口请求成',
description: '远程初始化数据接口请求时触发' description: '远程初始化数据接口请求成时触发'
}, },
{ {
eventName: 'fetchSchemaInited', eventName: 'fetchSchemaInited',
eventLabel: '初始化Schema接口请求', eventLabel: '初始化Schema接口请求成',
description: '远程初始化Schema接口请求时触发' description: '远程初始化Schema接口请求成时触发'
} }
]; ];

View File

@ -27,7 +27,11 @@ import {
getEventControlConfig, getEventControlConfig,
getArgsWrapper getArgsWrapper
} from '../renderer/event-control/helper'; } from '../renderer/event-control/helper';
import {schemaArrayFormat, schemaToArray, resolveArrayDatasource} from '../util'; import {
schemaArrayFormat,
schemaToArray,
resolveArrayDatasource
} from '../util';
export class TablePlugin extends BasePlugin { export class TablePlugin extends BasePlugin {
// 关联渲染器名字 // 关联渲染器名字
@ -289,7 +293,51 @@ export class TablePlugin extends BasePlugin {
properties: { properties: {
'event.data.item': { 'event.data.item': {
type: 'object', type: 'object',
title: '行点击数据' title: '当前行数据'
},
'event.data.index': {
type: 'number',
title: '当前行索引'
}
}
}
]
},
{
eventName: 'rowMouseEnter',
eventLabel: '鼠标移入行事件',
description: '移入整行时触发',
dataSchema: [
{
type: 'object',
properties: {
'event.data.item': {
type: 'object',
title: '当前行数据'
},
'event.data.index': {
type: 'number',
title: '当前行索引'
}
}
}
]
},
{
eventName: 'rowMouseLeave',
eventLabel: '鼠标移出行事件',
description: '移出整行时触发',
dataSchema: [
{
type: 'object',
properties: {
'event.data.item': {
type: 'object',
title: '当前行数据'
},
'event.data.index': {
type: 'number',
title: '当前行索引'
} }
} }
} }
@ -680,15 +728,27 @@ export class TablePlugin extends BasePlugin {
item => item.isRegion && item.region === 'columns' item => item.isRegion && item.region === 'columns'
); );
for (let current of columns?.children) { // todo以下的处理无效需要cell实现才能深层细化
const schema = current.schema; // for (let current of columns?.children) {
if (schema.name) { // const schema = current.schema;
itemsSchema.properties[schema.name] = current.info?.plugin // if (schema.name) {
?.buildDataSchemas // itemsSchema.properties[schema.name] = current.info?.plugin
? await current.info.plugin.buildDataSchemas(current, region) // ?.buildDataSchemas
: { // ? await current.info.plugin.buildDataSchemas(current, region)
// : {
// type: 'string',
// title: schema.label || schema.name
// };
// }
// }
// 一期先简单处理上面todo实现之后这里可以废弃
// table 无法根据source确定异步数据来源因此不能在table层做异步数据列的收集
for (let current of node.schema?.columns) {
if (current.name) {
itemsSchema.properties[current.name] = {
type: 'string', type: 'string',
title: schema.label || schema.name title: current.label || current.name
}; };
} }
} }
@ -716,6 +776,16 @@ export class TablePlugin extends BasePlugin {
type: 'array', type: 'array',
title: '数据列表', title: '数据列表',
items: itemsSchema items: itemsSchema
},
selectedItems: {
type: 'array',
title: '已选中行'
// items: itemsSchema
},
unSelectedItems: {
type: 'array',
title: '未选中行'
// items: itemsSchema
} }
} }
}; };

View File

@ -775,7 +775,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
const ctx = createObject(store.mergedData, { const ctx = createObject(store.mergedData, {
...selectedItems[0], ...selectedItems[0],
currentPageData: store.mergedData.items.concat(), currentPageData: (store.mergedData?.items || []).concat(),
rows: selectedItems, rows: selectedItems,
items: selectedItems, items: selectedItems,
selectedItems, selectedItems,
@ -865,7 +865,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
}, },
false, false,
true, true,
this.props.initFetch !== false this.props.initFetch !== false,
true
); );
store.setPristineQuery(); store.setPristineQuery();
@ -910,7 +911,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
values: Record<string, any>, values: Record<string, any>,
jumpToFirstPage: boolean = true, jumpToFirstPage: boolean = true,
replaceLocation: boolean = false, replaceLocation: boolean = false,
search: boolean = true search: boolean = true,
isInit: boolean = false
) { ) {
const { const {
store, store,
@ -942,8 +944,15 @@ export default class CRUD extends React.Component<CRUDProps, any> {
perPageField perPageField
); );
this.lastQuery = store.query; this.lastQuery = store.query;
search && search &&
this.search(undefined, undefined, undefined, loadDataOnceFetchOnFilter); this.search(
undefined,
undefined,
undefined,
loadDataOnceFetchOnFilter,
isInit
);
} }
handleBulkGo( handleBulkGo(
@ -1110,7 +1119,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
values?: any, values?: any,
silent?: boolean, silent?: boolean,
clearSelection?: boolean, clearSelection?: boolean,
forceReload = false forceReload = false,
isInit: boolean = false
) { ) {
const { const {
store, store,
@ -1130,7 +1140,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
loadDataOnce, loadDataOnce,
loadDataOnceFetchOnFilter, loadDataOnceFetchOnFilter,
source, source,
columns columns,
dispatchEvent
} = this.props; } = this.props;
// reload 需要清空用户选择。 // reload 需要清空用户选择。
@ -1175,7 +1186,26 @@ export default class CRUD extends React.Component<CRUDProps, any> {
columns: store.columns ?? columns columns: store.columns ?? columns
}) })
.then(value => { .then(value => {
const {page, lastPage} = store; const {page, lastPage, data, error, msg} = store;
if (isInit) {
// 初始化请求完成
const rendererEvent = dispatchEvent?.(
'fetchInited',
createObject(this.props.data, {
...data,
__response: {
error,
msg
}
})
);
if (rendererEvent?.prevented) {
return;
}
}
// 空列表 且 页数已经非法超出,则跳转到最后的合法页数 // 空列表 且 页数已经非法超出,则跳转到最后的合法页数
if ( if (
!store.data.items.length && !store.data.items.length &&
@ -1537,8 +1567,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
newItems.splice(0, newItems.length - 1) newItems.splice(0, newItems.length - 1)
); );
} }
store.setSelectedItems(newItems); store.updateSelectData(newItems, newUnSelectedItems);
store.setUnSelectedItems(newUnSelectedItems);
onSelect && onSelect(newItems, newUnSelectedItems); onSelect && onSelect(newItems, newUnSelectedItems);
} }
@ -1701,7 +1731,6 @@ export default class CRUD extends React.Component<CRUDProps, any> {
let bulkBtns: Array<ActionSchema> = []; let bulkBtns: Array<ActionSchema> = [];
let itemBtns: Array<ActionSchema> = []; let itemBtns: Array<ActionSchema> = [];
const ctx = createObject(store.mergedData, { const ctx = createObject(store.mergedData, {
currentPageData: (store.mergedData?.items || []).concat(), currentPageData: (store.mergedData?.items || []).concat(),
rows: selectedItems.concat(), rows: selectedItems.concat(),
@ -2414,4 +2443,13 @@ export class CRUDRenderer extends CRUD {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.close(target); scoped.close(target);
} }
setData(values: object, replace?: boolean) {
return this.props.store.updateData(values, undefined, replace);
}
getData() {
const {store, data} = this.props;
return store.getData(data);
}
} }

View File

@ -910,6 +910,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
); );
} }
store.updateSelectData(newItems, newUnSelectedItems); store.updateSelectData(newItems, newUnSelectedItems);
onSelect && onSelect(newItems); onSelect && onSelect(newItems);
} }

View File

@ -512,10 +512,13 @@ export default class Service extends React.Component<ServiceProps> {
const data = result?.hasOwnProperty('ok') ? result.data ?? {} : result; const data = result?.hasOwnProperty('ok') ? result.data ?? {} : result;
const {onBulkChange, dispatchEvent, store} = this.props; const {onBulkChange, dispatchEvent, store} = this.props;
dispatchEvent?.('fetchInited', { dispatchEvent?.(
'fetchInited',
createObject(this.props.data, {
...data, ...data,
__response: {msg: store.msg, error: store.error} __response: {msg: store.msg, error: store.error}
}); })
);
if (!isEmpty(data) && onBulkChange) { if (!isEmpty(data) && onBulkChange) {
onBulkChange(data); onBulkChange(data);

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {ClassNamesFn} 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';
@ -26,6 +26,15 @@ export interface TableBodyProps extends LocaleProps {
props: any props: any
) => React.ReactNode; ) => React.ReactNode;
onCheck: (item: IRow, value: boolean, shift?: boolean) => void; onCheck: (item: IRow, value: boolean, shift?: boolean) => void;
onRowClick: (item: IRow, index: number) => Promise<RendererEvent<any> | void>;
onRowMouseEnter: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
onRowMouseLeave: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
onQuickChange?: ( onQuickChange?: (
item: IRow, item: IRow,
values: object, values: object,
@ -69,12 +78,14 @@ export class TableBody extends React.Component<TableBodyProps> {
footable, footable,
ignoreFootableContent, ignoreFootableContent,
footableColumns, footableColumns,
itemAction itemAction,
onRowClick,
onRowMouseEnter,
onRowMouseLeave
} = this.props; } = this.props;
return rows.map((item: IRow, rowIndex: number) => { return rows.map((item: IRow, rowIndex: number) => {
const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null; const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null;
const doms = [ const doms = [
<TableRow <TableRow
{...itemProps} {...itemProps}
@ -99,6 +110,9 @@ export class TableBody extends React.Component<TableBodyProps> {
onCheck={onCheck} onCheck={onCheck}
// todo 先注释 quickEditEnabled={item.depth === 1} // todo 先注释 quickEditEnabled={item.depth === 1}
onQuickChange={onQuickChange} onQuickChange={onQuickChange}
onRowClick={onRowClick}
onRowMouseEnter={onRowMouseEnter}
onRowMouseLeave={onRowMouseLeave}
{...rowProps} {...rowProps}
/> />
]; ];
@ -124,6 +138,9 @@ export class TableBody extends React.Component<TableBodyProps> {
render={render} render={render}
onAction={onAction} onAction={onAction}
onCheck={onCheck} onCheck={onCheck}
onRowClick={onRowClick}
onRowMouseEnter={onRowMouseEnter}
onRowMouseLeave={onRowMouseLeave}
footableMode footableMode
footableColSpan={columns.length} footableColSpan={columns.length}
onQuickChange={onQuickChange} onQuickChange={onQuickChange}

View File

@ -5,7 +5,8 @@ import {
SchemaNode, SchemaNode,
ActionObject, ActionObject,
LocaleProps, LocaleProps,
OnEventProps OnEventProps,
RendererEvent
} from 'amis-core'; } from 'amis-core';
import {TableBody} from './TableBody'; import {TableBody} from './TableBody';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
@ -42,6 +43,15 @@ export interface TableContentProps extends LocaleProps {
props: any props: any
) => React.ReactNode; ) => React.ReactNode;
onCheck: (item: IRow, value: boolean, shift?: boolean) => void; onCheck: (item: IRow, value: boolean, shift?: boolean) => void;
onRowClick: (item: IRow, index: number) => Promise<RendererEvent<any> | void>;
onRowMouseEnter: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
onRowMouseLeave: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
onQuickChange?: ( onQuickChange?: (
item: IRow, item: IRow,
values: object, values: object,
@ -128,6 +138,9 @@ export class TableContent extends React.Component<TableContentProps> {
renderHeadCell, renderHeadCell,
renderCell, renderCell,
onCheck, onCheck,
onRowClick,
onRowMouseEnter,
onRowMouseLeave,
rowClassName, rowClassName,
onQuickChange, onQuickChange,
footable, footable,
@ -233,6 +246,9 @@ export class TableContent extends React.Component<TableContentProps> {
render={render} render={render}
renderCell={renderCell} renderCell={renderCell}
onCheck={onCheck} onCheck={onCheck}
onRowClick={onRowClick}
onRowMouseEnter={onRowMouseEnter}
onRowMouseLeave={onRowMouseLeave}
onQuickChange={onQuickChange} onQuickChange={onQuickChange}
footable={footable} footable={footable}
footableColumns={footableColumns} footableColumns={footableColumns}

View File

@ -1,12 +1,21 @@
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'; import type {IColumn, IRow} from 'amis-core/lib/store/table';
import {RendererProps} from 'amis-core'; import {RendererEvent, RendererProps} from 'amis-core';
import {Action} from '../Action'; import {Action} from '../Action';
import {isClickOnInput, createObject} from 'amis-core'; import {isClickOnInput, createObject} from 'amis-core';
interface TableRowProps extends Pick<RendererProps, 'render'> { interface TableRowProps extends Pick<RendererProps, 'render'> {
onCheck: (item: IRow) => Promise<void>; onCheck: (item: IRow) => Promise<void>;
onRowClick: (item: IRow, index: number) => Promise<RendererEvent<any> | void>;
onRowMouseEnter: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
onRowMouseLeave: (
item: IRow,
index: number
) => Promise<RendererEvent<any> | void>;
classPrefix: string; classPrefix: string;
renderCell: ( renderCell: (
region: string, region: string,
@ -39,26 +48,13 @@ export class TableRow extends React.Component<TableRowProps> {
} }
handleMouseEnter(e: React.MouseEvent<HTMLTableRowElement>) { handleMouseEnter(e: React.MouseEvent<HTMLTableRowElement>) {
const {item, itemIndex, data, dispatchEvent} = this.props; const {item, itemIndex, onRowMouseEnter} = this.props;
onRowMouseEnter?.(item?.data, itemIndex);
dispatchEvent(
'rowMouseEnter',
createObject(data, {
item: item?.data,
index: itemIndex
})
);
} }
handleMouseLeave(e: React.MouseEvent<HTMLTableRowElement>) { handleMouseLeave(e: React.MouseEvent<HTMLTableRowElement>) {
const {item, itemIndex, data, dispatchEvent} = this.props; const {item, itemIndex, onRowMouseLeave} = this.props;
dispatchEvent( onRowMouseLeave?.(item?.data, itemIndex);
'rowMouseLeave',
createObject(data, {
item: item?.data,
index: itemIndex
})
);
} }
// 定义点击一行的行为,通过 itemAction配置 // 定义点击一行的行为,通过 itemAction配置
@ -70,24 +66,10 @@ export class TableRow extends React.Component<TableRowProps> {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const { const {itemAction, onAction, item, itemIndex, onCheck, onRowClick} =
itemAction, this.props;
onAction,
item,
itemIndex,
data,
dispatchEvent,
onCheck
} = this.props;
const rendererEvent = await dispatchEvent( const rendererEvent = await onRowClick?.(item?.data, itemIndex);
'rowClick',
createObject(data, {
rowItem: item?.data, // 保留rowItem 可能有用户已经在用 兼容之前的版本
item: item?.data,
index: itemIndex
})
);
if (rendererEvent?.prevented) { if (rendererEvent?.prevented) {
return; return;

View File

@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import {ScopedContext, IScopedContext, SchemaExpression} from 'amis-core'; import {
ScopedContext,
IScopedContext,
SchemaExpression,
extendObject
} from 'amis-core';
import {Renderer, RendererProps} from 'amis-core'; import {Renderer, RendererProps} from 'amis-core';
import {SchemaNode, ActionObject, Schema} from 'amis-core'; import {SchemaNode, ActionObject, Schema} from 'amis-core';
import forEach from 'lodash/forEach'; import forEach from 'lodash/forEach';
@ -547,6 +552,9 @@ export default class Table extends React.Component<TableProps, object> {
this.handleMouseLeave = this.handleMouseLeave.bind(this); this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.subFormRef = this.subFormRef.bind(this); this.subFormRef = this.subFormRef.bind(this);
this.handleColumnToggle = this.handleColumnToggle.bind(this); this.handleColumnToggle = this.handleColumnToggle.bind(this);
this.handleRowClick = this.handleRowClick.bind(this);
this.handleRowMouseEnter = this.handleRowMouseEnter.bind(this);
this.handleRowMouseLeave = this.handleRowMouseLeave.bind(this);
this.updateAutoFillHeight = this.updateAutoFillHeight.bind(this); this.updateAutoFillHeight = this.updateAutoFillHeight.bind(this);
@ -910,16 +918,51 @@ export default class Table extends React.Component<TableProps, object> {
this.syncSelected(); this.syncSelected();
} }
handleRowClick(item: IRow, index: number) {
const {dispatchEvent, store, data} = this.props;
return dispatchEvent(
'rowClick',
createObject(data, {
rowItem: item, // 保留rowItem 可能有用户已经在用 兼容之前的版本
item,
index
})
);
}
handleRowMouseEnter(item: IRow, index: number) {
const {dispatchEvent, store, data} = this.props;
return dispatchEvent(
'rowMouseEnter',
createObject(data, {
item,
index
})
);
}
handleRowMouseLeave(item: IRow, index: number) {
const {dispatchEvent, store, data} = this.props;
return dispatchEvent(
'rowMouseLeave',
createObject(data, {
item,
index
})
);
}
async handleCheckAll() { async handleCheckAll() {
const {store, data, dispatchEvent} = this.props; const {store, data, dispatchEvent} = this.props;
const items = store.rows.map((row: any) => row.data);
const items = store.getSelectedRows().map(item => item.data); const selectedItems = store.getSelectedRows().map(item => item.data);
const rendererEvent = await dispatchEvent( const rendererEvent = await dispatchEvent(
'selectedChange', 'selectedChange',
createObject(data, { createObject(data, {
selectedItems: store.allChecked ? [] : items, selectedItems: store.allChecked ? [] : selectedItems,
unSelectedItems: store.allChecked ? items : [] unSelectedItems: store.allChecked ? selectedItems : [],
items
}) })
); );
@ -2236,7 +2279,13 @@ export default class Table extends React.Component<TableProps, object> {
// 操作列不下发loading否则会导致操作栏里面的所有按钮都出现loading // 操作列不下发loading否则会导致操作栏里面的所有按钮都出现loading
loading: column.type === 'operation' ? false : props.loading, loading: column.type === 'operation' ? false : props.loading,
btnDisabled: store.dragging, btnDisabled: store.dragging,
data: item.locals, data: this.props.selectable
? extendObject(item.locals, {
// 只有table时也可以获取选中行
selectedItems: store.selectedRows.map(item => item.data),
unSelectedItems: store.unSelectedRows.map(item => item.data)
})
: item.locals,
value: column.name value: column.name
? resolveVariable( ? resolveVariable(
column.name, column.name,
@ -2430,6 +2479,9 @@ export default class Table extends React.Component<TableProps, object> {
render={render} render={render}
renderCell={this.renderCell} renderCell={this.renderCell}
onCheck={this.handleCheck} onCheck={this.handleCheck}
onRowClick={this.handleRowClick}
onRowMouseEnter={this.handleRowMouseEnter}
onRowMouseLeave={this.handleRowMouseLeave}
onQuickChange={store.dragging ? undefined : this.handleQuickChange} onQuickChange={store.dragging ? undefined : this.handleQuickChange}
footable={store.footable} footable={store.footable}
ignoreFootableContent ignoreFootableContent
@ -2817,6 +2869,7 @@ export default class Table extends React.Component<TableProps, object> {
{ {
...this.props, ...this.props,
selectedItems: store.selectedRows.map(item => item.data), selectedItems: store.selectedRows.map(item => item.data),
unSelectedItems: store.unSelectedRows.map(item => item.data),
items: store.rows.map(item => item.data) items: store.rows.map(item => item.data)
}, },
this.renderToolbar this.renderToolbar
@ -2916,6 +2969,9 @@ export default class Table extends React.Component<TableProps, object> {
renderHeadCell={this.renderHeadCell} renderHeadCell={this.renderHeadCell}
renderCell={this.renderCell} renderCell={this.renderCell}
onCheck={this.handleCheck} onCheck={this.handleCheck}
onRowClick={this.handleRowClick}
onRowMouseEnter={this.handleRowMouseEnter}
onRowMouseLeave={this.handleRowMouseLeave}
onQuickChange={store.dragging ? undefined : this.handleQuickChange} onQuickChange={store.dragging ? undefined : this.handleQuickChange}
footable={store.footable} footable={store.footable}
footableColumns={store.footableColumns} footableColumns={store.footableColumns}
@ -3091,6 +3147,19 @@ export class TableRenderer extends Table {
return scoped.reload(subPath, ctx); return scoped.reload(subPath, ctx);
} }
} }
setData(values: any, replace?: boolean) {
const data = {
...values,
rows: values.rows ?? values.items // 做个兼容
};
return this.props.store.updateData(data, undefined, replace);
}
getData() {
const {store, data} = this.props;
return store.getData(data);
}
} }
export {TableCell}; export {TableCell};