202 lines
5.5 KiB
TypeScript
202 lines
5.5 KiB
TypeScript
import { useCallback } from 'react';
|
|
import type { CSSProperties, ReactNode } from 'react';
|
|
import {
|
|
Tree,
|
|
List,
|
|
Input,
|
|
Button,
|
|
ButtonGroup,
|
|
Dropdown,
|
|
Typography,
|
|
Toast,
|
|
} from '@douyinfe/semi-ui';
|
|
import {
|
|
IconCopyAdd,
|
|
IconDelete,
|
|
IconSearch,
|
|
} from '@douyinfe/semi-icons';
|
|
import { IconModal, IconToken as LabIconToken, IconTreeSelect } from '@douyinfe/semi-icons-lab';
|
|
import { useNoteStore } from '../../stores/useNoteStore';
|
|
import { useUIStore } from '../../stores/useUIStore';
|
|
import noteService from '../../services/note.service';
|
|
import type { INote, ITreeNode } from '../../types';
|
|
|
|
const NAV_ITEMS = ['首页', '设置', '已删除'] as const;
|
|
type NavItem = (typeof NAV_ITEMS)[number];
|
|
|
|
interface NoteTreeProps {
|
|
onRefreshTree: () => void;
|
|
onNavItemClick: (item: NavItem) => void;
|
|
onSearch: () => void;
|
|
}
|
|
|
|
export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: NoteTreeProps) {
|
|
const { treeData, viewData, currentNoteId, setCurrentNote } = useNoteStore();
|
|
const { openDeleteConfirm, setSearchQuery, searchQuery, setActiveView } = useUIStore();
|
|
|
|
const toNoteId = (id: string | number) => Number(id);
|
|
|
|
const loadNote = useCallback(
|
|
(id: number) => {
|
|
noteService.get(id).then(
|
|
(res) => {
|
|
const note = res.data;
|
|
setCurrentNote(note.id, note.title, note.context);
|
|
setActiveView('note');
|
|
},
|
|
() => Toast.error('加载笔记失败'),
|
|
);
|
|
},
|
|
[setCurrentNote, setActiveView],
|
|
);
|
|
|
|
const addNote = useCallback(
|
|
(pid: number) => {
|
|
noteService.add(pid).then(
|
|
() => {
|
|
Toast.success('已添加');
|
|
onRefreshTree();
|
|
},
|
|
() => Toast.error('添加失败'),
|
|
);
|
|
},
|
|
[onRefreshTree],
|
|
);
|
|
|
|
const treeStyle: CSSProperties = {
|
|
height: 550,
|
|
border: '1px solid var(--semi-color-border)',
|
|
position: 'sticky',
|
|
top: '85px',
|
|
};
|
|
|
|
const renderLabel = (label: ReactNode, item: ITreeNode) => (
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Typography.Text
|
|
ellipsis={{ showTooltip: true }}
|
|
style={{ width: 'calc(100% - 68px)' }}
|
|
>
|
|
{label}
|
|
</Typography.Text>
|
|
<ButtonGroup size="small" theme="borderless">
|
|
<Button
|
|
size="small"
|
|
icon={<IconCopyAdd />}
|
|
onClick={(e) => {
|
|
addNote(toNoteId(item.key));
|
|
e.stopPropagation();
|
|
}}
|
|
/>
|
|
<Button
|
|
size="small"
|
|
type="warning"
|
|
icon={<IconDelete />}
|
|
onClick={(e) => {
|
|
openDeleteConfirm(toNoteId(item.key), String(label));
|
|
e.stopPropagation();
|
|
}}
|
|
/>
|
|
</ButtonGroup>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<br />
|
|
<div>
|
|
<Input
|
|
prefix={<IconSearch />}
|
|
showClear
|
|
value={searchQuery}
|
|
onChange={(v) => setSearchQuery(v)}
|
|
onEnterPress={onSearch}
|
|
/>
|
|
</div>
|
|
|
|
<h3
|
|
style={{
|
|
marginBottom: 16,
|
|
position: 'sticky',
|
|
top: '3px',
|
|
paddingLeft: '10px',
|
|
paddingTop: '3px',
|
|
}}
|
|
>
|
|
CYY Note
|
|
</h3>
|
|
|
|
<List
|
|
bordered
|
|
style={{
|
|
flexBasis: '100%',
|
|
flexShrink: 0,
|
|
borderBottom: '1px solid var(--semi-color-border)',
|
|
}}
|
|
dataSource={NAV_ITEMS as unknown as NavItem[]}
|
|
renderItem={(item: NavItem) => (
|
|
<div
|
|
style={{ margin: 5, border: '1px solid var(--semi-color-border)', cursor: 'pointer' }}
|
|
className="list-item"
|
|
onClick={() => onNavItemClick(item)}
|
|
>
|
|
{item === '首页' && <Button type="danger" theme="borderless" icon={<LabIconToken />} style={{ marginRight: 4 }} />}
|
|
{item === '设置' && <Button type="danger" theme="borderless" icon={<IconTreeSelect />} style={{ marginRight: 4 }} />}
|
|
{item === '已删除' && <Button type="danger" theme="borderless" icon={<IconModal />} style={{ marginRight: 4 }} />}
|
|
{item}
|
|
</div>
|
|
)}
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
marginBottom: 16,
|
|
marginTop: 16,
|
|
textAlign: 'center',
|
|
position: 'sticky',
|
|
top: '40px',
|
|
}}
|
|
>
|
|
<Button type="secondary" onClick={() => addNote(0)}>
|
|
添加主菜单
|
|
</Button>
|
|
|
|
<Dropdown
|
|
trigger="click"
|
|
showTick
|
|
position="bottomLeft"
|
|
render={
|
|
<Dropdown.Menu>
|
|
<Dropdown.Title>最近浏览</Dropdown.Title>
|
|
<Dropdown.Divider />
|
|
{viewData.map((item: INote) => (
|
|
<Dropdown.Item
|
|
key={item.id}
|
|
type="primary"
|
|
onClick={() => loadNote(item.id)}
|
|
>
|
|
{item.title}
|
|
</Dropdown.Item>
|
|
))}
|
|
</Dropdown.Menu>
|
|
}
|
|
>
|
|
<Button type="secondary">最近浏览</Button>
|
|
</Dropdown>
|
|
</div>
|
|
|
|
<Tree
|
|
filterTreeNode
|
|
searchPlaceholder=" "
|
|
showFilteredOnly
|
|
directory
|
|
virtualize={{ itemSize: 28 }}
|
|
value={currentNoteId === null ? undefined : String(currentNoteId)}
|
|
treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']}
|
|
renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']}
|
|
style={treeStyle}
|
|
onSelect={(id) => loadNote(toNoteId(id as string | number))}
|
|
/>
|
|
</>
|
|
);
|
|
}
|