feat: 完成,登录获取cookie有bug

This commit is contained in:
tsl
2026-02-27 09:07:50 +08:00
parent 4cf22c9205
commit 71bb171f48
19 changed files with 5210 additions and 1017 deletions
+645 -160
View File
@@ -1,160 +1,645 @@
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
const greetMsg = ref("");
const name = ref("");
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
greetMsg.value = await invoke("greet", { name: name.value });
}
</script>
<template>
<main class="container">
<h1>Welcome to Tauri + Vue</h1>
<div class="row">
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" class="logo tauri" alt="Tauri logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<p>Click on the Tauri, Vite, and Vue logos to learn more.</p>
<form class="row" @submit.prevent="greet">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
<p>{{ greetMsg }}</p>
</main>
</template>
<style scoped>
.logo.vite:hover {
filter: drop-shadow(0 0 2em #747bff);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #249b73);
}
</style>
<style>
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}
</style>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { TrayIcon } from "@tauri-apps/api/tray";
import { Image } from "@tauri-apps/api/image";
import { Menu, MenuItem } from "@tauri-apps/api/menu";
import { fetch } from "@tauri-apps/plugin-http";
import { isPermissionGranted, requestPermission, sendNotification } from "@tauri-apps/plugin-notification";
import { enable as enableAutostart, disable as disableAutostart, isEnabled as isAutostartEnabled } from "@tauri-apps/plugin-autostart";
// ===== 常量 =====
const API_BASE = "https://crm.yunvip123.com/api";
const LOGIN_URL = `${API_BASE}/SystemUser/Login`;
const CHECK_URL = `${API_BASE}/DemandManage/QueryIndexCount`;
// ===== 响应式数据 =====
const username = ref("");
const password = ref("");
const message = ref("");
const isLoading = ref(false);
const isLoggedIn = ref(false);
const isMonitoring = ref(false);
const autoStartEnabled = ref(false);
const logs = ref<{ timestamp: string; level: string; message: string; category: string }[]>([]);
const showLogs = ref(false);
const showConfig = ref(false);
const networkStatus = ref({
is_connected: false,
api_reachable: false,
response_time: null as number | null,
last_check: "",
});
const config = ref({
check_interval: 60,
auto_start: false,
show_notifications: true,
network_timeout: 30,
});
// ===== 内部状态 =====
let sessionId = "";
let userGid = "";
let userName = "";
let monitorTimer: ReturnType<typeof setInterval> | null = null;
// ===== 日志 =====
function addLog(level: string, msg: string, category: string) {
const now = new Date();
const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`;
logs.value.push({ timestamp, level, message: msg, category });
if (logs.value.length > 1000) logs.value.shift();
}
// ===== 通知 =====
async function notify(title: string, body: string) {
if (!config.value.show_notifications) return;
let granted = await isPermissionGranted();
if (!granted) {
const permission = await requestPermission();
granted = permission === "granted";
}
if (granted) {
sendNotification({ title, body, actionTypeId: "crm-open" });
addLog("INFO", `通知: ${title} - ${body}`, "NOTIFICATION");
}
}
// ===== API 调用 =====
async function apiLogin(): Promise<boolean> {
try {
const body = JSON.stringify({ Account: username.value, PassWord: password.value });
const resp = await fetch(LOGIN_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": String(new TextEncoder().encode(body).length),
"Host": "crm.yunvip123.com",
"Cache-Control": "no-cache",
},
body,
});
console.log(Object.fromEntries(resp.headers.entries()));
const setCookie = resp.headers.get("set-cookie") || "";
const match = setCookie.match(/ASP\.NET_SessionId=([^;]+)/);
if (match) sessionId = match[1];
addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN");
const data = await resp.json();
if (data.success && data.data) {
userGid = data.data.GID;
userName = data.data.SU_UserName || "用户";
if (!sessionId) {
addLog("WARN", "未能从响应头提取SessionIdAPI检查将使用无Cookie模式", "LOGIN");
}
isLoggedIn.value = true;
addLog("INFO", `登录成功,用户: ${userName}, GID: ${userGid}`, "LOGIN");
return true;
} else {
addLog("ERROR", `登录失败: ${data.msg}`, "LOGIN");
return false;
}
} catch (e: any) {
addLog("ERROR", `登录请求失败: ${e.message || e}`, "LOGIN");
return false;
}
}
async function apiCheckStatus(): Promise<void> {
if (!userGid) {
addLog("WARN", "会话已失效,请重新登录", "SCHEDULER");
return;
}
try {
const resp = await fetch(CHECK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `ASP.NET_SessionId=${sessionId}`,
},
body: JSON.stringify({ UserGID: userGid }),
});
const result = await resp.json();
if (result.success && result.data) {
const d = result.data;
const pending = d.PendingCount || 0;
const stayclose = d.StaycloseCount || 0;
const confirm = d.ConfirmCount || 0;
addLog("INFO", `检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}`, "SCHEDULER");
if (pending > 0 || stayclose > 0 || confirm > 0) {
const msgs: string[] = [];
if (pending > 0) msgs.push(`待处理工单: ${pending}`);
if (stayclose > 0) msgs.push(`待关闭工单: ${stayclose}`);
if (confirm > 0) msgs.push(`待确认工单: ${confirm}`);
await notify("工单提醒", msgs.join(""));
}
} else {
addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER");
}
} catch (e: any) {
addLog("ERROR", `API 检查失败: ${e.message || e}`, "SCHEDULER");
}
}
// ===== 网络检测 =====
async function checkNetworkStatus() {
isLoading.value = true;
let isConnected = false;
let apiReachable = false;
let responseTime: number | null = null;
// 检查基本网络
try {
const resp = await fetch("https://www.baidu.com", { method: "GET", connectTimeout: config.value.network_timeout * 1000 });
console.log("Network check response:", resp);
console.log(resp.headers);
isConnected = resp.ok;
} catch { /* 网络不通 */ }
// 检查 API 服务器
if (isConnected) {
try {
const apiStart = Date.now();
const resp = await fetch(LOGIN_URL, { method: "HEAD", connectTimeout: config.value.network_timeout * 1000 });
responseTime = Date.now() - apiStart;
apiReachable = resp.status < 500;
} catch { /* API 不可达 */ }
}
const now = new Date();
networkStatus.value = {
is_connected: isConnected,
api_reachable: apiReachable,
response_time: responseTime,
last_check: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`,
};
addLog("INFO", `网络检查完成 - 连接: ${isConnected}, API可达: ${apiReachable}`, "NETWORK");
message.value = "网络状态检查完成";
isLoading.value = false;
}
// ===== 保存设置 =====
function saveSettings() {
if (!username.value.trim() || !password.value.trim()) {
message.value = "请输入用户名和密码";
return;
}
message.value = "设置保存成功";
addLog("INFO", "用户凭据已保存", "CONFIG");
}
// ===== 监控控制 =====
async function startMonitoring() {
if (!username.value.trim() || !password.value.trim()) {
message.value = "请先设置用户名和密码";
return;
}
isLoading.value = true;
const ok = await apiLogin();
if (!ok) {
message.value = "登录失败,无法开始监控";
isLoading.value = false;
return;
}
// 启动定时检查(先立即执行一次,再按间隔定时)
isMonitoring.value = true;
await apiCheckStatus();
monitorTimer = setInterval(() => {
apiCheckStatus();
}, config.value.check_interval * 1000);
addLog("INFO", "监控已启动", "MONITOR");
message.value = `监控已开始,欢迎 ${userName}`;
isLoading.value = false;
}
function stopMonitoring() {
if (monitorTimer) {
clearInterval(monitorTimer);
monitorTimer = null;
}
isMonitoring.value = false;
isLoggedIn.value = false;
sessionId = "";
userGid = "";
addLog("INFO", "监控已停止", "MONITOR");
message.value = "监控已停止";
}
// ===== 手动检查 =====
async function manualCheck() {
if (!isLoggedIn.value) {
message.value = "请先开始监控(登录)";
return;
}
isLoading.value = true;
await apiCheckStatus();
message.value = "手动检查完成";
isLoading.value = false;
}
// ===== 配置更新 =====
async function updateConfig() {
isLoading.value = true;
// 自启动
try {
if (config.value.auto_start) {
await enableAutostart();
} else {
await disableAutostart();
}
autoStartEnabled.value = config.value.auto_start;
addLog("INFO", `自启动设置: ${config.value.auto_start ? "启用" : "禁用"}`, "CONFIG");
} catch (e: any) {
addLog("ERROR", `自启动设置失败: ${e.message || e}`, "CONFIG");
}
// 如果监控中,重启定时器以应用新间隔
if (isMonitoring.value && monitorTimer) {
clearInterval(monitorTimer);
monitorTimer = setInterval(() => {
apiCheckStatus();
}, config.value.check_interval * 1000);
addLog("INFO", `检查间隔已更新为: ${config.value.check_interval}`, "CONFIG");
}
message.value = "配置更新成功";
isLoading.value = false;
}
// ===== 系统托盘 =====
async function setupTray() {
try {
const mainWindow = getCurrentWindow();
// 创建托盘菜单
const menu = await Menu.new({
items: [
await MenuItem.new({
id: "show",
text: "显示设置",
action: async () => {
await mainWindow.show();
await mainWindow.setFocus();
}
}),
await MenuItem.new({
id: "quit",
text: "退出",
action: async () => {
await mainWindow.destroy();
}
})
]
});
// 创建托盘图标
const icon = await Image.fromPath("icons/32x32.png");
await TrayIcon.new({
icon,
tooltip: "工单监控系统",
menu,
menuOnLeftClick: false,
action: async (event) => {
if (event.type === "Click" && event.button === "Left") {
await mainWindow.show();
await mainWindow.setFocus();
}
}
});
addLog("INFO", "系统托盘创建成功", "SYSTEM");
} catch (e: any) {
addLog("ERROR", `系统托盘创建失败: ${e.message || e}`, "SYSTEM");
}
}
// ===== 最小化到托盘 =====
async function minimizeToTray() {
try {
const mainWindow = getCurrentWindow();
await mainWindow.hide();
} catch (e: any) {
message.value = `最小化失败: ${e}`;
}
}
// ===== 日志操作 =====
function toggleLogs() {
showLogs.value = !showLogs.value;
}
function clearLogs() {
logs.value = [];
message.value = "日志已清除";
}
function toggleConfig() {
showConfig.value = !showConfig.value;
if (showConfig.value) {
config.value.auto_start = autoStartEnabled.value;
}
}
function clearMessage() {
message.value = "";
}
function formatResponseTime(time: number | null) {
return time ? `${time}ms` : "N/A";
}
function getNetworkStatusText() {
if (!networkStatus.value.is_connected) return "网络断开";
if (!networkStatus.value.api_reachable) return "API不可达";
return "网络正常";
}
// ===== 生命周期 =====
onMounted(async () => {
// 初始化系统托盘
await setupTray();
// 检查自启动状态
try {
autoStartEnabled.value = await isAutostartEnabled();
config.value.auto_start = autoStartEnabled.value;
} catch { /* ignore */ }
addLog("INFO", "应用程序启动完成", "SYSTEM");
});
onUnmounted(() => {
if (monitorTimer) clearInterval(monitorTimer);
});
</script>
<template>
<main class="app">
<div class="app__header">
<h1 class="app__title">系统监控设置</h1>
<div class="app__status">
<div class="status-row">
<span class="status-indicator" :class="{ 'status-indicator--active': isLoggedIn }"></span>
<span class="status-text">
{{ isLoggedIn ? '已登录' : '未登录' }}
{{ isMonitoring ? ' - 监控中' : '' }}
</span>
</div>
<div class="status-row">
<span class="network-indicator" :class="{
'network-indicator--connected': networkStatus.is_connected,
'network-indicator--api': networkStatus.api_reachable
}"></span>
<span class="network-text">
{{ getNetworkStatusText() }}
<span v-if="networkStatus.response_time" class="response-time">
({{ formatResponseTime(networkStatus.response_time) }})
</span>
</span>
</div>
</div>
</div>
<div class="app__content">
<!-- 设置表单 -->
<div class="settings-card">
<h2 class="settings-card__title">登录设置</h2>
<form class="settings-form" @submit.prevent="saveSettings">
<div class="form-group">
<label for="username" class="form-group__label">用户名</label>
<input id="username" v-model="username" type="text" class="form-group__input" placeholder="请输入用户名" :disabled="isLoading" />
</div>
<div class="form-group">
<label for="password" class="form-group__label">密码</label>
<input id="password" v-model="password" type="password" class="form-group__input" placeholder="请输入密码" :disabled="isLoading" />
</div>
<button type="submit" class="btn btn--primary" :disabled="isLoading">
{{ isLoading ? '保存中...' : '保存设置' }}
</button>
</form>
</div>
<!-- 操作按钮 -->
<div class="actions-card">
<h2 class="actions-card__title">监控控制</h2>
<div class="actions-grid">
<button class="btn btn--success" :disabled="isLoading || isMonitoring" @click="startMonitoring">
{{ isLoading ? '启动中...' : '开始监控' }}
</button>
<button class="btn btn--warning" :disabled="isLoading || !isMonitoring" @click="stopMonitoring">
停止监控
</button>
<button class="btn btn--info" :disabled="isLoading || !isLoggedIn" @click="manualCheck">
{{ isLoading ? '检查中...' : '手动检查API' }}
</button>
<button class="btn btn--secondary" :disabled="isLoading" @click="minimizeToTray">
最小化到托盘
</button>
</div>
</div>
<!-- 测试按钮 -->
<div class="actions-card">
<h2 class="actions-card__title">测试功能</h2>
<div class="actions-grid">
<button class="btn btn--primary" :disabled="isLoading" @click="checkNetworkStatus">
{{ isLoading ? '检查中...' : '网络检测' }}
</button>
<button class="btn btn--info" @click="toggleLogs">
{{ showLogs ? '隐藏日志' : '显示日志' }}
</button>
<button class="btn btn--primary" @click="toggleConfig">
{{ showConfig ? '隐藏配置' : '显示配置' }}
</button>
</div>
</div>
<!-- 配置面板 -->
<div v-if="showConfig" class="config-card">
<h2 class="config-card__title">高级配置</h2>
<div class="config-form">
<div class="form-group">
<label for="check_interval" class="form-group__label">检查间隔</label>
<input id="check_interval" v-model.number="config.check_interval" type="number" min="10" max="3600" class="form-group__input" />
</div>
<div class="form-group">
<label for="network_timeout" class="form-group__label">网络超时</label>
<input id="network_timeout" v-model.number="config.network_timeout" type="number" min="5" max="120" class="form-group__input" />
</div>
<div class="form-group form-group--checkbox">
<label class="checkbox-label">
<input v-model="config.auto_start" type="checkbox" class="checkbox-input" />
<span class="checkbox-text">开机自启动</span>
</label>
</div>
<div class="form-group form-group--checkbox">
<label class="checkbox-label">
<input v-model="config.show_notifications" type="checkbox" class="checkbox-input" />
<span class="checkbox-text">显示系统通知</span>
</label>
</div>
<button class="btn btn--primary" :disabled="isLoading" @click="updateConfig">
{{ isLoading ? '保存中...' : '保存配置' }}
</button>
</div>
</div>
<!-- 日志面板 -->
<div v-if="showLogs" class="logs-card">
<div class="logs-header">
<h2 class="logs-card__title">系统日志</h2>
<button class="btn btn--small btn--secondary" @click="clearLogs">清除日志</button>
</div>
<div class="logs-content">
<div v-if="logs.length === 0" class="logs-empty">暂无日志记录</div>
<div v-else class="logs-list">
<div v-for="log in [...logs].reverse()" :key="`${log.timestamp}-${log.message}`" class="log-entry" :class="`log-entry--${log.level.toLowerCase()}`">
<span class="log-time">{{ log.timestamp }}</span>
<span class="log-category">[{{ log.category }}]</span>
<span class="log-level">{{ log.level }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</div>
</div>
<!-- 消息显示 -->
<div v-if="message" class="message-card">
<div class="message-card__content">
<p class="message-card__text">{{ message }}</p>
<button class="message-card__close" @click="clearMessage">×</button>
</div>
</div>
</div>
</main>
</template>
<style scoped>
.app {
min-height: 100vh;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.app__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
}
.app__title { color: white; font-size: 24px; font-weight: 600; margin: 0; }
.app__status { display: flex; flex-direction: column; gap: 8px; }
.status-row { display: flex; align-items: center; gap: 8px; }
.status-indicator {
width: 12px; height: 12px; border-radius: 50%;
background-color: #dc3545; transition: background-color 0.3s ease;
}
.status-indicator--active { background-color: #28a745; }
.status-text { color: white; font-size: 14px; font-weight: 500; }
.network-indicator {
width: 12px; height: 12px; border-radius: 50%;
background-color: #dc3545; transition: background-color 0.3s ease;
}
.network-indicator--connected { background-color: #ffc107; }
.network-indicator--api { background-color: #28a745; }
.network-text { color: white; font-size: 12px; font-weight: 400; }
.response-time { color: #ccc; font-size: 11px; }
.app__content { display: grid; gap: 20px; max-width: 600px; margin: 0 auto; }
.settings-card, .actions-card, .config-card, .logs-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px; padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.settings-card__title, .actions-card__title, .config-card__title, .logs-card__title {
color: #333; font-size: 18px; font-weight: 600; margin: 0 0 20px 0;
}
.settings-form, .config-form { display: flex; flex-direction: column; gap: 16px; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-group__label { color: #555; font-size: 14px; font-weight: 500; }
.form-group__input {
padding: 12px 16px; border: 2px solid #e1e5e9; border-radius: 8px;
font-size: 14px; transition: border-color 0.3s ease; background: white;
}
.form-group__input:focus { outline: none; border-color: #667eea; }
.form-group__input:disabled { background-color: #f8f9fa; cursor: not-allowed; }
.actions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; }
.btn {
padding: 12px 20px; border: none; border-radius: 8px;
font-size: 14px; font-weight: 500; cursor: pointer;
transition: all 0.3s ease; text-align: center;
}
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
.btn--primary { background: #667eea; color: white; }
.btn--primary:hover:not(:disabled) { background: #5a6fd8; transform: translateY(-2px); }
.btn--success { background: #28a745; color: white; }
.btn--success:hover:not(:disabled) { background: #218838; transform: translateY(-2px); }
.btn--warning { background: #ffc107; color: #212529; }
.btn--warning:hover:not(:disabled) { background: #e0a800; transform: translateY(-2px); }
.btn--info { background: #17a2b8; color: white; }
.btn--info:hover:not(:disabled) { background: #138496; transform: translateY(-2px); }
.btn--secondary { background: #6c757d; color: white; }
.btn--secondary:hover:not(:disabled) { background: #545b62; transform: translateY(-2px); }
.btn--small { padding: 6px 12px; font-size: 12px; }
.message-card {
background: rgba(255, 255, 255, 0.95); border-radius: 12px;
padding: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border-left: 4px solid #667eea;
}
.message-card__content { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.message-card__text { color: #333; font-size: 14px; margin: 0; flex: 1; }
.message-card__close {
background: none; border: none; font-size: 20px; color: #999;
cursor: pointer; padding: 0; width: 24px; height: 24px;
display: flex; align-items: center; justify-content: center;
border-radius: 4px; transition: all 0.3s ease;
}
.message-card__close:hover { background: #f8f9fa; color: #333; }
.form-group--checkbox { flex-direction: row; align-items: center; }
.checkbox-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.checkbox-input { width: 16px; height: 16px; }
.checkbox-text { color: #555; font-size: 14px; font-weight: 500; }
.logs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.logs-content {
max-height: 400px; overflow-y: auto;
border: 1px solid #e1e5e9; border-radius: 8px; background: #f8f9fa;
}
.logs-empty { padding: 20px; text-align: center; color: #999; font-style: italic; }
.logs-list { padding: 8px; }
.log-entry {
display: grid; grid-template-columns: auto auto auto 1fr; gap: 8px;
padding: 4px 8px; font-family: 'Courier New', monospace; font-size: 12px;
border-bottom: 1px solid #e9ecef;
}
.log-entry:last-child { border-bottom: none; }
.log-entry--info { color: #0066cc; }
.log-entry--warn { color: #ff8800; }
.log-entry--error { color: #cc0000; }
.log-entry--debug { color: #666; }
.log-time { color: #666; font-weight: 500; }
.log-category { color: #0066cc; font-weight: 600; }
.log-level { font-weight: 600; text-transform: uppercase; }
.log-message { word-break: break-word; }
@media (max-width: 768px) {
.app { padding: 16px; }
.app__header { flex-direction: column; gap: 12px; text-align: center; }
.actions-grid { grid-template-columns: 1fr; }
.log-entry { grid-template-columns: 1fr; gap: 4px; }
.logs-content { max-height: 300px; }
}
</style>
+71
View File
@@ -0,0 +1,71 @@
<script setup>
import { ref, onMounted } from 'vue'
import { invoke } from '@tauri-apps/api/core'
const showSettings = ref(true)
const username = ref('')
const password = ref('')
// 页面加载时尝试从存储中读取配置
onMounted(async () => {
try {
const config = await invoke('load_config')
if (config && config.username) {
username.value = config.username
password.value = config.password || ''
}
} catch (error) {
console.log('No saved config found.')
}
})
// 保存设置并登录
async function saveAndLogin() {
try {
await invoke('save_config', {
config: { username: username.value, password: password.value }
})
await invoke('start_monitoring') // 开始轮询
showSettings.value = false // 隐藏设置界面
await invoke('minimize_to_tray') // 最小化到托盘
} catch (error) {
alert('保存失败: ' + error)
}
}
</script>
<template>
<div v-if="showSettings" class="settings">
<h2>登录设置</h2>
<div>
<label>用户名:</label>
<input v-model="username" type="text" />
</div>
<div>
<label>密码:</label>
<input v-model="password" type="password" />
</div>
<button @click="saveAndLogin">登录</button>
</div>
<div v-else class="main">
<h2>正在监控...</h2>
<p>程序已最小化到系统托盘</p>
<button @click="() => showSettings = true">重新打开设置</button>
</div>
</template>
<style>
.settings, .main {
padding: 20px;
font-family: Arial, sans-serif;
}
input {
margin: 5px 0;
padding: 8px;
width: 200px;
}
button {
margin-top: 10px;
padding: 10px 20px;
}
</style>