From a7cb3dc2c715b4a0d3d085eba9581b5a692c051c Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 18:25:57 +0800
Subject: [PATCH 01/11] =?UTF-8?q?feat(components):=20=E5=9C=A8=E7=99=BB?=
=?UTF-8?q?=E5=BD=95=E5=92=8C=E6=B3=A8=E5=86=8C=E9=A1=B5=E9=9D=A2=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E8=BF=94=E5=9B=9E=E9=A6=96=E9=A1=B5=E6=8C=89=E9=92=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 LoginPage.vue 和 RegisterPage.vue 中添加返回首页按钮
- 实现 goToHome 方法,用于跳转到首页- 优化用户操作流程,提供更便捷的页面导航
---
biji-qianduan/src/components/LoginPage.vue | 5 +++++
biji-qianduan/src/components/RegisterPage.vue | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/biji-qianduan/src/components/LoginPage.vue b/biji-qianduan/src/components/LoginPage.vue
index 8d7b202..8bc0e97 100644
--- a/biji-qianduan/src/components/LoginPage.vue
+++ b/biji-qianduan/src/components/LoginPage.vue
@@ -16,6 +16,7 @@
登录
注册
+ 返回首页
@@ -58,6 +59,10 @@ const handleLogin = async () => {
const goToRegister = () => {
router.push('/register');
};
+
+const goToHome = () => {
+ router.push('/home');
+};
From 384ac43370137b2b695397243827492dc032421d Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 18:50:52 +0800
Subject: [PATCH 03/11] =?UTF-8?q?feat(menu):=20=E6=B7=BB=E5=8A=A0=E8=8F=9C?=
=?UTF-8?q?=E5=8D=95=E9=A1=B9=E9=9A=90=E8=97=8F=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 ElSubMenu 组件中添加 popperClass 属性,根据菜单是否折叠来决定是否隐藏- 在样式中添加 .hide-popper 类,用于隐藏菜单项
---
biji-qianduan/src/components/HomePage.vue | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue
index 6a4fbbb..569bc18 100644
--- a/biji-qianduan/src/components/HomePage.vue
+++ b/biji-qianduan/src/components/HomePage.vue
@@ -448,7 +448,10 @@ const renderMenu = (item) => {
});
if (item.children && item.children.length > 0) {
- return h(ElSubMenu, { index: `group-${item.id}` }, {
+ return h(ElSubMenu, {
+ index: `group-${item.id}`,
+ popperClass: isCollapsed.value ? 'hide-popper' : ''
+ }, {
title: () => h('div', { onClick: () => selectFile(item), style: 'width: 100%;' }, [ wrappedContent() ]),
default: () => item.children.map(child => renderMenu(child))
});
@@ -837,6 +840,10 @@ watch(activeMenu, (newVal) => {
background-color: var(--primary-color-light) !important;
}
+.hide-popper {
+ display: none !important;
+}
+
/* 移除预览中代码块的内部滚动条 */
.markdown-preview .vditor-reset pre {
max-height: none !important;
From 8cbd5b02b32567e2bdc2d0a30203c52519f8647c Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 19:21:58 +0800
Subject: [PATCH 04/11] =?UTF-8?q?feat(recycle-bin):=20=E5=AE=9E=E7=8E=B0?=
=?UTF-8?q?=E5=9B=9E=E6=94=B6=E7=AB=99=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增回收站相关 API 接口
- 添加回收站页面组件和路由
- 实现笔记和分类的软删除功能
- 支持回收站内容的获取、恢复和永久删除操作
- 优化用户界面,增加回收站入口和相关提示
---
biji-qianduan/package-lock.json | 197 +++++++++++++++++++++
biji-qianduan/package.json | 2 +
biji-qianduan/src/api/CommonApi.js | 12 ++
biji-qianduan/src/components/HomePage.vue | 149 +++++++++++++++-
biji-qianduan/src/components/TrashPage.vue | 104 +++++++++++
biji-qianduan/src/router/index.js | 6 +
回收站功能设计.md | 22 +++
7 files changed, 485 insertions(+), 7 deletions(-)
create mode 100644 biji-qianduan/src/components/TrashPage.vue
create mode 100644 回收站功能设计.md
diff --git a/biji-qianduan/package-lock.json b/biji-qianduan/package-lock.json
index 72afe44..377d74a 100644
--- a/biji-qianduan/package-lock.json
+++ b/biji-qianduan/package-lock.json
@@ -12,6 +12,8 @@
"codemirror": "^6.0.1",
"element-plus": "^2.10.4",
"highlight.js": "^11.11.1",
+ "html2canvas": "^1.4.1",
+ "jspdf": "^3.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"vditor": "^3.11.1",
@@ -1132,6 +1134,20 @@
"undici-types": "~7.8.0"
}
},
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@@ -1604,6 +1620,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/birpc": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz",
@@ -1665,6 +1690,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+ "license": "(MIT OR Apache-2.0)",
+ "bin": {
+ "btoa": "bin/btoa.js"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/cache-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@@ -1691,6 +1728,26 @@
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"license": "MIT"
},
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -1855,6 +1912,18 @@
"toggle-selection": "^1.0.6"
}
},
+ "node_modules/core-js": {
+ "version": "3.44.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz",
+ "integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/cose-base": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
@@ -1870,6 +1939,15 @@
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
@@ -2795,6 +2873,12 @@
}
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -3075,6 +3159,19 @@
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -3304,6 +3401,34 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jspdf": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz",
+ "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.7",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.11",
+ "core-js": "^3.6.0",
+ "dompurify": "^3.2.4",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "node_modules/jspdf/node_modules/dompurify": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
+ "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optional": true,
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/katex": {
"version": "0.13.24",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
@@ -4300,6 +4425,13 @@
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4422,6 +4554,23 @@
"node": ">=6"
}
},
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -4481,6 +4630,16 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
@@ -4844,6 +5003,16 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -4927,6 +5096,25 @@
"node": ">=4"
}
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
@@ -5149,6 +5337,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
diff --git a/biji-qianduan/package.json b/biji-qianduan/package.json
index b089ac1..98b8b70 100644
--- a/biji-qianduan/package.json
+++ b/biji-qianduan/package.json
@@ -13,6 +13,8 @@
"codemirror": "^6.0.1",
"element-plus": "^2.10.4",
"highlight.js": "^11.11.1",
+ "html2canvas": "^1.4.1",
+ "jspdf": "^3.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"vditor": "^3.11.1",
diff --git a/biji-qianduan/src/api/CommonApi.js b/biji-qianduan/src/api/CommonApi.js
index 4f44429..bd3a719 100644
--- a/biji-qianduan/src/api/CommonApi.js
+++ b/biji-qianduan/src/api/CommonApi.js
@@ -115,3 +115,15 @@ export const MD5 = (data, file) => {
+
+// 获取回收站内容
+export const getTrash = () => axiosApi.get('/api/trash');
+
+// 恢复项目
+export const restoreTrashItem = (id) => axiosApi.post(`/api/trash/restore/${id}`);
+
+// 彻底删除
+export const permanentlyDeleteItem = (id) => axiosApi.delete(`/api/trash/permanently/${id}`);
+
+// 清空回收站
+export const cleanTrash = () => axiosApi.delete('/api/trash/clean');
diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue
index 569bc18..5e62255 100644
--- a/biji-qianduan/src/components/HomePage.vue
+++ b/biji-qianduan/src/components/HomePage.vue
@@ -26,6 +26,10 @@
+
+
+ 回收站
+
@@ -46,7 +50,18 @@
返回
保存
{{ saveStatus }}
- 导出为.md
+
+
+ 导出
+
+
+
+ 导出为 .md
+ 导出为 .pdf
+ 导出为 .html
+
+
+
@@ -212,7 +227,7 @@ import {
deleteGrouping as apiDeleteGrouping,
getRecentFiles
} from '@/api/CommonApi.js'
-import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete } from "@element-plus/icons-vue";
+import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete, ArrowDown, Clock } from "@element-plus/icons-vue";
import { useUserStore } from '../stores/user';
import { useRouter } from 'vue-router';
@@ -715,18 +730,134 @@ const handleSearch = async () => {
}
};
-const handleExportMd = () => {
- if (!selectedFile.value) return;
- const blob = new Blob([selectedFile.value.content], { type: 'text/markdown;charset=utf-8' });
+import jsPDF from 'jspdf';
+import html2canvas from 'html2canvas';
+
+
+const showExportLoading = ref(false);
+
+// 文件名特殊字符清理
+const sanitizeFilename = (name) => name.replace(/[<>:"/\\|?*]/g, '_').trim() || '未命名笔记';
+
+const handleExport = async (format) => {
+ if (!selectedFile.value || showExportLoading.value) return;
+
+ const title = sanitizeFilename(selectedFile.value.title);
+ const content = selectedFile.value.content;
+ const previewElement = document.querySelector('.markdown-preview');
+
+ if (!previewElement) {
+ ElMessage.error('无法找到预览区域');
+ return;
+ }
+
+ showExportLoading.value = true;
+ ElMessage.info(`正在导出为 ${format.toUpperCase()}...`);
+
+ try {
+ switch (format) {
+ case 'md':
+ exportAsMd(title, content);
+ break;
+ case 'pdf':
+ await exportAsPdf(title, previewElement);
+ break;
+ case 'html':
+ exportAsHtml(title, previewElement.innerHTML);
+ break;
+ }
+ ElMessage.success(`${format.toUpperCase()} 导出成功`);
+ } catch (error) {
+ console.error('Export failed:', error);
+ ElMessage.error(`导出失败: ${error.message}`);
+ } finally {
+ showExportLoading.value = false;
+ }
+};
+
+const exportAsMd = (title, content) => {
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
+ downloadBlob(blob, `${title}.md`);
+};
+
+const exportAsPdf = async (title, element) => {
+ const canvas = await html2canvas(element, {
+ scale: 2,
+ useCORS: true,
+ logging: false,
+ });
+
+ const pdf = new jsPDF({
+ orientation: 'p',
+ unit: 'mm',
+ format: 'a4',
+ });
+
+ const pageHeight = pdf.internal.pageSize.getHeight() - 20; // 减去页边距
+ const pageWidth = pdf.internal.pageSize.getWidth() - 20;
+ const imgWidth = pageWidth;
+ const imgHeight = (canvas.height * imgWidth) / canvas.width;
+ let heightLeft = imgHeight;
+ let position = 10; // 初始Y轴位置
+ const imgData = canvas.toDataURL('image/png');
+
+ pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
+ heightLeft -= pageHeight;
+
+ let pageCount = 1;
+ while (heightLeft > 0) {
+ pageCount++;
+ position = -pageHeight * (pageCount - 1) + 10;
+ pdf.addPage();
+ pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
+ heightLeft -= pageHeight;
+ }
+
+ // 添加页眉和页脚
+ for (let i = 1; i <= pageCount; i++) {
+ pdf.setPage(i);
+ pdf.setFontSize(8);
+ pdf.setTextColor(150);
+ pdf.text(title, pdf.internal.pageSize.getWidth() / 2, 8, { align: 'center' });
+ pdf.text(`第 ${i} 页 / 共 ${pageCount} 页`, pdf.internal.pageSize.getWidth() / 2, pdf.internal.pageSize.getHeight() - 8, { align: 'center' });
+ }
+
+ pdf.save(`${title}.pdf`);
+};
+
+const exportAsHtml = (title, htmlContent) => {
+ const fullHtml = `
+
+
+
+
+ ${title}
+
+
+
+
+ ${title}
+ ${htmlContent}
+
+
+ `;
+ const blob = new Blob([fullHtml], { type: 'text/html;charset=utf-8' });
+ downloadBlob(blob, `${title}.html`);
+};
+
+const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
- link.download = `${selectedFile.value.title}.md`;
+ link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
-};
+}
const goToLogin = () => {
router.push('/login');
@@ -742,6 +873,10 @@ const handleLogout = () => {
router.push('/login');
};
+const goToTrash = () => {
+ router.push({ name: 'Trash' });
+};
+
const resetToHomeView = async () => {
selectedFile.value = null;
showEditor.value = false;
diff --git a/biji-qianduan/src/components/TrashPage.vue b/biji-qianduan/src/components/TrashPage.vue
new file mode 100644
index 0000000..268ed4c
--- /dev/null
+++ b/biji-qianduan/src/components/TrashPage.vue
@@ -0,0 +1,104 @@
+
+
+
+ 回收站
+ 清空回收站
+ 返回首页
+
+
+
+
+
+
+
+ {{ scope.row.type === 'group' ? '分类' : '笔记' }}
+
+
+
+
+
+
+ 恢复
+ 永久删除
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/biji-qianduan/src/router/index.js b/biji-qianduan/src/router/index.js
index eea42c0..97e135d 100644
--- a/biji-qianduan/src/router/index.js
+++ b/biji-qianduan/src/router/index.js
@@ -3,6 +3,7 @@ import HomePage from '../components/HomePage.vue';
import MarkdownEditor from '../components/MarkdownEditor.vue';
import LoginPage from '../components/LoginPage.vue';
import RegisterPage from '../components/RegisterPage.vue';
+import TrashPage from '../components/TrashPage.vue';
const routes = [
{
@@ -34,6 +35,11 @@ const routes = [
path: '/register',
name: 'Register',
component: RegisterPage
+ },
+ {
+ path: '/trash',
+ name: 'Trash',
+ component: TrashPage
}
];
diff --git a/回收站功能设计.md b/回收站功能设计.md
new file mode 100644
index 0000000..65a66e8
--- /dev/null
+++ b/回收站功能设计.md
@@ -0,0 +1,22 @@
+# 回收站功能设计方案
+
+## 功能需求
+- 实现笔记和分类的软删除功能
+- 提供30天数据保留期
+- 支持恢复和永久删除操作
+
+## 前端修改
+1. 侧边栏添加回收站入口
+2. 删除操作改为"移至回收站"
+3. 新建TrashPage.vue组件
+
+## 后端修改
+```sql
+ALTER TABLE markdown ADD COLUMN is_deleted BOOLEAN DEFAULT false;
+ALTER TABLE markdown ADD COLUMN deleted_at TIMESTAMP;
+```
+
+## API接口
+- GET /api/trash - 获取回收站内容
+- POST /api/trash/restore - 恢复项目
+- DELETE /api/trash/clean - 清空回收站
\ No newline at end of file
From 16998c514419e522791df66ddcd68843456e8be6 Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 19:26:34 +0800
Subject: [PATCH 05/11] =?UTF-8?q?style(qianduan):=20=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E5=AD=90=E8=8F=9C=E5=8D=95=E6=A0=87=E9=A2=98=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 为子菜单标题添加样式类 'submenu-title-wrapper'
- 在标题容器内添加 flex 布局,使内容垂直居中
---
biji-qianduan/src/components/HomePage.vue | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue
index 5e62255..8dc27cf 100644
--- a/biji-qianduan/src/components/HomePage.vue
+++ b/biji-qianduan/src/components/HomePage.vue
@@ -467,7 +467,11 @@ const renderMenu = (item) => {
index: `group-${item.id}`,
popperClass: isCollapsed.value ? 'hide-popper' : ''
}, {
- title: () => h('div', { onClick: () => selectFile(item), style: 'width: 100%;' }, [ wrappedContent() ]),
+ title: () => h('div', {
+ class: 'submenu-title-wrapper',
+ onClick: () => selectFile(item),
+ style: 'width: 100%; display: flex; align-items: center;'
+ }, [ wrappedContent() ]),
default: () => item.children.map(child => renderMenu(child))
});
}
From cd43768bafecd7c6d2cec1b1dabc9fda0d15fe7e Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 19:39:59 +0800
Subject: [PATCH 06/11] =?UTF-8?q?feat(security):=20=E6=B7=BB=E5=8A=A0=20To?=
=?UTF-8?q?ken=20=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 CommonApi.js 中添加 validateToken 函数,用于验证 Token 有效性
- 在 HomePage.vue 中集成 Token 验证功能,导出前验证登录状态- 在 UserController.java 中添加 validateToken 接口,用于后端验证 Token
---
.../bijihoudaun/controller/UserController.java | 7 ++++++-
biji-qianduan/src/api/CommonApi.js | 3 +++
biji-qianduan/src/components/HomePage.vue | 14 ++++++++++++--
3 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java
index 51f885e..75ea2ad 100644
--- a/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java
+++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/controller/UserController.java
@@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@@ -57,6 +58,10 @@ public class UserController {
return R.success("删除成功");
}
-
+ @Operation(summary = "验证Token有效性")
+ @PostMapping("/validate-token")
+ public R validateToken() {
+ return R.success("Token is valid");
+ }
}
diff --git a/biji-qianduan/src/api/CommonApi.js b/biji-qianduan/src/api/CommonApi.js
index bd3a719..cf4d372 100644
--- a/biji-qianduan/src/api/CommonApi.js
+++ b/biji-qianduan/src/api/CommonApi.js
@@ -127,3 +127,6 @@ export const permanentlyDeleteItem = (id) => axiosApi.delete(`/api/trash/permane
// 清空回收站
export const cleanTrash = () => axiosApi.delete('/api/trash/clean');
+
+// 验证Token
+export const validateToken = () => axiosApi.post('/api/user/validate-token');
diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue
index 8dc27cf..9465c02 100644
--- a/biji-qianduan/src/components/HomePage.vue
+++ b/biji-qianduan/src/components/HomePage.vue
@@ -50,7 +50,7 @@
返回
保存
{{ saveStatus }}
-
+
导出
@@ -225,7 +225,8 @@ import {
updateGroupingName,
updateMarkdownTitle,
deleteGrouping as apiDeleteGrouping,
- getRecentFiles
+ getRecentFiles,
+ validateToken
} from '@/api/CommonApi.js'
import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete, ArrowDown, Clock } from "@element-plus/icons-vue";
import { useUserStore } from '../stores/user';
@@ -746,6 +747,15 @@ const sanitizeFilename = (name) => name.replace(/[<>:"/\\|?*]/g, '_').trim() ||
const handleExport = async (format) => {
if (!selectedFile.value || showExportLoading.value) return;
+ try {
+ await validateToken();
+ } catch (error) {
+ ElMessage.error('登录已过期,请重新登录');
+ userStore.logout();
+ router.push('/login');
+ return;
+ }
+
const title = sanitizeFilename(selectedFile.value.title);
const content = selectedFile.value.content;
const previewElement = document.querySelector('.markdown-preview');
From 56633dfd3b3602804708062bdb5cd5dde64487be Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Thu, 31 Jul 2025 22:07:00 +0800
Subject: [PATCH 07/11] =?UTF-8?q?feat:=E9=87=8D=E6=9E=84=20UI=20=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F=E5=B9=B6=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93?=
=?UTF-8?q?=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 更新了全局样式,增加了更多主题颜色和样式
- 重新设计了首页、登录和注册页面的布局和样式
- 增加了暗黑主题支持
- 优化了表单元素和按钮的样式- 调整了字体颜色和背景渐变
---
biji-qianduan/src/assets/styles/global.css | 5 +-
biji-qianduan/src/assets/styles/theme.css | 88 +++++----
biji-qianduan/src/components/HomePage.vue | 186 +++++++++++++-----
biji-qianduan/src/components/LoginPage.vue | 160 +++++++++++----
biji-qianduan/src/components/RegisterPage.vue | 166 ++++++++++++----
5 files changed, 440 insertions(+), 165 deletions(-)
diff --git a/biji-qianduan/src/assets/styles/global.css b/biji-qianduan/src/assets/styles/global.css
index 5277f59..a64d0fc 100644
--- a/biji-qianduan/src/assets/styles/global.css
+++ b/biji-qianduan/src/assets/styles/global.css
@@ -7,9 +7,10 @@ body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- background-color: var(--bg-color);
+ background: var(--bg-gradient);
color: var(--text-color);
- transition: background-color var(--transition-duration), color var(--transition-duration);
+ transition: background var(--transition-duration), color var(--transition-duration);
+ background-attachment: fixed;
}
/* 滚动条美化 */
diff --git a/biji-qianduan/src/assets/styles/theme.css b/biji-qianduan/src/assets/styles/theme.css
index df4b1ed..31ef241 100644
--- a/biji-qianduan/src/assets/styles/theme.css
+++ b/biji-qianduan/src/assets/styles/theme.css
@@ -2,46 +2,66 @@
/* 默认浅色主题 */
:root {
+ /* 主色调 - 活力紫 */
+ --primary-color: #6a11cb;
+ --primary-color-light: #f3e5f5; /* 浅紫色,用于悬停 */
+ --primary-color-dark: #4a008f; /* 深紫色,用于激活 */
+
+ /* 辅助色 */
+ --secondary-color: #f7fafc;
+ --accent-color: #2575fc; /* 亮蓝色,用于强调 */
+ --danger-color: #f5365c;
+ --success-color: #2dce89;
+ --warning-color: #fb6340;
+
+ /* 背景色 - 带有色彩倾向的渐变 */
--bg-color: #ffffff;
- --bg-color-secondary: #f7f8fa;
- --bg-color-tertiary: #eff2f5;
- --text-color: #303133;
- --text-color-secondary: #606266;
- --text-color-placeholder: #a8abb2;
- --border-color: #dcdfe6;
- --border-color-light: #e4e7ed;
- --primary-color: #409eff;
- --primary-color-light: #ecf5ff;
- --danger-color: #f56c6c;
- --success-color: #67c23a;
- --warning-color: #e6a23c;
- --info-color: #909399;
+ --bg-color-secondary: #f8f9fe;
+ --bg-gradient: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
- --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
- --box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.08);
- --box-shadow-dark: 0 4px 16px rgba(0, 0, 0, 0.15);
+ /* 文本色 */
+ --text-color: #32325d; /* 深蓝灰色 */
+ --text-color-secondary: #6b7c93; /* 灰蓝色 */
+ --text-color-placeholder: #adb5bd;
- --transition-duration: 0.3s;
+ /* 边框和阴影 */
+ --border-color: #e9ecef;
+ --border-radius: 8px;
+ --box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
+ --box-shadow-light: 0 2px 4px rgba(50, 50, 93, 0.1);
+ --box-shadow-dark: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
+
+ /* 过渡 */
+ --transition-duration: 0.25s;
}
/* 深色主题 */
.dark-theme {
- --bg-color: #141414;
- --bg-color-secondary: #1a1a1a;
- --bg-color-tertiary: #222222;
- --text-color: #e5e5e5;
- --text-color-secondary: #a3a3a3;
- --text-color-placeholder: #5c5c5c;
- --border-color: #3a3a3a;
- --border-color-light: #2a2a2a;
- --primary-color: #409eff;
- --primary-color-light: #26334f;
- --danger-color: #f56c6c;
- --success-color: #67c23a;
- --warning-color: #e6a23c;
- --info-color: #909399;
+ /* 主色调 - 科技蓝 */
+ --primary-color: #007bff;
+ --primary-color-light: #1f2937;
+ --primary-color-dark: #0056b3;
- --box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
- --box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.25);
- --box-shadow-dark: 0 4px 16px rgba(0, 0, 0, 0.4);
+ /* 辅助色 */
+ --secondary-color: #111827;
+ --accent-color: #10b981; /* 亮绿色 */
+ --danger-color: #ef4444;
+ --success-color: #22c55e;
+ --warning-color: #f97316;
+
+ /* 背景色 - 深邃的渐变 */
+ --bg-color: #0d1117;
+ --bg-color-secondary: #1f2937;
+ --bg-gradient: linear-gradient(135deg, #0d1117 0%, #1f2937 100%);
+
+ /* 文本色 */
+ --text-color: #e5e7eb;
+ --text-color-secondary: #9ca3af;
+ --text-color-placeholder: #4b5563;
+
+ /* 边框和阴影 */
+ --border-color: #374151;
+ --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2), 0 1px 3px rgba(0, 0, 0, 0.15);
+ --box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.18);
+ --box-shadow-dark: 0 7px 14px rgba(0, 0, 0, 0.2), 0 3px 6px rgba(0, 0, 0, 0.15);
}
\ No newline at end of file
diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue
index 9465c02..29985ed 100644
--- a/biji-qianduan/src/components/HomePage.vue
+++ b/biji-qianduan/src/components/HomePage.vue
@@ -71,7 +71,7 @@