🛠️ 增强代码健壮性,添加错误处理和状态常量

This commit is contained in:
hi2hi 2025-12-10 10:47:07 +08:00
parent 586f1dd063
commit 881c9a05e5
14 changed files with 98 additions and 41 deletions

View File

@ -8,7 +8,8 @@
"build:cdn": "cross-env VITE_SARASA_TERM_SC_USE_CDN=1 VITE_USE_CDN=1 vite build", "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", "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", "preview": "vite preview",
"lint": "eslint ." "lint": "eslint .",
"lint:fix": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"axios": "^1.13.2", "axios": "^1.13.2",

View File

@ -11,7 +11,7 @@ window.$$nazhuaConfig = {
// showLantern: true, // 是否显示灯笼 // showLantern: true, // 是否显示灯笼
enableInnerSearch: true, // 启用内部搜索 enableInnerSearch: true, // 启用内部搜索
// listServerItemTypeToggle: true, // 服务器列表项类型切换 // listServerItemTypeToggle: true, // 服务器列表项类型切换
// listServerItemType: 'server-status', // 服务器列表项类型 card/row/server-status row列表模式移动端自动切换至card listServerItemType: 'card', // 服务器列表项类型 card/row/server-status row列表模式移动端自动切换至card
// serverStatusColumnsTpl: null, // 服务器状态列配置模板 // serverStatusColumnsTpl: null, // 服务器状态列配置模板
// listServerStatusType: 'progress', // 服务器状态类型--列表 // listServerStatusType: 'progress', // 服务器状态类型--列表
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载 // listServerRealTimeShowLoad: true, // 列表显示服务器实时负载

View File

@ -15,6 +15,7 @@ import {
watch, watch,
provide, provide,
onMounted, onMounted,
onUnmounted,
} from 'vue'; } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
@ -24,6 +25,7 @@ import config, {
import sleep from '@/utils/sleep'; import sleep from '@/utils/sleep';
import LayoutMain from './layout/main.vue'; import LayoutMain from './layout/main.vue';
import { WS_CONNECTION_STATUS } from './ws/service';
import activeWebsocketService, { import activeWebsocketService, {
wsService, wsService,
restart, restart,
@ -39,9 +41,16 @@ provide('currentTime', currentTime);
/** /**
* 刷新当前时间 * 刷新当前时间
* 使用 requestAnimationFrame 持续更新时间但只在秒级变化时更新值以减少不必要的响应式更新
*/ */
let lastUpdateTime = 0;
function refreshTime() { 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); window.requestAnimationFrame(refreshTime);
} }
refreshTime(); refreshTime();
@ -108,17 +117,22 @@ onMounted(async () => {
console.log('ws connected'); console.log('ws connected');
store.dispatch('watchWsMsg'); store.dispatch('watchWsMsg');
}); });
window.addEventListener('focus', () => { const handleFocus = () => {
// ws // ws
// -1 //
if ([-1].includes(wsService.connected)) { if (wsService.connected === WS_CONNECTION_STATUS.CLOSED) {
restart(); restart();
} }
}); };
window.addEventListener('focus', handleFocus);
/** /**
* 激活websocket服务 * 激活websocket服务
*/ */
activeWebsocketService(); activeWebsocketService();
onUnmounted(() => {
window.removeEventListener('focus', handleFocus);
});
}); });
window.addEventListener('unhandledrejection', (event) => { window.addEventListener('unhandledrejection', (event) => {

View File

@ -187,6 +187,10 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keydown', handleEscKey); window.removeEventListener('keydown', handleEscKey);
if (handleSearchTimer) {
clearTimeout(handleSearchTimer);
handleSearchTimer = null;
}
}); });
</script> </script>

View File

@ -32,6 +32,7 @@
import { import {
ref, ref,
computed, computed,
onUnmounted,
} from 'vue'; } from 'vue';
import config from '@/config'; import config from '@/config';
import Fireworks from '@/components/fireworks.vue'; import Fireworks from '@/components/fireworks.vue';
@ -73,8 +74,14 @@ const enableInnerSearch = computed(() => {
return config.nazhua.enableInnerSearch; return config.nazhua.enableInnerSearch;
}); });
window.addEventListener('resize', () => { const handleResize = () => {
windowWidth.value = window.innerWidth; windowWidth.value = window.innerWidth;
};
window.addEventListener('resize', handleResize);
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
}); });
</script> </script>

View File

@ -35,7 +35,13 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()).
if (!configStr) { if (!configStr) {
return null; 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) { if (remoteConfig?.servers) {
remoteConfig.servers = remoteConfig.servers.map((i) => { remoteConfig.servers = remoteConfig.servers.map((i) => {
const item = { const item = {
@ -43,7 +49,8 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()).
}; };
try { try {
item.PublicNote = JSON.parse(i.PublicNote); item.PublicNote = JSON.parse(i.PublicNote);
} catch { } catch (error) {
console.warn('Failed to parse PublicNote for server:', i.ID || i.id, error);
item.PublicNote = {}; item.PublicNote = {};
} }
return item; return item;
@ -51,7 +58,10 @@ export default async () => fetch(getNezhaConfigUrl()).then((res) => res.text()).
return remoteConfig; return remoteConfig;
} }
return null; return null;
}).catch(() => null); }).catch((error) => {
console.error('Failed to load nezha config:', error);
return null;
});
/** /**
* 获取标签列表 * 获取标签列表

View File

@ -21,7 +21,10 @@ export const loadServerGroup = async () => request({
}); });
} }
return null; 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 res.data?.data || {};
} }
return null; 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 res.data?.data || {};
} }
return null; return null;
}).catch(() => null); }).catch((error) => {
console.error('Failed to load profile:', error);
return null;
});

View File

@ -122,10 +122,11 @@ export default function (v1Data) {
try { try {
v0Data.PublicNote = JSON.parse(v1Data.public_note); v0Data.PublicNote = JSON.parse(v1Data.public_note);
} catch (e) { } catch (e) {
v1Data.PublicNote = null; console.warn('Failed to parse public_note for server:', v1Data.id, e);
v0Data.PublicNote = null;
} }
} else { } else {
v1Data.PublicNote = null; v0Data.PublicNote = null;
} }
return v0Data; return v0Data;
} }

View File

@ -263,7 +263,7 @@ const monitorChartType = computed(() => {
const nowServerTime = computed(() => store.state.serverTime || Date.now()); const nowServerTime = computed(() => store.state.serverTime || Date.now());
// const nowServerTime = computed(() => Date.now()); // const nowServerTime = computed(() => Date.now());
// console.log(store.state.serverTime); // 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 minuteActiveArrowStyle = computed(() => {
const index = minutes.findIndex((i) => i.value === minute.value); const index = minutes.findIndex((i) => i.value === minute.value);
@ -310,7 +310,7 @@ const monitorChartData = computed(() => {
if (time < earliestTimestamp) { if (time < earliestTimestamp) {
earliestTimestamp = time; earliestTimestamp = time;
} }
const status = time >= accpetShowTime.value; const status = time >= acceptShowTime.value;
// cateAcceptTime // cateAcceptTime
if (status) { if (status) {
@ -328,7 +328,7 @@ const monitorChartData = computed(() => {
// //
const actualStartTime = Math.max( const actualStartTime = Math.max(
accpetShowTime.value, acceptShowTime.value,
earliestTimestamp, earliestTimestamp,
); );

View File

@ -49,7 +49,7 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
accpetEmpty: { acceptEmpty: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
@ -74,7 +74,7 @@ const activeValue = computed({
function toggleModelValue(item) { function toggleModelValue(item) {
if (activeValue.value === item.value) { if (activeValue.value === item.value) {
if (props.accpetEmpty) { if (props.acceptEmpty) {
activeValue.value = ''; activeValue.value = '';
} }
} else { } else {

View File

@ -56,7 +56,7 @@ const COLUMN_MAP = Object.freeze({
}, },
speeds: { speeds: {
label: '网速', label: '网速',
width: 120, width: 122,
align: 'center', align: 'center',
}, },
inSpeed: { inSpeed: {
@ -71,7 +71,7 @@ const COLUMN_MAP = Object.freeze({
}, },
transfer: { transfer: {
label: '流量', label: '流量',
width: 120, width: 122,
align: 'center', align: 'center',
}, },
inTransfer: { inTransfer: {
@ -159,7 +159,7 @@ const COLUMN_MAP = Object.freeze({
* 默认列配置 * 默认列配置
*/ */
// eslint-disable-next-line max-len, vue/max-len // 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';
/** /**
* 需要实时更新的数据 * 需要实时更新的数据

View File

@ -15,7 +15,7 @@
</div> </div>
<div <div
v-if="showFilter" v-if="showFilter"
class="fitler-group" class="filter-group"
:class="{ :class="{
'list-is--row': showListRow, 'list-is--row': showListRow,
'list-is--card': showListCard, 'list-is--card': showListCard,
@ -44,7 +44,7 @@
v-if="config.nazhua.listServerItemTypeToggle" v-if="config.nazhua.listServerItemTypeToggle"
v-model="listType" v-model="listType"
:options="listTypeOptions" :options="listTypeOptions"
:accpet-empty="false" :accept-empty="false"
:mobile-show="false" :mobile-show="false"
/> />
</div> </div>
@ -412,7 +412,10 @@ const worldMapPosition = computed(() => {
* 处理窗口大小变化 * 处理窗口大小变化
*/ */
function handleResize() { 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; windowWidth.value = window.innerWidth;
} }
@ -471,6 +474,7 @@ onActivated(() => {
&.list-is--server-status { &.list-is--server-status {
--list-container-width: 1300px; --list-container-width: 1300px;
// 1440px // 1440px
@media screen and (max-width: 1440px) { @media screen and (max-width: 1440px) {
--list-container-width: 1300px; --list-container-width: 1300px;
@ -483,7 +487,7 @@ onActivated(() => {
} }
} }
.fitler-group { .filter-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;

View File

@ -2,7 +2,7 @@ import config from '@/config';
import MessageSubscribe from '@/utils/subscribe'; import MessageSubscribe from '@/utils/subscribe';
import v1TransformV0 from '@/utils/transform-v1-2-v0'; import v1TransformV0 from '@/utils/transform-v1-2-v0';
import WSService from './service'; import WSService, { WS_CONNECTION_STATUS } from './service';
/** /**
* 获取不同版本的WebSocket路径 * 获取不同版本的WebSocket路径
@ -50,7 +50,7 @@ const wsService = new WSService({
}); });
function restart() { function restart() {
if (wsService.connected !== 0) { if (wsService.connected !== WS_CONNECTION_STATUS.DISCONNECTED) {
wsService.close(); wsService.close();
} }
wsService.active(); wsService.active();
@ -63,7 +63,7 @@ export {
}; };
export default (actived) => { export default (actived) => {
if (wsService.connected === 2) { if (wsService.connected === WS_CONNECTION_STATUS.CONNECTED) {
if (actived) { if (actived) {
actived(); actived();
} }
@ -75,7 +75,7 @@ export default (actived) => {
} }
}); });
// 如果已经连接中,则不再连接 // 如果已经连接中,则不再连接
if (wsService.connected === 1) { if (wsService.connected === WS_CONNECTION_STATUS.CONNECTING) {
return; return;
} }
wsService.active(); wsService.active();

View File

@ -1,3 +1,11 @@
// WebSocket 连接状态常量
export const WS_CONNECTION_STATUS = {
DISCONNECTED: 0, // 未连接
CONNECTING: 1, // 连接中
CONNECTED: 2, // 已连接
CLOSED: -1, // 已关闭
};
class WSService { class WSService {
constructor(options) { constructor(options) {
const { const {
@ -23,16 +31,15 @@ class WSService {
messageError: onMessageError || (() => {}), messageError: onMessageError || (() => {}),
}; };
// 单例模式 遇到重复的ws服务不再允许建立新的ws消息处理如果遇到问题等待用户自行刷新页面破罐子破摔解决方法 // 单例模式:防止重复创建 WebSocket 连接
// 如果检测到已有实例,触发错误回调并返回,避免资源浪费
if (WSService.instance) { if (WSService.instance) {
// 抛出错误,防止重复创建 WebSocket 连接
this.$on.error(new Error('WebSocket connection already exists')); this.$on.error(new Error('WebSocket connection already exists'));
return; return;
} }
WSService.instance = this; WSService.instance = this;
// 0: 未连接1: 连接中2: 已连接,-1: 已关闭 this.connected = WS_CONNECTION_STATUS.DISCONNECTED;
this.connected = 0;
this.ws = undefined; this.ws = undefined;
this.evt = (event) => { this.evt = (event) => {
if (this.debug) { if (this.debug) {
@ -52,18 +59,18 @@ class WSService {
} }
get isConnected() { get isConnected() {
return this.connected === 2; return this.connected === WS_CONNECTION_STATUS.CONNECTED;
} }
active() { active() {
// 如果已经连接中或已连接,则不再连接 // 如果已经连接中或已连接,则不再连接
if (this.connected > 0) { if (this.connected > WS_CONNECTION_STATUS.DISCONNECTED) {
console.warn('WebSocket connection already exists or is connecting'); console.warn('WebSocket connection already exists or is connecting');
return; return;
} }
// 标记为正在连接中 // 标记为正在连接中
this.connected = 1; this.connected = WS_CONNECTION_STATUS.CONNECTING;
// 创建 WebSocket 连接 // 创建 WebSocket 连接
this.ws = new WebSocket(this.$wsUrl); this.ws = new WebSocket(this.$wsUrl);
@ -71,14 +78,14 @@ class WSService {
if (this.debug) { if (this.debug) {
console.log('socket connected', event); console.log('socket connected', event);
} }
this.connected = 2; this.connected = WS_CONNECTION_STATUS.CONNECTED;
this.$on.connect(event); this.$on.connect(event);
}); });
this.ws.addEventListener('close', (event) => { this.ws.addEventListener('close', (event) => {
if (this.debug) { if (this.debug) {
console.log('socket closed', event); console.log('socket closed', event);
} }
this.connected = -1; this.connected = WS_CONNECTION_STATUS.CLOSED;
WSService.instance = null; // 清除实例引用 WSService.instance = null; // 清除实例引用
this.$on.close(event); this.$on.close(event);
}); });