update
This commit is contained in:
+2
-1
@@ -41,7 +41,7 @@ build/Release
|
|||||||
# Dependency directories
|
# Dependency directories
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
jspm_packages/
|
||||||
|
scripts/
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
web_modules/
|
web_modules/
|
||||||
|
|
||||||
@@ -137,3 +137,4 @@ dist
|
|||||||
*.db
|
*.db
|
||||||
*.jpg
|
*.jpg
|
||||||
/packages/backend/uploads/backgrounds
|
/packages/backend/uploads/backgrounds
|
||||||
|
/temp_iterm_schemes
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { settingsRepository, Setting } from '../repositories/settings.repository
|
|||||||
const DEFAULT_FOCUS_SEQUENCE = ["quickCommandsSearch", "commandHistorySearch", "fileManagerSearch", "commandInput", "terminalSearch"];
|
const DEFAULT_FOCUS_SEQUENCE = ["quickCommandsSearch", "commandHistorySearch", "fileManagerSearch", "commandInput", "terminalSearch"];
|
||||||
const FOCUS_SEQUENCE_KEY = 'focusSwitcherSequence'; // 焦点切换顺序设置键
|
const FOCUS_SEQUENCE_KEY = 'focusSwitcherSequence'; // 焦点切换顺序设置键
|
||||||
const NAV_BAR_VISIBLE_KEY = 'navBarVisible'; // 导航栏可见性设置键
|
const NAV_BAR_VISIBLE_KEY = 'navBarVisible'; // 导航栏可见性设置键
|
||||||
|
const LAYOUT_TREE_KEY = 'layoutTree'; // 布局树设置键
|
||||||
|
|
||||||
export const settingsService = {
|
export const settingsService = {
|
||||||
/**
|
/**
|
||||||
@@ -163,5 +164,45 @@ export const settingsService = {
|
|||||||
console.error(`[Service] Error calling settingsRepository.setSetting for key ${NAV_BAR_VISIBLE_KEY}:`, error);
|
console.error(`[Service] Error calling settingsRepository.setSetting for key ${NAV_BAR_VISIBLE_KEY}:`, error);
|
||||||
throw new Error('Failed to save nav bar visibility setting.');
|
throw new Error('Failed to save nav bar visibility setting.');
|
||||||
}
|
}
|
||||||
} // *** 最后的方法后面不需要逗号 ***
|
}, // *** 确保这里有逗号 ***
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取布局树设置
|
||||||
|
* @returns 返回存储的布局树 JSON 字符串,如果未设置则返回 null
|
||||||
|
*/
|
||||||
|
async getLayoutTree(): Promise<string | null> {
|
||||||
|
console.log(`[Service] Attempting to get setting for key: ${LAYOUT_TREE_KEY}`);
|
||||||
|
try {
|
||||||
|
const layoutJson = await settingsRepository.getSetting(LAYOUT_TREE_KEY);
|
||||||
|
console.log(`[Service] Raw value from repository for ${LAYOUT_TREE_KEY}:`, layoutJson ? layoutJson.substring(0, 100) + '...' : null); // 只打印部分内容
|
||||||
|
return layoutJson; // 直接返回 JSON 字符串或 null
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Service] Error getting layout tree setting (key: ${LAYOUT_TREE_KEY}):`, error);
|
||||||
|
return null; // 出错时返回 null
|
||||||
|
}
|
||||||
|
}, // *** 确保这里有逗号 ***
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置布局树
|
||||||
|
* @param layoutJson 布局树的 JSON 字符串
|
||||||
|
*/
|
||||||
|
async setLayoutTree(layoutJson: string): Promise<void> {
|
||||||
|
console.log(`[Service] setLayoutTree called with JSON (first 100 chars): ${layoutJson.substring(0, 100)}...`);
|
||||||
|
// 可选:在这里添加 JSON 格式验证
|
||||||
|
try {
|
||||||
|
JSON.parse(layoutJson); // 尝试解析以验证格式
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Service] Invalid JSON format provided for layout tree:', e);
|
||||||
|
throw new Error('Invalid layout tree JSON format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`[Service] Attempting to save setting. Key: ${LAYOUT_TREE_KEY}`);
|
||||||
|
await settingsRepository.setSetting(LAYOUT_TREE_KEY, layoutJson);
|
||||||
|
console.log(`[Service] Successfully saved setting for key: ${LAYOUT_TREE_KEY}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Service] Error calling settingsRepository.setSetting for key ${LAYOUT_TREE_KEY}:`, error);
|
||||||
|
throw new Error('Failed to save layout tree setting.');
|
||||||
|
}
|
||||||
|
} // *** 最后的方法后面不需要逗号 ***
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { ipBlacklistService } from '../services/ip-blacklist.service'; // 引入
|
|||||||
const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
||||||
|
|
||||||
export const settingsController = {
|
export const settingsController = {
|
||||||
// ... (getAllSettings, updateSettings, getFocusSwitcherSequence 保持不变) ...
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有设置项
|
* 获取所有设置项
|
||||||
*/
|
*/
|
||||||
@@ -26,18 +24,15 @@ export const settingsController = {
|
|||||||
*/
|
*/
|
||||||
async updateSettings(req: Request, res: Response): Promise<void> {
|
async updateSettings(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// TODO: 添加输入验证,确保 req.body 是 Record<string, string>
|
|
||||||
const settingsToUpdate: Record<string, string> = req.body;
|
const settingsToUpdate: Record<string, string> = req.body;
|
||||||
if (typeof settingsToUpdate !== 'object' || settingsToUpdate === null) {
|
if (typeof settingsToUpdate !== 'object' || settingsToUpdate === null) {
|
||||||
res.status(400).json({ message: '无效的请求体,应为 JSON 对象' });
|
res.status(400).json({ message: '无效的请求体,应为 JSON 对象' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 过滤掉外观设置和焦点切换顺序相关的键 ---
|
|
||||||
const allowedSettingsKeys = [
|
const allowedSettingsKeys = [
|
||||||
'language', 'ipWhitelist', 'maxLoginAttempts', 'loginBanDuration',
|
'language', 'ipWhitelist', 'maxLoginAttempts', 'loginBanDuration',
|
||||||
'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled'
|
'showPopupFileEditor', 'shareFileEditorTabs', 'ipWhitelistEnabled'
|
||||||
// 不在此处处理 'focusSwitcherSequence'
|
|
||||||
];
|
];
|
||||||
const filteredSettings: Record<string, string> = {};
|
const filteredSettings: Record<string, string> = {};
|
||||||
for (const key in settingsToUpdate) {
|
for (const key in settingsToUpdate) {
|
||||||
@@ -45,14 +40,11 @@ export const settingsController = {
|
|||||||
filteredSettings[key] = settingsToUpdate[key];
|
filteredSettings[key] = settingsToUpdate[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- 结束过滤 ---
|
|
||||||
|
|
||||||
// 只传递过滤后的设置给 service
|
|
||||||
if (Object.keys(filteredSettings).length > 0) {
|
if (Object.keys(filteredSettings).length > 0) {
|
||||||
await settingsService.setMultipleSettings(filteredSettings);
|
await settingsService.setMultipleSettings(filteredSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录审计日志
|
|
||||||
const updatedKeys = Object.keys(filteredSettings);
|
const updatedKeys = Object.keys(filteredSettings);
|
||||||
if (updatedKeys.length > 0) {
|
if (updatedKeys.length > 0) {
|
||||||
if (updatedKeys.includes('ipWhitelist') || updatedKeys.includes('ipWhitelistEnabled')) {
|
if (updatedKeys.includes('ipWhitelist') || updatedKeys.includes('ipWhitelistEnabled')) {
|
||||||
@@ -68,52 +60,47 @@ export const settingsController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// +++ 新增:获取焦点切换顺序 +++
|
|
||||||
/**
|
/**
|
||||||
* 获取焦点切换顺序
|
* 获取焦点切换顺序
|
||||||
*/
|
*/
|
||||||
async getFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
async getFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('[Controller] Received request to get focus switcher sequence.'); // +++ 添加日志 +++
|
console.log('[Controller] Received request to get focus switcher sequence.');
|
||||||
const sequence = await settingsService.getFocusSwitcherSequence();
|
const sequence = await settingsService.getFocusSwitcherSequence();
|
||||||
console.log('[Controller] Sending focus switcher sequence to client:', JSON.stringify(sequence)); // +++ 添加日志 +++
|
console.log('[Controller] Sending focus switcher sequence to client:', JSON.stringify(sequence));
|
||||||
res.json(sequence);
|
res.json(sequence);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Controller] 获取焦点切换顺序时出错:', error); // +++ 更新日志前缀 +++
|
console.error('[Controller] 获取焦点切换顺序时出错:', error);
|
||||||
res.status(500).json({ message: '获取焦点切换顺序失败', error: error.message });
|
res.status(500).json({ message: '获取焦点切换顺序失败', error: error.message });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// +++ 新增:设置焦点切换顺序 +++
|
|
||||||
/**
|
/**
|
||||||
* 设置焦点切换顺序
|
* 设置焦点切换顺序
|
||||||
*/
|
*/
|
||||||
async setFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
async setFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
||||||
console.log('[Controller] Received request to set focus switcher sequence.'); // +++ 添加日志 +++
|
console.log('[Controller] Received request to set focus switcher sequence.');
|
||||||
try {
|
try {
|
||||||
const { sequence } = req.body;
|
const { sequence } = req.body;
|
||||||
console.log('[Controller] Request body sequence:', JSON.stringify(sequence)); // +++ 添加日志 +++
|
console.log('[Controller] Request body sequence:', JSON.stringify(sequence));
|
||||||
|
|
||||||
// 输入验证
|
|
||||||
if (!Array.isArray(sequence) || !sequence.every(item => typeof item === 'string')) {
|
if (!Array.isArray(sequence) || !sequence.every(item => typeof item === 'string')) {
|
||||||
console.warn('[Controller] Invalid sequence format received:', sequence); // +++ 添加日志 +++
|
console.warn('[Controller] Invalid sequence format received:', sequence);
|
||||||
res.status(400).json({ message: '无效的请求体,"sequence" 必须是一个字符串数组' });
|
res.status(400).json({ message: '无效的请求体,"sequence" 必须是一个字符串数组' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Controller] Calling settingsService.setFocusSwitcherSequence...'); // +++ 添加日志 +++
|
console.log('[Controller] Calling settingsService.setFocusSwitcherSequence...');
|
||||||
await settingsService.setFocusSwitcherSequence(sequence);
|
await settingsService.setFocusSwitcherSequence(sequence);
|
||||||
console.log('[Controller] settingsService.setFocusSwitcherSequence completed successfully.'); // +++ 添加日志 +++
|
console.log('[Controller] settingsService.setFocusSwitcherSequence completed successfully.');
|
||||||
|
|
||||||
// 记录审计日志 (可选)
|
console.log('[Controller] Logging audit action: FOCUS_SWITCHER_SEQUENCE_UPDATED');
|
||||||
console.log('[Controller] Logging audit action: FOCUS_SWITCHER_SEQUENCE_UPDATED'); // +++ 添加日志 +++
|
|
||||||
auditLogService.logAction('FOCUS_SWITCHER_SEQUENCE_UPDATED', { sequence });
|
auditLogService.logAction('FOCUS_SWITCHER_SEQUENCE_UPDATED', { sequence });
|
||||||
|
|
||||||
console.log('[Controller] Sending success response.'); // +++ 添加日志 +++
|
console.log('[Controller] Sending success response.');
|
||||||
res.status(200).json({ message: '焦点切换顺序已成功更新' });
|
res.status(200).json({ message: '焦点切换顺序已成功更新' });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Controller] 设置焦点切换顺序时出错:', error); // +++ 更新日志前缀 +++
|
console.error('[Controller] 设置焦点切换顺序时出错:', error);
|
||||||
// 区分是服务层抛出的验证错误还是其他错误
|
|
||||||
if (error.message === 'Invalid sequence format provided.') {
|
if (error.message === 'Invalid sequence format provided.') {
|
||||||
res.status(400).json({ message: '设置焦点切换顺序失败: 无效的格式', error: error.message });
|
res.status(400).json({ message: '设置焦点切换顺序失败: 无效的格式', error: error.message });
|
||||||
} else {
|
} else {
|
||||||
@@ -122,7 +109,6 @@ export const settingsController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// +++ 新增:获取导航栏可见性 +++
|
|
||||||
/**
|
/**
|
||||||
* 获取导航栏可见性设置
|
* 获取导航栏可见性设置
|
||||||
*/
|
*/
|
||||||
@@ -131,14 +117,13 @@ export const settingsController = {
|
|||||||
console.log('[Controller] Received request to get nav bar visibility.');
|
console.log('[Controller] Received request to get nav bar visibility.');
|
||||||
const isVisible = await settingsService.getNavBarVisibility();
|
const isVisible = await settingsService.getNavBarVisibility();
|
||||||
console.log(`[Controller] Sending nav bar visibility to client: ${isVisible}`);
|
console.log(`[Controller] Sending nav bar visibility to client: ${isVisible}`);
|
||||||
res.json({ visible: isVisible }); // 返回包含 visible 键的对象
|
res.json({ visible: isVisible });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Controller] 获取导航栏可见性时出错:', error);
|
console.error('[Controller] 获取导航栏可见性时出错:', error);
|
||||||
res.status(500).json({ message: '获取导航栏可见性失败', error: error.message });
|
res.status(500).json({ message: '获取导航栏可见性失败', error: error.message });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// +++ 新增:设置导航栏可见性 +++
|
|
||||||
/**
|
/**
|
||||||
* 设置导航栏可见性
|
* 设置导航栏可见性
|
||||||
*/
|
*/
|
||||||
@@ -148,7 +133,6 @@ export const settingsController = {
|
|||||||
const { visible } = req.body;
|
const { visible } = req.body;
|
||||||
console.log('[Controller] Request body visible:', visible);
|
console.log('[Controller] Request body visible:', visible);
|
||||||
|
|
||||||
// 输入验证
|
|
||||||
if (typeof visible !== 'boolean') {
|
if (typeof visible !== 'boolean') {
|
||||||
console.warn('[Controller] Invalid visible format received:', visible);
|
console.warn('[Controller] Invalid visible format received:', visible);
|
||||||
res.status(400).json({ message: '无效的请求体,"visible" 必须是一个布尔值' });
|
res.status(400).json({ message: '无效的请求体,"visible" 必须是一个布尔值' });
|
||||||
@@ -159,8 +143,6 @@ export const settingsController = {
|
|||||||
await settingsService.setNavBarVisibility(visible);
|
await settingsService.setNavBarVisibility(visible);
|
||||||
console.log('[Controller] settingsService.setNavBarVisibility completed successfully.');
|
console.log('[Controller] settingsService.setNavBarVisibility completed successfully.');
|
||||||
|
|
||||||
// 记录审计日志 (可选)
|
|
||||||
// console.log('[Controller] Logging audit action: NAV_BAR_VISIBILITY_UPDATED');
|
|
||||||
// auditLogService.logAction('NAV_BAR_VISIBILITY_UPDATED', { visible });
|
// auditLogService.logAction('NAV_BAR_VISIBILITY_UPDATED', { visible });
|
||||||
|
|
||||||
console.log('[Controller] Sending success response.');
|
console.log('[Controller] Sending success response.');
|
||||||
@@ -171,6 +153,66 @@ export const settingsController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取布局树设置
|
||||||
|
*/
|
||||||
|
async getLayoutTree(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('[Controller] Received request to get layout tree.');
|
||||||
|
const layoutJson = await settingsService.getLayoutTree();
|
||||||
|
if (layoutJson) {
|
||||||
|
try {
|
||||||
|
const layout = JSON.parse(layoutJson);
|
||||||
|
console.log('[Controller] Sending layout tree to client.');
|
||||||
|
res.json(layout);
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('[Controller] Failed to parse layout tree JSON from DB:', parseError);
|
||||||
|
res.status(500).json({ message: '获取布局树失败:存储的数据格式无效' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[Controller] No layout tree found in settings, sending null.');
|
||||||
|
res.json(null);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[Controller] 获取布局树时出错:', error);
|
||||||
|
res.status(500).json({ message: '获取布局树失败', error: error.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置布局树
|
||||||
|
*/
|
||||||
|
async setLayoutTree(req: Request, res: Response): Promise<void> {
|
||||||
|
console.log('[Controller] Received request to set layout tree.');
|
||||||
|
try {
|
||||||
|
const layoutTree = req.body;
|
||||||
|
|
||||||
|
if (typeof layoutTree !== 'object' || layoutTree === null) {
|
||||||
|
console.warn('[Controller] Invalid layout tree format received (not an object):', layoutTree);
|
||||||
|
res.status(400).json({ message: '无效的请求体,应为 JSON 对象格式的布局树' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutJson = JSON.stringify(layoutTree);
|
||||||
|
|
||||||
|
console.log('[Controller] Calling settingsService.setLayoutTree...');
|
||||||
|
await settingsService.setLayoutTree(layoutJson);
|
||||||
|
console.log('[Controller] settingsService.setLayoutTree completed successfully.');
|
||||||
|
|
||||||
|
// auditLogService.logAction('LAYOUT_TREE_UPDATED');
|
||||||
|
|
||||||
|
console.log('[Controller] Sending success response.');
|
||||||
|
res.status(200).json({ message: '布局树已成功更新' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[Controller] 设置布局树时出错:', error);
|
||||||
|
if (error.message === 'Invalid layout tree JSON format.') {
|
||||||
|
res.status(400).json({ message: '设置布局树失败: 无效的 JSON 格式', error: error.message });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: '设置布局树失败', error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 IP 黑名单列表 (分页)
|
* 获取 IP 黑名单列表 (分页)
|
||||||
*/
|
*/
|
||||||
@@ -196,9 +238,7 @@ export const settingsController = {
|
|||||||
res.status(400).json({ message: '缺少要删除的 IP 地址' });
|
res.status(400).json({ message: '缺少要删除的 IP 地址' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: 可以添加对 IP 格式的验证
|
|
||||||
await ipBlacklistService.removeFromBlacklist(ipToDelete);
|
await ipBlacklistService.removeFromBlacklist(ipToDelete);
|
||||||
// 记录审计日志 (可选)
|
|
||||||
// auditLogService.logAction('IP_BLACKLIST_REMOVED', { ip: ipToDelete });
|
// auditLogService.logAction('IP_BLACKLIST_REMOVED', { ip: ipToDelete });
|
||||||
res.status(200).json({ message: `IP 地址 ${ipToDelete} 已从黑名单中移除` });
|
res.status(200).json({ message: `IP 地址 ${ipToDelete} 已从黑名单中移除` });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ router.get('/nav-bar-visibility', settingsController.getNavBarVisibility);
|
|||||||
// PUT /api/v1/settings/nav-bar-visibility - 更新导航栏可见性
|
// PUT /api/v1/settings/nav-bar-visibility - 更新导航栏可见性
|
||||||
router.put('/nav-bar-visibility', settingsController.setNavBarVisibility);
|
router.put('/nav-bar-visibility', settingsController.setNavBarVisibility);
|
||||||
|
|
||||||
|
// +++ 新增:布局树路由 +++
|
||||||
|
// GET /api/v1/settings/layout - 获取布局树
|
||||||
|
router.get('/layout', settingsController.getLayoutTree);
|
||||||
|
// PUT /api/v1/settings/layout - 更新布局树
|
||||||
|
router.put('/layout', settingsController.setLayoutTree);
|
||||||
|
|
||||||
// --- IP 黑名单管理路由 ---
|
// --- IP 黑名单管理路由 ---
|
||||||
// GET /api/v1/settings/ip-blacklist - 获取 IP 黑名单列表 (需要认证)
|
// GET /api/v1/settings/ip-blacklist - 获取 IP 黑名单列表 (需要认证)
|
||||||
router.get('/ip-blacklist', settingsController.getIpBlacklist);
|
router.get('/ip-blacklist', settingsController.getIpBlacklist);
|
||||||
|
|||||||
@@ -110,22 +110,51 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Actions ---
|
// --- Actions ---
|
||||||
// 初始化布局:尝试从 localStorage 加载,否则使用默认布局
|
// 初始化布局:优先尝试从后端加载,然后 localStorage,最后默认布局
|
||||||
function initializeLayout() {
|
async function initializeLayout() {
|
||||||
|
let loadedFromBackend = false;
|
||||||
|
// 1. 尝试从后端加载
|
||||||
try {
|
try {
|
||||||
const savedLayout = localStorage.getItem(LAYOUT_STORAGE_KEY);
|
console.log('[Layout Store] Attempting to load layout from backend...');
|
||||||
if (savedLayout) {
|
const response = await axios.get<LayoutNode | null>('/api/v1/settings/layout');
|
||||||
const parsedLayout = JSON.parse(savedLayout) as LayoutNode;
|
if (response.data) {
|
||||||
// 可选:添加验证逻辑确保加载的布局结构有效
|
// TODO: 在这里添加对 response.data 的结构验证,确保它符合 LayoutNode 接口
|
||||||
layoutTree.value = parsedLayout;
|
layoutTree.value = response.data;
|
||||||
console.log('[Layout Store] 从 localStorage 加载布局成功。');
|
loadedFromBackend = true;
|
||||||
|
console.log('[Layout Store] 从后端加载布局成功。');
|
||||||
|
// 可选:如果后端加载成功,可以更新 localStorage
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(response.data));
|
||||||
|
} catch (lsError) {
|
||||||
|
console.error('[Layout Store] 保存后端布局到 localStorage 失败:', lsError);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
layoutTree.value = getDefaultLayout();
|
console.log('[Layout Store] 后端未返回布局数据。');
|
||||||
console.log('[Layout Store] 未找到保存的布局,使用默认布局。');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Layout Store] 加载或解析布局失败:', error);
|
console.error('[Layout Store] 从后端加载布局失败:', error);
|
||||||
layoutTree.value = getDefaultLayout(); // 出错时回退到默认布局
|
// 加载失败,继续尝试 localStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果后端未加载成功,尝试从 localStorage 加载
|
||||||
|
if (!loadedFromBackend) {
|
||||||
|
console.log('[Layout Store] Attempting to load layout from localStorage...');
|
||||||
|
try {
|
||||||
|
const savedLayout = localStorage.getItem(LAYOUT_STORAGE_KEY);
|
||||||
|
if (savedLayout) {
|
||||||
|
const parsedLayout = JSON.parse(savedLayout) as LayoutNode;
|
||||||
|
// TODO: 添加验证逻辑确保加载的布局结构有效
|
||||||
|
layoutTree.value = parsedLayout;
|
||||||
|
console.log('[Layout Store] 从 localStorage 加载布局成功。');
|
||||||
|
} else {
|
||||||
|
// 3. 如果 localStorage 也没有,使用默认布局
|
||||||
|
layoutTree.value = getDefaultLayout();
|
||||||
|
console.log('[Layout Store] 未找到保存的布局,使用默认布局。');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Layout Store] 从 localStorage 加载或解析布局失败:', error);
|
||||||
|
layoutTree.value = getDefaultLayout(); // 出错时回退到默认布局
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,25 +249,59 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
// alert('Failed to save preference.'); // 或者通知用户
|
// alert('Failed to save preference.'); // 或者通知用户
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增 Action: 将当前布局树持久化到后端和 localStorage
|
||||||
|
async function persistLayoutTree() {
|
||||||
|
if (!layoutTree.value) {
|
||||||
|
console.warn('[Layout Store] persistLayoutTree: layoutTree is null, cannot persist.');
|
||||||
|
// 可选:如果布局为空,是否也通知后端?或者删除后端的设置?
|
||||||
|
// await axios.delete('/api/v1/settings/layout'); // 示例:删除后端设置
|
||||||
|
localStorage.removeItem(LAYOUT_STORAGE_KEY); // 保持移除本地存储
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutToSave = JSON.stringify(layoutTree.value);
|
||||||
|
|
||||||
|
// 1. 保存到后端
|
||||||
|
try {
|
||||||
|
console.log('[Layout Store] Attempting to save layout to backend...');
|
||||||
|
await axios.put('/api/v1/settings/layout', layoutTree.value); // 发送对象,后端会 stringify
|
||||||
|
console.log('[Layout Store] 布局已成功保存到后端。');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Layout Store] 保存布局到后端失败:', error);
|
||||||
|
// 可以考虑添加用户提示
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 保存到 localStorage (作为备份或离线支持)
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LAYOUT_STORAGE_KEY, layoutToSave);
|
||||||
|
console.log('[Layout Store] 布局已自动保存到 localStorage。');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Layout Store] 保存布局到 localStorage 失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 持久化 ---
|
// --- 持久化 ---
|
||||||
// 监听 layoutTree 的变化,并自动保存到 localStorage
|
// 监听 layoutTree 的变化,并调用持久化方法
|
||||||
|
// 添加防抖以避免过于频繁的 API 调用
|
||||||
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
watch(
|
watch(
|
||||||
layoutTree,
|
layoutTree,
|
||||||
(newTree) => {
|
(newTree, oldTree) => {
|
||||||
if (newTree) {
|
// 避免初始化时触发 (虽然 initializeLayout 已经是 async,但以防万一)
|
||||||
try {
|
if (oldTree === undefined) return;
|
||||||
localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(newTree));
|
// 只有在实际发生变化时才触发持久化
|
||||||
console.log('[Layout Store] 布局已自动保存到 localStorage。');
|
if (JSON.stringify(newTree) !== JSON.stringify(oldTree)) {
|
||||||
} catch (error) {
|
console.log('[Layout Store] Layout tree changed, scheduling persistence...');
|
||||||
console.error('[Layout Store] 保存布局到 localStorage 失败:', error);
|
if (debounceTimer) {
|
||||||
|
clearTimeout(debounceTimer);
|
||||||
}
|
}
|
||||||
} else {
|
debounceTimer = setTimeout(() => {
|
||||||
// 如果布局被清空,也移除本地存储
|
persistLayoutTree();
|
||||||
localStorage.removeItem(LAYOUT_STORAGE_KEY);
|
}, 1000); // 1秒防抖
|
||||||
console.log('[Layout Store] 布局为空,已从 localStorage 移除。');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true } // 需要深度监听来捕获嵌套结构的变化
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- 初始化 ---
|
// --- 初始化 ---
|
||||||
|
|||||||
Reference in New Issue
Block a user