feat: 优化web

This commit is contained in:
2026-04-23 18:58:13 +08:00
commit 544a2f3428
160 changed files with 27327 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
'use client';
import * as Dialog from '@radix-ui/react-dialog';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useAuthStore } from '@/stores/auth-store';
import { useAuthUiStore } from '@/stores/auth-ui-store';
export function LoginModal() {
const open = useAuthUiStore((s) => s.loginModalOpen);
const close = useAuthUiStore((s) => s.closeLoginModal);
const hint = useAuthUiStore((s) => s.loginHint);
const login = useAuthStore((s) => s.login);
const router = useRouter();
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
const [tenant, setTenant] = useState('');
const [err, setErr] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setErr(null);
setLoading(true);
try {
await login(user, pass, tenant || undefined);
close();
setUser('');
setPass('');
setTenant('');
router.refresh();
} catch (ex) {
setErr(ex instanceof Error ? ex.message : String(ex));
} finally {
setLoading(false);
}
}
return (
<Dialog.Root
open={open}
onOpenChange={(next) => {
if (!next) {
close();
}
}}
>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-280 bg-black/40" />
<Dialog.Content
className="fixed left-1/2 top-1/2 z-280 w-full max-w-sm -translate-x-1/2 -translate-y-1/2 rounded-lg border border-neutral-200 bg-white p-5 shadow-xl outline-none focus:outline-none"
onPointerDownOutside={(e) => e.preventDefault()}
>
<Dialog.Title id="relogin-title" className="text-lg font-medium text-neutral-900">
</Dialog.Title>
{hint ? (
<Dialog.Description className="mt-1 text-sm text-neutral-600">{hint}</Dialog.Description>
) : (
<Dialog.Description className="sr-only"></Dialog.Description>
)}
<form onSubmit={onSubmit} className="mt-4 space-y-3">
<label className="block text-sm">
<span className="text-neutral-600"></span>
<input
className="mt-1 w-full rounded border border-neutral-300 px-2 py-1"
value={user}
onChange={(e) => setUser(e.target.value)}
autoComplete="username"
required
/>
</label>
<label className="block text-sm">
<span className="text-neutral-600"></span>
<input
type="password"
className="mt-1 w-full rounded border border-neutral-300 px-2 py-1"
value={pass}
onChange={(e) => setPass(e.target.value)}
autoComplete="current-password"
required
/>
</label>
<label className="block text-sm">
<span className="text-neutral-600"> ID</span>
<input
className="mt-1 w-full rounded border border-neutral-300 px-2 py-1"
value={tenant}
onChange={(e) => setTenant(e.target.value)}
/>
</label>
{err ? <p className="text-sm text-red-600">{err}</p> : null}
<div className="flex justify-end gap-2 pt-2">
<button
type="button"
className="rounded border border-neutral-300 px-3 py-1.5 text-sm"
onClick={() => {
close();
setErr(null);
}}
>
</button>
<button
type="submit"
disabled={loading}
className="rounded bg-neutral-900 px-3 py-1.5 text-sm text-white disabled:opacity-50"
>
{loading ? '提交中…' : '登录'}
</button>
</div>
</form>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}