diff --git a/package.json b/package.json index 67215ee..f24d11d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build:cdn": "cross-env VITE_SARASA_TERM_SC_USE_CDN=1 VITE_USE_CDN=1 vite build", "build:nazhua": "cross-env VITE_BASE_PATH=/nazhua/ VITE_NEZHA_VERSION=v0 VITE_SARASA_TERM_SC_USE_CDN=1 VITE_USE_CDN=1 vite build", "preview": "vite preview", - "lint": "eslint ." + "lint": "eslint .", + "lint:fix": "eslint . --fix" }, "dependencies": { "axios": "^1.13.2", diff --git a/public/config.js b/public/config.js index 8673b61..ff4e2d6 100644 --- a/public/config.js +++ b/public/config.js @@ -11,7 +11,7 @@ window.$$nazhuaConfig = { // showLantern: true, // 是否显示灯笼 enableInnerSearch: true, // 启用内部搜索 // listServerItemTypeToggle: true, // 服务器列表项类型切换 - // listServerItemType: 'server-status', // 服务器列表项类型 card/row/server-status row列表模式移动端自动切换至card + listServerItemType: 'card', // 服务器列表项类型 card/row/server-status row列表模式移动端自动切换至card // serverStatusColumnsTpl: null, // 服务器状态列配置模板 // listServerStatusType: 'progress', // 服务器状态类型--列表 // listServerRealTimeShowLoad: true, // 列表显示服务器实时负载 diff --git a/src/App.vue b/src/App.vue index 98c0518..f7ae683 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,6 +15,7 @@ import { watch, provide, onMounted, + onUnmounted, } from 'vue'; import { useStore } from 'vuex'; import { useRoute } from 'vue-router'; @@ -24,6 +25,7 @@ import config, { import sleep from '@/utils/sleep'; import LayoutMain from './layout/main.vue'; +import { WS_CONNECTION_STATUS } from './ws/service'; import activeWebsocketService, { wsService, restart, @@ -39,9 +41,16 @@ provide('currentTime', currentTime); /** * 刷新当前时间 + * 使用 requestAnimationFrame 持续更新时间,但只在秒级变化时更新值以减少不必要的响应式更新 */ +let lastUpdateTime = 0; function refreshTime() { - currentTime.value = Date.now(); + const now = Date.now(); + // 只在秒级变化时更新,减少响应式更新频率 + if (Math.floor(now / 1000) !== Math.floor(lastUpdateTime / 1000)) { + currentTime.value = now; + lastUpdateTime = now; + } window.requestAnimationFrame(refreshTime); } refreshTime(); @@ -108,17 +117,22 @@ onMounted(async () => { console.log('ws connected'); store.dispatch('watchWsMsg'); }); - window.addEventListener('focus', () => { + const handleFocus = () => { // ws在离开焦点后出现断连,尝试重新连接 - // 暂定仅针对-1状态进行重连 - if ([-1].includes(wsService.connected)) { + // 仅针对已关闭状态进行重连 + if (wsService.connected === WS_CONNECTION_STATUS.CLOSED) { restart(); } - }); + }; + window.addEventListener('focus', handleFocus); /** * 激活websocket服务 */ activeWebsocketService(); + + onUnmounted(() => { + window.removeEventListener('focus', handleFocus); + }); }); window.addEventListener('unhandledrejection', (event) => { diff --git a/src/layout/components/search-box.vue b/src/layout/components/search-box.vue index d0c9f8d..1be5c16 100644 --- a/src/layout/components/search-box.vue +++ b/src/layout/components/search-box.vue @@ -187,6 +187,10 @@ onMounted(() => { onUnmounted(() => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keydown', handleEscKey); + if (handleSearchTimer) { + clearTimeout(handleSearchTimer); + handleSearchTimer = null; + } }); diff --git a/src/layout/main.vue b/src/layout/main.vue index 76e3a68..0d27c8a 100644 --- a/src/layout/main.vue +++ b/src/layout/main.vue @@ -32,6 +32,7 @@ import { ref, computed, + onUnmounted, } from 'vue'; import config from '@/config'; import Fireworks from '@/components/fireworks.vue'; @@ -73,8 +74,14 @@ const enableInnerSearch = computed(() => { return config.nazhua.enableInnerSearch; }); -window.addEventListener('resize', () => { +const handleResize = () => { windowWidth.value = window.innerWidth; +}; + +window.addEventListener('resize', handleResize); + +onUnmounted(() => { + window.removeEventListener('resize', handleResize); }); diff --git a/src/utils/load-nezha-v0-config.js b/src/utils/load-nezha-v0-config.js index f1d0e39..042d8be 100644 --- a/src/utils/load-nezha-v0-config.js +++ b/src/utils/load-nezha-v0-config.js @@ -35,7 +35,13 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()). if (!configStr) { return null; } - const remoteConfig = JSON.parse(unescaped(configStr)); + let remoteConfig; + try { + remoteConfig = JSON.parse(unescaped(configStr)); + } catch (error) { + console.error('Failed to parse nezha config:', error); + return null; + } if (remoteConfig?.servers) { remoteConfig.servers = remoteConfig.servers.map((i) => { const item = { @@ -43,7 +49,8 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()). }; try { item.PublicNote = JSON.parse(i.PublicNote); - } catch { + } catch (error) { + console.warn('Failed to parse PublicNote for server:', i.ID || i.id, error); item.PublicNote = {}; } return item; @@ -51,7 +58,10 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()). return remoteConfig; } return null; -}).catch(() => null); +}).catch((error) => { + console.error('Failed to load nezha config:', error); + return null; +}); /** * 获取标签列表 diff --git a/src/utils/load-nezha-v1-config.js b/src/utils/load-nezha-v1-config.js index ef2d728..b6b484b 100644 --- a/src/utils/load-nezha-v1-config.js +++ b/src/utils/load-nezha-v1-config.js @@ -21,7 +21,10 @@ export const loadServerGroup = async () => request({ }); } return null; -}).catch(() => null); +}).catch((error) => { + console.error('Failed to load server group:', error); + return null; +}); /** * 加载网站配置 @@ -37,7 +40,10 @@ export const loadSetting = async () => request({ return res.data?.data || {}; } return null; -}).catch(() => null); +}).catch((error) => { + console.error('Failed to load setting:', error); + return null; +}); /** * 加载个人信息 @@ -53,4 +59,7 @@ export const loadProfile = async (check) => request({ return res.data?.data || {}; } return null; -}).catch(() => null); +}).catch((error) => { + console.error('Failed to load profile:', error); + return null; +}); diff --git a/src/utils/transform-v1-2-v0.js b/src/utils/transform-v1-2-v0.js index 31cc625..c4fd79d 100644 --- a/src/utils/transform-v1-2-v0.js +++ b/src/utils/transform-v1-2-v0.js @@ -122,10 +122,11 @@ export default function (v1Data) { try { v0Data.PublicNote = JSON.parse(v1Data.public_note); } catch (e) { - v1Data.PublicNote = null; + console.warn('Failed to parse public_note for server:', v1Data.id, e); + v0Data.PublicNote = null; } } else { - v1Data.PublicNote = null; + v0Data.PublicNote = null; } return v0Data; } diff --git a/src/views/components/server-detail/server-monitor.vue b/src/views/components/server-detail/server-monitor.vue index 9634396..5b9e11d 100644 --- a/src/views/components/server-detail/server-monitor.vue +++ b/src/views/components/server-detail/server-monitor.vue @@ -263,7 +263,7 @@ const monitorChartType = computed(() => { const nowServerTime = computed(() => store.state.serverTime || Date.now()); // const nowServerTime = computed(() => Date.now()); // console.log(store.state.serverTime); -const accpetShowTime = computed(() => (Math.floor(nowServerTime.value / 60000) - minute.value) * 60000); +const acceptShowTime = computed(() => (Math.floor(nowServerTime.value / 60000) - minute.value) * 60000); const minuteActiveArrowStyle = computed(() => { const index = minutes.findIndex((i) => i.value === minute.value); @@ -310,7 +310,7 @@ const monitorChartData = computed(() => { if (time < earliestTimestamp) { earliestTimestamp = time; } - const status = time >= accpetShowTime.value; + const status = time >= acceptShowTime.value; // 允许显示的数据,记录到cateAcceptTime if (status) { @@ -328,7 +328,7 @@ const monitorChartData = computed(() => { // 允许显示的最早时间戳,用于生成显示时间范围内的数据 const actualStartTime = Math.max( - accpetShowTime.value, + acceptShowTime.value, earliestTimestamp, ); diff --git a/src/views/components/server-list/server-option-box.vue b/src/views/components/server-list/server-option-box.vue index e3c0662..5944929 100644 --- a/src/views/components/server-list/server-option-box.vue +++ b/src/views/components/server-list/server-option-box.vue @@ -49,7 +49,7 @@ const props = defineProps({ type: Array, default: () => [], }, - accpetEmpty: { + acceptEmpty: { type: Boolean, default: true, }, @@ -74,7 +74,7 @@ const activeValue = computed({ function toggleModelValue(item) { if (activeValue.value === item.value) { - if (props.accpetEmpty) { + if (props.acceptEmpty) { activeValue.value = ''; } } else { diff --git a/src/views/components/server-list/server-status/server-status.js b/src/views/components/server-list/server-status/server-status.js index 9b09022..95469f3 100644 --- a/src/views/components/server-list/server-status/server-status.js +++ b/src/views/components/server-list/server-status/server-status.js @@ -56,7 +56,7 @@ const COLUMN_MAP = Object.freeze({ }, speeds: { label: '网速', - width: 120, + width: 122, align: 'center', }, inSpeed: { @@ -71,7 +71,7 @@ const COLUMN_MAP = Object.freeze({ }, transfer: { label: '流量', - width: 120, + width: 122, align: 'center', }, inTransfer: { @@ -159,7 +159,7 @@ const COLUMN_MAP = Object.freeze({ * 默认列配置 */ // eslint-disable-next-line max-len, vue/max-len -const DEFAULT_COLUMNS = 'status,name,country,system,config,duration,speeds,transfer,tcp,udp,load,cpuText,memText,diskText,billing,remainingTime'; +const DEFAULT_COLUMNS = 'status,name,country,system,config,duration,speeds,transfer,load,cpu,mem,disk,billing,remainingTime'; /** * 需要实时更新的数据 diff --git a/src/views/home.vue b/src/views/home.vue index b253deb..dc60a5e 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -15,7 +15,7 @@
@@ -412,7 +412,10 @@ const worldMapPosition = computed(() => { * 处理窗口大小变化 */ function handleResize() { - worldMapWidth.value = document.querySelector('.server-list-container').clientWidth - 40; + const serverListContainer = document.querySelector('.server-list-container'); + if (serverListContainer) { + worldMapWidth.value = serverListContainer.clientWidth - 40; + } windowWidth.value = window.innerWidth; } @@ -471,6 +474,7 @@ onActivated(() => { &.list-is--server-status { --list-container-width: 1300px; + // 针对1440px以下的屏幕 @media screen and (max-width: 1440px) { --list-container-width: 1300px; @@ -483,7 +487,7 @@ onActivated(() => { } } -.fitler-group { +.filter-group { display: flex; flex-wrap: wrap; justify-content: space-between; diff --git a/src/ws/index.js b/src/ws/index.js index 538d7bf..5b2f18c 100644 --- a/src/ws/index.js +++ b/src/ws/index.js @@ -2,7 +2,7 @@ import config from '@/config'; import MessageSubscribe from '@/utils/subscribe'; import v1TransformV0 from '@/utils/transform-v1-2-v0'; -import WSService from './service'; +import WSService, { WS_CONNECTION_STATUS } from './service'; /** * 获取不同版本的WebSocket路径 @@ -50,7 +50,7 @@ const wsService = new WSService({ }); function restart() { - if (wsService.connected !== 0) { + if (wsService.connected !== WS_CONNECTION_STATUS.DISCONNECTED) { wsService.close(); } wsService.active(); @@ -63,7 +63,7 @@ export { }; export default (actived) => { - if (wsService.connected === 2) { + if (wsService.connected === WS_CONNECTION_STATUS.CONNECTED) { if (actived) { actived(); } @@ -75,7 +75,7 @@ export default (actived) => { } }); // 如果已经连接中,则不再连接 - if (wsService.connected === 1) { + if (wsService.connected === WS_CONNECTION_STATUS.CONNECTING) { return; } wsService.active(); diff --git a/src/ws/service.js b/src/ws/service.js index 97f0fc0..fb13021 100644 --- a/src/ws/service.js +++ b/src/ws/service.js @@ -1,3 +1,11 @@ +// WebSocket 连接状态常量 +export const WS_CONNECTION_STATUS = { + DISCONNECTED: 0, // 未连接 + CONNECTING: 1, // 连接中 + CONNECTED: 2, // 已连接 + CLOSED: -1, // 已关闭 +}; + class WSService { constructor(options) { const { @@ -23,16 +31,15 @@ class WSService { messageError: onMessageError || (() => {}), }; - // 单例模式 遇到重复的ws服务,不再允许建立新的ws消息处理,如果遇到问题,等待用户自行刷新页面(破罐子破摔解决方法) + // 单例模式:防止重复创建 WebSocket 连接 + // 如果检测到已有实例,触发错误回调并返回,避免资源浪费 if (WSService.instance) { - // 抛出错误,防止重复创建 WebSocket 连接 this.$on.error(new Error('WebSocket connection already exists')); return; } WSService.instance = this; - // 0: 未连接,1: 连接中,2: 已连接,-1: 已关闭 - this.connected = 0; + this.connected = WS_CONNECTION_STATUS.DISCONNECTED; this.ws = undefined; this.evt = (event) => { if (this.debug) { @@ -52,18 +59,18 @@ class WSService { } get isConnected() { - return this.connected === 2; + return this.connected === WS_CONNECTION_STATUS.CONNECTED; } active() { // 如果已经连接中或已连接,则不再连接 - if (this.connected > 0) { + if (this.connected > WS_CONNECTION_STATUS.DISCONNECTED) { console.warn('WebSocket connection already exists or is connecting'); return; } // 标记为正在连接中 - this.connected = 1; + this.connected = WS_CONNECTION_STATUS.CONNECTING; // 创建 WebSocket 连接 this.ws = new WebSocket(this.$wsUrl); @@ -71,14 +78,14 @@ class WSService { if (this.debug) { console.log('socket connected', event); } - this.connected = 2; + this.connected = WS_CONNECTION_STATUS.CONNECTED; this.$on.connect(event); }); this.ws.addEventListener('close', (event) => { if (this.debug) { console.log('socket closed', event); } - this.connected = -1; + this.connected = WS_CONNECTION_STATUS.CLOSED; WSService.instance = null; // 清除实例引用 this.$on.close(event); });