update
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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 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(item.key);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
type="warning"
|
||||
icon={<IconDelete />}
|
||||
onClick={(e) => {
|
||||
openDeleteConfirm(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
|
||||
value={currentNoteId}
|
||||
treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']}
|
||||
renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']}
|
||||
style={treeStyle}
|
||||
onSelect={(id) => loadNote(id as unknown as number)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user