-
-

-
-
-
-
-
+ return (
+
+ );
}
diff --git a/cyynote-frontend/src/components/modals/DeleteModal.tsx b/cyynote-frontend/src/components/modals/DeleteModal.tsx
new file mode 100644
index 0000000..31cbf7e
--- /dev/null
+++ b/cyynote-frontend/src/components/modals/DeleteModal.tsx
@@ -0,0 +1,42 @@
+import { Modal, Toast } from '@douyinfe/semi-ui';
+import { useUIStore } from '../../stores/useUIStore';
+import { useNoteStore } from '../../stores/useNoteStore';
+import noteService from '../../services/note.service';
+
+interface DeleteModalProps {
+ onDeleted: () => void;
+}
+
+export default function DeleteModal({ onDeleted }: DeleteModalProps) {
+ const { deleteVisible, deleteId, deleteNoteName, closeDeleteConfirm } = useUIStore();
+ const clearCurrentNote = useNoteStore((s) => s.clearCurrentNote);
+
+ const handleOk = () => {
+ if (deleteId === null) return;
+ noteService.delete(deleteId).then(
+ () => {
+ Toast.info(`已删除:${deleteNoteName}`);
+ closeDeleteConfirm();
+ clearCurrentNote();
+ onDeleted();
+ },
+ () => {
+ Toast.error('删除失败,请重试');
+ closeDeleteConfirm();
+ },
+ );
+ };
+
+ return (
+
+ 确认要删除「{deleteNoteName}」吗?删除后可在回收站恢复。
+
+ );
+}
diff --git a/cyynote-frontend/src/components/modals/PasswordModal.tsx b/cyynote-frontend/src/components/modals/PasswordModal.tsx
new file mode 100644
index 0000000..f3f6b3a
--- /dev/null
+++ b/cyynote-frontend/src/components/modals/PasswordModal.tsx
@@ -0,0 +1,78 @@
+import { Modal, Form, Button, Toast } from '@douyinfe/semi-ui';
+import { useAuthStore } from '../../stores/useAuthStore';
+import { useUIStore } from '../../stores/useUIStore';
+import authService from '../../services/auth.service';
+
+export default function PasswordModal() {
+ const { updatePasswordModelVisible, setUpdatePasswordModelVisible } = useUIStore();
+ const user = useAuthStore((s) => s.user);
+
+ const handleSubmit = (values: {
+ username: string;
+ oldpassword: string;
+ newpassword: string;
+ }) => {
+ authService
+ .updatePassword(values.username, values.oldpassword, values.newpassword)
+ .then(
+ () => {
+ Toast.success('密码修改成功');
+ setUpdatePasswordModelVisible(false);
+ },
+ (error: unknown) => {
+ const msg =
+ error instanceof Error ? error.message : '修改失败,请重试';
+ Toast.error(msg);
+ },
+ );
+ };
+
+ return (
+
setUpdatePasswordModelVisible(false)}
+ closeOnEsc
+ >
+
+
+ );
+}
diff --git a/cyynote-frontend/src/components/modals/TrashListModal.tsx b/cyynote-frontend/src/components/modals/TrashListModal.tsx
new file mode 100644
index 0000000..93ea6d3
--- /dev/null
+++ b/cyynote-frontend/src/components/modals/TrashListModal.tsx
@@ -0,0 +1,66 @@
+import { Modal, List, Avatar, Button, ButtonGroup, Toast, Typography } from '@douyinfe/semi-ui';
+import { useUIStore } from '../../stores/useUIStore';
+import { useNoteStore } from '../../stores/useNoteStore';
+import noteService from '../../services/note.service';
+import type { INote } from '../../types';
+
+interface TrashListModalProps {
+ trashList: INote[];
+ onRestored: () => void;
+}
+
+export default function TrashListModal({ trashList, onRestored }: TrashListModalProps) {
+ const { deleteModelVisible, setDeleteModelVisible } = useUIStore();
+
+ const handleRestore = (id: number) => {
+ noteService.restoreFromTrash(id).then(
+ () => {
+ Toast.success('已从回收站恢复');
+ setDeleteModelVisible(false);
+ onRestored();
+ },
+ () => Toast.error('恢复失败,请重试'),
+ );
+ };
+
+ return (
+
setDeleteModelVisible(false)}
+ closeOnEsc
+ >
+
+
(
+ NOTE}
+ main={
+
+
+ {item.title}
+
+
+ 创建时间:{item.createtime},更新时间:{item.updatetime},
+ 最后浏览时间:{item.viewtime}。
+
+
+ }
+ extra={
+
+
+
+ }
+ />
+ )}
+ />
+
+
+ );
+}
diff --git a/cyynote-frontend/src/components/note/NoteEditor.tsx b/cyynote-frontend/src/components/note/NoteEditor.tsx
new file mode 100644
index 0000000..0bb827c
--- /dev/null
+++ b/cyynote-frontend/src/components/note/NoteEditor.tsx
@@ -0,0 +1,64 @@
+import { useCallback } from 'react';
+import { Input, Skeleton, Breadcrumb } from '@douyinfe/semi-ui';
+import { useNoteStore } from '../../stores/useNoteStore';
+import { useUIStore } from '../../stores/useUIStore';
+import PlaygroundApp from '../lexical/LexicalApp';
+
+export default function NoteEditor() {
+ const { currentNoteId, currentTitle, currentContent, setTitle } = useNoteStore();
+ const { isNoteLoading } = useUIStore();
+
+ const handleTitleChange = useCallback(
+ (value: string) => {
+ setTitle(value);
+ },
+ [setTitle],
+ );
+
+ // handleRefreshTree is intentionally a no-op here:
+ // LexicalApp calls it after structural changes, but tree refresh
+ // is driven by the NoteStore and handled at the NotePage level.
+ const handleRefreshTree = useCallback(() => {
+ // no-op: tree is refreshed by NotePage via store subscription
+ }, []);
+
+ return (
+ <>
+
+
+
}
+ loading={isNoteLoading}
+ >
+
+
+
+ {currentNoteId !== null && currentContent !== null ? (
+
+ ) : (
+
请从左侧选择笔记
+ )}
+
+
+ >
+ );
+}
diff --git a/cyynote-frontend/src/components/profile.component.tsx b/cyynote-frontend/src/components/profile.component.tsx
index 9043fb5..cda8bea 100644
--- a/cyynote-frontend/src/components/profile.component.tsx
+++ b/cyynote-frontend/src/components/profile.component.tsx
@@ -1,7 +1,7 @@
import { Component } from "react";
-import { Navigate } from "react-router-dom";
+import { Navigate } from 'react-router';
import AuthService from "../services/auth.service";
-import IUser from "../types/user.type";
+import type { IUser } from '../types';
type Props = {};
diff --git a/cyynote-frontend/src/components/register.component.tsx b/cyynote-frontend/src/components/register.component.tsx
index e0919eb..85369ef 100644
--- a/cyynote-frontend/src/components/register.component.tsx
+++ b/cyynote-frontend/src/components/register.component.tsx
@@ -1,178 +1,126 @@
-import { Component } from "react";
-import { Formik, Field, Form, ErrorMessage } from "formik";
-import * as Yup from "yup";
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers/yup';
+import * as Yup from 'yup';
+import authService from '../services/auth.service';
-import AuthService from "../services/auth.service";
+const schema = Yup.object().shape({
+ username: Yup.string()
+ .min(3, '用户名最少 3 个字符')
+ .max(20, '用户名最多 20 个字符')
+ .required('请输入用户名'),
+ email: Yup.string().email('邮箱格式不正确').required('请输入邮箱'),
+ password: Yup.string()
+ .min(6, '密码最少 6 个字符')
+ .max(40, '密码最多 40 个字符')
+ .required('请输入密码'),
+});
-type Props = {};
-
-type State = {
- username: string,
- email: string,
- password: string,
- successful: boolean,
- message: string
-};
-
-export default class Register extends Component
{
- constructor(props: Props) {
- super(props);
- this.handleRegister = this.handleRegister.bind(this);
-
- this.state = {
- username: "",
- email: "",
- password: "",
- successful: false,
- message: ""
- };
- }
-
- validationSchema() {
- return Yup.object().shape({
- username: Yup.string()
- .test(
- "len",
- "The username must be between 3 and 20 characters.",
- (val: any) =>
- val &&
- val.toString().length >= 3 &&
- val.toString().length <= 20
- )
- .required("This field is required!"),
- email: Yup.string()
- .email("This is not a valid email.")
- .required("This field is required!"),
- password: Yup.string()
- .test(
- "len",
- "The password must be between 6 and 40 characters.",
- (val: any) =>
- val &&
- val.toString().length >= 6 &&
- val.toString().length <= 40
- )
- .required("This field is required!"),
- });
- }
-
- handleRegister(formValue: { username: string; email: string; password: string }) {
- const { username, email, password } = formValue;
-
- this.setState({
- message: "",
- successful: false
- });
-
- AuthService.register(
- username,
- email,
- password
- ).then(
- response => {
- this.setState({
- message: response.data.message,
- successful: true
- });
- },
- error => {
- const resMessage =
- (error.response &&
- error.response.data &&
- error.response.data.message) ||
- error.message ||
- error.toString();
-
- this.setState({
- successful: false,
- message: resMessage
- });
- }
- );
- }
-
- render() {
- const { successful, message } = this.state;
-
- const initialValues = {
- username: "",
- email: "",
- password: "",
- };
-
- return (
-
-
-

-
-
-
-
-
-
- );
- }
+interface RegisterForm {
+ username: string;
+ email: string;
+ password: string;
+}
+
+export default function Register() {
+ const [message, setMessage] = useState('');
+ const [successful, setSuccessful] = useState(false);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({ resolver: yupResolver(schema) });
+
+ const onSubmit = async (data: RegisterForm) => {
+ setMessage('');
+ setSuccessful(false);
+ try {
+ await authService.register(data.username, data.email, data.password);
+ setSuccessful(true);
+ setMessage('注册成功!请登录。');
+ } catch (error: unknown) {
+ const msg = error instanceof Error ? error.message : '注册失败,请重试';
+ setSuccessful(false);
+ setMessage(msg);
+ }
+ };
+
+ return (
+
+
+

+
+
+
+
+ );
}
diff --git a/cyynote-frontend/src/components/sidebar/NoteTree.tsx b/cyynote-frontend/src/components/sidebar/NoteTree.tsx
new file mode 100644
index 0000000..35db517
--- /dev/null
+++ b/cyynote-frontend/src/components/sidebar/NoteTree.tsx
@@ -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) => (
+
+
+ {label}
+
+
+ }
+ onClick={(e) => {
+ addNote(item.key);
+ e.stopPropagation();
+ }}
+ />
+ }
+ onClick={(e) => {
+ openDeleteConfirm(item.key, String(label));
+ e.stopPropagation();
+ }}
+ />
+
+
+ );
+
+ return (
+ <>
+
+
+ }
+ showClear
+ value={searchQuery}
+ onChange={(v) => setSearchQuery(v)}
+ onEnterPress={onSearch}
+ />
+
+
+
+ CYY Note
+
+
+ (
+ onNavItemClick(item)}
+ >
+ {item === '首页' && } style={{ marginRight: 4 }} />}
+ {item === '设置' && } style={{ marginRight: 4 }} />}
+ {item === '已删除' && } style={{ marginRight: 4 }} />}
+ {item}
+
+ )}
+ />
+
+
+
+
+
+ 最近浏览
+
+ {viewData.map((item: INote) => (
+ loadNote(item.id)}
+ >
+ {item.title}
+
+ ))}
+
+ }
+ >
+
+
+
+
+ [0]['treeData']}
+ renderLabel={renderLabel as Parameters[0]['renderLabel']}
+ style={treeStyle}
+ onSelect={(id) => loadNote(id as unknown as number)}
+ />
+ >
+ );
+}
diff --git a/cyynote-frontend/src/lib/axios.ts b/cyynote-frontend/src/lib/axios.ts
new file mode 100644
index 0000000..2b1f064
--- /dev/null
+++ b/cyynote-frontend/src/lib/axios.ts
@@ -0,0 +1,30 @@
+import axios from 'axios';
+import { useAuthStore } from '../stores/useAuthStore';
+
+const axiosInstance = axios.create({
+ baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api',
+ timeout: 30_000,
+});
+
+axiosInstance.interceptors.request.use((config) => {
+ const { token } = useAuthStore.getState();
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
+
+axiosInstance.interceptors.response.use(
+ (response) => response,
+ (error: unknown) => {
+ if (
+ axios.isAxiosError(error) &&
+ error.response?.status === 401
+ ) {
+ useAuthStore.getState().logout();
+ }
+ return Promise.reject(error);
+ },
+);
+
+export default axiosInstance;
diff --git a/cyynote-frontend/src/main.tsx b/cyynote-frontend/src/main.tsx
index 9f1aab0..64c49c5 100644
--- a/cyynote-frontend/src/main.tsx
+++ b/cyynote-frontend/src/main.tsx
@@ -1,28 +1,15 @@
-// import React from 'react'
-// import ReactDOM from 'react-dom/client'
-// import App from './App.tsx'
-// import './index.css'
-//
-// ReactDOM.createRoot(document.getElementById('root')!).render(
-//
-//
-// ,
-// )
-
-
-
+import React from 'react';
import ReactDOM from 'react-dom/client';
-import { BrowserRouter } from "react-router-dom";
+import { BrowserRouter } from 'react-router';
import './index.css';
import App from './App';
-const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+ ,
);
-root.render(
-
-
-
-);
diff --git a/cyynote-frontend/src/pages/home/HomePage.tsx b/cyynote-frontend/src/pages/home/HomePage.tsx
new file mode 100644
index 0000000..a8ab501
--- /dev/null
+++ b/cyynote-frontend/src/pages/home/HomePage.tsx
@@ -0,0 +1,73 @@
+import {
+ List,
+ Avatar,
+ Button,
+ ButtonGroup,
+ Typography,
+ Tabs,
+ TabPane,
+ Toast,
+} from '@douyinfe/semi-ui';
+import { useNoteStore } from '../../stores/useNoteStore';
+import noteService from '../../services/note.service';
+import type { INote } from '../../types';
+
+export default function HomePage() {
+ const { homeData, setHomeData, setCurrentNote } = useNoteStore();
+
+ const loadNote = (id: number) => {
+ noteService.get(id).then(
+ (res) => {
+ const note = res.data;
+ setCurrentNote(note.id, note.title, note.context);
+ },
+ () => Toast.error('加载笔记失败'),
+ );
+ };
+
+ const handleTabChange = (key: string) => {
+ noteService.getHomeData(key, '0').then(
+ (res) => setHomeData(res.data),
+ () => Toast.error('加载列表失败'),
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
(
+ NOTE}
+ main={
+
+
+ {item.title}
+
+
+ 创建时间:{item.createtime},更新时间:{item.updatetime},
+ 最后浏览时间:{item.viewtime}。
+
+
+ }
+ extra={
+
+
+
+ }
+ />
+ )}
+ />
+
+ >
+ );
+}
diff --git a/cyynote-frontend/src/pages/note-page.tsx b/cyynote-frontend/src/pages/note-page.tsx
index 6eaeac9..c465fa1 100644
--- a/cyynote-frontend/src/pages/note-page.tsx
+++ b/cyynote-frontend/src/pages/note-page.tsx
@@ -1,1154 +1,374 @@
-import { Component } from "react";
-import { Navigate } from "react-router-dom";
-import AuthService from "../services/auth.service";
-import IUser from "../types/user.type";
-import {
- Layout,
- Nav,
- Button,
- Breadcrumb,
- Skeleton,
- Avatar,
- List,
- Tree,
- Input,
- Typography,
- Toast,
- ButtonGroup, Form,
- SideSheet, Tabs, TabPane, BackTop, Modal, Dropdown, Tag,
- Row
-} from '@douyinfe/semi-ui';
-
-import {
- IconBell,
- IconBytedanceLogo,
- IconSearch,
- IconExit, IconArrowUp, IconMenu, IconCopyAdd, IconDelete, IconMinusCircle, IconCode
-} from '@douyinfe/semi-icons';
-import { IconDescriptions, IconModal, IconToken, IconTreeSelect } from "@douyinfe/semi-icons-lab";
-
-import "./styles.css";
-
-
-import NoteService from "../services/note.service";
-
-import PlaygroundApp from "../components/lexical/LexicalApp.tsx";
-
-
-type Props = NonNullable;
-
-type State = {
- redirect: string | null,
- showSettings: boolean,
- showNote: boolean,
- showHome: boolean,
- showSearch: boolean,
- searchData: string,
- userReady: boolean,
- sideVisible: boolean,
- siderFlag: boolean,
- siderWidth: string,
- skeletonLoad: boolean,
- noteTitle: string,
- noteId: bigint,
- editData: null,
- isEditMode: boolean,
- isNoteShow: boolean,
- treeNodeData: object,
- dataHome: object,
- deleteListData: object,
- deleteVisible: boolean,
- deleteId: bigint,
- treeValue: bigint,
- dataView: object,
- deleteModelVisible: boolean,
- updatePasswordModelVisible: boolean,
- currentUser: IUser & { accessToken: string }
-}
-
-export default class NotePage extends Component {
-
- constructor(props: Props) {
- super(props);
- this.handleLawClick = this.handleLawClick.bind(this)
- this.state = {
- showSettings: false,
- showNote: false,
- showHome: true,
- showSearch: false,
- searchData: '',
- redirect: null,
- userReady: false,
- sideVisible: false,
- siderFlag: true,
- siderWidth: '265px',
- skeletonLoad: false,
- isEditMode: false,
- noteTitle: '',
- noteId: null,
- editData: null,
- isNoteShow: true,
- treeNodeData: [],
- dataHome: [],
- dataView: [],
- deleteListData: [],
- deleteVisible: false,
- deleteId: '',
- treeValue: null,
- deleteModelVisible: false,
- updatePasswordModelVisible: false,
- currentUser: { accessToken: "" }
- };
- }
-
- componentDidMount() {
- const currentUser = AuthService.getCurrentUser();
-
- console.log(currentUser)
- if (!currentUser) {
- this.setState({ redirect: "/login" });
- return;
- }
- this.setState({ currentUser: currentUser, userReady: true })
-
-
- NoteService.getAllContent().then(
- response => {
- // console.log(response.data.data)
-
- const listData = response.data.data
- this.setState({
- treeNodeData: listData
- });
- },
- error => {
- console.log(error)
- Toast.info('鉴权失败');
- this.logOut()
- // return;
- }
- );
-
- NoteService.dataHome('1', '0').then(
- response => {
- // console.log(response.data.data);
- const data = response.data.data
- console.log(data);
- this.setState({
- dataHome: data,
- })
- },
- error => {
- console.log(error)
- }
- );
- NoteService.dataHome('3', '0').then(
- response => {
- const data = response.data.data
- console.log(data);
- const viewdata = data.slice(0, -10);
- this.setState({
- dataView: viewdata,
- })
- },
- error => {
- console.log(error)
- }
- );
-
- }
-
- logOut = () => {
- console.log('logout')
- AuthService.logout();
-
- AuthService.logoutApi().then(
- response => {
-
- console.log(response)
- this.setState({
- currentUser: undefined
- });
- console.log('logout2')
- this.setState({ redirect: "/login" })
- },
- error => {
- console.log(error)
- this.setState({ redirect: "/login" })
- }
- );
-
- }
-
-
- handleLawClick = (val) => {
- console.log('handleLawClick')
- console.log(val)
- NoteService.getAllContent().then(
- response => {
- // console.log(response.data.data)
- const listData = response.data.data
- this.setState({
- treeNodeData: listData
- });
- },
- error => {
- console.log(error)
- }
- );
- }
-
-
- handleUpdateSubmit = (values: { username: string; oldpassword: string; newpassword: string }) => {
- console.log(values);
- // Toast.info('表单已提交');
-
- AuthService.updatePassword(values.username,values.oldpassword, values.newpassword).then(
- () => {
- Toast.info('修改成功');
- this.setState({
- updatePasswordModelVisible:false
- });
- },
- error => {
- const resMessage =
- (error.response &&
- error.response.data &&
- error.response.data.message) ||
- error.message ||
- error.toString();
- Toast.info(resMessage);
- }
- );
- };
-
-
- render() {
- const { Header, Footer, Sider, Content } = Layout;
-
- if (this.state.redirect) {
- return
- }
-
- const {
- currentUser, siderWidth, siderFlag, isNoteShow,
- sideVisible, skeletonLoad, treeNodeData, noteTitle, noteId,
- showHome, showNote, showSettings, showSearch, searchData, editData,
- dataHome, dataView, deleteVisible, deleteId, deleteModelVisible, deleteListData, updatePasswordModelVisible
- } = this.state;
-
- const data = [
- '首页',
- '设置',
- '已删除',
- ];
-
- const sidechange = () => {
- console.log(this.state.noteId)
- this.setState({
- sideVisible: !sideVisible,
- treeValue: this.state.noteId
- });
- };
-
- const treeButton = (value) => (
-
-
-
-
- {/**/}
- {/* Menu Item 1*/}
- {/* Menu Item 2*/}
- {/* Menu Item 3*/}
- {/* */}
- {/* }*/}
- {/*>*/}
- {/* 更多*/}
- {/**/}
-
-
- );
-
- const handleDeleteOk = () => {
- console.log('handleOk');
- deleteNote(this.state.deleteId);
- console.log(this.state.deleteId)
- this.setState({ deleteVisible: false, });
- }
-
- const handleDeleteAfterClose = () => {
- console.log('handleAfterClose');
- this.setState({ deleteVisible: false, });
- }
-
- const handleDeleteCancel = () => {
- console.log('onCancel');
- this.setState({ deleteVisible: false, });
- }
-
- const handleDeleteModelOk = () => {
- console.log('handleDeleteModelOk');
- this.setState({ deleteModelVisible: false, });
- }
-
- const handleDeleteModelAfterClose = () => {
- console.log('handleDeleteModelAfterClose');
- this.setState({ deleteModelVisible: false, });
- }
-
- const handleDeleteModelCancel = () => {
- console.log('handleDeleteModelCancel');
- this.setState({ deleteModelVisible: false, });
- }
-
- const handleUpdatePasswordModelOk = () => {
- console.log('handleupdatePasswordModelOk');
- this.setState({ updatePasswordModelVisible: false, });
- }
-
- const handleUpdatePasswordModelAfterClose = () => {
- console.log('handleupdatePasswordModelAfterClose');
- this.setState({ updatePasswordModelVisible: false, });
- }
-
- const handleUpdatePasswordModelCancel = () => {
- console.log('handleupdatePasswordModelCancel');
- this.setState({ updatePasswordModelVisible: false, });
- }
-
-
-
- const topStyle = {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- height: 30,
- width: 30,
- borderRadius: '100%',
- backgroundColor: '#0077fa',
- color: '#fff',
- bottom: 65,
- right: 20,
- };
-
-
- const treeDataStyle = {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center'
- };
-
- const renderLabel = (label, item) => (
-
-
- {label}
-
- {/*{label}*/}
- {treeButton(item.key)}
-
- );
- const onTreeSelect = (e) => {
- console.log('当前选项: ', e)
- this.setState({
- isNoteShow: false
- });
- NoteService.get(e).then(
- response => {
- // console.log(response.data.data)
- const data = response.data.data
- // console.log(data)
- this.setState({
- showSettings: false,
- showNote: true,
- showHome: false,
- showSearch: false,
- noteId: data.id,
- editData: data.context,
- noteTitle: data.title,
- isNoteShow: true,
- treeValue: data.id,
- });
- },
- error => {
- console.log(error)
- }
- );
- console.log(e);
- };
-
-
- const treeDataWithNode = treeNodeData;
-
- console.log(treeDataWithNode)
-
- const treeStyle = {
- // width: 260,
- height: 550,
- border: '1px solid var(--semi-color-border)',
- position: 'sticky',
- top: '85px',
- };
-
- const onbreakpoint = (screen, bool) => {
- console.log(screen, bool);
-
- if (!bool) {
- this.setState({
- siderFlag: false,
- siderWidth: '0px'
- });
- } else {
- this.setState({
- siderFlag: true,
- siderWidth: '265px'
- });
- }
-
- };
-
- const inputOnChange = (value, e) => {
- console.log(value);
- this.setState({
- noteTitle: value,
- });
- }
-
- const itemClick = (e, item) => {
- console.log(e);
- console.log(e.item);
- console.log(item);
- console.log('itemClick');
- Toast.info('菜单选择:' + item);
- if (item === '首页') {
- this.setState({
- showSettings: false,
- showNote: false,
- showHome: true,
- showSearch: false,
- });
- } else if (item === '设置') {
- this.setState({
- showSettings: true,
- showNote: false,
- showHome: false,
- showSearch: false,
- });
-
- } else if (item === '已删除') {
- console.log('已删除');
- this.setState({
- deleteModelVisible: true,
- });
-
- NoteService.dataHome('4', '0').then(
- response => {
- // console.log(response.data.data);
- const data = response.data.data
- // console.log(data);
- this.setState({
- deleteListData: data,
- })
- },
- error => {
- console.log(error)
- }
- );
- }
- };
- const homeDataClick = (e, item) => {
- console.log(e);
- console.log(e.item);
- console.log(item);
- console.log('homeDataClick');
- // Toast.info('查看:' + item);
- onTreeSelect(item);
- };
-
- const deleteNote = (id) => {
- console.log('deleteNote');
- NoteService.delete(id).then(
- response => {
- console.log(response.data.data);
- const data = response.data.data
- console.log(data);
- Toast.info('删除:' + data);
- this.handleLawClick();
- },
- error => {
- console.log(error)
- }
- );
- }
-
- const addNote = (pid) => {
- console.log('addNote');
- console.log(pid);
- NoteService.add(pid).then(
- response => {
- console.log(response.data.data);
- const data = response.data.data
- console.log(data);
- Toast.info('添加:' + data);
- this.handleLawClick();
- },
- error => {
- console.log(error)
- }
- );
- }
-
-
- const updatePassword = () => {
- console.log('updatePassword');
- this.setState({updatePasswordModelVisible: true,});
- }
-
-
- const historyClick = (flag) => {
- console.log('historyClick');
- console.log(flag);
-
- NoteService.historyQueryOrDelete(flag).then(
- response => {
- console.log("data:"+response.data.data)
- Toast.info("historyCount:"+response.data.data);
- },
- error => {
- console.log(error)
- }
- );
-
- }
-
-
- const dropdownClick = (id) => {
- console.log('dropdownClick');
- console.log(id);
- console.log('dropdownClick=============');
- onTreeSelect(id);
- }
- const search = () => {
- console.log('search');
- console.log(this.state.searchData);
-
- this.setState({
- showSettings: false,
- showNote: false,
- showHome: false,
- showSearch: true,
- });
-
- NoteService.dataHome('0', this.state.searchData).then(
- response => {
- console.log(response.data.data);
- const data = response.data.data
- console.log(data);
- this.setState({
- dataHome: data,
- })
- },
- error => {
- console.log(error)
- }
- );
-
- }
-
- const onTabSelect = (e) => {
- console.log(e);
- console.log('onTabSelect');
-
- NoteService.dataHome(e, '0').then(
- response => {
- console.log(response.data.data);
- const data = response.data.data
- console.log(data);
- this.setState({
- dataHome: data,
- })
- },
- error => {
- console.log(error)
- }
- );
-
- };
- const deleteListDataClick = (e, item) => {
- console.log(e);
- console.log(e.item);
- console.log(item);
- console.log('deleteListDataClick');
-
- NoteService.deleteBack(item).then(
- response => {
- console.log(response.data.data)
- Toast.info('已恢复:' + response.data.data);
- this.handleLawClick(0)
- this.setState({ deleteModelVisible: false, });
- },
- error => {
- console.log(error)
- }
- );
- };
-
-
- return (
-
-
-
-
-
-
-
-
-
- {siderFlag ? (
- <>
-
-
- } showClear onEnterPress={() => search()} >
-
-
- CYY Note
-
- itemClick(e, item)}>
- {/*} style={{ marginRight: 4 }} />*/}
- {item === '首页' && } style={{ marginRight: 4 }} />}
- {item === '设置' && } style={{ marginRight: 4 }} />}
- {item === '已删除' && } style={{ marginRight: 4 }} />}
-
- {item}
-
- }
-
- // renderItem={item => itemClick(e, item)}>{item}}
- />
-
-
-
-
-
- 最近浏览
-
- {
- this.state.dataView.map((item, key) => {
- return (
- dropdownClick(item.id)}>{item.title}
- )
- })
- }
-
- }
- >
-
-
-
- (
-
- )}
- showFilteredOnly={true}
- directory
- value={this.state.treeValue}
- treeData={treeDataWithNode}
- renderLabel={renderLabel}
- style={treeStyle}
- onChange={e => console.log('当前所有选中项: ', e)}
- onSelect={e => onTreeSelect(e)}
- />
- >
- ) : null
- }
-
- =1.16.0
- onCancel={handleDeleteCancel}
- closeOnEsc={true}
- >
- 确认要删除 {noteTitle} 吗?.
-
- More content... {noteId}
-
-
- =1.16.0
- onCancel={handleDeleteModelCancel}
- closeOnEsc={true}
- >
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
-
-
- =1.16.0
- onCancel={handleUpdatePasswordModelCancel}
- closeOnEsc={true}
- >
- 已经删除列表
-
-
-
(
- NOTE}
- main={
-
- {item.title}
-
-
- 创建时间:{item.createtime},更新时间:{item.updatetime},最后浏览时间:{item.viewtime}。
-
-
-
- }
- extra={
-
-
-
- }
- />
- )}
- />
-
-
- {showNote &&
-
-
-
-
-
} loading={skeletonLoad}>
-
-
-
- {isNoteShow ? (
-
- ) : (
-
- 空
-
- )}
-
.
-
-
- }
- {showHome &&
-
-
-
onTabSelect(e)}>
-
-
-
-
-
-
-
-
-
-
(
- NOTE}
- main={
-
- {item.title}
-
- 创建时间:{item.createtime},更新时间:{item.updatetime},最后浏览时间:{item.viewtime}。
-
-
-
- }
- extra={
-
-
-
-
- }
- />
- )}
- />
-
-
- }
-
- {showSettings &&
-
-
- Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
-
- {showSearch &&
-
-
- Search: {this.state.searchData}
- {this.state.searchData}
-
-
-
(
- NOTE}
- main={
-
- {item.title},
-
- 创建时间:{item.createtime},更新时间:{item.updatetime},最后浏览时间:{item.viewtime}。
-
-
- }
- extra={
-
-
-
-
- }
- />
- )}
- />
-
-
-
- }
-
-
-
-
-
-
-
-
- );
- }
-}
+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,
+ setDeleteModelVisible,
+ setUpdatePasswordModelVisible,
+ } = useUIStore();
+
+ const [trashList, setTrashList] = useState([]);
+
+ 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);
+ setDeleteModelVisible(true);
+ },
+ () => Toast.error('加载回收站失败'),
+ );
+ }
+ },
+ [setActiveView, setDeleteModelVisible],
+ );
+
+ 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 (
+
+
+
+
+
+
+
+ {siderFlag && (
+
+ )}
+
+
+ {/* Delete confirmation modal */}
+
+
+ {/* Change password modal */}
+
+
+ {/* Trash / deleted notes modal */}
+
+
+ {/* Main content area */}
+ {activeView === 'note' && (
+
+
+
+ )}
+
+ {activeView === 'home' && (
+
+
+
+
+
+
+ )}
+
+ {activeView === 'settings' && (
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {activeView === 'search' && (
+
+
+
+ {
+ setActiveView('note');
+ noteService.get(id).then(
+ (res) => {
+ const note = res.data;
+ useNoteStore.getState().setCurrentNote(note.id, note.title, note.context);
+ },
+ () => Toast.error('加载失败'),
+ );
+ }} />
+
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
+
+/** 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 (
+ <>
+ 搜索:{searchQuery}
+
+
(
+ NOTE}
+ main={
+
+
+ {item.title}
+
+
+ 创建时间:{item.createtime},更新时间:{item.updatetime}。
+
+
+ }
+ extra={
+
+
+
+ }
+ />
+ )}
+ />
+
+ >
+ );
+}
diff --git a/cyynote-frontend/src/pages/sign-in.tsx b/cyynote-frontend/src/pages/sign-in.tsx
index 9599a03..b91aa12 100644
--- a/cyynote-frontend/src/pages/sign-in.tsx
+++ b/cyynote-frontend/src/pages/sign-in.tsx
@@ -1,123 +1,79 @@
-import { Component } from "react";
-import { Form,Toast, Button } from '@douyinfe/semi-ui';
-// import { IconSemiLogo, IconFeishuLogo, IconHelpCircle, IconBell } from '@douyinfe/semi-icons';
-import { Layout } from '@douyinfe/semi-ui';
-
-import { Navigate } from "react-router-dom";
-// import { Formik, Field, Form, ErrorMessage } from "formik";
-// import * as Yup from "yup";
-
-import AuthService from "../services/auth.service";
-import {IconDescriptions} from "@douyinfe/semi-icons-lab";
-
-type Props = {};
-
-type State = {
- redirect: string | null,
- username: string,
- password: string,
- loading: boolean,
- message: string
-};
-
-export default class SignIn extends Component {
- constructor(props: Props) {
- super(props);
- // this.handleLogin = this.handleLogin.bind(this);
-
- this.state = {
- redirect: null,
- username: "",
- password: "",
- loading: false,
- message: ""
- };
- }
-
- componentDidMount() {
- const currentUser = AuthService.getCurrentUser();
-
- if (currentUser) {
- this.setState({ redirect: "/home" });
- }
- }
-
- // componentWillUnmount() {
- // window.location.reload();
- // }
-
-
- handleSubmit = (values: { username: string; password: string }) => {
- console.log(values);
- // Toast.info('表单已提交');
-
- AuthService.login(values.username, values.password).then(
- () => {
- Toast.info('登录成功');
- this.setState({
- redirect: "/home"
- });
- },
- error => {
- const resMessage =
- (error.response &&
- error.response.data &&
- error.response.data.message) ||
- error.message ||
- error.toString();
- Toast.info(resMessage);
- }
- );
- };
-
- render() {
- if (this.state.redirect) {
- return
- }
-
-
- const { Header, Footer, Content } = Layout;
-
- const commonStyle = {
- height: 64,
- lineHeight: '64px',
- background: 'var(--semi-color-fill-0)'
- };
- return (
-
-
-
-
-
-
-
-
-
- {/*Header*/}
-
-
- CyyNote 登录
-
-
- I have read and agree to the terms of service
-
-
- Or
-
-
-
- >
- )}
-
-
-
-
-
-
-
- );
- }
-}
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router';
+import { Form, Button, Toast, Layout } from '@douyinfe/semi-ui';
+import { IconDescriptions } from '@douyinfe/semi-icons-lab';
+import authService from '../services/auth.service';
+import { useAuthStore } from '../stores/useAuthStore';
+
+interface SignInForm {
+ username: string;
+ password: string;
+}
+
+export default function SignIn() {
+ const navigate = useNavigate();
+ const user = useAuthStore((s) => s.user);
+
+ useEffect(() => {
+ if (user) navigate('/home', { replace: true });
+ }, [user, navigate]);
+
+ const handleSubmit = async (values: SignInForm) => {
+ try {
+ await authService.login(values.username, values.password);
+ Toast.success('登录成功');
+ navigate('/home', { replace: true });
+ } catch (error: unknown) {
+ const msg =
+ error instanceof Error ? error.message : '登录失败,请重试';
+ Toast.error(msg);
+ }
+ };
+
+ const { Header, Content } = Layout;
+ const commonStyle = {
+ height: 64,
+ lineHeight: '64px',
+ background: 'var(--semi-color-fill-0)',
+ };
+
+ return (
+
+
+
+
+ CyyNote 登录
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/cyynote-frontend/src/services/auth-header.ts b/cyynote-frontend/src/services/auth-header.ts
index a2db2bb..5d703e4 100644
--- a/cyynote-frontend/src/services/auth-header.ts
+++ b/cyynote-frontend/src/services/auth-header.ts
@@ -1,15 +1,15 @@
-export default function authHeader() {
- const userStr = localStorage.getItem("user");
- console.log('userStr:'+userStr)
- let user = null;
- if (userStr)
- user = JSON.parse(userStr);
+/**
+ * @deprecated Use src/lib/axios.ts instead — the unified Axios instance
+ * automatically injects the Authorization header via a request interceptor.
+ * This file is kept temporarily for services not yet migrated.
+ */
+import { useAuthStore } from '../stores/useAuthStore';
- if (user && user.accessToken) {
- return { Authorization: 'Bearer ' + user.accessToken }; // for Spring Boot back-end
- // return { 'x-access-token': user.accessToken }; // for Node.js Express back-end
- } else {
- return { Authorization: '' }; // for Spring Boot back-end
- // return { 'x-access-token': null }; // for Node Express back-end
+export default function authHeader(): Record {
+ const { token } = useAuthStore.getState();
+ if (token) {
+ return { Authorization: `Bearer ${token}` };
}
+ return { Authorization: '' };
}
+
diff --git a/cyynote-frontend/src/services/auth.service.ts b/cyynote-frontend/src/services/auth.service.ts
index a82973a..c390282 100644
--- a/cyynote-frontend/src/services/auth.service.ts
+++ b/cyynote-frontend/src/services/auth.service.ts
@@ -1,68 +1,52 @@
-import axios from "axios";
-import authHeader from "./auth-header.ts";
+import axiosInstance from '../lib/axios';
+import { useAuthStore } from '../stores/useAuthStore';
+import type { IAuthResponse } from '../types';
-// const API_URL = "http://localhost:8080/api/auth/";
-const API_URL = "/api/auth/";
-class AuthService {
- login(username: string, password: string) {
- return axios
- .post(API_URL + "signin", {
- username,
- password
- })
- .then(response => {
- console.log(response)
- if (response.data.data.accessToken) {
- console.log(response.data.data)
- localStorage.setItem("user", JSON.stringify(response.data.data));
- }
- debugger
+const API_URL = 'auth/';
- return response.data.data;
- });
- }
+const authService = {
+ async login(username: string, password: string): Promise {
+ const response = await axiosInstance.post<{ data: IAuthResponse }>(
+ `${API_URL}signin`,
+ { username, password },
+ );
+ const { data } = response.data;
+ useAuthStore.getState().setAuth(data.user, data.accessToken);
+ return data;
+ },
- updatePassword(username: string, oldpassword: string, newpassword: string) {
- return axios
- .post(API_URL + "updatePassword", {
- username,
- oldpassword,
- newpassword
- }, { headers: authHeader() })
- .then(response => {
- console.log(response)
- return response.data;
- });
- }
+ async logout(): Promise {
+ try {
+ await axiosInstance.get(`${API_URL}logout`);
+ } finally {
+ useAuthStore.getState().logout();
+ }
+ },
- logout() {
- localStorage.removeItem("user");
- }
+ async register(
+ username: string,
+ email: string,
+ password: string,
+ ): Promise {
+ await axiosInstance.post(`${API_URL}signup`, { username, email, password });
+ },
-
- logoutApi() {
- return axios
- .get(API_URL + "logout",{ headers: authHeader() })
- .then(response => {
- console.log(response)
- });
- }
-
- register(username: string, email: string, password: string) {
- return axios.post(API_URL + "signup", {
+ async updatePassword(
+ username: string,
+ oldpassword: string,
+ newpassword: string,
+ ): Promise {
+ await axiosInstance.post(`${API_URL}updatePassword`, {
username,
- email,
- password
+ oldpassword,
+ newpassword,
});
- }
+ },
getCurrentUser() {
- const userStr = localStorage.getItem("user");
- console.log(userStr)
- if (userStr) return JSON.parse(userStr);
+ return useAuthStore.getState().user;
+ },
+};
- return null;
- }
-}
+export default authService;
-export default new AuthService();
diff --git a/cyynote-frontend/src/services/note.service.ts b/cyynote-frontend/src/services/note.service.ts
index ff920f9..7d654c5 100644
--- a/cyynote-frontend/src/services/note.service.ts
+++ b/cyynote-frontend/src/services/note.service.ts
@@ -1,54 +1,68 @@
-import axios from 'axios';
-import authHeader from './auth-header';
+import axiosInstance from '../lib/axios';
+import type { ApiResponse, INote, ITreeNode } from '../types';
-// const API_URL = 'http://localhost:8080/api/note/';
-const API_URL = '/api/note/';
-class NoteService {
- getAllContent() {
- return axios.get(API_URL + 'all',{ headers: authHeader() });
- }
+const API_URL = 'note/';
- get(id) {
- return axios.get(API_URL + 'get?id='+id, { headers: authHeader() });
- }
+const noteService = {
+ getAllContent(): Promise> {
+ return axiosInstance.get(API_URL + 'all').then((r) => r.data);
+ },
- deleteBack(id) {
- return axios.get(API_URL + 'deleteBack?id='+id, { headers: authHeader() });
- }
+ get(id: number): Promise> {
+ return axiosInstance
+ .get(API_URL + 'get', { params: { id } })
+ .then((r) => r.data);
+ },
+ update(id: number, title: string, context: string): Promise> {
+ return axiosInstance
+ .post(API_URL + 'update', { id, title, context })
+ .then((r) => r.data);
+ },
- historyQueryOrDelete(flag) {
- return axios.get(API_URL + 'historyQueryOrDelete?flag='+flag, { headers: authHeader() });
- }
+ delete(id: number): Promise> {
+ return axiosInstance
+ .post(API_URL + 'delete', { id })
+ .then((r) => r.data);
+ },
- dataHome(type,search) {
- return axios.get(API_URL + 'home?type='+type+'&search='+search, { headers: authHeader() });
- }
+ add(pid: number): Promise> {
+ return axiosInstance
+ .get(API_URL + 'add', { params: { pid } })
+ .then((r) => r.data);
+ },
- update(id: bigint, title: string, context: string) {
- return axios.post(API_URL + "update", {
- id,
- title,
- context
- },{ headers: authHeader() });
- }
+ /**
+ * Restore a note from trash.
+ * @todo Backend: rename to DELETE /note/trash/:id for correct REST semantics.
+ */
+ restoreFromTrash(id: number): Promise> {
+ return axiosInstance
+ .get(API_URL + 'deleteBack', { params: { id } })
+ .then((r) => r.data);
+ },
+ /**
+ * Query view history count or delete all history.
+ * @todo Backend: split into GET /note/history/count and DELETE /note/history.
+ */
+ historyQueryOrDelete(flag: number): Promise> {
+ return axiosInstance
+ .get(API_URL + 'historyQueryOrDelete', { params: { flag } })
+ .then((r) => r.data);
+ },
- delete(id: bigint) {
- return axios.post(API_URL + "delete", {
- id
- },{ headers: authHeader() });
- }
+ /**
+ * Fetch home list by type and optional search query.
+ * type: '0'=search, '1'=recently updated, '2'=newly added,
+ * '3'=recently viewed, '4'=deleted/trash list
+ */
+ getHomeData(type: string, search: string): Promise> {
+ return axiosInstance
+ .get(API_URL + 'home', { params: { type, search } })
+ .then((r) => r.data);
+ },
+};
- add(pid: bigint) {
- if(pid){
- return axios.get(API_URL + 'add?pid='+pid, { headers: authHeader() });
- }else{
- return axios.get(API_URL + 'add?pid=0', { headers: authHeader() });
- }
+export default noteService;
- }
-
-}
-
-export default new NoteService();
diff --git a/cyynote-frontend/src/services/user.service.ts b/cyynote-frontend/src/services/user.service.ts
index 4e7a1c7..f3f996c 100644
--- a/cyynote-frontend/src/services/user.service.ts
+++ b/cyynote-frontend/src/services/user.service.ts
@@ -1,24 +1,25 @@
-import axios from 'axios';
-import authHeader from './auth-header';
+import axiosInstance from '../lib/axios';
+import type { ApiResponse } from '../types';
-const API_URL = 'http://localhost:8080/api/test/';
+const API_URL = 'test/';
-class UserService {
- getPublicContent() {
- return axios.get(API_URL + 'all');
- }
+const userService = {
+ getPublicContent(): Promise> {
+ return axiosInstance.get(API_URL + 'all').then((r) => r.data);
+ },
- getUserBoard() {
- return axios.get(API_URL + 'user', { headers: authHeader() });
- }
+ getUserBoard(): Promise> {
+ return axiosInstance.get(API_URL + 'user').then((r) => r.data);
+ },
- getModeratorBoard() {
- return axios.get(API_URL + 'mod', { headers: authHeader() });
- }
+ getModeratorBoard(): Promise> {
+ return axiosInstance.get(API_URL + 'mod').then((r) => r.data);
+ },
- getAdminBoard() {
- return axios.get(API_URL + 'admin', { headers: authHeader() });
- }
-}
+ getAdminBoard(): Promise> {
+ return axiosInstance.get(API_URL + 'admin').then((r) => r.data);
+ },
+};
+
+export default userService;
-export default new UserService();
diff --git a/cyynote-frontend/src/stores/useAuthStore.ts b/cyynote-frontend/src/stores/useAuthStore.ts
new file mode 100644
index 0000000..36ab683
--- /dev/null
+++ b/cyynote-frontend/src/stores/useAuthStore.ts
@@ -0,0 +1,29 @@
+import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
+import type { IUser } from '../types';
+
+interface AuthState {
+ user: IUser | null;
+ token: string | null;
+ setAuth: (user: IUser, token: string) => void;
+ logout: () => void;
+}
+
+export const useAuthStore = create()(
+ persist(
+ (set) => ({
+ user: null,
+ token: null,
+ setAuth: (user, token) => set({ user, token }),
+ logout: () => {
+ set({ user: null, token: null });
+ window.location.href = '/login';
+ },
+ }),
+ {
+ name: 'cyynote-auth',
+ // sessionStorage: token cleared when browser tab closes
+ storage: createJSONStorage(() => sessionStorage),
+ },
+ ),
+);
diff --git a/cyynote-frontend/src/stores/useNoteStore.ts b/cyynote-frontend/src/stores/useNoteStore.ts
new file mode 100644
index 0000000..8273588
--- /dev/null
+++ b/cyynote-frontend/src/stores/useNoteStore.ts
@@ -0,0 +1,34 @@
+import { create } from 'zustand';
+import type { INote, ITreeNode } from '../types';
+
+interface NoteState {
+ currentNoteId: number | null;
+ currentTitle: string;
+ currentContent: string | null;
+ treeData: ITreeNode[];
+ homeData: INote[];
+ viewData: INote[];
+ setCurrentNote: (id: number, title: string, content: string) => void;
+ setTreeData: (data: ITreeNode[]) => void;
+ setTitle: (title: string) => void;
+ setHomeData: (data: INote[]) => void;
+ setViewData: (data: INote[]) => void;
+ clearCurrentNote: () => void;
+}
+
+export const useNoteStore = create()((set) => ({
+ currentNoteId: null,
+ currentTitle: '',
+ currentContent: null,
+ treeData: [],
+ homeData: [],
+ viewData: [],
+ setCurrentNote: (id, title, content) =>
+ set({ currentNoteId: id, currentTitle: title, currentContent: content }),
+ setTreeData: (treeData) => set({ treeData }),
+ setTitle: (title) => set({ currentTitle: title }),
+ setHomeData: (homeData) => set({ homeData }),
+ setViewData: (viewData) => set({ viewData }),
+ clearCurrentNote: () =>
+ set({ currentNoteId: null, currentTitle: '', currentContent: null }),
+}));
diff --git a/cyynote-frontend/src/stores/useUIStore.ts b/cyynote-frontend/src/stores/useUIStore.ts
new file mode 100644
index 0000000..64ced32
--- /dev/null
+++ b/cyynote-frontend/src/stores/useUIStore.ts
@@ -0,0 +1,50 @@
+import { create } from 'zustand';
+
+export type ActiveView = 'home' | 'note' | 'settings' | 'search';
+
+interface UIState {
+ activeView: ActiveView;
+ sideVisible: boolean;
+ siderFlag: boolean;
+ isNoteLoading: boolean;
+ deleteVisible: boolean;
+ deleteId: number | null;
+ deleteNoteName: string;
+ deleteModelVisible: boolean;
+ updatePasswordModelVisible: boolean;
+ searchQuery: string;
+ setActiveView: (view: ActiveView) => void;
+ setSideVisible: (visible: boolean) => void;
+ setSiderFlag: (flag: boolean) => void;
+ setNoteLoading: (loading: boolean) => void;
+ openDeleteConfirm: (id: number, name: string) => void;
+ closeDeleteConfirm: () => void;
+ setDeleteModelVisible: (visible: boolean) => void;
+ setUpdatePasswordModelVisible: (visible: boolean) => void;
+ setSearchQuery: (query: string) => void;
+}
+
+export const useUIStore = create()((set) => ({
+ activeView: 'home',
+ sideVisible: false,
+ siderFlag: true,
+ isNoteLoading: false,
+ deleteVisible: false,
+ deleteId: null,
+ deleteNoteName: '',
+ deleteModelVisible: false,
+ updatePasswordModelVisible: false,
+ searchQuery: '',
+ setActiveView: (activeView) => set({ activeView }),
+ setSideVisible: (sideVisible) => set({ sideVisible }),
+ setSiderFlag: (siderFlag) => set({ siderFlag }),
+ setNoteLoading: (isNoteLoading) => set({ isNoteLoading }),
+ openDeleteConfirm: (deleteId, deleteNoteName) =>
+ set({ deleteVisible: true, deleteId, deleteNoteName }),
+ closeDeleteConfirm: () =>
+ set({ deleteVisible: false, deleteId: null, deleteNoteName: '' }),
+ setDeleteModelVisible: (deleteModelVisible) => set({ deleteModelVisible }),
+ setUpdatePasswordModelVisible: (updatePasswordModelVisible) =>
+ set({ updatePasswordModelVisible }),
+ setSearchQuery: (searchQuery) => set({ searchQuery }),
+}));
diff --git a/cyynote-frontend/src/types/index.ts b/cyynote-frontend/src/types/index.ts
new file mode 100644
index 0000000..987c97b
--- /dev/null
+++ b/cyynote-frontend/src/types/index.ts
@@ -0,0 +1,36 @@
+export type UserRole = 'ROLE_USER' | 'ROLE_MODERATOR' | 'ROLE_ADMIN';
+
+export interface IUser {
+ id: number;
+ username: string;
+ email: string;
+ roles: UserRole[];
+}
+
+export interface IAuthResponse {
+ accessToken: string;
+ tokenType: string;
+ user: IUser;
+}
+
+export interface INote {
+ id: number;
+ pid: number;
+ title: string;
+ context: string;
+ createtime: string;
+ updatetime: string;
+ viewtime: string;
+}
+
+export interface ITreeNode {
+ key: number;
+ label: string;
+ children?: ITreeNode[];
+}
+
+export interface ApiResponse {
+ code: number;
+ message: string;
+ data: T;
+}
diff --git a/cyynote-frontend/tsconfig.json b/cyynote-frontend/tsconfig.json
index a7fc6fb..8c7ac16 100644
--- a/cyynote-frontend/tsconfig.json
+++ b/cyynote-frontend/tsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "target": "ES2020",
+ "target": "ES2022",
"useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
@@ -11,9 +11,16 @@
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
+ "verbatimModuleSyntax": true,
"noEmit": true,
"jsx": "react-jsx",
+ /* Path aliases */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+
/* Linting */
"strict": true,
"noUnusedLocals": true,
diff --git a/cyynote-frontend/vite.config.ts b/cyynote-frontend/vite.config.ts
index 3324658..4a46b6f 100644
--- a/cyynote-frontend/vite.config.ts
+++ b/cyynote-frontend/vite.config.ts
@@ -1,19 +1,51 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
-export default defineConfig({
+export default defineConfig(({ command }) => ({
plugins: [react()],
- server:{
- proxy:{
- //此处编写代理规则
- "/api":{
- target:"http://localhost:8080",
- changeOrigin:true,
- rewrite:path=>{
- return path.replace(/\/api/,'api/');
- }
- }
- }
- }
-})
+ resolve: {
+ // new URL is pure ESM — no __dirname needed, no @types/node required
+ alias: { '@': new URL('./src', import.meta.url).pathname },
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ api: 'modern-compiler',
+ },
+ },
+ },
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ // no rewrite: /api/... proxied as-is to http://localhost:8080/api/...
+ },
+ },
+ },
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ lexical: [
+ 'lexical',
+ '@lexical/react',
+ '@lexical/rich-text',
+ '@lexical/list',
+ '@lexical/code',
+ '@lexical/table',
+ ],
+ semi: ['@douyinfe/semi-ui', '@douyinfe/semi-icons'],
+ excalidraw: ['@excalidraw/excalidraw'],
+ vendor: ['react', 'react-dom', 'react-router', 'axios'],
+ yjs: ['yjs', 'y-websocket'],
+ },
+ },
+ },
+ },
+ esbuild: {
+ // strip console.* and debugger in production builds
+ drop: command === 'build' ? (['console', 'debugger'] as const) : [],
+ },
+}));