Files
Nexus/web/app/preview-v5.html
T
Nexus Deploy a2ae74d582 release: BUG fixes batch 1-6, full bug scan docs, Ubuntu/Linux dev
- Backend: auth refresh reuse, schedule/retry/sync/agent/files fixes
- Frontend: push WS, files ETag browse, vite build assets
- Docs: audit/changelog, production deploy gate, bug scan registry B7-77
- Deploy: deploy-production.sh, prune assets, gate logs
2026-06-04 14:01:14 +08:00

376 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Nexus — Vuetify 风格预览</title>
<script src="/app/vendor/tailwindcss-browser.js"></script>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* ═══ Vuetify Material 配色 ═══ */
:root {
--v-primary: #1867C0;
--v-secondary: #5CBBF6;
--v-accent: #E7810D;
--v-error: #972800;
--v-info: #3462E3;
--v-success: #356859;
--v-warning: #E7810D;
--v-surface: #FFFFFF;
--v-surface-variant: #F5F5F5;
--v-background: #FAFAFA;
--v-on-surface: #212121;
--v-on-surface-variant: #616161;
--v-on-surface-muted: #9E9E9E;
--v-border: rgba(0,0,0,.12);
--v-divider: rgba(0,0,0,.08);
--v-radius: 12px;
--v-radius-sm: 8px;
}
:root.dark {
--v-surface: #1E1E1E;
--v-surface-variant: #2D2D2D;
--v-background: #121212;
--v-on-surface: #FFFFFF;
--v-on-surface-variant: #BDBDBD;
--v-on-surface-muted: #757575;
--v-border: rgba(255,255,255,.12);
--v-divider: rgba(255,255,255,.08);
}
body {
background: var(--v-background); color: var(--v-on-surface);
font-family: Roboto,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
-webkit-font-smoothing: antialiased; font-size: 14px;
}
.scrollbar::-webkit-scrollbar{width:6px}
.scrollbar::-webkit-scrollbar-thumb{background:var(--v-on-surface-muted);border-radius:3px}
/* ═══ Material Elevation ═══ */
.elevation-1{box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.06)}
.elevation-2{box-shadow:0 2px 4px rgba(0,0,0,.12),0 1px 4px rgba(0,0,0,.08)}
.elevation-4{box-shadow:0 4px 8px rgba(0,0,0,.14),0 2px 8px rgba(0,0,0,.1)}
.dark .elevation-1{box-shadow:0 1px 3px rgba(0,0,0,.4)}
.dark .elevation-2{box-shadow:0 2px 6px rgba(0,0,0,.5)}
.dark .elevation-4{box-shadow:0 4px 12px rgba(0,0,0,.6)}
/* ═══ Sidebar ═══ */
.nav-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--v-on-surface-muted);padding:8px 16px 4px}
.nav-item{
display:flex;align-items:center;gap:12px;padding:10px 16px;margin:0 4px;border-radius:var(--v-radius-sm);
font-size:14px;font-weight:500;color:var(--v-on-surface-variant);
transition:all .2s;cursor:pointer;text-decoration:none;position:relative;
}
.nav-item:hover{background:rgba(24,103,192,.08)}
.nav-item.active{color:var(--v-primary);background:rgba(24,103,192,.12)}
.nav-item.active::before{content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:60%;border-radius:0 3px 3px 0;background:var(--v-primary)}
/* ═══ Buttons ═══ */
.v-btn{
display:inline-flex;align-items:center;justify-content:center;gap:8px;
height:40px;padding:0 20px;border-radius:var(--v-radius-sm);font-size:14px;font-weight:500;
letter-spacing:.02em;transition:all .2s;cursor:pointer;border:none;
text-transform:none;position:relative;overflow:hidden;
}
.v-btn::after{content:'';position:absolute;inset:0;background:currentColor;opacity:0;transition:opacity .2s}
.v-btn:hover::after{opacity:.08}
.v-btn:active::after{opacity:.12}
.v-btn-primary{background:var(--v-primary);color:#fff}.v-btn-primary:hover{box-shadow:0 2px 8px rgba(24,103,192,.4)}
.v-btn-tonal{background:rgba(24,103,192,.08);color:var(--v-primary)}.v-btn-tonal:hover{background:rgba(24,103,192,.12)}
.v-btn-outlined{border:1px solid var(--v-border);background:transparent;color:var(--v-on-surface)}
.v-btn-outlined:hover{border-color:var(--v-on-surface-muted)}
.v-btn-text{background:transparent;color:var(--v-on-surface-variant)}.v-btn-text:hover{background:var(--v-surface-variant)}
.v-btn-sm{height:32px;padding:0 12px;font-size:12px;border-radius:6px}
/* ═══ Inputs ═══ */
.v-input{
height:40px;padding:0 14px;border-radius:var(--v-radius-sm);font-size:14px;
border:1px solid var(--v-border);background:var(--v-surface);color:var(--v-on-surface);
outline:none;transition:all .2s;width:100%;
}
.v-input:hover{border-color:var(--v-on-surface-muted)}
.v-input:focus{border-color:var(--v-primary);border-width:2px;padding:0 13px}
.v-input::placeholder{color:var(--v-on-surface-muted)}
select.v-input{-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24'%3E%3Cpath fill='%239E9E9E' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:36px}
/* ═══ Cards ═══ */
.v-card{
background:var(--v-surface);border-radius:var(--v-radius);border:1px solid var(--v-divider);
overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.06);
}
.v-card-header{padding:16px 20px;border-bottom:1px solid var(--v-divider);font-size:14px;font-weight:500;color:var(--v-on-surface)}
.v-card-body{padding:20px}
.v-card-actions{padding:12px 20px;border-top:1px solid var(--v-divider);display:flex;justify-content:flex-end;gap:8px}
/* ═══ Table ═══ */
.v-table{width:100%;border-collapse:collapse}
.v-table thead{background:var(--v-surface-variant)}
.v-table th{
padding:12px 20px;text-align:left;font-size:12px;font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--v-on-surface-variant);border-bottom:1px solid var(--v-divider);height:48px;
}
.v-table td{
padding:14px 20px;border-bottom:1px solid var(--v-divider);color:var(--v-on-surface);height:52px;
}
.v-table tr:last-child td{border-bottom:none}
.v-table tbody tr:hover{background:rgba(24,103,192,.04)}
/* ═══ Badges ═══ */
.v-chip{
display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:16px;
font-size:12px;font-weight:500;white-space:nowrap;
}
.v-chip-success{background:rgba(53,104,89,.12);color:var(--v-success)}
.v-chip-error{background:rgba(151,40,0,.12);color:var(--v-error)}
.v-chip-muted{background:var(--v-surface-variant);color:var(--v-on-surface-muted)}
.v-chip-primary{background:rgba(24,103,192,.08);color:var(--v-primary)}
.v-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.v-dot-success{background:var(--v-success)}
.v-dot-error{background:var(--v-error)}
.v-dot-muted{background:var(--v-on-surface-muted)}
.v-dot-error-pulse{animation:v-pulse 2s infinite}
@keyframes v-pulse{0%,100%{opacity:1}50%{opacity:.3}}
.v-link{font-size:13px;color:var(--v-primary);text-decoration:none;cursor:pointer;background:none;border:none;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s}
.v-link:hover{background:rgba(24,103,192,.08)}
.v-link-muted{color:var(--v-on-surface-variant)}.v-link-muted:hover{color:var(--v-on-surface)}
.v-link-error{color:var(--v-error)}.v-link-error:hover{background:rgba(151,40,0,.08)}
/* ═══ Tabs ═══ */
.v-tabs{display:flex;border-bottom:1px solid var(--v-divider)}
.v-tab{
padding:12px 20px;font-size:13px;font-weight:500;color:var(--v-on-surface-variant);
border-bottom:2px solid transparent;transition:all .2s;cursor:pointer;background:none;border-top:none;border-left:none;border-right:none;letter-spacing:.02em;
}
.v-tab:hover{color:var(--v-on-surface);background:var(--v-surface-variant)}
.v-tab.active{color:var(--v-primary);border-bottom-color:var(--v-primary);font-weight:600}
/* ═══ Pagination ═══ */
.v-pagination{display:flex;align-items:center;gap:4px}
.v-page-btn{min-width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;border:none;background:transparent;color:var(--v-on-surface-variant);font-size:13px;font-weight:500;cursor:pointer;transition:all .15s}
.v-page-btn:hover{background:var(--v-surface-variant)}
.v-page-btn.active{background:var(--v-primary);color:#fff;box-shadow:0 2px 6px rgba(24,103,192,.3)}
.v-page-btn:disabled{opacity:.3;cursor:default}
/* ═══ Dialog ═══ */
.v-overlay{position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.5);opacity:0;visibility:hidden;transition:all .2s}
.v-overlay.open{opacity:1;visibility:visible}
.v-dialog{background:var(--v-surface);border-radius:var(--v-radius);width:100%;max-width:460px;box-shadow:0 11px 15px rgba(0,0,0,.2),0 24px 38px rgba(0,0,0,.14);overflow:hidden}
.v-dialog-title{padding:20px 24px 0;font-size:16px;font-weight:500;color:var(--v-on-surface)}
.v-dialog-text{padding:16px 24px;font-size:14px;color:var(--v-on-surface-variant);line-height:1.6}
.v-dialog-actions{padding:16px 24px 20px;display:flex;justify-content:flex-end;gap:8px}
/* ═══ Toast ═══ */
.v-snackbar{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none}
.v-snackbar-item{
pointer-events:auto;display:flex;align-items:center;gap:12px;padding:10px 20px;border-radius:var(--v-radius-sm);
font-size:13px;background:var(--v-surface);color:var(--v-on-surface);
box-shadow:0 4px 12px rgba(0,0,0,.25);max-width:400px;
}
.v-snackbar-success{border-left:3px solid var(--v-success)}
.v-snackbar-error{border-left:3px solid var(--v-error)}
.v-snackbar-warning{border-left:3px solid var(--v-warning)}
/* ═══ Stat Cards ═══ */
.v-stat{padding:20px 24px}
.v-stat-label{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--v-on-surface-muted);margin-bottom:8px}
.v-stat-value{font-size:34px;font-weight:700;line-height:1;letter-spacing:-.02em}
.v-stat-sub{font-size:12px;color:var(--v-on-surface-muted);margin-top:4px}
.slide-up{animation:v-slide-up .25s ease}
@keyframes v-slide-up{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
.v-chip-group{display:flex;gap:6px;flex-wrap:wrap}
</style>
</head>
<body class="flex min-h-screen">
<div class="v-snackbar" id="toastContainer"></div>
<!-- ═══ NAVIGATION DRAWER ═══ -->
<aside class="w-60 shrink-0 flex flex-col border-r scrollbar" style="background:var(--v-surface);border-color:var(--v-divider)">
<div class="px-4 py-4 border-b" style="border-color:var(--v-divider)">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg flex items-center justify-center" style="background:var(--v-primary)">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/></svg>
</div>
<span class="font-bold text-base tracking-tight" style="color:var(--v-on-surface)">Nexus</span>
</div>
</div>
<nav class="flex-1 py-3 overflow-y-auto">
<div class="nav-label">主菜单</div>
<a class="nav-item active"><span class="v-chip v-chip-primary" style="padding:2px 6px;font-size:11px">H</span>仪表盘</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">S</span>服务器</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">F</span>文件管理</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">P</span>推送</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">C</span>脚本库</a>
<div style="border-top:1px solid var(--v-divider);margin:8px 16px"></div>
<div class="nav-label">系统</div>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">L</span>命令日志</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">A</span>告警中心</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">U</span>审计日志</a>
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">G</span>设置</a>
</nav>
<div style="border-top:1px solid var(--v-divider);padding:12px 16px">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold text-white" style="background:var(--v-primary)">A</div>
<div><div class="text-xs font-medium" style="color:var(--v-on-surface)">admin</div><div class="text-[10px]" style="color:var(--v-on-surface-muted)">管理员</div></div>
</div>
<button onclick="document.documentElement.classList.toggle('dark')" class="v-btn v-btn-text" style="width:36px;height:36px;padding:0;border-radius:50%" title="切换主题">
<svg style="width:18px;height:18px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"/></svg>
</button>
</div>
</div>
</aside>
<!-- ═══ MAIN ═══ -->
<div class="flex-1 flex flex-col min-w-0">
<header class="h-14 shrink-0 flex items-center justify-between px-6 border-b" style="background:var(--v-surface);border-color:var(--v-divider)">
<div class="flex items-center gap-4">
<h1 class="text-base font-medium tracking-tight" style="color:var(--v-on-surface)">仪表盘</h1>
</div>
<div class="flex items-center gap-3">
<select class="v-input" style="width:140px"><option>全部分类</option><option>商城</option></select>
<button class="v-btn v-btn-tonal v-btn-sm" onclick="showToast('success','数据已刷新')">刷新</button>
</div>
</header>
<main class="flex-1 overflow-y-auto p-6 space-y-6 scrollbar">
<!-- ═══ STATS ═══ -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div class="v-card v-stat"><div class="v-stat-label">服务器总数</div><div class="v-stat-value" style="color:var(--v-primary)">8</div><div class="v-stat-sub">+2 本月新增</div></div>
<div class="v-card v-stat"><div class="v-stat-label">在线</div><div class="v-stat-value" style="color:var(--v-success)">1</div><div class="v-stat-sub">12.5% 在线率</div></div>
<div class="v-card v-stat"><div class="v-stat-label">离线</div><div class="v-stat-value" style="color:var(--v-error)">7</div><div class="v-stat-sub">需要关注</div></div>
<div class="v-card v-stat"><div class="v-stat-label">告警中</div><div class="v-stat-value" style="color:var(--v-warning)">0</div><div class="v-stat-sub">一切正常</div></div>
</div>
<!-- ═══ TABLE ═══ -->
<div class="v-card">
<div class="v-card-header">服务器列表</div>
<div style="overflow-x:auto">
<table class="v-table">
<thead><tr><th>状态</th><th>名称</th><th>地址</th><th>分类</th><th>Agent</th><th>最后心跳</th><th style="text-align:right">操作</th></tr></thead>
<tbody>
<tr>
<td><span class="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span></td>
<td><span style="font-weight:500">prod-web-01</span></td>
<td style="color:var(--v-on-surface-variant)">192.168.1.10:22</td>
<td><span class="v-chip v-chip-primary">商城</span></td>
<td style="color:var(--v-on-surface-variant)">v1.2.0</td>
<td style="color:var(--v-on-surface-muted);font-size:13px">3分钟前</td>
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
</tr>
<tr>
<td><span class="v-chip v-chip-error"><span class="v-dot v-dot-error v-dot-error-pulse"></span>离线</span></td>
<td><span style="font-weight:500">dev-db-01</span></td>
<td style="color:var(--v-on-surface-variant)">10.0.0.5:22</td>
<td style="color:var(--v-on-surface-muted)"></td>
<td style="color:var(--v-on-surface-variant)">v1.1.8</td>
<td style="color:var(--v-on-surface-muted);font-size:13px">2小时前</td>
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
</tr>
<tr>
<td><span class="v-chip v-chip-muted"><span class="v-dot v-dot-muted"></span>未安装</span></td>
<td><span style="font-weight:500">new-server-03</span></td>
<td style="color:var(--v-on-surface-variant)">172.16.0.8:22</td>
<td style="color:var(--v-on-surface-muted)"></td>
<td style="color:var(--v-on-surface-muted)"></td>
<td style="color:var(--v-on-surface-muted);font-size:13px"></td>
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
</tr>
<tr>
<td><span class="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span></td>
<td><span style="font-weight:500">cache-redis-01</span></td>
<td style="color:var(--v-on-surface-variant)">10.0.0.10:6379</td>
<td><span class="v-chip v-chip-primary">数据库</span></td>
<td style="color:var(--v-on-surface-variant)">v1.2.0</td>
<td style="color:var(--v-on-surface-muted);font-size:13px">1分钟前</td>
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
</tr>
</tbody>
</table>
</div>
<div class="v-card-actions">
<span style="color:var(--v-on-surface-muted);font-size:12px;margin-right:auto">每页 50 条 · 共 8 台</span>
<div class="v-pagination">
<button class="v-page-btn" disabled></button>
<button class="v-page-btn active">1</button>
<button class="v-page-btn">2</button>
<button class="v-page-btn"></button>
</div>
</div>
</div>
<!-- ═══ FORMS + COMPONENTS ═══ -->
<div class="grid grid-cols-2 gap-4">
<div class="v-card">
<div class="v-card-header">添加服务器</div>
<div class="v-card-body space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">名称 *</label><input class="v-input" placeholder="web-server-01"></div>
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">地址 *</label><input class="v-input" placeholder="192.168.1.10"></div>
</div>
<div class="grid grid-cols-3 gap-4">
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">端口</label><input class="v-input" value="22"></div>
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">用户</label><input class="v-input" value="root"></div>
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">认证</label><select class="v-input"><option>密码</option><option>密钥</option></select></div>
</div>
</div>
<div class="v-card-actions">
<button class="v-btn v-btn-primary v-btn-sm" onclick="showToast('success','服务器已添加')">保存</button>
<button class="v-btn v-btn-text v-btn-sm">取消</button>
</div>
</div>
<div class="v-card">
<div class="v-card-header">组件展示</div>
<div class="v-card-body space-y-5">
<div class="v-chip-group"><button class="v-btn v-btn-primary v-btn-sm">主要</button><button class="v-btn v-btn-tonal v-btn-sm">色调</button><button class="v-btn v-btn-outlined v-btn-sm">描边</button><button class="v-btn v-btn-text v-btn-sm">文本</button></div>
<div style="height:1px;background:var(--v-divider)"></div>
<div class="v-tabs"><button class="v-tab active">系统信息</button><button class="v-tab">同步日志</button><button class="v-tab">SSH 会话</button></div>
<div style="height:1px;background:var(--v-divider)"></div>
<div class="v-chip-group">
<span class="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span>
<span class="v-chip v-chip-error"><span class="v-dot v-dot-error v-dot-error-pulse"></span>离线</span>
<span class="v-chip v-chip-muted"><span class="v-dot v-dot-muted"></span>未安装</span>
</div>
<div style="height:1px;background:var(--v-divider)"></div>
<label class="flex items-center gap-3 cursor-pointer" style="color:var(--v-on-surface)">
<input type="checkbox" checked style="width:18px;height:18px;accent-color:var(--v-primary)"> 全选服务器
</label>
<div style="height:1px;background:var(--v-divider)"></div>
<button class="v-btn v-btn-outlined v-btn-sm" onclick="document.getElementById('demoDialog').classList.add('open')">打开删除确认对话框</button>
</div>
</div>
</div>
</main>
</div>
<!-- ═══ DIALOG ═══ -->
<div class="v-overlay" id="demoDialog" onclick="if(event.target===this)this.classList.remove('open')">
<div class="v-dialog slide-up">
<div class="v-dialog-title">确认删除</div>
<div class="v-dialog-text">您确定要删除服务器 <strong style="color:var(--v-on-surface)">prod-web-01</strong> 吗?此操作无法撤销。</div>
<div class="v-dialog-actions">
<button class="v-btn v-btn-text v-btn-sm" onclick="document.getElementById('demoDialog').classList.remove('open')">取消</button>
<button class="v-btn v-btn-primary v-btn-sm" style="background:var(--v-error)" onclick="document.getElementById('demoDialog').classList.remove('open');showToast('success','服务器已删除')">删除</button>
</div>
</div>
</div>
<script>
function showToast(type,msg){
const icons={success:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#356859" d="M21 7L9 19l-5.5-5.5 1.41-1.41L9 16.17 19.59 5.59z"/></svg>',error:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#972800" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></svg>',warning:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#E7810D" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'};
const el=document.createElement('div');
el.className='v-snackbar-item v-snackbar-'+type+' slide-up';
el.innerHTML=(icons[type]||icons.success)+'<span style="flex:1">'+msg+'</span><button onclick="this.parentElement.remove()" class="v-btn v-btn-text" style="width:28px;height:28px;padding:0;border-radius:50%">×</button>';
document.getElementById('toastContainer').appendChild(el);
setTimeout(()=>{el.style.opacity='0';el.style.transition='opacity .2s';setTimeout(()=>el.remove(),200)},3500);
}
</script>
</body>
</html>