backup at 2026-04-16-16-39-42
This commit is contained in:
207
src/pages/Login.tsx
Normal file
207
src/pages/Login.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { User, Template, SystemSettings } from '../types';
|
||||
import { defaultReportContent } from '../utils/defaultContent';
|
||||
import { storage } from '../utils/storage';
|
||||
import { User as UserIcon, Lock } from 'lucide-react';
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const initData = () => {
|
||||
const existingUsers = storage.get<User[]>('users', []);
|
||||
const hasAdmin = existingUsers.some((u) => u.username === 'admin' && u.password === '123456');
|
||||
|
||||
let savedTemplates = storage.get<Template[]>('templates', []);
|
||||
if (savedTemplates.length === 0) {
|
||||
const initialTemplate: Template = {
|
||||
id: 'surgery',
|
||||
name: '腹腔镜胆囊切除术报告',
|
||||
desc: '标准手术记录模板',
|
||||
content: defaultReportContent,
|
||||
createdAt: new Date().toISOString(),
|
||||
author: 'admin'
|
||||
};
|
||||
savedTemplates = [initialTemplate];
|
||||
storage.set('templates', savedTemplates);
|
||||
}
|
||||
|
||||
if (!hasAdmin) {
|
||||
const allTplIds = savedTemplates.map(t => t.id);
|
||||
const defaultUsers: User[] = [
|
||||
{ username: 'admin', password: '123456', role: 'super', name: '超级管理员', status: 'active', createdAt: '2024-01-01', visibleTemplates: allTplIds, manageableTemplates: allTplIds },
|
||||
{ username: 'manager', password: '123456', role: 'admin', name: '管理员', status: 'active', createdAt: '2024-01-01', department: '外科', visibleTemplates: allTplIds, manageableTemplates: allTplIds },
|
||||
{ username: '0001', password: '123456', role: 'user', name: '张医生', status: 'active', createdAt: '2024-01-01', department: '外科', visibleTemplates: allTplIds, manageableTemplates: [] }
|
||||
];
|
||||
storage.set('users', defaultUsers);
|
||||
console.log('Default users initialized');
|
||||
}
|
||||
|
||||
const settingsRaw = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
|
||||
if (!settingsRaw.frameCount) {
|
||||
const round1 = (n: number) => Math.round(n * 10) / 10;
|
||||
const positions: number[] = [];
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
positions.push(round1((100 / 13) * i));
|
||||
}
|
||||
const defaultSettings = {
|
||||
frameCount: 12,
|
||||
framePositions: positions,
|
||||
apiEndpoint: '',
|
||||
apiKey: '',
|
||||
defaultTemplate: savedTemplates[0]?.id || '',
|
||||
frameMode: 'uniform'
|
||||
};
|
||||
storage.set('systemSettings', defaultSettings);
|
||||
}
|
||||
};
|
||||
|
||||
initData();
|
||||
}, []);
|
||||
|
||||
const handleLogin = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const u = username.trim();
|
||||
const p = password.trim();
|
||||
|
||||
const users = storage.get<User[]>('users', []);
|
||||
let user = users.find(user => user.username === u && user.password === p);
|
||||
|
||||
// Fallback for default accounts if localStorage is messed up
|
||||
if (!user) {
|
||||
const defaults = [
|
||||
{ u: 'admin', p: '123456', r: 'super', n: '超级管理员' },
|
||||
{ u: 'manager', p: '123456', r: 'admin', n: '管理员' },
|
||||
{ u: '0001', p: '123456', r: 'user', n: '张医生' }
|
||||
];
|
||||
const d = defaults.find(item => item.u === u && item.p === p);
|
||||
if (d) {
|
||||
const allTemplates = storage.get<Template[]>('templates', []);
|
||||
const allTplIds = allTemplates.map(t => t.id);
|
||||
user = { username: d.u, password: d.p, role: d.r as any, name: d.n, status: 'active', createdAt: '2024-01-01', visibleTemplates: allTplIds, manageableTemplates: d.r === 'user' ? [] : allTplIds, department: d.r === 'super' ? '' : '外科' };
|
||||
// Sync back to localStorage
|
||||
const updatedUsers = [...users.filter(item => item.username !== u), user];
|
||||
storage.set('users', updatedUsers);
|
||||
}
|
||||
}
|
||||
|
||||
if (user) {
|
||||
if (user.status === 'inactive') {
|
||||
setError('该账号已被禁用');
|
||||
return;
|
||||
}
|
||||
storage.set('currentUser', user);
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
setError('用户ID或密码错误');
|
||||
console.log('Login failed for:', u);
|
||||
}
|
||||
};
|
||||
|
||||
const fillLogin = (u: string, p: string) => {
|
||||
setUsername(u);
|
||||
setPassword(p);
|
||||
setTimeout(() => {
|
||||
// Trigger the robust login logic manually
|
||||
const users = storage.get<User[]>('users', []);
|
||||
let user = users.find(user => user.username === u && user.password === p);
|
||||
|
||||
if (!user) {
|
||||
const defaults = [
|
||||
{ u: 'admin', p: '123456', r: 'super', n: '超级管理员' },
|
||||
{ u: 'manager', p: '123456', r: 'admin', n: '管理员' },
|
||||
{ u: '0001', p: '123456', r: 'user', n: '张医生' }
|
||||
];
|
||||
const d = defaults.find(item => item.u === u && item.p === p);
|
||||
if (d) {
|
||||
user = { username: d.u, password: d.p, role: d.r as any, name: d.n, status: 'active', createdAt: '2024-01-01' };
|
||||
const updatedUsers = [...users.filter(item => item.username !== u), user];
|
||||
storage.set('users', updatedUsers);
|
||||
}
|
||||
}
|
||||
|
||||
if (user) {
|
||||
storage.set('currentUser', user);
|
||||
navigate('/dashboard');
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-bg p-6">
|
||||
<div className="bg-white rounded-3xl shadow-[0_20px_50px_-12px_rgba(0,0,0,0.08)] p-12 w-full max-w-[460px] border border-border">
|
||||
<div className="text-center mb-10">
|
||||
<div className="flex flex-col items-center">
|
||||
<img src="/logo_square.png" alt="Logo" className="w-16 h-16 object-contain mb-6" />
|
||||
<h1 className="text-2xl font-bold text-text-main tracking-tight mb-1">手术图文病历报告生成终端</h1>
|
||||
<p className="text-xs text-text-muted uppercase tracking-widest font-bold">智能图文报告管理系统</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-[10px] font-bold text-text-main uppercase tracking-wider">用户ID</label>
|
||||
<div className="relative">
|
||||
<UserIcon className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-muted" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入您的用户ID"
|
||||
required
|
||||
className="input-minimal pl-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="block text-[10px] font-bold text-text-main uppercase tracking-wider">密码</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-muted" size={18} />
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入您的登录密码"
|
||||
required
|
||||
className="input-minimal pl-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-accent w-full py-4 text-base shadow-[0_8px_20px_-4px_rgba(37,99,235,0.2)]"
|
||||
>
|
||||
进入系统
|
||||
</button>
|
||||
{error && <div className="text-red-500 text-xs text-center font-bold animate-pulse">{error}</div>}
|
||||
</form>
|
||||
|
||||
<div className="mt-10 pt-8 border-t border-border">
|
||||
<h3 className="text-[10px] text-text-muted mb-4 uppercase tracking-widest font-bold text-center">快捷登录测试账号</h3>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{[
|
||||
{ u: 'admin', p: '123456', r: '超级管理员', c: 'bg-amber-100 text-amber-700' },
|
||||
{ u: 'manager', p: '123456', r: '管理员', c: 'bg-blue-100 text-blue-700' },
|
||||
{ u: '0001', p: '123456', r: '医生', c: 'bg-green-100 text-green-700' }
|
||||
].map(test => (
|
||||
<div
|
||||
key={test.u}
|
||||
onClick={() => fillLogin(test.u, test.p)}
|
||||
className="flex justify-between items-center p-3 bg-slate-50 rounded-xl cursor-pointer transition-all hover:bg-white hover:shadow-md border border-transparent hover:border-border group"
|
||||
>
|
||||
<span className="text-xs font-bold text-text-main">{test.u} / {test.p}</span>
|
||||
<span className={`text-[9px] px-2 py-0.5 rounded-full font-bold uppercase tracking-wider ${test.c}`}>
|
||||
{test.r}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user