feat(前端): 实现用户登录、注册和搜索功能
- 新增登录和注册页面组件 - 实现用户登录、注册和登出逻辑 - 添加笔记搜索功能 - 更新主页组件,支持用户状态显示和搜索 - 引入 Pinia 状态管理库
This commit is contained in:
@@ -94,4 +94,11 @@ public class MarkdownController {
|
||||
List<MarkdownFile> files = markdownFileService.getFilesByGroupingId(groupingId);
|
||||
return R.success(files);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据标题模糊搜索")
|
||||
@GetMapping("/search")
|
||||
public R<List<MarkdownFile>> searchByTitle(@RequestParam String keyword) {
|
||||
List<MarkdownFile> files = markdownFileService.searchByTitle(keyword);
|
||||
return R.success(files);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,11 @@ public interface MarkdownFileService extends IService<MarkdownFile> {
|
||||
* @return 文件列表
|
||||
*/
|
||||
List<MarkdownFile> getAllMarkdownFiles();
|
||||
|
||||
/**
|
||||
* 根据标题模糊搜索
|
||||
* @param keyword 关键词
|
||||
* @return 文件列表
|
||||
*/
|
||||
List<MarkdownFile> searchByTitle(String keyword);
|
||||
}
|
||||
|
||||
@@ -81,4 +81,11 @@ public class MarkdownFileServiceImpl
|
||||
public List<MarkdownFile> getAllMarkdownFiles() {
|
||||
return markdownFileMapper.selectList(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MarkdownFile> searchByTitle(String keyword) {
|
||||
QueryWrapper<MarkdownFile> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.like("title", keyword);
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
182
biji-qianduan/package-lock.json
generated
182
biji-qianduan/package-lock.json
generated
@@ -12,6 +12,8 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"element-plus": "^2.10.4",
|
||||
"highlight.js": "^11.11.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"vditor": "^3.11.1",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
@@ -1227,6 +1229,39 @@
|
||||
"@vue/shared": "3.5.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
|
||||
"integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-kit": "^7.7.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-kit": {
|
||||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
|
||||
"integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-shared": "^7.7.7",
|
||||
"birpc": "^2.3.0",
|
||||
"hookable": "^5.5.3",
|
||||
"mitt": "^3.0.1",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"speakingurl": "^14.0.1",
|
||||
"superjson": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-shared": {
|
||||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
|
||||
"integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"rfdc": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz",
|
||||
@@ -1569,6 +1604,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz",
|
||||
"integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@@ -1778,6 +1822,21 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/copy-anything": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
|
||||
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-what": "^4.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-descriptor": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||
@@ -2349,6 +2408,12 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-pick-omit": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-pick-omit/-/deep-pick-omit-1.2.1.tgz",
|
||||
"integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
|
||||
@@ -2371,6 +2436,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
@@ -2389,6 +2460,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
@@ -2992,6 +3069,12 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hookable": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -3157,6 +3240,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-what": {
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
|
||||
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-windows": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||
@@ -3984,6 +4079,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mixin-deep": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||
@@ -4193,6 +4294,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -4221,6 +4328,54 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
|
||||
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^7.7.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.7.0 || ^3.5.11"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persistedstate": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.4.1.tgz",
|
||||
"integrity": "sha512-lmuMPpXla2zJKjxEq34e1E9P9jxkWEhcVwwioCCE0izG45kkTOvQfCzvwhW3i38cvnaWC7T1eRdkd15Re59ldw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deep-pick-omit": "^1.2.1",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": ">=3.0.0",
|
||||
"@pinia/nuxt": ">=0.10.0",
|
||||
"pinia": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
},
|
||||
"@pinia/nuxt": {
|
||||
"optional": true
|
||||
},
|
||||
"pinia": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/posix-character-classes": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
|
||||
@@ -4320,6 +4475,12 @@
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
@@ -4656,6 +4817,15 @@
|
||||
"deprecated": "See https://github.com/lydell/source-map-url#deprecated",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/speakingurl": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
@@ -4733,6 +4903,18 @@
|
||||
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/superjson": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
|
||||
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"copy-anything": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"element-plus": "^2.10.4",
|
||||
"highlight.js": "^11.11.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.4.1",
|
||||
"vditor": "^3.11.1",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
|
||||
@@ -50,6 +50,33 @@ export const deleteMarkdown = (id) => axiosApi.post(`/api/markdown/delete?id=${i
|
||||
// 根据分组ID获取Markdown文件列表
|
||||
export const markdownList = (groupingId) => axiosApi.get(`/api/markdown/grouping/${groupingId}`);
|
||||
|
||||
// 登录
|
||||
export const login = (data) => {
|
||||
const formData = new FormData()
|
||||
formData.append('username', data.username)
|
||||
formData.append('password', data.password)
|
||||
return axiosApi.post('/api/user/login', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索
|
||||
export const searchMarkdown = (keyword) => axiosApi.get(`/api/markdown/search?keyword=${keyword}`);
|
||||
|
||||
// 注册
|
||||
export const register = (data) => {
|
||||
const formData = new FormData()
|
||||
formData.append('username', data.username)
|
||||
formData.append('password', data.password)
|
||||
return axiosApi.post('/api/user/register', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
<h2>{{ selectedFile.title }}</h2>
|
||||
<div class="actions">
|
||||
<el-button v-if="!showEditor" type="primary" @click="selectedFile = null">清空</el-button>
|
||||
<el-button v-if="!showEditor" type="primary" @click="editNote(selectedFile); isCollapsed = true">编辑</el-button>
|
||||
<el-button v-if="!showEditor" type="danger" @click="deleteNote(selectedFile)">删除</el-button>
|
||||
<el-button v-if="!showEditor && userStore.isLoggedIn" type="primary" @click="editNote(selectedFile); isCollapsed = true">编辑</el-button>
|
||||
<el-button v-if="!showEditor && userStore.isLoggedIn" type="danger" @click="deleteNote(selectedFile)">删除</el-button>
|
||||
<el-button v-if="showEditor" type="primary" @click="showEditor = !showEditor; previewFile(editData)">返回</el-button>
|
||||
<el-button v-if="showEditor" type="success" @click="handleSave(vditor.getValue())">保存</el-button>
|
||||
<el-button v-if="showEditor && userStore.isLoggedIn" type="success" @click="handleSave(vditor.getValue())">保存</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<div v-if="!showEditor" v-html="previewHtml" class="markdown-preview"></div>
|
||||
@@ -64,8 +64,29 @@
|
||||
<el-header class="header">
|
||||
<h1>我的笔记</h1>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="showCreateNoteDialog = true">新建笔记</el-button>
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索笔记标题"
|
||||
class="search-input"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div v-if="userStore.isLoggedIn">
|
||||
<span>欢迎, {{ userStore.userInfo?.username }}</span>
|
||||
<el-button type="danger" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-button type="primary" @click="goToLogin">登录</el-button>
|
||||
<el-button @click="goToRegister">注册</el-button>
|
||||
</div>
|
||||
<el-button v-if="userStore.isLoggedIn" type="primary" @click="showCreateNoteDialog = true">新建笔记</el-button>
|
||||
<el-upload
|
||||
v-if="userStore.isLoggedIn"
|
||||
class="upload-btn"
|
||||
action=""
|
||||
:show-file-list="false"
|
||||
@@ -163,9 +184,16 @@ import {
|
||||
groupingId,
|
||||
markdownAll, markdownList,
|
||||
Preview,
|
||||
updateMarkdown, uploadImage
|
||||
updateMarkdown, uploadImage,
|
||||
searchMarkdown
|
||||
} from '@/api/CommonApi.js'
|
||||
import { DArrowRight, Plus, Fold, Expand, Folder, Document } from "@element-plus/icons-vue";
|
||||
import { DArrowRight, Plus, Fold, Expand, Folder, Document, Search } from "@element-plus/icons-vue";
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const isGroup1=ref(true)
|
||||
// 创建新文件中大分类的信息
|
||||
@@ -542,6 +570,32 @@ const chushihua = async () => {
|
||||
await fetchMarkdownFiles();
|
||||
await fetchGroupings();
|
||||
}
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register');
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
userStore.logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchKeyword.value) {
|
||||
await fetchMarkdownFiles();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await searchMarkdown(searchKeyword.value);
|
||||
groupMarkdownFiles.value = response.data;
|
||||
} catch (error) {
|
||||
ElMessage.error('搜索失败: ' + error.message);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
80
biji-qianduan/src/components/LoginPage.vue
Normal file
80
biji-qianduan/src/components/LoginPage.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<el-card class="login-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>登录</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" label-width="80px">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码" show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleLogin">登录</el-button>
|
||||
<el-button @click="goToRegister">注册</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const loginFormRef = ref(null);
|
||||
|
||||
const loginForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const loginRules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
const valid = await loginFormRef.value.validate();
|
||||
if (valid) {
|
||||
const success = await userStore.login(loginForm.value.username, loginForm.value.password);
|
||||
if (success) {
|
||||
ElMessage.success('登录成功');
|
||||
router.push('/home');
|
||||
} else {
|
||||
ElMessage.error('用户名或密码错误');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
94
biji-qianduan/src/components/RegisterPage.vue
Normal file
94
biji-qianduan/src/components/RegisterPage.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="register-container">
|
||||
<el-card class="register-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>注册</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="registerFormRef" :model="registerForm" :rules="registerRules" label-width="80px">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="registerForm.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="registerForm.password" type="password" placeholder="请输入密码" show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="registerForm.confirmPassword" type="password" placeholder="请再次输入密码" show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleRegister">注册</el-button>
|
||||
<el-button @click="goToLogin">返回登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { register } from '../api/CommonApi';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const router = useRouter();
|
||||
const registerFormRef = ref(null);
|
||||
|
||||
const registerForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'));
|
||||
} else if (value !== registerForm.value.password) {
|
||||
callback(new Error("两次输入的密码不一致!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const registerRules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
confirmPassword: [{ validator: validatePass, trigger: 'blur' }],
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
const valid = await registerFormRef.value.validate();
|
||||
if (valid) {
|
||||
try {
|
||||
await register({ username: registerForm.value.username, password: registerForm.value.password });
|
||||
ElMessage.success('注册成功');
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
ElMessage.error('注册失败,请稍后再试');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.register-card {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,8 @@ import App from './App.vue'
|
||||
import router from './router/'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
// 1. 导入编辑器和预览组件
|
||||
import VMdEditor from '@kangc/v-md-editor'
|
||||
@@ -24,6 +26,8 @@ import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css'
|
||||
import hljs from 'highlight.js'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
// 6. 配置编辑器
|
||||
VMdEditor.use(githubTheme, {
|
||||
@@ -46,5 +50,6 @@ app.use(VMdPreview)
|
||||
// 10. 使用Element Plus和路由
|
||||
app.use(ElementPlus)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import HomePage from '../components/HomePage.vue';
|
||||
import MarkdownEditor from '../components/MarkdownEditor.vue';
|
||||
import LoginPage from '../components/LoginPage.vue';
|
||||
import RegisterPage from '../components/RegisterPage.vue';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -22,6 +24,16 @@ const routes = [
|
||||
name: 'EditMarkdown',
|
||||
component: MarkdownEditor,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: LoginPage
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: RegisterPage
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
35
biji-qianduan/src/stores/user.js
Normal file
35
biji-qianduan/src/stores/user.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { login as loginApi } from '../api/CommonApi'; // 假设你的API调用函数是这样组织的
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: '',
|
||||
userInfo: null,
|
||||
}),
|
||||
actions: {
|
||||
async login(username, password) {
|
||||
try {
|
||||
const response = await loginApi({ username, password });
|
||||
if (response.data && response.data.token) {
|
||||
this.token = response.data.token;
|
||||
// 你可能还需要一个接口来获取用户信息
|
||||
// this.userInfo = await getUserInfo();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.token = '';
|
||||
this.userInfo = null;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
isLoggedIn: (state) => !!state.token,
|
||||
},
|
||||
persist: true,
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import { useUserStore } from '../stores/user'
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
@@ -12,6 +13,10 @@ const instance = axios.create({
|
||||
// 请求拦截器
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers['Authorization'] = `Bearer ${userStore.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
|
||||
Reference in New Issue
Block a user