diff --git a/src/executor/open/view/form/index.tsx b/src/executor/open/view/form/index.tsx index 2d5db52bda6ef43df709861e0125554f9e5e3cf2..0fa8b5d04b2ec7a6ad1b6d3420413c6c4574a674 100644 --- a/src/executor/open/view/form/index.tsx +++ b/src/executor/open/view/form/index.tsx @@ -7,29 +7,28 @@ import { command, model, schema } from '@/ts/base'; import { IDirectory, TargetType } from '@/ts/core'; import { Spin, message, Layout, Empty } from 'antd'; import CustomStore from 'devextreme/data/custom_store'; -import React, { useRef, useState, createRef, useEffect, useCallback } from 'react'; +import React, { useRef, useState, useEffect, useCallback } from 'react'; import { ImCopy, ImHistory, ImProfile, ImShuffle, ImTicket } from 'react-icons/im'; import { MdPrint, MdOutlinePrint } from 'react-icons/md'; import { formatNumber } from '@/utils'; -import { DataGrid, TreeView } from 'devextreme-react'; +import { DataGrid } from 'devextreme-react'; import { IForm } from '@/ts/core'; import { Form } from '@/ts/core/thing/standard/form'; import Sider from 'antd/lib/layout/Sider'; -import { TreeViewTypes } from 'devextreme-react/cjs/tree-view'; import { HistoryFlowView } from '../../form/history/flow'; import { HistoryFileView } from '../../form/history/file'; import ThingView from '../../form/detail'; -import InnerTree from './innerTree'; +import MemberTree from './memberTree'; +import SpeciesMenu from './speciesMenu'; import '../../form/index.less'; -import { deptItemType } from './treeDataUtils'; +import { DeptItemType } from './treeDataUtils'; import orgCtrl from '@/ts/controller'; import { TableModel } from '@/ts/base/model'; import { createRoot } from 'react-dom/client'; import PrintTemplate from '../../form/printTemplate'; -import { ReportTree } from '@/ts/core/thing/standard/reporttree'; import _ from 'lodash'; interface IProps { - form: schema.XForm; + form: schema.XForm & { options?: { organizationTree: string } }; directory: IDirectory; finished: () => void; } @@ -68,13 +67,11 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { const [select, setSelcet] = useState(); const editData: { rows: schema.XThing[] } = { rows: [] }; let ref = useRef>(null); - const treeViewRef = createRef>(); metaForm.fields = fields; const FormBrower: React.FC = () => { const searchType = form.cmdType == 'openMember' && form.options?.organizationTree ? 'tree' : 'members'; - const [treeData, setTreeData] = useState([]); - const [selectMenu, setSelcetMenu] = useState([]); + const [selectMenu, setSelectMenu] = useState([]); const [treeMatchs, setTreeMatchs] = useState>({}); if ( directory.spaceId !== directory.target.belongId && @@ -90,69 +87,8 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { /> ); } - const loadSpeciesItemMenu = () => { - const speciesFields = fields.filter((i) => i.options?.species); - const result: any[] = []; - for (const filed of speciesFields) { - const newFiled = filed; - result.push({ - key: filed.id, - item: newFiled, - label: filed.name, - hasItems: true, - rootCode: filed.code, - typeName: filed.valueType, - children: [], - }); - } - return result; - }; - const treeItemMenu = async () => { - let rootCode: string = ''; - const loopData = (arr: any[]): any[] => { - return arr.map((item: any) => { - item.value = item.targetId; - return { - key: item.id, - label: item.name, - rootCode, - hasItems: item.children.length > 0, - typeName: '组织树', - children: loopData(item.children), - value: item.targetId, - item: item, - }; - }); - }; - let result = []; - if (searchType === 'tree' && form.options.organizationTree) { - const res = await directory.resource.reportTreeColl.find([ - form.options.organizationTree, - ]); - if (Array.isArray(res) && res.length > 0) { - const _field = res[0]; - const tree = new ReportTree(_field, directory); - let nodes = _.cloneDeep(await tree.loadNodes(true)); - const root = nodes.find((n) => n.id == _field.rootNodeId); - if (!root) { - return []; - } - rootCode = root.code; - let subTree = await tree.loadSubTree(root); - result = loopData(subTree); - } - } - return result; - }; - - useEffect(() => { - const result = loadSpeciesItemMenu(); - setTreeData(result); - setSelcetMenu([]); - }, []); - const [center, setCenter] = useState(<>); - if (!selectMenu.length && !treeData) return <>; + if (!loaded) return <>; const loadDeatil = () => { if (select) { @@ -216,8 +152,7 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { selectMenu.forEach((sel) => { const item = sel.itemData?.item; if (!item) return; - - if (item.value && ['分类项', '分类型'].includes(item.typeName)) { + if (item.value && ['分类项', '分类型'].includes(sel.itemData.typeName)) { loadOptions.userData.push(item.value); } else if (searchType === 'tree' && sel.itemData?.typeName === '组织树') { loadOptions.belongId = directory.target.belongId; @@ -273,10 +208,7 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { { name: 'title', location: 'before', - html: - selectMenu.length > 0 - ? ` 当前条件(${selectMenu.length}) ` - : '', + html: selectMenu.length ? ` 当前条件(${selectMenu.map((v) => v.text )}) ` : '', }, { name: 'print', @@ -472,162 +404,7 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { ); }; - const customTreeView = () => { - const getItem = async (node: TreeViewTypes.Node, fileds: any) => { - const arr = fileds.map((filed: any) => { - return filed.id; - }); - const children = await metaForm.loadItemsByParentId( - [node.itemData?.item.speciesId], - arr, - ); - return children; - }; - - const getSpeciesChildren = async (node: TreeViewTypes.Node) => { - let result: any[] = [], - children: any[] = [], - items: any[] = []; - if (node.parent) { - children = await metaForm.loadItemsByParentId( - [node.itemData?.item.speciesId], - [node.key], - ); - items = await getItem(node, children); - } else { - children = await metaForm.loadItemsByParentId( - [node.itemData?.item.speciesId], - [undefined], - ); - items = await getItem(node, children); - } - for (const filed of children) { - let arr: any[] = []; - items.forEach((item: any) => { - if (item.parentId === filed.id) { - const newFiled: any = item; - newFiled.value = 'S' + newFiled.id; - arr.push({ - key: item.id, - item: newFiled, - label: item.name, - parentId: item.parentId, - hasItems: false, - rootCode: item.code, - typeName: '分类型', - children: [], - }); - } - }); - const newFiled = filed; - newFiled.value = 'S' + newFiled.id; - result.push({ - key: filed.id, - item: newFiled, - label: filed.name, - parentId: node.key, - rootCode: node.itemData?.rootCode, - hasItems: arr.length > 0 ? true : false, - typeName: '分类型', - children: arr, - }); - } - return result; - }; - - const getOrganizationChildren = async (node: TreeViewTypes.Node) => { - const loopData = (arr: any[], pid?: string): any[] => { - return arr.map((item: any) => { - return { - key: item.id, - label: item.name, - parentId: pid, - rootCode: node.itemData?.rootCode, - hasItems: item.children.length > 0, - typeName: '组织树', - value: item.targetId, - children: loopData(item.children, item.id), - item: item, - }; - }); - }; - - if (node.itemData?.item) { - if (node.itemData.children) { - return node.itemData.children.map((v: { [x: string]: any }) => { - v['parentId'] = node.key; - return v; - }); - } - const tree = new ReportTree(node.itemData.item, directory); - let nodes = _.cloneDeep(await tree.loadNodes(true)); - const root = nodes.find((n) => n.id == node.itemData?.item.rootNodeId); - if (!root) { - return []; - } - let subTree = await tree.loadSubTree(root); - return loopData(subTree); - } - - return []; - }; - - const createChildren = async (node: TreeViewTypes.Node) => { - let result: any[] = []; - if (node) { - switch (node.itemData!.typeName) { - case '组织树': - result = await getOrganizationChildren(node); - break; - case '分类型': - case '分类项': - result = await getSpeciesChildren(node); - break; - default: - console.log('暂不支持类型:', node); - result = []; - break; - } - } else { - result = [...(await treeItemMenu()), ...loadSpeciesItemMenu()]; - } - return result; - }; - - return ( -
- { - const currentItem = e.node; - const allSelecteds = e.component.getSelectedNodes(); - if (currentItem?.selected) { - const oldSelectKey = allSelecteds.find( - (v) => - v.itemData?.rootCode === currentItem?.itemData?.rootCode && - v.itemData?.key !== currentItem?.itemData?.key, - ); - oldSelectKey && - treeViewRef.current?.instance.unselectItem(oldSelectKey.itemData); - } - setSelcetMenu(allSelecteds.filter((v) => v.selected)); - }} - /> -
- ); - }; - const creatFilterMatchs = (items: deptItemType[]) => { + const creatFilterMatchs = (items: DeptItemType[]) => { const viewDataRange: any = form.options?.viewDataRange || { company: 'belongId' }; const result: any[] = []; const filterData: Record = {}; @@ -671,13 +448,19 @@ const DictFormView: React.FC = ({ form, directory, finished }) => { {searchType === 'members' && ( - )}
- {customTreeView()} +
{loadContent()}
diff --git a/src/executor/open/view/form/innerTree.tsx b/src/executor/open/view/form/memberTree.tsx similarity index 95% rename from src/executor/open/view/form/innerTree.tsx rename to src/executor/open/view/form/memberTree.tsx index a0e4ae5f553f42ce8ae6bb5245377b3f63d7c318..ea9380529734ec502e38048cb1ac7a2674978e99 100644 --- a/src/executor/open/view/form/innerTree.tsx +++ b/src/executor/open/view/form/memberTree.tsx @@ -2,22 +2,22 @@ import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { ITarget } from '@/ts/core'; import { Input, Popover, Switch, Tree } from 'antd'; import cls from './index.module.less'; -import { deptItemType, getTreeDatas } from './treeDataUtils'; +import { DeptItemType, getTreeDatas } from './treeDataUtils'; import useAsyncLoad from '@/hooks/useAsyncLoad'; import { MemberList } from '@/components/Common/SelectMember/memberSelector'; import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; import { CaretDownOutlined } from '@ant-design/icons'; -interface InnerTreeType { +interface MemberTreeType { target: ITarget; onSelectChanged(items: T[]): void; } const { Search } = Input; -const InnerTree = ({ +const MemberTree = ({ target, onSelectChanged, -}: InnerTreeType) => { +}: MemberTreeType) => { const [selectedTarget, setSelectedTarget] = useState(); const [isLinkChildren, setIsLinkChildren] = useState(false); const [selectedKeys, setSelectedKeys] = useState([]); @@ -28,6 +28,7 @@ const InnerTree = ({ }); useEffect(() => { + //默认选中 if (dataSource && dataSource.length > 0) { setSelectedTarget(dataSource[0]); onSelectChanged([dataSource[0]]); @@ -77,6 +78,7 @@ const InnerTree = ({ loop(dataSource as T[]); return result; }, [dataSource, searchValue]); + const departList = () => (
@@ -141,4 +143,4 @@ const InnerTree = ({ ); }; -export default React.memo(InnerTree); +export default React.memo(MemberTree); diff --git a/src/executor/open/view/form/speciesMenu.tsx b/src/executor/open/view/form/speciesMenu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..250c7ae6917d066ad4c435797ee4b723d3c4fa57 --- /dev/null +++ b/src/executor/open/view/form/speciesMenu.tsx @@ -0,0 +1,239 @@ +import React, { createRef, useEffect, useState } from 'react'; +import TreeView, { TreeViewTypes } from 'devextreme-react/cjs/tree-view'; +import { IDirectory, IForm } from '@/ts/core'; +import { ReportTree } from '@/ts/core/thing/standard/reporttree'; +import _ from 'lodash'; +import { schema } from '@/ts/base'; + +interface SpeciesMenuType { + form: schema.XForm & { options?: { organizationTree: string } }; + metaForm: IForm; + directory: IDirectory; + searchType: string; + setSelectMenu: React.Dispatch>; +} +const SpeciesMenu: React.FC = ({ + form, + metaForm, + directory, + searchType, + setSelectMenu, +}) => { + const [treeData, setTreeData] = useState([]); + const treeViewRef = createRef>(); + const loadSpeciesItemMenu = () => { + const speciesFields = metaForm.fields.filter((i) => i.options?.species); + const result: any[] = []; + for (const filed of speciesFields) { + const newFiled = filed; + result.push({ + key: filed.id, + item: newFiled, + label: filed.name, + hasItems: true, + rootCode: filed.code, + typeName: filed.valueType, + children: [], + }); + } + return result; + }; + const treeItemMenu = async () => { + let rootCode: string = ''; + const loopData = (arr: any[]): any[] => { + return arr.map((item: any) => { + item.value = item.targetId; + return { + key: item.id, + label: item.name, + rootCode, + hasItems: item.children.length > 0, + typeName: '组织树', + children: loopData(item.children), + value: item.targetId, + item: item, + }; + }); + }; + let result = []; + if (searchType === 'tree' && form.options?.organizationTree) { + const res = await directory.resource.reportTreeColl.find([ + form.options.organizationTree, + ]); + if (Array.isArray(res) && res.length > 0) { + const _field = res[0]; + const tree = new ReportTree(_field, directory); + let nodes = _.cloneDeep(await tree.loadNodes(true)); + const root = nodes.find((n) => n.id == _field.rootNodeId); + if (!root) { + return []; + } + rootCode = root.code; + let subTree = await tree.loadSubTree(root); + result = loopData(subTree); + } + } + return result; + }; + + useEffect(() => { + const result = loadSpeciesItemMenu(); + setTreeData(result); + }, []); + + const getItem = async (node: TreeViewTypes.Node, fileds: any) => { + const arr = fileds.map((filed: any) => { + return filed.id; + }); + const children = await metaForm.loadItemsByParentId( + [node.itemData?.item.speciesId], + arr, + ); + return children; + }; + + const getSpeciesChildren = async (node: TreeViewTypes.Node) => { + let result: any[] = [], + children: any[] = [], + items: any[] = []; + if (node.parent) { + children = await metaForm.loadItemsByParentId( + [node.itemData?.item.speciesId], + [node.itemData?.key], + ); + items = await getItem(node, children); + } else { + children = await metaForm.loadItemsByParentId( + [node.itemData?.item.speciesId], + [undefined], + ); + items = await getItem(node, children); + } + for (const filed of children) { + let arr: any[] = []; + items.forEach((item: any) => { + if (item.parentId === filed.id) { + const newFiled: any = item; + newFiled.value = 'S' + newFiled.id; + arr.push({ + key: item.id, + item: newFiled, + label: item.name, + parentId: item.parentId, + hasItems: false, + rootCode: item.code, + typeName: '分类型', + children: [], + }); + } + }); + const newFiled = filed; + newFiled.value = 'S' + newFiled.id; + result.push({ + key: filed.id, + item: newFiled, + label: filed.name, + parentId: node.key, + rootCode: node.itemData?.rootCode, + hasItems: arr.length > 0 ? true : false, + typeName: '分类型', + children: arr, + }); + } + return result; + }; + + const getOrganizationChildren = async (node: TreeViewTypes.Node) => { + const loopData = (arr: any[], pid?: string): any[] => { + return arr.map((item: any) => { + return { + key: item.id, + label: item.name, + parentId: pid, + rootCode: node.itemData?.rootCode, + hasItems: item.children.length > 0, + typeName: '组织树', + value: item.targetId, + children: loopData(item.children, item.id), + item: item, + }; + }); + }; + + if (node.itemData?.item) { + if (node.itemData.children) { + return node.itemData.children.map((v: { [x: string]: any }) => { + v['parentId'] = node.key; + return v; + }); + } + const tree = new ReportTree(node.itemData.item, directory); + let nodes = _.cloneDeep(await tree.loadNodes(true)); + const root = nodes.find((n) => n.id == node.itemData?.item.rootNodeId); + if (!root) { + return []; + } + let subTree = await tree.loadSubTree(root); + return loopData(subTree); + } + + return []; + }; + + const createChildren = async (node: TreeViewTypes.Node) => { + let result: any[] = []; + if (node) { + switch (node.itemData!.typeName) { + case '组织树': + result = await getOrganizationChildren(node); + break; + case '分类型': + case '分类项': + result = await getSpeciesChildren(node); + break; + default: + console.log('暂不支持类型:', node); + result = []; + break; + } + } else { + result = [...(await treeItemMenu()), ...loadSpeciesItemMenu()]; + } + return result; + }; + + return ( +
+ { + const currentItem = e.node; + const allSelecteds = e.component.getSelectedNodes(); + if (currentItem?.selected) { + const oldSelectKey = allSelecteds.find( + (v) => + v.itemData?.rootCode === currentItem?.itemData?.rootCode && + v.itemData?.key !== currentItem?.itemData?.key, + ); + oldSelectKey && + treeViewRef.current?.instance.unselectItem(oldSelectKey.itemData); + } + setSelectMenu(allSelecteds.filter((v) => v.selected)); + }} + /> +
+ ); +}; + +export default React.memo(SpeciesMenu, (pre, next) => pre.form.id === next.form.id); diff --git a/src/executor/open/view/form/treeDataUtils.ts b/src/executor/open/view/form/treeDataUtils.ts index 23711662566f54b18102ec8b6ee6957f6fb59d4f..fe80a6c876d1a8c6c9fbcdb5054007ece5a2128e 100644 --- a/src/executor/open/view/form/treeDataUtils.ts +++ b/src/executor/open/view/form/treeDataUtils.ts @@ -1,5 +1,3 @@ -// src/utils/treeDataUtils.ts - import { schema } from '@/ts/base'; import { ICompany, @@ -10,79 +8,86 @@ import { ITarget, ITeam, } from '@/ts/core'; -export type deptItemType = { +export type DeptItemType = { key: string; id: string; label: string; item: schema.XEntity; - parentId: string | undefined; + parentId?: string; hasItems: boolean; - children: deptItemType[]; + children: DeptItemType[]; disabled?: boolean; typeName: string; }; -const loadDepartments = (depts: IDepartment[], parent: T) => { - depts.forEach((dept) => { - const item = getTreeNode(dept.metadata, dept.children, parent.key) as T; - if (item.hasItems) loadDepartments(dept.children, item); - parent.children.push(item); - }); -}; - -const findInTree = ( - data: T, - predicate: (node: T) => boolean, -): T | undefined => { - // 检查当前节点是否满足条件 - if (predicate(data)) { - return data; - } - for (const child of data.subTarget) { - const result = findInTree(child, predicate); - if (result) { - return result; - } - } - - // 如果未找到,返回 undefined - return undefined; -}; // 获取树节点 -export const getTreeNode = ( +const createTreeNode = ( data: schema.XEntity, - children?: ITeam[], + children: ITeam[] = [], pid?: string, ): T => { return { + //@ts-ignore key: `${pid ?? data?.key ?? '0'}_${data.id}`, id: data.id, parentId: pid, label: data.name, typeName: data.typeName, item: data, - hasItems: children ? children.length > 0 : false, + hasItems: children.length > 0, children: [], } as unknown as T; }; -const hasTeamAuth = (user: IPerson, teamIds: string[]) => { - const auths = [orgAuth.SuperAuthId, orgAuth.DataAuthId]; - return user.authenticate(teamIds, auths); + +// 递归加载部门数据 +const loadDepartments = (depts: IDepartment[], parent: T) => { + depts.forEach((dept) => { + const item = createTreeNode(dept.metadata, dept.children, parent.key); + if (item.hasItems) { + loadDepartments(dept.children, item); + } + parent.children.push(item); + }); }; -const loadTargetTrees = (target: ITeam) => { - let childrenItems: ITeam[] = []; - if (target.typeName === '单位') { - childrenItems = (target as ICompany).departments; - } else { - childrenItems = (target as IDepartment).children; + +// 查找树中满足条件的节点 +const findInTree = ( + data: T, + predicate: (node: T) => boolean, +): T | undefined => { + const queue: T[] = [data]; + + while (queue.length) { + const node = queue.shift()!; + if (predicate(node)) { + return node; + } + queue.push(...node.subTarget); } - const teamNode = getTreeNode(target.metadata, childrenItems); - loadDepartments(childrenItems as IDepartment[], teamNode); - return [teamNode] as T[]; + + return undefined; +}; + +// 检查用户是否拥有团队权限 +const hasTeamAuth = (user: IPerson, teamIds: string[]) => { + const requiredAuths = [orgAuth.SuperAuthId, orgAuth.DataAuthId]; + return user.authenticate(teamIds, requiredAuths); +}; + +// 加载目标树 +const loadTargetTree = (target: ITeam): T => { + const childrenItems = + target.typeName === TargetType.Company + ? (target as ICompany).departments + : (target as IDepartment).children; + + const rootNode = createTreeNode(target.metadata, childrenItems); + loadDepartments(childrenItems as IDepartment[], rootNode); + return rootNode as T; }; // 获取展示权限 -export const getTreeDatas = async ( +export const getTreeDatas = async ( target: ITarget, ): Promise => { const treeNodes: T[] = []; @@ -90,42 +95,39 @@ export const getTreeDatas = async ( if (target.typeName === TargetType.Group) { await target.loadMembers(); treeNodes.push( - getTreeNode({ + createTreeNode({ id: target.directory.spaceId, key: 'company', name: '本单位', typeName: '单位', } as unknown as schema.XEntity), - ...target.members.map((member) => getTreeNode(member)), + ...target.members.map((member) => createTreeNode(member)), ); - } else { - //单位/部门组织 列表 - const team = target as ICompany | IDepartment; - //默认权限 - treeNodes.push(getTreeNode({ ...team.user.metadata, name: '本人' })); - //超管权限 - if (hasTeamAuth(team.user, [team.belongId])) { - treeNodes.push(...loadTargetTrees(team)); - } else if (team.user.departments.length > 0) { - const departments = team.user.departments.filter( - (dept) => dept.belongId === team.directory.spaceId, - ); - for (const depart of departments) { - //部分部门管理员权限 - if (hasTeamAuth(team.user, [depart.id])) { - const predicate = (node: ITarget) => - node.typeName === depart.typeName && node.id === depart.id; - const _data = findInTree(team as ITarget, predicate); - if (_data) { - treeNodes.push(...loadTargetTrees(_data)); - } + return treeNodes; + } + + const team = target as ICompany | IDepartment; + //默认权限 + treeNodes.push(createTreeNode({ ...team.user.metadata, name: '本人' })); + //超管权限 + if (hasTeamAuth(team.user, [team.belongId])) { + treeNodes.push(loadTargetTree(team)); + } else if (team.user.departments.length > 0) { + const departments = team.user.departments.filter( + (dept) => dept.belongId === team.directory.spaceId, + ); + //部分部门管理员权限 + for (const depart of departments) { + if (hasTeamAuth(team.user, [depart.id])) { + const predicate = (node: ITarget) => + node.typeName === depart.typeName && node.id === depart.id; + const foundNode = findInTree(team as ITarget, predicate); + if (foundNode) { + treeNodes.push(loadTargetTree(foundNode)); } } } } - //无管理权 - return treeNodes; }; -// 获取集群组织树