Files
cunkebao_v3/Cunkebao/app/components/common/FormLayout.tsx
笔记本里的永平 5ff15472f5 feat: 本次提交更新内容如下
场景获客列表搞定
2025-07-07 17:08:27 +08:00

208 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import type { ReactNode } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
export interface FormSection {
title?: string
description?: string
children: ReactNode
}
export interface FormLayoutProps {
/** 表单标题 */
title?: string
/** 表单描述 */
description?: string
/** 表单部分 */
sections?: FormSection[]
/** 表单内容 */
children?: ReactNode
/** 提交按钮文本 */
submitText?: string
/** 取消按钮文本 */
cancelText?: string
/** 是否显示取消按钮 */
showCancel?: boolean
/** 是否显示重置按钮 */
showReset?: boolean
/** 重置按钮文本 */
resetText?: string
/** 提交处理函数 */
onSubmit?: () => void
/** 取消处理函数 */
onCancel?: () => void
/** 重置处理函数 */
onReset?: () => void
/** 是否禁用提交按钮 */
submitDisabled?: boolean
/** 是否显示加载状态 */
loading?: boolean
/** 自定义底部内容 */
footer?: ReactNode
/** 自定义类名 */
className?: string
/** 是否使用卡片包装 */
withCard?: boolean
/** 表单布局方向 */
direction?: "vertical" | "horizontal"
/** 表单标签宽度 (仅在水平布局时有效) */
labelWidth?: string
}
/**
* 统一的表单布局组件
*/
export function FormLayout({
title,
description,
sections = [],
children,
submitText = "提交",
cancelText = "取消",
showCancel = true,
showReset = false,
resetText = "重置",
onSubmit,
onCancel,
onReset,
submitDisabled = false,
loading = false,
footer,
className,
withCard = true,
direction = "vertical",
labelWidth = "120px",
}: FormLayoutProps) {
const FormContent = () => (
<>
{/* 表单内容 */}
<div className={cn("space-y-6", direction === "horizontal" && "form-horizontal")}>
{/* 如果有sections渲染sections */}
{sections.length > 0
? sections.map((section, index) => (
<div key={index} className="space-y-4">
{(section.title || section.description) && (
<div className="mb-4">
{section.title && <h3 className="text-lg font-medium">{section.title}</h3>}
{section.description && <p className="text-sm text-gray-500">{section.description}</p>}
</div>
)}
<div>{section.children}</div>
</div>
))
: // 否则直接渲染children
children}
</div>
{/* 表单底部 */}
{(onSubmit || onCancel || onReset || footer) && (
<div className="flex justify-end space-x-2 pt-6">
{footer || (
<>
{showReset && onReset && (
<Button type="button" variant="outline" onClick={onReset}>
{resetText}
</Button>
)}
{showCancel && onCancel && (
<Button type="button" variant="outline" onClick={onCancel}>
{cancelText}
</Button>
)}
{onSubmit && (
<Button
type="submit"
disabled={submitDisabled || loading}
onClick={onSubmit}
className={loading ? "opacity-70" : ""}
>
{loading ? "处理中..." : submitText}
</Button>
)}
</>
)}
</div>
)}
</>
)
// 添加水平布局的样式
if (direction === "horizontal") {
const style = document.createElement("style")
style.textContent = `
.form-horizontal .form-item {
display: flex;
align-items: flex-start;
margin-bottom: 1rem;
}
.form-horizontal .form-label {
width: ${labelWidth};
flex-shrink: 0;
padding-top: 0.5rem;
}
.form-horizontal .form-field {
flex: 1;
}
@media (max-width: 640px) {
.form-horizontal .form-item {
flex-direction: column;
align-items: stretch;
}
.form-horizontal .form-label {
width: 100%;
margin-bottom: 0.5rem;
padding-top: 0;
}
}
`
document.head.appendChild(style)
}
// 根据是否需要卡片包装返回不同的渲染结果
if (withCard) {
return (
<Card className={className}>
{(title || description) && (
<CardHeader>
{title && <CardTitle>{title}</CardTitle>}
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
)}
<CardContent>
<FormContent />
</CardContent>
</Card>
)
}
return (
<div className={className}>
{(title || description) && (
<div className="mb-6">
{title && <h2 className="text-xl font-semibold">{title}</h2>}
{description && <p className="text-sm text-gray-500 mt-1">{description}</p>}
</div>
)}
<FormContent />
</div>
)
}
/**
* 表单项组件 - 用于水平布局
*/
export function FormItem({ label, required, children }: { label: string; required?: boolean; children: ReactNode }) {
return (
<div className="form-item">
<div className="form-label">
{required && <span className="text-red-500 mr-1">*</span>}
<span>{label}:</span>
</div>
<div className="form-field">{children}</div>
</div>
)
}