diff --git a/packages/client/src/components/admin/system-config/system/index.tsx b/packages/client/src/components/admin/system-config/system/index.tsx index d4c539be..dd1d4005 100644 --- a/packages/client/src/components/admin/system-config/system/index.tsx +++ b/packages/client/src/components/admin/system-config/system/index.tsx @@ -1,4 +1,5 @@ -import { Banner, Button, Form, Toast } from '@douyinfe/semi-ui'; +import { IconHelpCircle } from '@douyinfe/semi-icons'; +import { Banner, Button, Form, Toast, Tooltip } from '@douyinfe/semi-ui'; import { DataRender } from 'components/data-render'; import { useSystemConfig } from 'data/user'; import { useToggle } from 'hooks/use-toggle'; @@ -27,9 +28,29 @@ export const System = () => { error={error} normalContent={() => (
-
- + + + + ), + }} + /> + + + + ), + }} + />
diff --git a/packages/client/src/components/document/collaboration/index.module.scss b/packages/client/src/components/document/collaboration/index.module.scss deleted file mode 100644 index 1a67f38a..00000000 --- a/packages/client/src/components/document/collaboration/index.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.userTable { - :global { - [class$='-pagination-outer'] { - flex-direction: column; - } - } -} diff --git a/packages/client/src/components/document/collaboration/index.tsx b/packages/client/src/components/document/collaboration/index.tsx index 39d9e0f1..a91b47a2 100644 --- a/packages/client/src/components/document/collaboration/index.tsx +++ b/packages/client/src/components/document/collaboration/index.tsx @@ -1,31 +1,12 @@ -import { IconDelete, IconUserAdd } from '@douyinfe/semi-icons'; -import { - Avatar, - AvatarGroup, - Button, - Checkbox, - Dropdown, - Input, - Modal, - Popconfirm, - Spin, - Table, - TabPane, - Tabs, - Toast, - Tooltip, - Typography, -} from '@douyinfe/semi-ui'; -import { DataRender } from 'components/data-render'; -import { DocumentLinkCopyer } from 'components/document/link'; +import { IconUserAdd } from '@douyinfe/semi-icons'; +import { Avatar, AvatarGroup, Button, Dropdown, Modal, Toast, Tooltip } from '@douyinfe/semi-ui'; +import { Members } from 'components/members'; import { useDoumentMembers } from 'data/document'; import { useUser } from 'data/user'; import { event, JOIN_USER } from 'event'; import { IsOnMobile } from 'hooks/use-on-mobile'; import { useToggle } from 'hooks/use-toggle'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; - -import styles from './index.module.scss'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; interface IProps { wikiId: string; @@ -33,116 +14,27 @@ interface IProps { disabled?: boolean; } -const { Paragraph } = Typography; -const { Column } = Table; - -// eslint-disable-next-line react/display-name -const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, docAuth) => { - const handle = (evt) => { - const data = { - ...docAuth.auth, - userName: docAuth.user.name, - }; - data[authKey] = evt.target.checked; - onChange(data); - }; - - return ; -}; - export const DocumentCollaboration: React.FC = ({ wikiId, documentId, disabled = false }) => { const { isMobile } = IsOnMobile.useHook(); - const ref = useRef(); const toastedUsersRef = useRef([]); const { user: currentUser } = useUser(); const [visible, toggleVisible] = useToggle(false); - const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId, { - enabled: visible, - }); - const [inviteUser, setInviteUser] = useState(''); const [collaborationUsers, setCollaborationUsers] = useState([]); - - const handleOk = useCallback(() => { - addUser(inviteUser).then(() => { - Toast.success('添加成功'); - setInviteUser(''); - }); - }, [addUser, inviteUser]); - - const handleDelete = useCallback( - (docAuth) => { - const data = { - ...docAuth.auth, - userName: docAuth.user.name, - }; - deleteUser(data); - }, - [deleteUser] - ); - const content = useMemo( () => ( - - -
- - - 将对方加入文档进行协作,您也可将该链接发送给对方。 - - - - - -
-
- - } - normalContent={() => ( - - - - - ( - handleDelete(document)}> -
- )} - /> -
-
+
+ +
), - [documentId, error, handleDelete, handleOk, inviteUser, loading, updateUser, users, wikiId] + [documentId] ); - const btn = useMemo( () => ( - - ); - return ( <> @@ -214,7 +93,7 @@ export const DocumentCollaboration: React.FC = ({ wikiId, documentId, di visible={visible} footer={null} onCancel={toggleVisible} - style={{ maxWidth: '96vw' }} + style={{ maxWidth: '96vw', maxHeight: '60vh', overflow: 'auto' }} > {content} @@ -233,6 +112,8 @@ export const DocumentCollaboration: React.FC = ({ wikiId, documentId, di width: 412, maxWidth: '96vw', padding: '0 24px', + maxHeight: '60vh', + overflow: 'auto', }} > {content} diff --git a/packages/client/src/components/document/create/index.tsx b/packages/client/src/components/document/create/index.tsx index e252a0ba..28464992 100644 --- a/packages/client/src/components/document/create/index.tsx +++ b/packages/client/src/components/document/create/index.tsx @@ -1,28 +1,32 @@ import { Checkbox, Modal, TabPane, Tabs } from '@douyinfe/semi-ui'; +import { IDocument, IWiki } from '@think/domains'; import { TemplateCardEmpty } from 'components/template/card'; import { TemplateList } from 'components/template/list'; import { useCreateDocument } from 'data/document'; import { useOwnTemplates, usePublicTemplates } from 'data/template'; +import { useRouterQuery } from 'hooks/use-router-query'; import Router from 'next/router'; import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'; import styles from './index.module.scss'; interface IProps { - wikiId: string; - parentDocumentId?: string; + wikiId: IWiki['id']; + parentDocumentId?: IDocument['id']; visible: boolean; toggleVisible: Dispatch>; onCreate?: () => void; } -export const DocumentCreator: React.FC = ({ wikiId, parentDocumentId, visible, toggleVisible, onCreate }) => { +export const DocumentCreator: React.FC = ({ parentDocumentId, wikiId, visible, toggleVisible, onCreate }) => { + const { organizationId } = useRouterQuery<{ organizationId?: string }>(); const { loading, create } = useCreateDocument(); const [createChildDoc, setCreateChildDoc] = useState(false); const [templateId, setTemplateId] = useState(''); const handleOk = () => { const data = { + organizationId, wikiId, parentDocumentId: createChildDoc ? parentDocumentId : null, templateId, @@ -32,7 +36,8 @@ export const DocumentCreator: React.FC = ({ wikiId, parentDocumentId, vi onCreate && onCreate(); setTemplateId(''); Router.push({ - pathname: `/wiki/${wikiId}/document/${res.id}/edit`, + pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit`, + query: { organizationId, wikiId, documentId: res.id }, }); }); }; diff --git a/packages/client/src/components/document/delete/index.tsx b/packages/client/src/components/document/delete/index.tsx index 59becf34..edcf759d 100644 --- a/packages/client/src/components/document/delete/index.tsx +++ b/packages/client/src/components/document/delete/index.tsx @@ -1,5 +1,5 @@ import { IconDelete } from '@douyinfe/semi-icons'; -import { Modal, Popconfirm, Space, Typography } from '@douyinfe/semi-ui'; +import { Popconfirm, Space, Typography } from '@douyinfe/semi-ui'; import { useDeleteDocument } from 'data/document'; import { useRouterQuery } from 'hooks/use-router-query'; import Router from 'next/router'; @@ -15,8 +15,11 @@ interface IProps { const { Text } = Typography; export const DocumentDeletor: React.FC = ({ wikiId, documentId, render, onDelete }) => { - const { wikiId: currentWikiId, documentId: currentDocumentId } = - useRouterQuery<{ wikiId?: string; documentId?: string }>(); + const { + organizationId, + wikiId: currentWikiId, + documentId: currentDocumentId, + } = useRouterQuery<{ organizationId: string; wikiId?: string; documentId?: string }>(); const { deleteDocument: api, loading } = useDeleteDocument(documentId); const deleteAction = useCallback(() => { @@ -26,14 +29,15 @@ export const DocumentDeletor: React.FC = ({ wikiId, documentId, render, return; } Router.push({ - pathname: `/wiki/${wikiId}`, + pathname: `/app/org/[organizationId]/wiki/[wikiId]`, + query: { organizationId, wikiId }, }); }; navigate(); onDelete && onDelete(); }); - }, [wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]); + }, [organizationId, wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]); const content = useMemo( () => ( diff --git a/packages/client/src/components/document/editor/editor.tsx b/packages/client/src/components/document/editor/editor.tsx index 35ba6a67..981f994d 100644 --- a/packages/client/src/components/document/editor/editor.tsx +++ b/packages/client/src/components/document/editor/editor.tsx @@ -1,16 +1,11 @@ import { IAuthority, ILoginUser } from '@think/domains'; import cls from 'classnames'; -import { useDoumentMembers } from 'data/document'; import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event'; import { useMount } from 'hooks/use-mount'; -import { useToggle } from 'hooks/use-toggle'; -import Router from 'next/router'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor'; -import { findMentions } from 'tiptap/prose-utils'; import styles from './index.module.scss'; -import { DocumentUserSetting } from './users'; interface IProps { user: ILoginUser; @@ -19,12 +14,8 @@ interface IProps { } export const Editor: React.FC = ({ user: currentUser, documentId, authority }) => { - const $hasShowUserSettingModal = useRef(false); const $editor = useRef(); const mounted = useMount(); - const { users, addUser, updateUser } = useDoumentMembers(documentId); - const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false); - const [mentionUsers, setMentionUsers] = useState([]); useEffect(() => { const handler = (data) => { @@ -39,56 +30,6 @@ export const Editor: React.FC = ({ user: currentUser, documentId, author }; }, []); - useEffect(() => { - const handler = () => { - const editor = $editor.current && $editor.current.getEditor(); - if (!editor) return; - - // 已经拦截过一次,不再拦截 - if ($hasShowUserSettingModal.current) return; - - const mentionUsers = findMentions(editor); - if (!mentionUsers || !mentionUsers.length) return; - - const currentUserAuth = users.find((user) => { - return user.user.name === currentUser.name; - }); - const isCurrentUserCreateUser = currentUserAuth.auth.createUserId === currentUser.id; - - if (!isCurrentUserCreateUser) { - return; - } - - const data = Array.from(new Set(mentionUsers)) - .filter((userName) => { - const exist = users.find((user) => { - return user.user.name === userName; - }); - if (!exist || !exist.auth.readable) return true; - return false; - }) - .filter(Boolean); - - if (!data.length) return; - - setMentionUsers(data); - toggleMentionUsersSettingVisible(true); - $hasShowUserSettingModal.current = true; - // ignore-me - const newErr = new Error('请完成权限操作后关闭页面'); - throw newErr; - }; - - Router.events.on('routeChangeStart', handler); - window.addEventListener('unload', handler); - - return () => { - $hasShowUserSettingModal.current = false; - Router.events.off('routeChangeStart', handler); - window.removeEventListener('unload', handler); - }; - }, [users, currentUser, toggleMentionUsersSettingVisible]); - return (
{mounted && ( @@ -103,14 +44,6 @@ export const Editor: React.FC = ({ user: currentUser, documentId, author onAwarenessUpdate={triggerJoinUser} /> )} -
); }; diff --git a/packages/client/src/components/document/editor/index.tsx b/packages/client/src/components/document/editor/index.tsx index af29ce65..f2af666f 100644 --- a/packages/client/src/components/document/editor/index.tsx +++ b/packages/client/src/components/document/editor/index.tsx @@ -38,11 +38,12 @@ export const DocumentEditor: React.FC = ({ documentId }) => { const goback = useCallback(() => { Router.push({ - pathname: `/wiki/${document.wikiId}/document/${documentId}`, + pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`, + query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id }, }).then(() => { triggerRefreshTocs(); }); - }, [document, documentId]); + }, [document]); const actions = useMemo( () => ( @@ -50,8 +51,17 @@ export const DocumentEditor: React.FC = ({ documentId }) => { {document && authority.readable && ( )} - {document && } - {document && } + {document && ( + + )} + {document && ( + + )} ), diff --git a/packages/client/src/components/document/editor/users.tsx b/packages/client/src/components/document/editor/users.tsx deleted file mode 100644 index bc936f7e..00000000 --- a/packages/client/src/components/document/editor/users.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Checkbox, Modal, Table, Typography } from '@douyinfe/semi-ui'; -import { IAuthority, IUser } from '@think/domains'; -import { DocAuth } from 'data/document'; -import React, { useMemo } from 'react'; - -interface IProps { - visible: boolean; - toggleVisible: (arg: boolean) => void; - mentionUsers: string[]; - users: Array<{ user: IUser; auth: IAuthority }>; - addUser: (auth: DocAuth) => Promise; - updateUser: (auth: DocAuth) => Promise; -} - -const { Text } = Typography; -const { Column } = Table; - -// eslint-disable-next-line react/display-name -const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, data) => { - const handle = (evt) => { - const ret = { - ...data, - }; - ret[authKey] = evt.target.checked; - onChange(ret); - }; - return ; -}; - -export const DocumentUserSetting: React.FC = ({ - visible, - toggleVisible, - mentionUsers, - users, - addUser, - updateUser, -}) => { - const renderUsers = useMemo(() => { - return mentionUsers - .map((mentionUser) => { - const exist = users.find((user) => { - return user.user.name === mentionUser; - }); - - if (!exist) return { userName: mentionUser, readable: false, editable: false, shouldAddToDocument: true }; - - return { - userName: mentionUser, - readable: exist.auth.readable, - editable: exist.auth.editable, - shouldAddToDocument: false, - }; - }) - .filter(Boolean); - }, [users, mentionUsers]); - - const handler = async (data) => { - if (data.shouldAddToDocument) { - await addUser(data.userName); - } - - await updateUser(data); - }; - - return ( - toggleVisible(false)} - maskClosable={false} - style={{ maxWidth: '96vw' }} - footer={null} - > - 您在该文档中 @ 了以下用户,请为他们操作权限,否则他们无法阅读该文档。 - - - - -
-
- ); -}; diff --git a/packages/client/src/components/document/reader/index.tsx b/packages/client/src/components/document/reader/index.tsx index 7224d237..6343c93a 100644 --- a/packages/client/src/components/document/reader/index.tsx +++ b/packages/client/src/components/document/reader/index.tsx @@ -56,7 +56,10 @@ export const DocumentReader: React.FC = ({ documentId }) => { ); const gotoEdit = useCallback(() => { - Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`); + Router.push({ + pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit`, + query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id }, + }); }, [document]); const actions = useMemo(() => { @@ -70,11 +73,26 @@ export const DocumentReader: React.FC = ({ documentId }) => { documentId={documentId} /> )} - {document && } + {document && ( + + )} + + + + + AuthEnumTextMap[auth]} + /> + } + /> + ( + <> + +
+ + )} + /> + + ); +}; diff --git a/packages/client/src/components/organization/delete/index.tsx b/packages/client/src/components/organization/delete/index.tsx new file mode 100644 index 00000000..e9714636 --- /dev/null +++ b/packages/client/src/components/organization/delete/index.tsx @@ -0,0 +1,49 @@ +import { IconDelete } from '@douyinfe/semi-icons'; +import { Modal, Space, Typography } from '@douyinfe/semi-ui'; +import { IOrganization } from '@think/domains'; +import { useOrganizationDetail } from 'data/organization'; +import { useRouterQuery } from 'hooks/use-router-query'; +import Router from 'next/router'; +import React, { useCallback } from 'react'; + +interface IProps { + organizationId: IOrganization['id']; + onDelete?: () => void; +} + +const { Text } = Typography; + +export const OrganizationDeletor: React.FC = ({ organizationId, onDelete, children }) => { + const { deleteOrganization } = useOrganizationDetail(organizationId); + + const deleteAction = useCallback(() => { + Modal.error({ + title: '确定删除吗?', + content: 删除后不可恢复!, + onOk: () => { + deleteOrganization().then(() => { + onDelete + ? onDelete() + : Router.push({ + pathname: `/`, + }); + }); + }, + okButtonProps: { + type: 'danger', + }, + style: { maxWidth: '96vw' }, + }); + }, [deleteOrganization, onDelete]); + + return ( + + {children || ( + + + 删除 + + )} + + ); +}; diff --git a/packages/client/src/components/organization/index.module.scss b/packages/client/src/components/organization/index.module.scss new file mode 100644 index 00000000..e7204454 --- /dev/null +++ b/packages/client/src/components/organization/index.module.scss @@ -0,0 +1,16 @@ +.wrap { + display: inline-flex !important; + flex-wrap: nowrap; + align-items: center; + + > svg { + width: 32px; + height: 32px; + } + + > span { + margin-left: 4px; + font-size: 1.2rem; + font-weight: 500; + } +} diff --git a/packages/client/src/components/organization/index.tsx b/packages/client/src/components/organization/index.tsx new file mode 100644 index 00000000..9e658005 --- /dev/null +++ b/packages/client/src/components/organization/index.tsx @@ -0,0 +1,66 @@ +import { Typography } from '@douyinfe/semi-ui'; +import { Avatar } from '@douyinfe/semi-ui'; +import { DataRender } from 'components/data-render'; +import { useOrganizationDetail } from 'data/organization'; +import { useRouterQuery } from 'hooks/use-router-query'; +import Link from 'next/link'; + +import styles from './index.module.scss'; + +const { Text } = Typography; + +export const OrganizationImage = () => { + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data, loading, error } = useOrganizationDetail(organizationId); + + return ( + { + return ( + +
+ + + + ); + }} + /> + ); +}; + +export const OrganizationText = () => { + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data, loading, error } = useOrganizationDetail(organizationId); + + return ( + { + return ( + + + {data.name} + + + ); + }} + /> + ); +}; diff --git a/packages/client/src/components/organization/public-switcher/index.module.scss b/packages/client/src/components/organization/public-switcher/index.module.scss new file mode 100644 index 00000000..5458e721 --- /dev/null +++ b/packages/client/src/components/organization/public-switcher/index.module.scss @@ -0,0 +1,4 @@ +.nameWrap { + display: flex; + align-items: center; +} diff --git a/packages/client/src/components/organization/public-switcher/index.tsx b/packages/client/src/components/organization/public-switcher/index.tsx new file mode 100644 index 00000000..f9362af6 --- /dev/null +++ b/packages/client/src/components/organization/public-switcher/index.tsx @@ -0,0 +1,22 @@ +import { Space } from '@douyinfe/semi-ui'; +import { LogoImage, LogoText } from 'components/logo'; +import { useUser } from 'data/user'; +import { useWindowSize } from 'hooks/use-window-size'; + +import { UserOrganizationsSwitcher } from '../switcher'; +import styles from './index.module.scss'; + +export const OrganizationPublicSwitcher = () => { + const { user } = useUser(); + const { width } = useWindowSize(); + + return ( + + + + {width >= 768 && } + + {user && } + + ); +}; diff --git a/packages/client/src/components/organization/setting/base/index.module.scss b/packages/client/src/components/organization/setting/base/index.module.scss new file mode 100644 index 00000000..c58f36b3 --- /dev/null +++ b/packages/client/src/components/organization/setting/base/index.module.scss @@ -0,0 +1,9 @@ +.cover { + margin-right: 12px; +} + +@media (max-width: 768px) { + .cover { + width: 100%; + } +} diff --git a/packages/client/src/components/organization/setting/base/index.tsx b/packages/client/src/components/organization/setting/base/index.tsx new file mode 100644 index 00000000..d4b1915b --- /dev/null +++ b/packages/client/src/components/organization/setting/base/index.tsx @@ -0,0 +1,118 @@ +import { Avatar, Button, Form, Toast, Typography } from '@douyinfe/semi-ui'; +import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; +import { ORGANIZATION_LOGOS } from '@think/constants'; +import { IOrganization } from '@think/domains'; +import { DataRender } from 'components/data-render'; +import { ImageUploader } from 'components/image-uploader'; +import { useCreateOrganization, useOrganizationDetail } from 'data/organization'; +import { useToggle } from 'hooks/use-toggle'; +import { SingleColumnLayout } from 'layouts/single-column'; +import Router from 'next/router'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import styles from './index.module.scss'; + +const images = [ + { + key: 'placeholers', + title: '图库', + images: ORGANIZATION_LOGOS, + }, +]; + +interface IProps { + organizationId: IOrganization['id']; +} + +export const Base: React.FC = ({ organizationId }) => { + const $form = useRef(); + const { data: organization, loading, error, update } = useOrganizationDetail(organizationId); + const [changed, toggleChanged] = useToggle(false); + const [currentCover, setCurrentCover] = useState(''); + + const setCover = useCallback((url) => { + $form.current.setValue('logo', url); + setCurrentCover(url); + }, []); + + const onFormChange = useCallback(() => { + toggleChanged(true); + }, [toggleChanged]); + + const onSubmit = useCallback(() => { + $form.current.validate().then((values) => { + update(values).then((res) => { + Toast.success('操作成功'); + }); + }); + }, [update]); + + useEffect(() => { + if (!organization) return; + setCurrentCover(organization.logo); + $form.current.setValues(organization); + }, [organization]); + + return ( + ( + ($form.current = formApi)} + onChange={onFormChange} + onSubmit={onSubmit} + > + +
+
+ +
+ + + +
+
+ + + + + +
+ +
+ + )} + /> + ); +}; diff --git a/packages/client/src/components/organization/setting/index.tsx b/packages/client/src/components/organization/setting/index.tsx new file mode 100644 index 00000000..aee04d64 --- /dev/null +++ b/packages/client/src/components/organization/setting/index.tsx @@ -0,0 +1,45 @@ +import { TabPane, Tabs } from '@douyinfe/semi-ui'; +import { IOrganization, IWiki } from '@think/domains'; +import { Seo } from 'components/seo'; +import { WikiTocsManager } from 'components/wiki/tocs/manager'; +import { useWikiDetail } from 'data/wiki'; +import React from 'react'; + +import { Base } from './base'; +import { OrganizationMembers } from './members'; +import { More } from './more'; + +interface IProps { + organizationId: IOrganization['id']; + tab?: string; + onNavigate: (arg: string) => void; +} + +const TitleMap = { + base: '基础信息', + members: '成员管理', + more: '更多', +}; + +export const OrganizationSetting: React.FC = ({ organizationId, tab, onNavigate }) => { + // const { data, update } = useWikiDetail(wikiId); + + return ( + <> + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/components/organization/setting/members/index.tsx b/packages/client/src/components/organization/setting/members/index.tsx new file mode 100644 index 00000000..e8381078 --- /dev/null +++ b/packages/client/src/components/organization/setting/members/index.tsx @@ -0,0 +1,12 @@ +import { IOrganization } from '@think/domains'; +import { Members } from 'components/members'; +import { useOrganizationMembers } from 'data/organization'; +import React from 'react'; + +interface IProps { + organizationId: IOrganization['id']; +} + +export const OrganizationMembers: React.FC = ({ organizationId }) => { + return ; +}; diff --git a/packages/client/src/components/organization/setting/more/index.tsx b/packages/client/src/components/organization/setting/more/index.tsx new file mode 100644 index 00000000..086c0126 --- /dev/null +++ b/packages/client/src/components/organization/setting/more/index.tsx @@ -0,0 +1,23 @@ +import { Banner, Button, Typography } from '@douyinfe/semi-ui'; +import { OrganizationDeletor } from 'components/organization/delete'; + +const { Paragraph } = Typography; + +export const More = ({ organizationId }) => { + return ( +
+ 删除组织及内部所有知识库以及文档,不可恢复!} + style={{ marginBottom: 16 }} + /> + + + +
+ ); +}; diff --git a/packages/client/src/components/organization/switcher/index.module.scss b/packages/client/src/components/organization/switcher/index.module.scss new file mode 100644 index 00000000..5458e721 --- /dev/null +++ b/packages/client/src/components/organization/switcher/index.module.scss @@ -0,0 +1,4 @@ +.nameWrap { + display: flex; + align-items: center; +} diff --git a/packages/client/src/components/organization/switcher/index.tsx b/packages/client/src/components/organization/switcher/index.tsx new file mode 100644 index 00000000..e2ee8ec0 --- /dev/null +++ b/packages/client/src/components/organization/switcher/index.tsx @@ -0,0 +1,161 @@ +import { IconAppCenter, IconApps, IconSmallTriangleDown } from '@douyinfe/semi-icons'; +import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui'; +import { Avatar } from '@douyinfe/semi-ui'; +import { DataRender } from 'components/data-render'; +import { useOrganizationDetail, useUserOrganizations } from 'data/organization'; +import { useRouterQuery } from 'hooks/use-router-query'; +import Link from 'next/link'; +import { useMemo } from 'react'; + +import styles from './index.module.scss'; + +const { Text, Paragraph } = Typography; + +export const UserOrganizationsSwitcher = () => { + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { + data: userOrganizations, + loading: userOrganizationsLoading, + error: userOrganizationsError, + } = useUserOrganizations(); + const filterUserOrganizations = useMemo(() => { + return userOrganizations && userOrganizations.length + ? userOrganizations.filter((org) => org.id !== organizationId) + : []; + }, [userOrganizations, organizationId]); + + return ( + { + return ( + + {filterUserOrganizations.length ? ( + <> + {filterUserOrganizations.map((org) => { + return ( + + + + + + {org.name} + + + + + ); + })} + + + ) : null} + + + + + + + + + + 前往广场 + + + + + + + + + + + + + + + 新建组织 + + + + + + + ); + }} + /> + } + > + - + ); }; diff --git a/packages/client/src/components/wiki/setting/users/add.tsx b/packages/client/src/components/wiki/setting/users/add.tsx deleted file mode 100644 index ae502663..00000000 --- a/packages/client/src/components/wiki/setting/users/add.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Banner, Button, Input, Modal, Select, Space, Typography } from '@douyinfe/semi-ui'; -import { WIKI_USER_ROLES, WikiUserRole } from '@think/domains'; -import { IWikiUserOpeateData } from 'data/wiki'; -import React, { useCallback, useState } from 'react'; - -interface IProps { - visible: boolean; - toggleVisible: (arg) => void; - onOk: (arg: IWikiUserOpeateData) => any; -} - -const { Paragraph } = Typography; - -export const AddUser: React.FC = ({ visible, toggleVisible, onOk }) => { - const [userRole, setUserRole] = useState(WikiUserRole.normal); - const [userName, setUserName] = useState(''); - const handleOk = useCallback(() => { - onOk({ userName, userRole } as unknown as IWikiUserOpeateData).then(() => { - setUserRole(WikiUserRole.normal); - setUserName(''); - toggleVisible(false); - }); - }, [onOk, userName, userRole, toggleVisible]); - - return ( - toggleVisible(false)} - maskClosable={false} - style={{ maxWidth: '96vw' }} - footer={null} - > -
- {userRole === WikiUserRole.admin ? ( - - ) : null} - - - - - -
-
- ); -}; diff --git a/packages/client/src/components/wiki/setting/users/edit.tsx b/packages/client/src/components/wiki/setting/users/edit.tsx deleted file mode 100644 index f68f98ab..00000000 --- a/packages/client/src/components/wiki/setting/users/edit.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Banner, Button, Modal, Select } from '@douyinfe/semi-ui'; -import { WIKI_USER_ROLES, WikiUserRole } from '@think/domains'; -import React, { useCallback, useState } from 'react'; - -interface IProps { - visible: boolean; - toggleVisible: (arg) => void; - onOk: (arg: WikiUserRole) => any; -} - -export const EditUser: React.FC = ({ visible, toggleVisible, onOk }) => { - const [userRole, setUserRole] = useState(WikiUserRole.normal); - const handleOk = useCallback(() => { - onOk(userRole).then(() => { - setUserRole(WikiUserRole.normal); - toggleVisible(false); - }); - }, [onOk, userRole, toggleVisible]); - - return ( - toggleVisible(false)} - maskClosable={false} - style={{ maxWidth: '96vw' }} - footer={null} - > -
- {userRole === WikiUserRole.admin ? ( - - ) : null} - - -
-
- ); -}; diff --git a/packages/client/src/components/wiki/setting/users/index.tsx b/packages/client/src/components/wiki/setting/users/index.tsx index 98f43f6e..ded43f0c 100644 --- a/packages/client/src/components/wiki/setting/users/index.tsx +++ b/packages/client/src/components/wiki/setting/users/index.tsx @@ -1,88 +1,17 @@ -import { IconDelete, IconEdit } from '@douyinfe/semi-icons'; -import { Button, Popconfirm, Table } from '@douyinfe/semi-ui'; -import { getWikiUserRoleText } from '@think/domains'; -import { DataRender } from 'components/data-render'; -import { LocaleTime } from 'components/locale-time'; +import { Members } from 'components/members'; import { useWikiMembers } from 'data/wiki'; -import { useToggle } from 'hooks/use-toggle'; -import React, { useState } from 'react'; - -import { AddUser } from './add'; -import { EditUser } from './edit'; -import { Placeholder } from './placeholder'; +import React from 'react'; interface IProps { wikiId: string; } -const { Column } = Table; - export const Users: React.FC = ({ wikiId }) => { - const [visible, toggleVisible] = useToggle(false); - const [editVisible, toggleEditVisible] = useToggle(false); - const [currentUser, setCurrentUser] = useState(null); - const { users, loading, error, addUser, updateUser, deleteUser } = useWikiMembers(wikiId); - - const editUser = (user) => { - setCurrentUser(user); - toggleEditVisible(true); - }; - - const handleEdit = (userRole) => { - return updateUser({ userName: currentUser.userName, userRole }).then(() => { - setCurrentUser(null); - }); - }; - return ( - <> - } - normalContent={() => ( -
-
- -
- - - - } - /> - ( - <> -
-
- )} - /> - - - + ); }; diff --git a/packages/client/src/components/wiki/setting/users/placeholder.tsx b/packages/client/src/components/wiki/setting/users/placeholder.tsx deleted file mode 100644 index 2c0d27be..00000000 --- a/packages/client/src/components/wiki/setting/users/placeholder.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Skeleton, Table } from '@douyinfe/semi-ui'; - -const columns = [ - { - title: '用户名', - dataIndex: 'userName', - key: 'userName', - }, - { - title: '成员角色', - dataIndex: 'userRole', - key: 'userRole', - }, - { - title: '加入时间', - dataIndex: 'createdAt', - key: 'createdAt', - }, - { - title: '操作', - dataIndex: 'actions', - key: 'actions', - }, -]; - -const PLACEHOLDER_DATA = Array.from({ length: 3 }).fill({ - name: , - userRole: , - createdAt: , -}); - -export const Placeholder = () => { - return ( - - } - loading={true} - > - ); -}; diff --git a/packages/client/src/components/wiki/star/index.tsx b/packages/client/src/components/wiki/star/index.tsx index ddc5931d..794c9e55 100644 --- a/packages/client/src/components/wiki/star/index.tsx +++ b/packages/client/src/components/wiki/star/index.tsx @@ -1,16 +1,18 @@ import { IconStar } from '@douyinfe/semi-icons'; import { Button, Tooltip } from '@douyinfe/semi-ui'; +import { IOrganization, IWiki } from '@think/domains'; import { useWikiStarToggle } from 'data/star'; import React from 'react'; interface IProps { - wikiId: string; + organizationId: IOrganization['id']; + wikiId: IWiki['id']; render?: (arg: { star: boolean; text: string; toggleStar: () => Promise }) => React.ReactNode; onChange?: () => void; } -export const WikiStar: React.FC = ({ wikiId, render, onChange }) => { - const { data, toggle } = useWikiStarToggle(wikiId); +export const WikiStar: React.FC = ({ organizationId, wikiId, render, onChange }) => { + const { data, toggle } = useWikiStarToggle(organizationId, wikiId); const text = data ? '取消收藏' : '收藏知识库'; return ( diff --git a/packages/client/src/components/wiki/tocs/index.tsx b/packages/client/src/components/wiki/tocs/index.tsx index 37734846..55512a08 100644 --- a/packages/client/src/components/wiki/tocs/index.tsx +++ b/packages/client/src/components/wiki/tocs/index.tsx @@ -1,10 +1,11 @@ import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons'; import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui'; +import { IDocument } from '@think/domains'; import cls from 'classnames'; import { DataRender } from 'components/data-render'; import { IconOverview, IconSetting } from 'components/icons'; import { findParents } from 'components/wiki/tocs/utils'; -import { useStarWikis, useWikiStarDocuments } from 'data/star'; +import { useStarDocumentsInWiki, useStarWikisInOrganization } from 'data/star'; import { useWikiDetail, useWikiTocs } from 'data/wiki'; import { triggerCreateDocument } from 'event'; import Link from 'next/link'; @@ -18,7 +19,7 @@ interface IProps { wikiId: string; documentId?: string; docAsLink?: string; - getDocLink?: (arg: string) => string; + getDocLink?: (arg: IDocument) => string; } const { Text } = Typography; @@ -26,18 +27,18 @@ const { Text } = Typography; export const WikiTocs: React.FC = ({ wikiId, documentId = null, - docAsLink = '/wiki/[wikiId]/document/[documentId]', - getDocLink = (documentId) => `/wiki/${wikiId}/document/${documentId}`, + docAsLink = '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]', + getDocLink = (document) => `/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`, }) => { const { pathname, query } = useRouter(); const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId); const { data: tocs, loading: tocsLoading, error: tocsError } = useWikiTocs(wikiId); - const { data: starWikis } = useStarWikis(); + const { data: starWikis } = useStarWikisInOrganization(query.organizationId); const { data: starDocuments, loading: starDocumentsLoading, error: starDocumentsError, - } = useWikiStarDocuments(wikiId); + } = useStarDocumentsInWiki(query.organizationId, wikiId); const [parentIds, setParentIds] = useState>([]); const otherStarWikis = useMemo(() => (starWikis || []).filter((wiki) => wiki.id !== wikiId), [starWikis, wikiId]); @@ -86,8 +87,8 @@ export const WikiTocs: React.FC = ({
= ({
@@ -235,11 +237,16 @@ export const WikiTocs: React.FC = ({ } error={wikiError} normalContent={() => ( -
+
diff --git a/packages/client/src/components/wiki/tocs/public.tsx b/packages/client/src/components/wiki/tocs/public.tsx index 986cc40e..5ea059da 100644 --- a/packages/client/src/components/wiki/tocs/public.tsx +++ b/packages/client/src/components/wiki/tocs/public.tsx @@ -1,5 +1,6 @@ import { IconPlus } from '@douyinfe/semi-icons'; import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui'; +import { IDocument } from '@think/domains'; import { DataRender } from 'components/data-render'; import { IconOverview } from 'components/icons'; import { LogoImage, LogoText } from 'components/logo'; @@ -17,7 +18,7 @@ interface IProps { wikiId: string; documentId?: string; docAsLink?: string; - getDocLink?: (arg: string) => string; + getDocLink?: (arg: IDocument) => string; pageTitle: string; } @@ -28,7 +29,7 @@ export const WikiPublicTocs: React.FC = ({ wikiId, documentId = null, docAsLink = '/share/wiki/[wikiId]/document/[documentId]', - getDocLink = (documentId) => `/share/wiki/${wikiId}/document/${documentId}`, + getDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`, }) => { const { pathname } = useRouter(); const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId); diff --git a/packages/client/src/components/wiki/tocs/tree.tsx b/packages/client/src/components/wiki/tocs/tree.tsx index a93492f4..dbc2b72b 100644 --- a/packages/client/src/components/wiki/tocs/tree.tsx +++ b/packages/client/src/components/wiki/tocs/tree.tsx @@ -16,6 +16,7 @@ const Actions = ({ node }) => { (
- + >; +type ICreateDocument = Partial>; type IDocumentWithAuth = { document: IDocument; authority: IAuthority }; type IUpdateDocument = Partial>; +/** + * 搜索组织内文档 + * @returns + */ +export const useSearchDocuments = (organizationId) => { + const [apiWithLoading, loading] = useAsyncLoading((keyword) => + HttpClient.request({ + method: DocumentApiDefinition.search.method, + url: DocumentApiDefinition.search.client(organizationId), + params: { keyword }, + }) + ); + + return { + search: apiWithLoading, + loading, + }; +}; + /** * 获取用户最近访问的文档 * @returns */ -export const getRecentVisitedDocuments = (cookie = null): Promise => { +export const getRecentVisitedDocuments = (organizationId, cookie = null): Promise => { return HttpClient.request({ method: DocumentApiDefinition.recent.method, - url: DocumentApiDefinition.recent.client(), + url: DocumentApiDefinition.recent.client(organizationId), cookie, }); }; @@ -26,10 +45,10 @@ export const getRecentVisitedDocuments = (cookie = null): Promise { +export const useRecentDocuments = (organizationId) => { const { data, error, isLoading, refetch } = useQuery( - DocumentApiDefinition.recent.client(), - getRecentVisitedDocuments, + DocumentApiDefinition.recent.client(organizationId), + () => getRecentVisitedDocuments(organizationId), { refetchOnWindowFocus: false, enabled: false } ); return { data, error, loading: isLoading, refresh: refetch }; @@ -46,11 +65,20 @@ export type DocAuth = { * @param cookie * @returns */ -export const getDocumentMembers = (documentId, cookie = null): Promise> => { +export const getDocumentMembers = ( + documentId, + page, + pageSize, + cookie = null +): Promise> => { return HttpClient.request({ method: DocumentApiDefinition.getMemberById.method, url: DocumentApiDefinition.getMemberById.client(documentId), cookie, + params: { + page, + pageSize, + }, }); }; @@ -60,23 +88,20 @@ export const getDocumentMembers = (documentId, cookie = null): Promise>) => { + const [pageSize] = useState(12); + const [page, setPage] = useState(1); const { data, error, isLoading, refetch } = useQuery( - DocumentApiDefinition.getMemberById.client(documentId), - () => getDocumentMembers(documentId), + [DocumentApiDefinition.getMemberById.client(documentId), page], + () => getDocumentMembers(documentId, page, pageSize), options ); const addUser = useCallback( - async (userName) => { + async (data) => { const ret = await HttpClient.request({ method: DocumentApiDefinition.addMemberById.method, url: DocumentApiDefinition.addMemberById.client(documentId), - data: { - documentId, - userName, - readable: true, - editable: false, - }, + data, }); refetch(); return ret; @@ -85,14 +110,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions { + async (data) => { const ret = await HttpClient.request({ method: DocumentApiDefinition.updateMemberById.method, url: DocumentApiDefinition.updateMemberById.client(documentId), - data: { - documentId, - ...docAuth, - }, + data, }); refetch(); return ret; @@ -101,14 +123,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions { + async (data) => { const ret = await HttpClient.request({ method: DocumentApiDefinition.deleteMemberById.method, url: DocumentApiDefinition.deleteMemberById.client(documentId), - data: { - documentId, - ...docAuth, - }, + data, }); refetch(); return ret; @@ -116,7 +135,7 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions { + const [apiWithLoading, loading] = useAsyncLoading((data) => + HttpClient.request({ + method: OrganizationApiDefinition.createOrganization.method, + url: OrganizationApiDefinition.createOrganization.client(), + data, + }) + ); + + return { + create: apiWithLoading, + loading, + }; +}; + +export const getPersonalOrganization = (cookie = null): Promise => { + return HttpClient.request({ + method: OrganizationApiDefinition.getPersonalOrganization.method, + url: OrganizationApiDefinition.getPersonalOrganization.client(), + cookie, + }); +}; + +/** + * 获取个人组织 + * @returns + */ +export const usePeronalOrganization = () => { + const { data, error, isLoading, refetch } = useQuery( + OrganizationApiDefinition.getPersonalOrganization.client(), + getPersonalOrganization + ); + return { data, error, loading: isLoading, refresh: refetch }; +}; + +export const getUserOrganizations = (cookie = null): Promise> => { + return HttpClient.request({ + method: OrganizationApiDefinition.getUserOrganizations.method, + url: OrganizationApiDefinition.getUserOrganizations.client(), + cookie, + }); +}; + +/** + * 获取用户除个人组织外可访问的组织 + * @returns + */ +export const useUserOrganizations = () => { + const { data, error, isLoading, refetch } = useQuery( + OrganizationApiDefinition.getUserOrganizations.client(), + getUserOrganizations + ); + + useEffect(() => { + event.on(REFRESH_ORGANIZATIONS, refetch); + return () => { + event.off(REFRESH_ORGANIZATIONS, refetch); + }; + }, [refetch]); + + return { data, error, loading: isLoading, refresh: refetch }; +}; + +export const getOrganizationDetail = (id, cookie = null): Promise => { + return HttpClient.request({ + method: OrganizationApiDefinition.getOrganizationDetail.method, + url: OrganizationApiDefinition.getOrganizationDetail.client(id), + cookie, + }); +}; + +/** + * 获取组织详情 + * @returns + */ +export const useOrganizationDetail = (id) => { + const { data, error, isLoading, refetch } = useQuery(OrganizationApiDefinition.getOrganizationDetail.client(id), () => + getOrganizationDetail(id) + ); + + /** + * 更新组织信息 + * @param data + * @returns + */ + const update = useCallback( + async (data) => { + const res = await HttpClient.request({ + method: OrganizationApiDefinition.updateOrganization.method, + url: OrganizationApiDefinition.updateOrganization.client(id), + data, + }); + refetch(); + triggerRefreshOrganizations(); + return res; + }, + [refetch, id] + ); + + const deleteOrganization = useCallback(async () => { + const res = await HttpClient.request({ + method: OrganizationApiDefinition.deleteOrganization.method, + url: OrganizationApiDefinition.deleteOrganization.client(id), + }); + refetch(); + return res; + }, [refetch, id]); + + return { data, error, loading: isLoading, refresh: refetch, update, deleteOrganization }; +}; + +export const getOrganizationMembers = ( + id, + page = 1, + pageSize, + cookie = null +): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => { + return HttpClient.request({ + method: OrganizationApiDefinition.getMembers.method, + url: OrganizationApiDefinition.getMembers.client(id), + cookie, + params: { + page, + pageSize, + }, + }); +}; + +/** + * 获取组织成员 + * @returns + */ +export const useOrganizationMembers = (id) => { + const [pageSize] = useState(12); + const [page, setPage] = useState(1); + const { data, error, isLoading, refetch } = useQuery([OrganizationApiDefinition.getMembers.client(id), page], () => + getOrganizationMembers(id, page, pageSize) + ); + + const addUser = useCallback( + async (data) => { + const ret = await HttpClient.request({ + method: OrganizationApiDefinition.addMemberById.method, + url: OrganizationApiDefinition.addMemberById.client(id), + data, + }); + refetch(); + return ret; + }, + [refetch, id] + ); + + const updateUser = useCallback( + async (data) => { + const ret = await HttpClient.request({ + method: OrganizationApiDefinition.updateMemberById.method, + url: OrganizationApiDefinition.updateMemberById.client(id), + data, + }); + refetch(); + return ret; + }, + [refetch, id] + ); + + const deleteUser = useCallback( + async (data) => { + const ret = await HttpClient.request({ + method: OrganizationApiDefinition.deleteMemberById.method, + url: OrganizationApiDefinition.deleteMemberById.client(id), + data, + }); + refetch(); + return ret; + }, + [refetch, id] + ); + + return { + data, + error, + loading: isLoading, + page, + pageSize, + setPage, + refresh: refetch, + addUser, + updateUser, + deleteUser, + }; +}; diff --git a/packages/client/src/data/star.ts b/packages/client/src/data/star.ts index e56ade8c..05e5cf24 100644 --- a/packages/client/src/data/star.ts +++ b/packages/client/src/data/star.ts @@ -7,25 +7,29 @@ import { HttpClient } from 'services/http-client'; export type IWikiWithIsMember = IWiki & { isMember?: boolean }; /** - * 获取用户收藏的知识库 + * 获取组织内加星的知识库 * @returns */ -export const getStarWikis = (cookie = null): Promise => { +export const getStarWikisInOrganization = (organizationId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.wikis.method, - url: StarApiDefinition.wikis.client(), + method: StarApiDefinition.getStarWikisInOrganization.method, + url: StarApiDefinition.getStarWikisInOrganization.client(organizationId), cookie, }); }; /** - * 获取用户收藏的知识库 + * 获取组织内加星的知识库 * @returns */ -export const useStarWikis = () => { - const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.wikis.client(), getStarWikis, { - staleTime: 500, - }); +export const useStarWikisInOrganization = (organizationId) => { + const { data, error, isLoading, refetch } = useQuery( + StarApiDefinition.getStarWikisInOrganization.client(organizationId), + () => getStarWikisInOrganization(organizationId), + { + staleTime: 500, + } + ); useEffect(() => { event.on(TOGGLE_STAR_WIKI, refetch); @@ -43,12 +47,13 @@ export const useStarWikis = () => { * @param wikiId * @returns */ -export const getWikiIsStar = (wikiId, cookie = null): Promise => { +export const getWikiIsStar = (organizationId, wikiId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.check.method, - url: StarApiDefinition.check.client(), + method: StarApiDefinition.isStared.method, + url: StarApiDefinition.isStared.client(), cookie, data: { + organizationId, wikiId, }, }); @@ -59,30 +64,33 @@ export const getWikiIsStar = (wikiId, cookie = null): Promise => { * @param wikiId * @returns */ -export const toggleStarWiki = (wikiId, cookie = null): Promise => { +export const toggleStarWiki = (organizationId, wikiId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.toggle.method, - url: StarApiDefinition.toggle.client(), + method: StarApiDefinition.toggleStar.method, + url: StarApiDefinition.toggleStar.client(), cookie, data: { + organizationId, wikiId, }, }); }; /** - * 收藏知识库 + * 加星或取消 * @param wikiId * @returns */ -export const useWikiStarToggle = (wikiId) => { - const { data, error, refetch } = useQuery([StarApiDefinition.check.client(), wikiId], () => getWikiIsStar(wikiId)); +export const useWikiStarToggle = (organizationId, wikiId) => { + const { data, error, refetch } = useQuery([StarApiDefinition.toggleStar.client(), organizationId, wikiId], () => + getWikiIsStar(organizationId, wikiId) + ); const toggle = useCallback(async () => { - await toggleStarWiki(wikiId); + await toggleStarWiki(organizationId, wikiId); refetch(); triggerToggleStarWiki(); - }, [refetch, wikiId]); + }, [refetch, organizationId, wikiId]); return { data, error, toggle }; }; @@ -91,10 +99,10 @@ export const useWikiStarToggle = (wikiId) => { * 获取用户收藏的文档 * @returns */ -export const getStarDocuments = (cookie = null): Promise => { +export const getStarDocumentsInOrganization = (organizationId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.documents.method, - url: StarApiDefinition.documents.client(), + method: StarApiDefinition.getStarDocumentsInOrganization.method, + url: StarApiDefinition.getStarDocumentsInOrganization.client(organizationId), cookie, }); }; @@ -103,10 +111,14 @@ export const getStarDocuments = (cookie = null): Promise => { * 获取用户收藏的文档 * @returns */ -export const useStarDocuments = () => { - const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.documents.client(), getStarDocuments, { - staleTime: 500, - }); +export const useStarDocumentsInOrganization = (organizationId) => { + const { data, error, isLoading, refetch } = useQuery( + StarApiDefinition.getStarDocumentsInOrganization.client(organizationId), + () => getStarDocumentsInOrganization(organizationId), + { + staleTime: 500, + } + ); useEffect(() => { event.on(TOGGLE_STAR_DOUCMENT, refetch); @@ -122,12 +134,13 @@ export const useStarDocuments = () => { * @param documentId * @returns */ -export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise => { +export const getDocumentIsStar = (organizationId, wikiId, documentId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.check.method, - url: StarApiDefinition.check.client(), + method: StarApiDefinition.isStared.method, + url: StarApiDefinition.isStared.client(), cookie, data: { + organizationId, wikiId, documentId, }, @@ -139,12 +152,13 @@ export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise => { +export const toggleDocumentStar = (organizationId, wikiId, documentId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.toggle.method, - url: StarApiDefinition.toggle.client(), + method: StarApiDefinition.toggleStar.method, + url: StarApiDefinition.toggleStar.client(), cookie, data: { + organizationId, wikiId, documentId, }, @@ -156,18 +170,18 @@ export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise) => { +export const useDocumentStarToggle = (organizationId, wikiId, documentId, options?: UseQueryOptions) => { const { data, error, refetch } = useQuery( - [StarApiDefinition.check.client(), wikiId, documentId], - () => getDocumentIsStar(wikiId, documentId), + [StarApiDefinition.isStared.client(), organizationId, wikiId, documentId], + () => getDocumentIsStar(organizationId, wikiId, documentId), options ); const toggle = useCallback(async () => { - await toggleDocumentStar(wikiId, documentId); + await toggleDocumentStar(organizationId, wikiId, documentId); refetch(); triggerToggleStarDocument(); - }, [refetch, wikiId, documentId]); + }, [refetch, organizationId, wikiId, documentId]); return { data, error, toggle }; }; @@ -176,12 +190,13 @@ export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOpti * 获取知识库加星的文档 * @returns */ -export const getWikiStarDocuments = (wikiId, cookie = null): Promise => { +export const getStarDocumentsInWiki = (organizationId, wikiId, cookie = null): Promise => { return HttpClient.request({ - method: StarApiDefinition.wikiDocuments.method, - url: StarApiDefinition.wikiDocuments.client(), + method: StarApiDefinition.getStarDocumentsInWiki.method, + url: StarApiDefinition.getStarDocumentsInWiki.client(), cookie, params: { + organizationId, wikiId, }, }); @@ -191,10 +206,10 @@ export const getWikiStarDocuments = (wikiId, cookie = null): Promise { +export const useStarDocumentsInWiki = (organizationId, wikiId) => { const { data, error, isLoading, refetch } = useQuery( - [StarApiDefinition.wikiDocuments.client(), wikiId], - () => getWikiStarDocuments(wikiId), + [StarApiDefinition.getStarDocumentsInWiki.client(), organizationId, wikiId], + () => getStarDocumentsInWiki(organizationId, wikiId), { staleTime: 500, } diff --git a/packages/client/src/data/user.ts b/packages/client/src/data/user.ts index 59fa011b..0c0778fc 100644 --- a/packages/client/src/data/user.ts +++ b/packages/client/src/data/user.ts @@ -1,5 +1,5 @@ import { Toast } from '@douyinfe/semi-ui'; -import { ILoginUser, ISystemConfig, IUser, UserApiDefinition } from '@think/domains'; +import { ILoginUser, ISystemConfig, IUser, SystemApiDefinition, UserApiDefinition } from '@think/domains'; import { getStorage, setStorage } from 'helpers/storage'; import { useAsyncLoading } from 'hooks/use-async-loading'; import Router, { useRouter } from 'next/router'; @@ -109,7 +109,7 @@ export const useUser = () => { refetch(); setStorage('user', JSON.stringify(user)); user.token && setStorage('token,', user.token); - const next = router.query?.redirect || '/'; + const next = router.query?.redirect || '/app'; Router.replace(next as string); }); }, @@ -138,10 +138,17 @@ export const useUser = () => { }; }; -/** - * 获取验证码 - * @returns - */ +export const useSystemPublicConfig = () => { + const { data, error, isLoading, refetch } = useQuery(SystemApiDefinition.getPublicConfig.client(), () => + HttpClient.request({ + method: SystemApiDefinition.getPublicConfig.method, + url: SystemApiDefinition.getPublicConfig.client(), + }) + ); + + return { data, error, loading: isLoading, refresh: refetch }; +}; + export const useSystemConfig = () => { const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () => HttpClient.request({ diff --git a/packages/client/src/data/wiki.ts b/packages/client/src/data/wiki.ts index 4954d8e2..86ef1bf3 100644 --- a/packages/client/src/data/wiki.ts +++ b/packages/client/src/data/wiki.ts @@ -1,4 +1,4 @@ -import { IDocument, IUser, IWiki, IWikiUser, WikiApiDefinition } from '@think/domains'; +import { IAuth, IDocument, IUser, IWiki, WikiApiDefinition } from '@think/domains'; import { event, REFRESH_TOCS } from 'event'; import { useCallback, useEffect, useState } from 'react'; import { useQuery } from 'react-query'; @@ -6,20 +6,16 @@ import { HttpClient } from 'services/http-client'; export type ICreateWiki = Pick; export type IUpdateWiki = Partial; -export type IWikiUserOpeateData = { - userName: Pick; - userRole: Pick; -}; export type IWikiWithIsMember = IWiki & { isMember: boolean }; /** * 获取用户所有知识库 * @returns */ -export const getAllWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => { +export const getAllWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => { return HttpClient.request({ method: WikiApiDefinition.getAllWikis.method, - url: WikiApiDefinition.getAllWikis.client(), + url: WikiApiDefinition.getAllWikis.client(organizationId), cookie, }); }; @@ -28,8 +24,10 @@ export const getAllWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb * 获取用户所有知识库 * @returns */ -export const useAllWikis = () => { - const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(), getAllWikis); +export const useAllWikis = (organizationId) => { + const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(organizationId), () => + getAllWikis(organizationId) + ); const list = (data && data.data) || []; const total = (data && data.total) || 0; return { data: list, total, error, loading: isLoading }; @@ -39,10 +37,10 @@ export const useAllWikis = () => { * 获取用户参与的知识库 * @returns */ -export const getJoinWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => { +export const getJoinWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => { return HttpClient.request({ method: WikiApiDefinition.getJoinWikis.method, - url: WikiApiDefinition.getJoinWikis.client(), + url: WikiApiDefinition.getJoinWikis.client(organizationId), cookie, }); }; @@ -51,8 +49,10 @@ export const getJoinWikis = (cookie = null): Promise<{ data: IWiki[]; total: num * 获取用户参与的知识库 * @returns */ -export const useJoinWikis = () => { - const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(), getJoinWikis); +export const useJoinWikis = (organizationId) => { + const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(organizationId), () => + getJoinWikis(organizationId) + ); const list = (data && data.data) || []; const total = (data && data.total) || 0; @@ -63,10 +63,10 @@ export const useJoinWikis = () => { * 获取用户创建的知识库 * @returns */ -export const getOwnWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => { +export const getOwnWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => { return HttpClient.request({ method: WikiApiDefinition.getOwnWikis.method, - url: WikiApiDefinition.getOwnWikis.client(), + url: WikiApiDefinition.getOwnWikis.client(organizationId), cookie, }); }; @@ -75,24 +75,29 @@ export const getOwnWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb * 获取用户创建的知识库 * @returns */ -export const useOwnWikis = () => { - const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(), getOwnWikis); +export const useOwnWikis = (organizationId) => { + const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(organizationId), () => + getOwnWikis(organizationId) + ); const createWiki = useCallback( async (data: ICreateWiki) => { - const res = await HttpClient.request({ + const res = await HttpClient.request({ method: WikiApiDefinition.add.method, url: WikiApiDefinition.add.client(), - data, + data: { + organizationId, + ...data, + }, }); refetch(); return res; }, - [refetch] + [organizationId, refetch] ); /** - * 删除文档 + * 删除知识库 * @param id * @returns */ @@ -311,11 +316,20 @@ export const useWikiDocuments = (wikiId) => { * @param cookie * @returns */ -export const getWikiMembers = (wikiId, cookie = null): Promise => { +export const getWikiMembers = ( + wikiId, + page, + pageSize, + cookie = null +): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => { return HttpClient.request({ method: WikiApiDefinition.getMemberById.method, url: WikiApiDefinition.getMemberById.client(wikiId), cookie, + params: { + page, + pageSize, + }, }); }; @@ -325,12 +339,14 @@ export const getWikiMembers = (wikiId, cookie = null): Promise => { * @returns */ export const useWikiMembers = (wikiId) => { - const { data, error, isLoading, refetch } = useQuery(WikiApiDefinition.getMemberById.client(wikiId), () => - getWikiMembers(wikiId) + const [pageSize] = useState(12); + const [page, setPage] = useState(1); + const { data, error, isLoading, refetch } = useQuery([WikiApiDefinition.getMemberById.client(wikiId), page], () => + getWikiMembers(wikiId, page, pageSize) ); const addUser = useCallback( - async (data: IWikiUserOpeateData) => { + async (data) => { const ret = await HttpClient.request({ method: WikiApiDefinition.addMemberById.method, url: WikiApiDefinition.addMemberById.client(wikiId), @@ -343,7 +359,7 @@ export const useWikiMembers = (wikiId) => { ); const updateUser = useCallback( - async (data: IWikiUserOpeateData) => { + async (data) => { const ret = await HttpClient.request({ method: WikiApiDefinition.updateMemberById.method, url: WikiApiDefinition.updateMemberById.client(wikiId), @@ -356,7 +372,7 @@ export const useWikiMembers = (wikiId) => { ); const deleteUser = useCallback( - async (data: IWikiUserOpeateData) => { + async (data) => { const ret = await HttpClient.request({ method: WikiApiDefinition.deleteMemberById.method, url: WikiApiDefinition.deleteMemberById.client(wikiId), @@ -368,7 +384,7 @@ export const useWikiMembers = (wikiId) => { [refetch, wikiId] ); - return { users: data, loading: isLoading, error, addUser, updateUser, deleteUser }; + return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser }; }; /** diff --git a/packages/client/src/event/index.ts b/packages/client/src/event/index.ts index 3a3627ff..ff385f74 100644 --- a/packages/client/src/event/index.ts +++ b/packages/client/src/event/index.ts @@ -3,6 +3,7 @@ import { EventEmitter } from 'helpers/event-emitter'; export const event = new EventEmitter(); +export const REFRESH_ORGANIZATIONS = 'REFRESH_ORGANIZATIONS'; // 刷新组织列表 export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录 export const CREATE_DOCUMENT = `CREATE_DOCUMENT`; export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库 @@ -60,3 +61,7 @@ export const triggerToggleStarWiki = () => { export const triggerToggleStarDocument = () => { event.emit(TOGGLE_STAR_DOUCMENT); }; + +export const triggerRefreshOrganizations = () => { + event.emit(REFRESH_ORGANIZATIONS); +}; diff --git a/packages/client/src/hooks/use-router-query.tsx b/packages/client/src/hooks/use-router-query.tsx index 2374a201..6a6c849d 100644 --- a/packages/client/src/hooks/use-router-query.tsx +++ b/packages/client/src/hooks/use-router-query.tsx @@ -2,5 +2,6 @@ import { Router, useRouter } from 'next/router'; export function useRouterQuery() { const router = useRouter(); + return router.query as T; } diff --git a/packages/client/src/illustrations/team-work.tsx b/packages/client/src/illustrations/team-work.tsx new file mode 100644 index 00000000..5c2160f6 --- /dev/null +++ b/packages/client/src/illustrations/team-work.tsx @@ -0,0 +1,229 @@ +import React from 'react'; + +export const TeamWorkIllustration = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/layouts/app-double-column/index.module.scss b/packages/client/src/layouts/app-double-column/index.module.scss new file mode 100644 index 00000000..d766473c --- /dev/null +++ b/packages/client/src/layouts/app-double-column/index.module.scss @@ -0,0 +1,46 @@ +.wrap { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--semi-color-nav-bg); + + .contentWrap { + display: flex; + flex: 1; + flex-wrap: nowrap; + overflow: hidden; + + > div { + height: calc(100% - 60px) !important; + + :global { + .Pane2 { + height: 100%; + overflow: auto; + } + } + } + + .leftWrap { + position: relative; + border-right: 1px solid var(--semi-color-border); + + .leftContentWrap { + height: 100%; + } + + .collapseBtn { + position: absolute; + top: 1rem; + right: 0; + z-index: 100; + transform: translate(50%); + } + } + + .rightWrap { + height: 100%; + overflow: auto; + } + } +} diff --git a/packages/client/src/layouts/app-double-column/index.tsx b/packages/client/src/layouts/app-double-column/index.tsx new file mode 100644 index 00000000..39e20300 --- /dev/null +++ b/packages/client/src/layouts/app-double-column/index.tsx @@ -0,0 +1,49 @@ +import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons'; +import { Button, Layout as SemiLayout } from '@douyinfe/semi-ui'; +import cls from 'classnames'; +import { throttle } from 'helpers/throttle'; +import { useDragableWidth } from 'hooks/use-dragable-width'; +import React, { useMemo } from 'react'; +import SplitPane from 'react-split-pane'; + +import { AppRouterHeader } from '../app-router-header'; +import styles from './index.module.scss'; + +const { Sider, Content } = SemiLayout; + +interface IProps { + leftNode: React.ReactNode; + rightNode: React.ReactNode; +} + +export const AppDoubleColumnLayout: React.FC = ({ leftNode, rightNode }) => { + const { minWidth, maxWidth, width, isCollapsed, updateWidth, toggleCollapsed } = useDragableWidth(); + const debounceUpdate = useMemo(() => throttle(updateWidth, 200), [updateWidth]); + + return ( + + + + + +
- +
diff --git a/packages/client/src/layouts/router-header/wiki.tsx b/packages/client/src/layouts/app-router-header/wiki.tsx similarity index 82% rename from packages/client/src/layouts/router-header/wiki.tsx rename to packages/client/src/layouts/app-router-header/wiki.tsx index 431538a8..d0002c1a 100644 --- a/packages/client/src/layouts/router-header/wiki.tsx +++ b/packages/client/src/layouts/app-router-header/wiki.tsx @@ -3,7 +3,7 @@ import { Avatar, Dropdown, Modal, Space, Typography } from '@douyinfe/semi-ui'; import { DataRender } from 'components/data-render'; import { Empty } from 'components/empty'; import { WikiStar } from 'components/wiki/star'; -import { useStarWikis } from 'data/star'; +import { useStarWikisInOrganization } from 'data/star'; import { useWikiDetail } from 'data/wiki'; import Link from 'next/link'; import { useRouter } from 'next/router'; @@ -16,7 +16,12 @@ const { Text } = Typography; const WikiContent = () => { const { query } = useRouter(); - const { data: starWikis, loading, error, refresh: refreshStarWikis } = useStarWikis(); + const { + data: starWikis, + loading, + error, + refresh: refreshStarWikis, + } = useStarWikisInOrganization(query.organizationId); const { data: currentWiki } = useWikiDetail(query.wikiId); return ( @@ -31,8 +36,9 @@ const WikiContent = () => {
{
- +
@@ -84,8 +94,9 @@ const WikiContent = () => {
{
- +
@@ -130,7 +145,10 @@ const WikiContent = () => {
diff --git a/packages/client/src/layouts/app-single-column/index.module.scss b/packages/client/src/layouts/app-single-column/index.module.scss new file mode 100644 index 00000000..b6baeac3 --- /dev/null +++ b/packages/client/src/layouts/app-single-column/index.module.scss @@ -0,0 +1,11 @@ +.wrap { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--semi-color-nav-bg); + + .contentWrap { + flex: 1; + overflow: auto; + } +} diff --git a/packages/client/src/layouts/app-single-column/index.tsx b/packages/client/src/layouts/app-single-column/index.tsx new file mode 100644 index 00000000..a36d37d3 --- /dev/null +++ b/packages/client/src/layouts/app-single-column/index.tsx @@ -0,0 +1,25 @@ +import { Layout as SemiLayout } from '@douyinfe/semi-ui'; +import React from 'react'; + +import { AppRouterHeader } from '../app-router-header'; +import styles from './index.module.scss'; + +const { Content } = SemiLayout; + +export const AppSingleColumnLayout: React.FC = ({ children }) => { + return ( + + + + +
{children}
+
+
+
+ ); +}; diff --git a/packages/client/src/layouts/router-header/index.tsx b/packages/client/src/layouts/router-header/index.tsx index 2a27aef0..f5efde88 100644 --- a/packages/client/src/layouts/router-header/index.tsx +++ b/packages/client/src/layouts/router-header/index.tsx @@ -1,22 +1,14 @@ -import { IconMenu } from '@douyinfe/semi-icons'; -import { Button, Dropdown, Layout as SemiLayout, Nav, Space, Typography } from '@douyinfe/semi-ui'; -import { LogoImage, LogoText } from 'components/logo'; +import { Button, Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui'; import { Message } from 'components/message'; -import { Search } from 'components/search'; +import { OrganizationPublicSwitcher } from 'components/organization/public-switcher'; import { Theme } from 'components/theme'; import { User } from 'components/user'; -import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator'; +import { useUser } from 'data/user'; import { IsOnMobile } from 'hooks/use-on-mobile'; -import { useToggle } from 'hooks/use-toggle'; -import { useWindowSize } from 'hooks/use-window-size'; import Router, { useRouter } from 'next/router'; -import React from 'react'; - -import { Recent, RecentModal } from './recent'; -import { Wiki, WikiModal } from './wiki'; +import React, { useCallback } from 'react'; const { Header: SemiHeader } = SemiLayout; -const { Text } = Typography; const menus = [ { @@ -29,19 +21,11 @@ const menus = [ }, }, { - itemKey: '/recent', - text: , - }, - { - itemKey: '/wiki', - text: , - }, - { - itemKey: '/star', - text: '星标', + itemKey: '/find', + text: '发现', onClick: () => { Router.push({ - pathname: `/star`, + pathname: `/find`, }); }, }, @@ -54,24 +38,16 @@ const menus = [ }); }, }, - { - itemKey: '/find', - text: '发现', - onClick: () => { - Router.push({ - pathname: `/find`, - }); - }, - }, ]; export const RouterHeader: React.FC = () => { + const { user } = useUser(); const { pathname } = useRouter(); const { isMobile } = IsOnMobile.useHook(); - const { width } = useWindowSize(); - const [dropdownVisible, toggleDropdownVisible] = useToggle(false); - const [recentModalVisible, toggleRecentModalVisible] = useToggle(false); - const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false); + + const gotoApp = useCallback(() => { + Router.push(`/app`); + }, []); return ( @@ -81,45 +57,16 @@ export const RouterHeader: React.FC = () => { style={{ overflow: 'auto' }} header={ - - - - - {menus.slice(0, 1).map((menu) => { - return ( - - {menu.text} - - ); - })} - 最近 - 知识库 - {menus.slice(3).map((menu) => { - return ( - - {menu.text} - - ); - })} - - } - > - + )} @@ -131,17 +78,19 @@ export const RouterHeader: React.FC = () => { style={{ overflow: 'auto' }} header={ - - {width >= 890 && } + } selectedKeys={[pathname || '/']} items={menus} footer={ - - - + {user && ( + + )} + {user && } diff --git a/packages/client/src/pages/admin/index.tsx b/packages/client/src/pages/admin/index.tsx index 93d98512..15b4bac0 100644 --- a/packages/client/src/pages/admin/index.tsx +++ b/packages/client/src/pages/admin/index.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@douyinfe/semi-ui'; +import { Banner, Typography } from '@douyinfe/semi-ui'; import { SystemConfig } from 'components/admin/system-config'; import { Seo } from 'components/seo'; import { useUser } from 'data/user'; @@ -37,6 +37,7 @@ const Page: NextPage = () => { 管理后台
+ ) : ( diff --git a/packages/client/src/pages/app/index.tsx b/packages/client/src/pages/app/index.tsx new file mode 100644 index 00000000..c5100569 --- /dev/null +++ b/packages/client/src/pages/app/index.tsx @@ -0,0 +1,104 @@ +import { Avatar, Button, Table, Typography } from '@douyinfe/semi-ui'; +import { IOrganization } from '@think/domains'; +import { DataRender } from 'components/data-render'; +import { LocaleTime } from 'components/locale-time'; +import { usePeronalOrganization, useUserOrganizations } from 'data/organization'; +import { SingleColumnLayout } from 'layouts/single-column'; +import Link from 'next/link'; +import Router from 'next/router'; +import { useCallback, useEffect } from 'react'; + +const { Title, Paragraph } = Typography; +const { Column } = Table; + +const Page = () => { + const { data: organization } = usePeronalOrganization(); + const { + data: userOrganizations, + loading: userOrganizationsLoading, + error: userOrganizationsError, + } = useUserOrganizations(); + + const gotoCreate = useCallback(() => { + Router.push({ + pathname: '/app/org/create', + }); + }, []); + + useEffect(() => { + if (userOrganizations && userOrganizations.length) return; + if (!organization) return; + + Router.replace({ + pathname: `/app/org/[organizationId]`, + query: { organizationId: organization.id }, + }); + }, [organization, userOrganizations]); + + return ( + +
+
+ + 组织列表 + + +
+ ( + <> + + { + return ( + + + + + + {org.name} + + + + + ); + }} + /> + } + /> +
+ + )} + /> +
+
+ ); +}; + +export default Page; diff --git a/packages/client/src/pages/star/index.module.scss b/packages/client/src/pages/app/org/[organizationId]/index.module.scss similarity index 100% rename from packages/client/src/pages/star/index.module.scss rename to packages/client/src/pages/app/org/[organizationId]/index.module.scss diff --git a/packages/client/src/pages/app/org/[organizationId]/index.tsx b/packages/client/src/pages/app/org/[organizationId]/index.tsx new file mode 100644 index 00000000..2e1739d3 --- /dev/null +++ b/packages/client/src/pages/app/org/[organizationId]/index.tsx @@ -0,0 +1,200 @@ +import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui'; +import { DocumentApiDefinition, IDocument, IOrganization, StarApiDefinition } from '@think/domains'; +import { DataRender } from 'components/data-render'; +import { DocumentActions } from 'components/document/actions'; +import { Empty } from 'components/empty'; +import { LocaleTime } from 'components/locale-time'; +import { Seo } from 'components/seo'; +import { WikiCreator } from 'components/wiki/create'; +import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card'; +import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document'; +import { getStarWikisInOrganization, useStarWikisInOrganization } from 'data/star'; +import { useRouterQuery } from 'hooks/use-router-query'; +import { useToggle } from 'hooks/use-toggle'; +import { AppSingleColumnLayout } from 'layouts/app-single-column'; +import type { NextPage } from 'next'; +import Link from 'next/link'; +import React, { useEffect, useMemo } from 'react'; +import { serverPrefetcher } from 'services/server-prefetcher'; + +import styles from './index.module.scss'; + +const { Title } = Typography; +const { Column } = Table; + +const grid = { + gutter: 16, + xs: 24, + sm: 12, + md: 12, + lg: 8, + xl: 8, +}; + +const RecentDocs = () => { + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data, error, loading, refresh } = useRecentDocuments(organizationId); + + const columns = useMemo( + () => [ + { + return ( + + {document.title} + + ); + }} + />, + , + { + return ( + + + {createUser.name.slice(0, 1)} + + {createUser.name} + + ); + }} + />, + } + />, + ( + + )} + />, + ], + [refresh] + ); + + useEffect(() => { + refresh(); + }, [refresh]); + + return ( + <> + + 最近访问 + + + {columns} + + } + error={error} + normalContent={() => + data && data.length ? ( + + {columns} +
+ ) : ( + + ) + } + /> + + ); +}; + +const Page: NextPage<{ organizationId: IOrganization['id'] }> = ({ organizationId }) => { + const [visible, toggleVisible] = useToggle(false); + const { data: staredWikis, loading, error, refresh } = useStarWikisInOrganization(organizationId); + + return ( + + +
+
+ + 快捷访问 + + <> + + + +
+ ( + ( + + + + )} + /> + )} + error={error} + normalContent={() => ( + ( + + + + )} + emptyContent={} + /> + )} + /> + +
+
+ ); +}; + +Page.getInitialProps = async (ctx) => { + const { organizationId } = ctx.query; + + const props = await serverPrefetcher(ctx, [ + { + url: StarApiDefinition.getStarWikisInOrganization.client(organizationId), + action: (cookie) => getStarWikisInOrganization(organizationId, cookie), + }, + { + url: DocumentApiDefinition.recent.client(organizationId), + action: (cookie) => getRecentVisitedDocuments(organizationId, cookie), + }, + ]); + return { ...props, organizationId } as any; +}; + +export default Page; diff --git a/packages/client/src/pages/app/org/[organizationId]/setting/index.tsx b/packages/client/src/pages/app/org/[organizationId]/setting/index.tsx new file mode 100644 index 00000000..8050caab --- /dev/null +++ b/packages/client/src/pages/app/org/[organizationId]/setting/index.tsx @@ -0,0 +1,51 @@ +import { Typography } from '@douyinfe/semi-ui'; +import { IOrganization } from '@think/domains'; +import { OrganizationSetting } from 'components/organization/setting'; +import { AppSingleColumnLayout } from 'layouts/app-single-column'; +import { NextPage } from 'next'; +import Router, { useRouter } from 'next/router'; +import React, { useCallback } from 'react'; + +interface IProps { + organizationId: IOrganization['id']; +} + +const { Title } = Typography; + +const Page: NextPage = ({ organizationId }) => { + const { query = {} } = useRouter(); + const { tab = 'base' } = query as { + tab?: string; + }; + + const navigate = useCallback( + (tab = 'base') => { + console.log(tab); + Router.push({ + pathname: `/app/org/[organizationId]/setting`, + query: { organizationId, tab }, + }); + }, + [organizationId] + ); + + return ( + +
+
+ + 组织设置 + +
+ +
+
+ ); +}; + +Page.getInitialProps = async (ctx) => { + const { organizationId } = ctx.query; + return { organizationId } as IProps; +}; + +export default Page; diff --git a/packages/client/src/pages/wiki/index.module.scss b/packages/client/src/pages/app/org/[organizationId]/star/index.module.scss similarity index 100% rename from packages/client/src/pages/wiki/index.module.scss rename to packages/client/src/pages/app/org/[organizationId]/star/index.module.scss diff --git a/packages/client/src/pages/star/index.tsx b/packages/client/src/pages/app/org/[organizationId]/star/index.tsx similarity index 72% rename from packages/client/src/pages/star/index.tsx rename to packages/client/src/pages/app/org/[organizationId]/star/index.tsx index 0cfef3e8..a6586cc8 100644 --- a/packages/client/src/pages/star/index.tsx +++ b/packages/client/src/pages/app/org/[organizationId]/star/index.tsx @@ -5,8 +5,14 @@ import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card' import { Empty } from 'components/empty'; import { Seo } from 'components/seo'; import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card'; -import { getStarDocuments, getStarWikis, useStarDocuments, useStarWikis } from 'data/star'; -import { SingleColumnLayout } from 'layouts/single-column'; +import { + getStarDocumentsInOrganization, + getStarWikisInOrganization, + useStarDocumentsInOrganization, + useStarWikisInOrganization, +} from 'data/star'; +import { useRouterQuery } from 'hooks/use-router-query'; +import { AppSingleColumnLayout } from 'layouts/app-single-column'; import type { NextPage } from 'next'; import React from 'react'; import { serverPrefetcher } from 'services/server-prefetcher'; @@ -25,7 +31,8 @@ const grid = { }; const StarDocs = () => { - const { data: docs, loading, error } = useStarDocuments(); + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data: docs, loading, error } = useStarDocumentsInOrganization(organizationId); return ( { }; const StarWikis = () => { - const { data, loading, error } = useStarWikis(); + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data, loading, error } = useStarWikisInOrganization(organizationId); return ( { const Page: NextPage = () => { return ( - +
@@ -111,14 +119,22 @@ const Page: NextPage = () => {
-
+ ); }; Page.getInitialProps = async (ctx) => { + const { organizationId } = ctx.query; + const props = await serverPrefetcher(ctx, [ - { url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) }, - { url: StarApiDefinition.documents.client(), action: (cookie) => getStarDocuments(cookie) }, + { + url: StarApiDefinition.getStarWikisInOrganization.client(organizationId), + action: (cookie) => getStarWikisInOrganization(organizationId, cookie), + }, + { + url: StarApiDefinition.getStarDocumentsInOrganization.client(organizationId), + action: (cookie) => getStarDocumentsInOrganization(organizationId, cookie), + }, ]); return props; }; diff --git a/packages/client/src/pages/wiki/[wikiId]/document/[documentId]/edit/index.tsx b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit/index.tsx similarity index 100% rename from packages/client/src/pages/wiki/[wikiId]/document/[documentId]/edit/index.tsx rename to packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit/index.tsx diff --git a/packages/client/src/pages/wiki/[wikiId]/document/[documentId]/index.tsx b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/index.tsx similarity index 92% rename from packages/client/src/pages/wiki/[wikiId]/document/[documentId]/index.tsx rename to packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/index.tsx index 80453c5a..478e473f 100644 --- a/packages/client/src/pages/wiki/[wikiId]/document/[documentId]/index.tsx +++ b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/index.tsx @@ -3,7 +3,7 @@ import { DocumentReader } from 'components/document/reader'; import { WikiTocs } from 'components/wiki/tocs'; import { getDocumentDetail } from 'data/document'; import { getWikiTocs } from 'data/wiki'; -import { DoubleColumnLayout } from 'layouts/double-column'; +import { AppDoubleColumnLayout } from 'layouts/app-double-column'; import { NextPage } from 'next'; import React from 'react'; import { serverPrefetcher } from 'services/server-prefetcher'; @@ -15,7 +15,7 @@ interface IProps { const Page: NextPage = ({ wikiId, documentId }) => { return ( - } rightNode={} /> diff --git a/packages/client/src/pages/wiki/[wikiId]/index.module.scss b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/index.module.scss similarity index 100% rename from packages/client/src/pages/wiki/[wikiId]/index.module.scss rename to packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/index.module.scss diff --git a/packages/client/src/pages/wiki/[wikiId]/index.tsx b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/index.tsx similarity index 94% rename from packages/client/src/pages/wiki/[wikiId]/index.tsx rename to packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/index.tsx index 61de53ed..623260ee 100644 --- a/packages/client/src/pages/wiki/[wikiId]/index.tsx +++ b/packages/client/src/pages/app/org/[organizationId]/wiki/[wikiId]/index.tsx @@ -4,7 +4,7 @@ import { DataRender } from 'components/data-render'; import { DocumentReader } from 'components/document/reader'; import { WikiTocs } from 'components/wiki/tocs'; import { getWikiDetail, getWikiTocs, useWikiDetail } from 'data/wiki'; -import { DoubleColumnLayout } from 'layouts/double-column'; +import { AppDoubleColumnLayout } from 'layouts/app-double-column'; import { NextPage } from 'next'; import React from 'react'; import { serverPrefetcher } from 'services/server-prefetcher'; @@ -17,7 +17,7 @@ const Page: NextPage = ({ wikiId }) => { const { data: wiki, loading, error } = useWikiDetail(wikiId); return ( - } rightNode={ = ({ wikiId }) => { (tab = 'base') => { return () => { Router.push({ - pathname: `/wiki/${wikiId}/setting`, - query: { tab }, + pathname: `/app/org/[organizationId]/wiki/[wikiId]/setting`, + query: { organizationId: query.organizationId, wikiId, tab }, }); }; }, - [wikiId] + [query, wikiId] ); return ( - } rightNode={
@@ -56,4 +56,5 @@ Page.getInitialProps = async (ctx) => { ]); return { ...res, wikiId } as IProps; }; + export default Page; diff --git a/packages/client/src/pages/app/org/[organizationId]/wiki/index.module.scss b/packages/client/src/pages/app/org/[organizationId]/wiki/index.module.scss new file mode 100644 index 00000000..4708e1b0 --- /dev/null +++ b/packages/client/src/pages/app/org/[organizationId]/wiki/index.module.scss @@ -0,0 +1,12 @@ +.wikiItemWrap { + padding: 12px 16px !important; + margin: 8px 2px; + cursor: pointer; + background-color: var(--semi-color-bg-2); + border: 1px solid var(--semi-color-border) !important; +} + +.titleWrap { + display: flex; + justify-content: space-between; +} diff --git a/packages/client/src/pages/wiki/index.tsx b/packages/client/src/pages/app/org/[organizationId]/wiki/index.tsx similarity index 76% rename from packages/client/src/pages/wiki/index.tsx rename to packages/client/src/pages/app/org/[organizationId]/wiki/index.tsx index 30bca788..a831ab63 100644 --- a/packages/client/src/pages/wiki/index.tsx +++ b/packages/client/src/pages/app/org/[organizationId]/wiki/index.tsx @@ -6,8 +6,9 @@ import { Seo } from 'components/seo'; import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card'; import { WikiCreator } from 'components/wiki-creator'; import { getAllWikis, getJoinWikis, getOwnWikis, useAllWikis, useJoinWikis, useOwnWikis } from 'data/wiki'; +import { useRouterQuery } from 'hooks/use-router-query'; import { CreateWikiIllustration } from 'illustrations/create-wiki'; -import { SingleColumnLayout } from 'layouts/single-column'; +import { AppSingleColumnLayout } from 'layouts/app-single-column'; import type { NextPage } from 'next'; import React from 'react'; import { serverPrefetcher } from 'services/server-prefetcher'; @@ -24,7 +25,8 @@ const grid = { const { Title } = Typography; const Wikis = ({ hook }) => { - const { data, loading, error } = hook(); + const { organizationId } = useRouterQuery<{ organizationId: string }>(); + const { data, loading, error } = hook(organizationId); return ( { const Page: NextPage = () => { return ( - +
@@ -77,15 +79,17 @@ const Page: NextPage = () => { </TabPane> </Tabs> </div> - </SingleColumnLayout> + </AppSingleColumnLayout> ); }; Page.getInitialProps = async (ctx) => { + const { orgId: organizationId } = ctx.query; + const props = await serverPrefetcher(ctx, [ - { url: WikiApiDefinition.getAllWikis.client(), action: (cookie) => getAllWikis(cookie) }, - { url: WikiApiDefinition.getJoinWikis.client(), action: (cookie) => getJoinWikis(cookie) }, - { url: WikiApiDefinition.getOwnWikis.client(), action: (cookie) => getOwnWikis(cookie) }, + { url: WikiApiDefinition.getAllWikis.client(organizationId), action: (cookie) => getAllWikis(cookie) }, + { url: WikiApiDefinition.getJoinWikis.client(organizationId), action: (cookie) => getJoinWikis(cookie) }, + { url: WikiApiDefinition.getOwnWikis.client(organizationId), action: (cookie) => getOwnWikis(cookie) }, ]); return props; }; diff --git a/packages/client/src/pages/app/org/create/index.module.scss b/packages/client/src/pages/app/org/create/index.module.scss new file mode 100644 index 00000000..c58f36b3 --- /dev/null +++ b/packages/client/src/pages/app/org/create/index.module.scss @@ -0,0 +1,9 @@ +.cover { + margin-right: 12px; +} + +@media (max-width: 768px) { + .cover { + width: 100%; + } +} diff --git a/packages/client/src/pages/app/org/create/index.tsx b/packages/client/src/pages/app/org/create/index.tsx new file mode 100644 index 00000000..5bf8ab59 --- /dev/null +++ b/packages/client/src/pages/app/org/create/index.tsx @@ -0,0 +1,119 @@ +import { Avatar, Button, Form, Typography } from '@douyinfe/semi-ui'; +import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; +import { ORGANIZATION_LOGOS } from '@think/constants'; +import { ImageUploader } from 'components/image-uploader'; +import { useCreateOrganization } from 'data/organization'; +import { useToggle } from 'hooks/use-toggle'; +import { SingleColumnLayout } from 'layouts/single-column'; +import Router from 'next/router'; +import { useCallback, useRef, useState } from 'react'; + +import styles from './index.module.scss'; + +const images = [ + { + key: 'placeholers', + title: '图库', + images: ORGANIZATION_LOGOS, + }, +]; + +const { Title } = Typography; + +const Page: React.FC = () => { + const $form = useRef<FormApi>(); + const [changed, toggleChanged] = useToggle(false); + const [currentCover, setCurrentCover] = useState(''); + const { create, loading } = useCreateOrganization(); + + const setCover = useCallback((url) => { + $form.current.setValue('logo', url); + setCurrentCover(url); + }, []); + + const onFormChange = useCallback(() => { + toggleChanged(true); + }, [toggleChanged]); + + const onSubmit = useCallback(() => { + $form.current.validate().then((values) => { + create(values).then((res) => { + Router.push({ + pathname: `/app/org/[organizationId]`, + query: { organizationId: res.id }, + }); + }); + }); + }, [create]); + + return ( + <SingleColumnLayout> + <div className="container"> + <div> + <Title heading={3} style={{ margin: '8px 0' }}> + 新建组织 + +
+
($form.current = formApi)} + onChange={onFormChange} + onSubmit={onSubmit} + > + +
+
+ +
+ + + +
+
+ + + + + +
+ +
+ +
+ + ); +}; + +export default Page; diff --git a/packages/client/src/pages/find/index.tsx b/packages/client/src/pages/find/index.tsx index 73ec3ef9..14d21ccc 100644 --- a/packages/client/src/pages/find/index.tsx +++ b/packages/client/src/pages/find/index.tsx @@ -75,7 +75,7 @@ const Page: NextPage = () => { Page.getInitialProps = async (ctx) => { const props = await serverPrefetcher(ctx, [ - { url: [WikiApiDefinition.getAllWikis.client(), 1], action: (cookie) => getAllPublicWikis(1, cookie) }, + { url: [WikiApiDefinition.getPublicWikis.client(), 1], action: (cookie) => getAllPublicWikis(1, cookie) }, ]); return props; }; diff --git a/packages/client/src/pages/index.module.scss b/packages/client/src/pages/index.module.scss index 4708e1b0..ba021ccd 100644 --- a/packages/client/src/pages/index.module.scss +++ b/packages/client/src/pages/index.module.scss @@ -1,12 +1,26 @@ -.wikiItemWrap { - padding: 12px 16px !important; - margin: 8px 2px; - cursor: pointer; - background-color: var(--semi-color-bg-2); - border: 1px solid var(--semi-color-border) !important; +.wrap { + z-index: 1; + display: flex; + width: 100%; + padding: 10vh 32px 48px; + align-items: center; + flex-direction: row; + + .hero { + max-width: 53%; + min-width: 53%; + margin-left: 48px; + } } -.titleWrap { - display: flex; - justify-content: space-between; +@media (max-width: 768px) { + .wrap { + flex-direction: column; + + .hero { + max-width: 100%; + min-width: 100%; + margin-left: 0; + } + } } diff --git a/packages/client/src/pages/index.tsx b/packages/client/src/pages/index.tsx index 5b809cfe..e894fc07 100644 --- a/packages/client/src/pages/index.tsx +++ b/packages/client/src/pages/index.tsx @@ -1,184 +1,61 @@ -import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui'; -import { DocumentApiDefinition, IDocument, StarApiDefinition } from '@think/domains'; -import { DataRender } from 'components/data-render'; -import { DocumentActions } from 'components/document/actions'; -import { Empty } from 'components/empty'; -import { LocaleTime } from 'components/locale-time'; +import { Button, Typography } from '@douyinfe/semi-ui'; import { Seo } from 'components/seo'; -import { WikiCreator } from 'components/wiki/create'; -import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card'; -import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document'; -import { getStarWikis, useStarWikis } from 'data/star'; -import { useToggle } from 'hooks/use-toggle'; +import { toLogin, useUser } from 'data/user'; +import { TeamWorkIllustration } from 'illustrations/team-work'; import { SingleColumnLayout } from 'layouts/single-column'; import type { NextPage } from 'next'; -import Link from 'next/link'; -import React, { useEffect, useMemo } from 'react'; -import { serverPrefetcher } from 'services/server-prefetcher'; +import Router from 'next/router'; +import React, { useCallback } from 'react'; import styles from './index.module.scss'; -const { Title } = Typography; -const { Column } = Table; - -const grid = { - gutter: 16, - xs: 24, - sm: 12, - md: 12, - lg: 8, - xl: 8, -}; - -const RecentDocs = () => { - const { data, error, loading, refresh } = useRecentDocuments(); - - const columns = useMemo( - () => [ - { - return ( - - {document.title} - - ); - }} - />, - , - { - return ( - - - {createUser.name.slice(0, 1)} - - {createUser.name} - - ); - }} - />, - } - />, - ( - - )} - />, - ], - [refresh] - ); - - useEffect(() => { - refresh(); - }, [refresh]); - - return ( - <> - - 最近访问 - - - {columns} - - } - error={error} - normalContent={() => - data && data.length ? ( - - {columns} -
- ) : ( - - ) - } - /> - - ); -}; +const { Title, Paragraph } = Typography; const Page: NextPage = () => { - const [visible, toggleVisible] = useToggle(false); - const { data: staredWikis, loading, error, refresh } = useStarWikis(); + const { user } = useUser(); + + const start = useCallback(() => { + if (user) { + Router.push(`/app`); + } else { + toLogin(); + } + }, [user]); + + const toGithub = useCallback(() => { + window.open('https://github.com/fantasticit/think'); + }, []); return (
-
- - 快捷访问 - - <> - - - +
+
+
+
+ 云策文档 + + 云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。 + +
+
+ + +
+
+
+
+ +
- ( - ( - - - - )} - /> - )} - error={error} - normalContent={() => ( - ( - - - - )} - emptyContent={} - /> - )} - /> -
); }; -Page.getInitialProps = async (ctx) => { - const props = await serverPrefetcher(ctx, [ - { url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) }, - { url: DocumentApiDefinition.recent.client(), action: (cookie) => getRecentVisitedDocuments(cookie) }, - ]); - return props; -}; - export default Page; diff --git a/packages/client/src/pages/register/index.tsx b/packages/client/src/pages/register/index.tsx index e414e526..37ae2b92 100644 --- a/packages/client/src/pages/register/index.tsx +++ b/packages/client/src/pages/register/index.tsx @@ -2,7 +2,7 @@ import { Button, Col, Form, Layout, Modal, Row, Space, Toast, Typography } from import { Author } from 'components/author'; import { LogoImage, LogoText } from 'components/logo'; import { Seo } from 'components/seo'; -import { useRegister, useVerifyCode } from 'data/user'; +import { useRegister, useSystemPublicConfig, useVerifyCode } from 'data/user'; import { isEmail } from 'helpers/validator'; import { useInterval } from 'hooks/use-interval'; import { useRouterQuery } from 'hooks/use-router-query'; @@ -24,6 +24,7 @@ const Page = () => { const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false); const [countDown, setCountDown] = useState(0); const { register, loading } = useRegister(); + const { data: systemConfig } = useSystemPublicConfig(); const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode(); const onFormChange = useCallback((formState) => { @@ -133,22 +134,29 @@ const Page = () => { ]} /> - - - - - - - - + {systemConfig && systemConfig.enableEmailVerify ? ( + + + + + + + + + ) : null}