From db92b5a344c1796054d22c0a6df27e1ea90828f7 Mon Sep 17 00:00:00 2001 From: Linzp Date: Thu, 28 May 2026 11:34:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/Apis/getApis.js | 21 ++ .../Dashboard/useRealtimeStatisticsSSE.js | 38 ++- .../MessageQueue/Dashboard/index.js | 32 +-- src/components/Task/locale/zh-CN.js | 4 +- src/components/Tenant/JoinInvitation/index.js | 6 +- .../Tenant/OrgInfo/LeaderFormInner.js | 37 +++ .../Tenant/OrgInfo/OrgLinkSetting.js | 225 +++++++++++++++ .../Tenant/OrgInfo/OrgNodeUserCount.js | 5 +- src/components/Tenant/OrgInfo/index.js | 142 ++++++++-- .../Tenant/OrgInfo/style.module.scss | 14 + src/components/Tenant/Role/buildRolesTitle.js | 13 + src/components/Tenant/Role/getRoleListApi.js | 18 ++ .../Tenant/Role/getRoleListFilterValue.js | 52 ---- src/components/Tenant/Role/index.js | 8 +- src/components/Tenant/Setting/Org.js | 66 +++-- src/components/Tenant/Setting/index.js | 8 +- .../SharedGroup/SharedGroupModulesField.js | 5 +- .../Tenant/TenantPermission/index.js | 13 +- .../TenantUserSelect/OrgTenantUserField.js | 8 +- .../Tenant/UserList/Actions/Create.js | 12 +- .../Tenant/UserList/Actions/Edit.js | 45 ++- .../Tenant/UserList/Actions/Invite.js | 6 +- .../Actions/buildInvitePersonalCardProps.js | 134 --------- .../buildInvitePersonalCardProps.test.js | 104 ------- .../Actions/personalCardWrap.module.scss | 9 - .../UserList/DepartmentTreeFilterItem.js | 34 +-- src/components/Tenant/UserList/FormInner.js | 13 +- .../Tenant/UserList/OrgTooltipContent.js | 42 --- .../Tenant/UserList/TenantUserPersonalCard.js | 36 --- .../Tenant/UserList/UserCard/index.js | 50 ---- .../UserList/UserCard/style.module.scss | 17 -- src/components/Tenant/UserList/UserOrgTags.js | 41 ++- .../Tenant/UserList/UserPersonalCard/index.js | 111 ++++++++ .../UserList/UserPersonalCard/index.test.js | 81 ++++++ .../style.module.scss} | 12 +- .../Tenant/UserList/buildOrgDisplayPath.js | 11 - .../buildTenantUserPersonalCardProps.js | 1 - .../UserList/buildUserListFilterFromSearch.js | 39 --- .../UserList/buildUserListWithPositionList.js | 79 ------ src/components/Tenant/UserList/doc/api.md | 102 +++++++ src/components/Tenant/UserList/doc/base.js | 27 ++ .../Tenant/UserList/doc/custom-render.js | 39 +++ .../Tenant/UserList/doc/example.json | 62 +++++ .../Tenant/UserList/doc/plugin-extension.js | 130 +++++++++ src/components/Tenant/UserList/doc/summary.md | 1 + .../Tenant/UserList/getUserListFilterValue.js | 72 ----- .../Tenant/UserList/getUserOrgDisplayItems.js | 57 ++-- src/components/Tenant/UserList/index.js | 258 ++++++------------ .../UserList/mapUserOptionsToFormValue.js | 60 ---- .../normalizeTenantUserForPersonalCard.js | 23 -- .../UserList/normalizeTenantUserStatus.js | 22 -- ...dsFromForm.js => transformUserFormData.js} | 10 + .../UserList/{getColumns.js => useColumns.js} | 20 +- .../Tenant/UserList/useFilterList.js | 46 ++++ src/components/Tenant/UserList/useListApi.js | 23 ++ .../Tenant/UserList/userListUrlFilters.js | 61 ----- src/components/Tenant/locale/en-US.js | 24 +- src/components/Tenant/locale/zh-CN.js | 24 +- .../TenantAdmin/TabDetail/Company/index.js | 9 +- .../TenantAdmin/TabDetail/Org/index.js | 96 ++++--- src/components/TenantAdmin/locale/en-US.js | 26 +- src/components/TenantAdmin/locale/zh-CN.js | 25 +- src/preset.js | 2 +- src/utils/useManagedEventSource.js | 57 ++++ 65 files changed, 1592 insertions(+), 1278 deletions(-) create mode 100644 src/components/Tenant/OrgInfo/LeaderFormInner.js create mode 100644 src/components/Tenant/OrgInfo/OrgLinkSetting.js create mode 100644 src/components/Tenant/Role/buildRolesTitle.js create mode 100644 src/components/Tenant/Role/getRoleListApi.js delete mode 100644 src/components/Tenant/UserList/Actions/buildInvitePersonalCardProps.js delete mode 100644 src/components/Tenant/UserList/Actions/buildInvitePersonalCardProps.test.js delete mode 100644 src/components/Tenant/UserList/Actions/personalCardWrap.module.scss delete mode 100644 src/components/Tenant/UserList/OrgTooltipContent.js delete mode 100644 src/components/Tenant/UserList/TenantUserPersonalCard.js delete mode 100644 src/components/Tenant/UserList/UserCard/index.js delete mode 100644 src/components/Tenant/UserList/UserCard/style.module.scss create mode 100644 src/components/Tenant/UserList/UserPersonalCard/index.js create mode 100644 src/components/Tenant/UserList/UserPersonalCard/index.test.js rename src/components/Tenant/UserList/{Actions/personalCard.module.scss => UserPersonalCard/style.module.scss} (52%) delete mode 100644 src/components/Tenant/UserList/buildOrgDisplayPath.js delete mode 100644 src/components/Tenant/UserList/buildTenantUserPersonalCardProps.js delete mode 100644 src/components/Tenant/UserList/buildUserListFilterFromSearch.js delete mode 100644 src/components/Tenant/UserList/buildUserListWithPositionList.js create mode 100644 src/components/Tenant/UserList/doc/api.md create mode 100644 src/components/Tenant/UserList/doc/base.js create mode 100644 src/components/Tenant/UserList/doc/custom-render.js create mode 100644 src/components/Tenant/UserList/doc/example.json create mode 100644 src/components/Tenant/UserList/doc/plugin-extension.js create mode 100644 src/components/Tenant/UserList/doc/summary.md delete mode 100644 src/components/Tenant/UserList/getUserListFilterValue.js delete mode 100644 src/components/Tenant/UserList/mapUserOptionsToFormValue.js delete mode 100644 src/components/Tenant/UserList/normalizeTenantUserForPersonalCard.js delete mode 100644 src/components/Tenant/UserList/normalizeTenantUserStatus.js rename src/components/Tenant/UserList/{pickTenantOrgIdsFromForm.js => transformUserFormData.js} (75%) rename src/components/Tenant/UserList/{getColumns.js => useColumns.js} (70%) create mode 100644 src/components/Tenant/UserList/useFilterList.js create mode 100644 src/components/Tenant/UserList/useListApi.js delete mode 100644 src/components/Tenant/UserList/userListUrlFilters.js create mode 100644 src/utils/useManagedEventSource.js diff --git a/package.json b/package.json index a9aca5f..07b3d11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-admin", - "version": "1.1.41", + "version": "1.1.42", "description": "用于实现一个后台管理系统的必要组件", "scripts": { "init": "husky", diff --git a/src/components/Apis/getApis.js b/src/components/Apis/getApis.js index 12a9244..08f795d 100644 --- a/src/components/Apis/getApis.js +++ b/src/components/Apis/getApis.js @@ -239,6 +239,23 @@ const getApis = options => { url: `${prefix}/tenant/admin/org-batch-import`, method: 'POST' }, + orgLinkConfig: { + url: `${prefix}/tenant/admin/org-link-config`, + method: 'GET' + }, + orgLinkSave: { + url: `${prefix}/tenant/admin/org-link-save`, + method: 'POST' + }, + orgLinkCancel: { + url: `${prefix}/tenant/admin/org-link-cancel`, + method: 'POST' + }, + orgLinkSync: { + url: `${prefix}/tenant/admin/org-link-sync`, + method: 'POST' + }, + userList: { url: `${prefix}/tenant/admin/user-list`, method: 'GET' @@ -407,6 +424,10 @@ const getApis = options => { url: `${prefix}/tenant/org-batch-import`, method: 'POST' }, + orgLinkConfig: { + url: `${prefix}/tenant/org-link-config`, + method: 'GET' + }, userList: { url: `${prefix}/tenant/user-list`, method: 'GET' diff --git a/src/components/MessageManger/Dashboard/useRealtimeStatisticsSSE.js b/src/components/MessageManger/Dashboard/useRealtimeStatisticsSSE.js index 9f2a97b..cf9d543 100644 --- a/src/components/MessageManger/Dashboard/useRealtimeStatisticsSSE.js +++ b/src/components/MessageManger/Dashboard/useRealtimeStatisticsSSE.js @@ -1,7 +1,8 @@ -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { getToken } from '@kne/token-storage'; import { buildUrlWithParams } from './constants'; import { getClientIanaTimezone } from '../utils'; +import useManagedEventSource from '../../../utils/useManagedEventSource'; const isLikelyStatisticsPayload = obj => obj && @@ -44,21 +45,19 @@ const useRealtimeStatisticsSSE = sseUrl => { const [isConnected, setIsConnected] = useState(false); const [lastUpdatedAt, setLastUpdatedAt] = useState(null); - useEffect(() => { - if (!sseUrl || typeof window === 'undefined' || typeof window.EventSource !== 'function') { - return undefined; - } - - const source = new EventSource( - buildUrlWithParams(sseUrl, { - interval: 5, - token: getToken('X-User-Token'), - timezone: getClientIanaTimezone() - }) - ); + const streamUrl = useMemo(() => { + if (!sseUrl) return null; + return buildUrlWithParams(sseUrl, { + interval: 5, + token: getToken('X-User-Token'), + timezone: getClientIanaTimezone() + }); + }, [sseUrl]); - source.onopen = () => setIsConnected(true); - source.onmessage = event => { + useManagedEventSource(streamUrl, { + onOpen: () => setIsConnected(true), + onError: () => setIsConnected(false), + onMessage: event => { try { const parsed = JSON.parse(event.data); const nextData = unwrapStatisticsPayload(parsed); @@ -69,13 +68,8 @@ const useRealtimeStatisticsSSE = sseUrl => { } catch { // ignore parse errors } - }; - source.onerror = () => setIsConnected(false); - - return () => { - source.close(); - }; - }, [sseUrl]); + } + }); return { realtimeData, isConnected, lastUpdatedAt }; }; diff --git a/src/components/MessageQueue/Dashboard/index.js b/src/components/MessageQueue/Dashboard/index.js index 2976339..2b452c7 100644 --- a/src/components/MessageQueue/Dashboard/index.js +++ b/src/components/MessageQueue/Dashboard/index.js @@ -1,12 +1,13 @@ import { createWithRemoteLoader } from '@kne/remote-loader'; import Fetch from '@kne/react-fetch'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Button, Card, Col, Row, Space, Statistic, Table, Tag } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import withLocale from '../withLocale'; import { useIntl } from '@kne/react-intl'; import Menu from '../Menu'; import { buildUrlWithParams, formatPercent, formatRate, getMetricTotal } from '../utils'; +import useManagedEventSource from '../../../utils/useManagedEventSource'; const toTopicRows = current => { const topics = new Set([ @@ -42,29 +43,22 @@ const DashboardContent = withLocale(({ Page, baseUrl, pageProps, apis, initialDa setLastUpdatedAt(initialData?.timestamp); }, [initialData]); - useEffect(() => { - if (!apis?.mq?.dashboard?.sse?.url || typeof window === 'undefined' || typeof window.EventSource !== 'function') { - return undefined; - } + const mqStreamUrl = useMemo(() => { + const url = apis?.mq?.dashboard?.sse?.url; + if (!url) return null; + return buildUrlWithParams(url, { interval: 1000 }); + }, [apis]); - const source = new EventSource(buildUrlWithParams(apis.mq.dashboard.sse.url, { interval: 1000 })); - source.onopen = () => { - setIsConnected(true); - }; - source.onmessage = event => { + useManagedEventSource(mqStreamUrl, { + onOpen: () => setIsConnected(true), + onError: () => setIsConnected(false), + onMessage: event => { const nextData = JSON.parse(event.data); setData(nextData); setLastUpdatedAt(nextData.timestamp || Date.now()); setIsConnected(true); - }; - source.onerror = () => { - setIsConnected(false); - }; - - return () => { - source.close(); - }; - }, [apis]); + } + }); const current = data?.current || {}; const rows = toTopicRows(current); diff --git a/src/components/Task/locale/zh-CN.js b/src/components/Task/locale/zh-CN.js index c02af7e..5326b4a 100644 --- a/src/components/Task/locale/zh-CN.js +++ b/src/components/Task/locale/zh-CN.js @@ -30,7 +30,7 @@ const locale = { // Status Enums Pending: '等待执行', Running: '执行中', - Waiting: '等待操作', + Waiting: '等待', Success: '成功', Failed: '失败', Canceled: '已取消', @@ -76,7 +76,7 @@ const locale = { ManualExecutionStats: '手动执行任务', ManualExecutionGoMyTaskTitle: '点击此处查看我的任务', ManualExecutionTasks: '手动执行数量', - ManualPendingTasks: '等待操作', + ManualPendingTasks: '等待', ManualExecutedTasks: '当日完成', ManualPendingMaxWaitLabel: '最长等待', ManualCompletedTotalDurationLabel: '总耗时', diff --git a/src/components/Tenant/JoinInvitation/index.js b/src/components/Tenant/JoinInvitation/index.js index 8dcff3e..a3969a4 100644 --- a/src/components/Tenant/JoinInvitation/index.js +++ b/src/components/Tenant/JoinInvitation/index.js @@ -8,8 +8,8 @@ import Fetch from '@kne/react-fetch'; import CountDown from '@kne/count-down'; import { useSearchParams, useNavigate } from 'react-router-dom'; import CompanyInfo from '../CompanyInfo'; -import TenantUserPersonalCard from '../UserList/TenantUserPersonalCard'; -import normalizeTenantUserForPersonalCard from '../UserList/normalizeTenantUserForPersonalCard'; +import TenantUserPersonalCard from '../UserList/UserPersonalCard'; + import withLocale from '../withLocale'; import { useIntl } from '@kne/react-intl'; import style from './style.module.scss'; @@ -174,7 +174,7 @@ const JoinInvitation = createWithRemoteLoader({ {formatMessage({ id: 'ConfirmEmployeeInfoHint' })} diff --git a/src/components/Tenant/OrgInfo/LeaderFormInner.js b/src/components/Tenant/OrgInfo/LeaderFormInner.js new file mode 100644 index 0000000..3d77252 --- /dev/null +++ b/src/components/Tenant/OrgInfo/LeaderFormInner.js @@ -0,0 +1,37 @@ +import { createWithRemoteLoader } from '@kne/remote-loader'; +import { useIntl } from '@kne/react-intl'; +import merge from 'lodash/merge'; +import withLocale from '../withLocale'; + +const LeaderFormInnerCore = createWithRemoteLoader({ + modules: ['components-core:FormInfo'] +})(({ remoteModules, apis, orgId }) => { + const [FormInfo] = remoteModules; + const { formatMessage } = useIntl(); + const { SuperSelect } = FormInfo.fields; + const list = []; + if (apis?.userList) { + list.push( + + ); + } + return ; +}); + +export default withLocale(LeaderFormInnerCore); diff --git a/src/components/Tenant/OrgInfo/OrgLinkSetting.js b/src/components/Tenant/OrgInfo/OrgLinkSetting.js new file mode 100644 index 0000000..0b29f05 --- /dev/null +++ b/src/components/Tenant/OrgInfo/OrgLinkSetting.js @@ -0,0 +1,225 @@ +import { createWithRemoteLoader } from '@kne/remote-loader'; +import { Flex, Tag, Button, App, Descriptions, Popconfirm, Alert, Space } from 'antd'; +import { LinkOutlined, DisconnectOutlined, CloudSyncOutlined } from '@ant-design/icons'; +import { useState } from 'react'; +import withLocale from '../withLocale'; +import { useIntl } from '@kne/react-intl'; +import Fetch from '@kne/react-fetch'; +import merge from 'lodash/merge'; + +const SOURCE_OPTIONS = [ + { value: 'wecom', label: '企业微信' }, + { value: 'dingtalk', label: '钉钉' } +]; + +const SYNC_INTERVAL_OPTIONS = [ + { value: 'daily', label: '每天' }, + { value: 'weekly', label: '每7天' }, + { value: 'monthly', label: '每个月' }, + { value: 'yearly', label: '每年' }, + { value: 'off', label: '关闭' } +]; + +const getSourceLabel = value => { + const item = SOURCE_OPTIONS.find(o => o.value === value); + return item ? item.label : value; +}; + +const getSyncIntervalLabel = value => { + const item = SYNC_INTERVAL_OPTIONS.find(o => o.value === value); + return item ? item.label : value; +}; + +const LinkFormInner = withLocale(createWithRemoteLoader({ + modules: ['components-core:FormInfo', 'components-core:Global@usePreset'] +})(({ remoteModules, tenantId, envArgs, onSuccess }) => { + const [FormInfo, usePreset] = remoteModules; + const { apis, ajax } = usePreset(); + const { formatMessage } = useIntl(); + const { message } = App.useApp(); + const { Select, RadioGroup } = FormInfo.fields; + + return ( + ({ + value: item.value, + label: item.label + }))} + />, + ({ + value: item.value, + label: item.label + }))} + />, +