This commit is contained in:
longdayi 2025-01-25 20:48:16 +08:00
parent 7919edec06
commit a2da55bd9e
13 changed files with 86 additions and 91 deletions

View File

@ -31,12 +31,12 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => {
label="用户名" label="用户名"
name="username" name="username"
rules={[ rules={[
{ required: true, message: "Username is required" }, { required: true, message: "请输入用户名" },
{ min: 2, message: "Username must be at least 2 characters" } { min: 2, message: "用户名至少需要2个字符" }
]} ]}
> >
<Input <Input
placeholder="Username" placeholder="请输入用户名"
className="rounded-lg" className="rounded-lg"
/> />
</Form.Item> </Form.Item>
@ -45,11 +45,11 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => {
label="密码" label="密码"
name="password" name="password"
rules={[ rules={[
{ required: true, message: "Password is required" } { required: true, message: "请输入密码" }
]} ]}
> >
<Input.Password <Input.Password
placeholder="Password" placeholder="请输入密码"
className="rounded-lg" className="rounded-lg"
/> />
</Form.Item> </Form.Item>

View File

@ -6,11 +6,6 @@ import { LoginForm } from "./login";
import { Card, Typography, Button, Spin, Divider } from "antd"; import { Card, Typography, Button, Spin, Divider } from "antd";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { useAuthForm } from "./useAuthForm"; import { useAuthForm } from "./useAuthForm";
import {
GithubOutlined,
GoogleOutlined,
LoadingOutlined
} from '@ant-design/icons';
const { Title, Text, Paragraph } = Typography; const { Title, Text, Paragraph } = Typography;
@ -41,23 +36,22 @@ const AuthPage: React.FC = () => {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
> >
<Card <div
className="w-full max-w-5xl shadow-xl rounded-3xl overflow-hidden transition-all duration-300 hover:shadow-3xl relative backdrop-blur-sm bg-white/90" className="w-full max-w-5xl shadow-elegant border-2 border-white rounded-xl overflow-hidden transition-all duration-300 relative"
bodyStyle={{ padding: 0 }}
> >
<div className="flex flex-col md:flex-row min-h-[650px]"> <div className="flex flex-col md:flex-row min-h-[650px]">
{/* Left Panel - Welcome Section */} {/* Left Panel - Welcome Section */}
<div className="w-full md:w-1/2 p-12 bg-gradient-to-br from-primary to-primary-400 text-white flex flex-col justify-center relative overflow-hidden"> <div className="w-full md:w-1/2 p-12 bg-gradient-to-br from-primary to-primary-600 text-white flex flex-col justify-center relative overflow-hidden">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5 }} transition={{ delay: 0.2, duration: 0.5 }}
> >
<div className=" text-4xl text-white mb-4"> <div className="text-4xl text-white mb-4 font-serif">
Leader Mail
</div> </div>
<Paragraph className="text-lg mb-8 text-blue-100"> <Paragraph className="text-lg mb-8 text-blue-100 leading-relaxed text-justify">
</Paragraph> </Paragraph>
{showLogin && ( {showLogin && (
<Button <Button
@ -67,7 +61,7 @@ const AuthPage: React.FC = () => {
onClick={toggleForm} onClick={toggleForm}
className="w-fit hover:bg-white hover:text-blue-700 transition-all" className="w-fit hover:bg-white hover:text-blue-700 transition-all"
> >
</Button> </Button>
)} )}
</motion.div> </motion.div>
@ -88,13 +82,13 @@ const AuthPage: React.FC = () => {
<motion.div className="mb-8"> <motion.div className="mb-8">
<Title level={3} className="mb-2"></Title> <Title level={3} className="mb-2"></Title>
<Text type="secondary"> <Text type="secondary">
{' '} 使{' '}
<Button <Button
type="link" type="link"
onClick={toggleForm} onClick={toggleForm}
className="p-0 font-medium" className="p-0 font-medium"
> >
</Button> </Button>
</Text> </Text>
</motion.div> </motion.div>
@ -113,15 +107,15 @@ const AuthPage: React.FC = () => {
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
> >
<motion.div className="mb-8"> <motion.div className="mb-8">
<Title level={3} className="mb-2"></Title> <Title level={3} className="mb-2"></Title>
<Text type="secondary"> <Text type="secondary">
{' '} {' '}
<Button <Button
type="link" type="link"
onClick={toggleForm} onClick={toggleForm}
className="p-0 font-medium" className="p-0 font-medium"
> >
</Button> </Button>
</Text> </Text>
</motion.div> </motion.div>
@ -135,7 +129,7 @@ const AuthPage: React.FC = () => {
</AnimatePresence> </AnimatePresence>
</div> </div>
</div> </div>
</Card> </div>
</motion.div> </motion.div>
</div> </div>

View File

@ -122,7 +122,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
loading={isLoading} loading={isLoading}
className="w-full h-10 rounded-lg" className="w-full h-10 rounded-lg"
> >
{isLoading ? "正在创建账户..." : "创建账户"} {isLoading ? "正在注册..." : "注册"}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -9,7 +9,7 @@ export default function LetterEditorPage() {
const termId = searchParams.get("termId"); const termId = searchParams.get("termId");
return ( return (
<div className="min-h-screen rounded-xl bg-gradient-to-b from-slate-100 to-slate-50 "> <div className="min-h-screen rounded-xl shadow-elegant border-2 border-white bg-gradient-to-b from-slate-100 to-slate-50 ">
<WriteHeader></WriteHeader> <WriteHeader></WriteHeader>
<LetterFormProvider receiverId={receiverId} termId={termId}> <LetterFormProvider receiverId={receiverId} termId={termId}>
<LetterBasicForm /> <LetterBasicForm />

View File

@ -5,7 +5,7 @@ export default function LetterListPage() {
return ( return (
// 添加 flex flex-col 使其成为弹性布局容器 // 添加 flex flex-col 使其成为弹性布局容器
<div className="min-h-screen rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col"> <div className="min-h-screen shadow-elegant border-2 border-white rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col">
<Header /> <Header />
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */} {/* 添加 flex-grow 使内容区域自动填充剩余空间 */}

View File

@ -63,44 +63,37 @@ export default function LetterProgressPage() {
return ( return (
<div <div
className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-50 " className="min-h-screen shadow-elegant border-2 border-white rounded-xl bg-gradient-to-b from-slate-100 to-slate-50 "
style={{ fontFamily: "Arial, sans-serif" }}> style={{ fontFamily: "Arial, sans-serif" }}>
<ProgressHeader></ProgressHeader> <ProgressHeader></ProgressHeader>
{/* Main Content */} {/* Main Content */}
<main className="container mx-auto px-4 py-8"> <main className="container mx-auto p-6">
{/* Search Section */} <div className="space-y-4">
<Card className="mb-8 border border-gray-200"> <div className="flex gap-4">
<div className="space-y-4"> <Input
<label className="block text-lg font-medium text-[#003366]"> prefix={
Ticket ID <SearchOutlined className=" text-secondary-300" />
</label> }
<div className="flex gap-4"> size="large"
<Input value={feedbackId}
prefix={ onChange={(e) => setFeedbackId(e.target.value)}
<SearchOutlined className="text-[#003366]" /> placeholder="请输入信件编码查询处理状态"
} status={error ? "error" : ""}
size="large" className="border border-gray-300"
value={feedbackId} />
onChange={(e) => setFeedbackId(e.target.value)} <Button
placeholder="e.g. USAF-2025-0123" type="primary"
status={error ? "error" : ""} size="large"
className="border border-gray-300" loading={loading}
/> onClick={mockLookup}
<Button >
type="primary"
size="large" </Button>
icon={<SearchOutlined />}
loading={loading}
onClick={mockLookup}
>
Track
</Button>
</div>
{error && (
<p className="text-red-600 text-sm">{error}</p>
)}
</div> </div>
</Card> {error && (
<p className="text-red-600 text-sm">{error}</p>
)}
</div>
{/* Results Section */} {/* Results Section */}
{status && ( {status && (

View File

@ -1,7 +1,7 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { StaffDto } from "@nice/common"; import { StaffDto } from "@nice/common";
import { Button, Tooltip, Badge } from 'antd'; import { Button, Tooltip, Badge } from 'antd';
import { SendOutlined } from '@ant-design/icons'; import { BankFilled, SendOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
export interface SendCardProps { export interface SendCardProps {
@ -13,15 +13,12 @@ export function SendCard({ staff, termId }: SendCardProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const handleSendLetter = () => { const handleSendLetter = () => {
navigate(`/editor?termId=${termId || ''}&receiverId=${staff.id}`); window.open(`/editor?termId=${termId || ''}&receiverId=${staff.id}`, '_blank');
}; };
return ( return (
<motion.div <div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} className="bg-white rounded-xl overflow-hidden border-2 hover:border-primary transition-all duration-300"
whileHover={{ scale: 1.005 }}
transition={{ duration: 0.2 }}
className="bg-white rounded-xl shadow-lg overflow-hidden border-2 border-gray-100 hover:border-primary-200 transition-all duration-300"
> >
<div className="flex flex-col sm:flex-row"> <div className="flex flex-col sm:flex-row">
{/* Image Container */} {/* Image Container */}
@ -61,11 +58,14 @@ export function SendCard({ staff, termId }: SendCardProps) {
<div> <div>
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<h3 className="text-2xl font-semibold text-gray-900"> <h3 className="text-2xl font-semibold text-gray-900">
{staff?.showname} {staff?.showname || staff?.username}
</h3> </h3>
<Badge status="success" />
</div> </div>
<p className="text-gray-600 text-lg font-medium">{staff.department?.name || '未设置部门'}</p> <p className="text-gray-600 text-lg font-medium flex items-center gap-2">
<BankFilled></BankFilled>
{staff.department?.name || '未设置部门'}</p>
</div> </div>
<Tooltip title="职级"> <Tooltip title="职级">
<span className="inline-flex items-center px-4 py-1.5 text-sm font-medium bg-gradient-to-r from-blue-50 to-blue-100 text-primary rounded-full hover:from-blue-100 hover:to-blue-200 transition-colors shadow-sm"> <span className="inline-flex items-center px-4 py-1.5 text-sm font-medium bg-gradient-to-r from-blue-50 to-blue-100 text-primary rounded-full hover:from-blue-100 hover:to-blue-200 transition-colors shadow-sm">
@ -112,6 +112,6 @@ export function SendCard({ staff, termId }: SendCardProps) {
</div> </div>
</div> </div>
</div> </div>
</motion.div> </div>
); );
} }

View File

@ -1,6 +1,6 @@
import { TermDto } from "@nice/common"; import { TermDto } from "@nice/common";
export default function WriteHeader({ term }: { term: TermDto }) { export default function WriteHeader({ term }: { term?: TermDto }) {
return <header className=" rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6"> return <header className=" rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
{/* 主标题 */} {/* 主标题 */}

View File

@ -46,7 +46,7 @@ export default function WriteLetterPage() {
}, [searchQuery, selectedDept, resetPage]); }, [searchQuery, selectedDept, resetPage]);
return ( return (
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-50"> <div className="min-h-screen shadow-elegant border-2 border-white rounded-xl bg-gradient-to-b from-slate-100 to-slate-50">
<WriteHeader term={getTerm(termId)} /> <WriteHeader term={getTerm(termId)} />
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-8">
<div className="mb-8 space-y-4"> <div className="mb-8 space-y-4">
@ -121,7 +121,10 @@ export default function WriteLetterPage() {
current={currentPage} current={currentPage}
total={data?.totalPages || 0} total={data?.totalPages || 0}
pageSize={pageSize} pageSize={pageSize}
onChange={(page) => setCurrentPage(page)} onChange={(page) => {
setCurrentPage(page);
window.scrollTo(0, 0);
}}
showSizeChanger={false} showSizeChanger={false}
showTotal={(total) => `${total} 条记录`} showTotal={(total) => `${total} 条记录`}
/> />

View File

@ -5,13 +5,14 @@ import Navigation from "./navigation";
import { useAuth } from "@web/src/providers/auth-provider"; import { useAuth } from "@web/src/providers/auth-provider";
import { UserOutlined } from "@ant-design/icons"; import { UserOutlined } from "@ant-design/icons";
import { UserMenu } from "../element/usermenu"; import { UserMenu } from "../element/usermenu";
import SineWavesCanvas from "../../animation/sine-wave";
interface HeaderProps { interface HeaderProps {
onSearch?: (query: string) => void; onSearch?: (query: string) => void;
} }
export const Header = memo(function Header({ onSearch }: HeaderProps) { export const Header = memo(function Header({ onSearch }: HeaderProps) {
const { isAuthenticated } = useAuth() const { isAuthenticated } = useAuth()
return ( return (
<header className="sticky top-0 z-50 bg-[#13294B] text-white shadow-lg"> <header className="sticky top-0 z-50 bg-gradient-to-br from-primary-500 to-primary-800 text-white shadow-lg">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="py-3"> <div className="py-3">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">

View File

@ -68,7 +68,6 @@ export function LetterFormProvider({
}, },
state: PostState.PENDING, state: PostState.PENDING,
isPublic: data?.isPublic, isPublic: data?.isPublic,
resources: data.resources?.length resources: data.resources?.length
? { ? {
connect: ( connect: (

View File

@ -2,7 +2,6 @@ import { Form, Input, Button, Checkbox, Select } from "antd";
import { useLetterEditor } from "../context/LetterEditorContext"; import { useLetterEditor } from "../context/LetterEditorContext";
import { SendOutlined } from "@ant-design/icons"; import { SendOutlined } from "@ant-design/icons";
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor"; import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
import { PostBadge } from "../../detail/badge/PostBadge";
import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
import StaffSelect from "../../../staff/staff-select"; import StaffSelect from "../../../staff/staff-select";
import TermSelect from "../../../term/term-select"; import TermSelect from "../../../term/term-select";
@ -20,8 +19,8 @@ export function LetterBasicForm() {
onFinish={handleFinish} onFinish={handleFinish}
initialValues={{ initialValues={{
meta: { tags: [] }, meta: { tags: [] },
receiverId, receivers: [receiverId],
termId, terms: [termId],
isPublic: true, isPublic: true,
}}> }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">

View File

@ -77,15 +77,21 @@ export const NiceTailwindConfig: Config = {
solid: "2px 2px 0 0 rgba(0, 0, 0, 0.2)", solid: "2px 2px 0 0 rgba(0, 0, 0, 0.2)",
glow: "0 0 8px rgba(230, 180, 0, 0.8)", glow: "0 0 8px rgba(230, 180, 0, 0.8)",
inset: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.15)", inset: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.15)",
"elevation-1": "0 1px 2px 0 rgba(0, 0, 0, 0.1)", "elevation-1": "0 1px 2px rgba(0, 48, 138, 0.05), 0 1px 1px rgba(0, 0, 0, 0.05)",
"elevation-2": "0 2px 4px 0 rgba(0, 0, 0, 0.15)", "elevation-2": "0 2px 4px rgba(0, 48, 138, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1)",
"elevation-3": "0 4px 8px 0 rgba(0, 0, 0, 0.2)", "elevation-3": "0 4px 8px rgba(0, 48, 138, 0.15), 0 4px 4px rgba(0, 0, 0, 0.15)",
"elevation-4": "0 8px 16px 0 rgba(0, 0, 0, 0.25)", "elevation-4": "0 8px 16px rgba(0, 48, 138, 0.2), 0 8px 8px rgba(0, 0, 0, 0.2)",
"elevation-5": "0 16px 32px 0 rgba(0, 0, 0, 0.3)", "elevation-5": "0 16px 32px rgba(0, 48, 138, 0.25), 0 16px 16px rgba(0, 0, 0, 0.25)",
panel: "0 4px 6px -1px rgba(0, 48, 138, 0.1), 0 2px 4px -2px rgba(0, 48, 138, 0.1)", panel: "0 4px 6px -1px rgba(0, 48, 138, 0.1), 0 2px 4px -2px rgba(0, 48, 138, 0.1), inset 0 -1px 2px rgba(255, 255, 255, 0.05)",
button: "0 2px 4px rgba(0, 48, 138, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.1)", button: "0 2px 4px rgba(0, 48, 138, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.1), 0 1px 1px rgba(0, 0, 0, 0.05)",
card: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)", card: "0 4px 6px rgba(0, 48, 138, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08), inset 0 -1px 2px rgba(255, 255, 255, 0.05)",
modal: "0 8px 32px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.15)", modal: "0 8px 32px rgba(0, 48, 138, 0.2), 0 4px 16px rgba(0, 0, 0, 0.15), inset 0 -2px 4px rgba(255, 255, 255, 0.05)",
"soft-primary": "0 2px 4px rgba(0, 48, 138, 0.1), 0 4px 8px rgba(0, 48, 138, 0.05)",
"soft-secondary": "0 2px 4px rgba(77, 77, 77, 0.1), 0 4px 8px rgba(77, 77, 77, 0.05)",
"soft-accent": "0 2px 4px rgba(230, 180, 0, 0.1), 0 4px 8px rgba(230, 180, 0, 0.05)",
"inner-glow": "inset 0 0 8px rgba(0, 48, 138, 0.1)",
"outer-glow": "0 0 16px rgba(0, 48, 138, 0.1)",
"elegant": "0 4px 24px rgba(0, 48, 138, 0.15), 0 2px 12px rgba(0, 48, 138, 0.1), 0 1px 6px rgba(0, 48, 138, 0.05), inset 0 -1px 2px rgba(255, 255, 255, 0.1)",
}, },
animation: { animation: {
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite", "pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",