mirror of https://github.com/fantasticit/think.git
Merge pull request #113 from fantasticit/feat/admin
refactor: support org
This commit is contained in:
commit
873c5a957f
|
@ -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 { DataRender } from 'components/data-render';
|
||||||
import { useSystemConfig } from 'data/user';
|
import { useSystemConfig } from 'data/user';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
|
@ -27,9 +28,29 @@ export const System = () => {
|
||||||
error={error}
|
error={error}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
<Banner type="warning" description="系统锁定后,除系统管理员外均不可登录,谨慎修改!" closeIcon={null} />
|
|
||||||
<Form labelPosition="left" initValues={data} onChange={onFormChange} onSubmit={onFinish}>
|
<Form labelPosition="left" initValues={data} onChange={onFormChange} onSubmit={onFinish}>
|
||||||
<Form.Switch field="isSystemLocked" label="系统锁定" />
|
<Form.Switch
|
||||||
|
field="isSystemLocked"
|
||||||
|
label={{
|
||||||
|
text: '系统锁定',
|
||||||
|
extra: (
|
||||||
|
<Tooltip content="系统锁定后,除系统管理员外均不可登录,谨慎修改!">
|
||||||
|
<IconHelpCircle style={{ color: '--semi-color-text-1' }} />
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Switch
|
||||||
|
field="enableEmailVerify"
|
||||||
|
label={{
|
||||||
|
text: '邮箱检验',
|
||||||
|
extra: (
|
||||||
|
<Tooltip content="开启邮箱检验后,新注册用户必须通过邮箱验证">
|
||||||
|
<IconHelpCircle style={{ color: '--semi-color-text-1' }} />
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { IconArticle, IconBranch, IconExport, IconHistory, IconMore, IconPlus, IconStar } from '@douyinfe/semi-icons';
|
import { IconArticle, IconBranch, IconExport, IconHistory, IconMore, IconPlus, IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
|
import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
import { ButtonProps } from '@douyinfe/semi-ui/button/Button';
|
import { ButtonProps } from '@douyinfe/semi-ui/button/Button';
|
||||||
import { IDocument } from '@think/domains';
|
import { IDocument, IOrganization, IWiki } from '@think/domains';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { DocumentCreator } from 'components/document/create';
|
import { DocumentCreator } from 'components/document/create';
|
||||||
import { DocumentDeletor } from 'components/document/delete';
|
import { DocumentDeletor } from 'components/document/delete';
|
||||||
|
@ -17,8 +17,9 @@ import React, { useCallback } from 'react';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
organizationId: IOrganization['id'];
|
||||||
documentId: string;
|
wikiId: IWiki['id'];
|
||||||
|
documentId: IDocument['id'];
|
||||||
document?: IDocument;
|
document?: IDocument;
|
||||||
hoverVisible?: boolean;
|
hoverVisible?: boolean;
|
||||||
onStar?: () => void;
|
onStar?: () => void;
|
||||||
|
@ -34,6 +35,7 @@ interface IProps {
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const DocumentActions: React.FC<IProps> = ({
|
export const DocumentActions: React.FC<IProps> = ({
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId,
|
documentId,
|
||||||
hoverVisible,
|
hoverVisible,
|
||||||
|
@ -107,6 +109,7 @@ export const DocumentActions: React.FC<IProps> = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DocumentStar
|
<DocumentStar
|
||||||
|
organizationId={organizationId}
|
||||||
wikiId={wikiId}
|
wikiId={wikiId}
|
||||||
documentId={documentId}
|
documentId={documentId}
|
||||||
render={({ star, toggleStar, text }) => (
|
render={({ star, toggleStar, text }) => (
|
||||||
|
|
|
@ -15,15 +15,18 @@ const { Text } = Typography;
|
||||||
|
|
||||||
export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) => {
|
export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) => {
|
||||||
const gotoEdit = useCallback(() => {
|
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]);
|
}, [document]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.cardWrap}>
|
<div className={styles.cardWrap}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: `/wiki/[wikiId]/document/[documentId]`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
|
||||||
query: { wikiId: document.wikiId, documentId: document.id },
|
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
|
@ -40,7 +43,11 @@ export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) =>
|
||||||
<Tooltip key="edit" content="编辑" position="bottom">
|
<Tooltip key="edit" content="编辑" position="bottom">
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={gotoEdit} />
|
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={gotoEdit} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DocumentStar wikiId={document.wikiId} documentId={document.id} />
|
<DocumentStar
|
||||||
|
organizationId={document.organizationId}
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={document.id}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
.userTable {
|
|
||||||
:global {
|
|
||||||
[class$='-pagination-outer'] {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +1,12 @@
|
||||||
import { IconDelete, IconUserAdd } from '@douyinfe/semi-icons';
|
import { IconUserAdd } from '@douyinfe/semi-icons';
|
||||||
import {
|
import { Avatar, AvatarGroup, Button, Dropdown, Modal, Toast, Tooltip } from '@douyinfe/semi-ui';
|
||||||
Avatar,
|
import { Members } from 'components/members';
|
||||||
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 { useDoumentMembers } from 'data/document';
|
import { useDoumentMembers } from 'data/document';
|
||||||
import { useUser } from 'data/user';
|
import { useUser } from 'data/user';
|
||||||
import { event, JOIN_USER } from 'event';
|
import { event, JOIN_USER } from 'event';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
|
@ -33,116 +14,27 @@ interface IProps {
|
||||||
disabled?: boolean;
|
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 <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const ref = useRef<HTMLInputElement>();
|
|
||||||
const toastedUsersRef = useRef([]);
|
const toastedUsersRef = useRef([]);
|
||||||
const { user: currentUser } = useUser();
|
const { user: currentUser } = useUser();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
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 [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(
|
const content = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Tabs type="line">
|
<div style={{ padding: '24px 0' }}>
|
||||||
<TabPane tab="添加成员" itemKey="add">
|
<Members
|
||||||
<div style={{ marginTop: 16 }}>
|
id={documentId}
|
||||||
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
hook={useDoumentMembers}
|
||||||
<Paragraph style={{ marginTop: 16 }}>
|
descriptions={[
|
||||||
将对方加入文档进行协作,您也可将该链接发送给对方。
|
'权限继承:默认继承知识库成员权限',
|
||||||
<span style={{ verticalAlign: 'middle' }}>
|
'超级管理员:组织超级管理员、知识库超级管理员和文档创建者',
|
||||||
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
]}
|
||||||
</span>
|
/>
|
||||||
</Paragraph>
|
</div>
|
||||||
<Button theme="solid" block style={{ margin: '24px 0' }} disabled={!inviteUser} onClick={handleOk}>
|
|
||||||
添加用户
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="协作成员" itemKey="list">
|
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
loadingContent={<Spin />}
|
|
||||||
normalContent={() => (
|
|
||||||
<Table
|
|
||||||
dataSource={users}
|
|
||||||
className={styles.userTable}
|
|
||||||
size="small"
|
|
||||||
pagination={{ popoverPosition: 'topLeft', popoverZIndex: 1061 }}
|
|
||||||
>
|
|
||||||
<Column title="用户名" dataIndex="user.name" key="name" />
|
|
||||||
<Column
|
|
||||||
title="是否可读"
|
|
||||||
dataIndex="auth.readable"
|
|
||||||
key="readable"
|
|
||||||
render={renderChecked(updateUser, 'readable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="是否可编辑"
|
|
||||||
dataIndex="auth.editable"
|
|
||||||
key="editable"
|
|
||||||
render={renderChecked(updateUser, 'editable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="操作"
|
|
||||||
dataIndex="operate"
|
|
||||||
key="operate"
|
|
||||||
render={(_, document) => (
|
|
||||||
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => handleDelete(document)}>
|
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
|
||||||
</Popconfirm>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
),
|
),
|
||||||
[documentId, error, handleDelete, handleOk, inviteUser, loading, updateUser, users, wikiId]
|
[documentId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const btn = useMemo(
|
const btn = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} />
|
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} />
|
||||||
|
@ -150,12 +42,6 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
[disabled, toggleVisible]
|
[disabled, toggleVisible]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
setTimeout(() => ref.current?.focus(), 100);
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (users) => {
|
const handler = (users) => {
|
||||||
const joinUsers = users
|
const joinUsers = users
|
||||||
|
@ -186,13 +72,6 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
};
|
};
|
||||||
}, [currentUser]);
|
}, [currentUser]);
|
||||||
|
|
||||||
if (error)
|
|
||||||
return (
|
|
||||||
<Tooltip content="邀请他人协作" position="bottom">
|
|
||||||
<Button theme="borderless" type="tertiary" icon={<IconUserAdd />}></Button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AvatarGroup maxCount={5} size="extra-small">
|
<AvatarGroup maxCount={5} size="extra-small">
|
||||||
|
@ -214,7 +93,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
visible={visible}
|
visible={visible}
|
||||||
footer={null}
|
footer={null}
|
||||||
onCancel={toggleVisible}
|
onCancel={toggleVisible}
|
||||||
style={{ maxWidth: '96vw' }}
|
style={{ maxWidth: '96vw', maxHeight: '60vh', overflow: 'auto' }}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -233,6 +112,8 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
width: 412,
|
width: 412,
|
||||||
maxWidth: '96vw',
|
maxWidth: '96vw',
|
||||||
padding: '0 24px',
|
padding: '0 24px',
|
||||||
|
maxHeight: '60vh',
|
||||||
|
overflow: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
import { Checkbox, Modal, TabPane, Tabs } from '@douyinfe/semi-ui';
|
import { Checkbox, Modal, TabPane, Tabs } from '@douyinfe/semi-ui';
|
||||||
|
import { IDocument, IWiki } from '@think/domains';
|
||||||
import { TemplateCardEmpty } from 'components/template/card';
|
import { TemplateCardEmpty } from 'components/template/card';
|
||||||
import { TemplateList } from 'components/template/list';
|
import { TemplateList } from 'components/template/list';
|
||||||
import { useCreateDocument } from 'data/document';
|
import { useCreateDocument } from 'data/document';
|
||||||
import { useOwnTemplates, usePublicTemplates } from 'data/template';
|
import { useOwnTemplates, usePublicTemplates } from 'data/template';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: IWiki['id'];
|
||||||
parentDocumentId?: string;
|
parentDocumentId?: IDocument['id'];
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
toggleVisible: Dispatch<SetStateAction<boolean>>;
|
toggleVisible: Dispatch<SetStateAction<boolean>>;
|
||||||
onCreate?: () => void;
|
onCreate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentCreator: React.FC<IProps> = ({ wikiId, parentDocumentId, visible, toggleVisible, onCreate }) => {
|
export const DocumentCreator: React.FC<IProps> = ({ parentDocumentId, wikiId, visible, toggleVisible, onCreate }) => {
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId?: string }>();
|
||||||
const { loading, create } = useCreateDocument();
|
const { loading, create } = useCreateDocument();
|
||||||
const [createChildDoc, setCreateChildDoc] = useState(false);
|
const [createChildDoc, setCreateChildDoc] = useState(false);
|
||||||
const [templateId, setTemplateId] = useState('');
|
const [templateId, setTemplateId] = useState('');
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
const data = {
|
const data = {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
parentDocumentId: createChildDoc ? parentDocumentId : null,
|
parentDocumentId: createChildDoc ? parentDocumentId : null,
|
||||||
templateId,
|
templateId,
|
||||||
|
@ -32,7 +36,8 @@ export const DocumentCreator: React.FC<IProps> = ({ wikiId, parentDocumentId, vi
|
||||||
onCreate && onCreate();
|
onCreate && onCreate();
|
||||||
setTemplateId('');
|
setTemplateId('');
|
||||||
Router.push({
|
Router.push({
|
||||||
pathname: `/wiki/${wikiId}/document/${res.id}/edit`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit`,
|
||||||
|
query: { organizationId, wikiId, documentId: res.id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconDelete } from '@douyinfe/semi-icons';
|
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 { useDeleteDocument } from 'data/document';
|
||||||
import { useRouterQuery } from 'hooks/use-router-query';
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
|
@ -15,8 +15,11 @@ interface IProps {
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render, onDelete }) => {
|
export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render, onDelete }) => {
|
||||||
const { wikiId: currentWikiId, documentId: currentDocumentId } =
|
const {
|
||||||
useRouterQuery<{ wikiId?: string; documentId?: string }>();
|
organizationId,
|
||||||
|
wikiId: currentWikiId,
|
||||||
|
documentId: currentDocumentId,
|
||||||
|
} = useRouterQuery<{ organizationId: string; wikiId?: string; documentId?: string }>();
|
||||||
const { deleteDocument: api, loading } = useDeleteDocument(documentId);
|
const { deleteDocument: api, loading } = useDeleteDocument(documentId);
|
||||||
|
|
||||||
const deleteAction = useCallback(() => {
|
const deleteAction = useCallback(() => {
|
||||||
|
@ -26,14 +29,15 @@ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Router.push({
|
Router.push({
|
||||||
pathname: `/wiki/${wikiId}`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
|
query: { organizationId, wikiId },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
navigate();
|
navigate();
|
||||||
onDelete && onDelete();
|
onDelete && onDelete();
|
||||||
});
|
});
|
||||||
}, [wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]);
|
}, [organizationId, wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]);
|
||||||
|
|
||||||
const content = useMemo(
|
const content = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import { IAuthority, ILoginUser } from '@think/domains';
|
import { IAuthority, ILoginUser } from '@think/domains';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { useDoumentMembers } from 'data/document';
|
|
||||||
import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event';
|
import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event';
|
||||||
import { useMount } from 'hooks/use-mount';
|
import { useMount } from 'hooks/use-mount';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import Router from 'next/router';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
|
||||||
import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor';
|
import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor';
|
||||||
import { findMentions } from 'tiptap/prose-utils';
|
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import { DocumentUserSetting } from './users';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
user: ILoginUser;
|
user: ILoginUser;
|
||||||
|
@ -19,12 +14,8 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => {
|
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => {
|
||||||
const $hasShowUserSettingModal = useRef(false);
|
|
||||||
const $editor = useRef<ICollaborationRefProps>();
|
const $editor = useRef<ICollaborationRefProps>();
|
||||||
const mounted = useMount();
|
const mounted = useMount();
|
||||||
const { users, addUser, updateUser } = useDoumentMembers(documentId);
|
|
||||||
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
|
|
||||||
const [mentionUsers, setMentionUsers] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (data) => {
|
const handler = (data) => {
|
||||||
|
@ -39,56 +30,6 @@ export const Editor: React.FC<IProps> = ({ 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 (
|
return (
|
||||||
<div className={cls(styles.editorWrap)}>
|
<div className={cls(styles.editorWrap)}>
|
||||||
{mounted && (
|
{mounted && (
|
||||||
|
@ -103,14 +44,6 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
||||||
onAwarenessUpdate={triggerJoinUser}
|
onAwarenessUpdate={triggerJoinUser}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<DocumentUserSetting
|
|
||||||
visible={mentionUsersSettingVisible}
|
|
||||||
toggleVisible={toggleMentionUsersSettingVisible}
|
|
||||||
mentionUsers={mentionUsers}
|
|
||||||
users={users}
|
|
||||||
addUser={addUser}
|
|
||||||
updateUser={updateUser}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,11 +38,12 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
|
|
||||||
const goback = useCallback(() => {
|
const goback = useCallback(() => {
|
||||||
Router.push({
|
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(() => {
|
}).then(() => {
|
||||||
triggerRefreshTocs();
|
triggerRefreshTocs();
|
||||||
});
|
});
|
||||||
}, [document, documentId]);
|
}, [document]);
|
||||||
|
|
||||||
const actions = useMemo(
|
const actions = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -50,8 +51,17 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
{document && authority.readable && (
|
{document && authority.readable && (
|
||||||
<DocumentCollaboration key={documentId} wikiId={document.wikiId} documentId={documentId} />
|
<DocumentCollaboration key={documentId} wikiId={document.wikiId} documentId={documentId} />
|
||||||
)}
|
)}
|
||||||
{document && <DocumentStar key="star" wikiId={document.wikiId} documentId={documentId} />}
|
{document && (
|
||||||
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
|
<DocumentStar
|
||||||
|
key="star"
|
||||||
|
organizationId={document.organizationId}
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={documentId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{document && (
|
||||||
|
<DocumentActions organizationId={document.organizationId} wikiId={document.wikiId} documentId={documentId} />
|
||||||
|
)}
|
||||||
<DocumentVersion documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
<DocumentVersion documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
|
|
|
@ -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<unknown>;
|
|
||||||
updateUser: (auth: DocAuth) => Promise<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DocumentUserSetting: React.FC<IProps> = ({
|
|
||||||
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 (
|
|
||||||
<Modal
|
|
||||||
title={'权限操作'}
|
|
||||||
visible={visible}
|
|
||||||
onCancel={() => toggleVisible(false)}
|
|
||||||
maskClosable={false}
|
|
||||||
style={{ maxWidth: '96vw' }}
|
|
||||||
footer={null}
|
|
||||||
>
|
|
||||||
<Text>您在该文档中 @ 了以下用户,请为他们操作权限,否则他们无法阅读该文档。</Text>
|
|
||||||
<Table style={{ margin: '24px 0' }} dataSource={renderUsers} size="small" pagination>
|
|
||||||
<Column title="用户名" dataIndex="userName" key="name" />
|
|
||||||
<Column
|
|
||||||
title="是否可读"
|
|
||||||
dataIndex="readable"
|
|
||||||
key="readable"
|
|
||||||
render={renderChecked(handler, 'readable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="是否可编辑"
|
|
||||||
dataIndex="editable"
|
|
||||||
key="editable"
|
|
||||||
render={renderChecked(handler, 'editable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -56,7 +56,10 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const gotoEdit = useCallback(() => {
|
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]);
|
}, [document]);
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
|
@ -70,11 +73,26 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
documentId={documentId}
|
documentId={documentId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{document && <DocumentStar disabled={!readable} key="star" wikiId={document.wikiId} documentId={documentId} />}
|
{document && (
|
||||||
|
<DocumentStar
|
||||||
|
disabled={!readable}
|
||||||
|
key="star"
|
||||||
|
organizationId={document.organizationId}
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={documentId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Tooltip key="edit" content="编辑" position="bottom">
|
<Tooltip key="edit" content="编辑" position="bottom">
|
||||||
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} document={document} />}
|
{document && (
|
||||||
|
<DocumentActions
|
||||||
|
organizationId={document.organizationId}
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={documentId}
|
||||||
|
document={document}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DocumentVersion documentId={documentId} />
|
<DocumentVersion documentId={documentId} />
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { IconStar } from '@douyinfe/semi-icons';
|
import { IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||||
import { IDocument, IWiki } from '@think/domains';
|
import { IDocument, IOrganization, IWiki } from '@think/domains';
|
||||||
import { useDocumentStarToggle } from 'data/star';
|
import { useDocumentStarToggle } from 'data/star';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
import VisibilitySensor from 'react-visibility-sensor';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
organizationId: IOrganization['id'];
|
||||||
wikiId: IWiki['id'];
|
wikiId: IWiki['id'];
|
||||||
documentId: IDocument['id'];
|
documentId: IDocument['id'];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -18,9 +19,9 @@ interface IProps {
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentStar: React.FC<IProps> = ({ wikiId, documentId, disabled = false, render }) => {
|
export const DocumentStar: React.FC<IProps> = ({ organizationId, wikiId, documentId, disabled = false, render }) => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { data, toggle: toggleStar } = useDocumentStarToggle(wikiId, documentId, { enabled: visible });
|
const { data, toggle: toggleStar } = useDocumentStarToggle(organizationId, wikiId, documentId, { enabled: visible });
|
||||||
const text = data ? '取消收藏' : '收藏文档';
|
const text = data ? '取消收藏' : '收藏文档';
|
||||||
|
|
||||||
const onViewportChange = useCallback(
|
const onViewportChange = useCallback(
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
width?: number;
|
||||||
|
mobileHeight?: number;
|
||||||
images: Array<{
|
images: Array<{
|
||||||
key: string;
|
key: string;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
|
@ -52,7 +54,7 @@ const UploadTab = ({ selectImage }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children }) => {
|
export const ImageUploader: React.FC<IProps> = ({ width = 360, mobileHeight = 370, images, selectImage, children }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
|
||||||
|
@ -131,7 +133,7 @@ export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children
|
||||||
title={'图片'}
|
title={'图片'}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onCancel={toggleVisible}
|
onCancel={toggleVisible}
|
||||||
height={370}
|
height={mobileHeight}
|
||||||
mask={false}
|
mask={false}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
@ -146,7 +148,7 @@ export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children
|
||||||
position="bottomRight"
|
position="bottomRight"
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onVisibleChange={toggleVisible}
|
onVisibleChange={toggleVisible}
|
||||||
content={<div style={{ width: 360, maxWidth: '96vw' }}>{content}</div>}
|
content={<div style={{ width, maxWidth: '96vw' }}>{content}</div>}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { Banner, Input, Popconfirm, Select, Space } from '@douyinfe/semi-ui';
|
||||||
|
import { AuthEnum, AuthEnumArray } from '@think/domains';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onOk: (arg) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddUser: React.FC<IProps> = ({ onOk, children }) => {
|
||||||
|
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
|
||||||
|
const [userName, setUserName] = useState('');
|
||||||
|
|
||||||
|
const handleOk = useCallback(() => {
|
||||||
|
onOk({ userName, userAuth }).then(() => {
|
||||||
|
setUserAuth(AuthEnum.noAccess);
|
||||||
|
setUserName('');
|
||||||
|
});
|
||||||
|
}, [onOk, userName, userAuth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popconfirm
|
||||||
|
showArrow
|
||||||
|
zIndex={1070}
|
||||||
|
title={'添加成员'}
|
||||||
|
okText={'邀请对方'}
|
||||||
|
style={{ maxWidth: '96vw', width: 380 }}
|
||||||
|
onConfirm={handleOk}
|
||||||
|
okButtonProps={{
|
||||||
|
disabled: !userName,
|
||||||
|
}}
|
||||||
|
content={
|
||||||
|
<div style={{ margin: '16px -68px 0 0' }}>
|
||||||
|
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
|
||||||
|
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
|
||||||
|
) : null}
|
||||||
|
<Space>
|
||||||
|
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }} zIndex={1080}>
|
||||||
|
{AuthEnumArray.map((wikiStatus) => {
|
||||||
|
return (
|
||||||
|
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
|
||||||
|
{wikiStatus.label}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
autofocus
|
||||||
|
placeholder="输入对方用户名"
|
||||||
|
value={userName}
|
||||||
|
onChange={setUserName}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
></Input>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popconfirm>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Banner, Popconfirm, Select, Toast } from '@douyinfe/semi-ui';
|
||||||
|
import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
userWithAuth: { user: IUser; auth: IAuth };
|
||||||
|
updateUser: (arg) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditUser: React.FC<IProps> = ({ userWithAuth, updateUser, children }) => {
|
||||||
|
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
|
||||||
|
|
||||||
|
const handleOk = useCallback(() => {
|
||||||
|
return updateUser({ userName: userWithAuth.user.name, userAuth }).then(() => {
|
||||||
|
Toast.success('操作成功');
|
||||||
|
});
|
||||||
|
}, [updateUser, userAuth, userWithAuth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popconfirm
|
||||||
|
showArrow
|
||||||
|
zIndex={1070}
|
||||||
|
title={`修改用户${userWithAuth && userWithAuth.user.name}权限`}
|
||||||
|
content={
|
||||||
|
<div style={{ margin: '16px -68px 0 0' }}>
|
||||||
|
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
|
||||||
|
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
|
||||||
|
) : null}
|
||||||
|
{}
|
||||||
|
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }} zIndex={1080}>
|
||||||
|
{AuthEnumArray.map((wikiStatus) => {
|
||||||
|
return (
|
||||||
|
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
|
||||||
|
{wikiStatus.label}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onConfirm={handleOk}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popconfirm>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
.wrap {
|
||||||
|
> header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
|
||||||
|
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { AuthEnumTextMap } from '@think/domains';
|
||||||
|
import { DataRender } from 'components/data-render';
|
||||||
|
import { LocaleTime } from 'components/locale-time';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { AddUser } from './add';
|
||||||
|
import { EditUser } from './edit';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
id: string;
|
||||||
|
hook: any;
|
||||||
|
descriptions?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Title, Paragraph } = Typography;
|
||||||
|
const { Column } = Table;
|
||||||
|
|
||||||
|
export const Members: React.FC<IProps> = ({ id, hook, descriptions }) => {
|
||||||
|
const { data, loading, error, page, pageSize, setPage, addUser, updateUser, deleteUser } = hook(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => (
|
||||||
|
<div>
|
||||||
|
<Banner
|
||||||
|
fullMode={false}
|
||||||
|
type="info"
|
||||||
|
bordered
|
||||||
|
icon={null}
|
||||||
|
style={{ margin: '16px 0' }}
|
||||||
|
title={<Title heading={6}>权限说明</Title>}
|
||||||
|
description={
|
||||||
|
<div>
|
||||||
|
{descriptions && descriptions.length ? (
|
||||||
|
descriptions.map((desc) => {
|
||||||
|
return <Paragraph key={desc}>{desc}</Paragraph>;
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Paragraph>超级管理员:管理组织内所有知识库、文档,可删除组织,默认创建者</Paragraph>
|
||||||
|
<Paragraph>管理员:管理组织内所有知识库、文档,不可删除组织</Paragraph>
|
||||||
|
<Paragraph>成员:可访问组织内所有知识库、文档,不可删除组织</Paragraph>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<AddUser onOk={addUser}>
|
||||||
|
<Button theme="solid">添加用户</Button>
|
||||||
|
</AddUser>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
style={{ margin: '16px 0' }}
|
||||||
|
dataSource={data.data}
|
||||||
|
size="small"
|
||||||
|
pagination={{
|
||||||
|
currentPage: page,
|
||||||
|
pageSize,
|
||||||
|
total: data.total,
|
||||||
|
onPageChange: setPage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Column title="用户名" dataIndex="user.name" key="user.name" />
|
||||||
|
<Column
|
||||||
|
title="成员权限"
|
||||||
|
dataIndex="auth.auth"
|
||||||
|
key="auth.auth"
|
||||||
|
align="center"
|
||||||
|
render={(auth) => AuthEnumTextMap[auth]}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="加入时间"
|
||||||
|
dataIndex="auth.createdAt"
|
||||||
|
key="auth.createdAt"
|
||||||
|
align="center"
|
||||||
|
render={(d) => <LocaleTime date={d} />}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
dataIndex="operate"
|
||||||
|
key="operate"
|
||||||
|
align="center"
|
||||||
|
render={(_, data) => (
|
||||||
|
<>
|
||||||
|
<EditUser userWithAuth={data} updateUser={updateUser}>
|
||||||
|
<Button type="tertiary" theme="borderless" icon={<IconEdit />} />
|
||||||
|
</EditUser>
|
||||||
|
<Popconfirm
|
||||||
|
showArrow
|
||||||
|
zIndex={1070}
|
||||||
|
title="确认删除该成员?"
|
||||||
|
onConfirm={() => deleteUser({ userName: data.user.name, userAuth: data.auth.auth })}
|
||||||
|
>
|
||||||
|
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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<IProps> = ({ organizationId, onDelete, children }) => {
|
||||||
|
const { deleteOrganization } = useOrganizationDetail(organizationId);
|
||||||
|
|
||||||
|
const deleteAction = useCallback(() => {
|
||||||
|
Modal.error({
|
||||||
|
title: '确定删除吗?',
|
||||||
|
content: <Text>删除后不可恢复!</Text>,
|
||||||
|
onOk: () => {
|
||||||
|
deleteOrganization().then(() => {
|
||||||
|
onDelete
|
||||||
|
? onDelete()
|
||||||
|
: Router.push({
|
||||||
|
pathname: `/`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
okButtonProps: {
|
||||||
|
type: 'danger',
|
||||||
|
},
|
||||||
|
style: { maxWidth: '96vw' },
|
||||||
|
});
|
||||||
|
}, [deleteOrganization, onDelete]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text type="danger" onClick={deleteAction}>
|
||||||
|
{children || (
|
||||||
|
<Space>
|
||||||
|
<IconDelete />
|
||||||
|
删除
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/app/org/[organizationId]',
|
||||||
|
query: {
|
||||||
|
organizationId: data.id,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ width: 36, height: 36 }}>
|
||||||
|
<Avatar alt="cute cat" size="small" src={data.logo} style={{ margin: 4 }} />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrganizationText = () => {
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { data, loading, error } = useOrganizationDetail(organizationId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/app/org/[organizationId]',
|
||||||
|
query: {
|
||||||
|
organizationId: data.id,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ width: 36, height: 36 }}>
|
||||||
|
<Text>{data.name}</Text>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
.nameWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<span className={styles.nameWrap}>
|
||||||
|
<Space>
|
||||||
|
<LogoImage />
|
||||||
|
{width >= 768 && <LogoText />}
|
||||||
|
</Space>
|
||||||
|
{user && <UserOrganizationsSwitcher />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
.cover {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cover {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<IProps> = ({ organizationId }) => {
|
||||||
|
const $form = useRef<FormApi>();
|
||||||
|
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 (
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => (
|
||||||
|
<Form
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
getFormApi={(formApi) => ($form.current = formApi)}
|
||||||
|
onChange={onFormChange}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<Form.Slot label="Logo">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'end' }}>
|
||||||
|
<div className={styles.cover}>
|
||||||
|
<Avatar
|
||||||
|
shape="square"
|
||||||
|
src={currentCover}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ImageUploader width={260} images={images} selectImage={setCover}>
|
||||||
|
<Button>更换Logo</Button>
|
||||||
|
</ImageUploader>
|
||||||
|
</div>
|
||||||
|
</Form.Slot>
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
label="名称"
|
||||||
|
field="name"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="请输入组织名称"
|
||||||
|
rules={[{ required: true, message: '请输入组织名称' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
label="描述"
|
||||||
|
field="description"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="请输入组织简介"
|
||||||
|
autosize
|
||||||
|
rules={[{ required: true, message: '请输入组织简介' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button htmlType="submit" type="primary" theme="solid" disabled={!changed} loading={loading}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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<IProps> = ({ organizationId, tab, onNavigate }) => {
|
||||||
|
// const { data, update } = useWikiDetail(wikiId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Seo title={TitleMap[tab]} />
|
||||||
|
<Tabs lazyRender type="line" activeKey={tab} onChange={onNavigate}>
|
||||||
|
<TabPane tab={TitleMap['base']} itemKey="base">
|
||||||
|
<Base organizationId={organizationId} />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane tab={TitleMap['members']} itemKey="members">
|
||||||
|
<OrganizationMembers organizationId={organizationId} />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane tab={TitleMap['more']} itemKey="more">
|
||||||
|
<More organizationId={organizationId} />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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<IProps> = ({ organizationId }) => {
|
||||||
|
return <Members id={organizationId} hook={useOrganizationMembers} />;
|
||||||
|
};
|
|
@ -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 (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Banner
|
||||||
|
fullMode={false}
|
||||||
|
type="danger"
|
||||||
|
closeIcon={null}
|
||||||
|
description={<Paragraph>删除组织及内部所有知识库以及文档,不可恢复!</Paragraph>}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
/>
|
||||||
|
<OrganizationDeletor organizationId={organizationId}>
|
||||||
|
<Button type="danger" theme="solid">
|
||||||
|
删除组织
|
||||||
|
</Button>
|
||||||
|
</OrganizationDeletor>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
.nameWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Dropdown
|
||||||
|
trigger="click"
|
||||||
|
render={
|
||||||
|
<DataRender
|
||||||
|
loading={userOrganizationsLoading}
|
||||||
|
error={userOrganizationsError}
|
||||||
|
normalContent={() => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{filterUserOrganizations.length ? (
|
||||||
|
<>
|
||||||
|
{filterUserOrganizations.map((org) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item key={org.id} style={{ padding: 0 }}>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/app/org/[organizationId]',
|
||||||
|
query: {
|
||||||
|
organizationId: org.id,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ display: 'flex', alignItems: 'center', width: '100%', padding: '8px 16px' }}>
|
||||||
|
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
maxWidth: 100,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{org.name}
|
||||||
|
</Paragraph>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Dropdown.Divider />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Dropdown.Item style={{ padding: 0 }}>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ display: 'flex', alignItems: 'center', width: '100%', padding: '8px 16px' }}>
|
||||||
|
<Text>
|
||||||
|
<Space>
|
||||||
|
<Avatar size="extra-small">
|
||||||
|
<IconApps />
|
||||||
|
</Avatar>
|
||||||
|
前往广场
|
||||||
|
</Space>
|
||||||
|
</Text>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</Dropdown.Item>
|
||||||
|
|
||||||
|
<Dropdown.Item style={{ padding: 0 }}>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/app/org/create',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ display: 'flex', alignItems: 'center', width: '100%', padding: '8px 16px' }}>
|
||||||
|
<Text>
|
||||||
|
<Space>
|
||||||
|
<Avatar size="extra-small">
|
||||||
|
<IconAppCenter />
|
||||||
|
</Avatar>
|
||||||
|
新建组织
|
||||||
|
</Space>
|
||||||
|
</Text>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button size="small" icon={<IconSmallTriangleDown />} style={{ marginLeft: 12 }} />
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrganizationSwitcher = () => {
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { data, loading, error } = useOrganizationDetail(organizationId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: '/app/org/[organizationId]',
|
||||||
|
query: {
|
||||||
|
organizationId: data.id,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a className={styles.nameWrap}>
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Avatar size="small" src={data.logo} style={{ marginRight: 8 }} />
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
maxWidth: 100,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
strong
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</Paragraph>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<UserOrganizationsSwitcher />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -7,8 +7,10 @@ import { Empty } from 'components/empty';
|
||||||
import { IconSearch } from 'components/icons';
|
import { IconSearch } from 'components/icons';
|
||||||
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
||||||
import { LocaleTime } from 'components/locale-time';
|
import { LocaleTime } from 'components/locale-time';
|
||||||
|
import { useSearchDocuments } from 'data/document';
|
||||||
import { useAsyncLoading } from 'hooks/use-async-loading';
|
import { useAsyncLoading } from 'hooks/use-async-loading';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
|
@ -19,8 +21,8 @@ import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const searchDocument = (keyword: string): Promise<IDocument[]> => {
|
const searchDocument = (organizationId, keyword: string): Promise<IDocument[]> => {
|
||||||
return HttpClient.get('/document/search', { params: { keyword } });
|
return HttpClient.get(`/document/search/${organizationId}`, { params: { keyword } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
|
@ -32,11 +34,8 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
<div className={styles.itemWrap} key={doc.id}>
|
<div className={styles.itemWrap} key={doc.id}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: '/wiki/[wikiId]/document/[documentId]',
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
|
||||||
query: {
|
query: { organizationId: doc.organizationId, wikiId: doc.wikiId, documentId: doc.id },
|
||||||
wikiId: doc.wikiId,
|
|
||||||
documentId: doc.id,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a className={styles.item}>
|
<a className={styles.item}>
|
||||||
|
@ -54,7 +53,7 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
<DocumentStar organizationId={doc.organizationId} wikiId={doc.wikiId} documentId={doc.id} />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -69,10 +68,12 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Search = () => {
|
export const Search = () => {
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { search: searchApi, loading } = useSearchDocuments(organizationId);
|
||||||
|
|
||||||
const ref = useRef<HTMLInputElement>();
|
const ref = useRef<HTMLInputElement>();
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
|
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
|
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { IconChevronDown, IconPlus } from '@douyinfe/semi-icons';
|
import { IconPlus } from '@douyinfe/semi-icons';
|
||||||
import { Button, Dropdown } from '@douyinfe/semi-ui';
|
import { Button, Dropdown } from '@douyinfe/semi-ui';
|
||||||
import { DocumentCreator } from 'components/document/create';
|
import { DocumentCreator } from 'components/document/create';
|
||||||
import { WikiCreator } from 'components/wiki/create';
|
import { WikiCreator } from 'components/wiki/create';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
|
||||||
import { useRouterQuery } from 'hooks/use-router-query';
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -12,7 +11,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
|
export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
|
||||||
const { wikiId, documentId } = useRouterQuery<{ wikiId?: string; documentId?: string }>();
|
const { wikiId, documentId } = useRouterQuery<{ wikiId?: string; documentId?: string }>();
|
||||||
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
|
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
|
|
@ -15,8 +15,8 @@ export const WikiCard: React.FC<{ wiki: IWikiWithIsMember; shareMode?: boolean }
|
||||||
<div className={styles.cardWrap}>
|
<div className={styles.cardWrap}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: `${shareMode || !wiki.isMember ? '/share' : ''}/wiki/[wikiId]`,
|
pathname: shareMode || !wiki.isMember ? '/share/wiki/[wikiId]' : `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
query: { wikiId: wiki.id },
|
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a target={shareMode ? '_blank' : '_self'}>
|
<a target={shareMode ? '_blank' : '_self'}>
|
||||||
|
@ -34,11 +34,13 @@ export const WikiCard: React.FC<{ wiki: IWikiWithIsMember; shareMode?: boolean }
|
||||||
>
|
>
|
||||||
{wiki.name.charAt(0)}
|
{wiki.name.charAt(0)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className={styles.rightWrap}>
|
{!shareMode && (
|
||||||
<Space>
|
<div className={styles.rightWrap}>
|
||||||
<WikiStar wikiId={wiki.id} />
|
<Space>
|
||||||
</Space>
|
<WikiStar organizationId={wiki.organizationId} wikiId={wiki.id} />
|
||||||
</div>
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Form, Modal } from '@douyinfe/semi-ui';
|
||||||
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
|
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
|
||||||
import type { IWiki } from '@think/domains';
|
import type { IWiki } from '@think/domains';
|
||||||
import { ICreateWiki, useOwnWikis } from 'data/wiki';
|
import { ICreateWiki, useOwnWikis } from 'data/wiki';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||||
|
|
||||||
|
@ -12,18 +13,21 @@ interface IProps {
|
||||||
|
|
||||||
export const WikiCreator: React.FC<IProps> = ({ visible, toggleVisible }) => {
|
export const WikiCreator: React.FC<IProps> = ({ visible, toggleVisible }) => {
|
||||||
const $form = useRef<FormApi>();
|
const $form = useRef<FormApi>();
|
||||||
const { createWiki } = useOwnWikis();
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { createWiki } = useOwnWikis(organizationId);
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
$form.current.validate().then((values) => {
|
$form.current.validate().then((values) => {
|
||||||
createWiki(values as ICreateWiki).then((res) => {
|
createWiki(values as ICreateWiki).then((res) => {
|
||||||
toggleVisible(false);
|
toggleVisible(false);
|
||||||
Router.push({
|
Router.push({
|
||||||
pathname: `/wiki/${(res as unknown as IWiki).id}`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
|
query: { organizationId: res.organizationId, wikiId: res.id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
toggleVisible(false);
|
toggleVisible(false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IconDelete } from '@douyinfe/semi-icons';
|
import { IconDelete } from '@douyinfe/semi-icons';
|
||||||
import { Modal, Space, Typography } from '@douyinfe/semi-ui';
|
import { Modal, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
import { useOwnWikis } from 'data/wiki';
|
import { useOwnWikis } from 'data/wiki';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
@ -11,8 +12,9 @@ interface IProps {
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => {
|
export const WikiDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => {
|
||||||
const { deletWiki } = useOwnWikis();
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { deletWiki } = useOwnWikis(organizationId);
|
||||||
|
|
||||||
const deleteAction = useCallback(() => {
|
const deleteAction = useCallback(() => {
|
||||||
Modal.error({
|
Modal.error({
|
||||||
|
@ -23,7 +25,8 @@ export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children
|
||||||
onDelete
|
onDelete
|
||||||
? onDelete()
|
? onDelete()
|
||||||
: Router.push({
|
: Router.push({
|
||||||
pathname: `/wiki`,
|
pathname: `/app/org/[organizationId]`,
|
||||||
|
query: { organizationId },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -32,7 +35,7 @@ export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children
|
||||||
},
|
},
|
||||||
style: { maxWidth: '96vw' },
|
style: { maxWidth: '96vw' },
|
||||||
});
|
});
|
||||||
}, [wikiId, deletWiki, onDelete]);
|
}, [organizationId, wikiId, deletWiki, onDelete]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text type="danger" onClick={deleteAction}>
|
<Text type="danger" onClick={deleteAction}>
|
||||||
|
|
|
@ -12,7 +12,12 @@ const { Text, Paragraph } = Typography;
|
||||||
export const WikiPinCard: React.FC<{ wiki: IWiki }> = ({ wiki }) => {
|
export const WikiPinCard: React.FC<{ wiki: IWiki }> = ({ wiki }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.cardWrap}>
|
<div className={styles.cardWrap}>
|
||||||
<Link href={{ pathname: `/wiki/[wikiId]`, query: { wikiId: wiki.id } }}>
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
|
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<a>
|
<a>
|
||||||
<header>
|
<header>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -30,7 +35,7 @@ export const WikiPinCard: React.FC<{ wiki: IWiki }> = ({ wiki }) => {
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<Space>
|
<Space>
|
||||||
<WikiStar wikiId={wiki.id} />
|
<WikiStar organizationId={wiki.organizationId} wikiId={wiki.id} />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Button, Toast, Typography, Upload } from '@douyinfe/semi-ui';
|
import { Button, Toast, Typography, Upload } from '@douyinfe/semi-ui';
|
||||||
import type { IWiki } from '@think/domains';
|
import type { IWiki } from '@think/domains';
|
||||||
import { useCreateDocument } from 'data/document';
|
import { useCreateDocument } from 'data/document';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ const { Text } = Typography;
|
||||||
|
|
||||||
export const Import: React.FC<IProps> = ({ wikiId }) => {
|
export const Import: React.FC<IProps> = ({ wikiId }) => {
|
||||||
const { create } = useCreateDocument();
|
const { create } = useCreateDocument();
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
const $upload = useRef<Upload>();
|
const $upload = useRef<Upload>();
|
||||||
const [loading, toggleLoading] = useToggle(false);
|
const [loading, toggleLoading] = useToggle(false);
|
||||||
const [markdownParser, setMarkdownParser] = useState<MarkdownParse>();
|
const [markdownParser, setMarkdownParser] = useState<MarkdownParse>();
|
||||||
|
@ -56,7 +58,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
|
||||||
|
|
||||||
for (const file of fileList) {
|
for (const file of fileList) {
|
||||||
const payload = markdownParser.parse(file.name, file.text);
|
const payload = markdownParser.parse(file.name, file.text);
|
||||||
create({ ...payload, wikiId })
|
create({ ...payload, organizationId, wikiId })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
success += 1;
|
success += 1;
|
||||||
})
|
})
|
||||||
|
@ -77,7 +79,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [markdownParser, fileList, toggleLoading, create, wikiId]);
|
}, [markdownParser, fileList, toggleLoading, create, organizationId, wikiId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const markdownParser = createMarkdownParser();
|
const markdownParser = createMarkdownParser();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Banner, Button, Typography } from '@douyinfe/semi-ui';
|
import { Banner, Button, Typography } from '@douyinfe/semi-ui';
|
||||||
import { WorkspaceDeletor } from 'components/wiki/delete';
|
import { WikiDeletor } from 'components/wiki/delete';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
|
@ -17,11 +17,11 @@ export const More: React.FC<IProps> = ({ wikiId }) => {
|
||||||
description={<Paragraph>删除知识库及内部所有文档,不可恢复!</Paragraph>}
|
description={<Paragraph>删除知识库及内部所有文档,不可恢复!</Paragraph>}
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
/>
|
/>
|
||||||
<WorkspaceDeletor wikiId={wikiId}>
|
<WikiDeletor wikiId={wikiId}>
|
||||||
<Button type="danger" theme="solid">
|
<Button type="danger" theme="solid">
|
||||||
删除知识库
|
删除知识库
|
||||||
</Button>
|
</Button>
|
||||||
</WorkspaceDeletor>
|
</WikiDeletor>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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<IProps> = ({ 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 (
|
|
||||||
<Modal
|
|
||||||
title={'添加成员'}
|
|
||||||
okText={'邀请对方'}
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={() => toggleVisible(false)}
|
|
||||||
maskClosable={false}
|
|
||||||
style={{ maxWidth: '96vw' }}
|
|
||||||
footer={null}
|
|
||||||
>
|
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
{userRole === WikiUserRole.admin ? (
|
|
||||||
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
|
|
||||||
) : null}
|
|
||||||
<Space>
|
|
||||||
<Select value={userRole} onChange={setUserRole} style={{ width: 120 }}>
|
|
||||||
{WIKI_USER_ROLES.map((wikiStatus) => {
|
|
||||||
return (
|
|
||||||
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
|
|
||||||
{wikiStatus.label}
|
|
||||||
</Select.Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
<Input
|
|
||||||
autofocus
|
|
||||||
placeholder="输入对方用户名"
|
|
||||||
value={userName}
|
|
||||||
onChange={setUserName}
|
|
||||||
style={{ width: 270 }}
|
|
||||||
></Input>
|
|
||||||
</Space>
|
|
||||||
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk} disabled={!userName}>
|
|
||||||
添加成员
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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<IProps> = ({ visible, toggleVisible, onOk }) => {
|
|
||||||
const [userRole, setUserRole] = useState(WikiUserRole.normal);
|
|
||||||
const handleOk = useCallback(() => {
|
|
||||||
onOk(userRole).then(() => {
|
|
||||||
setUserRole(WikiUserRole.normal);
|
|
||||||
toggleVisible(false);
|
|
||||||
});
|
|
||||||
}, [onOk, userRole, toggleVisible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={'修改角色'}
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={() => toggleVisible(false)}
|
|
||||||
maskClosable={false}
|
|
||||||
style={{ maxWidth: '96vw' }}
|
|
||||||
footer={null}
|
|
||||||
>
|
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
{userRole === WikiUserRole.admin ? (
|
|
||||||
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
|
|
||||||
) : null}
|
|
||||||
<Select value={userRole} onChange={setUserRole} style={{ width: '100%' }}>
|
|
||||||
{WIKI_USER_ROLES.map((wikiStatus) => {
|
|
||||||
return (
|
|
||||||
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
|
|
||||||
{wikiStatus.label}
|
|
||||||
</Select.Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk}>
|
|
||||||
提交修改
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,88 +1,17 @@
|
||||||
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
|
import { Members } from 'components/members';
|
||||||
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 { useWikiMembers } from 'data/wiki';
|
import { useWikiMembers } from 'data/wiki';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import React from 'react';
|
||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
import { AddUser } from './add';
|
|
||||||
import { EditUser } from './edit';
|
|
||||||
import { Placeholder } from './placeholder';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Column } = Table;
|
|
||||||
|
|
||||||
export const Users: React.FC<IProps> = ({ wikiId }) => {
|
export const Users: React.FC<IProps> = ({ 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 (
|
return (
|
||||||
<>
|
<Members
|
||||||
<DataRender
|
id={wikiId}
|
||||||
loading={loading}
|
hook={useWikiMembers}
|
||||||
error={error}
|
descriptions={['权限继承:默认继承组织成员权限', '超级管理员:组织超级管理员和知识库创建者']}
|
||||||
loadingContent={<Placeholder />}
|
/>
|
||||||
normalContent={() => (
|
|
||||||
<div style={{ margin: '24px 0' }}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
||||||
<Button onClick={toggleVisible} theme="solid">
|
|
||||||
添加用户
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Table style={{ margin: '16px 0' }} dataSource={users} size="small" pagination>
|
|
||||||
<Column title="用户名" dataIndex="userName" key="userName" />
|
|
||||||
<Column
|
|
||||||
title="成员角色"
|
|
||||||
dataIndex="userRole"
|
|
||||||
key="userRole"
|
|
||||||
align="center"
|
|
||||||
render={getWikiUserRoleText}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="加入时间"
|
|
||||||
dataIndex="createdAt"
|
|
||||||
key="createdAt"
|
|
||||||
align="center"
|
|
||||||
render={(d) => <LocaleTime date={d} />}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="操作"
|
|
||||||
dataIndex="operate"
|
|
||||||
key="operate"
|
|
||||||
align="center"
|
|
||||||
render={(_, data) => (
|
|
||||||
<>
|
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} />
|
|
||||||
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => deleteUser(data)}>
|
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
|
||||||
</Popconfirm>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
|
|
||||||
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} onOk={handleEdit} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: <Skeleton.Paragraph style={{ width: 50 }} rows={1} />,
|
|
||||||
userRole: <Skeleton.Paragraph style={{ width: 100 }} rows={1} />,
|
|
||||||
createdAt: <Skeleton.Paragraph style={{ width: 120 }} rows={1} />,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Placeholder = () => {
|
|
||||||
return (
|
|
||||||
<Skeleton
|
|
||||||
placeholder={
|
|
||||||
<Table
|
|
||||||
size="small"
|
|
||||||
style={{ margin: '24px 0' }}
|
|
||||||
columns={columns}
|
|
||||||
dataSource={PLACEHOLDER_DATA}
|
|
||||||
pagination={false}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
loading={true}
|
|
||||||
></Skeleton>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { IconStar } from '@douyinfe/semi-icons';
|
import { IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||||
|
import { IOrganization, IWiki } from '@think/domains';
|
||||||
import { useWikiStarToggle } from 'data/star';
|
import { useWikiStarToggle } from 'data/star';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
organizationId: IOrganization['id'];
|
||||||
|
wikiId: IWiki['id'];
|
||||||
render?: (arg: { star: boolean; text: string; toggleStar: () => Promise<void> }) => React.ReactNode;
|
render?: (arg: { star: boolean; text: string; toggleStar: () => Promise<void> }) => React.ReactNode;
|
||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WikiStar: React.FC<IProps> = ({ wikiId, render, onChange }) => {
|
export const WikiStar: React.FC<IProps> = ({ organizationId, wikiId, render, onChange }) => {
|
||||||
const { data, toggle } = useWikiStarToggle(wikiId);
|
const { data, toggle } = useWikiStarToggle(organizationId, wikiId);
|
||||||
const text = data ? '取消收藏' : '收藏知识库';
|
const text = data ? '取消收藏' : '收藏知识库';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons';
|
import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons';
|
||||||
import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { IDocument } from '@think/domains';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { IconOverview, IconSetting } from 'components/icons';
|
import { IconOverview, IconSetting } from 'components/icons';
|
||||||
import { findParents } from 'components/wiki/tocs/utils';
|
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 { useWikiDetail, useWikiTocs } from 'data/wiki';
|
||||||
import { triggerCreateDocument } from 'event';
|
import { triggerCreateDocument } from 'event';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
@ -18,7 +19,7 @@ interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
docAsLink?: string;
|
docAsLink?: string;
|
||||||
getDocLink?: (arg: string) => string;
|
getDocLink?: (arg: IDocument) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
@ -26,18 +27,18 @@ const { Text } = Typography;
|
||||||
export const WikiTocs: React.FC<IProps> = ({
|
export const WikiTocs: React.FC<IProps> = ({
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId = null,
|
documentId = null,
|
||||||
docAsLink = '/wiki/[wikiId]/document/[documentId]',
|
docAsLink = '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]',
|
||||||
getDocLink = (documentId) => `/wiki/${wikiId}/document/${documentId}`,
|
getDocLink = (document) => `/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`,
|
||||||
}) => {
|
}) => {
|
||||||
const { pathname, query } = useRouter();
|
const { pathname, query } = useRouter();
|
||||||
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
|
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
|
||||||
const { data: tocs, loading: tocsLoading, error: tocsError } = useWikiTocs(wikiId);
|
const { data: tocs, loading: tocsLoading, error: tocsError } = useWikiTocs(wikiId);
|
||||||
const { data: starWikis } = useStarWikis();
|
const { data: starWikis } = useStarWikisInOrganization(query.organizationId);
|
||||||
const {
|
const {
|
||||||
data: starDocuments,
|
data: starDocuments,
|
||||||
loading: starDocumentsLoading,
|
loading: starDocumentsLoading,
|
||||||
error: starDocumentsError,
|
error: starDocumentsError,
|
||||||
} = useWikiStarDocuments(wikiId);
|
} = useStarDocumentsInWiki(query.organizationId, wikiId);
|
||||||
const [parentIds, setParentIds] = useState<Array<string>>([]);
|
const [parentIds, setParentIds] = useState<Array<string>>([]);
|
||||||
const otherStarWikis = useMemo(() => (starWikis || []).filter((wiki) => wiki.id !== wikiId), [starWikis, wikiId]);
|
const otherStarWikis = useMemo(() => (starWikis || []).filter((wiki) => wiki.id !== wikiId), [starWikis, wikiId]);
|
||||||
|
|
||||||
|
@ -86,8 +87,8 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
<Dropdown.Item key={wiki.id}>
|
<Dropdown.Item key={wiki.id}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: `/wiki/[wikiId]`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
query: { wikiId: wiki.id },
|
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -192,13 +193,14 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
<div
|
<div
|
||||||
className={cls(
|
className={cls(
|
||||||
styles.linkWrap,
|
styles.linkWrap,
|
||||||
(pathname === '/wiki/[wikiId]' || query.documentId === wiki.homeDocumentId) && styles.isActive
|
(pathname === '/app/org/[organizationId]/wiki/[wikiId]' || query.documentId === wiki.homeDocumentId) &&
|
||||||
|
styles.isActive
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: `/wiki/[wikiId]`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||||
query: { wikiId },
|
query: { organizationId: wiki.organizationId, wikiId },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
|
@ -235,11 +237,16 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
}
|
}
|
||||||
error={wikiError}
|
error={wikiError}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<div className={cls(styles.linkWrap, pathname === '/wiki/[wikiId]/setting' && styles.isActive)}>
|
<div
|
||||||
|
className={cls(
|
||||||
|
styles.linkWrap,
|
||||||
|
pathname === '/app/org/[organizationId]/wiki/[wikiId]/setting' && styles.isActive
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: `/wiki/[wikiId]/setting`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/setting`,
|
||||||
query: { tab: 'base', wikiId },
|
query: { organizationId: wiki.organizationId, wikiId, tab: 'base' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { IconPlus } from '@douyinfe/semi-icons';
|
import { IconPlus } from '@douyinfe/semi-icons';
|
||||||
import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { IDocument } from '@think/domains';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { IconOverview } from 'components/icons';
|
import { IconOverview } from 'components/icons';
|
||||||
import { LogoImage, LogoText } from 'components/logo';
|
import { LogoImage, LogoText } from 'components/logo';
|
||||||
|
@ -17,7 +18,7 @@ interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
docAsLink?: string;
|
docAsLink?: string;
|
||||||
getDocLink?: (arg: string) => string;
|
getDocLink?: (arg: IDocument) => string;
|
||||||
pageTitle: string;
|
pageTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ export const WikiPublicTocs: React.FC<IProps> = ({
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId = null,
|
documentId = null,
|
||||||
docAsLink = '/share/wiki/[wikiId]/document/[documentId]',
|
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 { pathname } = useRouter();
|
||||||
const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId);
|
const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId);
|
||||||
|
|
|
@ -16,6 +16,7 @@ const Actions = ({ node }) => {
|
||||||
<DocumentActions
|
<DocumentActions
|
||||||
key={node.id}
|
key={node.id}
|
||||||
hoverVisible
|
hoverVisible
|
||||||
|
organizationId={node.organizationId}
|
||||||
wikiId={node.wikiId}
|
wikiId={node.wikiId}
|
||||||
documentId={node.id}
|
documentId={node.id}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -83,7 +84,7 @@ export const Tree = ({
|
||||||
const renderLabel = useCallback(
|
const renderLabel = useCallback(
|
||||||
(label, item) => (
|
(label, item) => (
|
||||||
<div className={styles.treeItemWrap} id={`item-${item.id}`}>
|
<div className={styles.treeItemWrap} id={`item-${item.id}`}>
|
||||||
<Link href={docAsLink} as={getDocLink(item.id)}>
|
<Link href={docAsLink} as={getDocLink(item)}>
|
||||||
<a className={styles.left}>
|
<a className={styles.left}>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
ellipsis={{
|
ellipsis={{
|
||||||
|
|
|
@ -6,18 +6,37 @@ import { QueriesOptions, useQuery, UseQueryOptions } from 'react-query';
|
||||||
import { HttpClient } from 'services/http-client';
|
import { HttpClient } from 'services/http-client';
|
||||||
|
|
||||||
type IDocumentWithVisitedAt = IDocument & { visitedAt: string };
|
type IDocumentWithVisitedAt = IDocument & { visitedAt: string };
|
||||||
type ICreateDocument = Partial<Pick<IDocument, 'wikiId' | 'parentDocumentId'>>;
|
type ICreateDocument = Partial<Pick<IDocument, 'organizationId' | 'wikiId' | 'parentDocumentId'>>;
|
||||||
type IDocumentWithAuth = { document: IDocument; authority: IAuthority };
|
type IDocumentWithAuth = { document: IDocument; authority: IAuthority };
|
||||||
type IUpdateDocument = Partial<Pick<IDocument, 'title' | 'content'>>;
|
type IUpdateDocument = Partial<Pick<IDocument, 'title' | 'content'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索组织内文档
|
||||||
|
* @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
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getRecentVisitedDocuments = (cookie = null): Promise<IDocumentWithVisitedAt[]> => {
|
export const getRecentVisitedDocuments = (organizationId, cookie = null): Promise<IDocumentWithVisitedAt[]> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: DocumentApiDefinition.recent.method,
|
method: DocumentApiDefinition.recent.method,
|
||||||
url: DocumentApiDefinition.recent.client(),
|
url: DocumentApiDefinition.recent.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -26,10 +45,10 @@ export const getRecentVisitedDocuments = (cookie = null): Promise<IDocumentWithV
|
||||||
* 获取用户最近访问的文档
|
* 获取用户最近访问的文档
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useRecentDocuments = () => {
|
export const useRecentDocuments = (organizationId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
DocumentApiDefinition.recent.client(),
|
DocumentApiDefinition.recent.client(organizationId),
|
||||||
getRecentVisitedDocuments,
|
() => getRecentVisitedDocuments(organizationId),
|
||||||
{ refetchOnWindowFocus: false, enabled: false }
|
{ refetchOnWindowFocus: false, enabled: false }
|
||||||
);
|
);
|
||||||
return { data, error, loading: isLoading, refresh: refetch };
|
return { data, error, loading: isLoading, refresh: refetch };
|
||||||
|
@ -46,11 +65,20 @@ export type DocAuth = {
|
||||||
* @param cookie
|
* @param cookie
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getDocumentMembers = (documentId, cookie = null): Promise<Array<{ user: IUser; auth: IAuthority }>> => {
|
export const getDocumentMembers = (
|
||||||
|
documentId,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
cookie = null
|
||||||
|
): Promise<Array<{ user: IUser; auth: IAuthority }>> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: DocumentApiDefinition.getMemberById.method,
|
method: DocumentApiDefinition.getMemberById.method,
|
||||||
url: DocumentApiDefinition.getMemberById.client(documentId),
|
url: DocumentApiDefinition.getMemberById.client(documentId),
|
||||||
cookie,
|
cookie,
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,23 +88,20 @@ export const getDocumentMembers = (documentId, cookie = null): Promise<Array<{ u
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{ user: IUser; auth: IAuthority }>>) => {
|
export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{ user: IUser; auth: IAuthority }>>) => {
|
||||||
|
const [pageSize] = useState(12);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
const { data, error, isLoading, refetch } = useQuery(
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
DocumentApiDefinition.getMemberById.client(documentId),
|
[DocumentApiDefinition.getMemberById.client(documentId), page],
|
||||||
() => getDocumentMembers(documentId),
|
() => getDocumentMembers(documentId, page, pageSize),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
const addUser = useCallback(
|
const addUser = useCallback(
|
||||||
async (userName) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: DocumentApiDefinition.addMemberById.method,
|
method: DocumentApiDefinition.addMemberById.method,
|
||||||
url: DocumentApiDefinition.addMemberById.client(documentId),
|
url: DocumentApiDefinition.addMemberById.client(documentId),
|
||||||
data: {
|
data,
|
||||||
documentId,
|
|
||||||
userName,
|
|
||||||
readable: true,
|
|
||||||
editable: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -85,14 +110,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateUser = useCallback(
|
const updateUser = useCallback(
|
||||||
async (docAuth: DocAuth) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: DocumentApiDefinition.updateMemberById.method,
|
method: DocumentApiDefinition.updateMemberById.method,
|
||||||
url: DocumentApiDefinition.updateMemberById.client(documentId),
|
url: DocumentApiDefinition.updateMemberById.client(documentId),
|
||||||
data: {
|
data,
|
||||||
documentId,
|
|
||||||
...docAuth,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -101,14 +123,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteUser = useCallback(
|
const deleteUser = useCallback(
|
||||||
async (docAuth: DocAuth) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: DocumentApiDefinition.deleteMemberById.method,
|
method: DocumentApiDefinition.deleteMemberById.method,
|
||||||
url: DocumentApiDefinition.deleteMemberById.client(documentId),
|
url: DocumentApiDefinition.deleteMemberById.client(documentId),
|
||||||
data: {
|
data,
|
||||||
documentId,
|
|
||||||
...docAuth,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -116,7 +135,7 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
|
||||||
[refetch, documentId]
|
[refetch, documentId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { users: data, loading: isLoading, error, addUser, updateUser, deleteUser };
|
return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { IAuth, IOrganization, IUser, OrganizationApiDefinition } from '@think/domains';
|
||||||
|
import { event, REFRESH_ORGANIZATIONS, triggerRefreshOrganizations } from 'event';
|
||||||
|
import { useAsyncLoading } from 'hooks/use-async-loading';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { HttpClient } from 'services/http-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索组织内文档
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useCreateOrganization = () => {
|
||||||
|
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<IOrganization> => {
|
||||||
|
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<Array<IOrganization>> => {
|
||||||
|
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<IOrganization> => {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -7,25 +7,29 @@ import { HttpClient } from 'services/http-client';
|
||||||
export type IWikiWithIsMember = IWiki & { isMember?: boolean };
|
export type IWikiWithIsMember = IWiki & { isMember?: boolean };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户收藏的知识库
|
* 获取组织内加星的知识库
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getStarWikis = (cookie = null): Promise<IWikiWithIsMember[]> => {
|
export const getStarWikisInOrganization = (organizationId, cookie = null): Promise<IWikiWithIsMember[]> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.wikis.method,
|
method: StarApiDefinition.getStarWikisInOrganization.method,
|
||||||
url: StarApiDefinition.wikis.client(),
|
url: StarApiDefinition.getStarWikisInOrganization.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户收藏的知识库
|
* 获取组织内加星的知识库
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useStarWikis = () => {
|
export const useStarWikisInOrganization = (organizationId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.wikis.client(), getStarWikis, {
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
staleTime: 500,
|
StarApiDefinition.getStarWikisInOrganization.client(organizationId),
|
||||||
});
|
() => getStarWikisInOrganization(organizationId),
|
||||||
|
{
|
||||||
|
staleTime: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
event.on(TOGGLE_STAR_WIKI, refetch);
|
event.on(TOGGLE_STAR_WIKI, refetch);
|
||||||
|
@ -43,12 +47,13 @@ export const useStarWikis = () => {
|
||||||
* @param wikiId
|
* @param wikiId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWikiIsStar = (wikiId, cookie = null): Promise<boolean> => {
|
export const getWikiIsStar = (organizationId, wikiId, cookie = null): Promise<boolean> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.check.method,
|
method: StarApiDefinition.isStared.method,
|
||||||
url: StarApiDefinition.check.client(),
|
url: StarApiDefinition.isStared.client(),
|
||||||
cookie,
|
cookie,
|
||||||
data: {
|
data: {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -59,30 +64,33 @@ export const getWikiIsStar = (wikiId, cookie = null): Promise<boolean> => {
|
||||||
* @param wikiId
|
* @param wikiId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const toggleStarWiki = (wikiId, cookie = null): Promise<boolean> => {
|
export const toggleStarWiki = (organizationId, wikiId, cookie = null): Promise<boolean> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.toggle.method,
|
method: StarApiDefinition.toggleStar.method,
|
||||||
url: StarApiDefinition.toggle.client(),
|
url: StarApiDefinition.toggleStar.client(),
|
||||||
cookie,
|
cookie,
|
||||||
data: {
|
data: {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收藏知识库
|
* 加星或取消
|
||||||
* @param wikiId
|
* @param wikiId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useWikiStarToggle = (wikiId) => {
|
export const useWikiStarToggle = (organizationId, wikiId) => {
|
||||||
const { data, error, refetch } = useQuery([StarApiDefinition.check.client(), wikiId], () => getWikiIsStar(wikiId));
|
const { data, error, refetch } = useQuery([StarApiDefinition.toggleStar.client(), organizationId, wikiId], () =>
|
||||||
|
getWikiIsStar(organizationId, wikiId)
|
||||||
|
);
|
||||||
|
|
||||||
const toggle = useCallback(async () => {
|
const toggle = useCallback(async () => {
|
||||||
await toggleStarWiki(wikiId);
|
await toggleStarWiki(organizationId, wikiId);
|
||||||
refetch();
|
refetch();
|
||||||
triggerToggleStarWiki();
|
triggerToggleStarWiki();
|
||||||
}, [refetch, wikiId]);
|
}, [refetch, organizationId, wikiId]);
|
||||||
|
|
||||||
return { data, error, toggle };
|
return { data, error, toggle };
|
||||||
};
|
};
|
||||||
|
@ -91,10 +99,10 @@ export const useWikiStarToggle = (wikiId) => {
|
||||||
* 获取用户收藏的文档
|
* 获取用户收藏的文档
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getStarDocuments = (cookie = null): Promise<IDocument[]> => {
|
export const getStarDocumentsInOrganization = (organizationId, cookie = null): Promise<IDocument[]> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.documents.method,
|
method: StarApiDefinition.getStarDocumentsInOrganization.method,
|
||||||
url: StarApiDefinition.documents.client(),
|
url: StarApiDefinition.getStarDocumentsInOrganization.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -103,10 +111,14 @@ export const getStarDocuments = (cookie = null): Promise<IDocument[]> => {
|
||||||
* 获取用户收藏的文档
|
* 获取用户收藏的文档
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useStarDocuments = () => {
|
export const useStarDocumentsInOrganization = (organizationId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.documents.client(), getStarDocuments, {
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
staleTime: 500,
|
StarApiDefinition.getStarDocumentsInOrganization.client(organizationId),
|
||||||
});
|
() => getStarDocumentsInOrganization(organizationId),
|
||||||
|
{
|
||||||
|
staleTime: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
event.on(TOGGLE_STAR_DOUCMENT, refetch);
|
event.on(TOGGLE_STAR_DOUCMENT, refetch);
|
||||||
|
|
||||||
|
@ -122,12 +134,13 @@ export const useStarDocuments = () => {
|
||||||
* @param documentId
|
* @param documentId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
|
export const getDocumentIsStar = (organizationId, wikiId, documentId, cookie = null): Promise<boolean> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.check.method,
|
method: StarApiDefinition.isStared.method,
|
||||||
url: StarApiDefinition.check.client(),
|
url: StarApiDefinition.isStared.client(),
|
||||||
cookie,
|
cookie,
|
||||||
data: {
|
data: {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
|
@ -139,12 +152,13 @@ export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise<bo
|
||||||
* @param wikiId
|
* @param wikiId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
|
export const toggleDocumentStar = (organizationId, wikiId, documentId, cookie = null): Promise<boolean> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.toggle.method,
|
method: StarApiDefinition.toggleStar.method,
|
||||||
url: StarApiDefinition.toggle.client(),
|
url: StarApiDefinition.toggleStar.client(),
|
||||||
cookie,
|
cookie,
|
||||||
data: {
|
data: {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
|
@ -156,18 +170,18 @@ export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise<b
|
||||||
* @param documentId
|
* @param documentId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOptions<boolean>) => {
|
export const useDocumentStarToggle = (organizationId, wikiId, documentId, options?: UseQueryOptions<boolean>) => {
|
||||||
const { data, error, refetch } = useQuery(
|
const { data, error, refetch } = useQuery(
|
||||||
[StarApiDefinition.check.client(), wikiId, documentId],
|
[StarApiDefinition.isStared.client(), organizationId, wikiId, documentId],
|
||||||
() => getDocumentIsStar(wikiId, documentId),
|
() => getDocumentIsStar(organizationId, wikiId, documentId),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggle = useCallback(async () => {
|
const toggle = useCallback(async () => {
|
||||||
await toggleDocumentStar(wikiId, documentId);
|
await toggleDocumentStar(organizationId, wikiId, documentId);
|
||||||
refetch();
|
refetch();
|
||||||
triggerToggleStarDocument();
|
triggerToggleStarDocument();
|
||||||
}, [refetch, wikiId, documentId]);
|
}, [refetch, organizationId, wikiId, documentId]);
|
||||||
|
|
||||||
return { data, error, toggle };
|
return { data, error, toggle };
|
||||||
};
|
};
|
||||||
|
@ -176,12 +190,13 @@ export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOpti
|
||||||
* 获取知识库加星的文档
|
* 获取知识库加星的文档
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWikiStarDocuments = (wikiId, cookie = null): Promise<IWikiWithIsMember[]> => {
|
export const getStarDocumentsInWiki = (organizationId, wikiId, cookie = null): Promise<IWikiWithIsMember[]> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: StarApiDefinition.wikiDocuments.method,
|
method: StarApiDefinition.getStarDocumentsInWiki.method,
|
||||||
url: StarApiDefinition.wikiDocuments.client(),
|
url: StarApiDefinition.getStarDocumentsInWiki.client(),
|
||||||
cookie,
|
cookie,
|
||||||
params: {
|
params: {
|
||||||
|
organizationId,
|
||||||
wikiId,
|
wikiId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -191,10 +206,10 @@ export const getWikiStarDocuments = (wikiId, cookie = null): Promise<IWikiWithIs
|
||||||
* 获取知识库加星的文档
|
* 获取知识库加星的文档
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useWikiStarDocuments = (wikiId) => {
|
export const useStarDocumentsInWiki = (organizationId, wikiId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
[StarApiDefinition.wikiDocuments.client(), wikiId],
|
[StarApiDefinition.getStarDocumentsInWiki.client(), organizationId, wikiId],
|
||||||
() => getWikiStarDocuments(wikiId),
|
() => getStarDocumentsInWiki(organizationId, wikiId),
|
||||||
{
|
{
|
||||||
staleTime: 500,
|
staleTime: 500,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Toast } from '@douyinfe/semi-ui';
|
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 { getStorage, setStorage } from 'helpers/storage';
|
||||||
import { useAsyncLoading } from 'hooks/use-async-loading';
|
import { useAsyncLoading } from 'hooks/use-async-loading';
|
||||||
import Router, { useRouter } from 'next/router';
|
import Router, { useRouter } from 'next/router';
|
||||||
|
@ -109,7 +109,7 @@ export const useUser = () => {
|
||||||
refetch();
|
refetch();
|
||||||
setStorage('user', JSON.stringify(user));
|
setStorage('user', JSON.stringify(user));
|
||||||
user.token && setStorage('token,', user.token);
|
user.token && setStorage('token,', user.token);
|
||||||
const next = router.query?.redirect || '/';
|
const next = router.query?.redirect || '/app';
|
||||||
Router.replace(next as string);
|
Router.replace(next as string);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -138,10 +138,17 @@ export const useUser = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const useSystemPublicConfig = () => {
|
||||||
* 获取验证码
|
const { data, error, isLoading, refetch } = useQuery(SystemApiDefinition.getPublicConfig.client(), () =>
|
||||||
* @returns
|
HttpClient.request<ISystemConfig>({
|
||||||
*/
|
method: SystemApiDefinition.getPublicConfig.method,
|
||||||
|
url: SystemApiDefinition.getPublicConfig.client(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data, error, loading: isLoading, refresh: refetch };
|
||||||
|
};
|
||||||
|
|
||||||
export const useSystemConfig = () => {
|
export const useSystemConfig = () => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () =>
|
const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () =>
|
||||||
HttpClient.request<ISystemConfig>({
|
HttpClient.request<ISystemConfig>({
|
||||||
|
|
|
@ -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 { event, REFRESH_TOCS } from 'event';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
@ -6,20 +6,16 @@ import { HttpClient } from 'services/http-client';
|
||||||
|
|
||||||
export type ICreateWiki = Pick<IWiki, 'name' | 'description'>;
|
export type ICreateWiki = Pick<IWiki, 'name' | 'description'>;
|
||||||
export type IUpdateWiki = Partial<IWiki>;
|
export type IUpdateWiki = Partial<IWiki>;
|
||||||
export type IWikiUserOpeateData = {
|
|
||||||
userName: Pick<IUser, 'name'>;
|
|
||||||
userRole: Pick<IWikiUser, 'userRole'>;
|
|
||||||
};
|
|
||||||
export type IWikiWithIsMember = IWiki & { isMember: boolean };
|
export type IWikiWithIsMember = IWiki & { isMember: boolean };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户所有知识库
|
* 获取用户所有知识库
|
||||||
* @returns
|
* @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({
|
return HttpClient.request({
|
||||||
method: WikiApiDefinition.getAllWikis.method,
|
method: WikiApiDefinition.getAllWikis.method,
|
||||||
url: WikiApiDefinition.getAllWikis.client(),
|
url: WikiApiDefinition.getAllWikis.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -28,8 +24,10 @@ export const getAllWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb
|
||||||
* 获取用户所有知识库
|
* 获取用户所有知识库
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useAllWikis = () => {
|
export const useAllWikis = (organizationId) => {
|
||||||
const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(), getAllWikis);
|
const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(organizationId), () =>
|
||||||
|
getAllWikis(organizationId)
|
||||||
|
);
|
||||||
const list = (data && data.data) || [];
|
const list = (data && data.data) || [];
|
||||||
const total = (data && data.total) || 0;
|
const total = (data && data.total) || 0;
|
||||||
return { data: list, total, error, loading: isLoading };
|
return { data: list, total, error, loading: isLoading };
|
||||||
|
@ -39,10 +37,10 @@ export const useAllWikis = () => {
|
||||||
* 获取用户参与的知识库
|
* 获取用户参与的知识库
|
||||||
* @returns
|
* @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({
|
return HttpClient.request({
|
||||||
method: WikiApiDefinition.getJoinWikis.method,
|
method: WikiApiDefinition.getJoinWikis.method,
|
||||||
url: WikiApiDefinition.getJoinWikis.client(),
|
url: WikiApiDefinition.getJoinWikis.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -51,8 +49,10 @@ export const getJoinWikis = (cookie = null): Promise<{ data: IWiki[]; total: num
|
||||||
* 获取用户参与的知识库
|
* 获取用户参与的知识库
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useJoinWikis = () => {
|
export const useJoinWikis = (organizationId) => {
|
||||||
const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(), getJoinWikis);
|
const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(organizationId), () =>
|
||||||
|
getJoinWikis(organizationId)
|
||||||
|
);
|
||||||
const list = (data && data.data) || [];
|
const list = (data && data.data) || [];
|
||||||
const total = (data && data.total) || 0;
|
const total = (data && data.total) || 0;
|
||||||
|
|
||||||
|
@ -63,10 +63,10 @@ export const useJoinWikis = () => {
|
||||||
* 获取用户创建的知识库
|
* 获取用户创建的知识库
|
||||||
* @returns
|
* @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({
|
return HttpClient.request({
|
||||||
method: WikiApiDefinition.getOwnWikis.method,
|
method: WikiApiDefinition.getOwnWikis.method,
|
||||||
url: WikiApiDefinition.getOwnWikis.client(),
|
url: WikiApiDefinition.getOwnWikis.client(organizationId),
|
||||||
cookie,
|
cookie,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -75,24 +75,29 @@ export const getOwnWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb
|
||||||
* 获取用户创建的知识库
|
* 获取用户创建的知识库
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useOwnWikis = () => {
|
export const useOwnWikis = (organizationId) => {
|
||||||
const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(), getOwnWikis);
|
const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(organizationId), () =>
|
||||||
|
getOwnWikis(organizationId)
|
||||||
|
);
|
||||||
|
|
||||||
const createWiki = useCallback(
|
const createWiki = useCallback(
|
||||||
async (data: ICreateWiki) => {
|
async (data: ICreateWiki) => {
|
||||||
const res = await HttpClient.request({
|
const res = await HttpClient.request<IWiki>({
|
||||||
method: WikiApiDefinition.add.method,
|
method: WikiApiDefinition.add.method,
|
||||||
url: WikiApiDefinition.add.client(),
|
url: WikiApiDefinition.add.client(),
|
||||||
data,
|
data: {
|
||||||
|
organizationId,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
[refetch]
|
[organizationId, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除文档
|
* 删除知识库
|
||||||
* @param id
|
* @param id
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
@ -311,11 +316,20 @@ export const useWikiDocuments = (wikiId) => {
|
||||||
* @param cookie
|
* @param cookie
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWikiMembers = (wikiId, cookie = null): Promise<IWikiUser[]> => {
|
export const getWikiMembers = (
|
||||||
|
wikiId,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
cookie = null
|
||||||
|
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
|
||||||
return HttpClient.request({
|
return HttpClient.request({
|
||||||
method: WikiApiDefinition.getMemberById.method,
|
method: WikiApiDefinition.getMemberById.method,
|
||||||
url: WikiApiDefinition.getMemberById.client(wikiId),
|
url: WikiApiDefinition.getMemberById.client(wikiId),
|
||||||
cookie,
|
cookie,
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -325,12 +339,14 @@ export const getWikiMembers = (wikiId, cookie = null): Promise<IWikiUser[]> => {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useWikiMembers = (wikiId) => {
|
export const useWikiMembers = (wikiId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(WikiApiDefinition.getMemberById.client(wikiId), () =>
|
const [pageSize] = useState(12);
|
||||||
getWikiMembers(wikiId)
|
const [page, setPage] = useState(1);
|
||||||
|
const { data, error, isLoading, refetch } = useQuery([WikiApiDefinition.getMemberById.client(wikiId), page], () =>
|
||||||
|
getWikiMembers(wikiId, page, pageSize)
|
||||||
);
|
);
|
||||||
|
|
||||||
const addUser = useCallback(
|
const addUser = useCallback(
|
||||||
async (data: IWikiUserOpeateData) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: WikiApiDefinition.addMemberById.method,
|
method: WikiApiDefinition.addMemberById.method,
|
||||||
url: WikiApiDefinition.addMemberById.client(wikiId),
|
url: WikiApiDefinition.addMemberById.client(wikiId),
|
||||||
|
@ -343,7 +359,7 @@ export const useWikiMembers = (wikiId) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateUser = useCallback(
|
const updateUser = useCallback(
|
||||||
async (data: IWikiUserOpeateData) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: WikiApiDefinition.updateMemberById.method,
|
method: WikiApiDefinition.updateMemberById.method,
|
||||||
url: WikiApiDefinition.updateMemberById.client(wikiId),
|
url: WikiApiDefinition.updateMemberById.client(wikiId),
|
||||||
|
@ -356,7 +372,7 @@ export const useWikiMembers = (wikiId) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteUser = useCallback(
|
const deleteUser = useCallback(
|
||||||
async (data: IWikiUserOpeateData) => {
|
async (data) => {
|
||||||
const ret = await HttpClient.request({
|
const ret = await HttpClient.request({
|
||||||
method: WikiApiDefinition.deleteMemberById.method,
|
method: WikiApiDefinition.deleteMemberById.method,
|
||||||
url: WikiApiDefinition.deleteMemberById.client(wikiId),
|
url: WikiApiDefinition.deleteMemberById.client(wikiId),
|
||||||
|
@ -368,7 +384,7 @@ export const useWikiMembers = (wikiId) => {
|
||||||
[refetch, wikiId]
|
[refetch, wikiId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { users: data, loading: isLoading, error, addUser, updateUser, deleteUser };
|
return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { EventEmitter } from 'helpers/event-emitter';
|
||||||
|
|
||||||
export const event = new EventEmitter();
|
export const event = new EventEmitter();
|
||||||
|
|
||||||
|
export const REFRESH_ORGANIZATIONS = 'REFRESH_ORGANIZATIONS'; // 刷新组织列表
|
||||||
export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录
|
export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录
|
||||||
export const CREATE_DOCUMENT = `CREATE_DOCUMENT`;
|
export const CREATE_DOCUMENT = `CREATE_DOCUMENT`;
|
||||||
export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库
|
export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库
|
||||||
|
@ -60,3 +61,7 @@ export const triggerToggleStarWiki = () => {
|
||||||
export const triggerToggleStarDocument = () => {
|
export const triggerToggleStarDocument = () => {
|
||||||
event.emit(TOGGLE_STAR_DOUCMENT);
|
event.emit(TOGGLE_STAR_DOUCMENT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const triggerRefreshOrganizations = () => {
|
||||||
|
event.emit(REFRESH_ORGANIZATIONS);
|
||||||
|
};
|
||||||
|
|
|
@ -2,5 +2,6 @@ import { Router, useRouter } from 'next/router';
|
||||||
|
|
||||||
export function useRouterQuery<T extends Router['query']>() {
|
export function useRouterQuery<T extends Router['query']>() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return router.query as T;
|
return router.query as T;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,229 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TeamWorkIllustration = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
data-name="Layer 1"
|
||||||
|
width="100%"
|
||||||
|
viewBox="0 0 896.67538 462.2717"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1047.147,681.03273H152.853a1.19069,1.19069,0,0,1,0-2.38137h894.294a1.19069,1.19069,0,0,1,0,2.38137Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#3f3d56"
|
||||||
|
/>
|
||||||
|
<circle cx="280.35323" cy="50.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="50.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="50.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="50.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="280.35323" cy="77.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="77.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="77.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="77.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="280.35323" cy="104.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="104.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="104.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="104.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="280.35323" cy="131.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="131.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="131.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="131.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="280.35323" cy="158.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="158.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="158.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="158.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="280.35323" cy="185.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="254.57993" cy="185.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="228.80662" cy="185.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="203.03332" cy="185.98023" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<path
|
||||||
|
d="M375.19044,581.264v2a12.51087,12.51087,0,0,0,12.5,12.5h90a12.50461,12.50461,0,0,0,12.5-12.5v-2a12.41025,12.41025,0,0,0-2.90039-8,12.85917,12.85917,0,0,0-2.10987-2s-49.48-1.12-49.48-2.5H387.69044A12.51734,12.51734,0,0,0,375.19044,581.264Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ccc"
|
||||||
|
/>
|
||||||
|
<rect x="275.25169" y="376.40327" width="13" height="84" fill="#ccc" />
|
||||||
|
<path
|
||||||
|
d="M476.914,678.81073c0,1.40463-19.69947.5433-44,.5433s-44,.86133-44-.5433,19.69948-12.54331,44-12.54331S476.914,677.4061,476.914,678.81073Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ccc"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M459.041,579.56765a10.7427,10.7427,0,0,0-3.80711-16.02664L427.681,469.65577l-20.9809,10.27567,33.68091,89.055a10.80091,10.80091,0,0,0,18.66,10.58123Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#a0616a"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M433.923,485.33463l-23.48261,6.35682a4.81687,4.81687,0,0,1-6.04231-4.08554l-2.83827-24.08746a13.37737,13.37737,0,0,1,25.8455-6.91525l9.674,22.1558a4.81688,4.81688,0,0,1-3.15629,6.57563Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<polygon points="313.516 450.101 325.776 450.1 331.609 418.304 313.514 418.304 313.516 450.101" fill="#a0616a" />
|
||||||
|
<path
|
||||||
|
d="M462.05094,664.96234l24.1438-.001h.001a15.38605,15.38605,0,0,1,15.38648,15.38623v.5l-39.53052.00146Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M479.40867,651.91435q-.21423,0-.43018-.02051l-16.96655-1.23535a4.49994,4.49994,0,0,1-3.80933-6.0293l22.70655-51.01465a3.4981,3.4981,0,0,0-.19629-2.79882,3.45078,3.45078,0,0,0-2.21118-1.75977c-10.67725-2.791-38.072-10.22266-61.78638-18.918-10.15991-3.72558-16.55884-9.10937-19.0188-16.00195-3.24316-9.08692,1.55469-17.374,1.7605-17.72168l.16089-.27246,22.31469,2.02832,24.19117,2.05762,53.01342,28.42773a20.086,20.086,0,0,1,8.81861,25.78418l-24.44092,54.80762A4.49689,4.49689,0,0,1,479.40867,651.91435Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<circle cx="274.15032" cy="193.81592" r="24.56103" fill="#a0616a" />
|
||||||
|
<polygon points="306.516 449.101 318.776 449.1 324.609 417.304 306.514 417.304 306.516 449.101" fill="#a0616a" />
|
||||||
|
<path
|
||||||
|
d="M455.05094,663.96234l24.1438-.001h.001a15.38605,15.38605,0,0,1,15.38648,15.38623v.5l-39.53052.00146Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M472.40867,654.91435q-.21423,0-.43018-.02051l-16.96655-1.23535a4.49994,4.49994,0,0,1-3.80933-6.0293l22.70655-51.01465a3.4981,3.4981,0,0,0-.19629-2.79882,3.45078,3.45078,0,0,0-2.21118-1.75977c-10.67725-2.791-38.072-10.22266-61.78638-18.918-10.15991-3.72558-16.55884-9.10937-19.0188-16.00195-3.24316-9.08692,1.55469-17.374,1.7605-17.72168l.16089-.27246,22.31469,2.02832,24.19117,2.05762,53.01342,28.42773a20.086,20.086,0,0,1,8.81861,25.78418l-24.44092,54.80762A4.49689,4.49689,0,0,1,472.40867,654.91435Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M433.38743,451.16825l-26-9s-16.322,12.54-8.481,43.64857a77.01216,77.01216,0,0,1-3.40009,48.32025,49.7778,49.7778,0,0,1-2.61889,5.53118s29,35,56,9l-10.5-50.5S454.88743,464.66825,433.38743,451.16825Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M428.862,438.07639c-3.49561-5.2323-6.25434-12.48755-2.40565-17.4659,3.79908-4.91417,11.29215-4.19018,17.11054-6.36466,8.104-3.02867,12.80409-12.5493,11.33825-21.07565s-8.31032-15.59442-16.464-18.48644-17.34839-1.95148-25.33312,1.37887c-9.82931,4.0997-18.26115,12.03028-21.79686,22.07625s-1.6456,22.10808,5.68929,29.82962c7.86381,8.27835,20.20556,10.48455,31.62276,10.35068"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M406.01515,393.44382c-4.40483,3.58587-11.12526,1.99318-15.85359-1.15387s-8.56507-7.62825-13.681-10.09566c-9.01922-4.35-19.92379-1.45825-28.70172,3.36009s-16.55915,11.475-25.83123,15.25617-21.10393,3.96808-28.12485-3.17161a25.732,25.732,0,0,0,37.7101,30.37144c10.15941-6.18838,15.77105-19.1637,27.16593-22.57932,6.3055-1.89008,13.07632-.36778,19.44917,1.28106s13.01783,3.43041,19.44912,2.02674,12.447-7.18312,11.62879-13.71476Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M485.96881,624.87071a167.50643,167.50643,0,0,1,0-50.97535,4.30257,4.30257,0,0,1,4.2371-3.6057h39.87252a4.24657,4.24657,0,0,1,3.35525,1.62624,4.31917,4.31917,0,0,1,.8074,3.70456,102.32847,102.32847,0,0,0-.0003,47.52515,4.31806,4.31806,0,0,1-.8071,3.70456,4.24657,4.24657,0,0,1-3.35525,1.62624H490.20591A4.30258,4.30258,0,0,1,485.96881,624.87071Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M517.80835,594.96627H496.88477a3.00328,3.00328,0,0,1-3-3v-1.17188a3.00328,3.00328,0,0,1,3-3h20.92358a3.00328,3.00328,0,0,1,3,3v1.17188A3.00328,3.00328,0,0,1,517.80835,594.96627Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M517.80835,607.311H496.88477a3.00328,3.00328,0,0,1-3-3v-1.17236a3.00328,3.00328,0,0,1,3-3h20.92358a3.00328,3.00328,0,0,1,3,3V604.311A3.00328,3.00328,0,0,1,517.80835,607.311Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M496.45406,575.06832a10.74266,10.74266,0,0,0-7.69048-14.56723L438.62823,476.477,420.88,491.66868l54.86227,77.816a10.80091,10.80091,0,0,0,20.71183,5.58367Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#a0616a"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M448.5895,490.09915l-21.14972,12.02212a4.81687,4.81687,0,0,1-6.87143-2.44636l-8.76626-22.61447a13.37737,13.37737,0,0,1,23.29816-13.15318l14.9026,19.03623a4.81687,4.81687,0,0,1-1.41335,7.15566Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M673.62956,519.6137h-406a6.5,6.5,0,1,0,0,13h11.5v141.5a6.5,6.5,0,0,0,13,0V545.10375l72.8711,132.07788a6.5,6.5,0,0,0,11.2583-6.5L300.08318,532.6137H648.12956v.08417l-76.1289,137.98376a6.49977,6.49977,0,1,0,11.25781,6.5L648.12956,559.6035V674.1137a6.5,6.5,0,0,0,13,0V536.04107l1.89112-3.42737h10.60888a6.5,6.5,0,0,0,0-13Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#3f3d56"
|
||||||
|
/>
|
||||||
|
<rect x="271.46726" y="296.74955" width="86" height="7" rx="3.5" fill="#ccc" />
|
||||||
|
<path
|
||||||
|
d="M558.62956,522.6137h-89a6.50737,6.50737,0,0,1-6.5-6.5v-49a6.50736,6.50736,0,0,1,6.5-6.5h89a6.50736,6.50736,0,0,1,6.5,6.5v49A6.50737,6.50737,0,0,1,558.62956,522.6137Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ccc"
|
||||||
|
/>
|
||||||
|
<circle cx="362.46726" cy="272.74955" r="6" fill="#fff" />
|
||||||
|
<path
|
||||||
|
d="M626.55028,401.88558v-175.5a7.53,7.53,0,0,1,7.52143-7.52143H772.80028a7.53,7.53,0,0,1,7.52143,7.52143v175.5a7.53,7.53,0,0,1-7.52143,7.52143H634.07171A7.53,7.53,0,0,1,626.55028,401.88558Zm7.52143-181.35a5.85658,5.85658,0,0,0-5.85,5.85v175.5a5.85658,5.85658,0,0,0,5.85,5.85H772.80028a5.85658,5.85658,0,0,0,5.85-5.85v-175.5a5.85658,5.85658,0,0,0-5.85-5.85Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#e6e6e6"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M711.21543,299.46186V263.42617a3.76488,3.76488,0,0,1,3.76071-3.76071h30.537a3.76489,3.76489,0,0,1,3.76072,3.76071v36.03569a3.76489,3.76489,0,0,1-3.76072,3.76071h-30.537A3.76488,3.76488,0,0,1,711.21543,299.46186Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M651.87971,316.17615V280.14046a3.7649,3.7649,0,0,1,3.76072-3.76072h30.537a3.76489,3.76489,0,0,1,3.76071,3.76072v36.03569a3.76488,3.76488,0,0,1-3.76071,3.76071h-30.537A3.76489,3.76489,0,0,1,651.87971,316.17615Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ccc"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M699.51543,367.15472V331.119a3.76489,3.76489,0,0,1,3.76071-3.76072h30.537a3.7649,3.7649,0,0,1,3.76072,3.76072v36.03569a3.76489,3.76489,0,0,1-3.76072,3.76071h-30.537A3.76488,3.76488,0,0,1,699.51543,367.15472Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ff6584"
|
||||||
|
/>
|
||||||
|
<circle cx="780.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="780.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="780.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="780.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="753.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="753.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="753.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="753.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="726.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="726.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="726.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="726.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="699.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="699.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="699.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="699.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="672.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="672.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="672.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="672.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="645.74124" cy="11.82028" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="645.74124" cy="37.59358" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="645.74124" cy="63.36688" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<circle cx="645.74124" cy="89.14018" r="6.46689" fill="#f2f2f2" />
|
||||||
|
<path
|
||||||
|
d="M823.45714,460.11655a11.00279,11.00279,0,0,1,7.76758-14.46387,10.58985,10.58985,0,0,1,1.45117-.23633l24.30274-42.59863-6.13672-10.54248,15.27539-11.78467,11.687,16.23194a11.14851,11.14851,0,0,1-.26025,13.30859L844.17931,452.745a10.50735,10.50735,0,0,1,.31055,1.03467A11.00313,11.00313,0,0,1,835.13,467.26938a10.706,10.706,0,0,1-1.34668.08447A11.036,11.036,0,0,1,823.45714,460.11655Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ffb8b8"
|
||||||
|
/>
|
||||||
|
<polygon points="624.38 450.888 612.12 450.887 606.288 403.599 624.382 403.6 624.38 450.888" fill="#ffb8b8" />
|
||||||
|
<path
|
||||||
|
d="M603.36318,447.38395H627.007a0,0,0,0,1,0,0v14.88687a0,0,0,0,1,0,0H588.47632a0,0,0,0,1,0,0v0A14.88686,14.88686,0,0,1,603.36318,447.38395Z"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<polygon points="725.38 450.888 713.12 450.887 707.288 403.599 725.382 403.6 725.38 450.888" fill="#ffb8b8" />
|
||||||
|
<path
|
||||||
|
d="M704.36318,447.38395H728.007a0,0,0,0,1,0,0v14.88687a0,0,0,0,1,0,0H689.47632a0,0,0,0,1,0,0v0A14.88686,14.88686,0,0,1,704.36318,447.38395Z"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M869.51771,382.79326l-27.52466,10.45937-6.72-18.66664a11.16824,11.16824,0,0,1,5.002-13.49951h0a11.16822,11.16822,0,0,1,14.26248,2.78445Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M777.1007,631.45249l-23.08447-5.26514a4.98641,4.98641,0,0,1-3.812-5.61914c9.2627-58.70947,18.17432-93.09082,41.14014-136.75244.3208-.814,3.90674-17.33789,6.24707-28.28516a4.97165,4.97165,0,0,1,4.21387-5.02392l43.04443-6.62452a5.57031,5.57031,0,0,1,2.02149-.30419l.43017.08154-.00537.42334c-.00537.4375-.44092.50439-.60449.52978l-.57227.08789a6.68453,6.68453,0,0,1,1.77491,1.20655c18.85986,22.68365,28.667,97.03973,41.123,179.18457a4.48423,4.48423,0,0,1-1.30371,3.64746,4.93337,4.93337,0,0,1-3.5376,1.60058l-27.50146.67041a4.98232,4.98232,0,0,1-5.03516-4.21826L833.11535,519.743a3.97443,3.97443,0,0,0-7.49463-1.15576c-14.94434,30.43457-28.88086,56.75489-42.60645,109.26612a4.95254,4.95254,0,0,1-4.79248,3.72607A5.03,5.03,0,0,1,777.1007,631.45249Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M799.50793,456.75327a4.99864,4.99864,0,0,1-2.0835-3.54883c-3.52539-32.90967-1.06982-58.66406,7.50586-78.73486a24.99167,24.99167,0,0,1,16.56689-14.17969l22.24951-1.12305.11524.05567a26.00516,26.00516,0,0,1,14.21924,27.58838c-7.15625,18.86328-13.14844,38.50293-8.57862,54.34619a5.122,5.122,0,0,1-.48388,3.9834,4.8841,4.8841,0,0,1-3.11573,2.30908l-42.37841,10.08105a4.96422,4.96422,0,0,1-4.0166-.77734Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M789.93029,409.52573l-42.67432-33.666a10.51634,10.51634,0,0,1-1.03613.30567A11.02707,11.02707,0,0,1,737.402,374.474a10.91273,10.91273,0,0,1-4.62647-7.7334,11.02294,11.02294,0,0,1,7.209-11.63818,11.00362,11.00362,0,0,1,14.42627,7.8374,10.57293,10.57293,0,0,1,.229,1.45215l42.48145,24.50732,10.57177-6.08642,11.71094,15.332-16.41406,11.69922a10.942,10.942,0,0,1-13.05957-.31836Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#ffb8b8"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M824.61122,402.91955l-24.34868-16.55739,11.44288-16.20685a11.16823,11.16823,0,0,1,13.92862-3.64l0,0a11.16823,11.16823,0,0,1,5.89441,13.2826Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#6c63ff"
|
||||||
|
/>
|
||||||
|
<circle cx="684.65901" cy="107.4314" r="24.56103" fill="#ffb8b8" />
|
||||||
|
<path
|
||||||
|
d="M827.31647,322.63738c-6.31272,4.32051-14.60145,8.76174-21.04891,4.16075-4.23352-3.02109-5.56737-8.61545-3.8506-13.52487,3.08726-8.82856,11.56943-12.71531,19.68052-15.69458,10.54441-3.87305,22.02572-6.95424,32.91112-4.18055s20.5056,13.31292,18.54058,24.37293c-1.58023,8.89423-9.97,16.29492-8.779,25.24958,1.19867,9.01215,11.33379,13.75589,20.28149,15.36658s18.96038,2.12822,25.39074,8.5552c8.2031,8.1988,6.15464,22.85325-1.56414,31.50955s-19.36683,12.58011-30.6538,15.24757c-14.95454,3.53422-31.12906,5.3916-45.33209-.47378s-25.25566-21.65434-21.14126-36.45976c1.73791-6.2538,5.83191-11.5391,9.78854-16.68452s7.9631-10.526,9.47237-16.83887c1.25773-5.26073.32683-11.36869-3.16181-15.31484a4.437,4.437,0,0,1-.5944-5.18643Z"
|
||||||
|
transform="translate(-151.66231 -218.86415)"
|
||||||
|
fill="#2f2e41"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<IProps> = ({ leftNode, rightNode }) => {
|
||||||
|
const { minWidth, maxWidth, width, isCollapsed, updateWidth, toggleCollapsed } = useDragableWidth();
|
||||||
|
const debounceUpdate = useMemo(() => throttle(updateWidth, 200), [updateWidth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SemiLayout className={styles.wrap}>
|
||||||
|
<AppRouterHeader />
|
||||||
|
<SemiLayout className={styles.contentWrap}>
|
||||||
|
<SplitPane minSize={minWidth} maxSize={maxWidth} size={width} onChange={debounceUpdate}>
|
||||||
|
<Sider style={{ width: '100%', height: '100%' }} className={styles.leftWrap}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={isCollapsed ? <IconChevronRight /> : <IconChevronLeft />}
|
||||||
|
className={cls(styles.collapseBtn, isCollapsed && styles.isCollapsed)}
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
opacity: isCollapsed ? 0 : 1,
|
||||||
|
}}
|
||||||
|
className={styles.leftContentWrap}
|
||||||
|
>
|
||||||
|
{leftNode}
|
||||||
|
</div>
|
||||||
|
</Sider>
|
||||||
|
<Content className={styles.rightWrap}>{rightNode}</Content>
|
||||||
|
</SplitPane>
|
||||||
|
</SemiLayout>
|
||||||
|
</SemiLayout>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
.titleWrap {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemsWrap {
|
||||||
|
max-height: 240px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemWrap {
|
||||||
|
border-radius: var(--semi-border-radius-small);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--semi-color-text-0);
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 16px;
|
||||||
|
|
||||||
|
.leftWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 8px;
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--semi-color-primary);
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { IconMenu } from '@douyinfe/semi-icons';
|
||||||
|
import { Button, Dropdown, Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui';
|
||||||
|
import { Message } from 'components/message';
|
||||||
|
import { OrganizationSwitcher } from 'components/organization/switcher';
|
||||||
|
import { Search } from 'components/search';
|
||||||
|
import { Theme } from 'components/theme';
|
||||||
|
import { User } from 'components/user';
|
||||||
|
import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator';
|
||||||
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
|
import Router, { useRouter } from 'next/router';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { Recent, RecentModal } from './recent';
|
||||||
|
import { Wiki, WikiModal } from './wiki';
|
||||||
|
|
||||||
|
const { Header: SemiHeader } = SemiLayout;
|
||||||
|
|
||||||
|
export const AppRouterHeader: React.FC = () => {
|
||||||
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { pathname } = useRouter();
|
||||||
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
|
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
|
||||||
|
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
|
||||||
|
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);
|
||||||
|
|
||||||
|
const menus = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
itemKey: '/app/org/[organizationId]',
|
||||||
|
text: '主页',
|
||||||
|
onClick: () => {
|
||||||
|
Router.push({
|
||||||
|
pathname: `/app/org/${organizationId}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemKey: 'recent',
|
||||||
|
text: <Recent />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemKey: '/app/org/[organizationId]/wiki',
|
||||||
|
text: <Wiki />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemKey: '/app/org/[organizationId]/star',
|
||||||
|
text: '星标',
|
||||||
|
onClick: () => {
|
||||||
|
Router.push({
|
||||||
|
pathname: `/app/org/${organizationId}/star`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemKey: '/app/org/[organizationId]/setting',
|
||||||
|
text: '设置',
|
||||||
|
onClick: () => {
|
||||||
|
Router.push({
|
||||||
|
pathname: `/app/org/${organizationId}/setting`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[organizationId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SemiHeader>
|
||||||
|
{isMobile ? (
|
||||||
|
<Nav
|
||||||
|
mode="horizontal"
|
||||||
|
style={{ overflow: 'auto' }}
|
||||||
|
header={
|
||||||
|
<Space>
|
||||||
|
<OrganizationSwitcher key={organizationId} />
|
||||||
|
<RecentModal visible={recentModalVisible} toggleVisible={toggleRecentModalVisible} />
|
||||||
|
<WikiModal visible={wikiModalVisible} toggleVisible={toggleWikiModalVisible} />
|
||||||
|
<Dropdown
|
||||||
|
trigger="click"
|
||||||
|
position="bottomRight"
|
||||||
|
visible={dropdownVisible}
|
||||||
|
onVisibleChange={toggleDropdownVisible}
|
||||||
|
render={
|
||||||
|
// @ts-ignore
|
||||||
|
<Dropdown.Menu onClick={toggleDropdownVisible}>
|
||||||
|
{menus.slice(0, 1).map((menu) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
|
||||||
|
{menu.text}
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Dropdown.Item onClick={toggleRecentModalVisible}>最近</Dropdown.Item>
|
||||||
|
<Dropdown.Item onClick={toggleWikiModalVisible}>知识库</Dropdown.Item>
|
||||||
|
{menus.slice(3).map((menu) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
|
||||||
|
{menu.text}
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button icon={<IconMenu />} type="tertiary" theme="borderless" onMouseDown={toggleDropdownVisible} />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
footer={
|
||||||
|
<Space>
|
||||||
|
<WikiOrDocumentCreator />
|
||||||
|
<Search />
|
||||||
|
<Message />
|
||||||
|
<Theme />
|
||||||
|
<User />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
></Nav>
|
||||||
|
) : (
|
||||||
|
<Nav
|
||||||
|
mode="horizontal"
|
||||||
|
style={{ overflow: 'auto' }}
|
||||||
|
header={
|
||||||
|
<Space style={{ marginRight: 12 }}>
|
||||||
|
<OrganizationSwitcher key={organizationId} />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
selectedKeys={[pathname || '/']}
|
||||||
|
items={menus}
|
||||||
|
footer={
|
||||||
|
<Space>
|
||||||
|
<WikiOrDocumentCreator />
|
||||||
|
<Search />
|
||||||
|
<Message />
|
||||||
|
<Theme />
|
||||||
|
<User />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
></Nav>
|
||||||
|
)}
|
||||||
|
</SemiHeader>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,6 +8,7 @@ import { LocaleTime } from 'components/locale-time';
|
||||||
import { useRecentDocuments } from 'data/document';
|
import { useRecentDocuments } from 'data/document';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -16,7 +17,8 @@ import { Placeholder } from './placeholder';
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const RecentDocs = ({ visible }) => {
|
export const RecentDocs = ({ visible }) => {
|
||||||
const { data: recentDocs, loading, error, refresh } = useRecentDocuments();
|
const { query } = useRouter();
|
||||||
|
const { data: recentDocs, loading, error, refresh } = useRecentDocuments(query.organizationId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
@ -40,8 +42,9 @@ export const RecentDocs = ({ visible }) => {
|
||||||
<div className={styles.itemWrap} key={doc.id}>
|
<div className={styles.itemWrap} key={doc.id}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: '/wiki/[wikiId]/document/[documentId]',
|
pathname: '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]',
|
||||||
query: {
|
query: {
|
||||||
|
organizationId: doc.organizationId,
|
||||||
wikiId: doc.wikiId,
|
wikiId: doc.wikiId,
|
||||||
documentId: doc.id,
|
documentId: doc.id,
|
||||||
},
|
},
|
||||||
|
@ -62,7 +65,11 @@ export const RecentDocs = ({ visible }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
<DocumentStar
|
||||||
|
organizationId={doc.organizationId}
|
||||||
|
wikiId={doc.wikiId}
|
||||||
|
documentId={doc.id}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
|
@ -3,7 +3,7 @@ import { Avatar, Dropdown, Modal, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
import { WikiStar } from 'components/wiki/star';
|
import { WikiStar } from 'components/wiki/star';
|
||||||
import { useStarWikis } from 'data/star';
|
import { useStarWikisInOrganization } from 'data/star';
|
||||||
import { useWikiDetail } from 'data/wiki';
|
import { useWikiDetail } from 'data/wiki';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
@ -16,7 +16,12 @@ const { Text } = Typography;
|
||||||
|
|
||||||
const WikiContent = () => {
|
const WikiContent = () => {
|
||||||
const { query } = useRouter();
|
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);
|
const { data: currentWiki } = useWikiDetail(query.wikiId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,8 +36,9 @@ const WikiContent = () => {
|
||||||
<div className={styles.itemWrap}>
|
<div className={styles.itemWrap}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: '/wiki/[wikiId]',
|
pathname: '/app/org/[organizationId]/wiki/[wikiId]',
|
||||||
query: {
|
query: {
|
||||||
|
organizationId: currentWiki.organizationId,
|
||||||
wikiId: currentWiki.id,
|
wikiId: currentWiki.id,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -59,7 +65,11 @@ const WikiContent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<WikiStar wikiId={currentWiki.id} onChange={refreshStarWikis} />
|
<WikiStar
|
||||||
|
organizationId={currentWiki.organizationId}
|
||||||
|
wikiId={currentWiki.id}
|
||||||
|
onChange={refreshStarWikis}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -84,8 +94,9 @@ const WikiContent = () => {
|
||||||
<div className={styles.itemWrap} key={wiki.id}>
|
<div className={styles.itemWrap} key={wiki.id}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: '/wiki/[wikiId]',
|
pathname: '/app/org/[organizationId]/wiki/[wikiId]',
|
||||||
query: {
|
query: {
|
||||||
|
organizationId: wiki.organizationId,
|
||||||
wikiId: wiki.id,
|
wikiId: wiki.id,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -112,7 +123,11 @@ const WikiContent = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<WikiStar wikiId={wiki.id} onChange={refreshStarWikis} />
|
<WikiStar
|
||||||
|
organizationId={wiki.organizationId}
|
||||||
|
wikiId={wiki.id}
|
||||||
|
onChange={refreshStarWikis}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -130,7 +145,10 @@ const WikiContent = () => {
|
||||||
<div className={styles.itemWrap}>
|
<div className={styles.itemWrap}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: '/wiki',
|
pathname: '/app/org/[organizationId]/wiki',
|
||||||
|
query: {
|
||||||
|
organizationId: query.organizationId,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a className={styles.item} style={{ padding: '12px 16px' }}>
|
<a className={styles.item} style={{ padding: '12px 16px' }}>
|
|
@ -0,0 +1,11 @@
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--semi-color-nav-bg);
|
||||||
|
|
||||||
|
.contentWrap {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<SemiLayout className={styles.wrap}>
|
||||||
|
<AppRouterHeader />
|
||||||
|
<SemiLayout className={styles.contentWrap}>
|
||||||
|
<Content
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px',
|
||||||
|
backgroundColor: 'var(--semi-color-bg-0)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{children}</div>
|
||||||
|
</Content>
|
||||||
|
</SemiLayout>
|
||||||
|
</SemiLayout>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,22 +1,14 @@
|
||||||
import { IconMenu } from '@douyinfe/semi-icons';
|
import { Button, Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui';
|
||||||
import { Button, Dropdown, Layout as SemiLayout, Nav, Space, Typography } from '@douyinfe/semi-ui';
|
|
||||||
import { LogoImage, LogoText } from 'components/logo';
|
|
||||||
import { Message } from 'components/message';
|
import { Message } from 'components/message';
|
||||||
import { Search } from 'components/search';
|
import { OrganizationPublicSwitcher } from 'components/organization/public-switcher';
|
||||||
import { Theme } from 'components/theme';
|
import { Theme } from 'components/theme';
|
||||||
import { User } from 'components/user';
|
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 { 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 Router, { useRouter } from 'next/router';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { Recent, RecentModal } from './recent';
|
|
||||||
import { Wiki, WikiModal } from './wiki';
|
|
||||||
|
|
||||||
const { Header: SemiHeader } = SemiLayout;
|
const { Header: SemiHeader } = SemiLayout;
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
{
|
{
|
||||||
|
@ -29,19 +21,11 @@ const menus = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
itemKey: '/recent',
|
itemKey: '/find',
|
||||||
text: <Recent />,
|
text: '发现',
|
||||||
},
|
|
||||||
{
|
|
||||||
itemKey: '/wiki',
|
|
||||||
text: <Wiki />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
itemKey: '/star',
|
|
||||||
text: '星标',
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
Router.push({
|
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 = () => {
|
export const RouterHeader: React.FC = () => {
|
||||||
|
const { user } = useUser();
|
||||||
const { pathname } = useRouter();
|
const { pathname } = useRouter();
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const { width } = useWindowSize();
|
|
||||||
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
|
const gotoApp = useCallback(() => {
|
||||||
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
|
Router.push(`/app`);
|
||||||
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SemiHeader>
|
<SemiHeader>
|
||||||
|
@ -81,45 +57,16 @@ export const RouterHeader: React.FC = () => {
|
||||||
style={{ overflow: 'auto' }}
|
style={{ overflow: 'auto' }}
|
||||||
header={
|
header={
|
||||||
<Space>
|
<Space>
|
||||||
<LogoImage />
|
<OrganizationPublicSwitcher />
|
||||||
<RecentModal visible={recentModalVisible} toggleVisible={toggleRecentModalVisible} />
|
|
||||||
<WikiModal visible={wikiModalVisible} toggleVisible={toggleWikiModalVisible} />
|
|
||||||
<Dropdown
|
|
||||||
trigger="click"
|
|
||||||
position="bottomRight"
|
|
||||||
visible={dropdownVisible}
|
|
||||||
onVisibleChange={toggleDropdownVisible}
|
|
||||||
render={
|
|
||||||
// @ts-ignore
|
|
||||||
<Dropdown.Menu onClick={toggleDropdownVisible}>
|
|
||||||
{menus.slice(0, 1).map((menu) => {
|
|
||||||
return (
|
|
||||||
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
|
|
||||||
{menu.text}
|
|
||||||
</Dropdown.Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Dropdown.Item onClick={toggleRecentModalVisible}>最近</Dropdown.Item>
|
|
||||||
<Dropdown.Item onClick={toggleWikiModalVisible}>知识库</Dropdown.Item>
|
|
||||||
{menus.slice(3).map((menu) => {
|
|
||||||
return (
|
|
||||||
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
|
|
||||||
{menu.text}
|
|
||||||
</Dropdown.Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Dropdown.Menu>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button icon={<IconMenu />} type="tertiary" theme="borderless" onMouseDown={toggleDropdownVisible} />
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<WikiOrDocumentCreator />
|
{user && (
|
||||||
<Search />
|
<Button theme="solid" onClick={gotoApp}>
|
||||||
<Message />
|
前往组织空间
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Theme />
|
<Theme />
|
||||||
<User />
|
<User />
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -131,17 +78,19 @@ export const RouterHeader: React.FC = () => {
|
||||||
style={{ overflow: 'auto' }}
|
style={{ overflow: 'auto' }}
|
||||||
header={
|
header={
|
||||||
<Space style={{ marginRight: 12 }}>
|
<Space style={{ marginRight: 12 }}>
|
||||||
<LogoImage />
|
<OrganizationPublicSwitcher />
|
||||||
{width >= 890 && <LogoText />}
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
selectedKeys={[pathname || '/']}
|
selectedKeys={[pathname || '/']}
|
||||||
items={menus}
|
items={menus}
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<WikiOrDocumentCreator />
|
{user && (
|
||||||
<Search />
|
<Button theme="solid" onClick={gotoApp}>
|
||||||
<Message />
|
前往组织空间
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{user && <Message />}
|
||||||
<Theme />
|
<Theme />
|
||||||
<User />
|
<User />
|
||||||
</Space>
|
</Space>
|
||||||
|
|
|
@ -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 { SystemConfig } from 'components/admin/system-config';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { useUser } from 'data/user';
|
import { useUser } from 'data/user';
|
||||||
|
@ -37,6 +37,7 @@ const Page: NextPage = () => {
|
||||||
管理后台
|
管理后台
|
||||||
</Title>
|
</Title>
|
||||||
</div>
|
</div>
|
||||||
|
<Banner type="info" description="该部分是全局的系统管理后台,用于系统配置管理等操作!" />
|
||||||
<SystemConfig tab={tab} onNavigate={navigate} />
|
<SystemConfig tab={tab} onNavigate={navigate} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -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 (
|
||||||
|
<SingleColumnLayout>
|
||||||
|
<div className="container">
|
||||||
|
<div style={{ marginBottom: 24, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
|
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||||
|
组织列表
|
||||||
|
</Title>
|
||||||
|
<Button onClick={gotoCreate}>新建组织</Button>
|
||||||
|
</div>
|
||||||
|
<DataRender
|
||||||
|
loading={userOrganizationsLoading}
|
||||||
|
error={userOrganizationsError}
|
||||||
|
normalContent={() => (
|
||||||
|
<>
|
||||||
|
<Table style={{ margin: '16px 0' }} dataSource={userOrganizations} size="small" pagination={false}>
|
||||||
|
<Column
|
||||||
|
title="名称"
|
||||||
|
dataIndex="name"
|
||||||
|
key="name"
|
||||||
|
width={200}
|
||||||
|
render={(_, org: IOrganization) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: `/app/org/[organizationId]`,
|
||||||
|
query: {
|
||||||
|
organizationId: org.id,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Avatar size="small" src={org.logo} style={{ marginRight: 8 }} />
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
maxWidth: 100,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
strong
|
||||||
|
>
|
||||||
|
{org.name}
|
||||||
|
</Paragraph>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
width={120}
|
||||||
|
title="创建时间"
|
||||||
|
dataIndex="createdAt"
|
||||||
|
key="createdAt"
|
||||||
|
render={(date) => <LocaleTime date={date} />}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SingleColumnLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -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(
|
||||||
|
() => [
|
||||||
|
<Column
|
||||||
|
title="标题"
|
||||||
|
dataIndex="title"
|
||||||
|
key="title"
|
||||||
|
width={200}
|
||||||
|
render={(_, document: IDocument) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
|
||||||
|
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a style={{ color: 'inherit', textDecoration: 'none' }}>{document.title}</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<Column width={100} title="阅读量" dataIndex="views" key="views" />,
|
||||||
|
<Column
|
||||||
|
title="创建者"
|
||||||
|
dataIndex="createUser"
|
||||||
|
key="createUser"
|
||||||
|
width={160}
|
||||||
|
render={(createUser) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Avatar size="extra-extra-small" src={createUser.avatar} style={{ marginRight: 4 }}>
|
||||||
|
{createUser.name.slice(0, 1)}
|
||||||
|
</Avatar>
|
||||||
|
{createUser.name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<Column
|
||||||
|
width={120}
|
||||||
|
title="访问时间"
|
||||||
|
dataIndex="visitedAt"
|
||||||
|
key="visitedAt"
|
||||||
|
render={(date) => <LocaleTime date={date} />}
|
||||||
|
/>,
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
dataIndex="operate"
|
||||||
|
key="operate"
|
||||||
|
width={80}
|
||||||
|
render={(_, document) => (
|
||||||
|
<DocumentActions
|
||||||
|
organizationId={document.organizationId}
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={document.id}
|
||||||
|
onDelete={refresh}
|
||||||
|
showCreateDocument
|
||||||
|
hideDocumentVersion
|
||||||
|
hideDocumentStyle
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
[refresh]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, [refresh]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title heading={3} style={{ margin: '24px 0 0' }}>
|
||||||
|
最近访问
|
||||||
|
</Title>
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
loadingContent={
|
||||||
|
<Table dataSource={[]} loading={true} pagination={false} size="small" style={{ marginTop: 16 }}>
|
||||||
|
{columns}
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
error={error}
|
||||||
|
normalContent={() =>
|
||||||
|
data && data.length ? (
|
||||||
|
<Table dataSource={data} loading={loading} pagination={false} size="small" style={{ marginTop: 16 }}>
|
||||||
|
{columns}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Empty message="最近访问的文档会出现在此处" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page: NextPage<{ organizationId: IOrganization['id'] }> = ({ organizationId }) => {
|
||||||
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
const { data: staredWikis, loading, error, refresh } = useStarWikisInOrganization(organizationId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppSingleColumnLayout>
|
||||||
|
<Seo title="主页" />
|
||||||
|
<div className="container">
|
||||||
|
<div className={styles.titleWrap}>
|
||||||
|
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||||
|
快捷访问
|
||||||
|
</Title>
|
||||||
|
<>
|
||||||
|
<Button onClick={toggleVisible}>创建知识库</Button>
|
||||||
|
<WikiCreator visible={visible} toggleVisible={toggleVisible} />
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
loadingContent={() => (
|
||||||
|
<List
|
||||||
|
grid={grid}
|
||||||
|
dataSource={[1, 2, 3]}
|
||||||
|
renderItem={() => (
|
||||||
|
<List.Item>
|
||||||
|
<WikiPinCardPlaceholder />
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => (
|
||||||
|
<List
|
||||||
|
grid={grid}
|
||||||
|
dataSource={staredWikis}
|
||||||
|
renderItem={(wiki) => (
|
||||||
|
<List.Item>
|
||||||
|
<WikiPinCard wiki={wiki} />
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
emptyContent={<Empty message="收藏的知识库会出现在此处" />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<RecentDocs />
|
||||||
|
</div>
|
||||||
|
</AppSingleColumnLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
|
@ -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<IProps> = ({ 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 (
|
||||||
|
<AppSingleColumnLayout>
|
||||||
|
<div className="container">
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||||
|
组织设置
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<OrganizationSetting organizationId={organizationId} tab={tab} onNavigate={navigate} />
|
||||||
|
</div>
|
||||||
|
</AppSingleColumnLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Page.getInitialProps = async (ctx) => {
|
||||||
|
const { organizationId } = ctx.query;
|
||||||
|
return { organizationId } as IProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -5,8 +5,14 @@ import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card'
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
||||||
import { getStarDocuments, getStarWikis, useStarDocuments, useStarWikis } from 'data/star';
|
import {
|
||||||
import { SingleColumnLayout } from 'layouts/single-column';
|
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 type { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
import { serverPrefetcher } from 'services/server-prefetcher';
|
||||||
|
@ -25,7 +31,8 @@ const grid = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarDocs = () => {
|
const StarDocs = () => {
|
||||||
const { data: docs, loading, error } = useStarDocuments();
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { data: docs, loading, error } = useStarDocumentsInOrganization(organizationId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
@ -59,7 +66,8 @@ const StarDocs = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarWikis = () => {
|
const StarWikis = () => {
|
||||||
const { data, loading, error } = useStarWikis();
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { data, loading, error } = useStarWikisInOrganization(organizationId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
@ -94,7 +102,7 @@ const StarWikis = () => {
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<SingleColumnLayout>
|
<AppSingleColumnLayout>
|
||||||
<Seo title="主页" />
|
<Seo title="主页" />
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className={styles.titleWrap}>
|
<div className={styles.titleWrap}>
|
||||||
|
@ -111,14 +119,22 @@ const Page: NextPage = () => {
|
||||||
</div>
|
</div>
|
||||||
<StarDocs />
|
<StarDocs />
|
||||||
</div>
|
</div>
|
||||||
</SingleColumnLayout>
|
</AppSingleColumnLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
Page.getInitialProps = async (ctx) => {
|
||||||
|
const { organizationId } = ctx.query;
|
||||||
|
|
||||||
const props = await serverPrefetcher(ctx, [
|
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;
|
return props;
|
||||||
};
|
};
|
|
@ -3,7 +3,7 @@ import { DocumentReader } from 'components/document/reader';
|
||||||
import { WikiTocs } from 'components/wiki/tocs';
|
import { WikiTocs } from 'components/wiki/tocs';
|
||||||
import { getDocumentDetail } from 'data/document';
|
import { getDocumentDetail } from 'data/document';
|
||||||
import { getWikiTocs } from 'data/wiki';
|
import { getWikiTocs } from 'data/wiki';
|
||||||
import { DoubleColumnLayout } from 'layouts/double-column';
|
import { AppDoubleColumnLayout } from 'layouts/app-double-column';
|
||||||
import { NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
import { serverPrefetcher } from 'services/server-prefetcher';
|
||||||
|
@ -15,7 +15,7 @@ interface IProps {
|
||||||
|
|
||||||
const Page: NextPage<IProps> = ({ wikiId, documentId }) => {
|
const Page: NextPage<IProps> = ({ wikiId, documentId }) => {
|
||||||
return (
|
return (
|
||||||
<DoubleColumnLayout
|
<AppDoubleColumnLayout
|
||||||
leftNode={<WikiTocs wikiId={wikiId} documentId={documentId} />}
|
leftNode={<WikiTocs wikiId={wikiId} documentId={documentId} />}
|
||||||
rightNode={<DocumentReader key={documentId} documentId={documentId} />}
|
rightNode={<DocumentReader key={documentId} documentId={documentId} />}
|
||||||
/>
|
/>
|
|
@ -4,7 +4,7 @@ import { DataRender } from 'components/data-render';
|
||||||
import { DocumentReader } from 'components/document/reader';
|
import { DocumentReader } from 'components/document/reader';
|
||||||
import { WikiTocs } from 'components/wiki/tocs';
|
import { WikiTocs } from 'components/wiki/tocs';
|
||||||
import { getWikiDetail, getWikiTocs, useWikiDetail } from 'data/wiki';
|
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 { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
import { serverPrefetcher } from 'services/server-prefetcher';
|
||||||
|
@ -17,7 +17,7 @@ const Page: NextPage<IProps> = ({ wikiId }) => {
|
||||||
const { data: wiki, loading, error } = useWikiDetail(wikiId);
|
const { data: wiki, loading, error } = useWikiDetail(wikiId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoubleColumnLayout
|
<AppDoubleColumnLayout
|
||||||
leftNode={<WikiTocs wikiId={wikiId} />}
|
leftNode={<WikiTocs wikiId={wikiId} />}
|
||||||
rightNode={
|
rightNode={
|
||||||
<DataRender
|
<DataRender
|
|
@ -2,7 +2,7 @@ import { IWiki, WikiApiDefinition } from '@think/domains';
|
||||||
import { WikiSetting } from 'components/wiki/setting';
|
import { WikiSetting } from 'components/wiki/setting';
|
||||||
import { WikiTocs } from 'components/wiki/tocs';
|
import { WikiTocs } from 'components/wiki/tocs';
|
||||||
import { getWikiMembers, getWikiTocs } from 'data/wiki';
|
import { getWikiMembers, getWikiTocs } from 'data/wiki';
|
||||||
import { DoubleColumnLayout } from 'layouts/double-column';
|
import { AppDoubleColumnLayout } from 'layouts/app-double-column';
|
||||||
import { NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import Router, { useRouter } from 'next/router';
|
import Router, { useRouter } from 'next/router';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
@ -22,16 +22,16 @@ const Page: NextPage<IProps> = ({ wikiId }) => {
|
||||||
(tab = 'base') => {
|
(tab = 'base') => {
|
||||||
return () => {
|
return () => {
|
||||||
Router.push({
|
Router.push({
|
||||||
pathname: `/wiki/${wikiId}/setting`,
|
pathname: `/app/org/[organizationId]/wiki/[wikiId]/setting`,
|
||||||
query: { tab },
|
query: { organizationId: query.organizationId, wikiId, tab },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[wikiId]
|
[query, wikiId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoubleColumnLayout
|
<AppDoubleColumnLayout
|
||||||
leftNode={<WikiTocs wikiId={wikiId} />}
|
leftNode={<WikiTocs wikiId={wikiId} />}
|
||||||
rightNode={
|
rightNode={
|
||||||
<div style={{ padding: '16px 24px' }}>
|
<div style={{ padding: '16px 24px' }}>
|
||||||
|
@ -56,4 +56,5 @@ Page.getInitialProps = async (ctx) => {
|
||||||
]);
|
]);
|
||||||
return { ...res, wikiId } as IProps;
|
return { ...res, wikiId } as IProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
|
@ -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;
|
||||||
|
}
|
|
@ -6,8 +6,9 @@ import { Seo } from 'components/seo';
|
||||||
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
||||||
import { WikiCreator } from 'components/wiki-creator';
|
import { WikiCreator } from 'components/wiki-creator';
|
||||||
import { getAllWikis, getJoinWikis, getOwnWikis, useAllWikis, useJoinWikis, useOwnWikis } from 'data/wiki';
|
import { getAllWikis, getJoinWikis, getOwnWikis, useAllWikis, useJoinWikis, useOwnWikis } from 'data/wiki';
|
||||||
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
import { CreateWikiIllustration } from 'illustrations/create-wiki';
|
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 type { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
import { serverPrefetcher } from 'services/server-prefetcher';
|
||||||
|
@ -24,7 +25,8 @@ const grid = {
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
const Wikis = ({ hook }) => {
|
const Wikis = ({ hook }) => {
|
||||||
const { data, loading, error } = hook();
|
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||||
|
const { data, loading, error } = hook(organizationId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
@ -59,7 +61,7 @@ const Wikis = ({ hook }) => {
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<SingleColumnLayout>
|
<AppSingleColumnLayout>
|
||||||
<Seo title="知识库" />
|
<Seo title="知识库" />
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<Title heading={3} style={{ margin: '8px 0' }}>
|
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||||
|
@ -77,15 +79,17 @@ const Page: NextPage = () => {
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</SingleColumnLayout>
|
</AppSingleColumnLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
Page.getInitialProps = async (ctx) => {
|
||||||
|
const { orgId: organizationId } = ctx.query;
|
||||||
|
|
||||||
const props = await serverPrefetcher(ctx, [
|
const props = await serverPrefetcher(ctx, [
|
||||||
{ url: WikiApiDefinition.getAllWikis.client(), action: (cookie) => getAllWikis(cookie) },
|
{ url: WikiApiDefinition.getAllWikis.client(organizationId), action: (cookie) => getAllWikis(cookie) },
|
||||||
{ url: WikiApiDefinition.getJoinWikis.client(), action: (cookie) => getJoinWikis(cookie) },
|
{ url: WikiApiDefinition.getJoinWikis.client(organizationId), action: (cookie) => getJoinWikis(cookie) },
|
||||||
{ url: WikiApiDefinition.getOwnWikis.client(), action: (cookie) => getOwnWikis(cookie) },
|
{ url: WikiApiDefinition.getOwnWikis.client(organizationId), action: (cookie) => getOwnWikis(cookie) },
|
||||||
]);
|
]);
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
.cover {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cover {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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' }}>
|
||||||
|
新建组织
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<Form
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
initValues={{
|
||||||
|
logo: ORGANIZATION_LOGOS[0],
|
||||||
|
}}
|
||||||
|
getFormApi={(formApi) => ($form.current = formApi)}
|
||||||
|
onChange={onFormChange}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<Form.Slot label="Logo">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'end' }}>
|
||||||
|
<div className={styles.cover}>
|
||||||
|
<Avatar
|
||||||
|
shape="square"
|
||||||
|
src={currentCover}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ImageUploader width={260} images={images} selectImage={setCover}>
|
||||||
|
<Button>更换Logo</Button>
|
||||||
|
</ImageUploader>
|
||||||
|
</div>
|
||||||
|
</Form.Slot>
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
label="名称"
|
||||||
|
field="name"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="请输入组织名称"
|
||||||
|
rules={[{ required: true, message: '请输入组织名称' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.TextArea
|
||||||
|
label="描述"
|
||||||
|
field="description"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="请输入组织简介"
|
||||||
|
autosize
|
||||||
|
rules={[{ required: true, message: '请输入组织简介' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button htmlType="submit" type="primary" theme="solid" disabled={!changed} loading={loading}>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</SingleColumnLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -75,7 +75,7 @@ const Page: NextPage = () => {
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
Page.getInitialProps = async (ctx) => {
|
||||||
const props = await serverPrefetcher(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;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
.wikiItemWrap {
|
.wrap {
|
||||||
padding: 12px 16px !important;
|
z-index: 1;
|
||||||
margin: 8px 2px;
|
display: flex;
|
||||||
cursor: pointer;
|
width: 100%;
|
||||||
background-color: var(--semi-color-bg-2);
|
padding: 10vh 32px 48px;
|
||||||
border: 1px solid var(--semi-color-border) !important;
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
max-width: 53%;
|
||||||
|
min-width: 53%;
|
||||||
|
margin-left: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleWrap {
|
@media (max-width: 768px) {
|
||||||
display: flex;
|
.wrap {
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,184 +1,61 @@
|
||||||
import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui';
|
import { Button, 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 { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { WikiCreator } from 'components/wiki/create';
|
import { toLogin, useUser } from 'data/user';
|
||||||
import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card';
|
import { TeamWorkIllustration } from 'illustrations/team-work';
|
||||||
import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document';
|
|
||||||
import { getStarWikis, useStarWikis } from 'data/star';
|
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
|
||||||
import { SingleColumnLayout } from 'layouts/single-column';
|
import { SingleColumnLayout } from 'layouts/single-column';
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import Link from 'next/link';
|
import Router from 'next/router';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title, Paragraph } = 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(
|
|
||||||
() => [
|
|
||||||
<Column
|
|
||||||
title="标题"
|
|
||||||
dataIndex="title"
|
|
||||||
key="title"
|
|
||||||
width={200}
|
|
||||||
render={(_, document: IDocument) => {
|
|
||||||
return (
|
|
||||||
<Link href={'/wiki/[wikiId]/document/[documentId]'} as={`/wiki/${document.wikiId}/document/${document.id}`}>
|
|
||||||
<a style={{ color: 'inherit', textDecoration: 'none' }}>{document.title}</a>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
<Column width={100} title="阅读量" dataIndex="views" key="views" />,
|
|
||||||
<Column
|
|
||||||
title="创建者"
|
|
||||||
dataIndex="createUser"
|
|
||||||
key="createUser"
|
|
||||||
width={160}
|
|
||||||
render={(createUser) => {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<Avatar size="extra-extra-small" src={createUser.avatar} style={{ marginRight: 4 }}>
|
|
||||||
{createUser.name.slice(0, 1)}
|
|
||||||
</Avatar>
|
|
||||||
{createUser.name}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
<Column
|
|
||||||
width={120}
|
|
||||||
title="访问时间"
|
|
||||||
dataIndex="visitedAt"
|
|
||||||
key="visitedAt"
|
|
||||||
render={(date) => <LocaleTime date={date} />}
|
|
||||||
/>,
|
|
||||||
<Column
|
|
||||||
title="操作"
|
|
||||||
dataIndex="operate"
|
|
||||||
key="operate"
|
|
||||||
width={80}
|
|
||||||
render={(_, document) => (
|
|
||||||
<DocumentActions
|
|
||||||
wikiId={document.wikiId}
|
|
||||||
documentId={document.id}
|
|
||||||
onDelete={refresh}
|
|
||||||
showCreateDocument
|
|
||||||
hideDocumentVersion
|
|
||||||
hideDocumentStyle
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>,
|
|
||||||
],
|
|
||||||
[refresh]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refresh();
|
|
||||||
}, [refresh]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title heading={3} style={{ margin: '24px 0 0' }}>
|
|
||||||
最近访问
|
|
||||||
</Title>
|
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
loadingContent={
|
|
||||||
<Table dataSource={[]} loading={true} pagination={false} size="small" style={{ marginTop: 16 }}>
|
|
||||||
{columns}
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
error={error}
|
|
||||||
normalContent={() =>
|
|
||||||
data && data.length ? (
|
|
||||||
<Table dataSource={data} loading={loading} pagination={false} size="small" style={{ marginTop: 16 }}>
|
|
||||||
{columns}
|
|
||||||
</Table>
|
|
||||||
) : (
|
|
||||||
<Empty message="最近访问的文档会出现在此处" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const { user } = useUser();
|
||||||
const { data: staredWikis, loading, error, refresh } = useStarWikis();
|
|
||||||
|
const start = useCallback(() => {
|
||||||
|
if (user) {
|
||||||
|
Router.push(`/app`);
|
||||||
|
} else {
|
||||||
|
toLogin();
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const toGithub = useCallback(() => {
|
||||||
|
window.open('https://github.com/fantasticit/think');
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleColumnLayout>
|
<SingleColumnLayout>
|
||||||
<Seo title="主页" />
|
<Seo title="主页" />
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className={styles.titleWrap}>
|
<div className={styles.wrap}>
|
||||||
<Title heading={3} style={{ margin: '8px 0' }}>
|
<div className={styles.content}>
|
||||||
快捷访问
|
<div>
|
||||||
</Title>
|
<div>
|
||||||
<>
|
<Title style={{ marginBottom: 12 }}>云策文档</Title>
|
||||||
<Button onClick={toggleVisible}>创建知识库</Button>
|
<Paragraph type="tertiary">
|
||||||
<WikiCreator visible={visible} toggleVisible={toggleVisible} />
|
云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。
|
||||||
</>
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
<div style={{ margin: '32px 0' }}>
|
||||||
|
<Button theme="solid" onClick={start}>
|
||||||
|
开始使用
|
||||||
|
</Button>
|
||||||
|
<Button style={{ marginLeft: 12 }} onClick={toGithub}>
|
||||||
|
Github
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.hero}>
|
||||||
|
<TeamWorkIllustration />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
loadingContent={() => (
|
|
||||||
<List
|
|
||||||
grid={grid}
|
|
||||||
dataSource={[1, 2, 3]}
|
|
||||||
renderItem={() => (
|
|
||||||
<List.Item>
|
|
||||||
<WikiPinCardPlaceholder />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
error={error}
|
|
||||||
normalContent={() => (
|
|
||||||
<List
|
|
||||||
grid={grid}
|
|
||||||
dataSource={staredWikis}
|
|
||||||
renderItem={(wiki) => (
|
|
||||||
<List.Item>
|
|
||||||
<WikiPinCard wiki={wiki} />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
emptyContent={<Empty message="收藏的知识库会出现在此处" />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<RecentDocs />
|
|
||||||
</div>
|
</div>
|
||||||
</SingleColumnLayout>
|
</SingleColumnLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
export default Page;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Button, Col, Form, Layout, Modal, Row, Space, Toast, Typography } from
|
||||||
import { Author } from 'components/author';
|
import { Author } from 'components/author';
|
||||||
import { LogoImage, LogoText } from 'components/logo';
|
import { LogoImage, LogoText } from 'components/logo';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { useRegister, useVerifyCode } from 'data/user';
|
import { useRegister, useSystemPublicConfig, useVerifyCode } from 'data/user';
|
||||||
import { isEmail } from 'helpers/validator';
|
import { isEmail } from 'helpers/validator';
|
||||||
import { useInterval } from 'hooks/use-interval';
|
import { useInterval } from 'hooks/use-interval';
|
||||||
import { useRouterQuery } from 'hooks/use-router-query';
|
import { useRouterQuery } from 'hooks/use-router-query';
|
||||||
|
@ -24,6 +24,7 @@ const Page = () => {
|
||||||
const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false);
|
const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false);
|
||||||
const [countDown, setCountDown] = useState(0);
|
const [countDown, setCountDown] = useState(0);
|
||||||
const { register, loading } = useRegister();
|
const { register, loading } = useRegister();
|
||||||
|
const { data: systemConfig } = useSystemPublicConfig();
|
||||||
const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode();
|
const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode();
|
||||||
|
|
||||||
const onFormChange = useCallback((formState) => {
|
const onFormChange = useCallback((formState) => {
|
||||||
|
@ -133,22 +134,29 @@ const Page = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Row gutter={8} style={{ paddingTop: 12 }}>
|
{systemConfig && systemConfig.enableEmailVerify ? (
|
||||||
<Col span={16}>
|
<Row gutter={8} style={{ paddingTop: 12 }}>
|
||||||
<Form.Input
|
<Col span={16}>
|
||||||
noLabel
|
<Form.Input
|
||||||
fieldStyle={{ paddingTop: 0 }}
|
noLabel
|
||||||
placeholder={'请输入验证码'}
|
fieldStyle={{ paddingTop: 0 }}
|
||||||
field="verifyCode"
|
placeholder={'请输入验证码'}
|
||||||
rules={[{ required: true, message: '请输入邮箱收到的验证码!' }]}
|
field="verifyCode"
|
||||||
/>
|
rules={[{ required: true, message: '请输入邮箱收到的验证码!' }]}
|
||||||
</Col>
|
/>
|
||||||
<Col span={8}>
|
</Col>
|
||||||
<Button disabled={!email || countDown > 0} loading={sendVerifyCodeLoading} onClick={getVerifyCode} block>
|
<Col span={8}>
|
||||||
{hasSendVerifyCode ? countDown : '获取验证码'}
|
<Button
|
||||||
</Button>
|
disabled={!email || countDown > 0}
|
||||||
</Col>
|
loading={sendVerifyCodeLoading}
|
||||||
</Row>
|
onClick={getVerifyCode}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{hasSendVerifyCode ? countDown : '获取验证码'}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Button htmlType="submit" type="primary" theme="solid" block loading={loading} style={{ margin: '16px 0' }}>
|
<Button htmlType="submit" type="primary" theme="solid" block loading={loading} style={{ margin: '16px 0' }}>
|
||||||
注册
|
注册
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
|
||||||
|
|
||||||
export const HttpClient = axios.create({
|
export const HttpClient = axios.create({
|
||||||
baseURL: process.env.SERVER_API_URL,
|
baseURL: process.env.SERVER_API_URL,
|
||||||
timeout: 10 * 60 * 1000,
|
timeout: process.env.NODE_ENV === 'production' ? 10 * 60 * 1000 : 3000,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
}) as AxiosInstance;
|
}) as AxiosInstance;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
export declare const DEFAULT_WIKI_AVATAR = "https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png";
|
export declare const DEFAULT_WIKI_AVATAR = "https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png";
|
||||||
export declare const WIKI_AVATARS: string[];
|
export declare const WIKI_AVATARS: string[];
|
||||||
|
export declare const ORGANIZATION_LOGOS: string[];
|
||||||
export declare const EMPTY_DOCUMNENT: {
|
export declare const EMPTY_DOCUMNENT: {
|
||||||
content: string;
|
content: string;
|
||||||
state: Buffer;
|
state: Buffer;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
exports.__esModule = true;
|
exports.__esModule = true;
|
||||||
exports.DOCUMENT_COVERS = exports.EMPTY_DOCUMNENT = exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
|
exports.DOCUMENT_COVERS = exports.EMPTY_DOCUMNENT = exports.ORGANIZATION_LOGOS = exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
|
||||||
exports.DEFAULT_WIKI_AVATAR = 'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png';
|
exports.DEFAULT_WIKI_AVATAR = 'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png';
|
||||||
exports.WIKI_AVATARS = [
|
exports.WIKI_AVATARS = [
|
||||||
exports.DEFAULT_WIKI_AVATAR,
|
exports.DEFAULT_WIKI_AVATAR,
|
||||||
|
@ -16,6 +16,20 @@ exports.WIKI_AVATARS = [
|
||||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
||||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||||
];
|
];
|
||||||
|
exports.ORGANIZATION_LOGOS = [
|
||||||
|
exports.DEFAULT_WIKI_AVATAR,
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||||
|
];
|
||||||
exports.EMPTY_DOCUMNENT = {
|
exports.EMPTY_DOCUMNENT = {
|
||||||
content: JSON.stringify({
|
content: JSON.stringify({
|
||||||
"default": {
|
"default": {
|
||||||
|
|
|
@ -15,6 +15,21 @@ export const WIKI_AVATARS = [
|
||||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const ORGANIZATION_LOGOS = [
|
||||||
|
DEFAULT_WIKI_AVATAR,
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
||||||
|
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||||
|
];
|
||||||
|
|
||||||
export const EMPTY_DOCUMNENT = {
|
export const EMPTY_DOCUMNENT = {
|
||||||
content: JSON.stringify({
|
content: JSON.stringify({
|
||||||
default: {
|
default: {
|
||||||
|
|
|
@ -5,16 +5,16 @@ export declare const DocumentApiDefinition: {
|
||||||
*/
|
*/
|
||||||
search: {
|
search: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "search";
|
server: "/:organizationId/search";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取用户最近访问的文档
|
* 获取用户最近访问的文档
|
||||||
*/
|
*/
|
||||||
recent: {
|
recent: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "recent";
|
server: "/:organizationId/recent";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 新建文档
|
* 新建文档
|
||||||
|
|
|
@ -7,16 +7,16 @@ exports.DocumentApiDefinition = {
|
||||||
*/
|
*/
|
||||||
search: {
|
search: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'search',
|
server: '/:organizationId/search',
|
||||||
client: function () { return '/document/search'; }
|
client: function (organizationId) { return "/document/".concat(organizationId, "/search"); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取用户最近访问的文档
|
* 获取用户最近访问的文档
|
||||||
*/
|
*/
|
||||||
recent: {
|
recent: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'recent',
|
server: '/:organizationId/recent',
|
||||||
client: function () { return '/document/recent'; }
|
client: function (organizationId) { return "/document/".concat(organizationId, "/recent"); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 新建文档
|
* 新建文档
|
||||||
|
|
|
@ -6,3 +6,5 @@ export * from './message';
|
||||||
export * from './template';
|
export * from './template';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './star';
|
export * from './star';
|
||||||
|
export * from './organization';
|
||||||
|
export * from './system';
|
||||||
|
|
|
@ -18,3 +18,5 @@ __exportStar(require("./message"), exports);
|
||||||
__exportStar(require("./template"), exports);
|
__exportStar(require("./template"), exports);
|
||||||
__exportStar(require("./comment"), exports);
|
__exportStar(require("./comment"), exports);
|
||||||
__exportStar(require("./star"), exports);
|
__exportStar(require("./star"), exports);
|
||||||
|
__exportStar(require("./organization"), exports);
|
||||||
|
__exportStar(require("./system"), exports);
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { IOrganization } from '../models';
|
||||||
|
export declare const OrganizationApiDefinition: {
|
||||||
|
/**
|
||||||
|
* 创建
|
||||||
|
*/
|
||||||
|
createOrganization: {
|
||||||
|
method: "post";
|
||||||
|
server: "/create";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取用户个人组织
|
||||||
|
*/
|
||||||
|
getPersonalOrganization: {
|
||||||
|
method: "get";
|
||||||
|
server: "/personal";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取用户除个人组织外其他组织
|
||||||
|
*/
|
||||||
|
getUserOrganizations: {
|
||||||
|
method: "get";
|
||||||
|
server: "/list/personal";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取组织详情
|
||||||
|
*/
|
||||||
|
getOrganizationDetail: {
|
||||||
|
method: "get";
|
||||||
|
server: "/detail/:id";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 更新组织基本信息
|
||||||
|
*/
|
||||||
|
updateOrganization: {
|
||||||
|
method: "post";
|
||||||
|
server: "/update/:id";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 更新组织基本信息
|
||||||
|
*/
|
||||||
|
deleteOrganization: {
|
||||||
|
method: "delete";
|
||||||
|
server: "/delete/:id";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取组织成员
|
||||||
|
*/
|
||||||
|
getMembers: {
|
||||||
|
method: "get";
|
||||||
|
server: "/member/:id";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 添加组织成员
|
||||||
|
*/
|
||||||
|
addMemberById: {
|
||||||
|
method: "post";
|
||||||
|
server: "member/:id/add";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 更新组织成员
|
||||||
|
*/
|
||||||
|
updateMemberById: {
|
||||||
|
method: "patch";
|
||||||
|
server: "member/:id/update";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 删除组织成员
|
||||||
|
*/
|
||||||
|
deleteMemberById: {
|
||||||
|
method: "delete";
|
||||||
|
server: "member/:id/delete";
|
||||||
|
client: (id: IOrganization['id']) => string;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
"use strict";
|
||||||
|
exports.__esModule = true;
|
||||||
|
exports.OrganizationApiDefinition = void 0;
|
||||||
|
exports.OrganizationApiDefinition = {
|
||||||
|
/**
|
||||||
|
* 创建
|
||||||
|
*/
|
||||||
|
createOrganization: {
|
||||||
|
method: 'post',
|
||||||
|
server: '/create',
|
||||||
|
client: function () { return '/organization/create'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取用户个人组织
|
||||||
|
*/
|
||||||
|
getPersonalOrganization: {
|
||||||
|
method: 'get',
|
||||||
|
server: '/personal',
|
||||||
|
client: function () { return '/organization/personal'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取用户除个人组织外其他组织
|
||||||
|
*/
|
||||||
|
getUserOrganizations: {
|
||||||
|
method: 'get',
|
||||||
|
server: '/list/personal',
|
||||||
|
client: function () { return '/organization/list/personal'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取组织详情
|
||||||
|
*/
|
||||||
|
getOrganizationDetail: {
|
||||||
|
method: 'get',
|
||||||
|
server: '/detail/:id',
|
||||||
|
client: function (id) { return "/organization/detail/".concat(id); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新组织基本信息
|
||||||
|
*/
|
||||||
|
updateOrganization: {
|
||||||
|
method: 'post',
|
||||||
|
server: '/update/:id',
|
||||||
|
client: function (id) { return "/organization/update/".concat(id); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新组织基本信息
|
||||||
|
*/
|
||||||
|
deleteOrganization: {
|
||||||
|
method: 'delete',
|
||||||
|
server: '/delete/:id',
|
||||||
|
client: function (id) { return "/organization/delete/".concat(id); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取组织成员
|
||||||
|
*/
|
||||||
|
getMembers: {
|
||||||
|
method: 'get',
|
||||||
|
server: '/member/:id',
|
||||||
|
client: function (id) { return "/organization/member/".concat(id); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 添加组织成员
|
||||||
|
*/
|
||||||
|
addMemberById: {
|
||||||
|
method: 'post',
|
||||||
|
server: 'member/:id/add',
|
||||||
|
client: function (id) { return "/organization/member/".concat(id, "/add"); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新组织成员
|
||||||
|
*/
|
||||||
|
updateMemberById: {
|
||||||
|
method: 'patch',
|
||||||
|
server: 'member/:id/update',
|
||||||
|
client: function (id) { return "/organization/member/".concat(id, "/update"); }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除组织成员
|
||||||
|
*/
|
||||||
|
deleteMemberById: {
|
||||||
|
method: 'delete',
|
||||||
|
server: 'member/:id/delete',
|
||||||
|
client: function (id) { return "/organization/member/".concat(id, "/delete"); }
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,8 +1,8 @@
|
||||||
export declare const StarApiDefinition: {
|
export declare const StarApiDefinition: {
|
||||||
/**
|
/**
|
||||||
* 收藏(或取消收藏)
|
* 加星或取消
|
||||||
*/
|
*/
|
||||||
toggle: {
|
toggleStar: {
|
||||||
method: "post";
|
method: "post";
|
||||||
server: "toggle";
|
server: "toggle";
|
||||||
client: () => string;
|
client: () => string;
|
||||||
|
@ -10,33 +10,33 @@ export declare const StarApiDefinition: {
|
||||||
/**
|
/**
|
||||||
* 检测是否收藏
|
* 检测是否收藏
|
||||||
*/
|
*/
|
||||||
check: {
|
isStared: {
|
||||||
method: "post";
|
method: "post";
|
||||||
server: "check";
|
server: "isStared";
|
||||||
client: () => string;
|
client: () => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取收藏的知识库
|
* 获取组织内加星的知识库
|
||||||
*/
|
*/
|
||||||
wikis: {
|
getStarWikisInOrganization: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "wikis";
|
server: "/:organizationId/wikis";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取知识库内加星的文章
|
* 获取知识库内加星的文章
|
||||||
*/
|
*/
|
||||||
wikiDocuments: {
|
getStarDocumentsInWiki: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "wiki/documents";
|
server: "/wiki/documents";
|
||||||
client: () => string;
|
client: () => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取收藏的文档
|
* 获取组织内加星的文档
|
||||||
*/
|
*/
|
||||||
documents: {
|
getStarDocumentsInOrganization: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "documents";
|
server: "/:organizationId/documents";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,9 +3,9 @@ exports.__esModule = true;
|
||||||
exports.StarApiDefinition = void 0;
|
exports.StarApiDefinition = void 0;
|
||||||
exports.StarApiDefinition = {
|
exports.StarApiDefinition = {
|
||||||
/**
|
/**
|
||||||
* 收藏(或取消收藏)
|
* 加星或取消
|
||||||
*/
|
*/
|
||||||
toggle: {
|
toggleStar: {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
server: 'toggle',
|
server: 'toggle',
|
||||||
client: function () { return '/star/toggle'; }
|
client: function () { return '/star/toggle'; }
|
||||||
|
@ -13,33 +13,33 @@ exports.StarApiDefinition = {
|
||||||
/**
|
/**
|
||||||
* 检测是否收藏
|
* 检测是否收藏
|
||||||
*/
|
*/
|
||||||
check: {
|
isStared: {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
server: 'check',
|
server: 'isStared',
|
||||||
client: function () { return '/star/check'; }
|
client: function () { return '/star/isStared'; }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取收藏的知识库
|
* 获取组织内加星的知识库
|
||||||
*/
|
*/
|
||||||
wikis: {
|
getStarWikisInOrganization: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'wikis',
|
server: '/:organizationId/wikis',
|
||||||
client: function () { return '/star/wikis'; }
|
client: function (organizationId) { return "/star/".concat(organizationId, "/wikis"); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取知识库内加星的文章
|
* 获取知识库内加星的文章
|
||||||
*/
|
*/
|
||||||
wikiDocuments: {
|
getStarDocumentsInWiki: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'wiki/documents',
|
server: '/wiki/documents',
|
||||||
client: function () { return '/star/wiki/documents'; }
|
client: function () { return "/star/wiki/documents"; }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取收藏的文档
|
* 获取组织内加星的文档
|
||||||
*/
|
*/
|
||||||
documents: {
|
getStarDocumentsInOrganization: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'documents',
|
server: '/:organizationId/documents',
|
||||||
client: function () { return '/star/documents'; }
|
client: function (organizationId) { return "/star/".concat(organizationId, "/documents"); }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export declare const SystemApiDefinition: {
|
||||||
|
getPublicConfig: {
|
||||||
|
method: "get";
|
||||||
|
server: "/";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
"use strict";
|
||||||
|
exports.__esModule = true;
|
||||||
|
exports.SystemApiDefinition = void 0;
|
||||||
|
exports.SystemApiDefinition = {
|
||||||
|
getPublicConfig: {
|
||||||
|
method: 'get',
|
||||||
|
server: '/',
|
||||||
|
client: function () { return '/system'; }
|
||||||
|
}
|
||||||
|
};
|
|
@ -5,24 +5,24 @@ export declare const WikiApiDefinition: {
|
||||||
*/
|
*/
|
||||||
getAllWikis: {
|
getAllWikis: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "list/all";
|
server: "list/all/:organizationId";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取用户创建的知识库
|
* 获取用户创建的知识库
|
||||||
*/
|
*/
|
||||||
getOwnWikis: {
|
getOwnWikis: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "list/own";
|
server: "list/own/:organizationId";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 获取用户参与的知识库
|
* 获取用户参与的知识库
|
||||||
*/
|
*/
|
||||||
getJoinWikis: {
|
getJoinWikis: {
|
||||||
method: "get";
|
method: "get";
|
||||||
server: "list/join";
|
server: "list/join/:organizationId";
|
||||||
client: () => string;
|
client: (organizationId: any) => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 新建知识库
|
* 新建知识库
|
||||||
|
|
|
@ -7,24 +7,24 @@ exports.WikiApiDefinition = {
|
||||||
*/
|
*/
|
||||||
getAllWikis: {
|
getAllWikis: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'list/all',
|
server: 'list/all/:organizationId',
|
||||||
client: function () { return '/wiki/list/all'; }
|
client: function (organizationId) { return "/wiki/list/all/".concat(organizationId); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取用户创建的知识库
|
* 获取用户创建的知识库
|
||||||
*/
|
*/
|
||||||
getOwnWikis: {
|
getOwnWikis: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'list/own',
|
server: 'list/own/:organizationId',
|
||||||
client: function () { return '/wiki/list/own'; }
|
client: function (organizationId) { return "/wiki/list/own/".concat(organizationId); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 获取用户参与的知识库
|
* 获取用户参与的知识库
|
||||||
*/
|
*/
|
||||||
getJoinWikis: {
|
getJoinWikis: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
server: 'list/join',
|
server: 'list/join/:organizationId',
|
||||||
client: function () { return '/wiki/list/join'; }
|
client: function (organizationId) { return "/wiki/list/join/".concat(organizationId); }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 新建知识库
|
* 新建知识库
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue