From c764603dbd14b4d9b9265ab7e1378e2454f06fe8 Mon Sep 17 00:00:00 2001 From: Chuyaoyuan Date: Thu, 30 Apr 2026 18:53:50 +0800 Subject: [PATCH] update --- README.md | 272 +++++++++++ cyynote-backend/pom.xml | 22 +- .../cyynote/controller/AuthController.java | 251 ++++++----- .../cyynote/controller/JwtInterceptor.java | 83 ++-- .../cyynote/controller/NoteController.java | 426 +++++++++--------- .../src/main/resources/app-dev.yml | 2 +- .../src/main/resources/db/schema.sql | 105 ++--- cyynote-frontend/package.json | 1 - .../src/components/board-admin.component.tsx | 65 +-- .../components/board-moderator.component.tsx | 65 +-- .../src/components/board-user.component.tsx | 65 +-- .../src/components/modals/PasswordModal.tsx | 8 +- .../src/components/modals/TrashListModal.tsx | 8 +- .../src/components/profile.component.tsx | 94 ++-- .../src/components/sidebar/NoteTree.tsx | 10 +- cyynote-frontend/src/pages/home/HomePage.tsx | 3 + cyynote-frontend/src/pages/note-page.tsx | 10 +- cyynote-frontend/src/stores/useUIStore.ts | 18 +- cyynote-frontend/src/types/index.ts | 5 +- cyynote-frontend/tsconfig.json | 7 +- 20 files changed, 866 insertions(+), 654 deletions(-) diff --git a/README.md b/README.md index e69de29..ac1b718 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,272 @@ +# CyyNote + +CyyNote 是一个前后端分离的在线笔记系统,后端基于 Solon + Wood + SQLite 提供鉴权、笔记树、笔记编辑、回收站和浏览历史接口;前端基于 React + Vite + Semi UI + Lexical 提供笔记管理与富文本编辑体验。 + +## 技术栈 + +### 前端 + +- React 18 +- TypeScript 5.8 +- Vite 6 +- React Router 7 +- Zustand 5:客户端状态管理 +- Axios:统一 HTTP 客户端与鉴权拦截器 +- Semi UI 2.96:页面组件库 +- Lexical 0.44:富文本编辑器 +- Yjs + y-websocket:协作编辑基础能力 +- pnpm 9:包管理器 +- ESLint 9 Flat Config + +### 后端 + +- JDK 17 +- Solon 3.0.5 +- Wood ORM / wood-solon-plugin +- SQLite JDBC 3.46 +- HikariCP +- solon-sessionstate-jwt:JWT Session +- Fastjson2 +- Hutool +- Maven + +## 目录结构 + +```text +CyyNote/ +├── cyynote-backend/ # Solon 后端工程 +│ ├── pom.xml +│ └── src/main/ +│ ├── java/com/cyynote/ +│ │ ├── controller/ # Auth / Note / Test 控制器 +│ │ ├── dso/ # DB 初始化、Wood Mapper +│ │ ├── model/ # 数据模型 +│ │ ├── payload/ # 请求/响应 DTO +│ │ └── util/ # 笔记树构建工具 +│ └── resources/ +│ ├── app.yml # 通用配置 +│ ├── app-dev.yml # 开发环境配置 +│ ├── app-pro.yml # 生产环境配置 +│ └── db/schema.sql # SQLite 初始化脚本 +└── cyynote-frontend/ # React 前端工程 + ├── package.json + ├── vite.config.ts + └── src/ + ├── components/ # 页面组件、Lexical 编辑器、弹窗、侧栏 + ├── pages/ # 路由页面 + ├── services/ # API Service + ├── stores/ # Zustand Store + ├── lib/axios.ts # Axios 实例与拦截器 + └── types/ # 前端类型定义 +``` + +## 环境要求 + +| 项目 | 要求 | +| --- | --- | +| JDK | 17+ | +| Maven | 3.8+ | +| Node.js | 20+ | +| pnpm | 9+ | + +后端 Maven 编译目标已设置为 Java 17;前端 `package.json` 中声明了 Node 20 与 pnpm 9。 + +## 后端配置与运行 + +### 数据库配置 + +开发环境默认使用当前运行目录下的 SQLite 文件: + +```yaml +test.db1: + jdbcUrl: "jdbc:sqlite:./cyynote.db" + driverClassName: "org.sqlite.JDBC" +``` + +首次启动时,如果数据库中不存在 `appx` 表,后端会执行 `src/main/resources/db/schema.sql` 初始化基础表结构和示例数据。 + +默认账号: + +| 用户名 | 密码 | 角色 | +| --- | --- | --- | +| admin | cyy123 | ROLE_ADMIN | +| test | test | ROLE_USER | + +### 启动后端 + +```bash +cd cyynote-backend +mvn clean package +java -jar target/cyynote.jar +``` + +默认服务端口:`8080`。 + +### 后端关键接口 + +接口统一以 `/api` 为前缀。 + +#### 鉴权接口 + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| POST | `/api/auth/signin` | 登录,返回 `accessToken`、`tokenType` 和 `user` | +| GET | `/api/auth/logout` | 清理服务端 Session | +| POST | `/api/auth/signup` | 注册普通用户 | +| POST | `/api/auth/updatePassword` | 修改密码 | + +登录成功响应中的 `data` 结构: + +```json +{ + "accessToken": "...", + "tokenType": "Bearer", + "user": { + "id": 1, + "username": "admin", + "email": "aaaaaaaaa", + "roles": ["ROLE_ADMIN"] + } +} +``` + +#### 笔记接口 + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| GET | `/api/note/all` | 获取笔记树 | +| GET | `/api/note/home?type=1&search=0` | 首页列表、搜索、回收站列表 | +| GET | `/api/note/get?id=1` | 获取笔记详情,同时更新浏览时间 | +| GET | `/api/note/add?pid=0` | 新增笔记 | +| POST | `/api/note/update` | 更新笔记标题和内容 | +| POST | `/api/note/delete` | 软删除笔记 | +| GET | `/api/note/deleteBack?id=1` | 从回收站恢复笔记 | +| GET | `/api/note/historyQueryOrDelete?flag=0` | 查询或清理历史记录 | + +`/api/note/home` 的 `type` 参数含义: + +| type | 说明 | +| --- | --- | +| 0 | 搜索 | +| 1 | 最近更新 | +| 2 | 最新添加 | +| 3 | 最近浏览 | +| 4 | 回收站 | + +## 前端配置与运行 + +### 环境变量 + +复制示例配置: + +```bash +cd cyynote-frontend +cp .env.example .env +``` + +常用配置: + +```env +VITE_API_BASE_URL=/api +VITE_WS_URL=ws://localhost:1234 +``` + +开发模式下,Vite 已配置 `/api` 代理到 `http://localhost:8080`。 + +### 安装与启动 + +```bash +cd cyynote-frontend +pnpm install +pnpm dev +``` + +### 构建与检查 + +```bash +pnpm typecheck +pnpm lint +pnpm build +``` + +## 前端架构说明 + +### 路由 + +前端入口使用 `BrowserRouter`。主要路由: + +| 路径 | 页面 | 说明 | +| --- | --- | --- | +| `/login` | SignIn | 登录页 | +| `/register` | Register | 注册页 | +| `/home` | NotePage | 笔记主界面 | +| `/profile` | Profile | 用户信息 | +| `/user` | BoardUser | 用户权限测试页 | +| `/mod` | BoardModerator | Moderator 权限测试页 | +| `/admin` | BoardAdmin | Admin 权限测试页 | + +除登录和注册外,其他路由由 `ProtectedRoute` 保护;未登录用户会跳转到 `/login`。 + +### 状态管理 + +Zustand Store 分为三类: + +- `useAuthStore`:用户信息、JWT Token、登录状态持久化到 `sessionStorage` +- `useNoteStore`:当前笔记、笔记树、首页列表、最近浏览列表 +- `useUIStore`:当前视图、侧栏状态、搜索条件、弹窗显隐状态 + +### HTTP 客户端 + +`src/lib/axios.ts` 创建统一 Axios 实例: + +- `baseURL` 读取 `VITE_API_BASE_URL`,默认 `/api` +- 请求拦截器自动添加 `Authorization: Bearer ` +- 响应拦截器在 HTTP 401 时自动登出并跳转登录页 + +### 编辑器 + +笔记编辑器基于 Lexical。`NoteEditor` 负责标题输入与编辑器挂载,`LexicalApp` 负责富文本编辑能力。协作相关配置位于 `components/lexical/collaboration.ts`,WebSocket 地址由 `VITE_WS_URL` 控制。 + +## 本次升级与修复说明 + +本次修复覆盖前端、后端和 JDK 17 运行兼容性: + +- 后端 Maven 编译目标升级为 Java 17 +- 修复后端登录返回结构,使其与前端 `IAuthResponse` 匹配 +- 修复登录失败时空指针问题,并返回 HTTP 401 +- 修复注册接口只返回空对象的问题,改为实际创建普通用户 +- 修复修改密码接口用户不存在时空指针问题 +- 修复 JWT 拦截器在 401 后仍继续执行 Controller 的问题 +- 修复软删除接口把标题更新成 `null` 的问题 +- 修复历史表缺失 `flag` 字段的问题 +- 修复开发环境 SQLite 路径在 Linux 下不可用的问题 +- 修复前端 Profile 页面访问不存在 `accessToken` 字段导致崩溃的问题 +- 修复首页点击“查看”后未切换到笔记视图的问题 +- 修复 Board 页面仍使用旧 service 返回结构的问题 +- 修复回收站和密码弹窗状态字段 `Model` / `Modal` 拼写不一致问题 +- 修复前端笔记树 `key` 类型与后端返回值不一致的问题 +- 清理未使用的 `@tanstack/react-query` 依赖声明 + +## 验证建议 + +网络正常后建议执行: + +```bash +cd cyynote-backend +mvn clean package + +cd ../cyynote-frontend +pnpm install +pnpm typecheck +pnpm lint +pnpm build +``` + +如果 CI 使用 `pnpm install --frozen-lockfile`,需要提交 `pnpm-lock.yaml`。 + +## 注意事项 + +- 当前密码仍为明文存储,生产环境应改为 BCrypt/Argon2 等不可逆哈希。 +- Lexical 从旧版本升级到 0.44 跨度较大,建议重点回归测试自定义 Node、Plugin、序列化/反序列化和保存逻辑。 +- y-websocket 3.x 与旧版存在差异,协作编辑功能需要单独联调。 +- SQLite 适合单机和轻量部署;多人协作、高并发或云部署建议迁移到 MySQL/PostgreSQL。 diff --git a/cyynote-backend/pom.xml b/cyynote-backend/pom.xml index d2e8d55..bcc0643 100644 --- a/cyynote-backend/pom.xml +++ b/cyynote-backend/pom.xml @@ -20,7 +20,11 @@ - 11 + 17 + ${java.version} + ${java.version} + ${java.version} + UTF-8 @@ -75,14 +79,6 @@ 3.46.1.3 - - - org.projectlombok - lombok - - provided - - org.noear solon-test @@ -96,6 +92,14 @@ ${project.artifactId} + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${project.build.sourceEncoding} + + org.noear solon-maven-plugin diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java b/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java index baba29e..bf5530e 100644 --- a/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java +++ b/cyynote-backend/src/main/java/com/cyynote/controller/AuthController.java @@ -1,103 +1,148 @@ -package com.cyynote.controller; - - -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson2.JSONObject; -import com.cyynote.controller.BaseController; -import com.cyynote.model.NoteModel; -import com.cyynote.model.UserModel; -import com.cyynote.payload.request.LoginRequest; -import com.cyynote.payload.request.SignupRequest; -import java.sql.SQLException; -import java.util.List; -import java.util.logging.Logger; - -import com.cyynote.payload.request.UpdatePasswordRequest; -import org.noear.solon.annotation.Body; -import org.noear.solon.annotation.Controller; -import org.noear.solon.annotation.Mapping; -import org.noear.solon.annotation.Post; -import org.noear.solon.core.handle.Context; -import org.noear.solon.core.handle.Result; -import org.noear.wood.DbContext; -import org.noear.wood.DbTableQuery; -import org.noear.wood.annotation.Db; - -@Mapping("/api/auth") -@Controller -public class AuthController extends BaseController { - private static final Logger log = Logger.getLogger(com.cyynote.controller.AuthController.class.getName()); - - @Db - DbContext db; - - @Mapping("/logout") - public void logout(Context ctx) { - ctx.sessionClear(); - } - - @Post - @Mapping("/signin") - public Result signin(Context ctx, @Body LoginRequest loginRequest) throws SQLException { - log.info("name:" + loginRequest.getUsername()); - log.info("password:" + loginRequest.getPassword()); - - long count = db.table("user").selectCount(); - log.info("countUser:" + count); - List all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); - log.info( all.toString()); - - UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getPassword())).selectItem("*", UserModel.class); - if ("admin".equals(model.getUsername())) { - ctx.sessionSet("user_name", loginRequest.getUsername()); - ctx.sessionSet("user_id", model.getId()); - JSONObject obj = new JSONObject(); - String token = ctx.sessionState().sessionToken(); - obj.put("accessToken", token); - obj.put("id", Integer.valueOf(1)); - obj.put("username", loginRequest.getUsername()); - obj.put("email", "1"); - obj.put("roles", "1"); - int flag = ((DbTableQuery)this.db.table("user").set("token", token).set("logintime", DateUtil.now()).whereEq("id", model.getId())).update(); - System.out.println(flag); - return Result.succeed(obj); - } - return Result.failure(); - } - - - @Post - @Mapping("/updatePassword") - public Result updatePassword(Context ctx, @Body UpdatePasswordRequest loginRequest) throws SQLException { - log.info("name:" + loginRequest.getUsername()); - log.info("old - password:" + loginRequest.getOldpassword()); - log.info("new - password:" + loginRequest.getNewpassword()); - - - long count = db.table("user").selectCount(); - log.info("countUser:" + count); - List all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); - log.info( all.toString()); - - - UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getOldpassword())).selectItem("*", UserModel.class); - if ("admin".equals(model.getUsername())) { - - int flag = ((DbTableQuery)this.db.table("user").set("password", loginRequest.getNewpassword()).whereEq("id", model.getId())).update(); - System.out.println(flag); - return Result.succeed(flag); - } - return Result.failure(); - } - - - - @Post - @Mapping("/signup") - public Result signup(@Body SignupRequest signUpRequest) { - log.info("signUpRequest:" + JSONObject.toJSONString(signUpRequest, new com.alibaba.fastjson2.JSONWriter.Feature[0])); - JSONObject obj = new JSONObject(); - return Result.succeed(obj); - } -} - +package com.cyynote.controller; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONObject; +import com.cyynote.model.UserModel; +import com.cyynote.payload.request.LoginRequest; +import com.cyynote.payload.request.SignupRequest; +import com.cyynote.payload.request.UpdatePasswordRequest; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; +import org.noear.solon.annotation.Body; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Post; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Result; +import org.noear.wood.DbContext; +import org.noear.wood.DbTableQuery; +import org.noear.wood.annotation.Db; + +@Mapping("/api/auth") +@Controller +public class AuthController extends BaseController { + private static final Logger log = Logger.getLogger(AuthController.class.getName()); + + @Db + DbContext db; + + @Mapping("/logout") + public void logout(Context ctx) { + ctx.sessionClear(); + } + + @Post + @Mapping("/signin") + public Result signin(Context ctx, @Body LoginRequest loginRequest) throws SQLException { + if (loginRequest == null || loginRequest.getUsername() == null || loginRequest.getPassword() == null) { + ctx.status(400); + return Result.failure(400, "用户名和密码不能为空"); + } + + log.info("signin username:" + loginRequest.getUsername()); + UserModel model = (UserModel) ((DbTableQuery) ((DbTableQuery) this.db.table("user") + .whereEq("username", loginRequest.getUsername())) + .andEq("password", loginRequest.getPassword())) + .selectItem("*", UserModel.class); + + if (model == null) { + ctx.status(401); + return Result.failure(401, "用户名或密码错误"); + } + + ctx.sessionSet("user_name", model.getUsername()); + ctx.sessionSet("user_id", model.getId()); + String token = ctx.sessionState().sessionToken(); + ((DbTableQuery) this.db.table("user") + .set("token", token) + .set("logintime", DateUtil.now()) + .whereEq("id", model.getId())) + .update(); + + JSONObject user = new JSONObject(); + user.put("id", model.getId()); + user.put("username", model.getUsername()); + user.put("email", model.getEmail()); + user.put("roles", normalizeRoles(model.getRole())); + + JSONObject payload = new JSONObject(); + payload.put("accessToken", token); + payload.put("tokenType", "Bearer"); + payload.put("user", user); + + return Result.succeed(payload); + } + + @Post + @Mapping("/updatePassword") + public Result updatePassword(Context ctx, @Body UpdatePasswordRequest loginRequest) throws SQLException { + if (loginRequest == null || loginRequest.getUsername() == null || loginRequest.getOldpassword() == null || loginRequest.getNewpassword() == null) { + ctx.status(400); + return Result.failure(400, "参数不完整"); + } + + log.info("update password username:" + loginRequest.getUsername()); + UserModel model = (UserModel) ((DbTableQuery) ((DbTableQuery) this.db.table("user") + .whereEq("username", loginRequest.getUsername())) + .andEq("password", loginRequest.getOldpassword())) + .selectItem("*", UserModel.class); + if (model == null) { + ctx.status(400); + return Result.failure(400, "用户名或旧密码错误"); + } + + int flag = ((DbTableQuery) this.db.table("user") + .set("password", loginRequest.getNewpassword()) + .whereEq("id", model.getId())) + .update(); + return Result.succeed(flag); + } + + @Post + @Mapping("/signup") + public Result signup(Context ctx, @Body SignupRequest signUpRequest) throws SQLException { + log.info("signup username:" + (signUpRequest == null ? null : signUpRequest.getUsername())); + if (signUpRequest == null || signUpRequest.getUsername() == null || signUpRequest.getPassword() == null || signUpRequest.getEmail() == null) { + ctx.status(400); + return Result.failure(400, "用户名、邮箱和密码不能为空"); + } + + long exists = ((DbTableQuery) this.db.table("user") + .whereEq("username", signUpRequest.getUsername())) + .selectCount(); + if (exists > 0) { + ctx.status(409); + return Result.failure(409, "用户名已存在"); + } + + UserModel user = new UserModel(); + user.setUsername(signUpRequest.getUsername()); + user.setEmail(signUpRequest.getEmail()); + user.setPassword(signUpRequest.getPassword()); + user.setRole("ROLE_USER"); + this.db.table("user").setEntityIf(user, (k, v) -> Boolean.valueOf(v != null)).insert(); + return Result.succeed("注册成功"); + } + + private List normalizeRoles(String role) { + if (role == null || role.isBlank()) { + return List.of("ROLE_USER"); + } + if (role.startsWith("ROLE_")) { + return List.of(role); + } + if ("admin".equalsIgnoreCase(role)) { + return List.of("ROLE_ADMIN"); + } + if ("moderator".equalsIgnoreCase(role) || "mod".equalsIgnoreCase(role)) { + return List.of("ROLE_MODERATOR"); + } + if (role.contains(",")) { + return Arrays.asList(role.split(",")); + } + return List.of("ROLE_USER"); + } +} + diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java b/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java index e9cc563..2872999 100644 --- a/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java +++ b/cyynote-backend/src/main/java/com/cyynote/controller/JwtInterceptor.java @@ -1,44 +1,39 @@ -package com.cyynote.controller; - - -import io.jsonwebtoken.Claims; -import org.noear.solon.annotation.Component; -import org.noear.solon.core.handle.Context; -import org.noear.solon.core.handle.Handler; -import org.noear.solon.core.handle.Result; -import org.noear.solon.core.route.RouterInterceptor; -import org.noear.solon.core.route.RouterInterceptorChain; -import org.noear.solon.sessionstate.jwt.JwtUtils; - -@Component -public class JwtInterceptor implements RouterInterceptor { - public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { - boolean flag = true; - if ("/api/auth/signup".equals(ctx.path()) || - "/api/auth/signin".equals(ctx.path()) || - "/demo".equals(ctx.path()) || - "/api/auth/logout".equals(ctx.path()) - - ) - flag = false; - if (flag) - if (null != ctx.header("Authorization")) { - String token = ctx.header("Authorization"); - token = token.replace("Bearer ", ""); - System.out.println("token:" + token); - Claims claims = JwtUtils.parseJwt(token); - System.out.println("claims:" + claims); - if (null != claims) { - System.out.println("username:" + claims.get("user_name")); - System.out.println("exp:" + claims.get("exp")); - } else { - ctx.status(401); - ctx.render(Result.failure(401, "")); - } - } else { - ctx.status(401); - ctx.render(Result.failure(401, "")); - } - chain.doIntercept(ctx, mainHandler); - } -} +package com.cyynote.controller; + +import io.jsonwebtoken.Claims; +import org.noear.solon.annotation.Component; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Handler; +import org.noear.solon.core.handle.Result; +import org.noear.solon.core.route.RouterInterceptor; +import org.noear.solon.core.route.RouterInterceptorChain; +import org.noear.solon.sessionstate.jwt.JwtUtils; + +@Component +public class JwtInterceptor implements RouterInterceptor { + public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { + boolean requireAuth = !("/api/auth/signup".equals(ctx.path()) + || "/api/auth/signin".equals(ctx.path()) + || "/demo".equals(ctx.path()) + || "/api/auth/logout".equals(ctx.path()) + || "OPTIONS".equalsIgnoreCase(ctx.method())); + + if (requireAuth) { + String token = ctx.header("Authorization"); + if (token == null || token.isBlank()) { + ctx.status(401); + ctx.render(Result.failure(401, "未登录或登录已过期")); + return; + } + + token = token.replace("Bearer ", ""); + Claims claims = JwtUtils.parseJwt(token); + if (claims == null) { + ctx.status(401); + ctx.render(Result.failure(401, "未登录或登录已过期")); + return; + } + } + chain.doIntercept(ctx, mainHandler); + } +} diff --git a/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java b/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java index 50e8284..5a58aa7 100644 --- a/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java +++ b/cyynote-backend/src/main/java/com/cyynote/controller/NoteController.java @@ -1,207 +1,219 @@ -package com.cyynote.controller; - -import cn.hutool.*; -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; -import com.alibaba.fastjson2.JSONObject; -import com.cyynote.controller.BaseController; -import com.cyynote.dso.NoteSqlAnnotation; -import com.cyynote.model.HistoryModel; -import com.cyynote.model.NoteModel; -import com.cyynote.payload.request.NoteRequest; -import com.cyynote.util.TreeBuild; -import com.cyynote.util.TreeNode; -import io.jsonwebtoken.Claims; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.logging.Logger; -import org.noear.solon.annotation.Body; -import org.noear.solon.annotation.Controller; -import org.noear.solon.annotation.Get; -import org.noear.solon.annotation.Mapping; -import org.noear.solon.annotation.Path; -import org.noear.solon.annotation.Post; -import org.noear.solon.annotation.Put; -import org.noear.solon.core.handle.Context; -import org.noear.solon.core.handle.Result; -import org.noear.solon.sessionstate.jwt.JwtUtils; -import org.noear.solon.web.cors.annotation.CrossOrigin; -import org.noear.wood.DbContext; -import org.noear.wood.DbTableQuery; -import org.noear.wood.annotation.Db; - -@Mapping("/api/note") -@Controller -public class NoteController extends BaseController { - private static final Logger log = Logger.getLogger(com.cyynote.controller.NoteController.class.getName()); - - @Db - NoteSqlAnnotation mapper; - - @Db - DbContext db; - - private static final String QUERY_F = "id,pid,title,createtime,updatetime,viewtime,flag"; - - @Get - @Mapping("get1") - public String test_post_get(Context context, @Path String appname) { - - return context.path(); - } - - @Post - @Mapping("post") - public String test_post(Context context) { - return context.param("name"); - } - - @Put - @Mapping("put") - public String test_put(Context context, String name) { - return context.param("name"); - } - - @Get - @Mapping("/all") - public Result noteAccess(Context ctx) throws Exception { - List all = ((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - List treeNodeList = new ArrayList<>(); - for (NoteModel n : all) { - TreeNode treeNode = new TreeNode(n.getId().intValue(), n.getPid().intValue(), n.getTitle(), String.valueOf(n.getId()), String.valueOf(n.getId())); - treeNodeList.add(treeNode); - } - TreeBuild bb = new TreeBuild(treeNodeList); - List collect = bb.buildTree(); - return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(collect))); - } - - @Get - @Mapping("/home") - public Result noteHome(Context ctx, @Path int type, @Path String search) throws Exception { - System.out.println(type); - System.out.println(search); - List all = new ArrayList<>(); - if (type == 0) { - all = ((DbTableQuery)((DbTableQuery)this.db.table("note").whereLk("context", "%" + search + "%")).andLk("title", "%" + search + "%")).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - } else if (type == 1) { - all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("updatetime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - } else if (type == 2) { - all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("createtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - } else if (type == 3) { - all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("viewtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - } else if (type == 4) { - all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(0))).orderByDesc("updatetime")).limit(10000)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - } - return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(all))); - } - - @Get - @Mapping("/get") - public Result getNote(Context ctx, @Path int id) throws Exception { - NoteModel model = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", Integer.valueOf(id))).selectItem("*", NoteModel.class); - String time = DateUtil.now(); - System.out.println(time); - int flag = ((DbTableQuery)this.db.table("note").set("viewtime", time).whereEq("id", Integer.valueOf(id))).update(); - System.out.println(flag); - log.info(JSONObject.toJSONString(model, new com.alibaba.fastjson2.JSONWriter.Feature[0])); - return Result.succeed(model); - } - - @Get - @Mapping("/add") - public Result addNote(Context ctx, @Path int pid) throws Exception { - String userid = getUserInfoId(ctx); - if (null == userid) - return Result.failure(401); - NoteModel note = new NoteModel(); - note.setPid(Integer.valueOf(pid)); - note.setTitle("未命名Note"); - note.setFlag(Integer.valueOf(1)); - note.setCreatetime(DateUtil.now()); - note.setContext("{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"未命名Note\",\"type\":\"text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}"); - -// note.setContext("{\"root\": {\"type\": \"root\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [{\"type\": \"paragraph\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [], \"direction\": null}], \"direction\": null}}"); - this.db.table("note").setEntityIf(note, (k, v) -> Boolean.valueOf((v != null))).insert(); - return Result.succeed("ok"); - } - - @Post - @Mapping("/update") - public Result updateNote(@Body NoteRequest noteRequest) throws SQLException { - NoteModel note = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", noteRequest.getId())).selectItem("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); - note.setTitle(noteRequest.getTitle()); - note.setUpdatetime(DateUtil.now()); - String cc = noteRequest.getContext(); - JSONObject ar = JSONObject.parseObject(cc); - note.setContext(ar.toJSONString()); - int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("updatetime", DateUtil.now()).set("context", ar.toJSONString()).whereEq("id", noteRequest.getId())).update(); - log.info("updateNote+ flag"); - HistoryModel history = new HistoryModel(); - history.setNid(Integer.valueOf(noteRequest.getId().intValue())); - history.setTitle(noteRequest.getTitle()); - history.setFlag(Integer.valueOf(1)); - history.setCreatetime(DateUtil.now()); - history.setContext(ar.toJSONString()); - long his = this.db.table("history").setEntityIf(history, (k, v) -> Boolean.valueOf((v != null))).insert(); - log.info("history+ his"); - return Result.succeed("ok"); - } - - @Post - @Mapping("/delete") - public Result deleteNote(@Body NoteRequest noteRequest) throws SQLException { - int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("flag", Integer.valueOf(0)).whereEq("id", noteRequest.getId())).update(); - System.out.println(flag); - return Result.succeed("ok"); - } - - @Get - @Mapping("/deleteBack") - public Result deleteBack(@Path int id) throws Exception { - int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(1)).whereEq("id", Integer.valueOf(id))).update(); - System.out.println(flag); - return Result.succeed("ok"); - } - - @Get - @Mapping("/historyQueryOrDelete") - public Result historyQueryOrDelete(@Path int flag) throws Exception { - - log.info("history+ flag" + flag); - - if(flag==0){ - long count = db.table("history").selectCount(); - log.info("count:" + count); - return Result.succeed(count); - }else if(flag==1){ - int flagdb = db.table("history").whereEq("flag",1).delete(); - log.info("flagdb:" + flagdb); - return Result.succeed(0); - } - return null; - } - - private String getUserInfoId(Context ctx) { - String userId = ""; - if (null != ctx.header("Authorization")) { - String token = ctx.header("Authorization"); - token = token.replace("Bearer ", ""); - System.out.println("token:" + token); - Claims claims = JwtUtils.parseJwt(token); - System.out.println("claims:" + claims); - if (null != claims) { - System.out.println("username:" + claims.get("user_name")); - System.out.println("userid:" + claims.get("user_id")); - System.out.println("exp:" + claims.get("exp")); - userId = claims.get("user_id").toString(); - return userId; - } - return null; - } - return null; - } -} +package com.cyynote.controller; + +import cn.hutool.*; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.cyynote.dso.NoteSqlAnnotation; +import com.cyynote.model.HistoryModel; +import com.cyynote.model.NoteModel; +import com.cyynote.payload.request.NoteRequest; +import com.cyynote.util.TreeBuild; +import com.cyynote.util.TreeNode; +import io.jsonwebtoken.Claims; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import org.noear.solon.annotation.Body; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; +import org.noear.solon.annotation.Path; +import org.noear.solon.annotation.Post; +import org.noear.solon.annotation.Put; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Result; +import org.noear.solon.sessionstate.jwt.JwtUtils; +import org.noear.wood.DbContext; +import org.noear.wood.DbTableQuery; +import org.noear.wood.annotation.Db; + +@Mapping("/api/note") +@Controller +public class NoteController extends BaseController { + private static final Logger log = Logger.getLogger(com.cyynote.controller.NoteController.class.getName()); + + @Db + NoteSqlAnnotation mapper; + + @Db + DbContext db; + + private static final String QUERY_F = "id,pid,title,createtime,updatetime,viewtime,flag"; + + @Get + @Mapping("get1") + public String test_post_get(Context context, @Path String appname) { + + return context.path(); + } + + @Post + @Mapping("post") + public String test_post(Context context) { + return context.param("name"); + } + + @Put + @Mapping("put") + public String test_put(Context context, String name) { + return context.param("name"); + } + + @Get + @Mapping("/all") + public Result noteAccess(Context ctx) throws Exception { + List all = ((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + List treeNodeList = new ArrayList<>(); + for (NoteModel n : all) { + TreeNode treeNode = new TreeNode(n.getId().intValue(), n.getPid().intValue(), n.getTitle(), String.valueOf(n.getId()), String.valueOf(n.getId())); + treeNodeList.add(treeNode); + } + TreeBuild bb = new TreeBuild(treeNodeList); + List collect = bb.buildTree(); + return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(collect))); + } + + @Get + @Mapping("/home") + public Result noteHome(Context ctx, @Path int type, @Path String search) throws Exception { + List all = new ArrayList<>(); + if (type == 0) { + all = ((DbTableQuery)((DbTableQuery)this.db.table("note").whereLk("context", "%" + search + "%")).andLk("title", "%" + search + "%")).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 1) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("updatetime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 2) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("createtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 3) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).orderByDesc("viewtime")).limit(20)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } else if (type == 4) { + all = ((DbTableQuery)((DbTableQuery)((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(0))).orderByDesc("updatetime")).limit(10000)).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + } + return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(all))); + } + + @Get + @Mapping("/get") + public Result getNote(Context ctx, @Path int id) throws Exception { + NoteModel model = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", Integer.valueOf(id))).selectItem("*", NoteModel.class); + if (model == null) { + ctx.status(404); + return Result.failure(404, "笔记不存在"); + } + String time = DateUtil.now(); + ((DbTableQuery)this.db.table("note").set("viewtime", time).whereEq("id", Integer.valueOf(id))).update(); + model.setViewtime(time); + log.info(JSONObject.toJSONString(model, new com.alibaba.fastjson2.JSONWriter.Feature[0])); + return Result.succeed(model); + } + + @Get + @Mapping("/add") + public Result addNote(Context ctx, @Path int pid) throws Exception { + String userid = getUserInfoId(ctx); + if (null == userid) + return Result.failure(401); + NoteModel note = new NoteModel(); + note.setPid(Integer.valueOf(pid)); + note.setTitle("未命名Note"); + note.setFlag(Integer.valueOf(1)); + note.setCreatetime(DateUtil.now()); + note.setUpdatetime(note.getCreatetime()); + note.setViewtime(note.getCreatetime()); + note.setContext("{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"未命名Note\",\"type\":\"text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}"); + +// note.setContext("{\"root\": {\"type\": \"root\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [{\"type\": \"paragraph\", \"format\": \"\", \"indent\": 0, \"version\": 1, \"children\": [], \"direction\": null}], \"direction\": null}}"); + this.db.table("note").setEntityIf(note, (k, v) -> Boolean.valueOf((v != null))).insert(); + return Result.succeed("ok"); + } + + @Post + @Mapping("/update") + public Result updateNote(Context ctx, @Body NoteRequest noteRequest) throws SQLException { + if (noteRequest == null || noteRequest.getId() == null || noteRequest.getTitle() == null || noteRequest.getContext() == null) { + ctx.status(400); + return Result.failure(400, "参数不完整"); + } + NoteModel note = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", noteRequest.getId())).selectItem("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); + if (note == null) { + ctx.status(404); + return Result.failure(404, "笔记不存在"); + } + note.setTitle(noteRequest.getTitle()); + note.setUpdatetime(DateUtil.now()); + String cc = noteRequest.getContext(); + JSONObject ar = JSONObject.parseObject(cc); + note.setContext(ar.toJSONString()); + int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("updatetime", DateUtil.now()).set("context", ar.toJSONString()).whereEq("id", noteRequest.getId())).update(); + log.info("updateNote+ flag"); + HistoryModel history = new HistoryModel(); + history.setNid(Integer.valueOf(noteRequest.getId().intValue())); + history.setTitle(noteRequest.getTitle()); + history.setFlag(Integer.valueOf(1)); + history.setCreatetime(DateUtil.now()); + history.setContext(ar.toJSONString()); + long his = this.db.table("history").setEntityIf(history, (k, v) -> Boolean.valueOf((v != null))).insert(); + log.info("history+ his"); + return Result.succeed("ok"); + } + + @Post + @Mapping("/delete") + public Result deleteNote(Context ctx, @Body NoteRequest noteRequest) throws SQLException { + if (noteRequest == null || noteRequest.getId() == null) { + ctx.status(400); + return Result.failure(400, "笔记ID不能为空"); + } + int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(0)).set("updatetime", DateUtil.now()).whereEq("id", noteRequest.getId())).update(); + if (flag == 0) { + ctx.status(404); + return Result.failure(404, "笔记不存在"); + } + return Result.succeed("ok"); + } + + @Get + @Mapping("/deleteBack") + public Result deleteBack(Context ctx, @Path int id) throws Exception { + int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(1)).set("updatetime", DateUtil.now()).whereEq("id", Integer.valueOf(id))).update(); + if (flag == 0) { + ctx.status(404); + return Result.failure(404, "笔记不存在"); + } + return Result.succeed("ok"); + } + + @Get + @Mapping("/historyQueryOrDelete") + public Result historyQueryOrDelete(@Path int flag) throws Exception { + + log.info("history+ flag" + flag); + + if(flag==0){ + long count = db.table("history").selectCount(); + log.info("count:" + count); + return Result.succeed(count); + }else if(flag==1){ + int flagdb = db.table("history").whereEq("flag",1).delete(); + log.info("flagdb:" + flagdb); + return Result.succeed(0); + } + return Result.failure(400, "不支持的操作类型"); + } + + private String getUserInfoId(Context ctx) { + String userId = ""; + if (null != ctx.header("Authorization")) { + String token = ctx.header("Authorization"); + token = token.replace("Bearer ", ""); + Claims claims = JwtUtils.parseJwt(token); + if (null != claims) { + userId = claims.get("user_id").toString(); + return userId; + } + return null; + } + return null; + } +} diff --git a/cyynote-backend/src/main/resources/app-dev.yml b/cyynote-backend/src/main/resources/app-dev.yml index da27b44..f9a5a65 100644 --- a/cyynote-backend/src/main/resources/app-dev.yml +++ b/cyynote-backend/src/main/resources/app-dev.yml @@ -1,3 +1,3 @@ test.db1: - jdbcUrl: "jdbc:sqlite:D:/cyynote.db" + jdbcUrl: "jdbc:sqlite:./cyynote.db" driverClassName: "org.sqlite.JDBC" \ No newline at end of file diff --git a/cyynote-backend/src/main/resources/db/schema.sql b/cyynote-backend/src/main/resources/db/schema.sql index 5c54a78..90a25e5 100644 --- a/cyynote-backend/src/main/resources/db/schema.sql +++ b/cyynote-backend/src/main/resources/db/schema.sql @@ -1,53 +1,54 @@ - - - - -CREATE TABLE `user` ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - email TEXT, - password TEXT, - username TEXT, - role TEXT, - token TEXT, - logintime TEXT -); -CREATE TABLE `note` ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - pid INTEGER, - title TEXT, - context TEXT, - conjson TEXT, - createtime TEXT, - updatetime TEXT, - viewtime TEXT, - userid INTEGER, - username TEXT, - flag INTEGER DEFAULT 1 -); -CREATE TABLE `history` ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - nid INTEGER, - title TEXT, - context TEXT, - conjson TEXT, - createtime TEXT, - userid INTEGER, - username TEXT -); -CREATE TABLE appx ( - app_id INTEGER PRIMARY KEY AUTOINCREMENT, - app_key TEXT, - akey TEXT, - ugroup_id INTEGER DEFAULT 0, - agroup_id INTEGER, - name TEXT, - note TEXT, - ar_is_setting INTEGER NOT NULL DEFAULT 0, - ar_is_examine INTEGER NOT NULL DEFAULT 0, - ar_examine_ver INTEGER NOT NULL DEFAULT 0, - log_fulltime TEXT -); - -INSERT INTO `user` VALUES ('1', 'aaaaaaaaa', 'cyy123', 'admin', 'admin','',''); -INSERT INTO `user` VALUES ('2', 'aaaaaaaaa', 'test', 'test', 'test','',''); + + + + +CREATE TABLE `user` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT, + password TEXT, + username TEXT, + role TEXT, + token TEXT, + logintime TEXT +); +CREATE TABLE `note` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + updatetime TEXT, + viewtime TEXT, + userid INTEGER, + username TEXT, + flag INTEGER DEFAULT 1 +); +CREATE TABLE `history` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nid INTEGER, + title TEXT, + context TEXT, + conjson TEXT, + createtime TEXT, + userid INTEGER, + username TEXT, + flag INTEGER DEFAULT 1 +); +CREATE TABLE appx ( + app_id INTEGER PRIMARY KEY AUTOINCREMENT, + app_key TEXT, + akey TEXT, + ugroup_id INTEGER DEFAULT 0, + agroup_id INTEGER, + name TEXT, + note TEXT, + ar_is_setting INTEGER NOT NULL DEFAULT 0, + ar_is_examine INTEGER NOT NULL DEFAULT 0, + ar_examine_ver INTEGER NOT NULL DEFAULT 0, + log_fulltime TEXT +); + +INSERT INTO `user` VALUES ('1', 'aaaaaaaaa', 'cyy123', 'admin', 'admin','',''); +INSERT INTO `user` VALUES ('2', 'aaaaaaaaa', 'test', 'test', 'test','',''); INSERT INTO `note` VALUES ('1', '0', 'test', 'test', 'test','','','','1','1','1'); \ No newline at end of file diff --git a/cyynote-frontend/package.json b/cyynote-frontend/package.json index 21d61c4..ebe2de6 100644 --- a/cyynote-frontend/package.json +++ b/cyynote-frontend/package.json @@ -38,7 +38,6 @@ "@lexical/table": "0.44.0", "@lexical/utils": "0.44.0", "@lexical/yjs": "0.44.0", - "@tanstack/react-query": "^5.74.0", "axios": "^1.8.0", "html-react-parser": "^5.2.0", "katex": "^0.16.21", diff --git a/cyynote-frontend/src/components/board-admin.component.tsx b/cyynote-frontend/src/components/board-admin.component.tsx index fa9d06d..a5853e9 100644 --- a/cyynote-frontend/src/components/board-admin.component.tsx +++ b/cyynote-frontend/src/components/board-admin.component.tsx @@ -1,54 +1,23 @@ -import { Component } from "react"; +import { useEffect, useState } from 'react'; +import userService from '../services/user.service'; -import UserService from "../services/user.service"; -import EventBus from "../common/EventBus"; +export default function BoardAdmin() { + const [content, setContent] = useState(''); -type Props = {}; - -type State = { - content: string; -} - -export default class BoardAdmin extends Component { - constructor(props: Props) { - super(props); - - this.state = { - content: "" - }; - } - - componentDidMount() { - UserService.getAdminBoard().then( - response => { - this.setState({ - content: response.data - }); + useEffect(() => { + userService.getAdminBoard().then( + (res) => setContent(String(res.data ?? '')), + (error: unknown) => { + setContent(error instanceof Error ? error.message : String(error)); }, - error => { - this.setState({ - content: - (error.response && - error.response.data && - error.response.data.message) || - error.message || - error.toString() - }); - - if (error.response && error.response.status === 401) { - EventBus.dispatch("logout"); - } - } ); - } + }, []); - render() { - return ( -
-
-

{this.state.content}

-
-
- ); - } + return ( +
+
+

{content}

+
+
+ ); } diff --git a/cyynote-frontend/src/components/board-moderator.component.tsx b/cyynote-frontend/src/components/board-moderator.component.tsx index be468e2..471030c 100644 --- a/cyynote-frontend/src/components/board-moderator.component.tsx +++ b/cyynote-frontend/src/components/board-moderator.component.tsx @@ -1,54 +1,23 @@ -import { Component } from "react"; +import { useEffect, useState } from 'react'; +import userService from '../services/user.service'; -import UserService from "../services/user.service"; -import EventBus from "../common/EventBus"; +export default function BoardModerator() { + const [content, setContent] = useState(''); -type Props = {}; - -type State = { - content: string; -} - -export default class BoardAdmin extends Component { - constructor(props: Props) { - super(props); - - this.state = { - content: "" - }; - } - - componentDidMount() { - UserService.getModeratorBoard().then( - response => { - this.setState({ - content: response.data - }); + useEffect(() => { + userService.getModeratorBoard().then( + (res) => setContent(String(res.data ?? '')), + (error: unknown) => { + setContent(error instanceof Error ? error.message : String(error)); }, - error => { - this.setState({ - content: - (error.response && - error.response.data && - error.response.data.message) || - error.message || - error.toString() - }); - - if (error.response && error.response.status === 401) { - EventBus.dispatch("logout"); - } - } ); - } + }, []); - render() { - return ( -
-
-

{this.state.content}

-
-
- ); - } + return ( +
+
+

{content}

+
+
+ ); } diff --git a/cyynote-frontend/src/components/board-user.component.tsx b/cyynote-frontend/src/components/board-user.component.tsx index ca08c19..fae31ed 100644 --- a/cyynote-frontend/src/components/board-user.component.tsx +++ b/cyynote-frontend/src/components/board-user.component.tsx @@ -1,54 +1,23 @@ -import { Component } from "react"; +import { useEffect, useState } from 'react'; +import userService from '../services/user.service'; -import UserService from "../services/user.service"; -import EventBus from "../common/EventBus"; +export default function BoardUser() { + const [content, setContent] = useState(''); -type Props = {}; - -type State = { - content: string; -} - -export default class BoardUser extends Component { - constructor(props: Props) { - super(props); - - this.state = { - content: "" - }; - } - - componentDidMount() { - UserService.getUserBoard().then( - response => { - this.setState({ - content: response.data - }); + useEffect(() => { + userService.getUserBoard().then( + (res) => setContent(String(res.data ?? '')), + (error: unknown) => { + setContent(error instanceof Error ? error.message : String(error)); }, - error => { - this.setState({ - content: - (error.response && - error.response.data && - error.response.data.message) || - error.message || - error.toString() - }); - - if (error.response && error.response.status === 401) { - EventBus.dispatch("logout"); - } - } ); - } + }, []); - render() { - return ( -
-
-

{this.state.content}

-
-
- ); - } + return ( +
+
+

{content}

+
+
+ ); } diff --git a/cyynote-frontend/src/components/modals/PasswordModal.tsx b/cyynote-frontend/src/components/modals/PasswordModal.tsx index f3f6b3a..9a90965 100644 --- a/cyynote-frontend/src/components/modals/PasswordModal.tsx +++ b/cyynote-frontend/src/components/modals/PasswordModal.tsx @@ -4,7 +4,7 @@ import { useUIStore } from '../../stores/useUIStore'; import authService from '../../services/auth.service'; export default function PasswordModal() { - const { updatePasswordModelVisible, setUpdatePasswordModelVisible } = useUIStore(); + const { updatePasswordModalVisible, setUpdatePasswordModalVisible } = useUIStore(); const user = useAuthStore((s) => s.user); const handleSubmit = (values: { @@ -17,7 +17,7 @@ export default function PasswordModal() { .then( () => { Toast.success('密码修改成功'); - setUpdatePasswordModelVisible(false); + setUpdatePasswordModalVisible(false); }, (error: unknown) => { const msg = @@ -30,10 +30,10 @@ export default function PasswordModal() { return ( setUpdatePasswordModelVisible(false)} + onCancel={() => setUpdatePasswordModalVisible(false)} closeOnEsc >
diff --git a/cyynote-frontend/src/components/modals/TrashListModal.tsx b/cyynote-frontend/src/components/modals/TrashListModal.tsx index 93ea6d3..9887617 100644 --- a/cyynote-frontend/src/components/modals/TrashListModal.tsx +++ b/cyynote-frontend/src/components/modals/TrashListModal.tsx @@ -10,13 +10,13 @@ interface TrashListModalProps { } export default function TrashListModal({ trashList, onRestored }: TrashListModalProps) { - const { deleteModelVisible, setDeleteModelVisible } = useUIStore(); + const { deleteModalVisible, setDeleteModalVisible } = useUIStore(); const handleRestore = (id: number) => { noteService.restoreFromTrash(id).then( () => { Toast.success('已从回收站恢复'); - setDeleteModelVisible(false); + setDeleteModalVisible(false); onRestored(); }, () => Toast.error('恢复失败,请重试'), @@ -26,10 +26,10 @@ export default function TrashListModal({ trashList, onRestored }: TrashListModal return ( setDeleteModelVisible(false)} + onCancel={() => setDeleteModalVisible(false)} closeOnEsc >
diff --git a/cyynote-frontend/src/components/profile.component.tsx b/cyynote-frontend/src/components/profile.component.tsx index cda8bea..22dbc41 100644 --- a/cyynote-frontend/src/components/profile.component.tsx +++ b/cyynote-frontend/src/components/profile.component.tsx @@ -1,69 +1,37 @@ -import { Component } from "react"; import { Navigate } from 'react-router'; -import AuthService from "../services/auth.service"; -import type { IUser } from '../types'; +import { useAuthStore } from '../stores/useAuthStore'; -type Props = {}; +export default function Profile() { + const user = useAuthStore((s) => s.user); + const token = useAuthStore((s) => s.token); -type State = { - redirect: string | null, - userReady: boolean, - currentUser: IUser & { accessToken: string } -} -export default class Profile extends Component { - constructor(props: Props) { - super(props); + if (!user) return ; - this.state = { - redirect: null, - userReady: false, - currentUser: { accessToken: "" } - }; - } - - componentDidMount() { - const currentUser = AuthService.getCurrentUser(); - - if (!currentUser) this.setState({ redirect: "/home" }); - this.setState({ currentUser: currentUser, userReady: true }) - } - - render() { - if (this.state.redirect) { - return - } - - const { currentUser } = this.state; - - return ( -
- {(this.state.userReady) ? -
-
-

- {currentUser.username} Profile -

-
-

- Token:{" "} - {currentUser.accessToken.substring(0, 20)} ...{" "} - {currentUser.accessToken.substr(currentUser.accessToken.length - 20)} -

-

- Id:{" "} - {currentUser.id} -

-

- Email:{" "} - {currentUser.email} -

- Authorities: -
    - {currentUser.roles && - currentUser.roles.map((role, index) =>
  • {role}
  • )} -
-
: null} + return ( +
+
+
+

+ {user.username} Profile +

+
+

+ Token:{' '} + {token ? `${token.substring(0, 20)} ... ${token.substring(token.length - 20)}` : 'N/A'} +

+

+ Id: {user.id} +

+

+ Email: {user.email} +

+ Authorities: +
    + {user.roles.map((role, index) => ( +
  • {role}
  • + ))} +
- ); - } +
+ ); } diff --git a/cyynote-frontend/src/components/sidebar/NoteTree.tsx b/cyynote-frontend/src/components/sidebar/NoteTree.tsx index 35db517..72893df 100644 --- a/cyynote-frontend/src/components/sidebar/NoteTree.tsx +++ b/cyynote-frontend/src/components/sidebar/NoteTree.tsx @@ -34,6 +34,8 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No 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( @@ -81,7 +83,7 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No size="small" icon={} onClick={(e) => { - addNote(item.key); + addNote(toNoteId(item.key)); e.stopPropagation(); }} /> @@ -90,7 +92,7 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No type="warning" icon={} onClick={(e) => { - openDeleteConfirm(item.key, String(label)); + openDeleteConfirm(toNoteId(item.key), String(label)); e.stopPropagation(); }} /> @@ -188,11 +190,11 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No showFilteredOnly directory virtualize - value={currentNoteId} + value={currentNoteId === null ? undefined : String(currentNoteId)} treeData={treeData as unknown as Parameters[0]['treeData']} renderLabel={renderLabel as Parameters[0]['renderLabel']} style={treeStyle} - onSelect={(id) => loadNote(id as unknown as number)} + onSelect={(id) => loadNote(toNoteId(id as string | number))} /> ); diff --git a/cyynote-frontend/src/pages/home/HomePage.tsx b/cyynote-frontend/src/pages/home/HomePage.tsx index a8ab501..07bc26d 100644 --- a/cyynote-frontend/src/pages/home/HomePage.tsx +++ b/cyynote-frontend/src/pages/home/HomePage.tsx @@ -9,17 +9,20 @@ import { Toast, } from '@douyinfe/semi-ui'; import { useNoteStore } from '../../stores/useNoteStore'; +import { useUIStore } from '../../stores/useUIStore'; import noteService from '../../services/note.service'; import type { INote } from '../../types'; export default function HomePage() { const { homeData, setHomeData, setCurrentNote } = useNoteStore(); + const setActiveView = useUIStore((s) => s.setActiveView); const loadNote = (id: number) => { noteService.get(id).then( (res) => { const note = res.data; setCurrentNote(note.id, note.title, note.context); + setActiveView('note'); }, () => Toast.error('加载笔记失败'), ); diff --git a/cyynote-frontend/src/pages/note-page.tsx b/cyynote-frontend/src/pages/note-page.tsx index c465fa1..4a82fa7 100644 --- a/cyynote-frontend/src/pages/note-page.tsx +++ b/cyynote-frontend/src/pages/note-page.tsx @@ -63,8 +63,8 @@ export default function NotePage() { setActiveView, setSiderFlag, setSideVisible, - setDeleteModelVisible, - setUpdatePasswordModelVisible, + setDeleteModalVisible, + setUpdatePasswordModalVisible, } = useUIStore(); const [trashList, setTrashList] = useState([]); @@ -102,13 +102,13 @@ export default function NotePage() { noteService.getHomeData('4', '0').then( (res) => { setTrashList(res.data); - setDeleteModelVisible(true); + setDeleteModalVisible(true); }, () => Toast.error('加载回收站失败'), ); } }, - [setActiveView, setDeleteModelVisible], + [setActiveView, setDeleteModalVisible], ); const handleSearch = useCallback(() => { @@ -244,7 +244,7 @@ export default function NotePage() { theme="solid" type="secondary" style={{ marginRight: 8 }} - onClick={() => setUpdatePasswordModelVisible(true)} + onClick={() => setUpdatePasswordModalVisible(true)} > 修改密码 diff --git a/cyynote-frontend/src/stores/useUIStore.ts b/cyynote-frontend/src/stores/useUIStore.ts index 64ced32..283f063 100644 --- a/cyynote-frontend/src/stores/useUIStore.ts +++ b/cyynote-frontend/src/stores/useUIStore.ts @@ -10,8 +10,8 @@ interface UIState { deleteVisible: boolean; deleteId: number | null; deleteNoteName: string; - deleteModelVisible: boolean; - updatePasswordModelVisible: boolean; + deleteModalVisible: boolean; + updatePasswordModalVisible: boolean; searchQuery: string; setActiveView: (view: ActiveView) => void; setSideVisible: (visible: boolean) => void; @@ -19,8 +19,8 @@ interface UIState { setNoteLoading: (loading: boolean) => void; openDeleteConfirm: (id: number, name: string) => void; closeDeleteConfirm: () => void; - setDeleteModelVisible: (visible: boolean) => void; - setUpdatePasswordModelVisible: (visible: boolean) => void; + setDeleteModalVisible: (visible: boolean) => void; + setUpdatePasswordModalVisible: (visible: boolean) => void; setSearchQuery: (query: string) => void; } @@ -32,8 +32,8 @@ export const useUIStore = create()((set) => ({ deleteVisible: false, deleteId: null, deleteNoteName: '', - deleteModelVisible: false, - updatePasswordModelVisible: false, + deleteModalVisible: false, + updatePasswordModalVisible: false, searchQuery: '', setActiveView: (activeView) => set({ activeView }), setSideVisible: (sideVisible) => set({ sideVisible }), @@ -43,8 +43,8 @@ export const useUIStore = create()((set) => ({ set({ deleteVisible: true, deleteId, deleteNoteName }), closeDeleteConfirm: () => set({ deleteVisible: false, deleteId: null, deleteNoteName: '' }), - setDeleteModelVisible: (deleteModelVisible) => set({ deleteModelVisible }), - setUpdatePasswordModelVisible: (updatePasswordModelVisible) => - set({ updatePasswordModelVisible }), + setDeleteModalVisible: (deleteModalVisible) => set({ deleteModalVisible }), + setUpdatePasswordModalVisible: (updatePasswordModalVisible) => + set({ updatePasswordModalVisible }), setSearchQuery: (searchQuery) => set({ searchQuery }), })); diff --git a/cyynote-frontend/src/types/index.ts b/cyynote-frontend/src/types/index.ts index 987c97b..5ffccfa 100644 --- a/cyynote-frontend/src/types/index.ts +++ b/cyynote-frontend/src/types/index.ts @@ -24,7 +24,10 @@ export interface INote { } export interface ITreeNode { - key: number; + id?: number; + pid?: number; + key: string; + value?: string; label: string; children?: ITreeNode[]; } diff --git a/cyynote-frontend/tsconfig.json b/cyynote-frontend/tsconfig.json index 8c7ac16..0ce2bfb 100644 --- a/cyynote-frontend/tsconfig.json +++ b/cyynote-frontend/tsconfig.json @@ -11,11 +11,12 @@ "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, - "verbatimModuleSyntax": true, + "verbatimModuleSyntax": false, "noEmit": true, "jsx": "react-jsx", /* Path aliases */ + "ignoreDeprecations": "6.0", "baseUrl": ".", "paths": { "@/*": ["./src/*"] @@ -23,8 +24,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "noFallthroughCasesInSwitch": true }, "include": ["src"],