@ -0,0 +1,27 @@
|
||||
// src/api/api.js
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = 'http://localhost:8080/api/users';
|
||||
|
||||
export const registerUser = async (user) => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/register`, user);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || 'Registration failed');
|
||||
}
|
||||
};
|
||||
|
||||
export const loginUser = async (loginAccount, password) => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
loginAccount,
|
||||
password
|
||||
});
|
||||
|
||||
const response = await axios.post(`${API_URL}/login`, params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || 'Login failed');
|
||||
}
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// 假设你的后端 API 地址是 http://localhost:8080
|
||||
const API_URL = 'http://localhost:8080/api/articles';
|
||||
|
||||
export default {
|
||||
// 获取所有文章
|
||||
getAllArticles() {
|
||||
return axios.get(`${API_URL}`);
|
||||
},
|
||||
|
||||
// 获取最新文章
|
||||
getLatestArticles() {
|
||||
return axios.get(`${API_URL}/latest`);
|
||||
},
|
||||
|
||||
// 获取热门文章
|
||||
getPopularArticles() {
|
||||
return axios.get(`${API_URL}/popular`);
|
||||
},
|
||||
|
||||
// 获取推荐文章
|
||||
getRecommendedArticles() {
|
||||
return axios.get(`${API_URL}/recommended`);
|
||||
},
|
||||
|
||||
// 获取当前用户的文章列表
|
||||
getArticlesByCurrentUser() {
|
||||
return axios.get(`${API_URL}/user/me`);
|
||||
},
|
||||
|
||||
// 创建新文章
|
||||
createArticle(article) {
|
||||
return axios.post(`${API_URL}/create`, article);
|
||||
},
|
||||
|
||||
// 获取所有未批准的文章(仅限管理员)
|
||||
getUnapprovedArticles() {
|
||||
return axios.get(`${API_URL}/unapproved`);
|
||||
},
|
||||
|
||||
// 审批文章(仅限管理员)
|
||||
approveArticle(id) {
|
||||
return axios.put(`${API_URL}/approve/${id}`);
|
||||
},
|
||||
|
||||
// 拒绝文章(仅限管理员)
|
||||
rejectArticle(id) {
|
||||
return axios.put(`${API_URL}/reject/${id}`);
|
||||
},
|
||||
|
||||
// 删除文章(仅限管理员)
|
||||
deleteArticle(id) {
|
||||
return axios.delete(`${API_URL}/${id}`);
|
||||
}
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder; // 用于加密密码
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils; // 用于生成 JWT Token
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<Map<String, Object>> login(@RequestBody Map<String, String> credentials) {
|
||||
String loginAccount = credentials.get("loginAccount");
|
||||
String password = credentials.get("password");
|
||||
|
||||
// 查找用户
|
||||
User user = userRepository.findByLoginAccount(loginAccount)
|
||||
.orElse(null);
|
||||
|
||||
if (user != null && passwordEncoder.matches(password, user.getPassword())) {
|
||||
// 登录成功,生成 JWT Token
|
||||
String token = jwtUtils.generateToken(user);
|
||||
|
||||
// 返回用户信息和 Token
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("user", user);
|
||||
response.put("token", token);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} else {
|
||||
// 登录失败
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", "账号或密码错误");
|
||||
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// 使用 import.meta.env 访问环境变量
|
||||
const apiBaseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api';
|
||||
|
||||
// 创建一个 Axios 实例,设置默认配置
|
||||
const apiClient = axios.create({
|
||||
baseURL: apiBaseURL,
|
||||
withCredentials: true, // 确保携带凭证
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
});
|
||||
|
||||
export const registerUser = async (user) => {
|
||||
try {
|
||||
const response = await apiClient.post('/users/register', user);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const loginUser = async (loginAccount, password) => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
loginAccount,
|
||||
password
|
||||
});
|
||||
|
||||
const response = await apiClient.post('/users/login', params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error.response?.data || error.message);
|
||||
throw new Error('Login failed');
|
||||
}
|
||||
};
|
||||
|
||||
export const getCurrentUser = async () => {
|
||||
try {
|
||||
const response = await apiClient.get('/users/me');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const logoutUser = async () => {
|
||||
try {
|
||||
const response = await apiClient.post('/users/logout');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUser = async (updatedUser) => {
|
||||
try {
|
||||
const response = await apiClient.put('/users/me', updatedUser);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const resetPassword = async (currentPassword, newPassword) => {
|
||||
try {
|
||||
const response = await apiClient.post('/users/me/reset-password', { currentPassword, newPassword });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data || error.message);
|
||||
}
|
||||
};
|
@ -0,0 +1,2 @@
|
||||
# .env
|
||||
VITE_SERVER_URL=http://localhost:8080/api/users
|
@ -1,3 +1,3 @@
|
||||
VITE_SERVER_URL = "http://127.0.0.1:9099"
|
||||
VITE_BASE_URL = "/api/v1"
|
||||
VITE_HTTP_TIMEOUT = 5000
|
||||
# .env.development
|
||||
VITE_API_BASE_URL=http://localhost:8080/api
|
||||
VITE_SERVER_URL=http://localhost:8080/api/users
|
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 234 KiB |
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 203 KiB |
After Width: | Height: | Size: 234 KiB |
After Width: | Height: | Size: 187 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 238 KiB |
@ -0,0 +1,24 @@
|
||||
{
|
||||
"admins": [
|
||||
{
|
||||
"id": "1",
|
||||
"adminname": "admin",
|
||||
"password": "123456",
|
||||
"phone": "18963554712",
|
||||
"signature": "我是管理员",
|
||||
"birthday": "2024-12-16T16:00:00.000Z",
|
||||
"security_questions": [
|
||||
{
|
||||
"question": "您最新喜欢的歌曲是什么?",
|
||||
"question_key": "favorite_song",
|
||||
"answer": "星河叹"
|
||||
},
|
||||
{
|
||||
"question": "您最喜欢的动物是什么?",
|
||||
"question_key": "favorite_animal",
|
||||
"answer": "猫猫"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,44 @@
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"id": "1",
|
||||
"username": "黄",
|
||||
"password": "123456",
|
||||
"phone": "18963554712",
|
||||
"signature": "希君生羽翼,一化北溟鱼",
|
||||
"birthday": "2024-12-16T16:00:00.000Z",
|
||||
"security_questions": [
|
||||
{
|
||||
"question": "您最新喜欢的歌曲是什么?",
|
||||
"question_key": "favorite_song",
|
||||
"answer": "星河叹"
|
||||
},
|
||||
{
|
||||
"question": "您最喜欢的动物是什么?",
|
||||
"question_key": "favorite_animal",
|
||||
"answer": "猫猫"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"username": "user1",
|
||||
"password": "123456",
|
||||
"phone": "15608674327",
|
||||
"signature": "",
|
||||
"birthday": null,
|
||||
"security_questions": [
|
||||
{
|
||||
"question": "您小学的名字是什么?",
|
||||
"question_key": "elementary_school",
|
||||
"answer": "1"
|
||||
},
|
||||
{
|
||||
"question": "您最喜欢的城市是哪里?",
|
||||
"question_key": "favorite_city",
|
||||
"answer": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,19 +1,38 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPersistedstate from 'pinia-plugin-persistedstate';
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/main.css';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||
import ElementPlus from 'element-plus';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
|
||||
// 创建 Pinia 实例并使用持久化插件
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPersistedstate);
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
const app = createApp(App)
|
||||
// 创建 Vue 应用
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
// 使用 Pinia 和路由
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(ElementPlus);
|
||||
// 挂载应用
|
||||
app.mount('#app');
|
||||
// 在 main.js 或单独的文件中
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
app.mount('#app')
|
||||
// 添加请求拦截器
|
||||
axios.interceptors.request.use(config => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
}, error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
@ -1,49 +1,124 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import Post from '@/views/posts/post.vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
// 动态导入组件
|
||||
const HomeView = () => import('../views/HomeView.vue');
|
||||
const Post = () => import('@/views/posts/post.vue');
|
||||
const Login = () => import('../views/RegisterLogin/Login.vue');
|
||||
const Register = () => import('../views/RegisterLogin/Register.vue');
|
||||
const ForgotPassword = () => import('../views/RegisterLogin/ForgotPassword.vue');
|
||||
const ResetPassword = () => import('../views/RegisterLogin/ResetPassword.vue');
|
||||
const AdminLogin = () => import('../views/RegisterLogin/AdminLogin.vue');
|
||||
const AdminForgotPassword = () => import('../views/RegisterLogin/AdminForgotPassword.vue');
|
||||
const AdminResetPassword = () => import('../views/RegisterLogin/AdminResetPassword.vue');
|
||||
const LogoutDialog = () => import('../views/RegisterLogin/LogoutDialog.vue');
|
||||
const AdminView = () => import('../views/AdminView.vue');
|
||||
const ProfileView = () => import('../views/ProfileView.vue');
|
||||
const ArticleView = () => import('../views/ArticleView.vue');
|
||||
|
||||
// 创建路由实例
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: '/',
|
||||
name: 'Home',
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: '/post/:id', // 使用动态参数 :id
|
||||
name: 'postDetail',
|
||||
component: Post,
|
||||
props: true, // 将路由参数传递给组件
|
||||
},
|
||||
path: '/post/:id', // 使用动态参数 :id
|
||||
name: 'PostDetail',
|
||||
component: Post,
|
||||
props: true, // 将路由参数传递给组件
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/Login.vue'),
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: () => import('../views/Register.vue'),
|
||||
component: Register,
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
name: 'ForgotPassword',
|
||||
component: ForgotPassword,
|
||||
},
|
||||
{
|
||||
path: '/reset-password',
|
||||
name: 'ResetPassword',
|
||||
component: ResetPassword,
|
||||
},
|
||||
{
|
||||
path: '/Adminlogin',
|
||||
name: 'AdminLogin',
|
||||
component: AdminLogin,
|
||||
},
|
||||
{
|
||||
path: '/Adminforgot-password',
|
||||
name: 'AdminForgotPassword',
|
||||
component: AdminForgotPassword,
|
||||
},
|
||||
{
|
||||
path: '/Adminreset-password',
|
||||
name: 'AdminResetPassword',
|
||||
component: AdminResetPassword,
|
||||
},
|
||||
{
|
||||
path: '/logout',
|
||||
name: 'Logout',
|
||||
component: LogoutDialog,
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'Admin',
|
||||
component: () => import('../views/AdminView.vue'),
|
||||
component: AdminView,
|
||||
children: [
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: () => import('../views/ProfileView.vue'),
|
||||
name: 'AdminProfile',
|
||||
component: ProfileView, // 保留作为管理员的子路由
|
||||
},
|
||||
{
|
||||
path: '/article',
|
||||
name: 'Article',
|
||||
component: ArticleView,
|
||||
},
|
||||
]
|
||||
},{
|
||||
path: '/user',
|
||||
name: 'User',
|
||||
component: () => import('../views/UserView.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/profile1',
|
||||
name: 'Profile1',
|
||||
component: () => import('../views/ProfileView1.vue'),
|
||||
},
|
||||
{
|
||||
path: '/article1',
|
||||
name: 'Article1',
|
||||
component: () => import('../views/ArticleView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/edit',
|
||||
name: 'Edit',
|
||||
component: () => import('../views/edit.vue'),
|
||||
},
|
||||
{
|
||||
path: '/display',
|
||||
name: 'Display',
|
||||
component: () => import('../views/display.vue'),
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
name: 'accounts',
|
||||
component: () => import('../views/accounts.vue'),
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
@ -0,0 +1,2 @@
|
||||
# .env
|
||||
JWT_SECRET=your_secret_key_here
|
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<el-container>
|
||||
<el-header>Header</el-header>
|
||||
<el-container>
|
||||
<el-aside width="200px">Aside</el-aside>
|
||||
<el-main>Main</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.common-layout {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.el-header, .el-footer {
|
||||
background-color: #c6e2ff;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background-color: #d9ecff;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
background-color: #ecf5ff;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 160px;
|
||||
}
|
||||
|
||||
body > .el-container {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(5) .el-aside,
|
||||
.el-container:nth-child(6) .el-aside {
|
||||
line-height: 260px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(7) .el-aside {
|
||||
line-height: 320px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,878 @@
|
||||
{
|
||||
"name": "backend-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "backend-server",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.20.3",
|
||||
"cookie-session": "^2.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.1"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-session": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/cookie-session/-/cookie-session-2.1.0.tgz",
|
||||
"integrity": "sha512-u73BDmR8QLGcs+Lprs0cfbcAPKl2HnPcjpwRXT41sEV4DRJ2+W0vJEEZkG31ofkx+HZflA70siRIjiTdIodmOQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookies": "0.9.1",
|
||||
"debug": "3.2.7",
|
||||
"on-headers": "~1.0.2",
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-session/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-session/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookies": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmmirror.com/cookies/-/cookies-0.9.1.tgz",
|
||||
"integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"keygrip": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmmirror.com/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.10",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/keygrip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/keygrip/-/keygrip-1.1.0.tgz",
|
||||
"integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tsscmp": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tsscmp": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.x"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "lesson9",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"element-plus": "^1.0.2-beta.36",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// const jsonServer = require('json-server');
|
||||
// const bodyParser = require('body-parser');
|
||||
// const bcrypt = require('bcryptjs');
|
||||
// const jwt = require('jsonwebtoken');
|
||||
// const dotenv = require('dotenv');
|
||||
// const app = jsonServer.create();
|
||||
// const router = jsonServer.router('db.json');
|
||||
// const middlewares = jsonServer.defaults();
|
||||
|
||||
// // 加载环境变量
|
||||
// dotenv.config();
|
||||
|
||||
// // 使用中间件
|
||||
// app.use(middlewares);
|
||||
// app.use(bodyParser.urlencoded({ extended: true })); // 支持复杂的表单数据
|
||||
// app.use(bodyParser.json());
|
||||
|
||||
// // 添加 CORS 头
|
||||
// app.use((req, res, next) => {
|
||||
// res.header('Access-Control-Allow-Origin', '*');
|
||||
// res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
// res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
// next();
|
||||
// });
|
||||
|
||||
// // 获取用户数据
|
||||
// let users = router.db.get('users').value();
|
||||
|
||||
// // 登录接口 (POST)
|
||||
// app.post('/auth/login', async (req, res) => {
|
||||
// const { account, password } = req.body;
|
||||
|
||||
// // 查找用户
|
||||
// const user = users.find(u => u.account === account);
|
||||
|
||||
// if (!user) {
|
||||
// return res.status(401).json({
|
||||
// code: 0,
|
||||
// message: '账号不存在'
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 验证密码
|
||||
// const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
// if (!isPasswordValid) {
|
||||
// return res.status(401).json({
|
||||
// code: 0,
|
||||
// message: '密码错误'
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 生成 JWT 令牌
|
||||
// const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
|
||||
// // 返回成功响应
|
||||
// res.status(200).json({
|
||||
// code: 1,
|
||||
// message: '登录成功',
|
||||
// data: {
|
||||
// id: user.id,
|
||||
// account: user.account,
|
||||
// token
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// // 密码重置接口 (PUT)
|
||||
// app.put('/users/reset-password', async (req, res) => {
|
||||
// console.log('Received reset-password request:', req.body); // 添加日志输出
|
||||
// const { account, oldPassword, newPassword } = req.body;
|
||||
|
||||
// // 查找用户
|
||||
// const userIndex = users.findIndex(u => u.account === account);
|
||||
|
||||
// if (userIndex === -1) {
|
||||
// return res.status(404).json({
|
||||
// code: 0,
|
||||
// message: '用户未找到'
|
||||
// });
|
||||
// }
|
||||
|
||||
// const user = users[userIndex];
|
||||
|
||||
// // 验证旧密码
|
||||
// const isOldPasswordValid = await bcrypt.compare(oldPassword, user.password);
|
||||
// if (!isOldPasswordValid) {
|
||||
// return res.status(400).json({
|
||||
// code: 0,
|
||||
// message: '旧密码错误'
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 对新密码进行哈希处理
|
||||
// const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// // 更新密码
|
||||
// users[userIndex].password = hashedPassword;
|
||||
// router.db.get('users').find({ id: user.id }).assign({ password: hashedPassword }).write();
|
||||
|
||||
// res.status(200).json({
|
||||
// code: 1,
|
||||
// message: '密码重置成功'
|
||||
// });
|
||||
// });
|
||||
|
||||
// // 启动服务器
|
||||
// const port = 3000;
|
||||
// app.listen(port, () => {
|
||||
// console.log(`Server is running on http://localhost:${port}`);
|
||||
// });
|
@ -0,0 +1,23 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
isLoggedIn: localStorage.getItem('isLoggedIn') === 'true', // 从 localStorage 中恢复登录状态
|
||||
phoneNumber: localStorage.getItem('phoneNumber') || '' // 从 localStorage 中恢复电话号码
|
||||
}),
|
||||
actions: {
|
||||
setLoginState(value) {
|
||||
this.isLoggedIn = !!value; // 确保 value 是布尔类型
|
||||
localStorage.setItem('isLoggedIn', JSON.stringify(this.isLoggedIn)); // 将登录状态保存到 localStorage
|
||||
console.log('Setting isLoggedIn to:', this.isLoggedIn); // 添加调试信息
|
||||
},
|
||||
setPhoneNumber(phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
localStorage.setItem('phoneNumber', phoneNumber); // 将电话号码保存到 localStorage
|
||||
console.log('Setting phoneNumber to:', this.phoneNumber); // 添加调试信息
|
||||
},
|
||||
getPhoneNumber() {
|
||||
return this.phoneNumber || localStorage.getItem('phoneNumber') || ''; // 优先从 store 中获取,如果没有则从 localStorage 中获取
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
// stores/index.ts
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
isLoggedIn: false,
|
||||
username: ''
|
||||
}),
|
||||
actions: {
|
||||
setLoginState(isLoggedIn: boolean) {
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
},
|
||||
setUsername(username: string) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
// stores/user.js
|
||||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
user: null,
|
||||
}),
|
||||
actions: {
|
||||
async login(account, password) {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:3002/users', {
|
||||
params: {
|
||||
account,
|
||||
password,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.length > 0) {
|
||||
this.user = response.data[0];
|
||||
// 确保 isAdmin 字段正确设置
|
||||
this.user.isAdmin = response.data[0].isAdmin || false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
}
|
||||
},
|
||||
getCurrentUser() {
|
||||
return this.user;
|
||||
},
|
||||
},
|
||||
});
|
@ -1,103 +1,56 @@
|
||||
// src/stores/user.ts
|
||||
import type { IUserInfo } from '@/model/model';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useUserStore = defineStore('users', {
|
||||
state: () => ({
|
||||
users: JSON.parse(localStorage.getItem('users') || '[]') as IUserInfo[], // 从localStorage加载已注册用户
|
||||
currentUser: null as IUserInfo | null, // 当前登录的用户
|
||||
}),
|
||||
actions: {
|
||||
// 注册用户
|
||||
registerUser(user :IUserInfo) {
|
||||
// 检查用户名是否已存在
|
||||
const userExists = this.users.some((u: IUserInfo) => u.username === user.username);
|
||||
if (userExists) {
|
||||
return "用户已存在"
|
||||
}
|
||||
|
||||
// 检查是不是管理员
|
||||
if (user.username === "admin") {
|
||||
return "管理员账号,不可使用"
|
||||
}
|
||||
// 如果用户名不存在,将新用户保存到数组中
|
||||
this.users.push(user);
|
||||
|
||||
// 更新到 localStorage
|
||||
localStorage.setItem('users', JSON.stringify(this.users));
|
||||
|
||||
return "成功"
|
||||
},
|
||||
|
||||
// 登录用户
|
||||
loginUser(username: string, password: string) {
|
||||
|
||||
if (username === 'admin' && password === '123456') {
|
||||
// 模拟管理员登录
|
||||
this.currentUser = {
|
||||
username: 'admin',
|
||||
nickname: '管理员',
|
||||
signature: '我是系统管理员',
|
||||
phone: '',
|
||||
birthday: '1999-01-01',
|
||||
isAdmin: true, // 设置管理员标识
|
||||
password: '123456',
|
||||
};
|
||||
|
||||
return this.currentUser
|
||||
}
|
||||
|
||||
// 查找匹配的用户
|
||||
const user = this.users.find((user: IUserInfo) => user.username === username && user.password === password);
|
||||
|
||||
|
||||
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 设置当前用户
|
||||
this.currentUser = user;
|
||||
return user;
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
logoutUser() {
|
||||
this.currentUser = null;
|
||||
},
|
||||
|
||||
updateUser(updatedUserInfo: IUserInfo) {
|
||||
// 如果当前用户不存在,返回
|
||||
if (!this.currentUser) return;
|
||||
|
||||
console.log("currentuser = ", this.currentUser)
|
||||
// 更新当前用户
|
||||
this.currentUser = { ...this.currentUser, ...updatedUserInfo };
|
||||
|
||||
console.log("currentuser1 = ", this.currentUser)
|
||||
// 更新 users 数组中的对应用户信息
|
||||
const userIndex = this.users.findIndex((user: IUserInfo) => user.username === this.currentUser.username);
|
||||
|
||||
// 如果找到了对应的用户,更新该用户信息
|
||||
if (userIndex !== -1) {
|
||||
// 使用深拷贝来更新 user 数组中的用户信息
|
||||
this.users[userIndex] = { ...this.users[userIndex], ...updatedUserInfo };
|
||||
|
||||
// 更新 users 数组到 localStorage
|
||||
localStorage.setItem('users', JSON.stringify(this.users));
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前用户信息
|
||||
getCurrentUser() {
|
||||
return this.currentUser;
|
||||
},
|
||||
|
||||
// 设置管理员登录
|
||||
setAdmin() {
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface IUser {
|
||||
id: string;
|
||||
adminname: string;
|
||||
password: string;
|
||||
phone: string;
|
||||
signature: string;
|
||||
birthday: string;
|
||||
isAdmin: boolean;
|
||||
security_questions: Array<{ question: string; answer: string }>;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const currentUser = ref<IUser | null>(null);
|
||||
const isLoggedIn = ref(false);
|
||||
const isLoggedInAsAdmin = ref(false);
|
||||
|
||||
function setLoginState(isAdmin: boolean) {
|
||||
isLoggedIn.value = true;
|
||||
isLoggedInAsAdmin.value = isAdmin;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
currentUser.value = null;
|
||||
isLoggedIn.value = false;
|
||||
isLoggedInAsAdmin.value = false;
|
||||
}
|
||||
|
||||
function setCurrentUser(user: IUser) {
|
||||
currentUser.value = user;
|
||||
}
|
||||
|
||||
function getCurrentUser(): IUser | null {
|
||||
return currentUser.value;
|
||||
}
|
||||
|
||||
function updateUser(updatedUser: IUser) {
|
||||
if (currentUser.value) {
|
||||
Object.assign(currentUser.value, updatedUser);
|
||||
}
|
||||
},
|
||||
persist: true, // 启用持久化
|
||||
}
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
isLoggedIn,
|
||||
isLoggedInAsAdmin,
|
||||
setLoginState,
|
||||
logout,
|
||||
setCurrentUser,
|
||||
getCurrentUser,
|
||||
updateUser
|
||||
};
|
||||
});
|
||||
|
@ -1,179 +0,0 @@
|
||||
<template>
|
||||
<el-container class="login">
|
||||
<el-main class="main-content">
|
||||
<el-tabs v-model="activeTab" class="tabs" style="width: 100%;">
|
||||
<el-tab-pane label="管理员登录" name="first">
|
||||
<el-form :model="adminForm" :rules="adminRules" ref="adminFormRef" label-position="top" status-icon
|
||||
class="form-container">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="adminForm.username" placeholder="请输入管理员用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="adminForm.password" placeholder="请输入管理员密码" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="adminLogin" class="login-btn">登录</el-button>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="用户登录" name="second">
|
||||
<el-form :model="userForm" :rules="userRules" ref="userFormRef" label-position="top" status-icon
|
||||
class="form-container">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="userForm.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="userForm.password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="userLogin" class="login-btn">登录</el-button>
|
||||
<el-button type="text" @click="toggleRegister" class="register-btn">注册</el-button>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '../stores/user';
|
||||
|
||||
// 当前选中的标签页
|
||||
const activeTab = ref<string>('first');
|
||||
|
||||
// 管理员登录表单数据和验证规则
|
||||
const adminForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
const adminRules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
// 用户登录表单数据和验证规则
|
||||
const userForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
const userRules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 管理员登录逻辑
|
||||
const adminLogin = () => {
|
||||
console.log('管理员登录:', adminForm.value);
|
||||
if (adminForm.value.username == "admin" && adminForm.value.password == "123456") {
|
||||
const u = userStore.loginUser(adminForm.value.username, adminForm.value.password);
|
||||
if (u != null) {
|
||||
router.push("/admin");
|
||||
} else {
|
||||
ElMessage.error('登录失败,请检查用户名和密码');
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('登录失败,请检查用户名和密码');
|
||||
}
|
||||
};
|
||||
|
||||
// 用户登录逻辑
|
||||
const userLogin = () => {
|
||||
console.log('用户登录:', userForm.value);
|
||||
const u = userStore.loginUser(userForm.value.username, userForm.value.password);
|
||||
if (u != null) {
|
||||
router.push("/admin");
|
||||
} else {
|
||||
ElMessage.error('登录失败,请检查用户名和密码');
|
||||
}
|
||||
};
|
||||
|
||||
// 注册
|
||||
const toggleRegister = () => {
|
||||
router.push("/register");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-image: url('../assets/bg.jpg');
|
||||
background-repeat: no-repeat;
|
||||
/* 不重复背景图片 */
|
||||
background-size: cover;
|
||||
/* 使背景图片覆盖整个页面 */
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
/* 控制最大宽度 */
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
background-color: #409EFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
background-color: transparent;
|
||||
color: #409EFF;
|
||||
border: none;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.el-button:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.el-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="personal-info">
|
||||
<div class="info-container">
|
||||
<!-- 头像 -->
|
||||
<div class="avatar-container">
|
||||
<img v-if="currentUser.isAdmin" class="avatar" src="../assets/user/avatar.png" alt="User Avatar" />
|
||||
<img v-else class="avatar" src="../assets/user/user.png" alt="User Avatar" />
|
||||
</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div class="user-info">
|
||||
<p class="user-item"><strong>账户类型:普通用户</strong></p>
|
||||
|
||||
<!-- 可编辑的昵称 -->
|
||||
<p class="user-item">
|
||||
<strong>昵称:</strong>
|
||||
<input v-model="currentUser.username" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的个性签名 -->
|
||||
<p class="user-item">
|
||||
<strong>个性签名:</strong>
|
||||
<input v-model="currentUser.signature" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 手机号码 (管理员和普通用户都显示) -->
|
||||
<p class="user-item">
|
||||
<strong>手机号码:</strong>
|
||||
<input v-model="currentUser.phone" class="editable-input" type="text" disabled/>
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的生日 -->
|
||||
<p class="user-item">
|
||||
<strong>生日:</strong>
|
||||
<input v-model="currentUser.birthday" class="editable-input" type="date" />
|
||||
</p>
|
||||
|
||||
<!-- 安全问题 (管理员和普通用户都显示) -->
|
||||
<div class="security-questions">
|
||||
<p v-for="(question, index) in currentUser.security_questions" :key="index" class="user-item">
|
||||
<strong>{{ question.question }}:</strong>
|
||||
<input v-model="question.answer" class="editable-input" type="text" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="user-item">
|
||||
<el-button type="primary" @click="saveUser" style="width: 100%;">保存</el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { IUserInfo } from '../model/model';
|
||||
import axios from 'axios';
|
||||
|
||||
// 从Pinia中获取user store
|
||||
const userStore = useUserStore();
|
||||
|
||||
const currentUser = reactive<IUserInfo>({
|
||||
id: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
signature: '',
|
||||
birthday: '',
|
||||
isAdmin: false,
|
||||
security_questions: []
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 获取当前用户
|
||||
const user = userStore.getCurrentUser();
|
||||
if (user && user.username) {
|
||||
fetchUserInfo(user.username);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchUserInfo = async (username: string) => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (userStore.isLoggedInAsAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins?username=${username}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users?username=${username}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(apiUrl);
|
||||
if (response.data.length > 0) {
|
||||
const user = response.data[0];
|
||||
Object.assign(currentUser, user);
|
||||
currentUser.isAdmin = userStore.isLoggedInAsAdmin; // 确保用户类型一致
|
||||
console.log("Fetched user info:", currentUser);
|
||||
} else {
|
||||
console.error('No user found with the provided username.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (currentUser.isAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins/${currentUser.id}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users/${currentUser.id}`;
|
||||
}
|
||||
|
||||
await axios.put(apiUrl, currentUser);
|
||||
console.log('用户信息已更新:', currentUser);
|
||||
ElMessage.success("保存成功");
|
||||
} catch (error) {
|
||||
console.error('Error updating user info:', error);
|
||||
ElMessage.error("保存失败,请稍后再试");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整体布局居中 */
|
||||
.personal-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-align: left;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-item strong {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 样式修改:让输入框看起来更整洁 */
|
||||
.editable-input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.security-questions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
@ -1,191 +0,0 @@
|
||||
<template>
|
||||
<el-container class="register">
|
||||
<el-main class="main-content">
|
||||
<span class="title">用户注册</span>
|
||||
<el-form :model="userForm" :rules="userRules" ref="userFormRef" label-position="top" status-icon
|
||||
class="form-container">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="userForm.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="userForm.password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input type="password" v-model="userForm.confirmPassword" placeholder="请确认密码" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="userForm.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="个性签名" prop="signature">
|
||||
<el-input v-model="userForm.signature" placeholder="请输入个性签名" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="生日" prop="birthday">
|
||||
<input v-model="userForm.birthday" class="editable-input" type="date" />
|
||||
</el-form-item>
|
||||
|
||||
<el-button type="primary" @click="submit(userFormRef)" class="register-btn">注册</el-button>
|
||||
</el-form>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { IUserInfo } from '../model/model';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
interface RuleForm {
|
||||
username : string;
|
||||
password : string;
|
||||
confirmPassword : string;
|
||||
birthday : string;
|
||||
phone : string;
|
||||
signature : string;
|
||||
}
|
||||
|
||||
const userFormRef = ref<FormInstance>();
|
||||
|
||||
// 用户注册表单数据和验证规则
|
||||
const userForm = reactive<RuleForm>({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
birthday: '',
|
||||
phone: '',
|
||||
signature: '',
|
||||
});
|
||||
|
||||
const validatePass2 = (rule : any, value : any, callback : any) => {
|
||||
if (value === '') {
|
||||
callback(new Error('Please input the password again'));
|
||||
} else if (value !== userForm.password) {
|
||||
callback(new Error("Two inputs don't match!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const userRules = reactive<FormRules<RuleForm>>({
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
confirmPassword: [{ validator: validatePass2, trigger: 'blur', message: "密码不一致" }],
|
||||
});
|
||||
|
||||
const submit = async (formEl : FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
const user : IUserInfo = {
|
||||
username: userForm.username,
|
||||
password: userForm.password,
|
||||
isAdmin: false,
|
||||
birthday: userForm.birthday,
|
||||
phone: userForm.phone,
|
||||
signature: userForm.signature,
|
||||
};
|
||||
const msg = userStore.registerUser(user);
|
||||
if (msg == '成功') {
|
||||
ElMessage.success('注册成功');
|
||||
router.push('/login');
|
||||
} else {
|
||||
ElMessage.error(msg);
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('表单验证失败,请检查输入');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-image: url('../assets/bg.jpg');
|
||||
background-repeat: no-repeat; /* 不重复背景图片 */
|
||||
background-size: cover; /* 使背景图片覆盖整个页面 */
|
||||
background-position: center center;
|
||||
/* 页面背景色 */
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
/* 最大宽度 */
|
||||
padding: 30px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
/* 添加阴影 */
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
/* 标题与表单之间的间距 */
|
||||
color: #409eff;
|
||||
/* 标题颜色 */
|
||||
}
|
||||
|
||||
.el-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
font-size: 16px;
|
||||
padding: 12px 0;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-button:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.el-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 日期输入框样式 */
|
||||
.editable-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-main class="login-container">
|
||||
<el-card class="blur-card">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 10px; margin-top: -10px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 10px;">
|
||||
{{ step === 1 ? '忘记密码-页面1' : '忘记密码-页面2' }}
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="currentRules" ref="formRef" label-width="96px" label-position="right">
|
||||
<el-form-item v-if="step === 1" label="用户的账号:" prop="adminname">
|
||||
<el-input v-model="form.adminname" placeholder="请输入账号名" @blur="fetchSecurityQuestions"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="第一个问题:" prop="question1">
|
||||
<el-input v-model="form.question1" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="第一个答案:" prop="answer1">
|
||||
<el-input v-model="form.answer1" placeholder="请回答第一个问题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个问题:" prop="question2">
|
||||
<el-input v-model="form.question2" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个答案:" prop="answer2">
|
||||
<el-input v-model="form.answer2" placeholder="请回答第二个问题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="设置新密码:" prop="newPassword">
|
||||
<el-input type="password" v-model="form.newPassword" placeholder="请输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="确认新密码:" prop="confirmPassword">
|
||||
<el-input type="password" v-model="form.confirmPassword" placeholder="请再次输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-button style="position: relative; width: 35%;" @click="navigateToLogin">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 35%; background-color: #dc99a1;" @click="nextStep">
|
||||
下一步
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="step === 2" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<div v-if="step === 2" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-button style="position: relative; width: 35%;" @click="previousStep">
|
||||
上一步
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 35%; background-color: #dc99a1;" @click="submitForm">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
adminname: '',
|
||||
question1: '',
|
||||
answer1: '',
|
||||
question2: '',
|
||||
answer2: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
const step = ref(1);
|
||||
const loading = ref(false); // 加载指示器
|
||||
|
||||
const validateAnswer1 = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请回答第一个问题'));
|
||||
}
|
||||
if (adminSecurityQuestions.value && adminSecurityQuestions.value[0] && value !== adminSecurityQuestions.value[0].answer) {
|
||||
return callback(new Error('第一个问题答案错误'));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const validateAnswer2 = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请回答第二个问题'));
|
||||
}
|
||||
if (adminSecurityQuestions.value && adminSecurityQuestions.value[1] && value !== adminSecurityQuestions.value[1].answer) {
|
||||
return callback(new Error('第二个问题答案错误'));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== form.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
adminname: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' }
|
||||
],
|
||||
answer1: [
|
||||
{ validator: validateAnswer1, trigger: 'blur' }
|
||||
],
|
||||
answer2: [
|
||||
{ validator: validateAnswer2, trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
const currentRules = computed(() => {
|
||||
if (step.value === 1) {
|
||||
return {
|
||||
adminname: rules.adminname,
|
||||
answer1: rules.answer1
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
answer2: rules.answer2,
|
||||
newPassword: rules.newPassword,
|
||||
confirmPassword: rules.confirmPassword
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const adminSecurityQuestions = ref(null);
|
||||
|
||||
const fetchSecurityQuestions = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:3003/admins?adminname=${form.adminname}`);
|
||||
const admins = response.data;
|
||||
if (admins.length > 0) {
|
||||
const admin = admins[0];
|
||||
adminSecurityQuestions.value = admin.security_questions;
|
||||
form.question1 = admin.security_questions[0]?.question || '';
|
||||
form.question2 = admin.security_questions[1]?.question || '';
|
||||
} else {
|
||||
ElMessageBox.alert('用户不存在', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
form.question1 = '';
|
||||
form.question2 = '';
|
||||
resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`获取安全问题失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = async () => {
|
||||
const valid = await new Promise((resolve) => {
|
||||
formRef.value.validate(resolve);
|
||||
});
|
||||
if (valid) {
|
||||
step.value = 2;
|
||||
} else {
|
||||
ElMessage.error('请检查输入信息');
|
||||
}
|
||||
};
|
||||
|
||||
const previousStep = () => {
|
||||
step.value = 1;
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await new Promise((resolve) => {
|
||||
formRef.value.validate(resolve);
|
||||
});
|
||||
if (valid) {
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:3003/admins?adminname=${form.adminname}`);
|
||||
const admins = response.data;
|
||||
if (admins.length > 0) {
|
||||
const admin = admins[0];
|
||||
const answersMatch = admin.security_questions.every((q, index) => {
|
||||
return q.answer === (index === 0 ? form.answer1 : form.answer2);
|
||||
});
|
||||
|
||||
if (answersMatch) {
|
||||
// 更新用户密码
|
||||
await axios.put(`http://localhost:3003/admins/${admin.id}`, {
|
||||
...admin, // 保留原有字段
|
||||
password: form.newPassword // 更新密码
|
||||
});
|
||||
|
||||
ElMessage.success('密码重置成功');
|
||||
router.push('/login');
|
||||
resetForm();
|
||||
} else {
|
||||
ElMessageBox.alert('安全问题答案错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('用户不存在', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`安全问题验证失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('请正确填写所有信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToLogin = () => {
|
||||
router.push('/Adminlogin');
|
||||
};
|
||||
|
||||
const navigateToResetPassword = () => {
|
||||
router.push('/Adminreset-password');
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
background-image: url('../public/背景6.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px!important;
|
||||
width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px!important;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<el-main class="adminlogin-container">
|
||||
<el-card class="blur-card">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 20px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 20px;">
|
||||
管理员重置密码
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="rules" ref="formRef" label-width="82px" label-position="left">
|
||||
<el-form-item label="手机号码:" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入11位手机号码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="旧的密码:" prop="oldPassword">
|
||||
<el-input type="password" v-model="form.oldPassword" placeholder="请输入旧密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新的密码:" prop="newPassword">
|
||||
<el-input type="password" v-model="form.newPassword" placeholder="请输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码:" prop="confirmPassword">
|
||||
<el-input type="password" v-model="form.confirmPassword" placeholder="请再次输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 20px;">
|
||||
<el-link type="primary" @click="navigateToForgotPassword">忘记旧密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<el-button style="width: 45%; margin-left: -84px; margin-right: 54px;" @click="navigateToLogin">取消</el-button>
|
||||
<el-button type="primary" style="width: 45%; margin-left: 52px; background-color: #dc99a1;" @click="submitForm">确认</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
phone: '', // 修改为 phone
|
||||
oldPassword: '', // 新增旧密码字段
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
// 定义验证函数
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== form.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
phone: [ // 修改为 phone
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||
],
|
||||
oldPassword: [ // 新增旧密码验证规则
|
||||
{ required: true, message: '请输入旧密码', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await new Promise((resolve, reject) => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
try {
|
||||
// 获取用户数据
|
||||
const admins = await axios.get('http://localhost:3003/admins');
|
||||
const admin = admins.data.find(admin => admin.phone === form.phone && admin.password === form.oldPassword); // 核对旧密码
|
||||
|
||||
if (!admin) {
|
||||
ElMessageBox.alert('用户名或旧密码错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新用户密码并保留其他信息
|
||||
const updatedadmin = {
|
||||
...admin, // 保留原有字段
|
||||
password: form.newPassword // 更新密码
|
||||
};
|
||||
|
||||
// 发送 PUT 请求更新用户数据
|
||||
await axios.put(`http://localhost:3003/admins/${admin.id}`, updatedadmin);
|
||||
|
||||
// 成功提示
|
||||
ElMessageBox.alert('密码重置成功', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
router.push('/login');
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`密码重置失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('密码重置失败,请检查输入信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToLogin = () => {
|
||||
router.push('/Adminlogin');
|
||||
};
|
||||
|
||||
const navigateToForgotPassword = () => {
|
||||
router.push('/Adminforgot-password');
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.adminlogin-container {
|
||||
background-image: url('../public/背景8.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px!important;
|
||||
width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px!important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-main class="login-container">
|
||||
<el-card class="blur-card">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 10px; margin-top: -10px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 10px;">
|
||||
{{ step === 1 ? '忘记密码-页面1' : '忘记密码-页面2' }}
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="currentRules" ref="formRef" label-width="96px" label-position="right">
|
||||
<el-form-item v-if="step === 1" label="用户的账号:" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入账号(11位手机号码)" @blur="fetchSecurityQuestions"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="第一个问题:" prop="question1">
|
||||
<el-input v-model="form.question1" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="第一个答案:" prop="answer1">
|
||||
<el-input v-model="form.answer1" placeholder="请回答第一个问题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个问题:" prop="question2">
|
||||
<el-input v-model="form.question2" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个答案:" prop="answer2">
|
||||
<el-input v-model="form.answer2" placeholder="请回答第二个问题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="设置新密码:" prop="newPassword">
|
||||
<el-input type="password" v-model="form.newPassword" placeholder="请输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="确认新密码:" prop="confirmPassword">
|
||||
<el-input type="password" v-model="form.confirmPassword" placeholder="请再次输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-button style="position: relative; width: 35%;" @click="navigateToLogin">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 35%; background-color: #dc99a1;" @click="nextStep">
|
||||
下一步
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="step === 2" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<div v-if="step === 2" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-button style="position: relative; width: 35%;" @click="previousStep">
|
||||
上一步
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 35%; background-color: #dc99a1;" @click="submitForm">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
phone: '',
|
||||
question1: '',
|
||||
answer1: '',
|
||||
question2: '',
|
||||
answer2: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
const step = ref(1);
|
||||
const loading = ref(false); // 加载指示器
|
||||
|
||||
const validateAnswer1 = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请回答第一个问题'));
|
||||
}
|
||||
if (userSecurityQuestions.value && userSecurityQuestions.value[0] && value !== userSecurityQuestions.value[0].answer) {
|
||||
return callback(new Error('第一个问题答案错误'));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const validateAnswer2 = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请回答第二个问题'));
|
||||
}
|
||||
if (userSecurityQuestions.value && userSecurityQuestions.value[1] && value !== userSecurityQuestions.value[1].answer) {
|
||||
return callback(new Error('第二个问题答案错误'));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== form.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
phone: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' }
|
||||
],
|
||||
answer1: [
|
||||
{ validator: validateAnswer1, trigger: 'blur' }
|
||||
],
|
||||
answer2: [
|
||||
{ validator: validateAnswer2, trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
const currentRules = computed(() => {
|
||||
if (step.value === 1) {
|
||||
return {
|
||||
phone: rules.phone,
|
||||
answer1: rules.answer1
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
answer2: rules.answer2,
|
||||
newPassword: rules.newPassword,
|
||||
confirmPassword: rules.confirmPassword
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const userSecurityQuestions = ref(null);
|
||||
|
||||
const fetchSecurityQuestions = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:3002/users?phone=${form.phone}`);
|
||||
const users = response.data;
|
||||
if (users.length > 0) {
|
||||
const user = users[0];
|
||||
userSecurityQuestions.value = user.security_questions;
|
||||
form.question1 = user.security_questions[0]?.question || '';
|
||||
form.question2 = user.security_questions[1]?.question || '';
|
||||
} else {
|
||||
ElMessageBox.alert('用户不存在', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
form.question1 = '';
|
||||
form.question2 = '';
|
||||
resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`获取安全问题失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = async () => {
|
||||
const valid = await new Promise((resolve) => {
|
||||
formRef.value.validate(resolve);
|
||||
});
|
||||
if (valid) {
|
||||
step.value = 2;
|
||||
} else {
|
||||
ElMessage.error('请检查输入信息');
|
||||
}
|
||||
};
|
||||
|
||||
const previousStep = () => {
|
||||
step.value = 1;
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await new Promise((resolve) => {
|
||||
formRef.value.validate(resolve);
|
||||
});
|
||||
if (valid) {
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:3002/users?phone=${form.phone}`);
|
||||
const users = response.data;
|
||||
if (users.length > 0) {
|
||||
const user = users[0];
|
||||
const answersMatch = user.security_questions.every((q, index) => {
|
||||
return q.answer === (index === 0 ? form.answer1 : form.answer2);
|
||||
});
|
||||
|
||||
if (answersMatch) {
|
||||
// 更新用户密码
|
||||
await axios.put(`http://localhost:3002/users/${user.id}`, {
|
||||
...user, // 保留原有字段
|
||||
password: form.newPassword // 更新密码
|
||||
});
|
||||
|
||||
ElMessage.success('密码重置成功');
|
||||
router.push('/login');
|
||||
resetForm();
|
||||
} else {
|
||||
ElMessageBox.alert('安全问题答案错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('用户不存在', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`安全问题验证失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('请正确填写所有信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const navigateToResetPassword = () => {
|
||||
router.push('/reset-password');
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
background-image: url('../public/背景4.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px!important;
|
||||
width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px!important;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<el-main class="login-container">
|
||||
<el-card class="blur-card">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 20px; margin-top: -10px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 20px;">
|
||||
用户登录
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="rules" ref="formRef" label-width="70px">
|
||||
<el-form-item label="账号:" prop="phone" style="margin-bottom: 20px !important;">
|
||||
<el-input v-model="form.phone" placeholder="请输入11位手机号码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码:" prop="password" style="margin-bottom: 20px !important;">
|
||||
<el-input type="password" v-model="form.password" placeholder="请输入账号密码"></el-input>
|
||||
</el-form-item>
|
||||
<div style="display: flex; justify-content: space-between;margin-left: 20px; margin-top: 10px; margin-bottom: 20px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<el-button style="position: relative; width: 45%; margin-left: -50px;margin-right:72px;" @click="navigateToHome">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-32%, -50%); letter-spacing: 40px; white-space: nowrap;">取消</span>
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 45%; background-color: #dc99a1;" @click="submitForm">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-32%, -50%); letter-spacing: 40px; white-space: nowrap;">登录</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { useUserStore } from '../../stores/index.ts';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
phone: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
// 验证规则
|
||||
const rules = reactive({
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的11位手机号码', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
// 从 db.json 中读取用户数据
|
||||
axios.get('http://localhost:3002/users')
|
||||
.then(response => {
|
||||
const users = response.data;
|
||||
const user = users.find(u => u.phone === form.phone && u.password === form.password);
|
||||
if (user) {
|
||||
ElMessageBox.alert('登录成功', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: async () => {
|
||||
try {
|
||||
router.push('/user');
|
||||
userStore.setLoginState(true);
|
||||
} catch (error) {
|
||||
console.error('Error setting login state:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert('账号或密码错误,请重新输入', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
userStore.setLoginState(false);
|
||||
console.log('登录状态已重置为:', userStore.isLoggedIn);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert('请检查输入信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 导航到首页
|
||||
const navigateToHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
// 导航到重置密码页面
|
||||
const navigateToResetPassword = () => {
|
||||
router.push('/reset-password');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
background-image: url('../public/背景1.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(3px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px!important;
|
||||
width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px!important;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<el-main class="register-container">
|
||||
<el-card class="blur-card" style="width: 450px;">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 20px; margin-top: -10px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 20px;">
|
||||
{{ step === 1 ? '用户注册- 基础信息' : '用户注册 - 安全问题' }}
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="currentRules" ref="formRef" label-width="96px" label-position="left">
|
||||
<!-- Step 1: 用户昵称、密码、确认密码、手机号码、个性签名、出生日期 -->
|
||||
<el-form-item v-if="step === 1" label="用户昵称:" prop="username">
|
||||
<el-input v-model="form.username" placeholder="请输入用户昵称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="手机号码:" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="用户密码:" prop="password">
|
||||
<el-input type="password" v-model="form.password" placeholder="请输入账号密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="确认密码:" prop="checkpassword">
|
||||
<el-input type="password" v-model="form.checkpassword" placeholder="两次账号密码要一致"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="个性签名:" prop="signature">
|
||||
<el-input v-model="form.signature" placeholder="请输入个性签名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 1" label="出生日期:" prop="birthday">
|
||||
<el-date-picker v-model="form.birthday" type="date" placeholder="请选择出生日期" style="width: 100%;"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<!-- Step 2: 安全问题 -->
|
||||
<el-form-item v-if="step === 2" label="第一个问题:" prop="question1">
|
||||
<el-select v-model="form.question1" placeholder="请选择问题">
|
||||
<el-option label="您最新喜欢的歌曲是什么?" value="您最新喜欢的歌曲是什么?"></el-option>
|
||||
<el-option label="您小学的名字是什么?" value="您小学的名字是什么?"></el-option>
|
||||
<el-option label="您最喜欢的书是什么?" value="您最喜欢的书是什么?"></el-option>
|
||||
<el-option label="您最喜欢的电影是什么?" value="您最喜欢的电影是什么?"></el-option>
|
||||
<el-option label="您的第一只宠物叫什么名字?" value="您的第一只宠物叫什么名字?"></el-option>
|
||||
<el-option label="您小时候最好的朋友叫什么名字?" value="您小时候最好的朋友叫什么名字?"></el-option>
|
||||
<el-option label="您最不喜欢的运动是什么?" value="您最不喜欢的运动是什么?"></el-option>
|
||||
<el-option label="您第一次旅行的目的地是哪里?" value="您第一次旅行的目的地是哪里?"></el-option>
|
||||
<el-option label="您最喜欢的明星叫什么名字?" value="您最喜欢的明星叫什么名字?"></el-option>
|
||||
<el-option label="您最喜欢的音乐家是谁?" value="您最喜欢的音乐家是谁?"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第一个答案:" prop="answer1">
|
||||
<el-input v-model="form.answer1" placeholder="请回答第一个问题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个问题:" prop="question2">
|
||||
<el-select v-model="form.question2" placeholder="请选择问题">
|
||||
<el-option label="您最喜欢的城市是哪里?" value="您最喜欢的城市是哪里?"></el-option>
|
||||
<el-option label="您初中毕业的学校是哪所?" value="您初中毕业的学校是哪所?"></el-option>
|
||||
<el-option label="您最难忘的一次旅行是去哪里?" value="您最难忘的一次旅行是去哪里?"></el-option>
|
||||
<el-option label="您最喜欢的动物是什么?" value="您最喜欢的动物是什么?"></el-option>
|
||||
<el-option label="您最喜欢的季节是什么?" value="您最喜欢的季节是什么?"></el-option>
|
||||
<el-option label="您小时候最喜欢的玩具是什么?" value="您小时候最喜欢的玩具是什么?"></el-option>
|
||||
<el-option label="您最尊敬的人是谁?" value="您最尊敬的人是谁?"></el-option>
|
||||
<el-option label="您最擅长的科目是什么?" value="您最擅长的科目是什么?"></el-option>
|
||||
<el-option label="您最喜欢的歌曲是什么?" value="您最喜欢的歌曲是什么?"></el-option>
|
||||
<el-option label="您最喜欢的节日是什么?" value="您最喜欢的节日是什么?"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="step === 2" label="第二个答案:" prop="answer2">
|
||||
<el-input v-model="form.answer2" placeholder="请回答第二个问题"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<div v-if="step === 1" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-button style="position: relative; width: 35%;" @click="navigateToHome">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-32%, -50%); letter-spacing: 40px; white-space: nowrap;">取消</span>
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 35%; background-color: #dc99a1;" @click="nextStep">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-42%, -50%); letter-spacing: 20px; white-space: nowrap;">下一步</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="step === 2" style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px;">
|
||||
<el-link type="primary" @click="navigateToResetPassword">重置密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<el-form-item v-if="step === 2">
|
||||
<el-button style="position: relative; width: 45%; margin-left: -97px; margin-right: 116px;" @click="previousStep">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-42%, -50%); letter-spacing: 20px; white-space: nowrap;">上一步</span>
|
||||
</el-button>
|
||||
<el-button type="primary" style="position: relative; width: 45%; background-color: #dc99a1;" @click="submitForm">
|
||||
<span style="position: absolute; top: 50%; transform: translate(-32%, -50%); letter-spacing: 40px; white-space: nowrap;">注册</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
const form = reactive({
|
||||
username: '', // 用户昵称名
|
||||
phone: '', // 手机号码
|
||||
password: '',
|
||||
checkpassword: '',
|
||||
signature: '', // 个性签名
|
||||
birthday: null, // 出生日期
|
||||
question1: '',
|
||||
answer1: '',
|
||||
question2: '',
|
||||
answer2: ''
|
||||
});
|
||||
const formRef = ref(null);
|
||||
const step = ref(1);
|
||||
|
||||
// 验证密码一致性
|
||||
const validatePasswordConfirm = (rule, value, callback) => {
|
||||
if (value !== form.password) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// 验证规则
|
||||
const rules = reactive({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的11位手机号码', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
],
|
||||
checkpassword: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{ validator: validatePasswordConfirm, trigger: 'blur' }
|
||||
],
|
||||
signature: [
|
||||
{ required: false, message: '请输入个性签名', trigger: 'blur' }
|
||||
],
|
||||
birthday: [
|
||||
{ type: 'date', message: '请选择出生日期', trigger: 'change' }
|
||||
],
|
||||
question1: [
|
||||
{ required: true, message: '请选择第一个问题', trigger: 'change' }
|
||||
],
|
||||
answer1: [
|
||||
{ required: true, message: '请回答第一个问题', trigger: 'blur' }
|
||||
],
|
||||
question2: [
|
||||
{ required: true, message: '请选择第二个问题', trigger: 'change' }
|
||||
],
|
||||
answer2: [
|
||||
{ required: true, message: '请回答第二个问题', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
// 根据当前步骤动态设置验证规则
|
||||
const currentRules = computed(() => {
|
||||
if (step.value === 1) {
|
||||
return {
|
||||
username: rules.username,
|
||||
password: rules.password,
|
||||
checkpassword: rules.checkpassword,
|
||||
phone: rules.phone,
|
||||
signature: rules.signature,
|
||||
birthday: rules.birthday
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
question1: rules.question1,
|
||||
answer1: rules.answer1,
|
||||
question2: rules.question2,
|
||||
answer2: rules.answer2
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 下一步
|
||||
const nextStep = async () => {
|
||||
const valid = await new Promise((resolve, reject) => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (valid) {
|
||||
step.value = 2;
|
||||
} else {
|
||||
ElMessageBox.alert('请检查输入信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 上一步
|
||||
const previousStep = () => {
|
||||
step.value = 1;
|
||||
};
|
||||
|
||||
// 问题映射
|
||||
const questionMapping = {
|
||||
'您最新喜欢的歌曲是什么?': 'favorite_song',
|
||||
'您小学的名字是什么?': 'elementary_school',
|
||||
'您最喜欢的书是什么?': 'favorite_book',
|
||||
'您最喜欢的电影是什么?': 'favorite_movie',
|
||||
'您的第一只宠物叫什么名字?': 'first_pet_name',
|
||||
'您小时候最好的朋友叫什么名字?': 'best_friend_childhood',
|
||||
'您最不喜欢的运动是什么?': 'least_favorite_sport',
|
||||
'您第一次旅行的目的地是哪里?': 'first_trip_destination',
|
||||
'您最喜欢的明星叫什么名字?': 'favorite_celebrity',
|
||||
'您最喜欢的音乐家是谁?': 'favorite_musician',
|
||||
'您初中毕业的学校是哪所?': 'middle_school',
|
||||
'您最难忘的一次旅行是去哪里?': 'memorable_trip',
|
||||
'您最喜欢的动物是什么?': 'favorite_animal',
|
||||
'您最喜欢的季节是什么?': 'favorite_season',
|
||||
'您小时候最喜欢的玩具是什么?': 'childhood_toy',
|
||||
'您最尊敬的人是谁?': 'most_respected_person',
|
||||
'您最擅长的科目是什么?': 'best_subject',
|
||||
'您最喜欢的歌曲是什么?': 'favorite_song',
|
||||
'您最喜欢的节日是什么?': 'favorite_holiday',
|
||||
'您最喜欢的城市是哪里?': 'favorite_city'
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
const valid = await new Promise((resolve, reject) => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
try {
|
||||
// 检查账号是否已存在
|
||||
const users = await axios.get('http://localhost:3002/users');
|
||||
const existingUser = users.data.find(user => user.username === form.username); // 修改为检查 username
|
||||
if (existingUser) {
|
||||
ElMessageBox.alert('该账号已存在,不能重复注册', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取最大 ID
|
||||
const ids = users.data.map(user => user.id);
|
||||
const maxId = Math.max(...ids.filter(id => id !== null), 0);
|
||||
const nextId = maxId + 1;
|
||||
|
||||
// 映射安全问题
|
||||
const mappedQuestion1 = questionMapping[form.question1];
|
||||
const mappedQuestion2 = questionMapping[form.question2];
|
||||
|
||||
const securityQuestions = [
|
||||
{
|
||||
question: form.question1,
|
||||
question_key: mappedQuestion1,
|
||||
answer: form.answer1
|
||||
},
|
||||
{
|
||||
question: form.question2,
|
||||
question_key: mappedQuestion2,
|
||||
answer: form.answer2
|
||||
}
|
||||
];
|
||||
|
||||
// 发送注册请求
|
||||
await axios.post('http://localhost:3002/users', {
|
||||
id: nextId.toString(),
|
||||
username: form.username, // 修改为使用 username
|
||||
password: form.password,
|
||||
phone: form.phone,
|
||||
signature: form.signature,
|
||||
birthday: form.birthday,
|
||||
security_questions: securityQuestions
|
||||
});
|
||||
|
||||
ElMessageBox.alert('注册成功', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
resetForm();
|
||||
router.push('/login');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`注册失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('注册失败,请检查输入信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 导航到首页
|
||||
const navigateToHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
// 跳转到重置密码页面
|
||||
const navigateToResetPassword = () => {
|
||||
router.push('/reset-password');
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
background-image: url('../public/背景2.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<el-main class="USERlogin-container">
|
||||
<el-card class="blur-card">
|
||||
<div style="text-align: center; font-size: 40px; margin-bottom: 20px;">
|
||||
简书微澜
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 20px; margin-bottom: 20px;">
|
||||
重置密码
|
||||
</div>
|
||||
<el-form :model="form" status-icon :rules="rules" ref="formRef" label-width="82px" label-position="left">
|
||||
<el-form-item label="手机号码:" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="旧的密码:" prop="oldPassword">
|
||||
<el-input type="password" v-model="form.oldPassword" placeholder="请输入旧密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新的密码:" prop="newPassword">
|
||||
<el-input type="password" v-model="form.newPassword" placeholder="请输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码:" prop="confirmPassword">
|
||||
<el-input type="password" v-model="form.confirmPassword" placeholder="请再次输入新密码"></el-input>
|
||||
</el-form-item>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 20px;">
|
||||
<el-link type="primary" @click="navigateToForgotPassword">忘记旧密码</el-link>
|
||||
<el-link type="primary" @click="resetForm">重置输入框</el-link>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<el-button style="width: 45%; margin-left: -84px; margin-right: 54px;" @click="navigateToLogin">取消</el-button>
|
||||
<el-button type="primary" style="width: 45%; margin-left: 52px; background-color: #dc99a1;" @click="submitForm">确认</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = reactive({
|
||||
phone: '', // 修改为 phone
|
||||
oldPassword: '', // 新增旧密码字段
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
// 定义验证函数
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== form.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
phone: [ // 修改为 phone
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||
],
|
||||
oldPassword: [ // 新增旧密码验证规则
|
||||
{ required: true, message: '请输入旧密码', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await new Promise((resolve, reject) => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
try {
|
||||
// 获取用户数据
|
||||
const users = await axios.get('http://localhost:3002/users');
|
||||
const user = users.data.find(user => user.phone === form.phone && user.password === form.oldPassword); // 核对旧密码
|
||||
|
||||
if (!user) {
|
||||
ElMessageBox.alert('用户名或旧密码错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新用户密码并保留其他信息
|
||||
const updatedUser = {
|
||||
...user, // 保留原有字段
|
||||
password: form.newPassword // 更新密码
|
||||
};
|
||||
|
||||
// 发送 PUT 请求更新用户数据
|
||||
await axios.put(`http://localhost:3002/users/${user.id}`, updatedUser);
|
||||
|
||||
// 成功提示
|
||||
ElMessageBox.alert('密码重置成功', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
router.push('/login');
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
ElMessageBox.alert('模拟后端未正确启动或请求 URL 错误', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
} else {
|
||||
ElMessageBox.alert(`密码重置失败: ${error.message}`, '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessageBox.alert('密码重置失败,请检查输入信息', '提示', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const navigateToForgotPassword = () => {
|
||||
router.push('/forgot-password');
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.USERlogin-container {
|
||||
background-image: url('../public/背景3.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.blur-card {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(3px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 40px!important;
|
||||
width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 15px!important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="article-list">
|
||||
<ul>
|
||||
<li v-for="(article, index) in articles" :key="article.id">
|
||||
<div class="article-item">
|
||||
<span class="article-title">{{ article.title }}</span>
|
||||
<div class="button-group">
|
||||
<button class="delete-btn" @click="deleteArticle(index)">删除</button>
|
||||
<button class="edit-btn" @click="editArticle(article, index)">修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<div v-if="showEditArticle" class="overlay" @click.self="cancelEdit"></div>
|
||||
|
||||
<!-- 修改已有文章表单 -->
|
||||
<div v-if="showEditArticle" class="write-article-form edit-article-form">
|
||||
<div class="form-header">
|
||||
<h2>修改已有文章</h2>
|
||||
</div>
|
||||
<form @submit.prevent="submitEditArticle" class="form-container">
|
||||
<div class="form-field">
|
||||
<label for="edit-title">标题</label>
|
||||
<input type="text" id="edit-title" v-model="existingArticle.title" placeholder="修改标题" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="edit-author">作者</label>
|
||||
<input type="text" id="edit-author" v-model="existingArticle.author" placeholder="修改作者" required />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="edit-content">内容</label>
|
||||
<textarea id="edit-content" v-model="existingArticle.content" placeholder="修改内容" required></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit">更新文章</button>
|
||||
<button type="button" @click="cancelEdit">取消</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
articles: [
|
||||
{ id: 1, title: '文章一', author: '张三', content: '这是文章一的内容' },
|
||||
{ id: 2, title: '文章二', author: '李四', content: '这是文章二的内容' },
|
||||
{ id: 3, title: '文章三', author: '王五', content: '这是文章三的内容' }
|
||||
],
|
||||
showEditArticle: false,
|
||||
existingArticle: {}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteArticle(index) {
|
||||
if (confirm('确定要删除这篇文章吗?')) {
|
||||
this.articles.splice(index, 1);
|
||||
}
|
||||
},
|
||||
editArticle(article, index) {
|
||||
// 设置当前编辑的文章
|
||||
this.existingArticle = Object.assign({}, article, { originalIndex: index });
|
||||
this.showEditArticle = true;
|
||||
},
|
||||
submitEditArticle() {
|
||||
// 更新文章信息
|
||||
const index = this.existingArticle.originalIndex;
|
||||
this.articles.splice(index, 1, this.existingArticle);
|
||||
this.cancelEdit();
|
||||
},
|
||||
cancelEdit() {
|
||||
// 隐藏编辑表单
|
||||
this.showEditArticle = false;
|
||||
this.existingArticle = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
.article-list ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.article-list li {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.article-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 15px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* 新增样式 */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.write-article-form.edit-article-form {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #f8f9fa;
|
||||
padding: 47px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 80vh; /* 最大高度 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; /* 确保表单内容垂直居中 */
|
||||
align-items: center; /* 确保表单内容水平居中 */
|
||||
z-index: 1000;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 0 20px; /* 内边距以防止内容紧贴边缘 */
|
||||
flex-grow: 1; /* 允许表单容器扩展 */
|
||||
justify-content: center; /* 表单内容垂直居中 */
|
||||
align-items: stretch; /* 表单项宽度一致 */
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 8px;
|
||||
transition: border-color 0.3s ease;
|
||||
width: 96%; /* 确保输入框和文本区域宽度一致 */
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 15px 35px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #218838;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
button[type="button"] {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 15px 35px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[type="button"]:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="edit-container">
|
||||
<!-- 写文章表单 -->
|
||||
<div class="write-article-form">
|
||||
<h2>写文章</h2>
|
||||
<form @submit.prevent="submitNewArticle">
|
||||
<div>
|
||||
<label for="title">标题</label>
|
||||
<input type="text" id="title" v-model="newArticle.title" placeholder="输入标题" required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="author">作者</label>
|
||||
<input type="text" id="author" v-model="newArticle.author" placeholder="输入作者" required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="content">内容</label>
|
||||
<textarea id="content" v-model="newArticle.content" placeholder="输入内容" required></textarea>
|
||||
</div>
|
||||
<button type="submit">发布文章</button>
|
||||
<button type="button" @click="resetForm">取消</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
newArticle: {
|
||||
title: '',
|
||||
author: '',
|
||||
content: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submitNewArticle() {
|
||||
// 模拟提交文章逻辑
|
||||
if (this.newArticle.title && this.newArticle.author && this.newArticle.content) {
|
||||
console.log('发布文章:', this.newArticle);
|
||||
alert('文章已发布!');
|
||||
// 清空表单
|
||||
this.resetForm();
|
||||
} else {
|
||||
alert('请填写所有字段');
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
// 清空表单数据
|
||||
this.newArticle.title = '';
|
||||
this.newArticle.author = '';
|
||||
this.newArticle.content = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
.edit-container {
|
||||
padding: 20px;
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
form div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 8px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 15px 35px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #218838;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
button[type="button"] {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 15px 35px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[type="button"]:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.write-article-form {
|
||||
background-color: #f8f9fa;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
这是writer页面
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|