update
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user