2026-07-14 10:31:17 +08:00
|
|
|
import { defineComponent, reactive, ref } from 'vue';
|
2026-07-23 15:00:19 +08:00
|
|
|
import { Button, Checkbox, Form, FormItem, Input, message } from 'ant-design-vue';
|
2026-07-14 10:31:17 +08:00
|
|
|
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
import { auth } from '@/hooks/useAuth';
|
2026-07-23 15:00:19 +08:00
|
|
|
import { useEffect } from '@/hooks/useEffect';
|
2026-07-14 10:31:17 +08:00
|
|
|
import styles from './index.module.less';
|
|
|
|
|
|
|
|
|
|
interface LoginForm {
|
|
|
|
|
username: string;
|
|
|
|
|
password: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
|
name: 'LoginPage',
|
|
|
|
|
setup() {
|
|
|
|
|
const loading = ref(false);
|
2026-07-23 15:00:19 +08:00
|
|
|
const rememberMe = ref(false);
|
2026-07-14 10:31:17 +08:00
|
|
|
|
|
|
|
|
const form = reactive<LoginForm>({
|
2026-07-23 15:00:19 +08:00
|
|
|
username: '',
|
|
|
|
|
password: '',
|
2026-07-14 10:31:17 +08:00
|
|
|
});
|
|
|
|
|
|
2026-07-23 15:00:19 +08:00
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-07-14 10:31:17 +08:00
|
|
|
const rules = {
|
|
|
|
|
username: [{ required: true, message: '请输入用户名' }],
|
|
|
|
|
password: [{ required: true, message: '请输入密码' }],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
if (!form.username || !form.password) {
|
|
|
|
|
message.warning('请输入用户名和密码');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
2026-07-23 11:27:33 +08:00
|
|
|
try {
|
|
|
|
|
await new Promise((r) => setTimeout(r, 600));
|
|
|
|
|
await auth.login('mock_token_' + Date.now());
|
2026-07-14 10:31:17 +08:00
|
|
|
message.success('登录成功');
|
2026-07-23 15:00:19 +08:00
|
|
|
|
|
|
|
|
if (rememberMe.value) {
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
'cpms_login_remember',
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
username: form.username,
|
|
|
|
|
password: form.password,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
localStorage.removeItem('cpms_login_remember');
|
|
|
|
|
}
|
2026-07-23 11:27:33 +08:00
|
|
|
} catch (err) {
|
|
|
|
|
message.error('登录失败,请重试');
|
|
|
|
|
} finally {
|
2026-07-14 10:31:17 +08:00
|
|
|
loading.value = false;
|
2026-07-23 11:27:33 +08:00
|
|
|
}
|
2026-07-14 10:31:17 +08:00
|
|
|
};
|
|
|
|
|
|
2026-07-23 15:00:19 +08:00
|
|
|
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' },
|
|
|
|
|
];
|
|
|
|
|
|
2026-07-14 10:31:17 +08:00
|
|
|
return () => (
|
|
|
|
|
<div class={styles.container}>
|
2026-07-23 15:00:19 +08:00
|
|
|
<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,
|
|
|
|
|
}}
|
2026-07-14 10:31:17 +08:00
|
|
|
/>
|
2026-07-23 15:00:19 +08:00
|
|
|
))}
|
|
|
|
|
</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>
|
2026-07-14 10:31:17 +08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|