diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4893898..2e4a4d74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,7 @@ importers: p-min-delay: ^4.0.0 postcss: ^8.3.5 postcss-loader: ^6.1.0 + qs: ^6.10.3 rc-tree: ^5.3.6 react-dom: 17.0.2 react-easy-crop: ^3.5.2 @@ -234,6 +235,7 @@ importers: memoize-one: 6.0.0 mini-star: 1.3.1 p-min-delay: 4.0.1 + qs: 6.10.3 rc-tree: 5.3.6_react-dom@17.0.2+react@17.0.2 react-dom: 17.0.2_react@17.0.2 react-easy-crop: 3.5.3_react-dom@17.0.2+react@17.0.2 @@ -6588,7 +6590,7 @@ packages: engines: {node: '>=0.10.0'} /object-inspect/1.12.0: - resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/object-inspect/download/object-inspect-1.12.0.tgz} + resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -7120,8 +7122,15 @@ packages: postcss-selector-parser: 6.0.8 dev: false + /qs/6.10.3: + resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /qs/6.9.6: - resolution: {integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/qs/download/qs-6.9.6.tgz} + resolution: {integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==} engines: {node: '>=0.6'} dev: true @@ -8150,6 +8159,14 @@ packages: smoothscroll-polyfill: 0.4.4 dev: false + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + object-inspect: 1.12.0 + dev: false + /signal-exit/3.0.6: resolution: {integrity: sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==} diff --git a/web/package.json b/web/package.json index 9e255934..2ab81270 100644 --- a/web/package.json +++ b/web/package.json @@ -39,6 +39,7 @@ "memoize-one": "^6.0.0", "mini-star": "^1.3.1", "p-min-delay": "^4.0.0", + "qs": "^6.10.3", "rc-tree": "^5.3.6", "react-dom": "17.0.2", "react-easy-crop": "^3.5.2", diff --git a/web/plugins/com.msgbyte.openapi/src/MainPanel/index.tsx b/web/plugins/com.msgbyte.openapi/src/MainPanel/index.tsx index f176dd01..fe44f917 100644 --- a/web/plugins/com.msgbyte.openapi/src/MainPanel/index.tsx +++ b/web/plugins/com.msgbyte.openapi/src/MainPanel/index.tsx @@ -1,36 +1,17 @@ -import React, { useMemo, useState } from 'react'; -import { - postRequest, - useAsyncRefresh, - openModal, - closeModal, -} from '@capital/common'; -import { - LoadingSpinner, - Space, - Table, - Button, - Loading, -} from '@capital/component'; +import React, { useMemo } from 'react'; +import { openModal, closeModal } from '@capital/common'; +import { Space, Table, Button, Loading } from '@capital/component'; import { OpenApp } from './types'; import AppInfo from './AppInfo'; import { OpenAppInfoProvider } from './context'; import { CreateOpenApp } from '../modals/CreateOpenApp'; import { ServiceChecker } from '../components/ServiceChecker'; +import { useOpenAppList } from './useOpenAppList'; import './index.less'; const OpenApiMainPanel: React.FC = React.memo(() => { - const [selectedAppId, setSelectedAppId] = useState(null); - const { - loading, - value: allApps = [], - refresh, - } = useAsyncRefresh(async (): Promise => { - const { data } = await postRequest('/openapi/app/all'); - - return data ?? []; - }, []); - const appInfo = allApps.find((a) => a._id === selectedAppId); + const { loading, allApps, refresh, appInfo, handleSetSelectedApp } = + useOpenAppList(); const columns = useMemo( () => [ @@ -43,7 +24,9 @@ const OpenApiMainPanel: React.FC = React.memo(() => { key: 'action', render: (_, record: OpenApp) => ( - + ), }, diff --git a/web/plugins/com.msgbyte.openapi/src/MainPanel/useOpenAppList.ts b/web/plugins/com.msgbyte.openapi/src/MainPanel/useOpenAppList.ts new file mode 100644 index 00000000..b7d83415 --- /dev/null +++ b/web/plugins/com.msgbyte.openapi/src/MainPanel/useOpenAppList.ts @@ -0,0 +1,58 @@ +import { + postRequest, + appendUrlSearch, + useAsyncRefresh, + useHistory, + urlSearchParse, + isValidStr, +} from '@capital/common'; +import { useEffect, useState } from 'react'; +import { OpenApp } from './types'; + +/** + * 开放应用列表 + */ +export function useOpenAppList() { + const [selectedAppId, setSelectedAppId] = useState(null); + const { + loading, + value: allApps = [], + refresh, + } = useAsyncRefresh(async (): Promise => { + const { data } = await postRequest('/openapi/app/all'); + + return data ?? []; + }, []); + + const history = useHistory(); + + useEffect(() => { + // 仅初始化的时候才处理 + const { appId } = urlSearchParse(history.location.search, { + ignoreQueryPrefix: true, + }); + + if (isValidStr(appId)) { + setSelectedAppId(appId); + } + }, []); + + return { + loading, + allApps, + refresh, + appInfo: allApps.find((a) => a._id === selectedAppId), + /** + * 设置当前选中的开放app + */ + handleSetSelectedApp(appId: string) { + history.push({ + ...history.location, + search: appendUrlSearch({ + appId, + }), + }); + setSelectedAppId(appId); + }, + }; +} diff --git a/web/src/plugin/common/index.ts b/web/src/plugin/common/index.ts index c1c987df..141eb2db 100644 --- a/web/src/plugin/common/index.ts +++ b/web/src/plugin/common/index.ts @@ -15,6 +15,11 @@ export { export { Loadable } from '@/components/Loadable'; export { getGlobalState } from '@/utils/global-state-helper'; export { dataUrlToFile } from '@/utils/file-helper'; +export { + urlSearchStringify, + urlSearchParse, + appendUrlSearch, +} from '@/utils/url-helper'; export { useGroupIdContext } from '@/context/GroupIdContext'; import { request, RequestConfig } from 'tailchat-shared'; export { @@ -34,7 +39,9 @@ export { createFastFormSchema, fieldSchema, fetchAvailableServices, + isValidStr, } from 'tailchat-shared'; +export { useLocation, useHistory } from 'react-router'; /** * 处理axios的request config diff --git a/web/src/utils/__tests__/url-helper.spec.ts b/web/src/utils/__tests__/url-helper.spec.ts index a3765703..0dd616c7 100644 --- a/web/src/utils/__tests__/url-helper.spec.ts +++ b/web/src/utils/__tests__/url-helper.spec.ts @@ -1,4 +1,10 @@ -import { markAbsoluteUrl } from '../url-helper'; +import { + markAbsoluteUrl, + urlSearchParse, + urlSearchStringify, + appendUrlSearch, +} from '../url-helper'; +import _set from 'lodash/set'; describe('markAbsoluteUrl', () => { test.each([ @@ -12,3 +18,34 @@ describe('markAbsoluteUrl', () => { expect(markAbsoluteUrl(input)).toBe(output); }); }); + +describe('url search', () => { + test('urlSearchStringify', () => { + expect(urlSearchStringify({ foo: 'a', bar: 'b' })).toBe('foo=a&bar=b'); + }); + + test('urlSearchParse', () => { + expect(urlSearchParse('foo=a&bar=b')).toEqual({ foo: 'a', bar: 'b' }); + }); + + describe('appendUrlSearch', () => { + // Mock + _set(window, 'location.search', '?foo=a&bar=b'); + + test('append', () => { + expect( + appendUrlSearch({ + foz: 'c', + }) + ).toBe('foo=a&bar=b&foz=c'); + }); + + test('overwrite', () => { + expect( + appendUrlSearch({ + foo: 'c', + }) + ).toBe('foo=c&bar=b'); + }); + }); +}); diff --git a/web/src/utils/url-helper.ts b/web/src/utils/url-helper.ts index 9de60d10..65bb8d98 100644 --- a/web/src/utils/url-helper.ts +++ b/web/src/utils/url-helper.ts @@ -1,3 +1,5 @@ +import { stringify as urlSearchStringify, parse as urlSearchParse } from 'qs'; + /** * 根据输入url返回绝对url * @param relativeUrl 相对或绝对url @@ -6,3 +8,15 @@ export function markAbsoluteUrl(relativeUrl: string): string { return new URL(relativeUrl, location.href).href; } + +export { urlSearchStringify, urlSearchParse }; + +/** + * 往当前的url search上追加 + */ +export function appendUrlSearch(obj: Record): string { + return urlSearchStringify({ + ...urlSearchParse(window.location.search, { ignoreQueryPrefix: true }), + ...obj, + }); +}