Files
cpms_operation_platform/src/pages/login/index.tsx
T

253 lines
8.9 KiB
TypeScript
Raw Normal View History

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';
import { useUserStore } from '@/stores/userStore';
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));
const token = 'mock_token_' + Date.now();
// 写入账号信息到 userStore
const { setUser } = useUserStore();
setUser({ phone: form.username, nickname: form.username, token });
await auth.login(token);
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>
);
},
});