部分前端页面

This commit is contained in:
han 2025-07-29 22:14:39 +08:00
parent ee3b1228dd
commit 309c26e355
11 changed files with 478 additions and 4 deletions

View File

@ -438,7 +438,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "/home/leon/projects/nice/apps/casualroom/db/generated/prisma",
"value": "/home/han/project/nice/apps/casualroom/db/generated/prisma",
"fromEnvVar": null
},
"config": {
@ -456,7 +456,7 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "/home/leon/projects/nice/apps/casualroom/db/prisma/schema.prisma",
"sourceFilePath": "/home/han/project/nice/apps/casualroom/db/prisma/schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {

View File

@ -439,7 +439,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "/home/leon/projects/nice/apps/casualroom/db/generated/prisma",
"value": "/home/han/project/nice/apps/casualroom/db/generated/prisma",
"fromEnvVar": null
},
"config": {
@ -457,7 +457,7 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "/home/leon/projects/nice/apps/casualroom/db/prisma/schema.prisma",
"sourceFilePath": "/home/han/project/nice/apps/casualroom/db/prisma/schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {

0
apps/casualroom/db/generated/prisma/runtime/index-browser.d.ts vendored Executable file → Normal file
View File

0
apps/casualroom/db/generated/prisma/runtime/library.d.ts vendored Executable file → Normal file
View File

View File

@ -0,0 +1,202 @@
import { Input } from "@nice/ui/components/input";
import { Textarea } from "@nice/ui/components/textarea";
import { Label } from "@nice/ui/components/label";
import { Button } from "@nice/ui/components/button";
import { useState } from "react";
import { IconX } from '@tabler/icons-react';
// 可根据实际组件库调整导入
export type Family = {
name: string;
birth: string;
relation: string;
origin: string;
work: string;
address: string;
};
export type ApplicationFormData = {
applicantName: string;
applicantLevel: string;
applicantBirth: string;
applicantEntry: string;
applicantMarriage: string;
familyList: Family[];
daysLived: string;
daysLeft: string;
daysApply: string;
contact: string;
planCheckin: string;
planCheckout: string;
reason: string;
opinions: string[];
};
export interface ApplicationFormProps {
initialData?: ApplicationFormData;
onClose?: () => void;
onSubmit?: (data: ApplicationFormData) => void;
}
export function ApplicationForm({ initialData, onClose, onSubmit }: ApplicationFormProps) {
const [form, setForm] = useState<ApplicationFormData>(
initialData || {
applicantName: "",
applicantLevel: "",
applicantBirth: "",
applicantEntry: "",
applicantMarriage: "",
familyList: [
{ name: "", birth: "", relation: "", origin: "", work: "", address: "" },
],
daysLived: "",
daysLeft: "",
daysApply: "",
contact: "",
planCheckin: "",
planCheckout: "",
reason: "",
opinions: ["", "", "", "", ""],
}
);
const handleChange = (field: keyof ApplicationFormData, value: any) => {
setForm(f => ({ ...f, [field]: value }));
};
const handleFamilyChange = (idx: number, field: keyof Family, value: string) => {
setForm(f => {
const familyList = [...f.familyList];
familyList[idx] = { ...familyList[idx], [field]: value } as Family;
return { ...f, familyList };
});
};
const addFamily = () => {
setForm(f => ({
...f,
familyList: [
...f.familyList,
{ name: "", birth: "", relation: "", origin: "", work: "", address: "" },
],
}));
};
const removeFamily = (idx: number) => {
setForm(f => ({
...f,
familyList: f.familyList.filter((_, i) => i !== idx),
}));
};
const handleOpinionChange = (idx: number, value: string) => {
setForm(f => {
const opinions = [...f.opinions];
opinions[idx] = value;
return { ...f, opinions };
});
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit?.(form);
onClose?.();
};
return (
<form className="max-w-3xl mx-auto p-8 bg-white rounded shadow space-y-6" onSubmit={handleSubmit}>
<h2 className="text-xl font-bold text-center mb-4"></h2>
{/* 申请人信息 */}
<div className="border border-gray-300 rounded-md p-4 mb-2">
<div className="font-semibold mb-2 text-base"></div>
<div className="grid grid-cols-6 gap-4">
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="applicantName" value={form.applicantName} onChange={e => handleChange('applicantName', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="applicantLevel" value={form.applicantLevel} onChange={e => handleChange('applicantLevel', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="applicantBirth" type="date" value={form.applicantBirth} onChange={e => handleChange('applicantBirth', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="applicantEntry" type="date" value={form.applicantEntry} onChange={e => handleChange('applicantEntry', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="applicantMarriage" type="date" value={form.applicantMarriage} onChange={e => handleChange('applicantMarriage', e.target.value)} />
</div>
</div>
{/* 家人信息 */}
<div className="border border-gray-300 rounded-md p-4 mb-2">
<div className="font-semibold mb-2 text-base flex items-center justify-between">
<span></span>
<Button type="button" size="sm" onClick={addFamily}></Button>
</div>
{form.familyList.map((family, idx) => (
<div
className="grid grid-cols-6 gap-4 mb-4 p-4 border border-gray-200 rounded-md relative bg-gray-50 group"
key={idx}
>
<div className="absolute right-0 top-0">
{form.familyList.length > 1 && (
<button
type="button"
aria-label="删除"
className="p-1 rounded hover:bg-red-100 text-red-500 transition-colors opacity-0 group-hover:opacity-100"
onClick={() => removeFamily(idx)}
style={{ lineHeight: 0 }}
>
<IconX size={15} stroke={2} />
</button>
)}
</div>
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyName_${idx}`} value={family.name} onChange={e => handleFamilyChange(idx, 'name', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyBirth_${idx}`} type="date" value={family.birth} onChange={e => handleFamilyChange(idx, 'birth', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyRelation_${idx}`} value={family.relation} onChange={e => handleFamilyChange(idx, 'relation', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyOrigin_${idx}`} value={family.origin} onChange={e => handleFamilyChange(idx, 'origin', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyWork_${idx}`} value={family.work} onChange={e => handleFamilyChange(idx, 'work', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name={`familyAddress_${idx}`} value={family.address} onChange={e => handleFamilyChange(idx, 'address', e.target.value)} />
</div>
))}
</div>
{/* 住宿信息 */}
<div className="grid grid-cols-6 gap-4">
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="daysLived" type="number" value={form.daysLived} onChange={e => handleChange('daysLived', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="daysLeft" type="number" value={form.daysLeft} onChange={e => handleChange('daysLeft', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="daysApply" type="number" value={form.daysApply} onChange={e => handleChange('daysApply', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="contact" value={form.contact} onChange={e => handleChange('contact', e.target.value)} />
<Label className="col-span-1 self-center"></Label>
<Input className="col-span-2" name="planCheckin" type="date" value={form.planCheckin} onChange={e => handleChange('planCheckin', e.target.value)} />
<Label className="col-span-1 self-center">退</Label>
<Input className="col-span-2" name="planCheckout" type="date" value={form.planCheckout} onChange={e => handleChange('planCheckout', e.target.value)} />
</div>
{/* 申请理由 */}
<div>
<Label className="mb-2"></Label>
<Textarea name="reason" rows={3} value={form.reason} onChange={e => handleChange('reason', e.target.value)} />
</div>
{/* 意见(多行) */}
<div>
{form.opinions.map((op, idx) => (
<div key={idx} className="mb-2">
<Label className="mb-2"></Label>
<Textarea name={`opinion${idx + 1}`} rows={2} value={op} onChange={e => handleOpinionChange(idx, e.target.value)} />
</div>
))}
</div>
<div className="text-center flex gap-2 justify-center">
<Button type="submit"></Button>
{onClose && (
<Button type="button" variant="outline" onClick={onClose}></Button>
)}
</div>
</form>
);
}

View File

@ -0,0 +1,150 @@
import * as React from 'react';
import {
Card,
CardHeader,
CardTitle,
CardContent,
} from '@nice/ui/components/card'; // 卡片组件
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@nice/ui/components/table'; // 表格组件
import { Input } from '@nice/ui/components/input'; // 输入框
import { Label } from '@nice/ui/components/label'; // 标签
import { Button } from '@nice/ui/components/button'; // 按钮
import { cn } from '@nice/ui/lib/utils'; // 类名合并工具(如果需要自定义样式)
export function PersonForm() {
// 假设表单状态(你可以扩展为 useState 或 Formik 等)
const [formData, setFormData] = React.useState({
name: '申屠嘉',
birth: '人名时间',
birthPlace: '楼烦国',
death: '西汉前179年',
deathPlace: '五胡十六国',
alias: '申屠天成',
// 其他字段基于图片
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = () => {
console.log('表单提交:', formData);
// 这里可以添加实际提交逻辑
};
return (
<Card className="w-full max-w-md">
<CardHeader className='flex justify-center items-center'>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-1/3"></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
<Label htmlFor="name"></Label>
</TableCell>
<TableCell>
<Input
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Label htmlFor="birth"></Label>
</TableCell>
<TableCell>
<Input
id="birth"
name="birth"
value={formData.birth}
onChange={handleChange}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>
<Label htmlFor="birthPlace"></Label>
<Input
id="birthPlace"
name="birthPlace"
value={formData.birthPlace}
onChange={handleChange}
className="mt-1"
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Label htmlFor="death"></Label>
</TableCell>
<TableCell>
<Input
id="death"
name="death"
value={formData.death}
onChange={handleChange}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>
<Label htmlFor="deathPlace"></Label>
<Input
id="deathPlace"
name="deathPlace"
value={formData.deathPlace}
onChange={handleChange}
className="mt-1"
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Label htmlFor="alias"></Label>
</TableCell>
<TableCell>
<Input
id="alias"
name="alias"
value={formData.alias}
onChange={handleChange}
/>
</TableCell>
</TableRow>
{/* 图片中有一些重复或空行,这里用空行模拟 */}
<TableRow>
<TableCell colSpan={2} className="h-4" /> {/* 空行 */}
</TableRow>
<TableRow>
<TableCell colSpan={2}>
<Label></Label>
{/* 可以添加更多输入 */}
</TableCell>
</TableRow>
</TableBody>
</Table>
<div className="mt-4 flex justify-end">
<Button onClick={handleSubmit}></Button>
</div>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,17 @@
'use client';
import { useSetPageInfo } from '@/components/providers/dashboard-provider';
import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter } from "@nice/ui/components/card";
import { Button } from "@nice/ui/components/button";
import { PersonForm } from './components/PersonForm';
import { ApplicationForm } from './components/ApplicationForm';
export default function Page() {
// 设置页面信息
useSetPageInfo({
title: "我的表单",
subtitle: '自定义表单'
});
return (
// <PersonForm />
<ApplicationForm />
)
}

View File

@ -0,0 +1,51 @@
"use client";
import { Card, CardHeader, CardTitle, CardContent } from "@nice/ui/components/card";
import { IconX, IconEdit } from "@tabler/icons-react";
import { Drawer, DrawerContent } from "@nice/ui/components/drawer";
import { useState } from "react";
import { ApplicationForm } from "../../dashboard/form/components/ApplicationForm";
export function ApplicationFormList({ data }) {
const [editingIndex, setEditingIndex] = useState<number | null>(null);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{data.map((item, idx) => (
<div key={idx} className="relative group">
<Card>
<CardHeader className="flex flex-row justify-between items-start">
<CardTitle>{item.name || "未命名"}</CardTitle>
<div className="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity absolute right-4 top-4">
<button
className="p-1 rounded hover:bg-red-100 text-red-500"
onClick={() => {/* 删除逻辑 */}}
>
<IconX size={16} />
</button>
<button
className="p-1 rounded hover:bg-blue-100 text-blue-500"
onClick={() => setEditingIndex(idx)}
>
<IconEdit size={16} />
</button>
</div>
</CardHeader>
<CardContent>
<div>{item.relation}</div>
<div>{item.birth}</div>
{/* 其它简要信息 */}
</CardContent>
</Card>
{/* Drawer 抽屉 */}
{editingIndex === idx && (
<Drawer open={true} onOpenChange={open => !open && setEditingIndex(null)}>
<DrawerContent className="w-full max-w-lg ml-auto">
<ApplicationForm initialData={item} onClose={() => setEditingIndex(null)} />
</DrawerContent>
</Drawer>
)}
</div>
))}
</div>
);
}

View File

@ -0,0 +1,48 @@
import { Card } from "@nice/ui/components/card";
import { ApplicationFormList } from "./components/ApplicationFormList";
import type { ApplicationFormData } from "../dashboard/form/components/ApplicationForm";
export default function Page() {
const mockData: ApplicationFormData[] = [
{
applicantName: "张三",
applicantLevel: "高级",
applicantBirth: "1990-01-01",
applicantEntry: "2015-06-01",
applicantMarriage: "2018-10-01",
familyList: [
{ name: "李四", birth: "1992-02-02", relation: "配偶", origin: "北京", work: "公司职员", address: "北京市朝阳区" },
{ name: "王五", birth: "2015-05-05", relation: "子女", origin: "北京", work: "学生", address: "北京市朝阳区" },
],
daysLived: "30",
daysLeft: "60",
daysApply: "15",
contact: "13800000000",
planCheckin: "2025-08-01",
planCheckout: "2025-08-16",
reason: "家人来京探亲,需临时住宿。",
opinions: ["同意", "", "", "", ""],
},
{
applicantName: "赵六",
applicantLevel: "中级",
applicantBirth: "1985-03-03",
applicantEntry: "2010-09-01",
applicantMarriage: "2012-12-12",
familyList: [
{ name: "钱七", birth: "1987-07-07", relation: "配偶", origin: "上海", work: "教师", address: "上海市浦东新区" },
],
daysLived: "10",
daysLeft: "20",
daysApply: "5",
contact: "13900000000",
planCheckin: "2025-09-01",
planCheckout: "2025-09-06",
reason: "短期探亲。",
opinions: ["", "", "", "", ""],
},
];
return <ApplicationFormList data={mockData} />;
}

View File

@ -86,6 +86,12 @@ const createPortalData = (user: User) => {
icon: IconFolder,
permissions: [SystemPermission.FILE_VIEW],
},
{
title: '我的表单',
url: '/dashboard/form',
icon: IconFileText,
permissions: [SystemPermission.FILE_VIEW],
},
];