oak-frontend-base/lib/page.common.js

613 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFreshValue = exports.destroyNode = exports.execute = exports.loadMore = exports.refresh = exports.reRender = exports.onPathSet = void 0;
const assert_1 = require("oak-domain/lib/utils/assert");
const lodash_1 = require("oak-domain/lib/utils/lodash");
const relation_1 = require("oak-domain/lib/store/relation");
const filter_1 = require("oak-domain/lib/store/filter");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const row_1 = require("oak-domain/lib/utils/row");
function onPathSet(option, isPage) {
const { props, state } = this;
const { oakPath, oakId, oakFilters } = props;
const { singleton, entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { features } = this;
(0, assert_1.assert)(oakPath || path);
let oakPath2 = oakPath || path;
if (oakId && isPage) {
oakPath2 += `-${oakId}`;
}
if (entity) {
// entity在node生命周期中不可可变但sorter/filter/projection应当是运行时来决定
const entity2 = entity instanceof Function ? entity.call(this) : entity;
const projection2 = typeof projection === 'function' ? () => projection.call(this) : projection;
let filters2 = filters?.map((ele) => {
const { filter, '#name': name } = ele;
return {
filter: typeof filter === 'function' ? () => filter.call(this) : filter,
['#name']: name,
};
});
if (oakFilters) {
if (filters2) {
filters2 = filters2.concat(oakFilters);
}
else {
filters2 = oakFilters;
}
}
const sorters2 = sorters?.map((ele) => {
const { sorter, '#name': name } = ele;
return {
sorter: typeof sorter === 'function'
? () => sorter.call(this)
: sorter,
['#name']: name,
};
});
(0, assert_1.assert)(oakPath2, '没有正确的path信息请检查是否配置正确');
const { actions, cascadeActions } = option;
// 在这里适配宽窄屏处理getTotal/pagination不到运行时处理了 by Xc
let getTotal2;
if (getTotal) {
const { width } = this.props;
if (typeof getTotal === 'object') {
const { max, deviceWidth } = getTotal;
switch (deviceWidth) {
case 'all': {
getTotal2 = max;
break;
}
case 'mobile':
{
if (width === 'xs') {
getTotal2 = max;
}
break;
}
case 'pc':
default: {
if (width !== 'xs') {
getTotal2 = max;
}
break;
}
}
}
else {
if (width !== 'xs') {
getTotal2 = getTotal;
}
}
}
else {
// 不设置的默认情况宽屏取100窄屏不取
const { width } = this.props;
if (width !== 'xs') {
getTotal2 = 100;
}
}
let pagination2;
if (pagination) {
if (pagination instanceof Array) {
const { width } = this.props;
pagination2 = pagination.find(ele => width === 'xs' && ele.deviceWidth === 'mobile' || width !== 'xs' && ele.deviceWidth === 'pc');
}
else {
pagination2 = pagination;
}
}
features.runningTree.createNode({
path: oakPath2,
entity: entity2,
isList,
projection: projection2,
pagination: pagination2,
filters: filters2,
sorters: sorters2,
id: oakId,
actions: typeof actions === 'function' ? () => actions.call(this) : actions,
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
});
// 确保SetState生效这里改成异步
return {
oakEntity: entity2,
oakFullpath: oakPath2,
};
}
else {
// 创建virtualNode
features.runningTree.createNode({
path: oakPath2,
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
});
return {
oakFullpath: oakPath2,
};
}
}
exports.onPathSet = onPathSet;
function checkActionAttrsIfNecessary(cache, entity, action, id) {
if (typeof action === 'string') {
return action;
}
const { attrs } = action;
if (!attrs) {
return action;
}
// 处理一下#all
const idx = attrs.indexOf('#all');
if (idx >= 0) {
attrs.splice(idx, 1, ...Object.keys(cache.getSchema()[entity].attributes));
}
const attrs2 = cache.getLegalUpdateAttrs(entity, action.action, attrs, id);
return {
...action,
attrs: attrs2,
};
}
function checkActionsAndCascadeEntities(rows, option) {
const checkTypes = ['relation', 'row', 'logical'];
const actions = this.props.oakActions ? JSON.parse(this.props.oakActions) : (typeof option.actions === 'function' ? option.actions.call(this) : option.actions);
const legalActions = [];
// 这里向服务器请求相应的actionAuthcache层会对请求加以优化避免反复过频的不必要取数据
const destEntities = [];
if (actions) {
destEntities.push(this.state.oakEntity);
// 这里actions整体进行测试的性能应该要高于一个个去测试
for (const action of actions) {
if (rows instanceof Array) {
(0, assert_1.assert)(option.isList);
const filter = this.features.runningTree.getIntrinsticFilters(this.state.oakFullpath);
if (action === 'create' || typeof action === 'object' && action.action === 'create') {
// 创建对象的判定不落在具体行上但要考虑list上外键相关属性的限制
const data = typeof action === 'object' ? (0, lodash_1.cloneDeep)(action.data) : undefined;
if (this.checkOperation(this.state.oakEntity, { action: 'create', data, filter }, checkTypes) === true) {
legalActions.push(data ? checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, data.id) : action);
}
}
else {
const a2 = typeof action === 'object' ? action.action : action;
// 先尝试整体测试是否通过,再测试每一行
// todo这里似乎还能优化这些行一次性进行测试比单独测试的性能要高
if (filter && this.checkOperation(this.state.oakEntity, { action: a2, filter }, checkTypes) === true) {
rows.forEach((row) => {
if (row['#oakLegalActions']) {
row['#oakLegalActions'].push(checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, row.id));
}
else {
Object.assign(row, {
'#oakLegalActions': [checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, row.id)],
});
}
});
}
else {
rows.forEach((row) => {
const { id } = row;
if (this.checkOperation(this.state.oakEntity, { action: a2, filter: { id } }, checkTypes) === true) {
if (row['#oakLegalActions']) {
row['#oakLegalActions'].push(checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, row.id));
}
else {
Object.assign(row, {
'#oakLegalActions': [checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, row.id)],
});
}
}
});
}
}
}
else {
(0, assert_1.assert)(!option.isList);
if (action === 'create' || typeof action === 'object' && action.action === 'create') {
// 如果是create根据updateData来判定。create动作应该是自动创建行的并将$$createAt$$置为1
if (rows.$$createAt$$ === 1) {
const [{ operation }] = this.features.runningTree.getOperations(this.state.oakFullpath);
if (this.checkOperation(this.state.oakEntity, {
action: 'create',
data: operation.data,
}, checkTypes) === true) {
legalActions.push(checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, rows.id));
if (rows['#oakLegalActions']) {
rows['#oakLegalActions'].push(action);
}
else {
Object.assign(rows, {
'#oakLegalActions': [action],
});
}
}
}
}
else {
const a2 = typeof action === 'object' ? action.action : action;
const data = typeof action === 'object' ? action.data : undefined;
const filter1 = typeof action === 'object' ? action.filter : undefined;
const filter2 = this.features.runningTree.getIntrinsticFilters(this.state.oakFullpath);
const filter = (filter1 || filter2) && (0, filter_1.combineFilters)(this.state.oakEntity, this.features.cache.getSchema(), [
filter1, filter2
]);
if (filter && this.checkOperation(this.state.oakEntity, { action: a2, filter }, checkTypes) === true) {
const action2 = checkActionAttrsIfNecessary(this.features.cache, this.state.oakEntity, action, rows.id);
legalActions.push(action2);
if (rows['#oakLegalActions']) {
rows['#oakLegalActions'].push(action2);
}
else {
Object.assign(rows, {
'#oakLegalActions': [action2],
});
}
}
}
}
}
}
const cascadeActionDict = this.props.oakCascadeActions ? JSON.parse(this.props.oakCascadeActions) : ((option.cascadeActions && option.cascadeActions.call(this)));
if (cascadeActionDict) {
const addToRow = (entity, r, e, a) => {
if (!r['#oakLegalCascadeActions']) {
Object.assign(r, {
'#oakLegalCascadeActions': {
[e]: [checkActionAttrsIfNecessary(this.features.cache, entity, a, r.id)],
},
});
}
else if (!r['#oakLegalCascadeActions'][e]) {
Object.assign(r['#oakLegalCascadeActions'], {
[e]: [checkActionAttrsIfNecessary(this.features.cache, entity, a, r.id)],
});
}
else {
r['#oakLegalCascadeActions'][e].push(checkActionAttrsIfNecessary(this.features.cache, entity, a, r.id));
}
};
for (const e in cascadeActionDict) {
const cascadeActions = cascadeActionDict[e];
if (cascadeActions) {
const rel = (0, relation_1.judgeRelation)(this.features.cache.getSchema(), this.state.oakEntity, e);
(0, assert_1.assert)(rel instanceof Array, `${this.state.oakFullpath}上所定义的cascadeAction中的键值${e}不是一对多映射`);
destEntities.push(rel[0]);
for (const action of cascadeActions) {
if (rows instanceof Array) {
if (action === 'create' || typeof action === 'object' && action.action === 'create') {
rows.forEach((row) => {
const intrinsticData = rel[1] ? {
id: (0, uuid_1.generateNewId)(),
[rel[1]]: row.id,
} : { id: (0, uuid_1.generateNewId)(), entity: this.state.oakEntity, entityId: row.id };
if (typeof action === 'object') {
Object.assign(intrinsticData, action.data);
}
if (this.checkOperation(rel[0], {
action: 'create',
data: intrinsticData,
}, checkTypes) === true) {
addToRow(rel[0], row, e, action);
}
});
}
else {
const a2 = typeof action === 'object' ? action.action : action;
const filter = typeof action === 'object' ? action.filter : undefined;
const intrinsticFilter = rel[1] ? {
[rel[1].slice(0, rel[1].length - 2)]: this.features.runningTree.getIntrinsticFilters(this.state.oakFullpath),
} : {
[this.state.oakEntity]: this.features.runningTree.getIntrinsticFilters(this.state.oakFullpath),
};
const filter2 = (0, filter_1.combineFilters)(rel[0], this.features.cache.getSchema(), [filter, intrinsticFilter]);
// 先尝试整体测试是否通过,再测试每一行
// todo这里似乎还能优化这些行一次性进行测试比单独测试的性能要高
if (this.checkOperation(rel[0], {
action: a2,
filter: filter2,
}, checkTypes) === true) {
rows.forEach((row) => addToRow(rel[0], row, e, action));
}
else {
rows.forEach((row) => {
const { id } = row;
let intrinsticFilterRow = rel[1] ? {
[rel[1]]: id,
} : { entity: this.state.oakEntity, entityId: row.id };
if (filter) {
intrinsticFilterRow = (0, filter_1.combineFilters)(rel[0], this.features.cache.getSchema(), [filter, intrinsticFilterRow]);
}
if (this.checkOperation(rel[0], {
action: a2,
filter: intrinsticFilterRow,
}, checkTypes) === true) {
addToRow(rel[0], row, e, action);
}
});
}
}
}
else {
if (action === 'create' || typeof action === 'object' && action.action === 'create') {
const intrinsticData = rel[1] ? {
id: (0, uuid_1.generateNewId)(),
[rel[1]]: rows.id,
} : { id: (0, uuid_1.generateNewId)(), entity: this.state.oakEntity, entityId: rows.id };
if (typeof action === 'object') {
Object.assign(intrinsticData, action.data);
}
if (this.checkOperation(rel[0], {
action: 'create',
data: intrinsticData,
}, checkTypes) === true) {
addToRow(rel[0], rows, e, action);
}
}
else {
const a2 = typeof action === 'object' ? action.action : action;
const filter = typeof action === 'object' ? action.filter : undefined;
const intrinsticFilter = rel[1] ? {
[rel[1]]: rows.id,
} : { entity: this.state.oakEntity, entityId: rows.id };
const filter2 = (0, filter_1.combineFilters)(rel[0], this.features.cache.getSchema(), [filter, intrinsticFilter]);
// 先尝试整体测试是否通过,再测试每一行
// todo这里似乎还能优化这些行一次性进行测试比单独测试的性能要高
if (this.checkOperation(rel[0], {
action: a2,
filter: filter2,
}, checkTypes) === true) {
addToRow(rel[0], rows, e, action);
}
}
}
}
}
}
}
if (destEntities.length > 0) {
// 权限判断需要actionAuth的数据这里向cache请求时会根据keepFresh规则进行一定程度的优化。
this.features.cache.refresh('actionAuth', {
data: {
id: 1,
relationId: 1,
path: {
id: 1,
sourceEntity: 1,
destEntity: 1,
value: 1,
recursive: 1,
},
deActions: 1,
},
filter: {
path: {
destEntity: {
$in: destEntities,
}
}
}
}, undefined, undefined, {
useLocalCache: {
keys: destEntities,
gap: process.env.NODE_ENV === 'development' ? 60 * 1000 : 1200 * 1000,
onlyReturnFresh: true,
},
dontPublish: true,
}).then(({ data }) => {
// 这里利用cache的缓存行为如果没有返回新的actionAuth数据就不用再reRender了
if (data.length > 0) {
this.reRender();
}
});
}
return legalActions;
}
function reRender(option, extra) {
const { features } = this;
const { formData } = option;
const localeState = features.locales.getState();
const oakDirty = this.state.oakFullpath && this.features.runningTree.isDirty(this.state.oakFullpath);
if (this.state.oakEntity && this.state.oakFullpath) {
const oakLoadingMore = this.features.runningTree.isLoadingMore(this.state.oakFullpath);
const oakLoading = !oakLoadingMore && this.features.runningTree.isLoading(this.state.oakFullpath);
const oakExecuting = this.features.runningTree.isExecuting(this.state.oakFullpath);
const oakExecutable = !oakExecuting && this.tryExecute();
// 现在取数据需要把runningTree上的更新应用了再取判定actions也一样
let rollback = this.features.runningTree.begin();
try {
this.features.runningTree.redoBranchModis(this.state.oakFullpath);
const origin = oakDirty && this.features.runningTree.getFreshValue(this.state.oakFullpath);
const originLegalActions = origin ? checkActionsAndCascadeEntities.call(this, origin, option) : undefined;
this.features.runningTree.redoBranchOperations(this.state.oakFullpath);
const rows = this.features.runningTree.getFreshValue(this.state.oakFullpath);
const oakLegalActions = rows && checkActionsAndCascadeEntities.call(this, rows, option);
rollback();
rollback = () => undefined;
let modified = false;
if (oakDirty) {
const schema = this.features.cache.getSchema();
const entity = this.features.runningTree.getEntity(this.state.oakFullpath);
if (origin) {
if (rows instanceof Array) {
modified = (0, row_1.compareRows)(schema, entity, rows, origin);
}
else {
modified = (0, row_1.compareRow)(schema, entity, rows, origin);
}
}
else {
modified = true;
}
}
let data = formData && rows
? formData.call(this, {
data: rows,
origin: origin,
features,
props: this.props,
legalActions: oakLegalActions,
originLegalActions,
dirty: !!oakDirty,
modified,
})
: {};
Object.assign(data, {
oakLegalActions,
oakLocales: localeState.dataset,
oakLocalesVersion: localeState.version,
oakLng: localeState.lng,
oakDefaultLng: localeState.defaultLng,
});
if (option.isList) {
// 因为oakFilters和props里的oakFilters同名这里只能先注掉好像还没有组件用过
// const oakFilters = (this as ComponentFullThisType<ED, T>).getFilters();
// const oakSorters = (this as ComponentFullThisType<ED, T>).getSorters();
const oakPagination = this.getPagination();
Object.assign(data, {
// oakFilters,
// oakSorters,
oakPagination,
});
}
for (const k in data) {
if (data[k] === undefined) {
Object.assign(data, {
[k]: null,
});
}
}
;
Object.assign(data, {
oakExecutable,
oakDirty,
oakLoading,
oakLoadingMore,
oakExecuting,
});
if (extra) {
Object.assign(data, extra);
}
this.setState(data);
}
catch (err) {
rollback();
console.error(err);
// assert(false);
}
}
else {
try {
const data = formData
? formData.call(this, {
features,
props: this.props,
})
: {};
if (extra) {
Object.assign(data, extra);
}
if (this.state.oakFullpath) {
/**
* loadingMore和pullLoading设计上有问题暂不处理
*/
const oakDirty = this.features.runningTree.isDirty(this.state.oakFullpath);
const oakExecuting = this.features.runningTree.isExecuting(this.state.oakFullpath);
const oakExecutable = !oakExecuting && this.tryExecute();
const oakLoading = this.features.runningTree.isLoading(this.state.oakFullpath);
Object.assign(data, {
oakDirty,
oakExecutable,
oakExecuting,
oakLoading,
});
}
Object.assign(data, {
oakLocales: localeState.dataset,
oakLocalesVersion: localeState.version,
oakLng: localeState.lng,
oakDefaultLng: localeState.defaultLng,
__time: Date.now(),
}); // 有些环境下如果传空值不触发判断
this.setState(data);
}
catch (err) {
console.error(err);
}
}
}
exports.reRender = reRender;
async function refresh() {
if (this.state.oakFullpath) {
await this.features.runningTree.refresh(this.state.oakFullpath);
}
}
exports.refresh = refresh;
async function loadMore() {
if (this.state.oakEntity && this.state.oakFullpath) {
try {
await this.features.runningTree.loadMore(this.state.oakFullpath);
}
catch (err) {
this.setMessage({
type: 'error',
content: err.message,
});
}
}
}
exports.loadMore = loadMore;
async function execute(action, path, messageProps, //默认true
opers) {
if (this.state.oakExecuting) {
throw new Error('请仔细设计按钮状态,不要允许重复点击!');
}
/* this.setState({
oakFocused: undefined,
}); */
const fullpath = path ? path : this.state.oakFullpath;
const { message } = await this.features.runningTree.execute(fullpath, action, opers);
if (messageProps !== false) {
const messageData = {
type: 'success',
content: message || '操作成功',
};
if (typeof messageProps === 'object') {
Object.assign(messageData, messageProps);
}
this.setMessage(messageData);
}
}
exports.execute = execute;
function destroyNode(isPage) {
(0, assert_1.assert)(this.state.oakFullpath);
this.features.runningTree.destroyNode(this.state.oakFullpath, isPage);
(0, lodash_1.unset)(this.state, ['oakFullpath', 'oakEntity']);
}
exports.destroyNode = destroyNode;
function getFreshValue(path) {
const rollback = this.features.runningTree.begin();
try {
this.features.runningTree.redoBranchModis(path);
this.features.runningTree.redoBranchOperations(path);
const row = this.features.runningTree.getFreshValue(path);
rollback();
return row;
}
catch (err) {
rollback();
console.error(err);
(0, assert_1.assert)(false);
}
}
exports.getFreshValue = getFreshValue;