Files
CyyNote/cyynote-frontend/src/pages/note-page.tsx
T
2026-04-30 18:53:50 +08:00

375 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useCallback, useState } from 'react';
import type { CSSProperties } from 'react';
import {
Layout,
Nav,
Button,
Breadcrumb,
BackTop,
Avatar,
Toast,
SideSheet,
Row,
List,
ButtonGroup,
Typography,
} from '@douyinfe/semi-ui';
import {
IconBell,
IconExit,
IconArrowUp,
IconMenu,
IconCode,
} from '@douyinfe/semi-icons';
import { IconDescriptions } from '@douyinfe/semi-icons-lab';
import { useAuthStore } from '../stores/useAuthStore';
import { useNoteStore } from '../stores/useNoteStore';
import { useUIStore } from '../stores/useUIStore';
import noteService from '../services/note.service';
import authService from '../services/auth.service';
import NoteTree from '../components/sidebar/NoteTree';
import NoteEditor from '../components/note/NoteEditor';
import HomePage from './home/HomePage';
import DeleteModal from '../components/modals/DeleteModal';
import PasswordModal from '../components/modals/PasswordModal';
import TrashListModal from '../components/modals/TrashListModal';
import type { INote } from '../types';
import './styles.css';
const topStyle: CSSProperties = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: 30,
width: 30,
borderRadius: '100%',
backgroundColor: 'var(--semi-color-primary)',
color: '#fff',
bottom: 65,
right: 20,
};
export default function NotePage() {
const user = useAuthStore((s) => s.user);
const { setTreeData, setHomeData, setViewData } = useNoteStore();
const {
activeView,
siderFlag,
sideVisible,
setActiveView,
setSiderFlag,
setSideVisible,
setDeleteModalVisible,
setUpdatePasswordModalVisible,
} = useUIStore();
const [trashList, setTrashList] = useState<INote[]>([]);
const loadTree = useCallback(() => {
noteService.getAllContent().then(
(res) => setTreeData(res.data),
() => Toast.error('加载笔记树失败'),
);
}, [setTreeData]);
useEffect(() => {
loadTree();
noteService.getHomeData('1', '0').then(
(res) => setHomeData(res.data),
() => {},
);
noteService.getHomeData('3', '0').then(
(res) => setViewData(res.data.slice(0, 10)),
() => {},
);
}, [loadTree, setHomeData, setViewData]);
const handleLogout = useCallback(() => {
void authService.logout();
}, []);
const handleNavItemClick = useCallback(
(item: string) => {
if (item === '首页') {
setActiveView('home');
} else if (item === '设置') {
setActiveView('settings');
} else if (item === '已删除') {
noteService.getHomeData('4', '0').then(
(res) => {
setTrashList(res.data);
setDeleteModalVisible(true);
},
() => Toast.error('加载回收站失败'),
);
}
},
[setActiveView, setDeleteModalVisible],
);
const handleSearch = useCallback(() => {
const query = useUIStore.getState().searchQuery;
setActiveView('search');
noteService.getHomeData('0', query).then(
(res) => setHomeData(res.data),
() => Toast.error('搜索失败'),
);
}, [setActiveView, setHomeData]);
const handleBreakpoint = useCallback(
(_screen: string, matched: boolean) => {
setSiderFlag(matched);
},
[setSiderFlag],
);
const { Header, Footer, Sider, Content } = Layout;
const siderWidth = siderFlag ? '265px' : '0px';
return (
<Layout style={{ border: '1px solid var(--semi-color-border)' }}>
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
<Nav mode="horizontal" defaultSelectedKeys={['Home']}>
<Nav.Header>
<IconDescriptions style={{ height: '36px', fontSize: 36 }} />
</Nav.Header>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 600, marginRight: 24 }}>
</span>
<Nav.Footer>
{!siderFlag && (
<>
<Button
theme="borderless"
icon={<IconMenu size="large" />}
style={{ color: 'var(--semi-color-text-2)', marginRight: 12 }}
onClick={() => setSideVisible(!sideVisible)}
/>
<SideSheet
title="菜单"
width={335}
visible={sideVisible}
onCancel={() => setSideVisible(false)}
>
<NoteTree
onRefreshTree={loadTree}
onNavItemClick={handleNavItemClick}
onSearch={handleSearch}
/>
</SideSheet>
</>
)}
<Button
theme="borderless"
icon={<IconBell size="large" />}
style={{ color: 'var(--semi-color-text-2)', marginRight: 12 }}
/>
<Button
theme="borderless"
icon={<IconExit size="large" />}
style={{ color: 'var(--semi-color-text-2)', marginRight: 12 }}
onClick={handleLogout}
/>
<Avatar color="orange" size="small">
{user?.username ?? ''}
</Avatar>
</Nav.Footer>
</Nav>
</Header>
<Layout>
<Sider
style={{ width: siderWidth, backgroundColor: 'var(--semi-color-bg-1)' }}
breakpoint={['md']}
onBreakpoint={handleBreakpoint}
>
{siderFlag && (
<NoteTree
onRefreshTree={loadTree}
onNavItemClick={handleNavItemClick}
onSearch={handleSearch}
/>
)}
</Sider>
{/* Delete confirmation modal */}
<DeleteModal onDeleted={loadTree} />
{/* Change password modal */}
<PasswordModal />
{/* Trash / deleted notes modal */}
<TrashListModal trashList={trashList} onRestored={loadTree} />
{/* Main content area */}
{activeView === 'note' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}>
<NoteEditor />
</Content>
)}
{activeView === 'home' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['首页']} />
<div
style={{
borderRadius: 10,
border: '1px solid var(--semi-color-border)',
padding: 10,
}}
>
<HomePage />
</div>
</Content>
)}
{activeView === 'settings' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['设置']} />
<div
style={{
borderRadius: 10,
border: '1px solid var(--semi-color-border)',
padding: 15,
}}
>
Settings
<hr />
<Row>
<Button
theme="solid"
type="secondary"
style={{ marginRight: 8 }}
onClick={() => setUpdatePasswordModalVisible(true)}
>
</Button>
</Row>
<br />
<Row>
<Button
theme="solid"
type="tertiary"
style={{ marginRight: 8 }}
onClick={() => {
void noteService
.historyQueryOrDelete(0)
.then((res) => Toast.info(`历史记录数:${String(res.data)}`));
}}
>
Count
</Button>
</Row>
<br />
<Row>
<Button
theme="solid"
type="danger"
style={{ marginRight: 8 }}
onClick={() => {
void noteService
.historyQueryOrDelete(1)
.then(() => Toast.success('历史记录已清空'));
}}
>
</Button>
</Row>
</div>
</Content>
)}
{activeView === 'search' && (
<Content style={{ padding: 24, backgroundColor: 'var(--semi-color-bg-0)' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['搜索结果']} />
<div
style={{
borderRadius: 10,
border: '1px solid var(--semi-color-border)',
padding: 32,
}}
>
<SearchResults onViewNote={(id) => {
setActiveView('note');
noteService.get(id).then(
(res) => {
const note = res.data;
useNoteStore.getState().setCurrentNote(note.id, note.title, note.context);
},
() => Toast.error('加载失败'),
);
}} />
</div>
</Content>
)}
</Layout>
<Footer
style={{
display: 'flex',
justifyContent: 'space-between',
padding: '20px',
color: 'var(--semi-color-text-2)',
backgroundColor: 'rgba(var(--semi-grey-0), 1)',
}}
>
<span style={{ display: 'flex', alignItems: 'center' }}>
<IconCode size="large" style={{ marginRight: 8 }} />
<span>Copyright © 2023 Chuyaoyuan. CYY.IM.</span>
</span>
<span>
<span style={{ marginRight: 24 }}></span>
<span></span>
</span>
</Footer>
<BackTop style={topStyle}>
<IconArrowUp />
</BackTop>
</Layout>
);
}
/** Inline search results — reads homeData (set by search) from store */
function SearchResults({ onViewNote }: { onViewNote: (id: number) => void }) {
const homeData = useNoteStore((s) => s.homeData);
const searchQuery = useUIStore((s) => s.searchQuery);
return (
<>
<p>{searchQuery}</p>
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
<List
dataSource={homeData as INote[]}
renderItem={(item: INote) => (
<List.Item
header={<Avatar color="blue">NOTE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>
{item.title}
</span>
<Typography.Text
ellipsis={{ showTooltip: true }}
style={{ width: 'calc(100% - 68px)' }}
>
{item.createtime}{item.updatetime}
</Typography.Text>
</div>
}
extra={
<ButtonGroup theme="borderless">
<Button onClick={() => onViewNote(item.id)}></Button>
</ButtonGroup>
}
/>
)}
/>
</div>
</>
);
}