前端页面整改

frontend
黄佳程 4 months ago
parent 7b1439f0a9
commit e3b6203bed

@ -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);
}
};

@ -6,18 +6,6 @@
// biome-ignore lint: disable
export {}
declare global {
const ElAside: typeof import('element-plus/es')['ElAside']
const ElButton: typeof import('element-plus/es')['ElButton']
const ElCarousel: typeof import('element-plus/es')['ElCarousel']
const ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
const ElCol: typeof import('element-plus/es')['ElCol']
const ElContainer: typeof import('element-plus/es')['ElContainer']
const ElFooter: typeof import('element-plus/es')['ElFooter']
const ElHeader: typeof import('element-plus/es')['ElHeader']
const ElInput: typeof import('element-plus/es')['ElInput']
const ElMain: typeof import('element-plus/es')['ElMain']
const ElMenu: typeof import('element-plus/es')['ElMenu']
const ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElRow: typeof import('element-plus/es')['ElRow']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
}

@ -14,6 +14,8 @@ declare module 'vue' {
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDiver: typeof import('element-plus/es')['ElDiver']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDriver: typeof import('element-plus/es')['ElDriver']
@ -27,8 +29,10 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']

2
Frontend/env/.env vendored

@ -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

File diff suppressed because it is too large Load Diff

@ -14,8 +14,11 @@
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.7.7",
"element-plus": "^2.8.7",
"axios": "^1.7.9",
"bcryptjs": "^2.4.3",
"dotenv": "^16.4.7",
"element-plus": "^2.9.1",
"jsonwebtoken": "^9.0.2",
"museumv2-frontend": "file:",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
@ -42,6 +45,7 @@
"unplugin-auto-import": "^0.18.4",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.10",
"vite-plugin-env-compatible": "^2.0.1",
"vite-plugin-vue-devtools": "^7.5.4",
"vitest": "^2.1.4",
"vue-tsc": "^2.1.10"

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

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": "猫猫"
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

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
};
});

@ -51,7 +51,7 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '../stores/user';
import { useUserStore } from '../stores/user.ts';
const activeMenu = ref('/profile');
@ -63,7 +63,6 @@
})
const logout = () => {
userStore.logoutUser()
router.push("/")
};
</script>

@ -12,6 +12,7 @@
<el-col :span="6" class="auth-buttons">
<div class="auth-content">
<div v-if="!isLogin">
<el-button link @click="handleAdminLogin" class="login-button">管理员</el-button>
<el-button link @click="handleLogin" class="login-button">登录</el-button>
<el-button type="primary" @click="handleRegister" class="register-button">注册</el-button>
</div>
@ -85,91 +86,80 @@
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '../stores/user';
import { IUserInfo } from '../model/model';
import img1 from '@/assets/carousel/1.jpg';
import img2 from '@/assets/carousel/2.jpg';
import img3 from '@/assets/carousel/3.jpg';
import img4 from '@/assets/carousel/4.jpg';
import img5 from '@/assets/carousel/5.jpg';
import img6 from '@/assets/carousel/6.jpg';
const images = ref([img1, img2, img3, img4, img5, img6]);
const posts = ref([
{ id: 1, title: 'Vue 3 入门教程', author: 'John Doe', date: '2024-11-29', excerpt: '这是一篇关于 Vue 3 的入门教程,适合初学者阅读。' },
{ id: 2, title: 'Vue 3 响应式系统解析', author: 'Jane Doe', date: '2024-11-28', excerpt: '深入浅出地解析 Vue 3 中的响应式系统。' },
{ id: 3, title: 'Vue 3 性能优化技巧', author: 'Alice Smith', date: '2024-11-27', excerpt: '如何在 Vue 3 中实现性能优化,提升应用效率。' },
{ id: 4, title: 'Vue 3 Composition API 实战', author: 'Bob Brown', date: '2024-11-26', excerpt: '详细讲解 Vue 3 的 Composition API 并通过案例演示。' },
{ id: 5, title: 'Vue 3 路由详解', author: 'Carol White', date: '2024-11-25', excerpt: '讲解如何在 Vue 3 中配置和使用 Vue Router。' },
{ id: 6, title: 'Vue 3 状态管理Pinia vs Vuex', author: 'Dave Black', date: '2024-11-24', excerpt: '比较 Vue 3 中的 Pinia 和 Vuex帮助你选择最适合的状态管理工具。' },
{ id: 7, title: 'Vue 3 与 TypeScript 的完美结合', author: 'Eve Green', date: '2024-11-23', excerpt: '教你如何在 Vue 3 中使用 TypeScript 开发应用。' },
{ id: 8, title: 'Vue 3 表单验证最佳实践', author: 'Frank Blue', date: '2024-11-22', excerpt: '介绍如何在 Vue 3 中进行表单验证,提升用户体验。' },
{ id: 9, title: 'Vue 3 与 Webpack 配置优化', author: 'Grace Purple', date: '2024-11-21', excerpt: '讲解如何优化 Vue 3 与 Webpack 配合使用时的配置。' }
]);
const latestPosts = ref([
{ id: 1, title: '如何使用 Vue 3 构建高效的前端应用' },
{ id: 2, title: '深入浅出 Vue 3 响应式系统' },
{ id: 3, title: 'Vue 3 中的 Composition API 使用技巧' },
]);
const popularPosts = ref([
{ id: 4, title: 'Vue 3 性能优化实战' },
{ id: 5, title: '如何构建 Vue 3 + TypeScript 项目' },
{ id: 6, title: 'Vue 3 Router 动态路由实践' },
]);
const recommendedPosts = ref([
{ id: 7, title: 'Vue 3 状态管理Pinia vs Vuex' },
{ id: 8, title: '如何在 Vue 3 中使用 Vue CLI' },
{ id: 9, title: '从 Vue 2 到 Vue 3升级指南' },
]);
const router = useRouter();
const userStore = useUserStore();
const currentUser = reactive<IUserInfo>({ username: '', nickname: '', signature: '', phone: '', birthday: '', isAdmin: false, password: '' });
const isLogin = ref(false);
const init = () => {
const user = userStore.getCurrentUser();
if (user) {
currentUser.username = user.username || '';
currentUser.nickname = user.nickname || '';
currentUser.signature = user.signature || '';
currentUser.phone = user.phone || '';
currentUser.birthday = user.birthday || '';
currentUser.isAdmin = user.isAdmin || false;
currentUser.password = user.password || '';
isLogin.value = true;
} else {
isLogin.value = false;
}
};
import { onMounted, ref, computed } from 'vue'; // computed
import { useRouter } from 'vue-router';
import { useUserStore } from '../stores/user';
import { Article } from '../../api/articleApi';
//
import img1 from '@/assets/carousel/1.jpg';
import img2 from '@/assets/carousel/2.jpg';
import img3 from '@/assets/carousel/3.jpg';
import img4 from '@/assets/carousel/4.jpg';
import img5 from '@/assets/carousel/5.jpg';
import img6 from '@/assets/carousel/6.jpg';
const images = ref([img1, img2, img3, img4, img5, img6]);
const latestPosts = ref<Article[]>([]);
const popularPosts = ref<Article[]>([]);
const recommendedPosts = ref<Article[]>([]);
const posts = ref<Article[]>([]);
const store = useUserStore();
const router = useRouter();
// 使 computed
const isLogin = computed(() => store.isAuthenticated);
const currentUser = computed(() => store.currentUser);
const fetchArticles = async () => {
try {
const [latestRes, popularRes, recommendedRes, allArticlesRes] = await Promise.all([
articleApi.getLatestArticles(),
articleApi.getPopularArticles(),
articleApi.getRecommendedArticles(),
articleApi.getAllArticles()
]);
latestPosts.value = latestRes.data;
popularPosts.value = popularRes.data;
recommendedPosts.value = recommendedRes.data;
posts.value = allArticlesRes.data;
} catch (error) {
console.error('Failed to fetch articles:', error);
}
};
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString();
};
//
const handleLogin = () => {
router.push({ name: "Login" });
};
onMounted(() => {
init();
});
const handleAdminLogin = () => {
router.push("/Adminlogin");
};
const handleLogin = () => {
router.push({ "name": "Login" });
};
const handleRegister = () => {
router.push("/register");
};
const handleRegister = () => {
router.push("/register");
};
const handleLogout = () => {
store.logout();
router.push('/');
};
const handleLogout = () => {
userStore.logoutUser();
init();
router.push("/");
};
const gotoHome = () => {
router.push('/');
};
const gotoHome = () => {
router.push("/admin");
};
onMounted(() => {
fetchArticles();
});
</script>
<style scoped>

@ -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>

@ -3,40 +3,47 @@
<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" />
<img class="avatar" src="../assets/user/avatar.png" alt="User Avatar" />
</div>
<!-- 用户信息 -->
<div class="user-info">
<p class="user-item"><strong>账户类型:</strong> {{ currentUser.isAdmin ? '管理员' : '普通用户' }}</p>
<p class="user-item"><strong>账户类型:管理员</strong> </p>
<!-- 可编辑的昵称 -->
<p class="user-item">
<strong>昵称:</strong>
<input v-model="currentUser.username" class="editable-input" type="text" />
<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 v-if="!currentUser.isAdmin" class="user-item">
<!-- 手机号码 (管理员和普通用户都显示) -->
<p class="user-item">
<strong>手机号码:</strong>
<input v-model="currentUser.phone" class="editable-input" type="text" />
<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%;" v-if="!currentUser.isAdmin"></el-button>
<el-button type="primary" @click="saveUser" style="width: 100%;">保存</el-button>
</p>
</div>
</div>
@ -44,41 +51,72 @@
</template>
<script lang="ts" setup>
import { onMounted ,reactive} from 'vue';
import { onMounted, reactive } from 'vue';
import { useUserStore } from '../stores/user';
import { IUserInfo } from '../model/model';
import axios from 'axios';
// Piniauser store
const userStore = useUserStore();
const currentUser = reactive<IUserInfo>({
isAdmin: false,
nickname: '',
signature: '',
id: '',
username: '',
password: '',
phone: '',
signature: '',
birthday: '',
username: '',
password: ''
isAdmin: false,
security_questions: []
});
onMounted(() => {
//
const user = userStore.getCurrentUser()
currentUser.username = user.username || '';
currentUser.nickname = user.nickname || '';
currentUser.signature = user.signature || '';
currentUser.phone = user.phone || '';
currentUser.birthday = user.birthday || '';
currentUser.isAdmin = user.isAdmin || false;
currentUser.password = user.password || '';
console.log("current = ", currentUser);
})
const saveUser = () => {
// currentUser
userStore.updateUser(currentUser); //
console.log('用户信息已更新:', currentUser);
ElMessage.success("保存成功")
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>
@ -135,4 +173,8 @@ const saveUser = () => {
border-radius: 4px;
font-size: 14px;
}
.security-questions {
margin-top: 10px;
}
</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';
// Piniauser 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,128 @@
<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 { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { loginUser } from '../../../api/userApi'; // userApi.js loginUser
import { useUserStore } from '../../stores/index.ts';
const router = useRouter();
const userStore = useUserStore();
const formRef = ref(null);
const errorMessage = ref('');
//
const form = reactive({
phone: '', //
password: ''
});
//
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: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
]
});
//
const submitForm = async () => {
try {
await formRef.value.validate(); //
const response = await loginUser(form.phone, form.password);
//
ElMessage({
message: '登录成功',
type: 'success'
});
//
router.push('/admin');
} catch (error) {
errorMessage.value = error.message;
ElMessage.error('登录失败,请检查您的账号和密码');
}
};
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
};
//
const navigateToResetPassword = () => {
router.push('/reset-password');
};
//
const navigateToHome = () => {
router.push('/');
};
</script>
<style scoped>
.login-container {
background-image: url('../public/背景5.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,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,120 @@
<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 { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { loginUser } from '../../../api/userApi';
const router = useRouter();
const formRef = ref(null);
const errorMessage = ref('');
const form = reactive({
phone: '',
password: ''
});
const rules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1\d{10}$/, message: '请输入正确的11位手机号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
]
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
};
const navigateToResetPassword = () => {
router.push('/reset-password');
};
//
const navigateToHome = () => {
router.push('/');
};
const submitForm = async () => {
try {
await formRef.value.validate(); //
const response = await loginUser(form.phone, form.password);
ElMessage({
message: '登录成功',
type: 'success'
});
//
router.push('/profile1');
} catch (error) {
errorMessage.value = error.message;
ElMessage.error('登录失败,请检查您的账号和密码');
}
};
</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,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,123 @@
<template>
<el-main class="logout-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; color: red;margin-bottom: 10px;">
警告当前为注销操作
</div>
<div style="text-align: center; font-size: 20px; color: red;margin-bottom: 10px;">
如果您注销了该账号则该账号下绑定的一切信息都会丢失且不可恢复
</div>
<el-form :model="form" status-icon :rules="rules" ref="formRef" label-width="82px" label-position="left">
<el-form-item label="用户账号:" prop="account">
<el-input v-model="form.account" placeholder="请输入账号名/手机号/邮箱"></el-input>
</el-form-item>
<el-form-item label="密码:" prop="password">
<el-input type="password" v-model="form.password" placeholder="请输入密码"></el-input>
</el-form-item>
<div style="display: flex; justify-content: space-between; margin-top: 40px; margin-bottom: 10px;">
<el-link type="primary" @click="confirmLogout"></el-link>
<el-link type="primary" @click="cancel"></el-link>
</div>
</el-form>
</el-card>
</el-main>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { useRouter } from 'vue-router';
import axios from 'axios';
const router = useRouter();
const form = reactive({
account: '',
password: ''
});
const formRef = ref(null);
//
const validatePassword = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入密码'));
} else {
callback();
}
};
const rules = reactive({
account: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ validator: validatePassword, trigger: 'blur' }
]
});
const confirmLogout = 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:3001/users');
const user = users.data.find(user => user.account === form.account && user.password === form.password);
if (!user) {
ElMessage.error('账号或密码错误');
return;
}
// DELETE
await axios.delete(`http://localhost:3001/users/${user.id}`);
ElMessage.success('账号已成功注销');
router.push('/');
formRef.value.resetFields();
} catch (error) {
ElMessage.error(`注销账号失败: ${error.message}`);
}
} else {
ElMessage.error('注销账号失败,请检查输入信息');
}
};
const cancel = () => {
formRef.value.resetFields();
router.push('/user/myInfo');
};
</script>
<style scoped>
.logout-container {
background-image: url('../public/大背景.jpeg');
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(10px);
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,139 @@
<template>
<el-container style="height: 100%;">
<!-- Header -->
<el-header class="header">
<el-row type="flex" justify="space-between" style="width:100%;" align="middle">
<!-- Logo -->
<el-col :span="6" class="logo-container">
<img src="@/assets/logo.png" alt="Logo" style="height: 120px;">
</el-col>
<!-- Logout Button -->
<el-col :span="6" style="text-align: right;">
<el-button type="danger" @click="logout">退</el-button>
</el-col>
</el-row>
</el-header>
<!-- Main Content -->
<el-container style="height: 80%;">
<!-- Aside (Left Sidebar) -->
<el-aside class="custom-aside">
<el-menu :default-active="activeMenu" :router="true" class="custom-menu">
<el-menu-item index="/">
<i class="el-icon-house"></i>
<span>首页</span>
</el-menu-item>
<el-menu-item index="/profile1">
<i class="el-icon-user"></i>
<span>个人信息</span>
</el-menu-item>
<el-menu-item index="/edit">
<i class="el-icon-edit"></i>
<span>写文章</span>
</el-menu-item>
<el-menu-item index="/display">
<i class="el-icon-display"></i>
<span>文章列表</span>
</el-menu-item>
<el-menu-item index="/accounts">
<i class="el-icon-accounts"></i>
<span>账号安全</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- Main Section (Right Content) -->
<el-main>
<router-view />
</el-main>
</el-container>
<el-footer>
<span
style="height: 20px; position: fixed; bottom: 0; left: 0; right: 0; color: #333; text-align: center;">&copy;
2024 My Blog. All rights reserved.</span>
</el-footer>
</el-container>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '../stores/user';
const activeMenu = ref('/profile1'); //
const router = useRouter();
const userStore = useUserStore();
// /profile1
onMounted(() => {
if (userStore.isAuthenticated) {
router.push('/profile1');
}
});
// 退
const logout = () => {
// userStore.logoutUser();
router.push("/");
};
</script>
<style scoped>
.header {
height: 100px;
background-color: #ffffff;
border-bottom: 1px solid #ddd;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
/* 确保元素分布 */
overflow: hidden;
}
.logo {
height: 120px;
object-fit: contain;
overflow: hidden;
/* 防止 logo 超出容器 */
}
.el-header {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.custom-aside {
width: 220px;
height:100vh;
background-color: white; /* 更亮的背景色 */
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* 添加阴影 */
}
.custom-menu {
border: none;
}
.custom-menu .el-menu-item {
margin: 10px 0;
border-radius: 4px;
transition: background-color 0.3s, color 0.3s; /* 平滑的动画效果 */
}
.custom-menu .el-menu-item:hover {
background-color: #e6f7ff; /* 悬停背景 */
color: #1890ff; /* 悬停字体颜色 */
}
.custom-menu .el-menu-item.is-active {
background-color: #bae7ff; /* 激活状态背景 */
color: #096dd9; /* 激活状态字体颜色 */
}
</style>

@ -0,0 +1,166 @@
<template>
<el-container class="accounts">
<!-- 页面头部 -->
<el-header>
<h3 class="header-title">账户设置</h3>
</el-header>
<el-main>
<el-row justify="center" class="button-row">
<!-- 注销账号按钮 -->
<el-col :span="24" class="action-col">
<el-button type="danger" size="large" @click="showLogoutConfirm" class="action-btn">注销账号</el-button>
</el-col>
<!-- 修改密码按钮 -->
<el-col :span="24" class="action-col">
<el-button type="primary" size="large" @click="navigateToResetPassword" class="action-btn">修改密码</el-button>
</el-col>
</el-row>
</el-main>
</el-container>
</template>
<script>
import { ElMessageBox } from 'element-plus';
import { useRouter } from 'vue-router'; // Vue 3
export default {
setup() {
const router = useRouter(); // Vue 3
const showLogoutConfirm = () => {
ElMessageBox.confirm(
'您确定要注销您的账号吗?<br>此操作是不可逆的,并且会删除所有与该账号相关联的数据。<br>请谨慎操作。',
'警告',
{
dangerouslyUseHTMLString: true, // HTML
confirmButtonText: '取消', //
cancelButtonText: '我已阅读并同意注销', //
type: 'warning',
center: true,
customClass: 'custom-message-box', //
showClose: false, //
beforeClose: (action, instance, done) => {
if (action === 'cancel') { // action 'cancel'
//
console.log('用户确认了注销');
// API
//
setTimeout(() => {
done(); //
//
}, 1000);
} else {
done(); //
}
},
}
).catch(() => {
//
console.log('用户取消了注销');
});
};
const navigateToResetPassword = () => {
router.push('/reset-password');
};
return {
showLogoutConfirm,
navigateToResetPassword,
};
},
};
</script>
<style scoped>
.accounts {
padding: 30px;
background-color: #f5f7fa;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.el-header {
text-align: center;
font-size: 26px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
}
.header-title {
color: #409EFF;
font-family: 'Helvetica', sans-serif;
}
.button-row {
margin-top: 20px;
text-align: center;
}
.action-col {
margin-bottom: 20px;
}
.action-btn {
width: 200px;
height: 50px;
font-size: 16px;
border-radius: 25px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.action-btn:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.custom-dialog .el-dialog__header {
background-color: #409EFF;
color: white;
}
.password-form .el-form-item {
margin-bottom: 20px;
}
.input-field {
border-radius: 25px;
font-size: 14px;
height: 40px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.el-form-item label {
font-size: 14px;
color: #555;
}
.dialog-footer {
text-align: right;
}
.dialog-footer .el-button {
border-radius: 20px;
}
.el-button--primary {
background-color: #409EFF;
border-color: #409EFF;
transition: background-color 0.3s ease;
}
.el-button--primary:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
/* 增加按钮之间的间距 */
.custom-message-box .el-message-box__btns {
display: flex;
justify-content: space-between; /* 将按钮分布到两端 */
gap: 754px; /* 设置按钮之间的间距 */
}
</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>

@ -1,27 +1,26 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import { fileURLToPath, URL } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import vueDevTools from 'vite-plugin-vue-devtools';
import envCompatible from 'vite-plugin-env-compatible';
// Element-Plus 按需导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
// 根据当前工作目录process.cwd())中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有
// `VITE_` 前缀。
const env = loadEnv(mode, process.cwd()+"/env", 'VITE_')
// console.log("env = ", env)
const env = loadEnv(mode, process.cwd(), 'VITE_');
return {
plugins: [
vue(),
vueJsx(),
vueDevTools(),
envCompatible(), // 添加 vite-plugin-env-compatible 插件
AutoImport({
resolvers: [ElementPlusResolver()],
}),
@ -31,20 +30,20 @@ export default defineConfig(({ mode }) => {
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
// 处理跨域问题
server: {
port: 3000,
port: 5173, // 强制 Vite 使用 5173 端口
strictPort: true, // 如果 5173 端口被占用Vite 将不会自动选择其他端口,而是抛出错误
proxy: {
"/api/v1": {
'/api/v1': {
target: env.VITE_SERVER_URL,
changeOrigin: true,
}
}
rewrite: (path) => path.replace(/^\/api\/v1/, ''), // 重写路径,去掉前缀
},
},
},
// 自定义环境变量的目录
envDir: "env"
}
})
envDir: "env", // 自定义环境变量的目录
};
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save