refactor(web): 登录页改版,移除快速登录与记住密码
- 删除演示账号快捷填入、账号/密码预填值与"记住密码"(不再明文存 localStorage) - 进入登录页自动清理旧版本遗留的 kefu_saved_* 本地数据 - 改用 antd Form.Item 标准 label + 直挂 Input,修复自动填充值不进表单状态 导致点登录无反应的问题;支持 ?expired=1 过期提示
This commit is contained in:
@@ -124,6 +124,17 @@ input:-webkit-autofill:active {
|
||||
transition: background-color 50000s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.login-dark-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.login-form .ant-form-item .ant-form-item-label > label {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.login-dark-input,
|
||||
.login-dark-input.ant-input-affix-wrapper,
|
||||
.login-dark-input.ant-input {
|
||||
|
||||
+57
-142
@@ -1,72 +1,39 @@
|
||||
import { useState } from 'react'
|
||||
import { Form, Input, Button, message } from 'antd'
|
||||
import {
|
||||
UserOutlined, LockOutlined,
|
||||
SafetyCertificateOutlined, CrownOutlined, ArrowRightOutlined,
|
||||
CheckCircleFilled, CustomerServiceOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, Form, Input, message } from 'antd'
|
||||
import { UserOutlined, LockOutlined, ArrowRightOutlined, CheckCircleFilled } from '@ant-design/icons'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { useAuth } from '@/stores/auth'
|
||||
import { Logo, LogoIcon } from '@/components/common/Logo'
|
||||
|
||||
type DemoRole = {
|
||||
key: string
|
||||
title: string
|
||||
username: string
|
||||
pass: string
|
||||
icon: typeof CrownOutlined
|
||||
/** 历史版本遗留的记住密码/演示账号本地数据,进入登录页时统一清理 */
|
||||
function cleanupLegacyCredentials() {
|
||||
const keys = ['kefu_remember_me', 'kefu_saved_username', 'kefu_saved_password']
|
||||
keys.forEach(key => localStorage.removeItem(key))
|
||||
}
|
||||
|
||||
const demoRoles: DemoRole[] = [
|
||||
{ key: 'tenant', title: '租户管理', username: 'kefu_admin', pass: 'kefu_admin123', icon: CrownOutlined },
|
||||
{ key: 'admin', title: '平台管理', username: 'platform_admin', pass: 'kefu_admin123', icon: SafetyCertificateOutlined },
|
||||
{ key: 'agent', title: '坐席人员', username: 'agent1', pass: 'kefu_admin123', icon: CustomerServiceOutlined },
|
||||
]
|
||||
|
||||
const Login = () => {
|
||||
const [form] = Form.useForm()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedRole, setSelectedRole] = useState<string>('tenant')
|
||||
|
||||
// 记住密码状态持久化
|
||||
const [remember, setRemember] = useState<boolean>(() => {
|
||||
return localStorage.getItem('kefu_remember_me') === 'true'
|
||||
})
|
||||
|
||||
const [username, setUsername] = useState<string>(() => {
|
||||
const isRemembered = localStorage.getItem('kefu_remember_me') === 'true'
|
||||
if (isRemembered) {
|
||||
return localStorage.getItem('kefu_saved_username') || 'kefu_admin'
|
||||
}
|
||||
return 'kefu_admin'
|
||||
})
|
||||
|
||||
const [password, setPassword] = useState<string>(() => {
|
||||
const isRemembered = localStorage.getItem('kefu_remember_me') === 'true'
|
||||
if (isRemembered) {
|
||||
return localStorage.getItem('kefu_saved_password') || 'kefu_admin123'
|
||||
}
|
||||
return 'kefu_admin123'
|
||||
})
|
||||
const [searchParams] = useSearchParams()
|
||||
const expired = searchParams.get('expired') === '1'
|
||||
|
||||
const { login } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onFinish = async (values: { username?: string; password?: string }) => {
|
||||
useEffect(() => {
|
||||
cleanupLegacyCredentials()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (expired) {
|
||||
message.warning('登录已过期,请重新登录')
|
||||
}
|
||||
}, [expired])
|
||||
|
||||
const onFinish = async (values: { username: string; password: string }) => {
|
||||
setLoading(true)
|
||||
const u = (values.username || username).trim()
|
||||
const p = (values.password || password).trim()
|
||||
try {
|
||||
const user = await login(u, p)
|
||||
if (remember) {
|
||||
localStorage.setItem('kefu_remember_me', 'true')
|
||||
localStorage.setItem('kefu_saved_username', u)
|
||||
localStorage.setItem('kefu_saved_password', p)
|
||||
} else {
|
||||
localStorage.removeItem('kefu_remember_me')
|
||||
localStorage.removeItem('kefu_saved_username')
|
||||
localStorage.removeItem('kefu_saved_password')
|
||||
}
|
||||
const user = await login(values.username.trim(), values.password)
|
||||
message.success('登录成功')
|
||||
navigate(user.role === 'platform_admin' ? '/admin/dashboard' : '/agent/dashboard', { replace: true })
|
||||
} catch {
|
||||
@@ -76,13 +43,6 @@ const Login = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectDemoRole = (role: DemoRole) => {
|
||||
setSelectedRole(role.key)
|
||||
setUsername(role.username)
|
||||
setPassword(role.pass)
|
||||
form.setFieldsValue({ username: role.username, password: role.pass })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full relative flex flex-col justify-between bg-slate-950 p-4 sm:p-6 overflow-y-auto select-none">
|
||||
{/* 科技感深邃渐变背景 */}
|
||||
@@ -105,107 +65,58 @@ const Login = () => {
|
||||
{/* 核心登录卡片 */}
|
||||
<main className="relative z-10 flex-1 flex items-center justify-center py-6">
|
||||
<div className="w-full max-w-[420px]">
|
||||
<div className="relative rounded-3xl bg-slate-900/90 border border-slate-800 shadow-2xl p-7 sm:p-8 backdrop-blur-xl">
|
||||
<div className="relative rounded-3xl bg-slate-900/90 border border-slate-800 shadow-2xl p-7 sm:p-9 backdrop-blur-xl">
|
||||
{/* 顶端亮色发光描边 */}
|
||||
<div className="absolute inset-x-0 -top-px h-px bg-gradient-to-r from-transparent via-blue-500/50 to-transparent" />
|
||||
|
||||
{/* 卡片头部 */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="inline-flex items-center justify-center mb-3">
|
||||
<LogoIcon size={52} />
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center mb-4">
|
||||
<LogoIcon size={56} />
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-white tracking-tight m-0">登录客服云平台</h1>
|
||||
<p className="text-xs text-slate-400 mt-1.5 m-0">全渠道多租户智能客服与运营管理</p>
|
||||
</div>
|
||||
|
||||
{/* 顶部一键填入演示账号切换 Tab */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between text-[11px] text-slate-400 mb-2 font-medium">
|
||||
<span>快捷填入演示账号:</span>
|
||||
<span className="text-slate-500">点击自动填入</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1.5 p-1 rounded-xl bg-slate-950/80 border border-slate-800/80">
|
||||
{demoRoles.map(role => {
|
||||
const Icon = role.icon
|
||||
const active = selectedRole === role.key
|
||||
return (
|
||||
<button
|
||||
key={role.key}
|
||||
type="button"
|
||||
onClick={() => selectDemoRole(role)}
|
||||
className={`flex flex-col items-center justify-center py-2 px-1 rounded-lg text-xs font-medium border-0 cursor-pointer transition-all ${
|
||||
active
|
||||
? 'bg-blue-600 text-white shadow-sm font-semibold'
|
||||
: 'bg-transparent text-slate-400 hover:text-slate-200 hover:bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
<Icon className="text-xs mb-1" />
|
||||
<span className="text-[11px] leading-none">{role.title}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-2 m-0">全渠道多租户智能客服与运营管理</p>
|
||||
</div>
|
||||
|
||||
{/* 登录表单 */}
|
||||
<Form form={form} layout="vertical" onFinish={onFinish} autoComplete="off" size="large">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
requiredMark={false}
|
||||
size="large"
|
||||
className="login-form"
|
||||
>
|
||||
<Form.Item
|
||||
label={<span className="login-dark-label">账号</span>}
|
||||
name="username"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
className="!mb-4"
|
||||
className="!mb-5"
|
||||
>
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-semibold text-slate-300">账号 / 用户名</label>
|
||||
<Input
|
||||
value={username}
|
||||
onChange={e => {
|
||||
setUsername(e.target.value)
|
||||
form.setFieldsValue({ username: e.target.value })
|
||||
}}
|
||||
prefix={<UserOutlined className="text-slate-400 mr-1.5 text-sm" />}
|
||||
placeholder="请输入登录账号"
|
||||
className="login-dark-input !h-11 !rounded-xl !text-slate-100 placeholder:!text-slate-500 transition-all text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
autoFocus
|
||||
autoComplete="username"
|
||||
prefix={<UserOutlined className="text-slate-400 mr-1.5 text-sm" />}
|
||||
placeholder="请输入登录账号"
|
||||
className="login-dark-input !h-11 !rounded-xl !text-slate-100 placeholder:!text-slate-500 transition-all text-sm"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={<span className="login-dark-label">密码</span>}
|
||||
name="password"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
className="!mb-4"
|
||||
className="!mb-7"
|
||||
>
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-xs font-semibold text-slate-300">密码</label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={e => {
|
||||
setPassword(e.target.value)
|
||||
form.setFieldsValue({ password: e.target.value })
|
||||
}}
|
||||
prefix={<LockOutlined className="text-slate-400 mr-1.5 text-sm" />}
|
||||
placeholder="请输入登录密码"
|
||||
className="login-dark-input !h-11 !rounded-xl !text-slate-100 placeholder:!text-slate-500 transition-all text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Input.Password
|
||||
autoComplete="current-password"
|
||||
prefix={<LockOutlined className="text-slate-400 mr-1.5 text-sm" />}
|
||||
placeholder="请输入登录密码"
|
||||
className="login-dark-input !h-11 !rounded-xl !text-slate-100 placeholder:!text-slate-500 transition-all text-sm"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 记住密码勾选栏 */}
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<label className="flex items-center gap-2 cursor-pointer text-xs text-slate-300 hover:text-white select-none transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
onChange={e => setRemember(e.target.checked)}
|
||||
className="w-4 h-4 rounded bg-slate-800 border-slate-700 text-blue-600 focus:ring-blue-500/20 cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<span className="font-medium">记住密码</span>
|
||||
</label>
|
||||
<span className="text-[11px] text-slate-500 hover:text-slate-400 cursor-pointer">
|
||||
忘记密码请联系管理员
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Form.Item className="!mb-2">
|
||||
<Form.Item className="!mb-3">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
@@ -217,6 +128,10 @@ const Login = () => {
|
||||
<ArrowRightOutlined className="text-xs" />
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
<p className="text-center text-[11px] text-slate-500 m-0">
|
||||
忘记密码请联系平台管理员
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user