This commit is contained in:
Baobhan Sith
2025-04-28 12:13:26 +08:00
parent e82278e436
commit 869c5bb7a3
4 changed files with 92 additions and 57 deletions
@@ -44,7 +44,9 @@ export const settingsController = {
'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++
'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++
'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++
'timezone' // NEW: 添加时区键 'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight' // NEW: 添加 RDP 模态框高度键
]; ];
const filteredSettings: Record<string, string> = {}; const filteredSettings: Record<string, string> = {};
for (const key in settingsToUpdate) { for (const key in settingsToUpdate) {
@@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'; import { ref, onMounted, onUnmounted, watch, nextTick, computed, watchEffect } from 'vue'; // Import watchEffect
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSettingsStore } from '../stores/settings.store'; // Import settings store
// @ts-ignore - guacamole-common-js lacks official types // @ts-ignore - guacamole-common-js lacks official types
import Guacamole from 'guacamole-common-js'; import Guacamole from 'guacamole-common-js';
import apiClient from '../utils/apiClient'; import apiClient from '../utils/apiClient';
import { ConnectionInfo } from '../stores/connections.store'; import { ConnectionInfo } from '../stores/connections.store';
const { t } = useI18n(); const { t } = useI18n();
const settingsStore = useSettingsStore(); // Instantiate settings store
const props = defineProps<{ const props = defineProps<{
connection: ConnectionInfo | null; connection: ConnectionInfo | null;
@@ -14,6 +16,10 @@ const props = defineProps<{
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
let saveWidthTimeout: ReturnType<typeof setTimeout> | null = null;
let saveHeightTimeout: ReturnType<typeof setTimeout> | null = null;
const DEBOUNCE_DELAY = 500; // ms
const rdpDisplayRef = ref<HTMLDivElement | null>(null); const rdpDisplayRef = ref<HTMLDivElement | null>(null);
const rdpContainerRef = ref<HTMLDivElement | null>(null); // Added ref for the container const rdpContainerRef = ref<HTMLDivElement | null>(null); // Added ref for the container
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -34,8 +40,7 @@ const MIN_MODAL_HEIGHT = 768;
const RDP_BACKEND_API_BASE = 'http://localhost:9090'; const RDP_BACKEND_API_BASE = 'http://localhost:9090';
const RDP_BACKEND_WEBSOCKET_URL = 'ws://localhost:8081'; const RDP_BACKEND_WEBSOCKET_URL = 'ws://localhost:8081';
const LOCAL_STORAGE_MODAL_WIDTH_KEY = 'rdpModalWidth'; // Reverted key name // Removed localStorage keys
const LOCAL_STORAGE_MODAL_HEIGHT_KEY = 'rdpModalHeight'; // Reverted key name
const connectRdp = async () => { // Removed useInputValues parameter const connectRdp = async () => { // Removed useInputValues parameter
if (!props.connection || !rdpDisplayRef.value) { if (!props.connection || !rdpDisplayRef.value) {
@@ -238,49 +243,54 @@ const closeModal = () => {
// Removed setupResizeObserver as ResizeObserver is no longer used // Removed setupResizeObserver as ResizeObserver is no longer used
// Load desired MODAL size from localStorage on mount // Removed loadDesiredModalSize function
const loadDesiredModalSize = () => {
const savedWidth = localStorage.getItem(LOCAL_STORAGE_MODAL_WIDTH_KEY);
const savedHeight = localStorage.getItem(LOCAL_STORAGE_MODAL_HEIGHT_KEY);
if (savedWidth) {
// Validate loaded width against minimum
desiredModalWidth.value = Math.max(MIN_MODAL_WIDTH, parseInt(savedWidth, 10) || MIN_MODAL_WIDTH);
} else {
// Ensure default is also valid if nothing is saved
desiredModalWidth.value = Math.max(MIN_MODAL_WIDTH, desiredModalWidth.value);
}
if (savedHeight) {
// Validate loaded height against minimum
desiredModalHeight.value = Math.max(MIN_MODAL_HEIGHT, parseInt(savedHeight, 10) || MIN_MODAL_HEIGHT);
} else {
// Ensure default is also valid if nothing is saved
desiredModalHeight.value = Math.max(MIN_MODAL_HEIGHT, desiredModalHeight.value);
}
};
// Save desired MODAL size to localStorage when changed // Watch local refs and save validated size to settings store
watch(desiredModalWidth, (newWidth) => { watch(desiredModalWidth, (newWidth) => {
// Validate new width before saving and potentially update the ref // Validate new width before saving
const validatedWidth = Math.max(MIN_MODAL_WIDTH, Number(newWidth) || MIN_MODAL_WIDTH); const validatedWidth = Math.max(MIN_MODAL_WIDTH, Number(newWidth) || MIN_MODAL_WIDTH);
if (validatedWidth !== newWidth) { // Debounce saving the *validated* width
// If validation changed the value, update the v-model binding if (saveWidthTimeout) clearTimeout(saveWidthTimeout);
desiredModalWidth.value = validatedWidth; saveWidthTimeout = setTimeout(() => {
} // Only save the validated width, don't change the input value here
localStorage.setItem(LOCAL_STORAGE_MODAL_WIDTH_KEY, String(validatedWidth)); console.log(`[RDP Modal] Debounced Save - Saving width: ${validatedWidth} (Input value: ${newWidth})`);
}); settingsStore.updateSetting('rdpModalWidth', String(validatedWidth));
watch(desiredModalHeight, (newHeight) => { }, DEBOUNCE_DELAY);
// Validate new height before saving and potentially update the ref
const validatedHeight = Math.max(MIN_MODAL_HEIGHT, Number(newHeight) || MIN_MODAL_HEIGHT);
if (validatedHeight !== newHeight) {
// If validation changed the value, update the v-model binding
desiredModalHeight.value = validatedHeight;
}
localStorage.setItem(LOCAL_STORAGE_MODAL_HEIGHT_KEY, String(validatedHeight));
}); });
watch(desiredModalHeight, (newHeight) => {
// Validate new height before saving
const validatedHeight = Math.max(MIN_MODAL_HEIGHT, Number(newHeight) || MIN_MODAL_HEIGHT);
// Debounce saving the *validated* height
if (saveHeightTimeout) clearTimeout(saveHeightTimeout);
saveHeightTimeout = setTimeout(() => {
// Only save the validated height, don't change the input value here
console.log(`[RDP Modal] Debounced Save - Saving height: ${validatedHeight} (Input value: ${newHeight})`);
settingsStore.updateSetting('rdpModalHeight', String(validatedHeight));
}, DEBOUNCE_DELAY);
});
// Load initial size from settings store when component mounts or settings change
watchEffect(() => {
const storeWidth = settingsStore.settings.rdpModalWidth;
const storeHeight = settingsStore.settings.rdpModalHeight;
console.log(`[RDP Modal] Loading size from store - Width: ${storeWidth}, Height: ${storeHeight}`); // +++ Add log +++
// Use defaults from store if available, otherwise use component defaults
const initialWidth = storeWidth ? parseInt(storeWidth, 10) : desiredModalWidth.value; // Use current ref value as fallback default
const initialHeight = storeHeight ? parseInt(storeHeight, 10) : desiredModalHeight.value; // Use current ref value as fallback default
// Validate against minimums
const finalWidth = Math.max(MIN_MODAL_WIDTH, isNaN(initialWidth) ? MIN_MODAL_WIDTH : initialWidth);
const finalHeight = Math.max(MIN_MODAL_HEIGHT, isNaN(initialHeight) ? MIN_MODAL_HEIGHT : initialHeight);
console.log(`[RDP Modal] Applying validated size - Width: ${finalWidth}, Height: ${finalHeight}`); // +++ Add log +++
desiredModalWidth.value = finalWidth;
desiredModalHeight.value = finalHeight;
});
onMounted(() => { onMounted(() => {
loadDesiredModalSize(); // Load saved size first // Initial size loading is now handled by watchEffect
if (props.connection) { if (props.connection) {
nextTick(async () => { nextTick(async () => {
@@ -317,9 +327,12 @@ const computedModalStyle = computed(() => {
// const footerHeight = 35; // Defined in connectRdp // const footerHeight = 35; // Defined in connectRdp
// const extraHeight = headerHeight + footerHeight + 10; // Defined in connectRdp // const extraHeight = headerHeight + footerHeight + 10; // Defined in connectRdp
// Apply minimum constraints here for the actual modal style
const actualWidth = Math.max(MIN_MODAL_WIDTH, desiredModalWidth.value);
const actualHeight = Math.max(MIN_MODAL_HEIGHT, desiredModalHeight.value);
return { return {
width: `${desiredModalWidth.value}px`, // Width is direct width: `${actualWidth}px`,
height: `${desiredModalHeight.value}px`, // Height is direct height: `${actualHeight}px`,
}; };
}); });
@@ -380,8 +393,7 @@ const computedModalStyle = computed(() => {
<input <input
id="modal-width" id="modal-width"
type="number" type="number"
v-model="desiredModalWidth" v-model.number="desiredModalWidth"
min="1024"
step="10" step="10"
class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary" class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
/> />
@@ -389,8 +401,7 @@ const computedModalStyle = computed(() => {
<input <input
id="modal-height" id="modal-height"
type="number" type="number"
v-model="desiredModalHeight" v-model.number="desiredModalHeight"
min="768"
step="10" step="10"
class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary" class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
/> />
@@ -41,7 +41,6 @@ export const useConnectionsStore = defineStore('connections', {
try { try {
const cachedData = localStorage.getItem(cacheKey); const cachedData = localStorage.getItem(cacheKey);
if (cachedData) { if (cachedData) {
console.log('[ConnectionsStore] Loading connections from cache.');
this.connections = JSON.parse(cachedData); this.connections = JSON.parse(cachedData);
this.isLoading = false; // 先显示缓存,设置为 false this.isLoading = false; // 先显示缓存,设置为 false
} else { } else {
@@ -57,21 +56,16 @@ export const useConnectionsStore = defineStore('connections', {
// 2. 后台获取最新数据 // 2. 后台获取最新数据
this.isLoading = true; // 标记正在后台获取 this.isLoading = true; // 标记正在后台获取
try { try {
console.log('[ConnectionsStore] Fetching latest connections from server...');
const response = await apiClient.get<ConnectionInfo[]>('/connections'); const response = await apiClient.get<ConnectionInfo[]>('/connections');
const freshData = response.data; const freshData = response.data;
console.log('[ConnectionsStore] Data received from API:', JSON.stringify(freshData, null, 2)); // Log received data
const freshDataString = JSON.stringify(freshData); const freshDataString = JSON.stringify(freshData);
// 3. 对比并更新 // 3. 对比并更新
const currentDataString = JSON.stringify(this.connections); const currentDataString = JSON.stringify(this.connections);
if (currentDataString !== freshDataString) { if (currentDataString !== freshDataString) {
console.log('[ConnectionsStore] Connections data changed, updating state and cache.');
this.connections = freshData; this.connections = freshData;
console.log('[ConnectionsStore] State updated with fresh data:', JSON.stringify(this.connections, null, 2)); // Log state after update
localStorage.setItem(cacheKey, freshDataString); // 更新缓存 localStorage.setItem(cacheKey, freshDataString); // 更新缓存
} else { } else {
console.log('[ConnectionsStore] Connections data is up-to-date.');
} }
this.error = null; // 清除之前的错误(如果有) this.error = null; // 清除之前的错误(如果有)
} catch (err: any) { } catch (err: any) {
+33 -5
View File
@@ -45,6 +45,8 @@ interface SettingsState {
fileManagerColWidths?: string; // NEW: 文件管理器列宽 JSON 字符串 (e.g., '{"name": 300, "size": 100}') fileManagerColWidths?: string; // NEW: 文件管理器列宽 JSON 字符串 (e.g., '{"name": 300, "size": 100}')
commandInputSyncTarget?: 'quickCommands' | 'commandHistory' | 'none'; // NEW: 命令输入同步目标 commandInputSyncTarget?: 'quickCommands' | 'commandHistory' | 'none'; // NEW: 命令输入同步目标
timezone?: string; // NEW: 时区设置 (e.g., 'Asia/Shanghai', 'UTC') timezone?: string; // NEW: 时区设置 (e.g., 'Asia/Shanghai', 'UTC')
rdpModalWidth?: string; // NEW: RDP 模态框宽度
rdpModalHeight?: string; // NEW: RDP 模态框高度
// Add other general settings keys here as needed // Add other general settings keys here as needed
[key: string]: string | undefined; // Allow other string settings [key: string]: string | undefined; // Allow other string settings
} }
@@ -211,6 +213,13 @@ export const useSettingsStore = defineStore('settings', () => {
if (settings.value.timezone === undefined) { if (settings.value.timezone === undefined) {
settings.value.timezone = 'UTC'; // 默认 UTC settings.value.timezone = 'UTC'; // 默认 UTC
} }
// NEW: RDP Modal Size defaults
if (settings.value.rdpModalWidth === undefined) {
settings.value.rdpModalWidth = '1064'; // 默认宽度 (1024 + 40 padding)
}
if (settings.value.rdpModalHeight === undefined) {
settings.value.rdpModalHeight = '858'; // 默认高度 (768 + chrome)
}
// --- 语言设置 --- // --- 语言设置 ---
const langFromSettings = settings.value.language; const langFromSettings = settings.value.language;
@@ -288,7 +297,9 @@ export const useSettingsStore = defineStore('settings', () => {
'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++
'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++
'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++
'timezone' // NEW: 添加时区键 'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight' // NEW: 添加 RDP 模态框高度键
]; ];
if (!allowedKeys.includes(key)) { if (!allowedKeys.includes(key)) {
console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`); console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`);
@@ -296,8 +307,12 @@ export const useSettingsStore = defineStore('settings', () => {
} }
try { try {
console.log(`[SettingsStore] Attempting to update setting - Key: ${key}, Value: ${value}`); // +++ Add log +++
// 注意:后端 controller 现在会过滤,但前端也做一层检查更好 // 注意:后端 controller 现在会过滤,但前端也做一层检查更好
await apiClient.put('/settings', { [key]: value }); // 使用 apiClient const payload = { [key]: value };
console.log('[SettingsStore] Sending PUT request to /settings with payload:', payload); // +++ Add log +++
await apiClient.put('/settings', payload); // 使用 apiClient
console.log(`[SettingsStore] Successfully updated setting via API - Key: ${key}`); // +++ Add log +++
// Update store state *after* successful API call // Update store state *after* successful API call
settings.value = { ...settings.value, [key]: value }; settings.value = { ...settings.value, [key]: value };
@@ -309,8 +324,19 @@ export const useSettingsStore = defineStore('settings', () => {
console.warn(`[SettingsStore] updateSetting: Attempted to set invalid language '${value}'. Ignoring i18n update.`); console.warn(`[SettingsStore] updateSetting: Attempted to set invalid language '${value}'. Ignoring i18n update.`);
} }
} catch (err: any) { } catch (err: any) {
console.error(`更新设置项 '${key}' 失败:`, err); // +++ Enhanced error logging +++
throw new Error(err.response?.data?.message || err.message || `更新设置项 '${key}' 失败`); console.error(`[SettingsStore] Failed to update setting '${key}' via API. Error:`, err);
if (err.response) {
console.error('[SettingsStore] API Error Response Data:', err.response.data);
console.error('[SettingsStore] API Error Response Status:', err.response.status);
console.error('[SettingsStore] API Error Response Headers:', err.response.headers);
} else if (err.request) {
console.error('[SettingsStore] API Error Request:', err.request);
} else {
console.error('[SettingsStore] API Error Message:', err.message);
}
// Rethrow the error but maybe provide a more specific message if possible
throw new Error(err.response?.data?.message || `更新设置项 '${key}' 失败: ${err.message}`);
} }
} }
@@ -330,7 +356,9 @@ export const useSettingsStore = defineStore('settings', () => {
'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++
'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++
'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++
'timezone' // NEW: 添加时区键 'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight' // NEW: 添加 RDP 模态框高度键
]; ];
const filteredUpdates: Partial<SettingsState> = {}; const filteredUpdates: Partial<SettingsState> = {};
let languageUpdate: string | undefined = undefined; // Use string type let languageUpdate: string | undefined = undefined; // Use string type