Compare commits
No commits in common. 'frontend' and 'main' have entirely different histories.
@ -1,6 +0,0 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
@ -1,32 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.vscode
|
@ -1,7 +0,0 @@
|
||||
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
# MuseumV2_Frontend
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
@ -1,27 +0,0 @@
|
||||
// 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');
|
||||
}
|
||||
};
|
@ -1,56 +0,0 @@
|
||||
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}`);
|
||||
}
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
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);
|
||||
}
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
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']
|
||||
ElFooter: typeof import('element-plus/es')['ElFooter']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElList: typeof import('element-plus/es')['ElList']
|
||||
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']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
|
||||
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
|
||||
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
|
||||
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
|
||||
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
|
||||
WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
@ -1,2 +0,0 @@
|
||||
# .env
|
||||
VITE_SERVER_URL=http://localhost:8080/api/users
|
@ -1,3 +0,0 @@
|
||||
# .env.development
|
||||
VITE_API_BASE_URL=http://localhost:8080/api
|
||||
VITE_SERVER_URL=http://localhost:8080/api/users
|
@ -1,3 +0,0 @@
|
||||
VITE_SERVER_URL = "http://127.0.0.1:9099"
|
||||
VITE_BASE_URL = "/api/v1"
|
||||
VITE_HTTP_TIMEOUT = 5000
|
@ -1,25 +0,0 @@
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import vueTsEslintConfig from '@vue/eslint-config-typescript'
|
||||
import pluginVitest from '@vitest/eslint-plugin'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||
},
|
||||
|
||||
...pluginVue.configs['flat/essential'],
|
||||
...vueTsEslintConfig(),
|
||||
|
||||
{
|
||||
...pluginVitest.configs.recommended,
|
||||
files: ['src/**/__tests__/*'],
|
||||
},
|
||||
skipFormatting,
|
||||
]
|
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"name": "museumv2-frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"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",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.9.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vitest/eslint-plugin": "1.1.7",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.1.3",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "~5.6.3",
|
||||
"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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 234 KiB |
Before Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 234 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 238 KiB |
@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
// import HomeView from './views/HomeView.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"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": "猫猫"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 503 KiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 550 KiB |
Before Width: | Height: | Size: 821 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 663 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 26 KiB |
@ -1,13 +0,0 @@
|
||||
/* @import './base.css'; */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,body,#app{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 84 KiB |
@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,11 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { mount } from '@vue/test-utils'
|
||||
import HelloWorld from '../HelloWorld.vue'
|
||||
|
||||
describe('HelloWorld', () => {
|
||||
it('renders properly', () => {
|
||||
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
||||
expect(wrapper.text()).toContain('Hello Vitest')
|
||||
})
|
||||
})
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,19 +0,0 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
@ -1,44 +0,0 @@
|
||||
{
|
||||
"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,38 +0,0 @@
|
||||
|
||||
|
||||
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(piniaPluginPersistedstate);
|
||||
|
||||
// 创建 Vue 应用
|
||||
const app = createApp(App);
|
||||
|
||||
// 使用 Pinia 和路由
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(ElementPlus);
|
||||
// 挂载应用
|
||||
app.mount('#app');
|
||||
// 在 main.js 或单独的文件中
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
// 添加请求拦截器
|
||||
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,11 +0,0 @@
|
||||
interface IUserInfo {
|
||||
username: string;
|
||||
password: string;
|
||||
phone?: string;
|
||||
isAdmin: boolean;
|
||||
nickname?: string;
|
||||
signature?: string;
|
||||
birthday?: string;
|
||||
}
|
||||
|
||||
export type { IUserInfo}
|
@ -1,124 +0,0 @@
|
||||
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: 'Home',
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: '/post/:id', // 使用动态参数 :id
|
||||
name: 'PostDetail',
|
||||
component: Post,
|
||||
props: true, // 将路由参数传递给组件
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
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: AdminView,
|
||||
children: [
|
||||
{
|
||||
path: '/profile',
|
||||
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;
|
@ -1,2 +0,0 @@
|
||||
# .env
|
||||
JWT_SECRET=your_secret_key_here
|
@ -1,55 +0,0 @@
|
||||
<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>
|
@ -1,878 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// 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}`);
|
||||
// });
|
@ -1,32 +0,0 @@
|
||||
// src/stores/authStore.js
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
isAuthenticated: false, // 用户是否已登录
|
||||
user: null, // 用户信息
|
||||
}),
|
||||
actions: {
|
||||
async login(phone, password) {
|
||||
try {
|
||||
const response = await loginUser(phone, password);
|
||||
this.isAuthenticated = true;
|
||||
this.user = response.data; // 假设 API 返回的数据包含用户信息
|
||||
ElMessage({
|
||||
message: '登录成功',
|
||||
type: 'success'
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.isAuthenticated = false;
|
||||
this.user = null;
|
||||
ElMessage.error('登录失败,请检查您的账号和密码');
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.isAuthenticated = false;
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,23 +0,0 @@
|
||||
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 中获取
|
||||
}
|
||||
}
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
// user.js (store)
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
isAuthenticated: false,
|
||||
currentUser: null,
|
||||
}),
|
||||
actions: {
|
||||
loginUser(username, password) {
|
||||
// 假设这里的逻辑是调用后端API进行登录验证
|
||||
// 登录成功后更新状态
|
||||
this.isAuthenticated = true;
|
||||
this.currentUser = { username, password }; // 简化示例,实际应包含更多用户信息
|
||||
|
||||
// 这里可以添加更多的业务逻辑,比如保存到本地存储等
|
||||
},
|
||||
logoutUser() {
|
||||
this.isAuthenticated = false;
|
||||
this.currentUser = null;
|
||||
|
||||
// 清除本地存储等操作
|
||||
}
|
||||
}
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
// src/stores/user.ts
|
||||
import { defineStore } from 'pinia';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
isLoggedIn,
|
||||
isLoggedInAsAdmin,
|
||||
setLoginState,
|
||||
logout,
|
||||
setCurrentUser,
|
||||
getCurrentUser,
|
||||
updateUser
|
||||
};
|
||||
});
|
@ -1,127 +0,0 @@
|
||||
<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="/profile">
|
||||
<i class="el-icon-user"></i>
|
||||
<span>个人信息</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/article">
|
||||
<i class="el-icon-document"></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;">©
|
||||
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.ts';
|
||||
|
||||
|
||||
const activeMenu = ref('/profile');
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(() => {
|
||||
router.push(activeMenu.value)
|
||||
})
|
||||
|
||||
const logout = () => {
|
||||
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>
|
@ -1,461 +0,0 @@
|
||||
<template>
|
||||
<el-container class="home">
|
||||
<!-- 头部 -->
|
||||
<el-header class="header">
|
||||
<el-row type="flex" justify="space-between" align="middle" style="width:100%;">
|
||||
<el-col :span="6" class="logo-container">
|
||||
<img src="@/assets/logo.png" alt="Logo" class="logo">
|
||||
</el-col>
|
||||
<el-col :span="12" class="search-col">
|
||||
<el-input placeholder="搜索..." prefix-icon="el-icon-search" size="large" class="search-input"></el-input>
|
||||
</el-col>
|
||||
<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>
|
||||
<div v-else>
|
||||
<el-link type="primary" :underline="false" @click="gotoHome" class="username-link">
|
||||
{{ currentUser.username }}
|
||||
</el-link>
|
||||
<el-button type="danger" @click="handleLogout" class="logout-button">退出登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
|
||||
|
||||
<!-- 轮播图 -->
|
||||
<el-container>
|
||||
<el-main>
|
||||
<el-carousel trigger="click" height="400px" arrow="always" class="carousel">
|
||||
<el-carousel-item v-for="(item, index) in images" :key="index">
|
||||
<img :src="item" class="carousel-img" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<!-- 文章内容部分 -->
|
||||
<el-container class="content-container">
|
||||
<el-aside width="400px" class="sidebar">
|
||||
<h3 class="section-title">最新文章</h3>
|
||||
<el-menu class="menu">
|
||||
<el-menu-item v-for="post in latestPosts" :key="post.id">
|
||||
<router-link :to="'/post/' + post.id" class="link-item">{{ post.title }}</router-link>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<h3 class="section-title">热门文章</h3>
|
||||
<el-menu class="menu">
|
||||
<el-menu-item v-for="post in popularPosts" :key="post.id">
|
||||
<router-link :to="'/post/' + post.id" class="link-item">{{ post.title }}</router-link>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<h3 class="section-title">推荐文章</h3>
|
||||
<el-menu class="menu">
|
||||
<el-menu-item v-for="post in recommendedPosts" :key="post.id">
|
||||
<router-link :to="'/post/' + post.id" class="link-item">{{ post.title }}</router-link>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<el-divider></el-divider>
|
||||
|
||||
</el-aside>
|
||||
|
||||
|
||||
<el-main class="posts-container">
|
||||
<div v-for="post in posts" :key="post.id" class="blog-post">
|
||||
<router-link :to="'/post/' + post.id">
|
||||
<h3>{{ post.title }}</h3>
|
||||
</router-link>
|
||||
<p><strong>Author:</strong> {{ post.author }} | <strong>Published:</strong> {{ post.date }}</p>
|
||||
<p>{{ post.excerpt }}</p>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<!-- 底部 -->
|
||||
<el-footer class="footer">
|
||||
<span>© 2024 My Blog. All rights reserved.</span>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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" });
|
||||
};
|
||||
|
||||
const handleAdminLogin = () => {
|
||||
router.push("/Adminlogin");
|
||||
};
|
||||
|
||||
const handleRegister = () => {
|
||||
router.push("/register");
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
store.logout();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const gotoHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchArticles();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础样式 */
|
||||
.home {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-image: url('@/assets/bg1.jpg'); /* Path to your background image */
|
||||
background-size: cover; /* Ensures the image covers the entire page */
|
||||
background-position: center; /* Centers the image */
|
||||
background-repeat: no-repeat;
|
||||
/* 设置背景色 */
|
||||
overflow-x: hidden;
|
||||
/* 防止内容横向溢出 */
|
||||
}
|
||||
|
||||
/* Header 样式 */
|
||||
.header {
|
||||
height: 100px;
|
||||
background-color: rgba(255, 255, 255, 0.8);;
|
||||
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 超出容器 */
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 登录注册按钮 */
|
||||
.auth-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-button,
|
||||
.register-button,
|
||||
.logout-button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.username-link {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 轮播图样式 */
|
||||
.carousel {
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
/* 遮住轮播图超出的部分 */
|
||||
}
|
||||
|
||||
.carousel-img {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
max-height: 100%;
|
||||
/* 确保图片不会溢出容器 */
|
||||
}
|
||||
|
||||
/* 内容容器 */
|
||||
.content-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: rgba(255, 255, 255, 0.8);;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
width: 280px;
|
||||
/* 控制侧边栏的宽度 */
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
}
|
||||
|
||||
.menu {
|
||||
background-color: rgba(255, 255, 255, 0);;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-bottom: 10px;
|
||||
font-weight: 600;
|
||||
|
||||
word-wrap: break-word;
|
||||
/* 防止标题溢出 */
|
||||
white-space: normal;
|
||||
/* 保证标题文本正常换行 */
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
}
|
||||
|
||||
.link-item {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s ease, padding-left 0.3s ease;
|
||||
display: block;
|
||||
/* 使链接成为块级元素 */
|
||||
padding: 8px 0;
|
||||
/* 给链接增加上下内边距 */
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
color: #1a73e8;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/* 帖子列表容器 */
|
||||
.posts-container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background-color:rgba(255, 255, 255, 0.8);;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
}
|
||||
|
||||
/* 博客文章样式 */
|
||||
.blog-post {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.blog-post:hover {
|
||||
background-color: #f9f9f9;
|
||||
/* 鼠标悬浮时背景色变化 */
|
||||
transform: translateY(-5px);
|
||||
/* 鼠标悬浮时向上浮动 */
|
||||
}
|
||||
|
||||
/* 文章标题样式 */
|
||||
.blog-post h3 {
|
||||
font-size: 22px;
|
||||
color: #1a73e8;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
transition: color 0.3s ease;
|
||||
word-wrap: break-word;
|
||||
/* 防止标题溢出 */
|
||||
white-space: normal;
|
||||
/* 保证标题文本正常换行 */
|
||||
overflow: hidden;
|
||||
/* 遮住超出部分 */
|
||||
}
|
||||
|
||||
.blog-post h3:hover {
|
||||
color: #0066cc;
|
||||
/* 悬浮时标题颜色变化 */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 文章元数据样式 (作者和发布时间) */
|
||||
.blog-post p {
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.blog-post p strong {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 文章摘要样式 */
|
||||
.blog-post .excerpt {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
margin-top: 10px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
/* 限制显示的行数 */
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
/* 超出的部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
/* 省略号 */
|
||||
}
|
||||
|
||||
.blog-post .excerpt:hover {
|
||||
text-decoration: underline;
|
||||
/* 悬浮时摘要增加下划线 */
|
||||
}
|
||||
|
||||
/* 响应式布局:适应小屏幕 */
|
||||
@media (max-width: 768px) {
|
||||
.posts-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.blog-post {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.blog-post h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.blog-post p {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer 样式 */
|
||||
.footer {
|
||||
background-color: #ffffff;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.footer span {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 响应式布局:适应小屏幕 */
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.content-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
}
|
||||
|
||||
.posts-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carousel-img {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
@ -1,180 +0,0 @@
|
||||
<template>
|
||||
<div class="personal-info">
|
||||
<div class="info-container">
|
||||
<!-- 头像 -->
|
||||
<div class="avatar-container">
|
||||
<img class="avatar" src="../assets/user/avatar.png" alt="User Avatar" />
|
||||
</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div class="user-info">
|
||||
<p class="user-item"><strong>账户类型:管理员</strong> </p>
|
||||
|
||||
<!-- 可编辑的昵称 -->
|
||||
<p class="user-item">
|
||||
<strong>昵称:</strong>
|
||||
<input v-model="currentUser.username" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的个性签名 -->
|
||||
<p class="user-item">
|
||||
<strong>个性签名:</strong>
|
||||
<input v-model="currentUser.signature" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 手机号码 (管理员和普通用户都显示) -->
|
||||
<p class="user-item">
|
||||
<strong>手机号码:</strong>
|
||||
<input v-model="currentUser.phone" class="editable-input" type="text" disabled/>
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的生日 -->
|
||||
<p class="user-item">
|
||||
<strong>生日:</strong>
|
||||
<input v-model="currentUser.birthday" class="editable-input" type="date" />
|
||||
</p>
|
||||
|
||||
<!-- 安全问题 (管理员和普通用户都显示) -->
|
||||
<div class="security-questions">
|
||||
<p v-for="(question, index) in currentUser.security_questions" :key="index" class="user-item">
|
||||
<strong>{{ question.question }}:</strong>
|
||||
<input v-model="question.answer" class="editable-input" type="text" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="user-item">
|
||||
<el-button type="primary" @click="saveUser" style="width: 100%;">保存</el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { IUserInfo } from '../model/model';
|
||||
import axios from 'axios';
|
||||
|
||||
// 从Pinia中获取user store
|
||||
const userStore = useUserStore();
|
||||
|
||||
const currentUser = reactive<IUserInfo>({
|
||||
id: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
signature: '',
|
||||
birthday: '',
|
||||
isAdmin: false,
|
||||
security_questions: []
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 获取当前用户
|
||||
const user = userStore.getCurrentUser();
|
||||
if (user && user.username) {
|
||||
fetchUserInfo(user.username);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchUserInfo = async (username: string) => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (userStore.isLoggedInAsAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins?username=${username}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users?username=${username}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(apiUrl);
|
||||
if (response.data.length > 0) {
|
||||
const user = response.data[0];
|
||||
Object.assign(currentUser, user);
|
||||
currentUser.isAdmin = userStore.isLoggedInAsAdmin; // 确保用户类型一致
|
||||
console.log("Fetched user info:", currentUser);
|
||||
} else {
|
||||
console.error('No user found with the provided username.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (currentUser.isAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins/${currentUser.id}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users/${currentUser.id}`;
|
||||
}
|
||||
|
||||
await axios.put(apiUrl, currentUser);
|
||||
console.log('用户信息已更新:', currentUser);
|
||||
ElMessage.success("保存成功");
|
||||
} catch (error) {
|
||||
console.error('Error updating user info:', error);
|
||||
ElMessage.error("保存失败,请稍后再试");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整体布局居中 */
|
||||
.personal-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-align: left;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-item strong {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 样式修改:让输入框看起来更整洁 */
|
||||
.editable-input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.security-questions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
@ -1,181 +0,0 @@
|
||||
<template>
|
||||
<div class="personal-info">
|
||||
<div class="info-container">
|
||||
<!-- 头像 -->
|
||||
<div class="avatar-container">
|
||||
<img v-if="currentUser.isAdmin" class="avatar" src="../assets/user/avatar.png" alt="User Avatar" />
|
||||
<img v-else class="avatar" src="../assets/user/user.png" alt="User Avatar" />
|
||||
</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div class="user-info">
|
||||
<p class="user-item"><strong>账户类型:普通用户</strong></p>
|
||||
|
||||
<!-- 可编辑的昵称 -->
|
||||
<p class="user-item">
|
||||
<strong>昵称:</strong>
|
||||
<input v-model="currentUser.username" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的个性签名 -->
|
||||
<p class="user-item">
|
||||
<strong>个性签名:</strong>
|
||||
<input v-model="currentUser.signature" class="editable-input" type="text" />
|
||||
</p>
|
||||
|
||||
<!-- 手机号码 (管理员和普通用户都显示) -->
|
||||
<p class="user-item">
|
||||
<strong>手机号码:</strong>
|
||||
<input v-model="currentUser.phone" class="editable-input" type="text" disabled/>
|
||||
</p>
|
||||
|
||||
<!-- 可编辑的生日 -->
|
||||
<p class="user-item">
|
||||
<strong>生日:</strong>
|
||||
<input v-model="currentUser.birthday" class="editable-input" type="date" />
|
||||
</p>
|
||||
|
||||
<!-- 安全问题 (管理员和普通用户都显示) -->
|
||||
<div class="security-questions">
|
||||
<p v-for="(question, index) in currentUser.security_questions" :key="index" class="user-item">
|
||||
<strong>{{ question.question }}:</strong>
|
||||
<input v-model="question.answer" class="editable-input" type="text" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="user-item">
|
||||
<el-button type="primary" @click="saveUser" style="width: 100%;">保存</el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { IUserInfo } from '../model/model';
|
||||
import axios from 'axios';
|
||||
|
||||
// 从Pinia中获取user store
|
||||
const userStore = useUserStore();
|
||||
|
||||
const currentUser = reactive<IUserInfo>({
|
||||
id: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
signature: '',
|
||||
birthday: '',
|
||||
isAdmin: false,
|
||||
security_questions: []
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 获取当前用户
|
||||
const user = userStore.getCurrentUser();
|
||||
if (user && user.username) {
|
||||
fetchUserInfo(user.username);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchUserInfo = async (username: string) => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (userStore.isLoggedInAsAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins?username=${username}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users?username=${username}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(apiUrl);
|
||||
if (response.data.length > 0) {
|
||||
const user = response.data[0];
|
||||
Object.assign(currentUser, user);
|
||||
currentUser.isAdmin = userStore.isLoggedInAsAdmin; // 确保用户类型一致
|
||||
console.log("Fetched user info:", currentUser);
|
||||
} else {
|
||||
console.error('No user found with the provided username.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
try {
|
||||
let apiUrl = '';
|
||||
if (currentUser.isAdmin) {
|
||||
apiUrl = `http://localhost:3003/admins/${currentUser.id}`;
|
||||
} else {
|
||||
apiUrl = `http://localhost:3002/users/${currentUser.id}`;
|
||||
}
|
||||
|
||||
await axios.put(apiUrl, currentUser);
|
||||
console.log('用户信息已更新:', currentUser);
|
||||
ElMessage.success("保存成功");
|
||||
} catch (error) {
|
||||
console.error('Error updating user info:', error);
|
||||
ElMessage.error("保存失败,请稍后再试");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整体布局居中 */
|
||||
.personal-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-align: left;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-item strong {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 样式修改:让输入框看起来更整洁 */
|
||||
.editable-input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.security-questions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
@ -1,284 +0,0 @@
|
||||
<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>
|
@ -1,173 +0,0 @@
|
||||
<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>
|
@ -1,284 +0,0 @@
|
||||
<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>
|
@ -1,141 +0,0 @@
|
||||
<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>
|
@ -1,357 +0,0 @@
|
||||
<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>
|
@ -1,173 +0,0 @@
|
||||
<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>
|
@ -1,269 +0,0 @@
|
||||
<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>
|
@ -1,153 +0,0 @@
|
||||
<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>
|
@ -1,9 +0,0 @@
|
||||
<template>
|
||||
这是writer页面
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "auto-imports.d.ts",
|
||||
"components.d.ts",],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"noEmit": false
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"auto-imports.d.ts",
|
||||
"components.d.ts",
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.vitest.json"
|
||||
}
|
||||
],
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"auto-imports.d.ts",
|
||||
"components.d.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": false,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||
|
||||
"lib": [],
|
||||
"types": ["node", "jsdom"]
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||
import viteConfig from './vite.config'
|
||||
|
||||
export default mergeConfig(
|
||||
viteConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||
},
|
||||
}),
|
||||
)
|