This commit is contained in:
Baobhan Sith
2025-04-20 10:26:43 +08:00
parent 430c3af1f6
commit 1160f8a514
16 changed files with 171 additions and 74 deletions
+4
View File
@@ -691,6 +691,7 @@
"configureFocusSwitch": "Configure Focus Switcher"
},
"layout": {
"loading": "Loading...",
"configure": "Configure Layout",
"pane": {
"connections": "Connections",
@@ -703,6 +704,9 @@
"quickCommands": "Quick Commands"
}
},
"header": {
"hide": "Hide"
},
"commandHistory": {
"title": "Command History",
"searchPlaceholder": "Search history...",
+4
View File
@@ -691,6 +691,7 @@
"configureFocusSwitch": "配置焦点切换器"
},
"layout": {
"loading": "加载中...",
"configure": "配置布局",
"pane": {
"connections": "连接列表",
@@ -703,6 +704,9 @@
"quickCommands": "快捷指令"
}
},
"header": {
"hide": "隐藏"
},
"commandHistory": {
"title": "命令历史",
"searchPlaceholder": "搜索历史记录...",
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { ref, computed, watch, nextTick } from 'vue'; // 导入 nextTick
import type { ITheme } from 'xterm';
import type { TerminalTheme } from '../../../backend/src/types/terminal-theme.types'; // 引用后端类型
@@ -89,8 +89,8 @@ export const useAppearanceStore = defineStore('appearance', () => {
try {
// 并行加载外观设置和主题列表
const [settingsResponse, themesResponse] = await Promise.all([
axios.get<AppearanceSettings>('/api/v1/appearance'),
axios.get<TerminalTheme[]>('/api/v1/terminal-themes') // 获取所有主题
apiClient.get<AppearanceSettings>('/appearance'), // 使用 apiClient
apiClient.get<TerminalTheme[]>('/terminal-themes') // 使用 apiClient
]);
appearanceSettings.value = settingsResponse.data;
allTerminalThemes.value = themesResponse.data; // 更新 allTerminalThemes
@@ -137,7 +137,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
async function updateAppearanceSettings(updates: UpdateAppearanceDto) {
try {
// 移除预设主题闪烁修复逻辑,不再需要
const response = await axios.put<AppearanceSettings>('/api/v1/appearance', updates);
const response = await apiClient.put<AppearanceSettings>('/appearance', updates); // 使用 apiClient
// 使用后端返回的最新设置更新本地状态
appearanceSettings.value = response.data;
console.log('[AppearanceStore] 外观设置已更新:', appearanceSettings.value);
@@ -234,7 +234,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
*/
async function createTerminalTheme(name: string, themeData: ITheme) {
try {
await axios.post('/api/v1/terminal-themes', { name, themeData });
await apiClient.post('/terminal-themes', { name, themeData }); // 使用 apiClient
await loadInitialAppearanceData(); // 重新加载所有数据以更新列表
} catch (err: any) {
console.error('创建终端主题失败:', err);
@@ -250,7 +250,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
*/
async function updateTerminalTheme(id: string, name: string, themeData: ITheme) {
try {
await axios.put(`/api/v1/terminal-themes/${id}`, { name, themeData });
await apiClient.put(`/terminal-themes/${id}`, { name, themeData }); // 使用 apiClient
await loadInitialAppearanceData(); // 重新加载所有数据以更新列表
} catch (err: any) {
console.error('更新终端主题失败:', err);
@@ -264,7 +264,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
*/
async function deleteTerminalTheme(id: string) {
try {
await axios.delete(`/api/v1/terminal-themes/${id}`);
await apiClient.delete(`/terminal-themes/${id}`); // 使用 apiClient
// 如果删除的是当前激活的主题,则切换回默认主题 ID
// 需要将字符串 id 转换为数字进行比较
const idNum = parseInt(id, 10);
@@ -294,7 +294,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
formData.append('name', name);
}
try {
await axios.post('/api/v1/terminal-themes/import', formData, {
await apiClient.post('/terminal-themes/import', formData, { // 使用 apiClient
headers: { 'Content-Type': 'multipart/form-data' }
});
await loadInitialAppearanceData(); // 重新加载所有数据以更新列表
@@ -310,7 +310,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
*/
async function exportTerminalTheme(id: string) {
try {
const response = await axios.get(`/api/v1/terminal-themes/${id}/export`, {
const response = await apiClient.get(`/terminal-themes/${id}/export`, { // 使用 apiClient
responseType: 'blob' // 重要:接收二进制数据
});
// 从响应头获取文件名
@@ -346,7 +346,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
const formData = new FormData();
formData.append('pageBackgroundFile', file);
try {
const response = await axios.post<{ filePath: string }>('/api/v1/appearance/background/page', formData, {
const response = await apiClient.post<{ filePath: string }>('/appearance/background/page', formData, { // 使用 apiClient
headers: { 'Content-Type': 'multipart/form-data' }
});
// 更新本地状态 (虽然 updateAppearanceSettings 也会做,但这里立即反映)
@@ -366,7 +366,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
const formData = new FormData();
formData.append('terminalBackgroundFile', file);
try {
const response = await axios.post<{ filePath: string }>('/api/v1/appearance/background/terminal', formData, {
const response = await apiClient.post<{ filePath: string }>('/appearance/background/terminal', formData, { // 使用 apiClient
headers: { 'Content-Type': 'multipart/form-data' }
});
appearanceSettings.value.terminalBackgroundImage = response.data.filePath;
+2 -2
View File
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { AuditLogEntry, AuditLogApiResponse, AuditLogActionType } from '../types/server.types';
export const useAuditLogStore = defineStore('auditLog', () => {
@@ -26,7 +26,7 @@ export const useAuditLogStore = defineStore('auditLog', () => {
// Remove undefined filter values
Object.keys(params).forEach(key => params[key] === undefined && delete params[key]);
const response = await axios.get<AuditLogApiResponse>('/api/v1/audit-logs', { params });
const response = await apiClient.get<AuditLogApiResponse>('/audit-logs', { params }); // 使用 apiClient
logs.value = response.data.logs;
totalLogs.value = response.data.total;
} catch (err: any) {
+9 -9
View File
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import router from '../router'; // 引入 router 用于重定向
import { setLocale } from '../i18n'; // 导入 setLocale
@@ -57,7 +57,7 @@ export const useAuthStore = defineStore('auth', {
try {
// 后端可能返回 user 或 requiresTwoFactor
// 将完整的 payload (包含 rememberMe) 发送给后端
const response = await axios.post<{ message: string; user?: UserInfo; requiresTwoFactor?: boolean }>('/api/v1/auth/login', payload);
const response = await apiClient.post<{ message: string; user?: UserInfo; requiresTwoFactor?: boolean }>('/auth/login', payload); // 使用 apiClient
if (response.data.requiresTwoFactor) {
// 需要 2FA 验证
@@ -100,7 +100,7 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.post<{ message: string; user: UserInfo }>('/api/v1/auth/login/2fa', { token });
const response = await apiClient.post<{ message: string; user: UserInfo }>('/auth/login/2fa', { token }); // 使用 apiClient
// 2FA 验证成功
this.isAuthenticated = true;
this.user = response.data.user;
@@ -130,7 +130,7 @@ export const useAuthStore = defineStore('auth', {
this.loginRequires2FA = false; // 重置 2FA 状态
try {
// 调用后端的登出 API
await axios.post('/api/v1/auth/logout');
await apiClient.post('/auth/logout'); // 使用 apiClient
// 清除本地状态
this.isAuthenticated = false;
@@ -178,7 +178,7 @@ export const useAuthStore = defineStore('auth', {
async checkAuthStatus() {
this.isLoading = true;
try {
const response = await axios.get<{ isAuthenticated: boolean; user: UserInfo }>('/api/v1/auth/status');
const response = await apiClient.get<{ isAuthenticated: boolean; user: UserInfo }>('/auth/status'); // 使用 apiClient
if (response.data.isAuthenticated && response.data.user) {
this.isAuthenticated = true;
this.user = response.data.user; // 更新用户信息,包含 isTwoFactorEnabled 和 language
@@ -213,7 +213,7 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.put<{ message: string }>('/api/v1/auth/password', {
const response = await apiClient.put<{ message: string }>('/auth/password', { // 使用 apiClient
currentPassword,
newPassword,
});
@@ -240,7 +240,7 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.get('/api/v1/settings/ip-blacklist', {
const response = await apiClient.get('/settings/ip-blacklist', { // 使用 apiClient
params: { limit, offset }
});
// 注意:这里需要将获取到的数据存储在 state 中,
@@ -266,7 +266,7 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = true;
this.error = null;
try {
await axios.delete(`/api/v1/settings/ip-blacklist/${encodeURIComponent(ip)}`);
await apiClient.delete(`/settings/ip-blacklist/${encodeURIComponent(ip)}`); // 使用 apiClient
console.log(`IP ${ip} 已从黑名单删除`);
// 成功后需要重新获取列表或从本地 state 中移除
return true;
@@ -284,7 +284,7 @@ export const useAuthStore = defineStore('auth', {
async checkSetupStatus() {
// 不需要设置 isLoading,这个检查应该在后台快速完成
try {
const response = await axios.get<{ needsSetup: boolean }>('/api/v1/auth/needs-setup');
const response = await apiClient.get<{ needsSetup: boolean }>('/auth/needs-setup'); // 使用 apiClient
this.needsSetup = response.data.needsSetup;
console.log(`[AuthStore] Needs setup status: ${this.needsSetup}`);
return this.needsSetup; // 返回状态给调用者
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios'; // 假设项目使用 axios
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { ref, computed } from 'vue';
import { useUiNotificationsStore } from './uiNotifications.store'; // 用于显示通知
@@ -42,7 +42,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get<CommandHistoryEntryBE[]>('/api/v1/command-history');
const response = await apiClient.get<CommandHistoryEntryBE[]>('/command-history'); // 使用 apiClient
// 后端返回的是按时间戳升序 (旧->新)
// 前端需要按时间戳降序 (新->旧),所以反转数组
historyList.value = response.data.reverse();
@@ -62,7 +62,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => {
return; // 不添加空命令
}
try {
const response = await axios.post<{ id: number }>('/api/v1/command-history', { command: command.trim() });
const response = await apiClient.post<{ id: number }>('/command-history', { command: command.trim() }); // 使用 apiClient
// 添加成功后,重新获取列表以保证顺序和 ID 正确
// 或者,可以在本地模拟添加,但为了简单和一致性,重新获取更好
await fetchHistory();
@@ -77,7 +77,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => {
// 删除单条历史记录
const deleteCommand = async (id: number) => {
try {
await axios.delete(`/api/v1/command-history/${id}`);
await apiClient.delete(`/command-history/${id}`); // 使用 apiClient
// 从本地列表中移除
const index = historyList.value.findIndex(entry => entry.id === id);
if (index !== -1) {
@@ -95,7 +95,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => {
const clearAllHistory = async () => {
// 可以在调用前添加确认逻辑 (例如在组件层)
try {
await axios.delete('/api/v1/command-history');
await apiClient.delete('/command-history'); // 使用 apiClient
historyList.value = []; // 清空本地列表
uiNotificationsStore.showSuccess('所有历史记录已清空');
} catch (err: any) {
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios'; // 引入 axios
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
// 定义连接信息接口 (与后端对应,不含敏感信息)
export interface ConnectionInfo {
@@ -37,7 +37,7 @@ export const useConnectionsStore = defineStore('connections', {
this.error = null;
try {
// 注意:axios 默认会携带 cookie,因此如果用户已登录,会话 cookie 会被发送
const response = await axios.get<ConnectionInfo[]>('/api/v1/connections');
const response = await apiClient.get<ConnectionInfo[]>('/connections'); // 使用 apiClient
this.connections = response.data;
} catch (err: any) {
console.error('获取连接列表失败:', err);
@@ -69,7 +69,7 @@ export const useConnectionsStore = defineStore('connections', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.post<{ message: string; connection: ConnectionInfo }>('/api/v1/connections', newConnectionData);
const response = await apiClient.post<{ message: string; connection: ConnectionInfo }>('/connections', newConnectionData); // 使用 apiClient
// 添加成功后,将新连接添加到列表前面 (或重新获取整个列表)
this.connections.unshift(response.data.connection);
return true; // 表示成功
@@ -93,7 +93,7 @@ export const useConnectionsStore = defineStore('connections', {
try {
// 发送 PUT 请求到 /api/v1/connections/:id
// 注意:后端 API 需要支持接收这些字段并进行更新
const response = await axios.put<{ message: string; connection: ConnectionInfo }>(`/api/v1/connections/${connectionId}`, updatedData);
const response = await apiClient.put<{ message: string; connection: ConnectionInfo }>(`/connections/${connectionId}`, updatedData); // 使用 apiClient
// 更新成功后,在列表中找到并更新对应的连接信息
const index = this.connections.findIndex(conn => conn.id === connectionId);
@@ -124,7 +124,7 @@ export const useConnectionsStore = defineStore('connections', {
this.error = null;
try {
// 发送 DELETE 请求到 /api/v1/connections/:id
await axios.delete(`/api/v1/connections/${connectionId}`);
await apiClient.delete(`/connections/${connectionId}`); // 使用 apiClient
// 删除成功后,从本地列表中移除该连接
this.connections = this.connections.filter(conn => conn.id !== connectionId);
@@ -148,7 +148,7 @@ export const useConnectionsStore = defineStore('connections', {
// this.isLoading = true;
// this.error = null;
try {
const response = await axios.post<{ success: boolean; message: string }>(`/api/v1/connections/${connectionId}/test`);
const response = await apiClient.post<{ success: boolean; message: string }>(`/connections/${connectionId}/test`); // 使用 apiClient
return { success: response.data.success, message: response.data.message };
} catch (err: any) {
console.error(`测试连接 ${connectionId} 失败:`, err);
+5 -5
View File
@@ -1,7 +1,7 @@
import { defineStore } from 'pinia';
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue';
// 导入 axios 用于 API 调用
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
// 定义所有可用面板的名称
export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands';
@@ -116,7 +116,7 @@ export const useLayoutStore = defineStore('layout', () => {
// 1. 尝试从后端加载
try {
console.log('[Layout Store] Attempting to load layout from backend...');
const response = await axios.get<LayoutNode | null>('/api/v1/settings/layout');
const response = await apiClient.get<LayoutNode | null>('/settings/layout'); // 使用 apiClient
if (response.data) {
// TODO: 在这里添加对 response.data 的结构验证,确保它符合 LayoutNode 接口
layoutTree.value = response.data;
@@ -217,7 +217,7 @@ export const useLayoutStore = defineStore('layout', () => {
console.log('[Layout Store] Attempting to load header visibility from backend...');
try {
// --- 调用后端 API (复用 nav-bar-visibility 接口) ---
const response = await axios.get<{ visible: boolean }>('/api/v1/settings/nav-bar-visibility');
const response = await apiClient.get<{ visible: boolean }>('/settings/nav-bar-visibility'); // 使用 apiClient
if (response && typeof response.data.visible === 'boolean') {
isHeaderVisible.value = response.data.visible;
console.log(`[Layout Store] Header visibility loaded from backend: ${isHeaderVisible.value}`);
@@ -240,7 +240,7 @@ export const useLayoutStore = defineStore('layout', () => {
try {
// --- 调用后端 API (复用 nav-bar-visibility 接口) ---
await axios.put('/api/v1/settings/nav-bar-visibility', { visible: newValue });
await apiClient.put('/settings/nav-bar-visibility', { visible: newValue }); // 使用 apiClient
console.log('[Layout Store] Header visibility saved to backend.');
} catch (error) {
console.error('[Layout Store] Failed to save header visibility to backend:', error);
@@ -265,7 +265,7 @@ export const useLayoutStore = defineStore('layout', () => {
// 1. 保存到后端
try {
console.log('[Layout Store] Attempting to save layout to backend...');
await axios.put('/api/v1/settings/layout', layoutTree.value); // 发送对象,后端会 stringify
await apiClient.put('/settings/layout', layoutTree.value); // 使用 apiClient
console.log('[Layout Store] 布局已成功保存到后端。');
} catch (error) {
console.error('[Layout Store] 保存布局到后端失败:', error);
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import axios from 'axios'; // Assuming axios is globally available or installed
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { NotificationSetting, NotificationSettingData, NotificationChannelType } from '../types/server.types'; // Import NotificationChannelType
export const useNotificationsStore = defineStore('notifications', () => {
@@ -12,7 +12,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get<NotificationSetting[]>('/api/v1/notifications');
const response = await apiClient.get<NotificationSetting[]>('/notifications'); // 使用 apiClient
settings.value = response.data;
} catch (err: any) {
console.error('Error fetching notification settings:', err);
@@ -27,7 +27,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.post<NotificationSetting>('/api/v1/notifications', settingData);
const response = await apiClient.post<NotificationSetting>('/notifications', settingData); // 使用 apiClient
settings.value.push(response.data);
return response.data;
} catch (err: any) {
@@ -43,7 +43,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.put<NotificationSetting>(`/api/v1/notifications/${id}`, settingData);
const response = await apiClient.put<NotificationSetting>(`/notifications/${id}`, settingData); // 使用 apiClient
const index = settings.value.findIndex(s => s.id === id);
if (index !== -1) {
settings.value[index] = response.data;
@@ -65,7 +65,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
isLoading.value = true;
error.value = null;
try {
await axios.delete(`/api/v1/notifications/${id}`);
await apiClient.delete(`/notifications/${id}`); // 使用 apiClient
settings.value = settings.value.filter(s => s.id !== id);
return true;
} catch (err: any) {
@@ -83,7 +83,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
error.value = null; // Clear previous general errors
try {
// Send the config to test in the request body
const response = await axios.post<{ message: string }>(`/api/v1/notifications/${id}/test`, { config });
const response = await apiClient.post<{ message: string }>(`/notifications/${id}/test`, { config }); // 使用 apiClient
return { success: true, message: response.data.message || '测试成功' };
} catch (err: any) {
console.error(`Error testing notification setting ${id}:`, err);
@@ -99,7 +99,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
error.value = null;
try {
// Send the channel type and config in the request body
const response = await axios.post<{ message: string }>(`/api/v1/notifications/test-unsaved`, { channel_type: channelType, config });
const response = await apiClient.post<{ message: string }>(`/notifications/test-unsaved`, { channel_type: channelType, config }); // 使用 apiClient
return { success: true, message: response.data.message || '测试成功' };
} catch (err: any) {
console.error(`Error testing unsaved notification setting:`, err);
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
// 定义代理信息接口 (前端使用,不含密码)
export interface ProxyInfo {
@@ -33,7 +33,7 @@ export const useProxiesStore = defineStore('proxies', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.get<ProxyInfo[]>('/api/v1/proxies');
const response = await apiClient.get<ProxyInfo[]>('/proxies'); // 使用 apiClient
this.proxies = response.data;
} catch (err: any) {
console.error('获取代理列表失败:', err);
@@ -59,7 +59,7 @@ export const useProxiesStore = defineStore('proxies', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.post<{ message: string; proxy: ProxyInfo }>('/api/v1/proxies', newProxyData);
const response = await apiClient.post<{ message: string; proxy: ProxyInfo }>('/proxies', newProxyData); // 使用 apiClient
this.proxies.unshift(response.data.proxy); // 将新代理添加到列表开头
return true; // 成功
} catch (err: any) {
@@ -82,7 +82,7 @@ export const useProxiesStore = defineStore('proxies', {
this.isLoading = true;
this.error = null;
try {
const response = await axios.put<{ message: string; proxy: ProxyInfo }>(`/api/v1/proxies/${proxyId}`, updatedData);
const response = await apiClient.put<{ message: string; proxy: ProxyInfo }>(`/proxies/${proxyId}`, updatedData); // 使用 apiClient
const index = this.proxies.findIndex(p => p.id === proxyId);
if (index !== -1) {
// 使用返回的更新后的信息替换旧信息
@@ -111,7 +111,7 @@ export const useProxiesStore = defineStore('proxies', {
this.isLoading = true;
this.error = null;
try {
await axios.delete(`/api/v1/proxies/${proxyId}`);
await apiClient.delete(`/proxies/${proxyId}`); // 使用 apiClient
this.proxies = this.proxies.filter(p => p.id !== proxyId); // 从列表中移除
return true; // 成功
} catch (err: any) {
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { ref, computed } from 'vue';
import { useUiNotificationsStore } from './uiNotifications.store';
import type { QuickCommand } from '../../../backend/src/repositories/quick-commands.repository'; // 复用后端类型定义
@@ -58,7 +58,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get<QuickCommandFE[]>('/api/v1/quick-commands', {
const response = await apiClient.get<QuickCommandFE[]>('/quick-commands', { // 使用 apiClient
params: { sortBy: sortBy.value } // 将排序参数传递给后端
});
quickCommandsList.value = response.data;
@@ -74,7 +74,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => {
// 添加快捷指令
const addQuickCommand = async (name: string | null, command: string): Promise<boolean> => {
try {
await axios.post('/api/v1/quick-commands', { name, command });
await apiClient.post('/quick-commands', { name, command }); // 使用 apiClient
await fetchQuickCommands(); // 添加成功后刷新列表
uiNotificationsStore.showSuccess('快捷指令已添加');
return true;
@@ -89,7 +89,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => {
// 更新快捷指令
const updateQuickCommand = async (id: number, name: string | null, command: string): Promise<boolean> => {
try {
await axios.put(`/api/v1/quick-commands/${id}`, { name, command });
await apiClient.put(`/quick-commands/${id}`, { name, command }); // 使用 apiClient
await fetchQuickCommands(); // 更新成功后刷新列表
uiNotificationsStore.showSuccess('快捷指令已更新');
return true;
@@ -104,7 +104,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => {
// 删除快捷指令
const deleteQuickCommand = async (id: number) => {
try {
await axios.delete(`/api/v1/quick-commands/${id}`);
await apiClient.delete(`/quick-commands/${id}`); // 使用 apiClient
// 从本地列表中移除,避免重新请求
const index = quickCommandsList.value.findIndex(cmd => cmd.id === id);
if (index !== -1) {
@@ -121,7 +121,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => {
// 增加使用次数 (调用 API,然后更新本地数据)
const incrementUsage = async (id: number) => {
try {
await axios.post(`/api/v1/quick-commands/${id}/increment-usage`);
await apiClient.post(`/quick-commands/${id}/increment-usage`); // 使用 apiClient
// 更新本地计数,避免重新请求整个列表
const command = quickCommandsList.value.find(cmd => cmd.id === id);
if (command) {
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
import { ref, computed } from 'vue'; // 移除 watch
import i18n, { setLocale, defaultLng } from '../i18n'; // Import i18n instance and setLocale
// 移除 ITheme 和默认主题定义,这些移到 appearance.store.ts
@@ -39,7 +39,7 @@ export const useSettingsStore = defineStore('settings', () => {
try {
console.log('[SettingsStore] 加载通用设置...');
const response = await axios.get<Record<string, string>>('/api/v1/settings');
const response = await apiClient.get<Record<string, string>>('/settings'); // 使用 apiClient
settings.value = response.data; // Store fetched general settings
console.log('[SettingsStore] 通用设置已加载:', settings.value);
@@ -118,7 +118,7 @@ export const useSettingsStore = defineStore('settings', () => {
try {
// 注意:后端 controller 现在会过滤,但前端也做一层检查更好
await axios.put('/api/v1/settings', { [key]: value });
await apiClient.put('/settings', { [key]: value }); // 使用 apiClient
// Update store state *after* successful API call
settings.value = { ...settings.value, [key]: value };
@@ -164,7 +164,7 @@ export const useSettingsStore = defineStore('settings', () => {
try {
// 注意:后端 controller 现在会过滤,但前端也做一层检查更好
await axios.put('/api/v1/settings', filteredUpdates);
await apiClient.put('/settings', filteredUpdates); // 使用 apiClient
// Update store state *after* successful API call
settings.value = { ...settings.value, ...filteredUpdates };
+5 -5
View File
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios'; // 假设使用 axios 发送请求
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
// 定义标签信息接口
export interface TagInfo {
@@ -20,7 +20,7 @@ export const useTagsStore = defineStore('tags', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get<TagInfo[]>('/api/v1/tags');
const response = await apiClient.get<TagInfo[]>('/tags'); // 使用 apiClient 并移除 base URL
tags.value = response.data;
return true;
} catch (err: any) {
@@ -37,7 +37,7 @@ export const useTagsStore = defineStore('tags', () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.post<{ message: string, tag: TagInfo }>('/api/v1/tags', { name });
const response = await apiClient.post<{ message: string, tag: TagInfo }>('/tags', { name }); // 使用 apiClient 并移除 base URL
// 添加成功后,重新获取列表以保证数据同步 (或者直接将新标签添加到 ref)
await fetchTags(); // 简单起见,重新获取
// tags.value.push(response.data.tag); // 另一种方式
@@ -57,7 +57,7 @@ export const useTagsStore = defineStore('tags', () => {
isLoading.value = true;
error.value = null;
try {
await axios.put(`/api/v1/tags/${id}`, { name });
await apiClient.put(`/tags/${id}`, { name }); // 使用 apiClient 并移除 base URL
// 更新成功后,重新获取列表
await fetchTags();
return true;
@@ -75,7 +75,7 @@ export const useTagsStore = defineStore('tags', () => {
isLoading.value = true;
error.value = null;
try {
await axios.delete(`/api/v1/tags/${id}`);
await apiClient.delete(`/tags/${id}`); // 使用 apiClient 并移除 base URL
// 删除成功后,重新获取列表
await fetchTags();
return true;
+88
View File
@@ -0,0 +1,88 @@
import axios from 'axios';
import router from '../router'; // 引入 router 用于可能的重定向
import { useAuthStore } from '../stores/auth.store'; // 引入 auth store 用于检查认证状态和登出
// 创建 axios 实例
const apiClient = axios.create({
baseURL: '/api/v1', // 设置基础URL
timeout: 10000, // 设置请求超时时间
withCredentials: true, // 允许携带 cookie
});
// 请求拦截器 (可选,例如添加认证 Token)
apiClient.interceptors.request.use(
(config) => {
// 可以在这里添加逻辑,比如从 store 获取 token 并添加到请求头
// const authStore = useAuthStore();
// if (authStore.token) {
// config.headers.Authorization = `Bearer ${authStore.token}`;
// }
return config;
},
(error) => {
// 处理请求错误
console.error('Request error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
apiClient.interceptors.response.use(
(response) => {
// 对响应数据做点什么
return response;
},
(error) => {
// 处理响应错误
console.error('Response error:', error.response || error.message);
if (error.response) {
const { status } = error.response;
const authStore = useAuthStore(); // 在需要时获取 store 实例
// 处理常见的 HTTP 错误状态码
switch (status) {
case 401: // 未授权
// 如果用户当前是认证状态,则可能是 session 过期或无效
if (authStore.isAuthenticated) {
console.warn('Unauthorized access detected. Logging out.');
// 调用 store 中的 logout 方法,它会处理状态重置和路由跳转
authStore.logout();
// 可以选择抛出错误或返回一个特定的值,防止后续代码执行
return Promise.reject(new Error('Unauthorized, logging out.'));
} else {
// 如果用户本来就未认证,可能只是访问了需要登录的接口,暂时不强制跳转
console.log('Unauthorized access to protected route.');
}
break;
case 403: // 禁止访问
// 可以显示一个权限不足的提示
console.error('Forbidden access.');
// alert('您没有权限执行此操作。'); // 或者使用更友好的通知组件
break;
case 404: // 未找到
console.error('Resource not found.');
break;
case 500: // 服务器内部错误
console.error('Internal server error.');
// alert('服务器发生错误,请稍后重试。');
break;
// 可以根据需要添加更多错误状态码的处理
default:
console.error(`Unhandled error status: ${status}`);
}
} else if (error.request) {
// 请求已发出,但没有收到响应 (例如网络问题)
console.error('Network error or no response received:', error.request);
// alert('网络错误,请检查您的连接。');
} else {
// 发送请求时出了点问题
console.error('Error setting up request:', error.message);
}
// 将错误继续抛出,以便调用方可以捕获并处理
return Promise.reject(error);
}
);
export default apiClient;
+8 -7
View File
@@ -222,7 +222,8 @@ import { useAppearanceStore } from '../stores/appearance.store'; // 导入外观
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
// setLocale is handled by the store now
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使 apiClient
import { isAxiosError } from 'axios'; // isAxiosError
import { startRegistration } from '@simplewebauthn/browser';
const authStore = useAuthStore();
@@ -361,17 +362,17 @@ const handleRegisterPasskey = async () => {
return;
}
try {
const optionsResponse = await axios.post('/api/v1/auth/passkey/register-options');
const optionsResponse = await apiClient.post('/auth/passkey/register-options'); // 使 apiClient
const options = optionsResponse.data;
let registrationResponse = await startRegistration(options);
await axios.post('/api/v1/auth/passkey/verify-registration', { registrationResponse, name: passkeyName.value });
await apiClient.post('/auth/passkey/verify-registration', { registrationResponse, name: passkeyName.value }); // 使 apiClient
passkeyMessage.value = t('settings.passkey.success.registered');
passkeyName.value = '';
} catch (error: any) {
console.error('Passkey 注册流程出错:', error);
if (error.name === 'NotAllowedError') {
passkeyError.value = t('settings.passkey.error.cancelled');
} else if (axios.isAxiosError(error) && error.response) {
} else if (isAxiosError(error) && error.response) { // 使 isAxiosError
passkeyError.value = t('settings.passkey.error.verificationFailed', { message: error.response.data.message || 'Server error' });
} else {
passkeyError.value = t('settings.passkey.error.genericRegistration', { message: error.message || t('settings.passkey.error.unknown') });
@@ -427,7 +428,7 @@ const handleSetup2FA = async () => {
twoFactorMessage.value = ''; twoFactorSuccess.value = false; twoFactorLoading.value = true;
setupData.value = null; verificationCode.value = '';
try {
const response = await axios.post<{ secret: string; qrCodeUrl: string }>('/api/v1/auth/2fa/setup');
const response = await apiClient.post<{ secret: string; qrCodeUrl: string }>('/auth/2fa/setup'); // 使 apiClient
setupData.value = response.data;
} catch (error: any) {
console.error('开始设置 2FA 失败:', error);
@@ -440,7 +441,7 @@ const handleVerifyAndActivate2FA = async () => {
}
twoFactorMessage.value = ''; twoFactorSuccess.value = false; twoFactorLoading.value = true;
try {
await axios.post('/api/v1/auth/2fa/verify', { token: verificationCode.value });
await apiClient.post('/auth/2fa/verify', { token: verificationCode.value }); // 使 apiClient
twoFactorMessage.value = t('settings.twoFactor.success.activated');
twoFactorSuccess.value = true; twoFactorEnabled.value = true;
setupData.value = null; verificationCode.value = '';
@@ -455,7 +456,7 @@ const handleDisable2FA = async () => {
}
twoFactorMessage.value = ''; twoFactorSuccess.value = false; twoFactorLoading.value = true;
try {
await axios.delete('/api/v1/auth/2fa', { data: { password: disablePassword.value } });
await apiClient.delete('/auth/2fa', { data: { password: disablePassword.value } }); // 使 apiClient
twoFactorMessage.value = t('settings.twoFactor.success.disabled');
twoFactorSuccess.value = true; twoFactorEnabled.value = false;
disablePassword.value = '';
+2 -2
View File
@@ -63,7 +63,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import axios from 'axios';
import apiClient from '../utils/apiClient'; // 使 apiClient
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '../stores/auth.store'; // *** Auth Store ***
@@ -97,7 +97,7 @@ const handleSetup = async () => {
try {
// API
await axios.post('/api/v1/auth/setup', {
await apiClient.post('/auth/setup', { // 使 apiClient base URL
username: username.value,
password: password.value,
confirmPassword: confirmPassword.value