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
+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>
);
},