From ab2a0d8ae249e4600d8e3a8ed8fc98c8005293d3 Mon Sep 17 00:00:00 2001 From: maggieyyy <61950669+maggieyyy@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:58:09 +0800 Subject: [PATCH 1/2] fix: Modify member transfer --- .../components/aoplatform/MemberTransfer.tsx | 214 ++++++------------ frontend/packages/core/src/App.css | 5 + .../src/pages/member/MemberDropdownModal.tsx | 1 - .../core/src/pages/team/TeamConfig.tsx | 7 +- .../core/src/pages/team/TeamInsideMember.tsx | 13 +- 5 files changed, 89 insertions(+), 151 deletions(-) diff --git a/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx b/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx index 90c2e424..4945379c 100644 --- a/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx +++ b/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx @@ -1,27 +1,24 @@ -import { GetProp, TransferProps, TreeDataNode, theme, Transfer, Tree, Spin } from "antd"; -import { DataNode, TreeProps } from "antd/es/tree"; +import { TransferProps, TreeDataNode, Tree, Spin, Input } from "antd"; +import { DataNode } from "antd/es/tree"; import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; -import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons"; -import { cloneDeep, debounce } from "lodash-es"; +import { LoadingOutlined } from "@ant-design/icons"; import { ColumnsType } from "antd/es/table"; import { $t } from "@common/locales"; import { useGlobalContext } from "@common/contexts/GlobalStateContext"; - -type TransferItem = GetProp[number]; +import Search from "antd/es/input/Search"; export type TransferTableProps = { request?:(k?:string)=>Promise<{data:T[],success:boolean}> columns: ColumnsType primaryKey:string - onSelect:(selectedData:T[])=>void + onSelect:(selectedData:string[])=>void tableType?:'member'|'api' disabledData:string[] searchPlaceholder?:string } export type TransferTableHandle = { - selectedData: () => T[]; selectedRowKeys: () => React.Key[]; } @@ -31,10 +28,6 @@ interface TreeTransferProps { onChange: TransferProps['onChange']; } -// Customize Table Transfer -const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) => - selectedKeys.includes(eventKey); - const generateTree = ( treeNodes: TreeDataNode[] = [], checkedKeys: TreeTransferProps['targetKeys'] = [], @@ -73,167 +66,102 @@ const generateTree = ( ) }; -const TransferTree = (props)=>{ - const { direction, token, tableHeight, dataSource, targetKeys, onItemSelect, onItemSelectAll,checkedKey,selectedKeys, filteredItems ,disabledData} = props; - const [expandedKeys, setExpandedKeys] = useState([]); - - const getExpandedKeys = (newData:TreeDataNode[], expandedSet:Set = new Set())=>{ - newData.forEach((item)=>{ - if(item.children && item.children.length > 0){ - expandedSet.add(item.key) - getExpandedKeys(item.children,expandedSet) - } - }) - return expandedSet - } - - const treeData:TreeDataNode[] = useMemo(()=>{ - const filteredSet = filteredItems && filteredItems.length > 0 ? new Set(filteredItems.map((x)=>x.id)) : new Set() - const res = dataSource && dataSource.length > 0 ? generateTree(dataSource, targetKeys,direction === 'right',disabledData,filteredSet) : [] - setExpandedKeys(Array.from(getExpandedKeys(res))) - return res - },[ - dataSource, targetKeys,direction ,disabledData,filteredItems - ]) - - const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { - setExpandedKeys(expandedKeysValue as string[]); - }; - - return ( - -
- { return (props.type === 'member' ? : )} } - treeData={treeData} - onCheck={(_checkedKeys, e:{checked: boolean, checkedNodes, node, event, halfCheckedKeys}) => { - if(e.checked){ - onItemSelectAll( _checkedKeys, e.checked); - }else{ - const checkedKeyArrFromTree = e.checkedNodes.map(node => node.key) - onItemSelectAll((checkedKey as string[]).filter(key => checkedKeyArrFromTree.indexOf(key) === -1),e.checked) - } - }} - onSelect={(_, { node: { key } }) => { - onItemSelect(key as string, !isChecked(checkedKey, key)); - }} - /> -
- ) -} - const MemberTransfer= forwardRef, TransferTableProps<{[k:string]:unknown}>>( (props: TransferTableProps, ref:Ref>) => { const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props - const [tableHeight, setTableHeight] = useState(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3); const [targetKeys, setTargetKeys] = useState([]); const [dataSource, setDataSource] = useState([]) const parentRef = useRef(null); const [loading, setLoading] = useState(false) const {state} = useGlobalContext() - + const [expandedKeys, setExpandedKeys] = useState([]); + const [searchWord, setSearchWord] = useState('') useEffect(()=>{ setTargetKeys(disabledData) },[disabledData]) useImperativeHandle(ref, () =>({ - selectedData: () => dataSource, selectedRowKeys: () => targetKeys,})) - const onChange: TreeTransferProps['onChange'] = (keys) => { - onSelect?.(new Set(keys)) - setTargetKeys(Array.from(new Set(keys))); - }; + const translatedDataSource = useMemo(()=>{ + + const loop = (data: DataNode[]): DataNode[] => + data?.map((item) => { + const strTitle:string = item.name === '所有成员' ? $t(item.name) as string : item.name as string; + const index = strTitle.indexOf(searchWord); + const beforeStr = strTitle.substring(0, index); + const afterStr = strTitle.slice(index + searchWord.length); + const title = + index > -1 ? ( + + {beforeStr} + {searchWord} + {afterStr} + + ) : ( + {strTitle} + ) + if (item.children) { + return { + ...item, + title, + disableCheckbox:disabledData.indexOf(item.key as string) !== -1, + children: loop(item.children as T[]) }; + } - const { token } = theme.useToken(); - - const transferDataSource: TransferItem[] = useMemo(()=>{ - function flatten(list: TreeDataNode[] = [], res:TransferItem[]) { - list.forEach((item) => { - res.push({...item, title:item.title === '所有成员' ? $t((item as unknown as {title:string}).title):item.title }as TransferItem); - flatten(item.children,res); - }); - } - const res:TransferItem[] =[] - flatten(dataSource,res); - return res - },[ - dataSource, state.language - ]) - - - const translatedDataSource = useMemo(()=>dataSource.map((item)=>({ - ...item, - name:item.name === '所有成员' ? $t((item as unknown as {name:string}).name):item.name, - })),[dataSource, state.language]) - - - - let memo: Record = {}; - - const handlerFilterOption = (inputValue: string, item: any, parentResult: boolean = false, childrenSet: Set = new Set()): boolean => { - const cacheKey = `${inputValue}_${item.key}`; - if (memo[cacheKey]) { - return memo[cacheKey]; - } - - childrenSet.add(item.key); - let result = item.title.includes(inputValue) || parentResult - if (item.children) { - for (const child of item.children) { - if (handlerFilterOption(inputValue, child, result,childrenSet)) { - result = true; - } - } - } - - if (result) { - memo[cacheKey] = result; - childrenSet.forEach((key) => { - memo[`${inputValue}_${key}`] = result; + return { + ...item, + title, + isLeaf:true, + disableCheckbox:disabledData.indexOf(item.key as string) !== -1 + }; }); - } - return result; - }; + console.log(searchWord, dataSource) + return loop(dataSource); + },[dataSource, state.language, searchWord]) + + const getInitExpandKeys = (data:T[], expandKeys:string[] = [])=>{ + data.forEach((item)=>{ + if(item.children?.length){ + expandKeys.push(item.key as string) + getInitExpandKeys(item.children,expandKeys) + } + }) + return expandKeys + } const getDataSource = ()=>{ setLoading(true) request && request().then((res)=>{ const {data,success} = res setDataSource(success? data : []) + setExpandedKeys(getInitExpandKeys(success? data:[])) }).finally(()=>{setLoading(false)}) -} + } -useEffect(() => { - getDataSource() - const handleResize = () => { - setTableHeight(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3) - }; - const debouncedHandleResize = debounce(handleResize, 200); - - // 监听窗口大小变化 - window.addEventListener('resize', debouncedHandleResize); - handleResize(); - return () => { - window.removeEventListener('resize', debouncedHandleResize); - }; -}, []); + useEffect(() => { + getDataSource() + }, []); return (
} spinning={loading} className=''> - setSearchWord(e.target.value)} value={searchWord} /> + {setTargetKeys(e); + onSelect(((e as string[])?.filter(x=>disabledData.indexOf(x as string) === -1))||[])}} + onExpand={setExpandedKeys} + treeData={translatedDataSource} + blockNode + /> + + {/* { memo = {}; @@ -266,7 +194,7 @@ useEffect(() => { ); } }} - + */}
); diff --git a/frontend/packages/core/src/App.css b/frontend/packages/core/src/App.css index d50fdc28..9afb2c31 100644 --- a/frontend/packages/core/src/App.css +++ b/frontend/packages/core/src/App.css @@ -290,3 +290,8 @@ a{ transition: background-color 0s 600000s, color 0s 600000s !important; } } + + .ant-select-selection-overflow-item:first-child { + max-width: calc(100% - 60px); + margin-right: 4px; + } \ No newline at end of file diff --git a/frontend/packages/core/src/pages/member/MemberDropdownModal.tsx b/frontend/packages/core/src/pages/member/MemberDropdownModal.tsx index d0c1a6f4..5d4cbe52 100644 --- a/frontend/packages/core/src/pages/member/MemberDropdownModal.tsx +++ b/frontend/packages/core/src/pages/member/MemberDropdownModal.tsx @@ -117,7 +117,6 @@ export const MemberDropdownModal = forwardRef
((props,ref) => { const [managerOption, setManagerOption] = useState([]) const { setBreadcrumb} = useBreadcrumb() const { setTeamInfo } =useTeamContext() - const {checkPermission,accessInit} = useGlobalContext() + const {checkPermission,accessInit,state} = useGlobalContext() const pageType= useMemo(()=>{ if(!accessInit) return 'myteam' return checkPermission('system.workspace.team.view_all') ? 'manage' : 'myteam' @@ -128,7 +128,10 @@ const TeamConfig= forwardRef((props,ref) => { getTeamInfo(); } else { setOnEdit(false); - form.setFieldsValue({id:uuidv4()}); // 清空 initialValues + form.setFieldsValue( + {id:uuidv4(), + master:state?.userData?.uid + }); // 清空 initialValues } return (form.setFieldsValue({})) }, [teamId]); diff --git a/frontend/packages/core/src/pages/team/TeamInsideMember.tsx b/frontend/packages/core/src/pages/team/TeamInsideMember.tsx index 0c61cc18..81836fb1 100644 --- a/frontend/packages/core/src/pages/team/TeamInsideMember.tsx +++ b/frontend/packages/core/src/pages/team/TeamInsideMember.tsx @@ -273,7 +273,6 @@ const TeamInsideMember:FC = ()=>{ },[ state.language,roleList]) useEffect(() => { - getRoleList() setBreadcrumb([ {title:{$t('团队')}}, {title:$t('成员')} @@ -281,6 +280,11 @@ const TeamInsideMember:FC = ()=>{ manualReloadTable() }, [teamId]); + + useEffect(()=>{ + getRoleList() + },[state.language]) + const treeDisabledData = useMemo(()=>{ return [...allMemberIds,...allMemberSelectedDepartIds]},[allMemberIds,allMemberSelectedDepartIds]) return ( @@ -303,7 +307,7 @@ const TeamInsideMember:FC = ()=>{ title={$t("添加成员")} open={modalVisible} destroyOnClose={true} - width={900} + width={600} onCancel={() => cleanModalData()} maskClosable={false} footer={[ @@ -327,10 +331,9 @@ const TeamInsideMember:FC = ()=>{ disabledData={treeDisabledData} request={()=>getDepartmentMemberList()} onSelect={(selectedData: Set) => { - const memberKeyFromModal = Array.from(selectedData)?.filter(x => allMemberIds.indexOf(x) === -1 &&selectableMemberIds.has(x)) || []; - setAddMemberBtnDisabled((memberKeyFromModal.length === 0)); + setAddMemberBtnDisabled((selectedData.length === 0)); }} - searchPlaceholder={$t("搜索用户名、邮箱")} + searchPlaceholder={$t("搜索用户名")} /> From 5f03973bf292b393876709d7f4b051473ef034ac Mon Sep 17 00:00:00 2001 From: maggieyyy <61950669+maggieyyy@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:37:38 +0800 Subject: [PATCH 2/2] fix: Modify guide link --- .../packages/core/src/pages/guide/Guide.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/packages/core/src/pages/guide/Guide.tsx b/frontend/packages/core/src/pages/guide/Guide.tsx index ef3045ed..9e79f2e8 100644 --- a/frontend/packages/core/src/pages/guide/Guide.tsx +++ b/frontend/packages/core/src/pages/guide/Guide.tsx @@ -21,22 +21,22 @@ export default function Guide(){ { title: $t("配置你的 AI 模型"), description: $t('通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。'), - link: 'https://docs.apipark.com/docs/quick/pre-work/team' + link: 'https://docs.apipark.com/docs/system_setting/ai_model_providers' }, { title: $t("创建 AI 服务和 API"), description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), - link: 'https://docs.apipark.com/docs/quick/provider/service' + link: 'https://docs.apipark.com/docs/services/ai_services' }, { title: $t("创建调用 Token"), description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), - link: 'https://docs.apipark.com/docs/quick/suberscriber/application' + link: 'https://docs.apipark.com/docs/consumers' }, { title: $t("调用"), description: $t('现在你可以通过 Token 来调用这些 API。'), - link: 'https://docs.apipark.com/docs/quick/suberscriber/application' + link: 'https://docs.apipark.com/docs/call_api' } ] }, @@ -46,17 +46,17 @@ export default function Guide(){ { title: $t("创建 REST 服务和 API"), description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), - link: 'https://docs.apipark.com/docs/tutorials/api-market/service' + link: 'https://docs.apipark.com/docs/services/rest_services' }, { title: $t("创建调用 Token"), description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), - link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe' + link: 'https://docs.apipark.com/docs/consumers' }, { title: $t("调用"), description: $t('现在你可以通过 Token 来调用这些 API。'), - link: 'https://docs.apipark.com/docs/quick/provider/approve' + link: 'https://docs.apipark.com/docs/call_api' } ] }, @@ -66,7 +66,7 @@ export default function Guide(){ { title: $t("统计 API 调用情况"), description: $t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。'), - link: 'https://docs.apipark.com/docs/quick/pre-work/monitor' + link: 'https://docs.apipark.com/docs/analysis' } ] } @@ -78,17 +78,17 @@ export default function Guide(){ { title: $t("账号与角色"), description: $t('邀请你的团队成员加入 APIPark,共同管理和调用 API。'), - link: 'https://docs.apipark.com/docs/quick/pre-work/team' + link: 'https://docs.apipark.com/docs/system_setting/account_role' }, { title: $t("团队"), description: $t('团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'), - link: 'https://docs.apipark.com/docs/quick/provider/service' + link: 'https://docs.apipark.com/docs/teams' }, { title: $t("服务"), description: $t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。'), - link: 'https://docs.apipark.com/docs/quick/suberscriber/application' + link: 'https://docs.apipark.com/docs/category/-%E6%9C%8D%E5%8A%A1' } ] }, @@ -98,12 +98,12 @@ export default function Guide(){ { title: $t("订阅服务"), description: $t('如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。'), - link: 'https://docs.apipark.com/docs/tutorials/api-market/service' + link: 'https://docs.apipark.com/docs/developer_portal' }, { title: $t("审核订阅申请"), description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。'), - link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe' + link: 'https://docs.apipark.com/docs/services/review_consumers' } ] }, @@ -113,7 +113,7 @@ export default function Guide(){ { title: $t("日志"), description: $t('APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。'), - link: 'https://docs.apipark.com/docs/quick/pre-work/monitor' + link: 'https://docs.apipark.com/docs/system_setting/log/' } ] }