Merge pull request #9955 from 2betop/feat-crud-quickSave-events

feat: 补充 crud 的快速保存与拖拽顺序保存事件 Close: #9877
This commit is contained in:
hsm-lv 2024-04-03 14:44:19 +08:00 committed by GitHub
commit 5b8b054f03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 29 deletions

View File

@ -3928,18 +3928,24 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据,
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细查看[事件动作](../../docs/concepts/event-action)。 当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`或`${event.data.[事件参数名]}`来获取事件产生的数据,详细查看[事件动作](../../docs/concepts/event-action)。
| 事件名称 | 事件参数 | 说明 | | 事件名称 | 事件参数 | 说明 |
| -------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| fetchInited | `responseData` 接口数据返回 <br /> `responseStatus` 接口返回状态 <br /> `responseMsg` 响应消息 | 远程初始化数据接口请求完成时触发 | | fetchInited | `responseData` 接口数据返回 <br /> `responseStatus` 接口返回状态 <br /> `responseMsg` 响应消息 | 远程初始化数据接口请求完成时触发 |
| selectedChange | `selectedItems: item[]` 已选择行<br/>`unSelectedItems: item[]` 未选择行 | 手动选择表格项时触发 | | quickSaveSucc | `result` 接口数据返回 <br /> `rows: object[]` 修改了的行集合 <br /> `rowsDiff: object[]` 与 rows 不同的地方时,对象中只有修改的部分和主键字段 <br /> `indexes: Array<string>` 修改的行索引,如果是树形模式,下标是字符串路劲如 `0.1` <br /> `rowsOrigin: object[]` 原始数据 | 快速编辑完后保存成功触发 |
| columnSort | `orderBy: string` 列排序列名<br/>`orderDir: string` 列排序值 | 点击列排序时触发 | | quickSaveFail | `error` 错误原因 | 快速编辑完后保存失败 |
| columnFilter | `filterName: string` 列筛选列名<br/>`filterValue: string \| undefined` 列筛选值 | 点击列筛选时触发,点击重置后事件参数`filterValue`为`undefined` | | quickSaveItemSucc | `result` 接口数据返回 <br /> `item: object` 当亲修改的那行数据 <br /> `modified:object` 质只包含修改的部分 <br /> `origin: object` 原始数据 | 快速编辑单条保存成功后触发 |
| columnSearch | `searchName: string` 列搜索列名<br/>`searchValue: object` 列搜索数据 | 点击列搜索时触发 | | quickSaveItemFail | `error` 错误原因 | 快速编辑单条保存失败后触发 |
| orderChange | `movedItems: item[]` 已排序数据 | 手动拖拽行排序时触发 | | saveOrderSucc | `result` 接口数据返回 <br /> `其他` 请参考 [拖拽排序](#拖拽排序) 章节说明 | 拖拽排序保存成功后触发 |
| columnToggled | `columns: item[]` 当前显示的列配置数据 | 点击自定义列时触发 | | saveOrderFail | `error` 错误原因 | 拖拽排序保存失败后触发 |
| rowClick | `item: object` 行点击数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 点击整行时触发 | | selectedChange | `selectedItems: item[]` 已选择行<br/>`unSelectedItems: item[]` 未选择行 | 手动选择表格项时触发 |
| rowMouseEnter | `item: object` 行移入数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 移入整行时触发 | | columnSort | `orderBy: string` 列排序列名<br/>`orderDir: string` 列排序值 | 点击列排序时触发 |
| rowMouseLeave | `item: object` 行移出数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 移出整行时触发 | | columnFilter | `filterName: string` 列筛选列名<br/>`filterValue: string \| undefined` 列筛选值 | 点击列筛选时触发,点击重置后事件参数`filterValue`为`undefined` |
| columnSearch | `searchName: string` 列搜索列名<br/>`searchValue: object` 列搜索数据 | 点击列搜索时触发 |
| orderChange | `movedItems: item[]` 已排序数据 | 手动拖拽行排序时触发 |
| columnToggled | `columns: item[]` 当前显示的列配置数据 | 点击自定义列时触发 |
| rowClick | `item: object` 行点击数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 点击整行时触发 |
| rowMouseEnter | `item: object` 行移入数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 移入整行时触发 |
| rowMouseLeave | `item: object` 行移出数据<br/>`index: number` 行索引 <br />`indexPath: string` 行索引路径 | 移出整行时触发 |
### selectedChange ### selectedChange

View File

@ -49,6 +49,11 @@ interface MatchFunc {
class ServerError extends Error { class ServerError extends Error {
type = 'ServerError'; type = 'ServerError';
readonly response: any;
constructor(msg: string, response?: any) {
super(msg);
this.response = response;
}
} }
export const CRUDStore = ServiceStore.named('CRUDStore') export const CRUDStore = ServiceStore.named('CRUDStore')
@ -595,7 +600,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
} }
: undefined : undefined
); );
throw new ServerError(self.msg); throw new ServerError(self.msg, json);
} else { } else {
self.updateMessage( self.updateMessage(
(api as ApiObject)?.messages?.success ?? (api as ApiObject)?.messages?.success ??
@ -615,11 +620,17 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
: undefined : undefined
); );
} }
return json.data; // 补一个空对象是为了区别于被取消的请求
// 请求被取消时会返回 undefined
return json.data || {};
} catch (e) { } catch (e) {
if (!isAlive(self) || self.disposed) {
return;
}
self.markSaving(false); self.markSaving(false);
if (!isAlive(self) || self.disposed) { if (getEnv(self).isCancel(e)) {
return; return;
} }

View File

@ -1465,7 +1465,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
primaryField, primaryField,
env, env,
messages, messages,
reload reload,
dispatchEvent
} = this.props; } = this.props;
if (Array.isArray(rows)) { if (Array.isArray(rows)) {
@ -1491,18 +1492,41 @@ export default class CRUD extends React.Component<CRUDProps, any> {
data.unModifiedItems = unModifiedItems; data.unModifiedItems = unModifiedItems;
} }
store return store
.saveRemote(quickSaveApi, data, { .saveRemote(quickSaveApi, data, {
successMessage: messages && messages.saveFailed, successMessage: messages && messages.saveFailed,
errorMessage: messages && messages.saveSuccess errorMessage: messages && messages.saveSuccess
}) })
.then(() => { .then(async result => {
// 如果请求 cancel 了,会来到这里
if (!result) {
return;
}
const event = await dispatchEvent?.(
'quickSaveSucc',
extendObject(data, {
result: result
})
);
if (event?.prevented) {
return;
}
const finalReload = options?.reload ?? reload; const finalReload = options?.reload ?? reload;
finalReload return finalReload
? this.reloadTarget(filterTarget(finalReload, data), data) ? this.reloadTarget(filterTarget(finalReload, data), data)
: this.search(undefined, undefined, true, true); : this.search(undefined, undefined, true, true);
}) })
.catch(() => {}); .catch(async err => {
await dispatchEvent?.(
'quickSaveFail',
createObject(this.props.data, {
error: err
})
);
});
} else { } else {
if (!isEffectiveApi(quickSaveItemApi)) { if (!isEffectiveApi(quickSaveItemApi)) {
env && env.alert('CRUD quickSaveItemApi is required!'); env && env.alert('CRUD quickSaveItemApi is required!');
@ -1516,23 +1540,52 @@ export default class CRUD extends React.Component<CRUDProps, any> {
}); });
const sendData = createObject(data, rows); const sendData = createObject(data, rows);
store return store
.saveRemote(quickSaveItemApi, sendData) .saveRemote(quickSaveItemApi, sendData)
.then(() => { .then(async (result: any) => {
// 如果请求 cancel 了,会来到这里
if (!result) {
return;
}
const event = await dispatchEvent?.(
'quickSaveItemSucc',
extendObject(data, {
result: result
})
);
if (event?.prevented) {
return;
}
const finalReload = options?.reload ?? reload; const finalReload = options?.reload ?? reload;
finalReload return finalReload
? this.reloadTarget(filterTarget(finalReload, data), data) ? this.reloadTarget(filterTarget(finalReload, data), data)
: this.search(undefined, undefined, true, true); : this.search(undefined, undefined, true, true);
}) })
.catch(() => { .catch(async err => {
options?.resetOnFailed && this.control.reset(); options?.resetOnFailed && this.control.reset();
await dispatchEvent?.(
'quickSaveItemFail',
createObject(this.props.data, {
error: err
})
);
}); });
} }
} }
handleSaveOrder(moved: Array<object>, rows: Array<object>) { handleSaveOrder(moved: Array<object>, rows: Array<object>) {
const {store, saveOrderApi, orderField, primaryField, env, reload} = const {
this.props; store,
saveOrderApi,
orderField,
primaryField,
env,
reload,
dispatchEvent
} = this.props;
if (!saveOrderApi) { if (!saveOrderApi) {
env && env.alert('CRUD saveOrderApi is required!'); env && env.alert('CRUD saveOrderApi is required!');
@ -1632,14 +1685,38 @@ export default class CRUD extends React.Component<CRUDProps, any> {
)); ));
} }
isEffectiveApi(saveOrderApi, model) && return (
isEffectiveApi(saveOrderApi, model) &&
store store
.saveRemote(saveOrderApi, model) .saveRemote(saveOrderApi, model)
.then(() => { .then(async result => {
// 如果请求 cancel 了,会来到这里
if (!result) {
return;
}
const event = await dispatchEvent?.(
'saveOrderSucc',
extendObject(model, {
result: result
})
);
if (event?.prevented) {
return;
}
reload && this.reloadTarget(filterTarget(reload, model), model); reload && this.reloadTarget(filterTarget(reload, model), model);
this.search(undefined, undefined, true, true); this.search(undefined, undefined, true, true);
}) })
.catch(() => {}); .catch(async err => {
await dispatchEvent?.(
'saveOrderFail',
createObject(this.props.data, {
error: err
})
);
})
);
} }
handleSelect(items: Array<any>, unSelectedItems: Array<any>) { handleSelect(items: Array<any>, unSelectedItems: Array<any>) {