feat: 运营平台基建 搭建

This commit is contained in:
2026-07-23 15:00:19 +08:00
parent 3b5a908ee4
commit 5523695518
11 changed files with 1096 additions and 130 deletions
+798 -12
View File
@@ -1,20 +1,806 @@
// ── 登录页 — 左右分栏 — 动画背景版 ──
// 左侧:深色品牌展示区,含浮动光球、网格呼吸、脉冲涟漪、粒子点阵等多层动画
// 右侧:白色登录表单区,入场渐显动画
.container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
}
.card {
width: 400px;
max-width: 90vw;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
// ━━━━ 左侧品牌展示区 ━━━━
.brand {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: linear-gradient(160deg, #0c1e3a 0%, #132744 50%, #0a1628 100%);
color: #fff;
overflow: hidden;
padding: 40px 56px 40px 64px;
}
:global(.ant-card-head-title) {
text-align: center;
font-size: 18px;
font-weight: 600;
// ── 动画层1: 浮动光球 ──
.orb1 {
position: absolute;
width: 460px;
height: 460px;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(22, 119, 255, 0.14) 0%,
rgba(22, 119, 255, 0.04) 40%,
transparent 70%
);
top: -100px;
right: 20%;
z-index: 0;
animation: orbFloat1 12s ease-in-out infinite;
filter: blur(2px);
}
.orb2 {
position: absolute;
width: 360px;
height: 360px;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(82, 196, 26, 0.1) 0%,
rgba(82, 196, 26, 0.03) 40%,
transparent 70%
);
bottom: 40px;
left: 10%;
z-index: 0;
animation: orbFloat2 15s ease-in-out infinite;
filter: blur(2px);
}
.orb3 {
position: absolute;
width: 280px;
height: 280px;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(250, 173, 20, 0.08) 0%,
rgba(250, 173, 20, 0.02) 40%,
transparent 70%
);
top: 50%;
left: 55%;
z-index: 0;
animation: orbFloat3 18s ease-in-out infinite;
filter: blur(3px);
}
// ── 动画层2: 脉冲涟漪 ──
.pulse {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
border: 1px solid rgba(22, 119, 255, 0.15);
top: 30%;
right: 15%;
z-index: 0;
animation: pulseExpand 4s ease-out infinite;
&::before {
content: '';
position: absolute;
inset: 30px;
border-radius: 50%;
border: 1px solid rgba(22, 119, 255, 0.08);
animation: pulseExpand 4s ease-out infinite 1s;
}
&::after {
content: '';
position: absolute;
inset: 60px;
border-radius: 50%;
border: 1px solid rgba(22, 119, 255, 0.04);
animation: pulseExpand 4s ease-out infinite 2s;
}
}
// ── 动画层3: 网格呼吸 ──
.brandGrid {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
background-image:
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
animation: gridBreathe 6s ease-in-out infinite;
}
// ── 动画层4: 粒子点阵 ──
.particles {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.particle {
position: absolute;
width: 3px;
height: 3px;
border-radius: 50%;
background: rgba(22, 119, 255, 0.5);
animation: particleFloat 8s ease-in-out infinite;
&::before {
content: '';
position: absolute;
width: 2px;
height: 2px;
border-radius: 50%;
background: rgba(105, 177, 255, 0.3);
top: -8px;
left: 3px;
animation: particleFloat 6s ease-in-out infinite reverse;
}
}
// ── 动画层5: 扫描线 ──
.scanLine {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(22, 119, 255, 0.3), transparent);
z-index: 1;
animation: scanDown 8s linear infinite;
}
// ━━━━ 动画定义 ━━━━
@keyframes orbFloat1 {
0%,
100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(30px, 20px) scale(1.05);
}
50% {
transform: translate(-20px, 40px) scale(0.95);
}
75% {
transform: translate(10px, -30px) scale(1.02);
}
}
@keyframes orbFloat2 {
0%,
100% {
transform: translate(0, 0) scale(1);
}
33% {
transform: translate(-40px, -20px) scale(1.08);
}
66% {
transform: translate(30px, 30px) scale(0.92);
}
}
@keyframes orbFloat3 {
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(-50px, -40px);
}
}
@keyframes pulseExpand {
0% {
transform: scale(0.8);
opacity: 0.6;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
@keyframes gridBreathe {
0%,
100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
@keyframes particleFloat {
0%,
100% {
transform: translateY(0) translateX(0);
opacity: 0.5;
}
25% {
transform: translateY(-30px) translateX(10px);
opacity: 1;
}
50% {
transform: translateY(-60px) translateX(-5px);
opacity: 0.3;
}
75% {
transform: translateY(-30px) translateX(15px);
opacity: 0.8;
}
}
@keyframes scanDown {
0% {
top: -2px;
}
100% {
top: 100%;
}
}
// ━━━━ 内容层 ━━━━
.brandInner {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
flex: 1;
animation: fadeInUp 0.8s ease-out both;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// ── Logo ──
.logoRow {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 48px;
}
.logoMark {
width: 48px;
height: 48px;
border-radius: 14px;
background: linear-gradient(135deg, #1677ff, #69b1ff);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 800;
color: #fff;
box-shadow: 0 4px 20px rgba(22, 119, 255, 0.45);
animation: logoPulse 3s ease-in-out infinite;
position: relative;
&::after {
content: '';
position: absolute;
top: 4px;
left: 4px;
width: 18px;
height: 7px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.3);
}
}
@keyframes logoPulse {
0%,
100% {
box-shadow: 0 4px 20px rgba(22, 119, 255, 0.45);
}
50% {
box-shadow:
0 4px 30px rgba(22, 119, 255, 0.7),
0 0 60px rgba(22, 119, 255, 0.2);
}
}
.logoText {
font-size: 18px;
font-weight: 600;
letter-spacing: 2px;
color: rgba(255, 255, 255, 0.9);
}
// ── 大标题 ──
.brandTitle {
font-size: 32px;
font-weight: 700;
line-height: 1.35;
letter-spacing: 1px;
margin-bottom: 12px;
animation: fadeInUp 0.8s ease-out 0.1s both;
}
.brandSubtitle {
font-size: 15px;
line-height: 1.7;
color: rgba(255, 255, 255, 0.55);
margin-bottom: 40px;
max-width: 400px;
animation: fadeInUp 0.8s ease-out 0.2s both;
}
// ── 数据统计条 ──
.statsRow {
display: flex;
gap: 36px;
margin-bottom: 44px;
animation: fadeInUp 0.8s ease-out 0.3s both;
}
.statItem {
display: flex;
flex-direction: column;
gap: 4px;
}
.statValue {
font-size: 28px;
font-weight: 700;
color: #fff;
letter-spacing: 1px;
}
.statLabel {
font-size: 12px;
color: rgba(255, 255, 255, 0.45);
letter-spacing: 1px;
}
.statHighlight {
color: #69b1ff;
}
// ── 赛事模拟卡片 ──
.eventCards {
display: flex;
gap: 16px;
margin-bottom: 44px;
animation: fadeInUp 0.8s ease-out 0.4s both;
}
.eventCard {
flex: 1;
padding: 20px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
backdrop-filter: blur(8px);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
// 内部光效装饰
&::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 30%, rgba(22, 119, 255, 0.06) 0%, transparent 50%);
animation: cardShimmer 5s linear infinite;
pointer-events: none;
}
&:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(22, 119, 255, 0.3);
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(22, 119, 255, 0.15);
}
}
@keyframes cardShimmer {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.eventIcon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: #fff;
margin-bottom: 12px;
position: relative;
z-index: 1;
&.blue {
background: linear-gradient(135deg, #1677ff, #4096ff);
box-shadow: 0 2px 12px rgba(22, 119, 255, 0.3);
}
&.green {
background: linear-gradient(135deg, #52c41a, #73d13d);
box-shadow: 0 2px 12px rgba(82, 196, 26, 0.3);
}
&.orange {
background: linear-gradient(135deg, #fa8c16, #ffa940);
box-shadow: 0 2px 12px rgba(250, 140, 22, 0.3);
}
}
.eventTitle {
font-size: 15px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 6px;
position: relative;
z-index: 1;
}
.eventDesc {
font-size: 12px;
color: rgba(255, 255, 255, 0.45);
line-height: 1.5;
position: relative;
z-index: 1;
}
// ── 步骤流程 ──
.flowSteps {
display: flex;
align-items: center;
gap: 0;
animation: fadeInUp 0.8s ease-out 0.5s both;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
}
// ── 星点效果 ──
// 中心发光点 + 四条十字光芒 + 缓慢旋转 + 闪烁呼吸
.stepDot {
position: relative;
width: 20px;
height: 20px;
margin-bottom: 12px;
// 中心核心圆点
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background: #fff;
box-shadow:
0 0 4px 2px rgba(22, 119, 255, 0.8),
0 0 12px 4px rgba(22, 119, 255, 0.5),
0 0 24px 8px rgba(22, 119, 255, 0.2);
transform: translate(-50%, -50%);
animation: starCorePulse 2s ease-in-out infinite;
}
// 十字光芒线(横+纵)
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
transform: translate(-50%, -50%) rotate(45deg);
animation:
starSpin 6s linear infinite,
starGlowPulse 2s ease-in-out infinite;
// 横向光芒
background:
linear-gradient(
0deg,
transparent 45%,
rgba(22, 119, 255, 0.9) 48%,
#fff 50%,
rgba(22, 119, 255, 0.9) 52%,
transparent 55%
),
linear-gradient(
90deg,
transparent 45%,
rgba(22, 119, 255, 0.9) 48%,
#fff 50%,
rgba(22, 119, 255, 0.9) 52%,
transparent 55%
);
}
}
// 步骤之间的连线
.stepLine {
flex: 1;
height: 1px;
min-width: 20px;
background: linear-gradient(90deg, rgba(22, 119, 255, 0.5), rgba(22, 119, 255, 0.15));
align-self: center;
margin-top: -6px; // 微调与星点中心对齐(因为label占空间)
}
@keyframes starCorePulse {
0%,
100% {
box-shadow:
0 0 4px 2px rgba(22, 119, 255, 0.8),
0 0 12px 4px rgba(22, 119, 255, 0.5),
0 0 24px 8px rgba(22, 119, 255, 0.2);
}
50% {
box-shadow:
0 0 6px 3px rgba(22, 119, 255, 1),
0 0 20px 6px rgba(22, 119, 255, 0.7),
0 0 40px 12px rgba(22, 119, 255, 0.3);
}
}
@keyframes starSpin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
@keyframes starGlowPulse {
0%,
100% {
opacity: 0.7;
}
50% {
opacity: 1;
}
}
.stepLabel {
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
text-align: center;
}
// ── 左侧底部版权 ──
.brandFooter {
margin-top: auto;
font-size: 12px;
color: rgba(255, 255, 255, 0.2);
letter-spacing: 1px;
}
// ━━━━ 右侧登录区 ━━━━
.loginSide {
width: 460px;
min-width: 380px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 48px;
background: #fff;
position: relative;
}
.loginWrapper {
width: 100%;
max-width: 340px;
animation: fadeInRight 0.6s ease-out 0.3s both;
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.loginTitle {
font-size: 24px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 6px;
}
.loginDesc {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 36px;
}
.loginForm {
:global(.ant-input-affix-wrapper) {
padding: 10px 14px;
border-radius: 10px;
border-color: #d9d9d9;
transition: all 0.3s;
&:hover {
border-color: #1677ff;
}
&:focus-within {
border-color: #1677ff;
box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.08);
}
}
:global(.ant-input-prefix) {
color: rgba(0, 0, 0, 0.3);
margin-right: 8px;
}
:global(.ant-form-item) {
margin-bottom: 22px;
}
}
.loginBtn {
height: 46px;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
letter-spacing: 3px;
transition: all 0.3s;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 16px rgba(22, 119, 255, 0.35);
}
}
// ── 记住密码行 ──
.rememberRow {
margin-bottom: 8px;
:global(.ant-checkbox-wrapper) {
color: rgba(0, 0, 0, 0.55);
font-size: 13px;
}
:global(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: #1677ff;
border-color: #1677ff;
}
}
.loginFooter {
margin-top: 28px;
text-align: center;
font-size: 12px;
color: rgba(0, 0, 0, 0.25);
}
// ━━━━ 响应式 — PC 端自适应 ━━━━
// ── 大屏 (>1200px): 默认样式,无需额外处理 ──
// ── 中屏 (≤1200px): 缩减间距、光球尺寸、字号 ──
@media screen and (max-width: 1200px) {
.brand {
padding: 32px 36px 32px 40px;
}
.brandTitle {
font-size: 28px;
}
.brandSubtitle {
font-size: 14px;
margin-bottom: 32px;
}
.statsRow {
gap: 24px;
margin-bottom: 32px;
}
.statValue {
font-size: 24px;
}
.eventCards {
gap: 12px;
margin-bottom: 32px;
}
.eventCard {
padding: 16px;
}
.eventIcon {
width: 32px;
height: 32px;
margin-bottom: 10px;
}
.eventTitle {
font-size: 14px;
}
.eventDesc {
font-size: 11px;
}
.orb1 {
width: 360px;
height: 360px;
}
.orb2 {
width: 280px;
height: 280px;
}
.orb3 {
width: 200px;
height: 200px;
}
.loginSide {
width: 400px;
min-width: 340px;
padding: 36px 40px;
}
.loginWrapper {
max-width: 320px;
}
.loginTitle {
font-size: 22px;
}
.loginDesc {
font-size: 13px;
}
.stepLabel {
font-size: 12px;
}
}
+196 -37
View File
@@ -1,7 +1,8 @@
import { defineComponent, reactive, ref } from 'vue';
import { Button, Card, Form, FormItem, Input, message } from 'ant-design-vue';
import { Button, Checkbox, Form, FormItem, Input, message } from 'ant-design-vue';
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
import { auth } from '@/hooks/useAuth';
import { useEffect } from '@/hooks/useEffect';
import styles from './index.module.less';
interface LoginForm {
@@ -9,20 +10,31 @@ interface LoginForm {
password: string;
}
/**
* 登录页
* auth.login() 内部会自动:保存 token → 拉取菜单+权限 → 动态注册路由 → 跳转首页
*/
export default defineComponent({
name: 'LoginPage',
setup() {
const loading = ref(false);
const rememberMe = ref(false);
const form = reactive<LoginForm>({
username: 'admin',
password: '123456',
username: '',
password: '',
});
useEffect(() => {
const saved = localStorage.getItem('cpms_login_remember');
if (saved) {
try {
const data = JSON.parse(saved);
form.username = data.username || '';
form.password = data.password || '';
rememberMe.value = true;
} catch {
localStorage.removeItem('cpms_login_remember');
}
}
}, []);
const rules = {
username: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }],
@@ -37,14 +49,21 @@ export default defineComponent({
loading.value = true;
try {
// TODO: 替换为真实登录接口
// const res = await post<LoginResult>('/auth/login', { username: form.username, password: form.password });
// await auth.login(res.token);
// Mock:模拟登录
await new Promise((r) => setTimeout(r, 600));
await auth.login('mock_token_' + Date.now());
message.success('登录成功');
if (rememberMe.value) {
localStorage.setItem(
'cpms_login_remember',
JSON.stringify({
username: form.username,
password: form.password,
}),
);
} else {
localStorage.removeItem('cpms_login_remember');
}
} catch (err) {
message.error('登录失败,请重试');
} finally {
@@ -52,34 +71,174 @@ export default defineComponent({
}
};
const particles = [
{ top: '8%', left: '15%', delay: '0s', duration: '8s' },
{ top: '22%', left: '65%', delay: '1.5s', duration: '7s' },
{ top: '45%', left: '25%', delay: '0.8s', duration: '9s' },
{ top: '60%', left: '75%', delay: '2s', duration: '6.5s' },
{ top: '75%', left: '40%', delay: '3s', duration: '8.5s' },
{ top: '15%', left: '80%', delay: '1s', duration: '7.5s' },
{ top: '35%', left: '50%', delay: '2.5s', duration: '6s' },
{ top: '85%', left: '20%', delay: '0.5s', duration: '9.5s' },
{ top: '50%', left: '90%', delay: '3.5s', duration: '8s' },
{ top: '92%', left: '55%', delay: '1.2s', duration: '7s' },
];
return () => (
<div class={styles.container}>
<Card class={styles.card} title="CPMS 运营平台">
<Form model={form} rules={rules} layout="vertical" onFinish={handleSubmit}>
<FormItem name="username">
<Input
v-model={[form.username, 'value']}
placeholder="用户名"
size="large"
v-slots={{ prefix: () => <UserOutlined /> }}
<div class={styles.brand}>
<div class={styles.orb1} />
<div class={styles.orb2} />
<div class={styles.orb3} />
<div class={styles.pulse} />
<div class={styles.brandGrid} />
<div class={styles.scanLine} />
<div class={styles.particles}>
{particles.map((p, i) => (
<div
key={i}
class={styles.particle}
style={{
top: p.top,
left: p.left,
animationDelay: p.delay,
animationDuration: p.duration,
}}
/>
</FormItem>
<FormItem name="password">
<Input
v-model={[form.password, 'value']}
type="password"
placeholder="密码"
size="large"
v-slots={{ prefix: () => <LockOutlined /> }}
/>
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" size="large" block loading={loading.value}>
</Button>
</FormItem>
</Form>
</Card>
))}
</div>
<div class={styles.brandInner}>
<div class={styles.logoRow}>
<div class={styles.logoMark}>6</div>
<span class={styles.logoText}></span>
</div>
<h1 class={styles.brandTitle}></h1>
<p class={styles.brandSubtitle}>
</p>
<div class={styles.statsRow}>
<div class={styles.statItem}>
<span class={styles.statValue}>
<span class={styles.statHighlight}>1,200</span>+
</span>
<span class={styles.statLabel}></span>
</div>
<div class={styles.statItem}>
<span class={styles.statValue}>
<span class={styles.statHighlight}>50,000</span>+
</span>
<span class={styles.statLabel}></span>
</div>
<div class={styles.statItem}>
<span class={styles.statValue}>
<span class={styles.statHighlight}>100</span>%
</span>
<span class={styles.statLabel}></span>
</div>
</div>
<div class={styles.eventCards}>
<div class={styles.eventCard}>
<div class={[styles.eventIcon, styles.blue]}>📋</div>
<div class={styles.eventTitle}></div>
<div class={styles.eventDesc}>线</div>
</div>
<div class={styles.eventCard}>
<div class={[styles.eventIcon, styles.green]}>🏆</div>
<div class={styles.eventTitle}></div>
<div class={styles.eventDesc}></div>
</div>
<div class={styles.eventCard}>
<div class={[styles.eventIcon, styles.orange]}>📊</div>
<div class={styles.eventTitle}></div>
<div class={styles.eventDesc}></div>
</div>
</div>
<div class={styles.flowSteps}>
<div class={styles.step}>
<span class={styles.stepDot} />
<span class={styles.stepLabel}></span>
</div>
<div class={styles.stepLine} />
<div class={styles.step}>
<span class={styles.stepDot} />
<span class={styles.stepLabel}></span>
</div>
<div class={styles.stepLine} />
<div class={styles.step}>
<span class={styles.stepDot} />
<span class={styles.stepLabel}></span>
</div>
<div class={styles.stepLine} />
<div class={styles.step}>
<span class={styles.stepDot} />
<span class={styles.stepLabel}></span>
</div>
<div class={styles.stepLine} />
<div class={styles.step}>
<span class={styles.stepDot} />
<span class={styles.stepLabel}></span>
</div>
</div>
<div class={styles.brandFooter}> ©{new Date().getFullYear()}</div>
</div>
</div>
<div class={styles.loginSide}>
<div class={styles.loginWrapper}>
<h2 class={styles.loginTitle}></h2>
<p class={styles.loginDesc}></p>
<Form
class={styles.loginForm}
model={form}
rules={rules}
layout="vertical"
onFinish={handleSubmit}
>
<FormItem name="username">
<Input
v-model={[form.username, 'value']}
placeholder="请输入用户名"
size="large"
v-slots={{ prefix: () => <UserOutlined /> }}
/>
</FormItem>
<FormItem name="password">
<Input
v-model={[form.password, 'value']}
type="password"
placeholder="请输入密码"
size="large"
v-slots={{ prefix: () => <LockOutlined /> }}
/>
</FormItem>
{/* 记住密码 */}
<div class={styles.rememberRow}>
<Checkbox v-model={[rememberMe.value, 'checked']}></Checkbox>
</div>
<FormItem>
<Button
class={styles.loginBtn}
type="primary"
htmlType="submit"
size="large"
block
loading={loading.value}
>
</Button>
</FormItem>
</Form>
<div class={styles.loginFooter}> ©{new Date().getFullYear()}</div>
</div>
</div>
</div>
);
},
+1 -1
View File
@@ -10,7 +10,7 @@ export default defineComponent({
setup() {
const router = useRouter();
const goHome = () => router.push('/');
const goHome = () => router.push('/login');
return () => (
<Result