feat: 实现修改管理员密码的功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { getDb } from '../database';
|
import { getDb } from '../database';
|
||||||
|
import sqlite3, { RunResult } from 'sqlite3'; // 导入 RunResult 类型
|
||||||
|
|
||||||
const db = getDb(); // 获取数据库实例
|
const db = getDb(); // 获取数据库实例
|
||||||
|
|
||||||
@@ -80,3 +81,96 @@ export const login = async (req: Request, res: Response): Promise<void> => {
|
|||||||
// 其他认证相关函数的占位符 (登出, 管理员设置等)
|
// 其他认证相关函数的占位符 (登出, 管理员设置等)
|
||||||
// export const logout = ...
|
// export const logout = ...
|
||||||
// export const setupAdmin = ...
|
// export const setupAdmin = ...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理修改密码请求 (PUT /api/v1/auth/password)
|
||||||
|
*/
|
||||||
|
export const changePassword = async (req: Request, res: Response): Promise<void> => {
|
||||||
|
const { currentPassword, newPassword } = req.body;
|
||||||
|
const userId = req.session.userId; // 从会话中获取用户 ID
|
||||||
|
|
||||||
|
// 检查用户是否登录
|
||||||
|
if (!userId) {
|
||||||
|
res.status(401).json({ message: '用户未认证,请先登录。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础输入验证
|
||||||
|
if (!currentPassword || !newPassword) {
|
||||||
|
res.status(400).json({ message: '当前密码和新密码不能为空。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:添加新密码复杂度要求
|
||||||
|
if (newPassword.length < 8) {
|
||||||
|
res.status(400).json({ message: '新密码长度至少需要 8 位。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentPassword === newPassword) {
|
||||||
|
res.status(400).json({ message: '新密码不能与当前密码相同。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取当前用户的哈希密码
|
||||||
|
const user = await new Promise<User | undefined>((resolve, reject) => {
|
||||||
|
db.get('SELECT id, hashed_password FROM users WHERE id = ?', [userId], (err, row: User) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`查询用户 ${userId} 时出错:`, err.message);
|
||||||
|
return reject(new Error('数据库查询失败'));
|
||||||
|
}
|
||||||
|
resolve(row);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// 理论上不应该发生,因为 userId 来自 session
|
||||||
|
console.error(`修改密码错误: 未找到 ID 为 ${userId} 的用户。`);
|
||||||
|
res.status(404).json({ message: '用户不存在。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证当前密码
|
||||||
|
const isMatch = await bcrypt.compare(currentPassword, user.hashed_password);
|
||||||
|
if (!isMatch) {
|
||||||
|
console.log(`修改密码尝试失败: 当前密码错误 - 用户 ID ${userId}`);
|
||||||
|
res.status(400).json({ message: '当前密码不正确。' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 哈希新密码
|
||||||
|
const saltRounds = 10;
|
||||||
|
const newHashedPassword = await bcrypt.hash(newPassword, saltRounds);
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
// 4. 更新数据库中的密码
|
||||||
|
await new Promise<void>((resolveUpdate, rejectUpdate) => {
|
||||||
|
const stmt = db.prepare(
|
||||||
|
'UPDATE users SET hashed_password = ?, updated_at = ? WHERE id = ?'
|
||||||
|
);
|
||||||
|
// 在回调函数中明确 this 的类型为 RunResult
|
||||||
|
stmt.run(newHashedPassword, now, userId, function (this: RunResult, err: Error | null) {
|
||||||
|
if (err) {
|
||||||
|
console.error(`更新用户 ${userId} 密码时出错:`, err.message);
|
||||||
|
return rejectUpdate(new Error('更新密码失败'));
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
// 理论上不应该发生
|
||||||
|
console.error(`修改密码错误: 更新影响行数为 0 - 用户 ID ${userId}`);
|
||||||
|
return rejectUpdate(new Error('未找到要更新的用户'));
|
||||||
|
}
|
||||||
|
console.log(`用户 ${userId} 密码已成功修改。`);
|
||||||
|
resolveUpdate();
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 返回成功响应
|
||||||
|
res.status(200).json({ message: '密码已成功修改。' });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`修改用户 ${userId} 密码时发生内部错误:`, error);
|
||||||
|
res.status(500).json({ message: '修改密码过程中发生内部服务器错误。' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { login } from './auth.controller';
|
import { login, changePassword } from './auth.controller'; // 导入 changePassword
|
||||||
|
import { isAuthenticated } from './auth.middleware'; // 导入认证中间件
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// POST /api/v1/auth/login - 用户登录接口
|
// POST /api/v1/auth/login - 用户登录接口
|
||||||
router.post('/login', login);
|
router.post('/login', login);
|
||||||
|
|
||||||
|
// PUT /api/v1/auth/password - 修改密码接口 (需要认证)
|
||||||
|
router.put('/password', isAuthenticated, changePassword);
|
||||||
|
|
||||||
// 未来可以添加的其他认证相关路由
|
// 未来可以添加的其他认证相关路由
|
||||||
// router.post('/logout', logout); // 登出
|
// router.post('/logout', logout); // 登出
|
||||||
// router.get('/status', getStatus); // 获取当前登录状态
|
// router.get('/status', getStatus); // 获取当前登录状态
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import connectionsRouter from './connections/connections.routes';
|
|||||||
import sftpRouter from './sftp/sftp.routes';
|
import sftpRouter from './sftp/sftp.routes';
|
||||||
import proxyRoutes from './proxies/proxies.routes'; // 导入代理路由
|
import proxyRoutes from './proxies/proxies.routes'; // 导入代理路由
|
||||||
import tagsRouter from './tags/tags.routes'; // 导入标签路由
|
import tagsRouter from './tags/tags.routes'; // 导入标签路由
|
||||||
|
import settingsRoutes from './settings/settings.routes'; // 导入设置路由
|
||||||
import { initializeWebSocket } from './websocket';
|
import { initializeWebSocket } from './websocket';
|
||||||
|
|
||||||
// 基础 Express 应用设置 (后续会扩展)
|
// 基础 Express 应用设置 (后续会扩展)
|
||||||
@@ -86,6 +87,7 @@ app.use('/api/v1/connections', connectionsRouter);
|
|||||||
app.use('/api/v1/sftp', sftpRouter);
|
app.use('/api/v1/sftp', sftpRouter);
|
||||||
app.use('/api/v1/proxies', proxyRoutes); // 挂载代理相关的路由
|
app.use('/api/v1/proxies', proxyRoutes); // 挂载代理相关的路由
|
||||||
app.use('/api/v1/tags', tagsRouter); // 挂载标签相关的路由
|
app.use('/api/v1/tags', tagsRouter); // 挂载标签相关的路由
|
||||||
|
app.use('/api/v1/settings', settingsRoutes); // 挂载设置相关的路由
|
||||||
|
|
||||||
// 状态检查接口
|
// 状态检查接口
|
||||||
app.get('/api/v1/status', (req: Request, res: Response) => {
|
app.get('/api/v1/status', (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { getDb } from '../database'; // 正确导入 getDb 函数
|
||||||
|
|
||||||
|
const db = getDb(); // 获取数据库实例
|
||||||
|
|
||||||
|
export interface Setting {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settingsRepository = {
|
||||||
|
async getAllSettings(): Promise<Setting[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.all('SELECT key, value FROM settings', (err: any, rows: Setting[]) => { // 添加 err 类型
|
||||||
|
if (err) {
|
||||||
|
console.error('获取所有设置时出错:', err); // 更新日志为中文
|
||||||
|
reject(new Error('获取设置失败')); // 更新错误消息为中文
|
||||||
|
} else {
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSetting(key: string): Promise<string | null> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.get('SELECT value FROM settings WHERE key = ?', [key], (err: any, row: { value: string } | undefined) => { // 添加 err 类型
|
||||||
|
if (err) {
|
||||||
|
console.error(`获取设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||||
|
reject(new Error(`获取设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||||
|
} else {
|
||||||
|
resolve(row ? row.value : null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async setSetting(key: string, value: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
|
||||||
|
[key, value],
|
||||||
|
function (err: any) { // 添加 err 类型
|
||||||
|
if (err) {
|
||||||
|
console.error(`设置设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||||
|
reject(new Error(`设置设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteSetting(key: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.run('DELETE FROM settings WHERE key = ?', [key], function (err: any) { // 添加 err 类型
|
||||||
|
if (err) {
|
||||||
|
console.error(`删除设置项 ${key} 时出错:`, err); // 更新日志为中文
|
||||||
|
reject(new Error(`删除设置项 ${key} 失败`)); // 更新错误消息为中文
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async setMultipleSettings(settings: Record<string, string>): Promise<void> {
|
||||||
|
const promises = Object.entries(settings).map(([key, value]) =>
|
||||||
|
this.setSetting(key, value)
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { settingsRepository, Setting } from '../repositories/settings.repository';
|
||||||
|
|
||||||
|
export const settingsService = {
|
||||||
|
/**
|
||||||
|
* 获取所有设置项
|
||||||
|
* @returns 返回包含所有设置项的数组
|
||||||
|
*/
|
||||||
|
async getAllSettings(): Promise<Record<string, string>> {
|
||||||
|
const settingsArray = await settingsRepository.getAllSettings();
|
||||||
|
const settingsRecord: Record<string, string> = {};
|
||||||
|
settingsArray.forEach(setting => {
|
||||||
|
settingsRecord[setting.key] = setting.value;
|
||||||
|
});
|
||||||
|
return settingsRecord;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个设置项的值
|
||||||
|
* @param key 设置项的键
|
||||||
|
* @returns 返回设置项的值,如果不存在则返回 null
|
||||||
|
*/
|
||||||
|
async getSetting(key: string): Promise<string | null> {
|
||||||
|
return settingsRepository.getSetting(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单个设置项的值 (如果键已存在则更新)
|
||||||
|
* @param key 设置项的键
|
||||||
|
* @param value 设置项的值
|
||||||
|
*/
|
||||||
|
async setSetting(key: string, value: string): Promise<void> {
|
||||||
|
await settingsRepository.setSetting(key, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置多个设置项的值
|
||||||
|
* @param settings 包含多个设置项键值对的对象
|
||||||
|
*/
|
||||||
|
async setMultipleSettings(settings: Record<string, string>): Promise<void> {
|
||||||
|
await settingsRepository.setMultipleSettings(settings);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个设置项
|
||||||
|
* @param key 要删除的设置项的键
|
||||||
|
*/
|
||||||
|
async deleteSetting(key: string): Promise<void> {
|
||||||
|
await settingsRepository.deleteSetting(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { settingsService } from '../services/settings.service';
|
||||||
|
|
||||||
|
export const settingsController = {
|
||||||
|
/**
|
||||||
|
* 获取所有设置项
|
||||||
|
*/
|
||||||
|
async getAllSettings(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const settings = await settingsService.getAllSettings();
|
||||||
|
res.json(settings);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取所有设置时出错:', error);
|
||||||
|
res.status(500).json({ message: '获取设置失败', error: error.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新设置项
|
||||||
|
*/
|
||||||
|
async updateSettings(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
// TODO: 添加输入验证,确保 req.body 是 Record<string, string>
|
||||||
|
const settingsToUpdate: Record<string, string> = req.body;
|
||||||
|
if (typeof settingsToUpdate !== 'object' || settingsToUpdate === null) {
|
||||||
|
res.status(400).json({ message: '无效的请求体,应为 JSON 对象' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 可以在这里添加更严格的验证,例如检查值的类型等
|
||||||
|
|
||||||
|
await settingsService.setMultipleSettings(settingsToUpdate);
|
||||||
|
res.status(200).json({ message: '设置已成功更新' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('更新设置时出错:', error);
|
||||||
|
res.status(500).json({ message: '更新设置失败', error: error.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 注意:通常不直接通过 API 提供单个设置项的获取、设置或删除,
|
||||||
|
// 而是通过批量获取/更新来管理。如果需要单独操作,可以添加相应方法。
|
||||||
|
// 例如:
|
||||||
|
// async getSetting(req: Request, res: Response): Promise<void> { ... }
|
||||||
|
// async setSetting(req: Request, res: Response): Promise<void> { ... }
|
||||||
|
// async deleteSetting(req: Request, res: Response): Promise<void> { ... }
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { settingsController } from './settings.controller';
|
||||||
|
import { isAuthenticated } from '../auth/auth.middleware'; // 导入认证中间件
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// 应用认证中间件,确保只有登录用户才能访问设置相关 API
|
||||||
|
router.use(isAuthenticated);
|
||||||
|
|
||||||
|
// 定义路由
|
||||||
|
router.get('/', settingsController.getAllSettings); // GET /api/v1/settings
|
||||||
|
router.put('/', settingsController.updateSettings); // PUT /api/v1/settings
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -21,6 +21,7 @@ const handleLogout = () => {
|
|||||||
<RouterLink to="/connections">{{ t('nav.connections') }}</RouterLink> |
|
<RouterLink to="/connections">{{ t('nav.connections') }}</RouterLink> |
|
||||||
<RouterLink to="/proxies">{{ t('nav.proxies') }}</RouterLink> | <!-- 新增代理链接 -->
|
<RouterLink to="/proxies">{{ t('nav.proxies') }}</RouterLink> | <!-- 新增代理链接 -->
|
||||||
<RouterLink to="/tags">{{ t('nav.tags') }}</RouterLink> | <!-- 新增标签链接 -->
|
<RouterLink to="/tags">{{ t('nav.tags') }}</RouterLink> | <!-- 新增标签链接 -->
|
||||||
|
<RouterLink to="/settings">{{ t('nav.settings') }}</RouterLink> | <!-- 新增设置链接 -->
|
||||||
<RouterLink v-if="!isAuthenticated" to="/login">{{ t('nav.login') }}</RouterLink>
|
<RouterLink v-if="!isAuthenticated" to="/login">{{ t('nav.login') }}</RouterLink>
|
||||||
<a href="#" v-if="isAuthenticated" @click.prevent="handleLogout">{{ t('nav.logout') }}</a>
|
<a href="#" v-if="isAuthenticated" @click.prevent="handleLogout">{{ t('nav.logout') }}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"proxies": "Proxies",
|
"proxies": "Proxies",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"tags": "Tags"
|
"tags": "Tags",
|
||||||
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "User Login",
|
"title": "User Login",
|
||||||
@@ -276,5 +277,23 @@
|
|||||||
"status": {
|
"status": {
|
||||||
"never": "Never"
|
"never": "Never"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Global Settings",
|
||||||
|
"changePassword": {
|
||||||
|
"title": "Change Password",
|
||||||
|
"currentPassword": "Current Password:",
|
||||||
|
"newPassword": "New Password:",
|
||||||
|
"confirmPassword": "Confirm New Password:",
|
||||||
|
"submit": "Change Password",
|
||||||
|
"success": "Password changed successfully!",
|
||||||
|
"error": {
|
||||||
|
"passwordsDoNotMatch": "New password and confirmation do not match.",
|
||||||
|
"generic": "Failed to change password. Please try again later."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "Loading..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"proxies": "代理管理",
|
"proxies": "代理管理",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
"logout": "登出",
|
"logout": "登出",
|
||||||
"tags": "标签管理"
|
"tags": "标签管理",
|
||||||
|
"settings": "设置"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "用户登录",
|
"title": "用户登录",
|
||||||
@@ -279,5 +280,23 @@
|
|||||||
"status": {
|
"status": {
|
||||||
"never": "从未"
|
"never": "从未"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "全局设置",
|
||||||
|
"changePassword": {
|
||||||
|
"title": "修改密码",
|
||||||
|
"currentPassword": "当前密码:",
|
||||||
|
"newPassword": "新密码:",
|
||||||
|
"confirmPassword": "确认新密码:",
|
||||||
|
"submit": "确认修改",
|
||||||
|
"success": "密码修改成功!",
|
||||||
|
"error": {
|
||||||
|
"passwordsDoNotMatch": "新密码和确认密码不匹配。",
|
||||||
|
"generic": "修改密码失败,请稍后重试。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "加载中..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import('../views/WorkspaceView.vue'),
|
component: () => import('../views/WorkspaceView.vue'),
|
||||||
props: true // 将路由参数作为 props 传递给组件
|
props: true // 将路由参数作为 props 传递给组件
|
||||||
},
|
},
|
||||||
|
// 新增:设置页面
|
||||||
|
{
|
||||||
|
path: '/settings',
|
||||||
|
name: 'Settings',
|
||||||
|
component: () => import('../views/SettingsView.vue')
|
||||||
|
},
|
||||||
// 其他路由...
|
// 其他路由...
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,31 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 修改密码 Action
|
||||||
|
async changePassword(currentPassword: string, newPassword: string) {
|
||||||
|
if (!this.isAuthenticated) {
|
||||||
|
throw new Error('用户未登录,无法修改密码。');
|
||||||
|
}
|
||||||
|
this.isLoading = true;
|
||||||
|
this.error = null;
|
||||||
|
try {
|
||||||
|
const response = await axios.put<{ message: string }>('/api/v1/auth/password', {
|
||||||
|
currentPassword,
|
||||||
|
newPassword,
|
||||||
|
});
|
||||||
|
console.log('密码修改成功:', response.data.message);
|
||||||
|
// 密码修改成功后,通常不需要更新本地状态,但可以清除错误
|
||||||
|
return true;
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('修改密码失败:', err);
|
||||||
|
this.error = err.response?.data?.message || err.message || '修改密码时发生未知错误。';
|
||||||
|
// 抛出错误,以便组件可以捕获并显示 (提供默认消息以防 this.error 为 null)
|
||||||
|
throw new Error(this.error ?? '修改密码时发生未知错误。');
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: true, // 使用默认持久化配置 (localStorage, 持久化所有 state)
|
persist: true, // 使用默认持久化配置 (localStorage, 持久化所有 state)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div class="settings-view">
|
||||||
|
<h1>{{ $t('settings.title') }}</h1>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h2>{{ $t('settings.changePassword.title') }}</h2>
|
||||||
|
<form @submit.prevent="handleChangePassword">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="currentPassword">{{ $t('settings.changePassword.currentPassword') }}</label>
|
||||||
|
<input type="password" id="currentPassword" v-model="currentPassword" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPassword">{{ $t('settings.changePassword.newPassword') }}</label>
|
||||||
|
<input type="password" id="newPassword" v-model="newPassword" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirmPassword">{{ $t('settings.changePassword.confirmPassword') }}</label>
|
||||||
|
<input type="password" id="confirmPassword" v-model="confirmPassword" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" :disabled="loading">{{ loading ? $t('common.loading') : $t('settings.changePassword.submit') }}</button>
|
||||||
|
<p v-if="message" :class="{ 'success-message': isSuccess, 'error-message': !isSuccess }">{{ message }}</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他设置项可以在这里添加 -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAuthStore } from '../stores/auth.store';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const currentPassword = ref('');
|
||||||
|
const newPassword = ref('');
|
||||||
|
const confirmPassword = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
const message = ref('');
|
||||||
|
const isSuccess = ref(false);
|
||||||
|
|
||||||
|
const handleChangePassword = async () => {
|
||||||
|
message.value = ''; // 清除之前的消息
|
||||||
|
isSuccess.value = false;
|
||||||
|
|
||||||
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
|
message.value = t('settings.changePassword.error.passwordsDoNotMatch');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:添加前端密码复杂度校验
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await authStore.changePassword(currentPassword.value, newPassword.value);
|
||||||
|
message.value = t('settings.changePassword.success');
|
||||||
|
isSuccess.value = true;
|
||||||
|
// 清空表单
|
||||||
|
currentPassword.value = '';
|
||||||
|
newPassword.value = '';
|
||||||
|
confirmPassword.value = '';
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('修改密码失败:', error);
|
||||||
|
message.value = error.message || t('settings.changePassword.error.generic');
|
||||||
|
isSuccess.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.settings-view {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: green;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: red;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user