Merge branch 'dev' into release

This commit is contained in:
Xu Chang 2024-06-21 18:12:46 +08:00
commit 1f06e12a00
20 changed files with 200 additions and 77 deletions

View File

@ -243,12 +243,13 @@ export declare class RunningTree<ED extends EntityDict & BaseEntityDict> extends
private root;
private nodeCountDict;
private pageNodeStack;
private singletonDict;
constructor(cache: Cache<ED>, schema: StorageSchema<ED>);
private increaseNodeCount;
private decreaseNodeCount;
private pushNodeIntoStack;
private popNodeFromStack;
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean): ListNode<ED, T> | SingleNode<ED, T> | VirtualNode<ED>;
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean, singleton: boolean): ListNode<ED, T> | SingleNode<ED, T> | VirtualNode<ED>;
private checkSingleNodeIsModiNode;
checkIsModiNode(path: string): boolean;
private findNode;

View File

@ -1570,15 +1570,12 @@ class VirtualNode extends Feature {
}
}
function analyzePath(path) {
const idx = path.lastIndexOf('.');
if (idx !== -1) {
return {
parent: path.slice(0, idx),
path: path.slice(idx + 1),
};
}
const paths = path.split('.');
const length = paths.length;
return {
path,
root: paths[0],
parent: length > 1 ? paths.slice(0, length - 1).join('.') : '',
path: paths[length - 1],
};
}
export class RunningTree extends Feature {
@ -1587,6 +1584,7 @@ export class RunningTree extends Feature {
root;
nodeCountDict;
pageNodeStack;
singletonDict;
constructor(cache, schema) {
super();
// this.aspectWrapper = aspectWrapper;
@ -1595,6 +1593,7 @@ export class RunningTree extends Feature {
this.root = {};
this.nodeCountDict = {};
this.pageNodeStack = {};
this.singletonDict = {};
}
increaseNodeCount(path) {
if (!this.nodeCountDict[path]) {
@ -1643,13 +1642,26 @@ export class RunningTree extends Feature {
this.nodeCountDict[path] = count;
}
}
createNode(options, isPage) {
createNode(options, isPage, singleton) {
const { entity, pagination, path: fullPath, filters, sorters, projection, isList, id, actions, cascadeActions, getTotal, } = options;
let node;
const { parent, path } = analyzePath(fullPath);
const parentNode = parent ? this.findNode(parent) : undefined;
node = this.findNode(fullPath);
if (singleton) {
assert(!parentNode, 'singleton页面必须是根结点');
this.singletonDict[fullPath] = 1;
}
if (node) {
if (singleton) {
assert(!parentNode, 'singleton页面必须是根结点');
this.increaseNodeCount(fullPath);
this.singletonDict[fullPath] = 1;
return node;
}
if (isPage) {
// 如果是page和过去的某个page在node path上一样尝试把上一个node压栈保存状态以使页面回到原来的page时保持其状态
assert(!parentNode);
@ -1777,7 +1789,8 @@ export class RunningTree extends Feature {
}
destroyNode(path, isPage) {
const rest = this.decreaseNodeCount(path);
if (rest === 0) {
const { root } = analyzePath(path);
if (rest === 0 && !this.singletonDict[root]) {
const node = this.findNode(path);
if (node) {
const childPath = path.slice(path.lastIndexOf('.') + 1);

View File

@ -6,11 +6,13 @@ import { generateNewId } from 'oak-domain/lib/utils/uuid';
export function onPathSet(option, isPage) {
const { props, state } = this;
const { oakPath, oakId, oakFilters } = props;
const { entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { singleton, entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { features } = this;
const oakPath2 = oakPath || path;
assert(oakPath2);
assert(!oakPath || !path);
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;
@ -103,7 +105,7 @@ export function onPathSet(option, isPage) {
actions: typeof actions === 'function' ? () => actions.call(this) : actions,
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
@ -120,7 +122,7 @@ export function onPathSet(option, isPage) {
// 创建virtualNode
features.runningTree.createNode({
path: oakPath2,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {

View File

@ -206,7 +206,7 @@ const oakBehavior = Behavior({
filter,
}, checkerTypes);
},
tryExecute(path) {
tryExecute(path, action) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -214,7 +214,11 @@ const oakBehavior = Behavior({
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}

2
es/page.react.d.ts vendored
View File

@ -73,7 +73,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
isDirty(path?: string | undefined): boolean;
getFreshValue(path?: string | undefined): Partial<ED[keyof ED]["Schema"]> | Partial<ED[keyof ED]["Schema"]>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_5 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];

View File

@ -188,7 +188,7 @@ class OakComponentBase extends React.PureComponent {
}
return this.features.cache.checkOperation(entity, operation, checkerTypes);
}
tryExecute(path) {
tryExecute(path, action) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -196,7 +196,11 @@ class OakComponentBase extends React.PureComponent {
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}

3
es/types/Page.d.ts vendored
View File

@ -37,6 +37,7 @@ type FeatureDef<IsList extends boolean, ED extends EntityDict & BaseEntityDict,
type DevideWidth = 'pc' | 'mobile';
interface ComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> {
isList?: IsList;
singleton?: true;
getTotal?: {
max: number;
deviceWidth?: DevideWidth | 'all';
@ -168,7 +169,7 @@ export type OakCommonComponentMethods<ED extends EntityDict & BaseEntityDict, T
data?: ED[T2]['Operation']['data'];
filter?: ED[T2]['Operation']['filter'];
}, checkerTypes?: (CheckerType | 'relation')[]) => boolean | OakUserException<ED>;
tryExecute: (path?: string) => boolean | OakUserException<ED>;
tryExecute: (path?: string, action?: ED[T]['Action']) => boolean | OakUserException<ED>;
getOperations: (path?: string) => {
operation: ED[T]['Operation'];
entity: T;

View File

@ -17,6 +17,7 @@ declare abstract class Node<ED extends EntityDict & BaseEntityDict, T extends ke
protected loading: number;
protected loadingMore: boolean;
protected executing: boolean;
private extraData;
private actions?;
private cascadeActions?;
constructor(entity: T, schema: StorageSchema<ED>, cache: Cache<ED>, projection?: ED[T]['Selection']['data'] | (() => Promise<ED[T]['Selection']['data']>), parent?: SingleNode<ED, keyof ED> | ListNode<ED, keyof ED> | VirtualNode<ED>, path?: string, actions?: ActionDef<ED, T>[] | (() => ActionDef<ED, T>[]), cascadeActions?: () => {
@ -36,6 +37,8 @@ declare abstract class Node<ED extends EntityDict & BaseEntityDict, T extends ke
protected getProjection(): ED[T]['Selection']['data'] | undefined;
setProjection(projection: ED[T]['Selection']['data']): void;
protected judgeRelation(attr: string): string | 0 | 1 | string[] | 2 | -1;
saveExtraData(key: string, data: any): void;
loadExtraData(key: string): any;
}
declare class ListNode<ED extends EntityDict & BaseEntityDict, T extends keyof ED> extends Node<ED, T> {
private updates;
@ -194,6 +197,7 @@ declare class VirtualNode<ED extends EntityDict & BaseEntityDict> extends Featur
private dirty;
private executing;
private loading;
private extraData;
protected children: Record<string, SingleNode<ED, keyof ED> | ListNode<ED, keyof ED> | VirtualNode<ED>>;
constructor(path?: string, parent?: VirtualNode<ED>);
getModiOperations(): Array<{
@ -219,6 +223,8 @@ declare class VirtualNode<ED extends EntityDict & BaseEntityDict> extends Featur
isLoading(): boolean;
clean(dontPublish?: true): void;
checkIfClean(): void;
saveExtraData(key: string, data: any): void;
loadExtraData(key: string): any;
}
export type CreateNodeOptions<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
path: string;
@ -243,12 +249,13 @@ export declare class RunningTree<ED extends EntityDict & BaseEntityDict> extends
private root;
private nodeCountDict;
private pageNodeStack;
private singletonDict;
constructor(cache: Cache<ED>, schema: StorageSchema<ED>);
private increaseNodeCount;
private decreaseNodeCount;
private pushNodeIntoStack;
private popNodeFromStack;
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean): ListNode<ED, T> | SingleNode<ED, T> | VirtualNode<ED>;
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean, singleton: boolean): ListNode<ED, T> | SingleNode<ED, T> | VirtualNode<ED>;
private checkSingleNodeIsModiNode;
checkIsModiNode(path: string): boolean;
private findNode;
@ -320,5 +327,7 @@ export declare class RunningTree<ED extends EntityDict & BaseEntityDict> extends
}>;
clean(path: string, dontPublish?: true): void;
getRoot(): Record<string, SingleNode<ED, keyof ED> | ListNode<ED, keyof ED> | VirtualNode<ED>>;
saveExtraData(path: string, key: string, data: any): void;
loadExtraData(path: string, key: string): any;
}
export {};

View File

@ -20,6 +20,7 @@ class Node extends Feature_1.Feature {
loading;
loadingMore;
executing;
extraData = {};
actions;
cascadeActions;
constructor(entity, schema, cache, projection, parent, path, actions, cascadeActions) {
@ -86,6 +87,12 @@ class Node extends Feature_1.Feature {
const attr2 = attr.split(':')[0]; // 处理attr:next
return (0, relation_1.judgeRelation)(this.schema, this.entity, attr2);
}
saveExtraData(key, data) {
this.extraData[key] = data;
}
loadExtraData(key) {
return this.extraData[key];
}
}
const DEFAULT_PAGINATION = {
currentPage: 0,
@ -1433,6 +1440,7 @@ class VirtualNode extends Feature_1.Feature {
dirty;
executing;
loading = false;
extraData = {};
children;
constructor(path, parent) {
super();
@ -1571,17 +1579,20 @@ class VirtualNode extends Feature_1.Feature {
}
this.dirty = false;
}
saveExtraData(key, data) {
this.extraData[key] = data;
}
loadExtraData(key) {
return this.extraData[key];
}
}
function analyzePath(path) {
const idx = path.lastIndexOf('.');
if (idx !== -1) {
return {
parent: path.slice(0, idx),
path: path.slice(idx + 1),
};
}
const paths = path.split('.');
const length = paths.length;
return {
path,
root: paths[0],
parent: length > 1 ? paths.slice(0, length - 1).join('.') : '',
path: paths[length - 1],
};
}
class RunningTree extends Feature_1.Feature {
@ -1590,6 +1601,7 @@ class RunningTree extends Feature_1.Feature {
root;
nodeCountDict;
pageNodeStack;
singletonDict;
constructor(cache, schema) {
super();
// this.aspectWrapper = aspectWrapper;
@ -1598,6 +1610,7 @@ class RunningTree extends Feature_1.Feature {
this.root = {};
this.nodeCountDict = {};
this.pageNodeStack = {};
this.singletonDict = {};
}
increaseNodeCount(path) {
if (!this.nodeCountDict[path]) {
@ -1646,13 +1659,21 @@ class RunningTree extends Feature_1.Feature {
this.nodeCountDict[path] = count;
}
}
createNode(options, isPage) {
createNode(options, isPage, singleton) {
const { entity, pagination, path: fullPath, filters, sorters, projection, isList, id, actions, cascadeActions, getTotal, } = options;
let node;
const { parent, path } = analyzePath(fullPath);
const parentNode = parent ? this.findNode(parent) : undefined;
node = this.findNode(fullPath);
if (singleton) {
(0, assert_1.assert)(!parentNode, 'singleton页面必须是根结点');
this.singletonDict[fullPath] = 1;
}
if (node) {
if (singleton) {
this.increaseNodeCount(fullPath);
return node;
}
if (isPage) {
// 如果是page和过去的某个page在node path上一样尝试把上一个node压栈保存状态以使页面回到原来的page时保持其状态
(0, assert_1.assert)(!parentNode);
@ -1780,7 +1801,8 @@ class RunningTree extends Feature_1.Feature {
}
destroyNode(path, isPage) {
const rest = this.decreaseNodeCount(path);
if (rest === 0) {
const { root } = analyzePath(path);
if (rest === 0 && !this.singletonDict[root]) {
const node = this.findNode(path);
if (node) {
const childPath = path.slice(path.lastIndexOf('.') + 1);
@ -2127,5 +2149,13 @@ class RunningTree extends Feature_1.Feature {
getRoot() {
return this.root;
}
saveExtraData(path, key, data) {
const node = this.findNode(path);
node.saveExtraData(key, data);
}
loadExtraData(path, key) {
const node = this.findNode(path);
return node.loadExtraData(key);
}
}
exports.RunningTree = RunningTree;

View File

@ -9,11 +9,13 @@ const uuid_1 = require("oak-domain/lib/utils/uuid");
function onPathSet(option, isPage) {
const { props, state } = this;
const { oakPath, oakId, oakFilters } = props;
const { entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { singleton, entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { features } = this;
const oakPath2 = oakPath || path;
(0, assert_1.assert)(oakPath2);
(0, assert_1.assert)(!oakPath || !path);
(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;
@ -106,7 +108,7 @@ function onPathSet(option, isPage) {
actions: typeof actions === 'function' ? () => actions.call(this) : actions,
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
@ -123,7 +125,7 @@ function onPathSet(option, isPage) {
// 创建virtualNode
features.runningTree.createNode({
path: oakPath2,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {

View File

@ -209,7 +209,7 @@ const oakBehavior = Behavior({
filter,
}, checkerTypes);
},
tryExecute(path) {
tryExecute(path, action) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -217,7 +217,11 @@ const oakBehavior = Behavior({
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}

2
lib/page.react.d.ts vendored
View File

@ -73,7 +73,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
isDirty(path?: string | undefined): boolean;
getFreshValue(path?: string | undefined): Partial<ED[keyof ED]["Schema"]> | Partial<ED[keyof ED]["Schema"]>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_5 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];

View File

@ -193,7 +193,7 @@ class OakComponentBase extends react_1.default.PureComponent {
}
return this.features.cache.checkOperation(entity, operation, checkerTypes);
}
tryExecute(path) {
tryExecute(path, action) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -201,7 +201,11 @@ class OakComponentBase extends react_1.default.PureComponent {
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}

3
lib/types/Page.d.ts vendored
View File

@ -37,6 +37,7 @@ type FeatureDef<IsList extends boolean, ED extends EntityDict & BaseEntityDict,
type DevideWidth = 'pc' | 'mobile';
interface ComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> {
isList?: IsList;
singleton?: true;
getTotal?: {
max: number;
deviceWidth?: DevideWidth | 'all';
@ -168,7 +169,7 @@ export type OakCommonComponentMethods<ED extends EntityDict & BaseEntityDict, T
data?: ED[T2]['Operation']['data'];
filter?: ED[T2]['Operation']['filter'];
}, checkerTypes?: (CheckerType | 'relation')[]) => boolean | OakUserException<ED>;
tryExecute: (path?: string) => boolean | OakUserException<ED>;
tryExecute: (path?: string, action?: ED[T]['Action']) => boolean | OakUserException<ED>;
getOperations: (path?: string) => {
operation: ED[T]['Operation'];
entity: T;

View File

@ -1,6 +1,6 @@
{
"name": "oak-frontend-base",
"version": "5.2.3",
"version": "5.2.4",
"description": "oak框架中前端与业务逻辑无关的平台部分",
"author": {
"name": "XuChang"
@ -24,7 +24,7 @@
"node-schedule": "^2.1.1",
"nprogress": "^0.2.0",
"oak-common-aspect": "~3.0.0",
"oak-domain": "~5.0.11",
"oak-domain": "~5.0.12",
"oak-memory-tree-store": "~3.3.1",
"ol": "^7.3.0",
"react-activation": "^0.12.4",

View File

@ -31,6 +31,7 @@ abstract class Node<
protected loading: number;
protected loadingMore: boolean;
protected executing: boolean;
private extraData: Record<string, any> = {};
private actions?: ActionDef<ED, T>[] | (() => ActionDef<ED, T>[]);
private cascadeActions?: () => {
[K in keyof ED[T]['Schema']]?: ActionDef<ED, keyof ED>[];
@ -122,6 +123,14 @@ abstract class Node<
const attr2 = attr.split(':')[0]; // 处理attr:next
return judgeRelation(this.schema, this.entity, attr2);
}
saveExtraData(key: string, data: any) {
this.extraData[key] = data;
}
loadExtraData(key: string) {
return this.extraData[key];
}
}
const DEFAULT_PAGINATION: Pagination = {
@ -1686,11 +1695,11 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
}
}
class VirtualNode<
ED extends EntityDict & BaseEntityDict> extends Feature {
class VirtualNode<ED extends EntityDict & BaseEntityDict> extends Feature {
private dirty: boolean;
private executing: boolean;
private loading = false;
private extraData: Record<string, any> = {};
protected children: Record<string, SingleNode<ED, keyof ED> | ListNode<ED, keyof ED> | VirtualNode<ED>>;
constructor(path?: string, parent?: VirtualNode<ED>) {
super();
@ -1848,6 +1857,13 @@ class VirtualNode<
this.dirty = false;
}
saveExtraData(key: string, data: any) {
this.extraData[key] = data;
}
loadExtraData(key: string) {
return this.extraData[key];
}
}
export type CreateNodeOptions<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
@ -1870,15 +1886,12 @@ export type CreateNodeOptions<ED extends EntityDict & BaseEntityDict, T extends
function analyzePath(path: string) {
const idx = path.lastIndexOf('.');
if (idx !== -1) {
return {
parent: path.slice(0, idx),
path: path.slice(idx + 1),
};
}
const paths = path.split('.');
const length = paths.length;
return {
path,
root: paths[0],
parent: length > 1 ? paths.slice(0, length - 1).join('.') : '',
path: paths[length - 1],
};
}
@ -1897,6 +1910,7 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
count: number;
node: Node<ED, keyof ED> | VirtualNode<ED>;
}>>;
private singletonDict: Record<string, 1>;
constructor(
cache: Cache<ED>,
@ -1909,6 +1923,7 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
this.root = {};
this.nodeCountDict = {};
this.pageNodeStack = {};
this.singletonDict = {};
}
private increaseNodeCount(path: string) {
@ -1963,9 +1978,7 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
}
}
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean): ListNode<ED, T>
| SingleNode<ED, T>
| VirtualNode<ED> {
createNode<T extends keyof ED>(options: CreateNodeOptions<ED, T>, isPage: boolean, singleton: boolean): ListNode<ED, T> | SingleNode<ED, T> | VirtualNode<ED> {
const {
entity,
pagination,
@ -1986,7 +1999,17 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
| SingleNode<ED, T>
| VirtualNode<ED>
| undefined);
if (singleton) {
assert(!parentNode, 'singleton页面必须是根结点');
this.singletonDict[fullPath] = 1;
}
if (node) {
if (singleton) {
this.increaseNodeCount(fullPath);
return node;
}
if (isPage) {
// 如果是page和过去的某个page在node path上一样尝试把上一个node压栈保存状态以使页面回到原来的page时保持其状态
assert(!parentNode);
@ -2142,7 +2165,8 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
destroyNode(path: string, isPage: boolean) {
const rest = this.decreaseNodeCount(path);
if (rest === 0) {
const { root } = analyzePath(path);
if (rest === 0 && !this.singletonDict[root]) {
const node = this.findNode(path);
if (node) {
const childPath = path.slice(path.lastIndexOf('.') + 1);
@ -2601,4 +2625,14 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
getRoot() {
return this.root;
}
saveExtraData(path: string, key: string, data: any) {
const node = this.findNode(path)!;
node.saveExtraData(key, data);
}
loadExtraData(path: string, key: string) {
const node = this.findNode(path)!;
return node.loadExtraData(key);
}
}

View File

@ -35,12 +35,14 @@ export function onPathSet<
isPage: boolean): Partial<OakComponentData<ED, T>> {
const { props, state } = this;
const { oakPath, oakId, oakFilters } = props as ComponentProps<ED, T, {}>;
const { entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { singleton, entity, path, projection, isList, filters, sorters, pagination, getTotal } = option;
const { features } = this;
const oakPath2 = oakPath || path;
assert(oakPath2);
assert(!oakPath || !path);
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;
@ -145,7 +147,7 @@ export function onPathSet<
actions: typeof actions === 'function' ? () => actions.call(this) : actions,
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2: string) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
@ -163,7 +165,7 @@ export function onPathSet<
// 创建virtualNode
features.runningTree.createNode({
path: oakPath2 as string,
}, isPage);
}, isPage, !!singleton);
this.addFeatureSub('runningTree', (path2: string) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
@ -681,7 +683,7 @@ export function destroyNode<
ED extends EntityDict & BaseEntityDict,
T extends keyof ED
>(this: ComponentFullThisType<ED, T>, isPage: boolean) {
assert(this.state.oakFullpath);
assert(this.state.oakFullpath);
this.features.runningTree.destroyNode(this.state.oakFullpath, isPage);
unset(this.state, ['oakFullpath', 'oakEntity']);
}

View File

@ -342,7 +342,7 @@ const oakBehavior = Behavior<
);
},
tryExecute(path?: string) {
tryExecute(path?: string, action?: string) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -350,7 +350,11 @@ const oakBehavior = Behavior<
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}

View File

@ -40,10 +40,10 @@ abstract class OakComponentBase<
TData extends DataOption,
TProperty extends DataOption,
TMethod extends MethodOption
> extends React.PureComponent<
> extends React.PureComponent<
ComponentProps<ED, T, TProperty>,
ComponentData<ED, T, FormedData, TData>
> {
> {
abstract features: FD &
BasicFeatures<ED>;
abstract oakOption: OakComponentOption<
@ -362,7 +362,7 @@ abstract class OakComponentBase<
);
}
tryExecute(path?: string) {
tryExecute(path?: string, action?: string) {
const path2 = path
? `${this.state.oakFullpath}.${path}`
: this.state.oakFullpath;
@ -370,7 +370,11 @@ abstract class OakComponentBase<
if (operations) {
for (const oper of operations) {
const { entity, operation } = oper;
const result = this.checkOperation(entity, operation);
const operation2 = action ? {
...operation,
action,
} : operation;
const result = this.checkOperation(entity, operation2);
if (result !== true) {
return result;
}
@ -986,7 +990,10 @@ export function createComponent<
componentWillUnmount() {
this.unsubscribeAll();
this.state.oakFullpath && (this.iAmThePage() || !this.props.oakZombie) && destroyNode.call(this as any, this.iAmThePage());
this.state.oakFullpath && (this.iAmThePage() || !this.props.oakZombie) && destroyNode.call(
this as any,
this.iAmThePage()
);
lifetimes?.detached && lifetimes.detached.call(this);
this.unmounted = true;
}

View File

@ -136,6 +136,7 @@ interface ComponentOption<
EMethod extends Record<string, Function> = {},
> {
isList?: IsList;
singleton?: true;
getTotal?: {
max: number;
deviceWidth?: DevideWidth | 'all';
@ -391,7 +392,7 @@ export type OakCommonComponentMethods<
},
checkerTypes?: (CheckerType | 'relation')[]
) => boolean | OakUserException<ED>;
tryExecute: (path?: string) => boolean | OakUserException<ED>;
tryExecute: (path?: string, action?: ED[T]['Action']) => boolean | OakUserException<ED>;
getOperations: (
path?: string
) => { operation: ED[T]['Operation']; entity: T }[] | undefined;