relationAuth迁到component里
This commit is contained in:
parent
b4276d5103
commit
58e815cdeb
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
import assert from "assert";
|
||||
import { uniq, pull, union, difference } from 'oak-domain/lib/utils/lodash';
|
||||
import { ED } from "../../../types/AbstractComponent";
|
||||
import { groupBy } from "oak-domain/lib/utils/lodash";
|
||||
import { AuthCascadePath } from "oak-domain/lib/types";
|
||||
import { RowWithActions } from "../../../types/Page";
|
||||
import { resolvePath } from "../../../utils/usefulFn";
|
||||
import { StorageSchema } from "oak-domain/lib/types";
|
||||
|
||||
export default OakComponent({
|
||||
entity: 'actionAuth',
|
||||
projection: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
paths: 1,
|
||||
deActions: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
},
|
||||
},
|
||||
isList: true,
|
||||
properties: {
|
||||
entity: '' as keyof ED,
|
||||
actions: [] as string[],
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { entity, actions } = this.props!;
|
||||
assert(entity);
|
||||
return {
|
||||
destEntity: entity as string,
|
||||
relation: {
|
||||
entityId: {
|
||||
$exists: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
/* if (!actions || actions.length === 0) {
|
||||
return {
|
||||
destEntity: entity as string,
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
destEntity: entity as string,
|
||||
deActions: {
|
||||
$overlaps: actions,
|
||||
},
|
||||
};
|
||||
} */
|
||||
}
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
pageSize: 1000,
|
||||
currentPage: 0,
|
||||
},
|
||||
formData({ data }) {
|
||||
const { entity } = this.props;
|
||||
const schema = this.features.cache.getSchema();
|
||||
const cascadeEntities = this.features.relationAuth.getCascadeActionAuths(entity!, true);
|
||||
const actionAuthGroup = groupBy(data, (ele) => ele.paths?.join(','));
|
||||
const actionAuthList = Object.keys(actionAuthGroup).map(
|
||||
(key) => {
|
||||
let result = {};
|
||||
const row = actionAuthGroup[key][0];
|
||||
const { paths } = row;
|
||||
const path = paths![0];
|
||||
if (path.includes('$')) {
|
||||
const relationEntity = this.resolveP(schema, path, row.destEntity);
|
||||
const relations = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: relationEntity,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
Object.assign(result, {
|
||||
sourceEntity: relationEntity,
|
||||
relationSelections: relations,
|
||||
})
|
||||
} else {
|
||||
const cascadeEntity = cascadeEntities.find((ele) =>
|
||||
ele[1] === path
|
||||
)
|
||||
const relations = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: cascadeEntity![2] as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
Object.assign(result, {
|
||||
sourceEntity: cascadeEntity![2],
|
||||
relationSelections: relations,
|
||||
})
|
||||
}
|
||||
Object.assign(result, {
|
||||
paths,
|
||||
relations: actionAuthGroup[key],
|
||||
})
|
||||
return result;
|
||||
}
|
||||
)
|
||||
console.log(actionAuthList);
|
||||
// const cascadeEntityActions = cascadeEntities.map(
|
||||
// (ele) => {
|
||||
// const [de, p, se] = ele;
|
||||
// const actionAuths = data?.filter(
|
||||
// ele => ele.destEntity === de
|
||||
// );
|
||||
// const relations = this.features.cache.get('relation', {
|
||||
// data: {
|
||||
// id: 1,
|
||||
// entity: 1,
|
||||
// entityId: 1,
|
||||
// name: 1,
|
||||
// display: 1,
|
||||
// },
|
||||
// filter: {
|
||||
// entity: se as string,
|
||||
// entityId: {
|
||||
// $exists: false,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// return {
|
||||
// actionAuths,
|
||||
// relations,
|
||||
// path: ele,
|
||||
// };
|
||||
// }
|
||||
// );
|
||||
// // path里含有$
|
||||
// const $pathActionAuths: (RowWithActions<ED, 'actionAuth'> & { path: string })[] = [];
|
||||
// data.forEach((ele) => {
|
||||
// if (ele.paths?.join('').includes('$')) {
|
||||
// ele.paths.forEach((path) => {
|
||||
// if (path.includes('$')) {
|
||||
// $pathActionAuths.push({
|
||||
// ...ele,
|
||||
// path,
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// // groupBy
|
||||
// // 分解groupBy 的key
|
||||
// const $actionAuthsObject = groupBy($pathActionAuths, 'path');
|
||||
// // 含有反向指针的路径,其所对应实体的请求放在了onChange方法
|
||||
// Object.keys($actionAuthsObject).forEach((ele) => {
|
||||
// const entities = ele.split('.');
|
||||
// const slicePath = entities[entities.length - 1];
|
||||
// const se = entities[entities.length - 1].split('$')[0];
|
||||
// const relationEntity = this.resolveP(schema, ele, entity);
|
||||
// const p = ele;
|
||||
// const de = entity!;
|
||||
// // 初始时 relation先用{name: relationName}表示
|
||||
// let relations = [];
|
||||
// if (relationEntity === 'user') {
|
||||
// relations = [{ id: '', name: '当前用户' }];
|
||||
// }
|
||||
// else {
|
||||
// relations = this.features.cache.get('relation', {
|
||||
// data: {
|
||||
// id: 1,
|
||||
// entity: 1,
|
||||
// entityId: 1,
|
||||
// name: 1,
|
||||
// display: 1,
|
||||
// },
|
||||
// filter: {
|
||||
// entity: relationEntity as string,
|
||||
// entityId: {
|
||||
// $exists: false,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// cascadeEntityActions.push({
|
||||
// path: [de, p, se, true],
|
||||
// relations: relations,
|
||||
// actionAuths: $actionAuthsObject[ele],
|
||||
// })
|
||||
// })
|
||||
|
||||
// // relationId为空字符串 表示为user的actionAuth 也要特殊处理
|
||||
// const hasUserActionAuths: (RowWithActions<ED, 'actionAuth'> & { path: string })[] = [];
|
||||
// data.forEach((ele) => {
|
||||
// if (ele.relationId === '') {
|
||||
// ele.paths?.forEach((path) => {
|
||||
// hasUserActionAuths.push({
|
||||
// ...ele,
|
||||
// path
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// // const hasUserActionAuths = data.filter((ele) => ele.relationId === '');
|
||||
// const $actionAuthsObject2 = groupBy(hasUserActionAuths, 'path');
|
||||
// Object.keys($actionAuthsObject2).forEach((ele) => {
|
||||
// const entities = ele.split('.');
|
||||
// const se = entities[entities.length - 1].split('$')[0];
|
||||
// const p = ele;
|
||||
// const de = entity!;
|
||||
// cascadeEntityActions.push({
|
||||
// path: [de, p, se, true],
|
||||
// relations: [{ id: '', name: '当前用户' }],
|
||||
// actionAuths: $actionAuthsObject2[ele],
|
||||
// })
|
||||
// })
|
||||
return {
|
||||
actionAuthList,
|
||||
actionAuths: data,
|
||||
// cascadeEntityActions,
|
||||
};
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
const { entity } = this.props;
|
||||
const cascadeEntities = this.features.relationAuth.getCascadeActionAuths(entity!, true);
|
||||
const destEntities = uniq(cascadeEntities.map(ele => ele[2])) as string[];
|
||||
this.features.cache.refresh('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
// entity: {
|
||||
// $in: destEntities,
|
||||
// },
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
data: {
|
||||
relations2: [] as Partial<ED['relation']['Schema']>[],
|
||||
},
|
||||
listeners: {
|
||||
actions(prev, next) {
|
||||
const actionAuths = this.features.runningTree.getFreshValue(this.state.oakFullpath);
|
||||
if (actionAuths) {
|
||||
(actionAuths as ED['actionAuth']['OpSchema'][]).forEach(
|
||||
(actionAuth) => {
|
||||
if (actionAuth.$$createAt$$ === 1) {
|
||||
const { id, deActions } = actionAuth;
|
||||
this.updateItem({
|
||||
deActions: next.actions,
|
||||
}, id);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
this.reRender();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onChange(checked: boolean, relationId: string, path: string, actionAuths?: ED['actionAuth']['Schema'][], relationName?: string) {
|
||||
const { actions } = this.props;
|
||||
assert(actions && actions.length > 0);
|
||||
// 排除user这种情况
|
||||
if (!relationId && (relationName && relationName !== '当前用户')) {
|
||||
const se = path.split('$')[0];
|
||||
const { data: relations } = await this.features.cache.refresh('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: se,
|
||||
name: relationName,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!relations || !relations.length) {
|
||||
this.setMessage({
|
||||
content: '数据缺失!',
|
||||
type: 'error',
|
||||
})
|
||||
return;
|
||||
}
|
||||
relationId = relations[0].id!;
|
||||
}
|
||||
if (actionAuths && actionAuths.length) {
|
||||
// const { deActions } = actionAuth;
|
||||
if (checked) {
|
||||
const dASameActionAuth = actionAuths.find((ele) => difference(actions, ele.deActions).length === 0);
|
||||
// 存在deActions相同,paths push并做去重处理
|
||||
if (dASameActionAuth) {
|
||||
if (dASameActionAuth.$$deleteAt$$ && dASameActionAuth.$$deleteAt$$ === 1) {
|
||||
this.recoverItem(dASameActionAuth.id);
|
||||
}
|
||||
this.updateItem({
|
||||
paths: dASameActionAuth.paths.concat(path),
|
||||
}, dASameActionAuth.id)
|
||||
}
|
||||
else {
|
||||
this.addItem({
|
||||
paths: [path],
|
||||
relationId,
|
||||
destEntity: this.props.entity as string,
|
||||
deActions: actions,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 将path从paths中删除
|
||||
actionAuths.forEach((ele) => {
|
||||
const pathIndex = ele.paths.findIndex((pathE) => pathE === path);
|
||||
if (pathIndex !== -1) {
|
||||
const newPaths = [...ele.paths];
|
||||
newPaths.splice(pathIndex, 1);
|
||||
if (!newPaths.length) {
|
||||
this.removeItem(ele.id);
|
||||
}
|
||||
else {
|
||||
this.updateItem({
|
||||
paths: newPaths
|
||||
}, ele.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// if (checked) {
|
||||
// const deActions2 = union(deActions, actions);
|
||||
// this.updateItem({
|
||||
// deActions: deActions2,
|
||||
// }, actionAuth.id);
|
||||
// }
|
||||
// else {
|
||||
// actions.forEach(
|
||||
// action => pull(deActions, action)
|
||||
// );
|
||||
// this.updateItem({
|
||||
// deActions,
|
||||
// }, actionAuth.id);
|
||||
// }
|
||||
}
|
||||
else {
|
||||
// 新增actionAuth
|
||||
assert(checked);
|
||||
this.addItem({
|
||||
paths: [path],
|
||||
relationId,
|
||||
destEntity: this.props.entity as string,
|
||||
deActions: actions,
|
||||
});
|
||||
}
|
||||
},
|
||||
async onChange2(checked: boolean, relationId: string, paths: string[], actionAuths: ED['actionAuth']['Schema'][]) {
|
||||
console.log(checked);
|
||||
const { actions } = this.props;
|
||||
if (checked) {
|
||||
const dASameActionAuth = (actionAuths as ED['actionAuth']['Schema'][]).find((ele) => ele.relationId === relationId);
|
||||
if (dASameActionAuth) {
|
||||
if (dASameActionAuth.$$deleteAt$$ && dASameActionAuth.$$deleteAt$$ === 1) {
|
||||
this.recoverItem(dASameActionAuth.id);
|
||||
this.updateItem({
|
||||
deActions: actions,
|
||||
}, dASameActionAuth.id)
|
||||
}
|
||||
else {
|
||||
this.updateItem({
|
||||
deActions: uniq((actions || []).concat(dASameActionAuth.deActions)),
|
||||
}, dASameActionAuth.id)
|
||||
}
|
||||
} else {
|
||||
this.addItem({
|
||||
paths,
|
||||
relationId,
|
||||
destEntity: this.props.entity as string,
|
||||
deActions: actions,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const dASameActionAuth = (actionAuths as ED['actionAuth']['Schema'][]).find((ele) => ele.relationId === relationId && !ele.$$deleteAt$$);
|
||||
assert(dASameActionAuth);
|
||||
const newActions = difference(dASameActionAuth.deActions, actions!);
|
||||
if (newActions.length === 0) {
|
||||
this.removeItem(dASameActionAuth.id);
|
||||
} else {
|
||||
this.updateItem({
|
||||
deActions: newActions,
|
||||
}, dASameActionAuth.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirm() {
|
||||
this.execute();
|
||||
},
|
||||
resolveP(schema: StorageSchema<ED>, path: string, destEntity: string) {
|
||||
if (path === '') {
|
||||
return destEntity;
|
||||
}
|
||||
const splitArr = path.split('.');
|
||||
splitArr.unshift(destEntity);
|
||||
for (let i = 1; i < splitArr.length; i++) {
|
||||
if (splitArr[i].includes('$')) {
|
||||
splitArr[i] = splitArr[i].split('$')[0];
|
||||
continue;
|
||||
}
|
||||
// 用已解析的前项来解析后项
|
||||
const { attributes } = schema[splitArr[i - 1]];
|
||||
const { ref } = attributes[`${splitArr[i]}Id`];
|
||||
splitArr[i] = ref as string;
|
||||
}
|
||||
return splitArr[splitArr.length - 1];
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"confirm": "确认", "reset": "重置"}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
import { Table, Checkbox, Button, Row, Radio, Col, Typography, Space, Modal, Badge, Tag } from 'antd';
|
||||
const { Title, Text } = Typography;
|
||||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { difference, intersection, isEqual } from 'oak-domain/lib/utils/lodash';
|
||||
import { useState, useEffect } from 'react';
|
||||
import ActionAuthListSingle from '../../relation/single';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
type CascadeEntityActions = Array<{
|
||||
path: AuthCascadePath<ED>;
|
||||
relations: ED['relation']['Schema'][];
|
||||
actionAuths?: ED['actionAuth']['OpSchema'][];
|
||||
}>;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
'actionAuth',
|
||||
true,
|
||||
{
|
||||
cascadeEntityActions: Array<{
|
||||
path: AuthCascadePath<ED>;
|
||||
relations: ED['relation']['Schema'][];
|
||||
actionAuths?: ED['actionAuth']['Schema'][];
|
||||
}>;
|
||||
actionAuthList: Array<{
|
||||
paths: string[];
|
||||
sourceEntity: string;
|
||||
relations: ED['actionAuth']['Schema'][],
|
||||
relationSelections: Array<{ id: string, name: string }>
|
||||
}>;
|
||||
actions: string[];
|
||||
entity: keyof EntityDict;
|
||||
},
|
||||
{
|
||||
onChange: (checked: boolean, relationId: string, path: string, actionAuth?: ED['actionAuth']['Schema'][]) => void;
|
||||
onChange2: (checked: boolean, relationId: string, paths: string[], actionAuths: ED['actionAuth']['Schema'][], actionAuth?: ED['actionAuth']['Schema']) => void;
|
||||
confirm: () => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { cascadeEntityActions, oakDirty, actions, entity, actionAuthList } = props.data;
|
||||
const { onChange, t, clean, confirm, onChange2 } = props.methods;
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<ActionAuthListSingle entity={entity} />
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
key: '1',
|
||||
title: '源对象',
|
||||
width: 100,
|
||||
render: (value, record) => {
|
||||
const { sourceEntity } = record;
|
||||
return sourceEntity;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: '路径',
|
||||
width: 200,
|
||||
render: (value, record) => {
|
||||
const { paths } = record;
|
||||
return paths.map((ele, index) => {
|
||||
if (index === 0) {
|
||||
return ele;
|
||||
} else {
|
||||
return <><br />{ele}</>
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
fixed: 'right',
|
||||
title: '相关角色',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
render: (value, record) => {
|
||||
// const { relations, actionAuths, path } = record;
|
||||
const { relations, relationSelections } = record;
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{/* {
|
||||
relations?.map(
|
||||
(r) => {
|
||||
let checked = false, indeterminate = false;
|
||||
// filter出对应path的actionAuth来决定relation的check
|
||||
// sort deActions长的在后,不然会影响checked
|
||||
const actionAuthsByPath = actionAuths?.filter((ele) => ele.paths.includes(path[1]))
|
||||
.sort((a, b) => b.deActions.length - a.deActions.length);
|
||||
if (actionAuthsByPath && actions.length > 0) {
|
||||
for (const aa of actionAuthsByPath) {
|
||||
// 1.relationId相同,deActions也要相同
|
||||
// 如果path中存在多对一的情况要使用name进行判断
|
||||
if (!aa.$$deleteAt$$ && (aa.relationId === r.id
|
||||
|| (record.path.includes('$') && aa.relation?.name === r.name))
|
||||
) {
|
||||
const { deActions } = aa;
|
||||
checked = difference(actions, deActions).length === 0;
|
||||
indeterminate = !checked && intersection(actions, deActions).length > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Checkbox
|
||||
disabled={actions.length === 0}
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
onChange={({ target }) => {
|
||||
const { checked } = target;
|
||||
const actionAuths2 = actionAuths?.filter(
|
||||
ele => ele.relationId === r.id || (record.path.includes('$') && ele.relation?.name === r.name)
|
||||
);
|
||||
|
||||
onChange(checked, r.id, path[1], actionAuths2)
|
||||
}}
|
||||
>
|
||||
{r.name}
|
||||
</Checkbox>
|
||||
)
|
||||
}
|
||||
)
|
||||
} */}
|
||||
{
|
||||
relationSelections.map(
|
||||
(ele) => {
|
||||
let checked = false, indeterminate = false;
|
||||
if (actions && actions.length > 0) {
|
||||
const relation = relations.find((ele2) => ele2.relationId === ele.id && !ele2.$$deleteAt$$);
|
||||
if (relation) {
|
||||
const { deActions } = relation;
|
||||
checked = difference(actions, deActions).length === 0;
|
||||
indeterminate = !checked && intersection(actions, deActions).length > 0;
|
||||
}
|
||||
}
|
||||
return <Checkbox
|
||||
disabled={actions.length === 0}
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
onChange={({ target }) => {
|
||||
onChange2(target.checked, ele.id, record.paths, relations);
|
||||
}}
|
||||
>
|
||||
{ele.name}
|
||||
</Checkbox>
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]}
|
||||
dataSource={actionAuthList}
|
||||
pagination={false}
|
||||
/>
|
||||
<Row justify="end" style={{ marginTop: 20, padding: 5 }}>
|
||||
<Button
|
||||
style={{ marginRight: 10 }}
|
||||
type='primary'
|
||||
disabled={!oakDirty}
|
||||
onClick={() => confirm()}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!oakDirty}
|
||||
onClick={() => clean()}
|
||||
>
|
||||
{t("reset")}
|
||||
</Button>
|
||||
</Row>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import assert from "assert";
|
||||
import { AuthCascadePath } from "oak-domain/lib/types";
|
||||
import { ED } from "../../../types/AbstractComponent";
|
||||
|
||||
export default OakComponent({
|
||||
entity: 'actionAuth',
|
||||
projection: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
paths: 1,
|
||||
deActions: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
},
|
||||
},
|
||||
isList: true,
|
||||
properties: {
|
||||
relationId: '',
|
||||
entity: '' as keyof ED,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { relationId } = this.props;
|
||||
assert(relationId);
|
||||
return {
|
||||
relationId,
|
||||
};
|
||||
}
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
pageSize: 1000,
|
||||
currentPage: 0,
|
||||
},
|
||||
formData({ data }) {
|
||||
const { entity, relationId } = this.props;
|
||||
const [relation] = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
id: relationId,
|
||||
},
|
||||
});
|
||||
const { name } = relation || {};
|
||||
const cascadeEntities = this.features.relationAuth.getCascadeActionEntitiesBySource(entity!);
|
||||
const cascadeEntityActions = cascadeEntities.map(
|
||||
(ele) => {
|
||||
const { path } = ele;
|
||||
const cascadePath = path[1];
|
||||
|
||||
const actionAuth = data?.find(
|
||||
ele => ele.paths?.includes(cascadePath) && ele.destEntity === path[0]
|
||||
);
|
||||
return {
|
||||
actionAuth,
|
||||
...ele,
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
relationName: name,
|
||||
cascadeEntityActions,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onChange(actions: string[], path: AuthCascadePath<ED>, actionAuth?: ED['actionAuth']['OpSchema']) {
|
||||
if (actionAuth) {
|
||||
this.updateItem({
|
||||
deActions: actions,
|
||||
}, actionAuth.id);
|
||||
}
|
||||
else {
|
||||
this.addItem({
|
||||
relationId: this.props.relationId,
|
||||
paths: [path[1]],
|
||||
destEntity: path[0] as string,
|
||||
deActions: actions,
|
||||
});
|
||||
}
|
||||
},
|
||||
confirm() {
|
||||
this.execute();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"confirm": "确认", "reset": "重置"}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import { Table, Checkbox, Button, Row } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
const { Title, Text } = Typography;
|
||||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
'actionAuth',
|
||||
true,
|
||||
{
|
||||
entity: string;
|
||||
relationName: string;
|
||||
cascadeEntityActions: Array<{
|
||||
path: AuthCascadePath<ED>;
|
||||
actions: string[];
|
||||
actionAuth?: ED['actionAuth']['OpSchema'];
|
||||
}>;
|
||||
},
|
||||
{
|
||||
onChange: (actions: string[], path: AuthCascadePath<ED>, actionAuth?: ED['actionAuth']['OpSchema']) => void;
|
||||
confirm: () => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { cascadeEntityActions, oakDirty, entity, relationName } = props.data;
|
||||
const { onChange, t, clean, confirm } = props.methods;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row justify="center" style={{ margin: 20, padding: 10 }}>
|
||||
<Row style={{ marginRight: 10 }}>
|
||||
<Title level={4}>当前对象:</Title>
|
||||
<Text code>{entity}</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Title level={4}>当前角色:</Title>
|
||||
<Text code>{relationName}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
key: '1',
|
||||
title: '目标对象',
|
||||
width: 100,
|
||||
render: (value, record) => {
|
||||
const { path } = record;
|
||||
return path[0];
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: '路径',
|
||||
width: 200,
|
||||
render: (value, record) => {
|
||||
const { path } = record;
|
||||
return path[1];
|
||||
},
|
||||
},
|
||||
{
|
||||
fixed: 'right',
|
||||
title: '操作',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
render: (value, record) => {
|
||||
const { actions, actionAuth, path } = record;
|
||||
return (
|
||||
<Checkbox.Group
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
options={actions}
|
||||
value={actionAuth?.deActions}
|
||||
onChange={(value) => onChange(value as string[], path, actionAuth)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
]}
|
||||
dataSource={cascadeEntityActions}
|
||||
pagination={false}
|
||||
/>
|
||||
<Row justify="end" style={{ marginTop: 20, padding: 5 }}>
|
||||
<Button
|
||||
style={{ marginRight: 10 }}
|
||||
type='primary'
|
||||
disabled={!oakDirty}
|
||||
onClick={() => confirm()}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!oakDirty}
|
||||
onClick={() => clean()}
|
||||
>
|
||||
{t("reset")}
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default OakComponent({
|
||||
isList: false,
|
||||
properties: {
|
||||
entity: '' as keyof ED,
|
||||
},
|
||||
data: {
|
||||
checkedActions: [] as string[],
|
||||
relationIds: [] as string[],
|
||||
},
|
||||
formData() {
|
||||
const { entity } = this.props;
|
||||
const actions = this.features.relationAuth.getActions(entity!);
|
||||
const daas = this.features.relationAuth.getCascadeActionAuths(entity!, false);
|
||||
const relations = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: entity as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
}
|
||||
});
|
||||
const dras = this.features.relationAuth.getCascadeRelationAuths(entity!, false);
|
||||
const deduceRelationAttr = this.features.relationAuth.getDeduceRelationAttribute(entity!);
|
||||
return {
|
||||
relations,
|
||||
actions,
|
||||
daas,
|
||||
dras,
|
||||
hasDirectActionAuth: daas.length > 0,
|
||||
hasDirectRelationAuth: dras.length > 0,
|
||||
deduceRelationAttr,
|
||||
};
|
||||
},
|
||||
lifetimes: {
|
||||
async ready() {
|
||||
const { entity } = this.props;
|
||||
// 现在的数据结构,可以在任意对象上增加relation,这个if以后并不成立。 by Xc
|
||||
if (this.features.relationAuth.hasRelation(entity!)) {
|
||||
await this.features.cache.refresh('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: entity as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
}
|
||||
});
|
||||
// 没定义entity,显式的reRender
|
||||
this.reRender();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onActionsSelected(checkedActions: string[]) {
|
||||
this.setState({ checkedActions });
|
||||
},
|
||||
onRelationsSelected(relationIds: string[]) {
|
||||
this.setState({ relationIds });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"action":"操作", "relation":"关系", "sourceEntity": "源对象", "path": "路径"}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.container {
|
||||
background-color: var(--oak-bg-color-container);
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Row, Radio, Col, Tabs, Checkbox, Table, Space, Button } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
const { Title, Text } = Typography;
|
||||
import ActionAuth from '../actionAuth';
|
||||
import RelationAuth from '../relationAuth';
|
||||
import { WebComponentProps } from '../../../types/Page';
|
||||
import { useState } from 'react';
|
||||
import Styles from './web.pc.module.less';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(props: WebComponentProps<ED, keyof ED, false, {
|
||||
entity: keyof ED;
|
||||
actions: string[];
|
||||
daas: AuthCascadePath<ED>[];
|
||||
dras: AuthCascadePath<ED>[];
|
||||
checkedActions: string[];
|
||||
relations: ED['relation']['OpSchema'][];
|
||||
relationIds: string[];
|
||||
hasDirectActionAuth: boolean;
|
||||
hasDirectRelationAuth: boolean;
|
||||
deduceRelationAttr?: string;
|
||||
}, {
|
||||
onActionsSelected: (actions: string[]) => void;
|
||||
onRelationsSelected: (relationIds: string[]) => void;
|
||||
}>) {
|
||||
const { oakFullpath, entity, actions, checkedActions, hasDirectActionAuth, hasDirectRelationAuth,
|
||||
dras, daas, relationIds, relations, deduceRelationAttr } = props.data;
|
||||
const { onActionsSelected, onRelationsSelected, t } = props.methods;
|
||||
const [tab, setTab] = useState('actionAuth');
|
||||
|
||||
const items = deduceRelationAttr ? [
|
||||
{
|
||||
label: 'deduceRelation',
|
||||
key: 'deduceRelation',
|
||||
children: (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
对象的actionAuth已被deduce到[<b>{deduceRelationAttr}</b>]属性上
|
||||
</div>
|
||||
)
|
||||
},
|
||||
] : [
|
||||
{
|
||||
label: 'actionAuth',
|
||||
key: 'actionAuth',
|
||||
children: (
|
||||
<ActionAuth
|
||||
entity={entity}
|
||||
oakPath={oakFullpath && `${oakFullpath}.actionAuths`}
|
||||
actions={checkedActions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
/* if (hasDirectActionAuth) {
|
||||
items.push(
|
||||
{
|
||||
label: 'directActionAuth',
|
||||
key: 'directActionAuth',
|
||||
children: (
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
key: '2',
|
||||
title: t('sourceEntity'),
|
||||
width: 100,
|
||||
render: (value, record) => {
|
||||
return record[2];
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: t('path'),
|
||||
width: 200,
|
||||
render: (value, record) => {
|
||||
return record[1];
|
||||
},
|
||||
},
|
||||
]}
|
||||
dataSource={daas}
|
||||
pagination={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
} */
|
||||
|
||||
// if (relations?.length > 0) {
|
||||
// items.push(
|
||||
// {
|
||||
// label: 'relationAuth',
|
||||
// key: 'relationAuth',
|
||||
// children: (
|
||||
// <RelationAuth
|
||||
// entity={entity}
|
||||
// oakPath={oakFullpath && `${oakFullpath}.relationAuths`}
|
||||
// relationIds={relationIds}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
const ActionSelector = actions && (
|
||||
<Row style={{ width: '100%' }} justify="center" align="middle">
|
||||
<Text strong>{t('action')}:</Text>
|
||||
<Row style={{ flex: 1, marginLeft: 10 }} justify="start" align="middle" wrap>
|
||||
<Checkbox.Group
|
||||
options={actions}
|
||||
value={checkedActions}
|
||||
onChange={(value) => onActionsSelected(value as string[])}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
const RelationSelector = relations && (
|
||||
<Row style={{ width: '100%' }} justify="center" align="middle">
|
||||
<Text strong>{t('relation')}:</Text>
|
||||
<Row style={{ flex: 1, marginLeft: 10 }} justify="start" align="middle" wrap>
|
||||
<Checkbox.Group
|
||||
options={relations.map(ele => ({ label: ele.name!, value: ele.id! }))}
|
||||
value={relationIds}
|
||||
onChange={(value) => onRelationsSelected(value as string[])}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
const showActionSelector = ['actionAuth', 'directActionAuth'].includes(tab);
|
||||
const showRelationSelector = ['relationAuth', 'directRelationAuth'].includes(tab);
|
||||
|
||||
return (
|
||||
<div className={Styles.container}>
|
||||
<Row justify="center" style={{ margin: 20, padding: 10, minHeight: 100 }} align="middle">
|
||||
<Col span={8}>
|
||||
<Row style={{ width: '100%' }} justify="center" align="middle">
|
||||
<Text strong>{t('actionAuth:attr.destEntity')}:</Text>
|
||||
<Text code style={{ marginLeft: 10 }}>{entity}</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
{showActionSelector ? ActionSelector : (showRelationSelector && RelationSelector)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
type="card"
|
||||
size="large"
|
||||
items={items}
|
||||
onChange={(key) => setTab(key)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"component": true
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import {
|
||||
CardDef,
|
||||
ED,
|
||||
OakAbsAttrDef,
|
||||
onActionFnDef,
|
||||
} from '../../../types/AbstractComponent';
|
||||
export default OakComponent({
|
||||
isList: true,
|
||||
formData() {
|
||||
const { data, links } = this.features.relationAuth.getEntityGraph();
|
||||
return {
|
||||
data,
|
||||
links,
|
||||
};
|
||||
},
|
||||
properties: {
|
||||
onEntityClicked: (entity: string) => undefined as void,
|
||||
},
|
||||
methods: {
|
||||
onEntityClicked(entity: string) {
|
||||
if (this.props.onEntityClicked) {
|
||||
this.props.onEntityClicked(entity);
|
||||
} else {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
'直接使用relation/entityList作为page用法即将废除,请使用自定义页面包裹'
|
||||
);
|
||||
}
|
||||
this.features.navigator.navigateTo(
|
||||
{
|
||||
url: '/relation/entity',
|
||||
},
|
||||
{
|
||||
entity,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.container {
|
||||
background-color: var(--oak-bg-color-container);
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Row, Switch, Col, Input, Form } from 'antd';
|
||||
import ReactEcharts from 'echarts-for-react';
|
||||
import { useState } from 'react';
|
||||
import { uniq } from 'oak-domain/lib/utils/lodash';
|
||||
import Styles from './web.pc.module.less';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
keyof ED,
|
||||
true,
|
||||
{
|
||||
data: Array<{ name: string, x?: number, y?: number }>;
|
||||
links: Array<{ source: string, target: string }>;
|
||||
},
|
||||
{
|
||||
onEntityClicked: (entity: string) => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { data, links } = props.data;
|
||||
const { onEntityClicked } = props.methods;
|
||||
const [search, setSearch] = useState('');
|
||||
const [strict, setStrict] = useState(false);
|
||||
|
||||
const keywords = search && search.split(',');
|
||||
let data2 = data;
|
||||
let links2 = links;
|
||||
|
||||
if (keywords) {
|
||||
if (!strict) {
|
||||
links2 = links.filter(
|
||||
ele => {
|
||||
if (keywords.find(k => ele.source.includes(k) || ele.target.includes(k))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
data2 = uniq(links2.map(ele => ele.source).concat(links2.map(ele => ele.target))).map(
|
||||
ele => ({ name: ele })
|
||||
);
|
||||
}
|
||||
else {
|
||||
links2 = links.filter(
|
||||
ele => {
|
||||
if (keywords.find(k => ele.source.includes(k) && ele.target.includes(k))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
data2 = data.filter(
|
||||
ele => {
|
||||
if (keywords.find(k => ele.name.includes(k))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={Styles.container}>
|
||||
<Form
|
||||
style={{
|
||||
margin: 20,
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="filter"
|
||||
>
|
||||
<>
|
||||
<Input
|
||||
onChange={({ currentTarget }) => setSearch(currentTarget.value)}
|
||||
allowClear
|
||||
/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="strict mode"
|
||||
>
|
||||
<>
|
||||
<Switch
|
||||
checked={strict}
|
||||
onChange={(checked) => setStrict(checked)}
|
||||
/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<ReactEcharts
|
||||
style={{ width: '100%', height: '100%', minHeight: 750 }}
|
||||
option={{
|
||||
tooltip: {},
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
force: {
|
||||
initLayout: 'circular',
|
||||
gravity: 0,
|
||||
repulsion: [10, 80],
|
||||
edgeLength: [10, 50]
|
||||
},
|
||||
data: data2,
|
||||
links: links2,
|
||||
lineStyle: {
|
||||
opacity: 0.9,
|
||||
width: 2,
|
||||
curveness: 0
|
||||
},
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
autoCurveness: true,
|
||||
roam: true,
|
||||
draggable: true,
|
||||
edgeSymbol: ['none', 'arrow'],
|
||||
edgeSymbolSize: 7,
|
||||
emphasis: {
|
||||
scale: true,
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
focus: 'adjacency',
|
||||
lineStyle: {
|
||||
width: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
}}
|
||||
notMerge={true}
|
||||
lazyUpdate={false}
|
||||
onEvents={{
|
||||
click: (info: any) => {
|
||||
const { data, dataType } = info;
|
||||
if (dataType === 'node') {
|
||||
const { name } = data;
|
||||
onEntityClicked(name);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"component": true
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import {
|
||||
CardDef,
|
||||
ED,
|
||||
OakAbsAttrDef,
|
||||
onActionFnDef,
|
||||
} from '../../../types/AbstractComponent';
|
||||
export default OakComponent({
|
||||
entity: 'relation',
|
||||
isList: true,
|
||||
projection: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
},
|
||||
formData({ data = [] }) {
|
||||
// 根据设计,这里如果同一个entity上同时存在有entityId和没有entityId的,则隐藏掉没有entityId的行
|
||||
const relations = data.filter((ele) => !!ele.entityId);
|
||||
data.forEach((ele) => {
|
||||
if (!ele.entityId) {
|
||||
if (
|
||||
!relations.find(
|
||||
(ele2) => ele2.entity === ele.entity && ele2.entityId
|
||||
)
|
||||
) {
|
||||
relations.push(ele);
|
||||
}
|
||||
} else {
|
||||
assert(ele.entityId === this.props.entityId);
|
||||
}
|
||||
});
|
||||
|
||||
const hasRelationEntites =
|
||||
this.features.relationAuth.getHasRelationEntities();
|
||||
return {
|
||||
relations,
|
||||
hasRelationEntites,
|
||||
};
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { entity, entityId } = this.props;
|
||||
const filter = {};
|
||||
if (entity) {
|
||||
Object.assign(filter, { entity });
|
||||
}
|
||||
if (entityId) {
|
||||
Object.assign(filter, {
|
||||
$or: [
|
||||
{
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
entityId,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
Object.assign(filter, {
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
return filter;
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
entity: '' as keyof ED,
|
||||
entityId: '',
|
||||
},
|
||||
features: ['relationAuth'],
|
||||
methods: {
|
||||
onActionClicked(id: string, entity: string) {
|
||||
this.features.navigator.navigateTo(
|
||||
{
|
||||
url: '/relation/actionAuthBySource',
|
||||
},
|
||||
{
|
||||
relationId: id,
|
||||
entity,
|
||||
}
|
||||
);
|
||||
},
|
||||
onRelationClicked(id: string, entity: string) {
|
||||
this.features.navigator.navigateTo(
|
||||
{
|
||||
url: '/relation/relationAuthBySource',
|
||||
},
|
||||
{
|
||||
relationId: id,
|
||||
entity,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import PureList from '../../../components/list';
|
||||
import FilterPanel from '../../../components/filterPanel';
|
||||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
'relation',
|
||||
true,
|
||||
{
|
||||
relations: RowWithActions<ED, 'relation'>[];
|
||||
hasRelationEntites: string[];
|
||||
},
|
||||
{
|
||||
onActionClicked: (id: string, entity: keyof ED) => void;
|
||||
onRelationClicked: (id: string, entity: keyof ED) => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { relations, oakLoading, oakFullpath, hasRelationEntites } = props.data;
|
||||
const { onActionClicked, onRelationClicked } = props.methods;
|
||||
return (
|
||||
<>
|
||||
<FilterPanel
|
||||
entity="relation"
|
||||
oakPath={oakFullpath}
|
||||
columns={[
|
||||
{
|
||||
attr: 'entity',
|
||||
selectProps: {
|
||||
options: hasRelationEntites ? hasRelationEntites.map(
|
||||
ele => ({
|
||||
label: ele,
|
||||
value: ele,
|
||||
})
|
||||
) : [],
|
||||
}
|
||||
},
|
||||
{
|
||||
attr: 'entityId',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<PureList
|
||||
entity="relation"
|
||||
loading={oakLoading}
|
||||
data={relations || []}
|
||||
attributes={['entity', 'entityId', 'name', 'display']}
|
||||
extraActions={[{ action: 'action', label: '动作', show: true }, { action: 'relation', label: '关系', show: true }]}
|
||||
onAction={(row: ED['relation']['OpSchema'], action: string) => {
|
||||
const { id, entity } = row;
|
||||
if (action === 'action') {
|
||||
onActionClicked(id!, entity!);
|
||||
}
|
||||
else {
|
||||
assert(action === 'relation');
|
||||
onRelationClicked(id!, entity!);
|
||||
}
|
||||
}}
|
||||
// disabledOp={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import assert from "assert";
|
||||
import { AuthCascadePath } from "oak-domain/lib/types";
|
||||
import { uniq, pull, difference } from 'oak-domain/lib/utils/lodash';
|
||||
import { ED } from "../../../types/AbstractComponent";
|
||||
|
||||
export default OakComponent({
|
||||
entity: 'relationAuth',
|
||||
projection: {
|
||||
id: 1,
|
||||
sourceRelationId: 1,
|
||||
destRelationId: 1,
|
||||
},
|
||||
isList: true,
|
||||
properties: {
|
||||
entity: '' as keyof ED,
|
||||
relationIds: [] as string[],
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { entity, relationIds } = this.props;
|
||||
// 这里不能用relationIds过滤,否则没法处理relationId的反选
|
||||
return {
|
||||
destRelation: {
|
||||
entity: entity as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
/* if (relationIds && relationIds.length > 0) {
|
||||
return {
|
||||
destRelationId: {
|
||||
$in: relationIds,
|
||||
},
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
destRelation: {
|
||||
entity: entity as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
} */
|
||||
},
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
pageSize: 1000,
|
||||
currentPage: 0,
|
||||
},
|
||||
formData({ data }) {
|
||||
const { entity, relationIds } = this.props;
|
||||
const auths = this.features.relationAuth.getCascadeRelationAuths(entity!, true);
|
||||
const sourceEntities = auths.map(
|
||||
ele => ele[2]
|
||||
) as string[];
|
||||
const sourceRelations = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: {
|
||||
$in: sourceEntities,
|
||||
},
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
relationAuths: data,
|
||||
auths,
|
||||
sourceRelations,
|
||||
};
|
||||
},
|
||||
listeners: {
|
||||
relationIds(prev, next) {
|
||||
const relationAuths = this.features.runningTree.getFreshValue(this.state.oakFullpath);
|
||||
if (relationAuths) {
|
||||
const { relationIds } = next;
|
||||
(relationAuths as ED['relationAuth']['OpSchema'][]).forEach(
|
||||
(relationAuth) => {
|
||||
if (relationAuth.$$createAt$$ === 1 && !relationIds.includes(relationAuth.destRelationId)) {
|
||||
const { id } = relationAuth;
|
||||
this.removeItem(id);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
this.reRender();
|
||||
},
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
const { entity } = this.props;
|
||||
const auths = this.features.relationAuth.getCascadeRelationAuths(entity!, true);
|
||||
const sourceEntities = auths.map(
|
||||
ele => ele[2]
|
||||
) as string[];
|
||||
this.features.cache.refresh('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: {
|
||||
$in: sourceEntities,
|
||||
},
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(checked: boolean, sourceRelationId: string, path: string, relationAuths?: ED['relationAuth']['OpSchema'][]) {
|
||||
const { relationIds } = this.props;
|
||||
assert(relationIds);
|
||||
if (checked) {
|
||||
if (relationAuths) {
|
||||
// 这些relationAuths可能是已经带有relationIds的,也有可能是被删除掉的,比较复杂
|
||||
const includedRelationIds = [] as string[];
|
||||
for (const auth of relationAuths) {
|
||||
if (auth.$$deleteAt$$) {
|
||||
this.recoverItem(auth.id);
|
||||
}
|
||||
includedRelationIds.push(auth.destRelationId);
|
||||
}
|
||||
const restRelationIds = difference(relationIds, includedRelationIds);
|
||||
restRelationIds.forEach(
|
||||
(relationId) => this.addItem({
|
||||
sourceRelationId,
|
||||
path,
|
||||
destRelationId: relationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
else {
|
||||
relationIds.forEach(
|
||||
relationId => this.addItem({
|
||||
sourceRelationId,
|
||||
path,
|
||||
destRelationId: relationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(relationAuths);
|
||||
relationAuths.forEach(
|
||||
relationAuth => {
|
||||
if (!relationAuth.$$deleteAt$$) {
|
||||
this.removeItem(relationAuth.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
confirm() {
|
||||
this.execute();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"confirm": "确认", "reset": "重置", "grantedRoles": "授权角色"}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import { Table, Checkbox, Button, Row, Radio, Col } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
const { Title, Text } = Typography;
|
||||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { intersection, difference } from 'oak-domain/lib/utils/lodash';
|
||||
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
'relationAuth',
|
||||
true,
|
||||
{
|
||||
relationIds: string[];
|
||||
relationAuths: ED['relationAuth']['OpSchema'][];
|
||||
auths: AuthCascadePath<ED>[];
|
||||
sourceRelations: ED['relation']['OpSchema'][];
|
||||
},
|
||||
{
|
||||
onChange: (checked: boolean, relationId: string, path: string, relationAuths?: ED['relationAuth']['OpSchema'][]) => void;
|
||||
confirm: () => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { relationIds, relationAuths, oakDirty, auths, sourceRelations } = props.data;
|
||||
const { onChange, t, clean, confirm } = props.methods;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
key: '1',
|
||||
title: t('relationAuth:attr.sourceEntity'),
|
||||
width: 100,
|
||||
render: (value, record) => record[2],
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: t('relationAuth:attr.path'),
|
||||
width: 200,
|
||||
render: (value, record) => record[1],
|
||||
},
|
||||
{
|
||||
fixed: 'right',
|
||||
title: t('grantedRoles'),
|
||||
key: 'roles',
|
||||
width: 300,
|
||||
render: (value, record) => {
|
||||
const sourceEntity = record[2];
|
||||
const relations = sourceRelations.filter(
|
||||
ele => ele.entity === sourceEntity && !relationIds.includes(ele.id)
|
||||
);
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{
|
||||
relations?.map(
|
||||
(r) => {
|
||||
const disabled = relationIds.length === 0;
|
||||
let checked = false, indeterminate = false;
|
||||
if (!disabled && relationAuths) {
|
||||
const includedRelationIds = [] as string[];
|
||||
for(const auth of relationAuths) {
|
||||
if (!auth.$$deleteAt$$ && auth.sourceRelationId === r.id && auth.path === record[1]) {
|
||||
includedRelationIds.push(auth.destRelationId);
|
||||
}
|
||||
}
|
||||
checked = difference(relationIds, includedRelationIds).length === 0;
|
||||
indeterminate = !checked && intersection(relationIds, includedRelationIds).length > 0;
|
||||
}
|
||||
return (
|
||||
<Checkbox
|
||||
disabled={disabled}
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
onChange={({ target }) => {
|
||||
const { checked } = target;
|
||||
const refRelationAuths = relationAuths?.filter(
|
||||
ele => ele.sourceRelationId === r.id && ele.path === record[1]
|
||||
&& relationIds!.includes(ele.destRelationId)
|
||||
);
|
||||
|
||||
onChange(checked, r.id, record[1], refRelationAuths)
|
||||
}}
|
||||
>
|
||||
{r.name}
|
||||
</Checkbox>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]}
|
||||
dataSource={auths}
|
||||
pagination={false}
|
||||
/>
|
||||
<Row justify="end" style={{ marginTop: 20, padding: 5 }}>
|
||||
<Button
|
||||
style={{ marginRight: 10 }}
|
||||
type='primary'
|
||||
disabled={!oakDirty}
|
||||
onClick={() => confirm()}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!oakDirty}
|
||||
onClick={() => clean()}
|
||||
>
|
||||
{t("reset")}
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
import assert from "assert";
|
||||
import { uniq } from 'oak-domain/lib/utils/lodash';
|
||||
import { AuthCascadePath } from "oak-domain/lib/types";
|
||||
import { ED } from "../../../types/AbstractComponent";
|
||||
|
||||
export default OakComponent({
|
||||
entity: 'relationAuth',
|
||||
projection: {
|
||||
id: 1,
|
||||
sourceRelationId: 1,
|
||||
sourceRelation: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
},
|
||||
paths: 1,
|
||||
destRelationId: 1,
|
||||
destRelation: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
entityId: 1,
|
||||
},
|
||||
},
|
||||
isList: true,
|
||||
properties: {
|
||||
relationId: '',
|
||||
entity: '' as keyof ED,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { relationId } = this.props;
|
||||
assert(relationId);
|
||||
return {
|
||||
sourceRelationId: relationId,
|
||||
};
|
||||
}
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
pageSize: 1000,
|
||||
currentPage: 0,
|
||||
},
|
||||
formData({ data }) {
|
||||
const { entity, relationId } = this.props;
|
||||
const [relation] = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
id: relationId,
|
||||
},
|
||||
});
|
||||
const { name } = relation || {};
|
||||
const cascadeEntities = this.features.relationAuth.getCascadeRelationAuthsBySource(entity!);
|
||||
const cascadeEntityRelations = cascadeEntities.map(
|
||||
(ele) => {
|
||||
const [de, p, se] = ele;
|
||||
|
||||
// 可能的relations只查询系统规定的relation,不考虑定制的relation
|
||||
const relations = this.features.cache.get('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
name: 1,
|
||||
entityId: 1,
|
||||
display: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: de as string,
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
$not: {
|
||||
entity: entity as string,
|
||||
id: relationId,
|
||||
}, // 自己不能管自己的权限
|
||||
},
|
||||
});
|
||||
|
||||
//已授权的relation中可能有定制的relationId
|
||||
const authedRelations = data ? data.filter(
|
||||
(ele) => ele.path === p
|
||||
) : [];
|
||||
return {
|
||||
entity: de,
|
||||
path: p,
|
||||
relations,
|
||||
authedRelations,
|
||||
};
|
||||
}
|
||||
).filter(
|
||||
(ele) => ele.relations.length > 0 || ele.authedRelations.length > 0
|
||||
);
|
||||
return {
|
||||
relationName: name,
|
||||
cascadeEntityRelations,
|
||||
};
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
const { entity } = this.props;
|
||||
const cascadeRelationEntities = this.features.relationAuth.getCascadeRelationAuthsBySource(entity!);
|
||||
const cascadeEntities = uniq(cascadeRelationEntities.map(ele => ele[0])) as string[];
|
||||
if (cascadeEntities.length > 0) {
|
||||
this.features.cache.refresh('relation', {
|
||||
data: {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
name: 1,
|
||||
entityId: 1,
|
||||
},
|
||||
filter: {
|
||||
entity: {
|
||||
$in: cascadeEntities,
|
||||
},
|
||||
entityId: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(relationId: string, checked: boolean, relationAuthId?: string, path?: string) {
|
||||
if (checked) {
|
||||
if (relationAuthId) {
|
||||
this.recoverItem(relationAuthId);
|
||||
}
|
||||
else {
|
||||
this.addItem({
|
||||
sourceRelationId: this.props.relationId!,
|
||||
path,
|
||||
destRelationId: relationId,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(relationAuthId);
|
||||
this.removeItem(relationAuthId);
|
||||
}
|
||||
},
|
||||
confirm() {
|
||||
this.execute();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"confirm": "确认", "reset": "重置"}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import { Table, Checkbox, Button, Row } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
const { Title, Text } = Typography;
|
||||
import { RowWithActions, WebComponentProps } from '../../../types/Page';
|
||||
import { AuthCascadePath, EntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
|
||||
type ED = EntityDict & BaseEntityDict;
|
||||
|
||||
export default function render(
|
||||
props: WebComponentProps<
|
||||
ED,
|
||||
'relationAuth',
|
||||
true,
|
||||
{
|
||||
entity: string;
|
||||
relationName: string;
|
||||
cascadeEntityRelations: Array<{
|
||||
entity: keyof ED;
|
||||
path: string;
|
||||
relations: ED['relation']['Schema'][];
|
||||
authedRelations: ED['relationAuth']['Schema'][];
|
||||
}>;
|
||||
},
|
||||
{
|
||||
onChange: (relationId: string, checked: boolean, relationAuthId?: string, path?: string) => void;
|
||||
confirm: () => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { cascadeEntityRelations, oakDirty, entity, relationName } = props.data;
|
||||
const { onChange, t, clean, confirm } = props.methods;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Row justify="center" style={{ margin: 20, padding: 10 }}>
|
||||
<Row style={{ marginRight: 10 }}>
|
||||
<Title level={4}>当前对象:</Title>
|
||||
<Text code>{entity}</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Title level={4}>当前角色:</Title>
|
||||
<Text code>{relationName}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
key: '1',
|
||||
title: '对象',
|
||||
width: 100,
|
||||
render: (value, record) => {
|
||||
const { entity } = record;
|
||||
return entity;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: '路径',
|
||||
width: 200,
|
||||
render: (value, record) => {
|
||||
const { path } = record;
|
||||
return path;
|
||||
},
|
||||
},
|
||||
{
|
||||
fixed: 'right',
|
||||
title: '角色',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
render: (value, record) => {
|
||||
const { relations, authedRelations, path } = record;
|
||||
// 可能存在定制的relation
|
||||
const customizedRelationsAuthed: ED['relation']['Schema'][] = [];
|
||||
const relationIdAuthed: string[] = [];
|
||||
authedRelations.forEach(
|
||||
(ele) => {
|
||||
if (!ele.$$deleteAt$$) {
|
||||
if (relations.find(ele2 => ele2.id === ele.destRelationId)) {
|
||||
relationIdAuthed.push(ele.destRelationId);
|
||||
}
|
||||
else {
|
||||
customizedRelationsAuthed.push(ele.destRelation!);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
{relations.map(
|
||||
(ele) => {
|
||||
const authed = relationIdAuthed.includes(ele.id);
|
||||
return (
|
||||
<Checkbox
|
||||
key={ele.id!}
|
||||
checked={authed}
|
||||
onChange={({ target }) => {
|
||||
const { checked } = target;
|
||||
const relationAuth = authedRelations.find(ele2 => ele2.destRelationId === ele.id);
|
||||
if (relationAuth) {
|
||||
onChange(ele.id, checked, relationAuth.id);
|
||||
}
|
||||
else {
|
||||
onChange(ele.id, checked, undefined, path);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ele.display || ele.name}
|
||||
</Checkbox>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{customizedRelationsAuthed.map(
|
||||
(ele) => <Checkbox
|
||||
key={ele.id!}
|
||||
checked={true}
|
||||
disabled={true}
|
||||
>
|
||||
{ele.display || ele.name}
|
||||
</Checkbox>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]}
|
||||
dataSource={cascadeEntityRelations}
|
||||
pagination={false}
|
||||
/>
|
||||
<Row justify="end" style={{ marginTop: 20, padding: 5 }}>
|
||||
<Button
|
||||
style={{ marginRight: 10 }}
|
||||
type='primary'
|
||||
disabled={!oakDirty}
|
||||
onClick={() => confirm()}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!oakDirty}
|
||||
onClick={() => clean()}
|
||||
>
|
||||
{t("reset")}
|
||||
</Button>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -252,6 +252,9 @@ export default OakComponent({
|
|||
const dASameActionAuth = actionAuths.find((ele) => difference(actions, ele.deActions).length === 0);
|
||||
// 存在deActions相同,paths push并做去重处理
|
||||
if (dASameActionAuth) {
|
||||
if (dASameActionAuth.$$deleteAt$$ && dASameActionAuth.$$deleteAt$$ === 1) {
|
||||
this.recoverItem(dASameActionAuth.id);
|
||||
}
|
||||
this.updateItem({
|
||||
paths: dASameActionAuth.paths.concat(path),
|
||||
}, dASameActionAuth.id)
|
||||
|
|
|
|||
Loading…
Reference in New Issue