You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
6.2 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script setup lang="ts">
import { Close, Minus } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { mergeConfig } from "../host/config";
import { log } from "../host/logger";
import { closeWindow, minimizeWindow } from "../host/window";
import { applyServerIpToHttp } from "../utils/service";
const router = useRouter();
const serverIp = ref("");
const saving = ref(false);
const isValid = computed(() => serverIp.value.trim().length > 0);
/**
* 在窄窗口中使用居中消息,避免提示框被裁剪。
*/
function showMessage(
type: "success" | "warning" | "error",
message: string,
): void {
ElMessage({
type,
message,
offset: 52,
grouping: true,
customClass: "narrow-window-message",
});
}
/**
* 校验服务地址输入是否合法。
*/
function validateHostInput(raw: string): boolean {
const value = raw.trim();
if (!value) {
return false;
}
if (value.startsWith("http://") || value.startsWith("https://")) {
try {
const url = new URL(value);
return Boolean(url);
} catch {
return false;
}
}
return /^[\w.\-]+(?::\d+)?$/.test(value) || /^\d{1,3}(\.\d{1,3}){3}(?::\d+)?$/.test(value);
}
/**
* 保存服务地址并跳转登录页。
*/
async function handleSave(): Promise<void> {
const value = serverIp.value.trim();
if (!value) {
showMessage("warning", "请输入服务器 IP 或地址");
return;
}
if (!validateHostInput(value)) {
showMessage("warning", "地址格式不正确例如192.168.1.10 或 192.168.1.10:8845");
return;
}
saving.value = true;
try {
await mergeConfig({ server_ip: value });
applyServerIpToHttp(value);
await log("info", `已保存服务器地址: ${value}`);
showMessage("success", "保存成功");
await router.replace("/login");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showMessage("error", message || "保存失败");
await log("error", `保存服务器地址失败: ${message}`);
} finally {
saving.value = false;
}
}
function handleMinimizeClick(event: MouseEvent): void {
event.preventDefault();
event.stopPropagation();
void minimizeWindow();
}
function handleCloseClick(event: MouseEvent): void {
event.preventDefault();
event.stopPropagation();
void closeWindow();
}
</script>
<template>
<div class="login-container">
<div class="background-elements">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
</div>
<div class="login-header drag-region">
<button class="control-button" type="button" @click="handleMinimizeClick">
<el-icon class="control-icon">
<component :is="Minus" />
</el-icon>
</button>
<button class="control-button" type="button" @click="handleCloseClick">
<el-icon class="control-icon">
<component :is="Close" />
</el-icon>
</button>
</div>
<div class="login-main">
<div class="header-section drag-region">
<div class="app-info">
<h1 class="app-title">服务地址</h1>
<h2 class="app-subtitle">请先配置服务器 IP 或地址</h2>
</div>
</div>
<div class="form-section">
<div class="form-wrapper">
<div class="form-header">
<p class="form-subtitle">默认端口 8845支持 host:port 或完整 http(s) 地址</p>
</div>
<el-input
v-model="serverIp"
size="large"
clearable
placeholder="例如 192.168.1.10"
@keyup.enter="handleSave"
/>
<div class="form-actions">
<el-button
type="primary"
size="large"
class="login-button"
:loading="saving"
:disabled="!isValid"
@click="handleSave"
>
保存并进入登录
</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.login-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
position: relative;
padding: 0 16px 20px;
}
.drag-region {
-webkit-app-region: drag;
}
.background-elements {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
.circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
&.circle-1 {
width: 300px;
height: 300px;
top: -100px;
left: -100px;
}
&.circle-2 {
width: 200px;
height: 200px;
right: -80px;
bottom: -80px;
}
}
}
.login-header {
width: 100%;
height: 32px;
display: flex;
justify-content: flex-end;
align-items: center;
flex-shrink: 0;
}
.control-button {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: white;
border-radius: 4px;
-webkit-app-region: no-drag;
border: none;
background: transparent;
}
.control-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.control-icon {
font-size: 20px;
}
.login-main {
width: 100%;
max-width: 420px;
z-index: 1;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 16px;
}
.header-section {
text-align: center;
margin-bottom: 30px;
}
.app-title {
font-size: 28px;
font-weight: 700;
color: white;
margin-bottom: 8px;
}
.app-subtitle {
font-size: 16px;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
}
.form-wrapper {
background: rgba(255, 255, 255, 0.95);
border-radius: 5px;
padding: 28px 30px 26px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.form-header {
text-align: center;
margin-bottom: 25px;
}
.form-subtitle {
color: #666;
font-size: 13px;
}
.form-actions {
margin-top: 24px;
}
.login-button {
width: 100%;
}
:global(.narrow-window-message) {
min-width: 0 !important;
width: calc(100vw - 32px) !important;
left: 16px !important;
right: 16px !important;
}
</style>