update #1

Merged
Chuyaoyuan merged 4 commits from v2 into v2-bak 2026-06-15 14:31:36 +08:00
20 changed files with 866 additions and 654 deletions
Showing only changes of commit c764603dbd - Show all commits
+272
View File
@@ -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-jwtJWT 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 <token>`
- 响应拦截器在 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。
+13 -9
View File
@@ -20,7 +20,11 @@
</parent> </parent>
<properties> <properties>
<java.version>11</java.version> <java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.release>${java.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
@@ -75,14 +79,6 @@
<version>3.46.1.3</version> <version>3.46.1.3</version>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.noear</groupId> <groupId>org.noear</groupId>
<artifactId>solon-test</artifactId> <artifactId>solon-test</artifactId>
@@ -96,6 +92,14 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.noear</groupId> <groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId> <artifactId>solon-maven-plugin</artifactId>
@@ -1,103 +1,148 @@
package com.cyynote.controller; package com.cyynote.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONObject; import com.cyynote.model.UserModel;
import com.cyynote.controller.BaseController; import com.cyynote.payload.request.LoginRequest;
import com.cyynote.model.NoteModel; import com.cyynote.payload.request.SignupRequest;
import com.cyynote.model.UserModel; import com.cyynote.payload.request.UpdatePasswordRequest;
import com.cyynote.payload.request.LoginRequest; import java.sql.SQLException;
import com.cyynote.payload.request.SignupRequest; import java.util.Arrays;
import java.sql.SQLException; import java.util.List;
import java.util.List; import java.util.logging.Logger;
import java.util.logging.Logger; import org.noear.solon.annotation.Body;
import org.noear.solon.annotation.Controller;
import com.cyynote.payload.request.UpdatePasswordRequest; import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Body; import org.noear.solon.annotation.Post;
import org.noear.solon.annotation.Controller; import org.noear.solon.core.handle.Context;
import org.noear.solon.annotation.Mapping; import org.noear.solon.core.handle.Result;
import org.noear.solon.annotation.Post; import org.noear.wood.DbContext;
import org.noear.solon.core.handle.Context; import org.noear.wood.DbTableQuery;
import org.noear.solon.core.handle.Result; import org.noear.wood.annotation.Db;
import org.noear.wood.DbContext;
import org.noear.wood.DbTableQuery; @Mapping("/api/auth")
import org.noear.wood.annotation.Db; @Controller
public class AuthController extends BaseController {
@Mapping("/api/auth") private static final Logger log = Logger.getLogger(AuthController.class.getName());
@Controller
public class AuthController extends BaseController { @Db
private static final Logger log = Logger.getLogger(com.cyynote.controller.AuthController.class.getName()); DbContext db;
@Db @Mapping("/logout")
DbContext db; public void logout(Context ctx) {
ctx.sessionClear();
@Mapping("/logout") }
public void logout(Context ctx) {
ctx.sessionClear(); @Post
} @Mapping("/signin")
public Result signin(Context ctx, @Body LoginRequest loginRequest) throws SQLException {
@Post if (loginRequest == null || loginRequest.getUsername() == null || loginRequest.getPassword() == null) {
@Mapping("/signin") ctx.status(400);
public Result signin(Context ctx, @Body LoginRequest loginRequest) throws SQLException { return Result.failure(400, "用户名和密码不能为空");
log.info("name:" + loginRequest.getUsername()); }
log.info("password:" + loginRequest.getPassword());
log.info("signin username:" + loginRequest.getUsername());
long count = db.table("user").selectCount(); UserModel model = (UserModel) ((DbTableQuery) ((DbTableQuery) this.db.table("user")
log.info("countUser:" + count); .whereEq("username", loginRequest.getUsername()))
List<UserModel> all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); .andEq("password", loginRequest.getPassword()))
log.info( all.toString()); .selectItem("*", UserModel.class);
UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getPassword())).selectItem("*", UserModel.class); if (model == null) {
if ("admin".equals(model.getUsername())) { ctx.status(401);
ctx.sessionSet("user_name", loginRequest.getUsername()); return Result.failure(401, "用户名或密码错误");
ctx.sessionSet("user_id", model.getId()); }
JSONObject obj = new JSONObject();
String token = ctx.sessionState().sessionToken(); ctx.sessionSet("user_name", model.getUsername());
obj.put("accessToken", token); ctx.sessionSet("user_id", model.getId());
obj.put("id", Integer.valueOf(1)); String token = ctx.sessionState().sessionToken();
obj.put("username", loginRequest.getUsername()); ((DbTableQuery) this.db.table("user")
obj.put("email", "1"); .set("token", token)
obj.put("roles", "1"); .set("logintime", DateUtil.now())
int flag = ((DbTableQuery)this.db.table("user").set("token", token).set("logintime", DateUtil.now()).whereEq("id", model.getId())).update(); .whereEq("id", model.getId()))
System.out.println(flag); .update();
return Result.succeed(obj);
} JSONObject user = new JSONObject();
return Result.failure(); user.put("id", model.getId());
} user.put("username", model.getUsername());
user.put("email", model.getEmail());
user.put("roles", normalizeRoles(model.getRole()));
@Post
@Mapping("/updatePassword") JSONObject payload = new JSONObject();
public Result updatePassword(Context ctx, @Body UpdatePasswordRequest loginRequest) throws SQLException { payload.put("accessToken", token);
log.info("name:" + loginRequest.getUsername()); payload.put("tokenType", "Bearer");
log.info("old - password:" + loginRequest.getOldpassword()); payload.put("user", user);
log.info("new - password:" + loginRequest.getNewpassword());
return Result.succeed(payload);
}
long count = db.table("user").selectCount();
log.info("countUser:" + count); @Post
List<UserModel> all = ((DbTableQuery)this.db.table("user")).selectList("*", UserModel.class); @Mapping("/updatePassword")
log.info( all.toString()); 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);
UserModel model = (UserModel)((DbTableQuery)((DbTableQuery)this.db.table("user").whereEq("username", loginRequest.getUsername())).andEq("password", loginRequest.getOldpassword())).selectItem("*", UserModel.class); return Result.failure(400, "参数不完整");
if ("admin".equals(model.getUsername())) { }
int flag = ((DbTableQuery)this.db.table("user").set("password", loginRequest.getNewpassword()).whereEq("id", model.getId())).update(); log.info("update password username:" + loginRequest.getUsername());
System.out.println(flag); UserModel model = (UserModel) ((DbTableQuery) ((DbTableQuery) this.db.table("user")
return Result.succeed(flag); .whereEq("username", loginRequest.getUsername()))
} .andEq("password", loginRequest.getOldpassword()))
return Result.failure(); .selectItem("*", UserModel.class);
} if (model == null) {
ctx.status(400);
return Result.failure(400, "用户名或旧密码错误");
}
@Post
@Mapping("/signup") int flag = ((DbTableQuery) this.db.table("user")
public Result signup(@Body SignupRequest signUpRequest) { .set("password", loginRequest.getNewpassword())
log.info("signUpRequest:" + JSONObject.toJSONString(signUpRequest, new com.alibaba.fastjson2.JSONWriter.Feature[0])); .whereEq("id", model.getId()))
JSONObject obj = new JSONObject(); .update();
return Result.succeed(obj); 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<String> 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");
}
}
@@ -1,44 +1,39 @@
package com.cyynote.controller; package com.cyynote.controller;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Claims; import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Component; import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Handler;
import org.noear.solon.core.handle.Handler; import org.noear.solon.core.handle.Result;
import org.noear.solon.core.handle.Result; import org.noear.solon.core.route.RouterInterceptor;
import org.noear.solon.core.route.RouterInterceptor; import org.noear.solon.core.route.RouterInterceptorChain;
import org.noear.solon.core.route.RouterInterceptorChain; import org.noear.solon.sessionstate.jwt.JwtUtils;
import org.noear.solon.sessionstate.jwt.JwtUtils;
@Component
@Component public class JwtInterceptor implements RouterInterceptor {
public class JwtInterceptor implements RouterInterceptor { public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { boolean requireAuth = !("/api/auth/signup".equals(ctx.path())
boolean flag = true; || "/api/auth/signin".equals(ctx.path())
if ("/api/auth/signup".equals(ctx.path()) || || "/demo".equals(ctx.path())
"/api/auth/signin".equals(ctx.path()) || || "/api/auth/logout".equals(ctx.path())
"/demo".equals(ctx.path()) || || "OPTIONS".equalsIgnoreCase(ctx.method()));
"/api/auth/logout".equals(ctx.path())
if (requireAuth) {
) String token = ctx.header("Authorization");
flag = false; if (token == null || token.isBlank()) {
if (flag) ctx.status(401);
if (null != ctx.header("Authorization")) { ctx.render(Result.failure(401, "未登录或登录已过期"));
String token = ctx.header("Authorization"); return;
token = token.replace("Bearer ", ""); }
System.out.println("token:" + token);
Claims claims = JwtUtils.parseJwt(token); token = token.replace("Bearer ", "");
System.out.println("claims:" + claims); Claims claims = JwtUtils.parseJwt(token);
if (null != claims) { if (claims == null) {
System.out.println("username:" + claims.get("user_name")); ctx.status(401);
System.out.println("exp:" + claims.get("exp")); ctx.render(Result.failure(401, "未登录或登录已过期"));
} else { return;
ctx.status(401); }
ctx.render(Result.failure(401, "")); }
} chain.doIntercept(ctx, mainHandler);
} else { }
ctx.status(401); }
ctx.render(Result.failure(401, ""));
}
chain.doIntercept(ctx, mainHandler);
}
}
@@ -1,207 +1,219 @@
package com.cyynote.controller; package com.cyynote.controller;
import cn.hutool.*; import cn.hutool.*;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONObject; import com.cyynote.dso.NoteSqlAnnotation;
import com.cyynote.controller.BaseController; import com.cyynote.model.HistoryModel;
import com.cyynote.dso.NoteSqlAnnotation; import com.cyynote.model.NoteModel;
import com.cyynote.model.HistoryModel; import com.cyynote.payload.request.NoteRequest;
import com.cyynote.model.NoteModel; import com.cyynote.util.TreeBuild;
import com.cyynote.payload.request.NoteRequest; import com.cyynote.util.TreeNode;
import com.cyynote.util.TreeBuild; import io.jsonwebtoken.Claims;
import com.cyynote.util.TreeNode; import java.sql.SQLException;
import io.jsonwebtoken.Claims; import java.util.ArrayList;
import java.sql.SQLException; import java.util.List;
import java.util.ArrayList; import java.util.logging.Logger;
import java.util.Date; import org.noear.solon.annotation.Body;
import java.util.List; import org.noear.solon.annotation.Controller;
import java.util.logging.Logger; import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Body; import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Path;
import org.noear.solon.annotation.Get; import org.noear.solon.annotation.Post;
import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Put;
import org.noear.solon.annotation.Path; import org.noear.solon.core.handle.Context;
import org.noear.solon.annotation.Post; import org.noear.solon.core.handle.Result;
import org.noear.solon.annotation.Put; import org.noear.solon.sessionstate.jwt.JwtUtils;
import org.noear.solon.core.handle.Context; import org.noear.wood.DbContext;
import org.noear.solon.core.handle.Result; import org.noear.wood.DbTableQuery;
import org.noear.solon.sessionstate.jwt.JwtUtils; import org.noear.wood.annotation.Db;
import org.noear.solon.web.cors.annotation.CrossOrigin;
import org.noear.wood.DbContext; @Mapping("/api/note")
import org.noear.wood.DbTableQuery; @Controller
import org.noear.wood.annotation.Db; public class NoteController extends BaseController {
private static final Logger log = Logger.getLogger(com.cyynote.controller.NoteController.class.getName());
@Mapping("/api/note")
@Controller @Db
public class NoteController extends BaseController { NoteSqlAnnotation mapper;
private static final Logger log = Logger.getLogger(com.cyynote.controller.NoteController.class.getName());
@Db
@Db DbContext db;
NoteSqlAnnotation mapper;
private static final String QUERY_F = "id,pid,title,createtime,updatetime,viewtime,flag";
@Db
DbContext db; @Get
@Mapping("get1")
private static final String QUERY_F = "id,pid,title,createtime,updatetime,viewtime,flag"; public String test_post_get(Context context, @Path String appname) {
@Get return context.path();
@Mapping("get1") }
public String test_post_get(Context context, @Path String appname) {
@Post
return context.path(); @Mapping("post")
} public String test_post(Context context) {
return context.param("name");
@Post }
@Mapping("post")
public String test_post(Context context) { @Put
return context.param("name"); @Mapping("put")
} public String test_put(Context context, String name) {
return context.param("name");
@Put }
@Mapping("put")
public String test_put(Context context, String name) { @Get
return context.param("name"); @Mapping("/all")
} public Result noteAccess(Context ctx) throws Exception {
List<NoteModel> all = ((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class);
@Get List<TreeNode> treeNodeList = new ArrayList<>();
@Mapping("/all") for (NoteModel n : all) {
public Result noteAccess(Context ctx) throws Exception { TreeNode treeNode = new TreeNode(n.getId().intValue(), n.getPid().intValue(), n.getTitle(), String.valueOf(n.getId()), String.valueOf(n.getId()));
List<NoteModel> all = ((DbTableQuery)this.db.table("note").whereEq("flag", Integer.valueOf(1))).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); treeNodeList.add(treeNode);
List<TreeNode> treeNodeList = new ArrayList<>(); }
for (NoteModel n : all) { TreeBuild bb = new TreeBuild(treeNodeList);
TreeNode treeNode = new TreeNode(n.getId().intValue(), n.getPid().intValue(), n.getTitle(), String.valueOf(n.getId()), String.valueOf(n.getId())); List<TreeNode> collect = bb.buildTree();
treeNodeList.add(treeNode); return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(collect)));
} }
TreeBuild bb = new TreeBuild(treeNodeList);
List<TreeNode> collect = bb.buildTree(); @Get
return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(collect))); @Mapping("/home")
} public Result noteHome(Context ctx, @Path int type, @Path String search) throws Exception {
List<NoteModel> all = new ArrayList<>();
@Get if (type == 0) {
@Mapping("/home") all = ((DbTableQuery)((DbTableQuery)this.db.table("note").whereLk("context", "%" + search + "%")).andLk("title", "%" + search + "%")).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class);
public Result noteHome(Context ctx, @Path int type, @Path String search) throws Exception { } else if (type == 1) {
System.out.println(type); 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);
System.out.println(search); } else if (type == 2) {
List<NoteModel> all = new ArrayList<>(); 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);
if (type == 0) { } else if (type == 3) {
all = ((DbTableQuery)((DbTableQuery)this.db.table("note").whereLk("context", "%" + search + "%")).andLk("title", "%" + search + "%")).selectList("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); 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 == 1) { } else if (type == 4) {
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); 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);
} 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); return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(all)));
} 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) { @Get
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); @Mapping("/get")
} public Result getNote(Context ctx, @Path int id) throws Exception {
return Result.succeed(JSONArray.parseArray(JSONArray.toJSONString(all))); NoteModel model = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", Integer.valueOf(id))).selectItem("*", NoteModel.class);
} if (model == null) {
ctx.status(404);
@Get return Result.failure(404, "笔记不存在");
@Mapping("/get") }
public Result getNote(Context ctx, @Path int id) throws Exception { String time = DateUtil.now();
NoteModel model = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", Integer.valueOf(id))).selectItem("*", NoteModel.class); ((DbTableQuery)this.db.table("note").set("viewtime", time).whereEq("id", Integer.valueOf(id))).update();
String time = DateUtil.now(); model.setViewtime(time);
System.out.println(time); log.info(JSONObject.toJSONString(model, new com.alibaba.fastjson2.JSONWriter.Feature[0]));
int flag = ((DbTableQuery)this.db.table("note").set("viewtime", time).whereEq("id", Integer.valueOf(id))).update(); return Result.succeed(model);
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 {
@Get String userid = getUserInfoId(ctx);
@Mapping("/add") if (null == userid)
public Result addNote(Context ctx, @Path int pid) throws Exception { return Result.failure(401);
String userid = getUserInfoId(ctx); NoteModel note = new NoteModel();
if (null == userid) note.setPid(Integer.valueOf(pid));
return Result.failure(401); note.setTitle("未命名Note");
NoteModel note = new NoteModel(); note.setFlag(Integer.valueOf(1));
note.setPid(Integer.valueOf(pid)); note.setCreatetime(DateUtil.now());
note.setTitle("未命名Note"); note.setUpdatetime(note.getCreatetime());
note.setFlag(Integer.valueOf(1)); note.setViewtime(note.getCreatetime());
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\":{\"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}}");
// 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();
this.db.table("note").setEntityIf(note, (k, v) -> Boolean.valueOf((v != null))).insert(); return Result.succeed("ok");
return Result.succeed("ok"); }
}
@Post
@Post @Mapping("/update")
@Mapping("/update") public Result updateNote(Context ctx, @Body NoteRequest noteRequest) throws SQLException {
public Result updateNote(@Body NoteRequest noteRequest) throws SQLException { if (noteRequest == null || noteRequest.getId() == null || noteRequest.getTitle() == null || noteRequest.getContext() == null) {
NoteModel note = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", noteRequest.getId())).selectItem("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class); ctx.status(400);
note.setTitle(noteRequest.getTitle()); return Result.failure(400, "参数不完整");
note.setUpdatetime(DateUtil.now()); }
String cc = noteRequest.getContext(); NoteModel note = (NoteModel)((DbTableQuery)this.db.table("note").whereEq("id", noteRequest.getId())).selectItem("id,pid,title,createtime,updatetime,viewtime,flag", NoteModel.class);
JSONObject ar = JSONObject.parseObject(cc); if (note == null) {
note.setContext(ar.toJSONString()); ctx.status(404);
int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("updatetime", DateUtil.now()).set("context", ar.toJSONString()).whereEq("id", noteRequest.getId())).update(); return Result.failure(404, "笔记不存在");
log.info("updateNote+ flag"); }
HistoryModel history = new HistoryModel(); note.setTitle(noteRequest.getTitle());
history.setNid(Integer.valueOf(noteRequest.getId().intValue())); note.setUpdatetime(DateUtil.now());
history.setTitle(noteRequest.getTitle()); String cc = noteRequest.getContext();
history.setFlag(Integer.valueOf(1)); JSONObject ar = JSONObject.parseObject(cc);
history.setCreatetime(DateUtil.now()); note.setContext(ar.toJSONString());
history.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();
long his = this.db.table("history").setEntityIf(history, (k, v) -> Boolean.valueOf((v != null))).insert(); log.info("updateNote+ flag");
log.info("history+ his"); HistoryModel history = new HistoryModel();
return Result.succeed("ok"); history.setNid(Integer.valueOf(noteRequest.getId().intValue()));
} history.setTitle(noteRequest.getTitle());
history.setFlag(Integer.valueOf(1));
@Post history.setCreatetime(DateUtil.now());
@Mapping("/delete") history.setContext(ar.toJSONString());
public Result deleteNote(@Body NoteRequest noteRequest) throws SQLException { long his = this.db.table("history").setEntityIf(history, (k, v) -> Boolean.valueOf((v != null))).insert();
int flag = ((DbTableQuery)this.db.table("note").set("title", noteRequest.getTitle()).set("flag", Integer.valueOf(0)).whereEq("id", noteRequest.getId())).update(); log.info("history+ his");
System.out.println(flag); return Result.succeed("ok");
return Result.succeed("ok"); }
}
@Post
@Get @Mapping("/delete")
@Mapping("/deleteBack") public Result deleteNote(Context ctx, @Body NoteRequest noteRequest) throws SQLException {
public Result deleteBack(@Path int id) throws Exception { if (noteRequest == null || noteRequest.getId() == null) {
int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(1)).whereEq("id", Integer.valueOf(id))).update(); ctx.status(400);
System.out.println(flag); return Result.failure(400, "笔记ID不能为空");
return Result.succeed("ok"); }
} int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(0)).set("updatetime", DateUtil.now()).whereEq("id", noteRequest.getId())).update();
if (flag == 0) {
@Get ctx.status(404);
@Mapping("/historyQueryOrDelete") return Result.failure(404, "笔记不存在");
public Result historyQueryOrDelete(@Path int flag) throws Exception { }
return Result.succeed("ok");
log.info("history+ flag" + flag); }
if(flag==0){ @Get
long count = db.table("history").selectCount(); @Mapping("/deleteBack")
log.info("count:" + count); public Result deleteBack(Context ctx, @Path int id) throws Exception {
return Result.succeed(count); int flag = ((DbTableQuery)this.db.table("note").set("flag", Integer.valueOf(1)).set("updatetime", DateUtil.now()).whereEq("id", Integer.valueOf(id))).update();
}else if(flag==1){ if (flag == 0) {
int flagdb = db.table("history").whereEq("flag",1).delete(); ctx.status(404);
log.info("flagdb:" + flagdb); return Result.failure(404, "笔记不存在");
return Result.succeed(0); }
} return Result.succeed("ok");
return null; }
}
@Get
private String getUserInfoId(Context ctx) { @Mapping("/historyQueryOrDelete")
String userId = ""; public Result historyQueryOrDelete(@Path int flag) throws Exception {
if (null != ctx.header("Authorization")) {
String token = ctx.header("Authorization"); log.info("history+ flag" + flag);
token = token.replace("Bearer ", "");
System.out.println("token:" + token); if(flag==0){
Claims claims = JwtUtils.parseJwt(token); long count = db.table("history").selectCount();
System.out.println("claims:" + claims); log.info("count:" + count);
if (null != claims) { return Result.succeed(count);
System.out.println("username:" + claims.get("user_name")); }else if(flag==1){
System.out.println("userid:" + claims.get("user_id")); int flagdb = db.table("history").whereEq("flag",1).delete();
System.out.println("exp:" + claims.get("exp")); log.info("flagdb:" + flagdb);
userId = claims.get("user_id").toString(); return Result.succeed(0);
return userId; }
} return Result.failure(400, "不支持的操作类型");
return null; }
}
return null; 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;
}
}
@@ -1,3 +1,3 @@
test.db1: test.db1:
jdbcUrl: "jdbc:sqlite:D:/cyynote.db" jdbcUrl: "jdbc:sqlite:./cyynote.db"
driverClassName: "org.sqlite.JDBC" driverClassName: "org.sqlite.JDBC"
@@ -1,53 +1,54 @@
CREATE TABLE `user` ( CREATE TABLE `user` (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT, email TEXT,
password TEXT, password TEXT,
username TEXT, username TEXT,
role TEXT, role TEXT,
token TEXT, token TEXT,
logintime TEXT logintime TEXT
); );
CREATE TABLE `note` ( CREATE TABLE `note` (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
pid INTEGER, pid INTEGER,
title TEXT, title TEXT,
context TEXT, context TEXT,
conjson TEXT, conjson TEXT,
createtime TEXT, createtime TEXT,
updatetime TEXT, updatetime TEXT,
viewtime TEXT, viewtime TEXT,
userid INTEGER, userid INTEGER,
username TEXT, username TEXT,
flag INTEGER DEFAULT 1 flag INTEGER DEFAULT 1
); );
CREATE TABLE `history` ( CREATE TABLE `history` (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
nid INTEGER, nid INTEGER,
title TEXT, title TEXT,
context TEXT, context TEXT,
conjson TEXT, conjson TEXT,
createtime TEXT, createtime TEXT,
userid INTEGER, userid INTEGER,
username TEXT username TEXT,
); flag INTEGER DEFAULT 1
CREATE TABLE appx ( );
app_id INTEGER PRIMARY KEY AUTOINCREMENT, CREATE TABLE appx (
app_key TEXT, app_id INTEGER PRIMARY KEY AUTOINCREMENT,
akey TEXT, app_key TEXT,
ugroup_id INTEGER DEFAULT 0, akey TEXT,
agroup_id INTEGER, ugroup_id INTEGER DEFAULT 0,
name TEXT, agroup_id INTEGER,
note TEXT, name TEXT,
ar_is_setting INTEGER NOT NULL DEFAULT 0, note TEXT,
ar_is_examine INTEGER NOT NULL DEFAULT 0, ar_is_setting INTEGER NOT NULL DEFAULT 0,
ar_examine_ver INTEGER NOT NULL DEFAULT 0, ar_is_examine INTEGER NOT NULL DEFAULT 0,
log_fulltime TEXT 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 `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'); INSERT INTO `note` VALUES ('1', '0', 'test', 'test', 'test','','','','1','1','1');
-1
View File
@@ -38,7 +38,6 @@
"@lexical/table": "0.44.0", "@lexical/table": "0.44.0",
"@lexical/utils": "0.44.0", "@lexical/utils": "0.44.0",
"@lexical/yjs": "0.44.0", "@lexical/yjs": "0.44.0",
"@tanstack/react-query": "^5.74.0",
"axios": "^1.8.0", "axios": "^1.8.0",
"html-react-parser": "^5.2.0", "html-react-parser": "^5.2.0",
"katex": "^0.16.21", "katex": "^0.16.21",
@@ -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"; export default function BoardAdmin() {
import EventBus from "../common/EventBus"; const [content, setContent] = useState('');
type Props = {}; useEffect(() => {
userService.getAdminBoard().then(
type State = { (res) => setContent(String(res.data ?? '')),
content: string; (error: unknown) => {
} setContent(error instanceof Error ? error.message : String(error));
export default class BoardAdmin extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getAdminBoard().then(
response => {
this.setState({
content: response.data
});
}, },
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 (
return ( <div className="container">
<div className="container"> <header className="jumbotron">
<header className="jumbotron"> <h3>{content}</h3>
<h3>{this.state.content}</h3> </header>
</header> </div>
</div> );
);
}
} }
@@ -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"; export default function BoardModerator() {
import EventBus from "../common/EventBus"; const [content, setContent] = useState('');
type Props = {}; useEffect(() => {
userService.getModeratorBoard().then(
type State = { (res) => setContent(String(res.data ?? '')),
content: string; (error: unknown) => {
} setContent(error instanceof Error ? error.message : String(error));
export default class BoardAdmin extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getModeratorBoard().then(
response => {
this.setState({
content: response.data
});
}, },
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 (
return ( <div className="container">
<div className="container"> <header className="jumbotron">
<header className="jumbotron"> <h3>{content}</h3>
<h3>{this.state.content}</h3> </header>
</header> </div>
</div> );
);
}
} }
@@ -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"; export default function BoardUser() {
import EventBus from "../common/EventBus"; const [content, setContent] = useState('');
type Props = {}; useEffect(() => {
userService.getUserBoard().then(
type State = { (res) => setContent(String(res.data ?? '')),
content: string; (error: unknown) => {
} setContent(error instanceof Error ? error.message : String(error));
export default class BoardUser extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getUserBoard().then(
response => {
this.setState({
content: response.data
});
}, },
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 (
return ( <div className="container">
<div className="container"> <header className="jumbotron">
<header className="jumbotron"> <h3>{content}</h3>
<h3>{this.state.content}</h3> </header>
</header> </div>
</div> );
);
}
} }
@@ -4,7 +4,7 @@ import { useUIStore } from '../../stores/useUIStore';
import authService from '../../services/auth.service'; import authService from '../../services/auth.service';
export default function PasswordModal() { export default function PasswordModal() {
const { updatePasswordModelVisible, setUpdatePasswordModelVisible } = useUIStore(); const { updatePasswordModalVisible, setUpdatePasswordModalVisible } = useUIStore();
const user = useAuthStore((s) => s.user); const user = useAuthStore((s) => s.user);
const handleSubmit = (values: { const handleSubmit = (values: {
@@ -17,7 +17,7 @@ export default function PasswordModal() {
.then( .then(
() => { () => {
Toast.success('密码修改成功'); Toast.success('密码修改成功');
setUpdatePasswordModelVisible(false); setUpdatePasswordModalVisible(false);
}, },
(error: unknown) => { (error: unknown) => {
const msg = const msg =
@@ -30,10 +30,10 @@ export default function PasswordModal() {
return ( return (
<Modal <Modal
title="修改密码" title="修改密码"
visible={updatePasswordModelVisible} visible={updatePasswordModalVisible}
size="large" size="large"
footer={null} footer={null}
onCancel={() => setUpdatePasswordModelVisible(false)} onCancel={() => setUpdatePasswordModalVisible(false)}
closeOnEsc closeOnEsc
> >
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}> <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
@@ -10,13 +10,13 @@ interface TrashListModalProps {
} }
export default function TrashListModal({ trashList, onRestored }: TrashListModalProps) { export default function TrashListModal({ trashList, onRestored }: TrashListModalProps) {
const { deleteModelVisible, setDeleteModelVisible } = useUIStore(); const { deleteModalVisible, setDeleteModalVisible } = useUIStore();
const handleRestore = (id: number) => { const handleRestore = (id: number) => {
noteService.restoreFromTrash(id).then( noteService.restoreFromTrash(id).then(
() => { () => {
Toast.success('已从回收站恢复'); Toast.success('已从回收站恢复');
setDeleteModelVisible(false); setDeleteModalVisible(false);
onRestored(); onRestored();
}, },
() => Toast.error('恢复失败,请重试'), () => Toast.error('恢复失败,请重试'),
@@ -26,10 +26,10 @@ export default function TrashListModal({ trashList, onRestored }: TrashListModal
return ( return (
<Modal <Modal
title="回收站" title="回收站"
visible={deleteModelVisible} visible={deleteModalVisible}
size="large" size="large"
footer={null} footer={null}
onCancel={() => setDeleteModelVisible(false)} onCancel={() => setDeleteModalVisible(false)}
closeOnEsc closeOnEsc
> >
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}> <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
@@ -1,69 +1,37 @@
import { Component } from "react";
import { Navigate } from 'react-router'; import { Navigate } from 'react-router';
import AuthService from "../services/auth.service"; import { useAuthStore } from '../stores/useAuthStore';
import type { IUser } from '../types';
type Props = {}; export default function Profile() {
const user = useAuthStore((s) => s.user);
const token = useAuthStore((s) => s.token);
type State = { if (!user) return <Navigate to="/login" replace />;
redirect: string | null,
userReady: boolean,
currentUser: IUser & { accessToken: string }
}
export default class Profile extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { return (
redirect: null, <div className="container">
userReady: false, <div>
currentUser: { accessToken: "" } <header className="jumbotron">
}; <h3>
} <strong>{user.username}</strong> Profile
</h3>
componentDidMount() { </header>
const currentUser = AuthService.getCurrentUser(); <p>
<strong>Token:</strong>{' '}
if (!currentUser) this.setState({ redirect: "/home" }); {token ? `${token.substring(0, 20)} ... ${token.substring(token.length - 20)}` : 'N/A'}
this.setState({ currentUser: currentUser, userReady: true }) </p>
} <p>
<strong>Id:</strong> {user.id}
render() { </p>
if (this.state.redirect) { <p>
return <Navigate to={this.state.redirect} /> <strong>Email:</strong> {user.email}
} </p>
<strong>Authorities:</strong>
const { currentUser } = this.state; <ul>
{user.roles.map((role, index) => (
return ( <li key={index}>{role}</li>
<div className="container"> ))}
{(this.state.userReady) ? </ul>
<div>
<header className="jumbotron">
<h3>
<strong>{currentUser.username}</strong> Profile
</h3>
</header>
<p>
<strong>Token:</strong>{" "}
{currentUser.accessToken.substring(0, 20)} ...{" "}
{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
</p>
<p>
<strong>Id:</strong>{" "}
{currentUser.id}
</p>
<p>
<strong>Email:</strong>{" "}
{currentUser.email}
</p>
<strong>Authorities:</strong>
<ul>
{currentUser.roles &&
currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
</ul>
</div> : null}
</div> </div>
); </div>
} );
} }
@@ -34,6 +34,8 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No
const { treeData, viewData, currentNoteId, setCurrentNote } = useNoteStore(); const { treeData, viewData, currentNoteId, setCurrentNote } = useNoteStore();
const { openDeleteConfirm, setSearchQuery, searchQuery, setActiveView } = useUIStore(); const { openDeleteConfirm, setSearchQuery, searchQuery, setActiveView } = useUIStore();
const toNoteId = (id: string | number) => Number(id);
const loadNote = useCallback( const loadNote = useCallback(
(id: number) => { (id: number) => {
noteService.get(id).then( noteService.get(id).then(
@@ -81,7 +83,7 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No
size="small" size="small"
icon={<IconCopyAdd />} icon={<IconCopyAdd />}
onClick={(e) => { onClick={(e) => {
addNote(item.key); addNote(toNoteId(item.key));
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
@@ -90,7 +92,7 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No
type="warning" type="warning"
icon={<IconDelete />} icon={<IconDelete />}
onClick={(e) => { onClick={(e) => {
openDeleteConfirm(item.key, String(label)); openDeleteConfirm(toNoteId(item.key), String(label));
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
@@ -188,11 +190,11 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No
showFilteredOnly showFilteredOnly
directory directory
virtualize virtualize
value={currentNoteId} value={currentNoteId === null ? undefined : String(currentNoteId)}
treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']} treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']}
renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']} renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']}
style={treeStyle} style={treeStyle}
onSelect={(id) => loadNote(id as unknown as number)} onSelect={(id) => loadNote(toNoteId(id as string | number))}
/> />
</> </>
); );
@@ -9,17 +9,20 @@ import {
Toast, Toast,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { useNoteStore } from '../../stores/useNoteStore'; import { useNoteStore } from '../../stores/useNoteStore';
import { useUIStore } from '../../stores/useUIStore';
import noteService from '../../services/note.service'; import noteService from '../../services/note.service';
import type { INote } from '../../types'; import type { INote } from '../../types';
export default function HomePage() { export default function HomePage() {
const { homeData, setHomeData, setCurrentNote } = useNoteStore(); const { homeData, setHomeData, setCurrentNote } = useNoteStore();
const setActiveView = useUIStore((s) => s.setActiveView);
const loadNote = (id: number) => { const loadNote = (id: number) => {
noteService.get(id).then( noteService.get(id).then(
(res) => { (res) => {
const note = res.data; const note = res.data;
setCurrentNote(note.id, note.title, note.context); setCurrentNote(note.id, note.title, note.context);
setActiveView('note');
}, },
() => Toast.error('加载笔记失败'), () => Toast.error('加载笔记失败'),
); );
+5 -5
View File
@@ -63,8 +63,8 @@ export default function NotePage() {
setActiveView, setActiveView,
setSiderFlag, setSiderFlag,
setSideVisible, setSideVisible,
setDeleteModelVisible, setDeleteModalVisible,
setUpdatePasswordModelVisible, setUpdatePasswordModalVisible,
} = useUIStore(); } = useUIStore();
const [trashList, setTrashList] = useState<INote[]>([]); const [trashList, setTrashList] = useState<INote[]>([]);
@@ -102,13 +102,13 @@ export default function NotePage() {
noteService.getHomeData('4', '0').then( noteService.getHomeData('4', '0').then(
(res) => { (res) => {
setTrashList(res.data); setTrashList(res.data);
setDeleteModelVisible(true); setDeleteModalVisible(true);
}, },
() => Toast.error('加载回收站失败'), () => Toast.error('加载回收站失败'),
); );
} }
}, },
[setActiveView, setDeleteModelVisible], [setActiveView, setDeleteModalVisible],
); );
const handleSearch = useCallback(() => { const handleSearch = useCallback(() => {
@@ -244,7 +244,7 @@ export default function NotePage() {
theme="solid" theme="solid"
type="secondary" type="secondary"
style={{ marginRight: 8 }} style={{ marginRight: 8 }}
onClick={() => setUpdatePasswordModelVisible(true)} onClick={() => setUpdatePasswordModalVisible(true)}
> >
</Button> </Button>
+9 -9
View File
@@ -10,8 +10,8 @@ interface UIState {
deleteVisible: boolean; deleteVisible: boolean;
deleteId: number | null; deleteId: number | null;
deleteNoteName: string; deleteNoteName: string;
deleteModelVisible: boolean; deleteModalVisible: boolean;
updatePasswordModelVisible: boolean; updatePasswordModalVisible: boolean;
searchQuery: string; searchQuery: string;
setActiveView: (view: ActiveView) => void; setActiveView: (view: ActiveView) => void;
setSideVisible: (visible: boolean) => void; setSideVisible: (visible: boolean) => void;
@@ -19,8 +19,8 @@ interface UIState {
setNoteLoading: (loading: boolean) => void; setNoteLoading: (loading: boolean) => void;
openDeleteConfirm: (id: number, name: string) => void; openDeleteConfirm: (id: number, name: string) => void;
closeDeleteConfirm: () => void; closeDeleteConfirm: () => void;
setDeleteModelVisible: (visible: boolean) => void; setDeleteModalVisible: (visible: boolean) => void;
setUpdatePasswordModelVisible: (visible: boolean) => void; setUpdatePasswordModalVisible: (visible: boolean) => void;
setSearchQuery: (query: string) => void; setSearchQuery: (query: string) => void;
} }
@@ -32,8 +32,8 @@ export const useUIStore = create<UIState>()((set) => ({
deleteVisible: false, deleteVisible: false,
deleteId: null, deleteId: null,
deleteNoteName: '', deleteNoteName: '',
deleteModelVisible: false, deleteModalVisible: false,
updatePasswordModelVisible: false, updatePasswordModalVisible: false,
searchQuery: '', searchQuery: '',
setActiveView: (activeView) => set({ activeView }), setActiveView: (activeView) => set({ activeView }),
setSideVisible: (sideVisible) => set({ sideVisible }), setSideVisible: (sideVisible) => set({ sideVisible }),
@@ -43,8 +43,8 @@ export const useUIStore = create<UIState>()((set) => ({
set({ deleteVisible: true, deleteId, deleteNoteName }), set({ deleteVisible: true, deleteId, deleteNoteName }),
closeDeleteConfirm: () => closeDeleteConfirm: () =>
set({ deleteVisible: false, deleteId: null, deleteNoteName: '' }), set({ deleteVisible: false, deleteId: null, deleteNoteName: '' }),
setDeleteModelVisible: (deleteModelVisible) => set({ deleteModelVisible }), setDeleteModalVisible: (deleteModalVisible) => set({ deleteModalVisible }),
setUpdatePasswordModelVisible: (updatePasswordModelVisible) => setUpdatePasswordModalVisible: (updatePasswordModalVisible) =>
set({ updatePasswordModelVisible }), set({ updatePasswordModalVisible }),
setSearchQuery: (searchQuery) => set({ searchQuery }), setSearchQuery: (searchQuery) => set({ searchQuery }),
})); }));
+4 -1
View File
@@ -24,7 +24,10 @@ export interface INote {
} }
export interface ITreeNode { export interface ITreeNode {
key: number; id?: number;
pid?: number;
key: string;
value?: string;
label: string; label: string;
children?: ITreeNode[]; children?: ITreeNode[];
} }
+4 -3
View File
@@ -11,11 +11,12 @@
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": false,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
/* Path aliases */ /* Path aliases */
"ignoreDeprecations": "6.0",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
@@ -23,8 +24,8 @@
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": false,
"noUnusedParameters": true, "noUnusedParameters": false,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["src"], "include": ["src"],