feat(): 优化状态栏
This commit is contained in:
+13
-3
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import {
|
||||
SettingOutlined,
|
||||
DashboardOutlined,
|
||||
@@ -12,13 +12,23 @@ import LoginForm from "./components/LoginForm.vue";
|
||||
import MonitorControl from "./components/MonitorControl.vue";
|
||||
import SettingsPanel from "./components/SettingsPanel.vue";
|
||||
import LogViewer from "./components/LogViewer.vue";
|
||||
import AppStatusBar from "./components/AppStatusBar.vue";
|
||||
import AppStatusBar, { type StatusBarItem } from "./components/AppStatusBar.vue";
|
||||
import CloseDialog from "./components/CloseDialog.vue";
|
||||
import { useMonitorStore } from "./stores/monitor";
|
||||
|
||||
const activeTab = ref("monitor");
|
||||
const store = useMonitorStore();
|
||||
|
||||
const statusItems = computed<StatusBarItem[]>(() => {
|
||||
const items: StatusBarItem[] = [];
|
||||
if (store.isPostponed) {
|
||||
items.push({ key: "postponed", type: "warning", content: store.message });
|
||||
} else if (store.message) {
|
||||
items.push({ key: "message", type: "info", content: store.message });
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
onMounted(() => store.initialize());
|
||||
onUnmounted(() => store.cleanup());
|
||||
</script>
|
||||
@@ -61,7 +71,7 @@ onUnmounted(() => store.cleanup());
|
||||
</a-tabs>
|
||||
</a-layout-content>
|
||||
|
||||
<AppStatusBar />
|
||||
<AppStatusBar :items="statusItems" />
|
||||
</a-layout>
|
||||
|
||||
<CloseDialog />
|
||||
|
||||
@@ -1,22 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
InfoCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
import { useMonitorStore } from "../stores/monitor";
|
||||
|
||||
const store = useMonitorStore();
|
||||
export interface StatusBarItem {
|
||||
key: string;
|
||||
content: string;
|
||||
type?: "info" | "warning" | "success" | "error";
|
||||
}
|
||||
|
||||
defineProps<{ items?: StatusBarItem[] }>();
|
||||
|
||||
const colorMap: Record<string, string> = {
|
||||
info: "rgba(255,255,255,0.85)",
|
||||
warning: "#ffe58f",
|
||||
success: "#95de64",
|
||||
error: "#ff7875",
|
||||
};
|
||||
|
||||
const iconMap: Record<string, unknown> = {
|
||||
info: InfoCircleOutlined,
|
||||
warning: ExclamationCircleOutlined,
|
||||
success: CheckCircleOutlined,
|
||||
error: CloseCircleOutlined,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-statusbar">
|
||||
<span v-if="store.isPostponed" class="statusbar-item statusbar-item--warning">
|
||||
<ExclamationCircleOutlined /> 监控已推迟,1小时后自动恢复
|
||||
</span>
|
||||
<span v-if="store.isPostponed && store.message" class="statusbar-sep" />
|
||||
<span v-if="store.message" class="statusbar-item statusbar-item--info">
|
||||
<InfoCircleOutlined /> {{ store.message }}
|
||||
</span>
|
||||
<template v-for="(item, index) in items" :key="item.key">
|
||||
<span v-if="index > 0" class="statusbar-sep" />
|
||||
<span class="statusbar-item" :style="{ color: colorMap[item.type ?? 'info'] }">
|
||||
<component :is="iconMap[item.type ?? 'info']" />
|
||||
{{ item.content }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,12 +59,6 @@ const store = useMonitorStore();
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
}
|
||||
.statusbar-item--warning {
|
||||
color: #ffe58f;
|
||||
}
|
||||
.statusbar-item--info {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.statusbar-sep {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import { useMonitorStore } from "../stores/monitor";
|
||||
|
||||
const store = useMonitorStore();
|
||||
|
||||
// ===== 检查间隔:内部用秒,TimePicker 用 Dayjs =====
|
||||
const intervalTime = computed<Dayjs>({
|
||||
get() {
|
||||
const t = store.config.check_interval;
|
||||
return dayjs().hour(Math.floor(t / 3600)).minute(Math.floor((t % 3600) / 60)).second(t % 60).millisecond(0);
|
||||
},
|
||||
set(val: Dayjs | null) {
|
||||
if (!val) return;
|
||||
store.config.check_interval = Math.max(10, val.hour() * 3600 + val.minute() * 60 + val.second());
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="检查间隔(秒)">
|
||||
<a-input-number
|
||||
v-model:value="store.config.check_interval"
|
||||
:min="10"
|
||||
:max="3600"
|
||||
<a-form-item label="检查间隔">
|
||||
<a-time-picker
|
||||
v-model:value="intervalTime"
|
||||
format="HH:mm:ss"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div class="form-hint">最短 10 秒,当前 {{ store.config.check_interval }} 秒</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="网络超时(秒)">
|
||||
<a-input-number
|
||||
@@ -50,6 +65,14 @@ const store = useMonitorStore();
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="点击「去处理」后暂停时长(分钟)">
|
||||
<a-input-number
|
||||
v-model:value="store.config.handle_pause_minutes"
|
||||
:min="1"
|
||||
:max="120"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="关闭窗口行为">
|
||||
<a-radio-group v-model:value="store.config.close_action">
|
||||
<a-radio value="ask">每次询问</a-radio>
|
||||
|
||||
+23
-2
@@ -68,6 +68,7 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
notification_sound: true,
|
||||
network_timeout: 30,
|
||||
close_action: "ask" as "ask" | "minimize" | "close",
|
||||
handle_pause_minutes: 20,
|
||||
});
|
||||
const showCloseDialog = ref(false);
|
||||
|
||||
@@ -233,8 +234,9 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
|
||||
async function tryReLogin(): Promise<void> {
|
||||
if (loginMode.value === "token") {
|
||||
addLog("WARN", "Token 模式下无法自动重登录,请更换新的 Token", "SESSION");
|
||||
message.value = "Token 可能已过期,请更换新的 Token";
|
||||
addLog("ERROR", "Token 已失效,监控已停止,请更换新的 Token 后重新开始", "SESSION");
|
||||
message.value = "Token 已失效,监控已停止";
|
||||
stopMonitoring();
|
||||
return;
|
||||
}
|
||||
if (!username.value || !password.value) {
|
||||
@@ -453,6 +455,24 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function pauseForHandle() {
|
||||
if (!isMonitoring.value) return;
|
||||
if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; }
|
||||
if (postponeTimer) { clearTimeout(postponeTimer); }
|
||||
isPostponed.value = true;
|
||||
const minutes = config.value.handle_pause_minutes;
|
||||
addLog("INFO", `点击"去处理",监控已暂停 ${minutes} 分钟`, "MONITOR");
|
||||
message.value = `监控已暂停 ${minutes} 分钟,将在 ${minutes} 分钟后自动恢复`;
|
||||
postponeTimer = setTimeout(() => {
|
||||
isPostponed.value = false;
|
||||
postponeTimer = null;
|
||||
apiCheckStatus();
|
||||
monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000);
|
||||
addLog("INFO", "暂停结束,监控已自动恢复", "MONITOR");
|
||||
message.value = "暂停结束,监控已自动恢复";
|
||||
}, minutes * 60 * 1000);
|
||||
}
|
||||
|
||||
async function manualCheck() {
|
||||
if (!isLoggedIn.value) { message.value = "请先开始监控(登录)"; return; }
|
||||
isLoading.value = true;
|
||||
@@ -564,6 +584,7 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
} catch { /* ignore */ }
|
||||
unlistenNotification = await listen<string>("notification-action", (event) => {
|
||||
if (event.payload === "postpone_1h") postponeMonitoring();
|
||||
else if (event.payload === "go_handle") pauseForHandle();
|
||||
});
|
||||
unlistenClose = await listen<void>("close-requested", async () => {
|
||||
const action = config.value.close_action;
|
||||
|
||||
Reference in New Issue
Block a user