feat(前端): 实现用户登录、注册和搜索功能
- 新增登录和注册页面组件 - 实现用户登录、注册和登出逻辑 - 添加笔记搜索功能 - 更新主页组件,支持用户状态显示和搜索 - 引入 Pinia 状态管理库
This commit is contained in:
@@ -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