OAFRONT6 完成合同台账表单分组

这个提交包含在:
2026-05-25 17:26:01 +08:00
父节点 7414e1ac92
当前提交 1b0b789392
共修改 5 个文件,包含 216 行新增和 186 行删除
+201 -171
查看文件
@@ -403,181 +403,211 @@ export function ContractsRegistryPage() {
</div>
) : null}
<div className="space-y-2">
<Label>合同编号</Label>
<Input
value={editor.contractNo}
onChange={(event) => setEditor((current) => ({ ...current, contractNo: event.target.value }))}
placeholder="例如 HT-2026-001"
/>
</div>
<div className="space-y-6">
<section className="space-y-4">
<div>
<h4 className="text-sm font-semibold text-zinc-900">基础信息</h4>
<p className="text-xs text-zinc-500">填写编号、类型、相对方、摘要和金额。</p>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>合同类型</Label>
<select
className="select-std"
value={editor.contractType}
onChange={(event) => setEditor((current) => ({ ...current, contractType: event.target.value as ContractRegistryType }))}
>
{CONTRACT_TYPE_OPTIONS.map((item) => (
<option key={item} value={item}>
{contractTypeLabel(item)}
</option>
))}
</select>
</div>
<div className="space-y-2">
<Label>状态</Label>
<select
className="select-std"
value={editor.status}
onChange={(event) => setEditor((current) => ({ ...current, status: event.target.value as ContractRegistryPersistedStatus }))}
>
{EDITOR_STATUS_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
</div>
</div>
<div className="space-y-2">
<Label>合同编号</Label>
<Input
value={editor.contractNo}
onChange={(event) => setEditor((current) => ({ ...current, contractNo: event.target.value }))}
placeholder="例如 HT-2026-001"
/>
</div>
<div className="space-y-2">
<Label>关联项目</Label>
<select
className="select-std"
value={editor.projectId}
onChange={(event) => setEditor((current) => ({ ...current, projectId: event.target.value }))}
>
<option value="">未关联</option>
{projectOptions.map((item) => (
<option key={item.id} value={item.id}>
{item.projectNo} · {item.name}
</option>
))}
</select>
</div>
<div className="space-y-2">
<Label>相对方</Label>
<Input
value={editor.counterpartyName}
onChange={(event) => setEditor((current) => ({ ...current, counterpartyName: event.target.value }))}
placeholder="合同对方名称"
/>
</div>
<div className="space-y-2">
<Label>摘要</Label>
<Textarea
value={editor.summary}
onChange={(event) => setEditor((current) => ({ ...current, summary: event.target.value }))}
placeholder="只写最小必要摘要,不录入涉密正文"
/>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>合同金额</Label>
<Input
value={editor.amount}
onChange={(event) => setEditor((current) => ({ ...current, amount: event.target.value }))}
placeholder="例如 100000.00"
/>
</div>
<div className="space-y-2">
<Label>币种</Label>
<Input
value={editor.currency}
onChange={(event) => setEditor((current) => ({ ...current, currency: event.target.value.toUpperCase() }))}
placeholder="CNY"
/>
</div>
</div>
{amountInvalid ? <div className="text-xs text-red-600">合同金额需为最多 12 位整数、最多 2 位小数。</div> : null}
<div className="grid gap-3 md:grid-cols-3">
<div className="space-y-2">
<Label>签署日期</Label>
<DateTextInput
value={editor.signedDate}
onChange={(event) => setEditor((current) => ({ ...current, signedDate: event.target.value }))}
/>
</div>
<div className="space-y-2">
<Label>生效日期</Label>
<DateTextInput
value={editor.effectiveDate}
onChange={(event) => setEditor((current) => ({ ...current, effectiveDate: event.target.value }))}
/>
</div>
<div className="space-y-2">
<Label>到期日期</Label>
<DateTextInput
value={editor.expiryDate}
onChange={(event) => setEditor((current) => ({ ...current, expiryDate: event.target.value }))}
/>
</div>
</div>
<div className="space-y-2">
<Label>归档位置</Label>
<Input
value={editor.archiveLocation}
onChange={(event) => setEditor((current) => ({ ...current, archiveLocation: event.target.value }))}
placeholder="NAS/文件柜/共享盘路径"
/>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>离线档案号</Label>
<Input
value={editor.offlineArchiveNo}
onChange={(event) => setEditor((current) => ({ ...current, offlineArchiveNo: event.target.value }))}
placeholder="可选"
/>
</div>
<div className="space-y-2">
<Label>密级</Label>
<select
className="select-std"
value={editor.confidentialityLevel}
onChange={(event) => setEditor((current) => ({ ...current, confidentialityLevel: event.target.value as ConfidentialityLevel }))}
>
{CONFIDENTIALITY_OPTIONS.map((item) => (
<option key={item} value={item}>
{confidentialityLabel(item)}
</option>
))}
</select>
</div>
</div>
<div className="space-y-2">
<Label>责任人</Label>
{canBrowseEmployees ? (
<select
className="select-std"
value={editor.ownerEmployeeId}
onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))}
>
<option value="">默认当前用户</option>
{employeeOptions.map((item) => (
<option key={item.id} value={item.id}>
{item.name} · {item.employeeNo}
</option>
))}
</select>
) : (
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm">
<div className="font-medium text-zinc-900">{readonlyOwnerLabel}</div>
<div className="mt-1 text-xs text-zinc-500">
当前账号无人员目录权限,新建时使用当前用户,编辑时保留原责任人。
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>合同类型</Label>
<select
className="select-std"
value={editor.contractType}
onChange={(event) => setEditor((current) => ({ ...current, contractType: event.target.value as ContractRegistryType }))}
>
{CONTRACT_TYPE_OPTIONS.map((item) => (
<option key={item} value={item}>
{contractTypeLabel(item)}
</option>
))}
</select>
</div>
<div className="space-y-2">
<Label>状态</Label>
<select
className="select-std"
value={editor.status}
onChange={(event) => setEditor((current) => ({ ...current, status: event.target.value as ContractRegistryPersistedStatus }))}
>
{EDITOR_STATUS_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
</div>
</div>
)}
<div className="space-y-2">
<Label>相对方</Label>
<Input
value={editor.counterpartyName}
onChange={(event) => setEditor((current) => ({ ...current, counterpartyName: event.target.value }))}
placeholder="合同对方名称"
/>
</div>
<div className="space-y-2">
<Label>摘要</Label>
<Textarea
value={editor.summary}
onChange={(event) => setEditor((current) => ({ ...current, summary: event.target.value }))}
placeholder="只写最小必要摘要,不录入涉密正文"
/>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>合同金额</Label>
<Input
value={editor.amount}
onChange={(event) => setEditor((current) => ({ ...current, amount: event.target.value }))}
placeholder="例如 100000.00"
/>
</div>
<div className="space-y-2">
<Label>币种</Label>
<Input
value={editor.currency}
onChange={(event) => setEditor((current) => ({ ...current, currency: event.target.value.toUpperCase() }))}
placeholder="CNY"
/>
</div>
</div>
{amountInvalid ? <div className="text-xs text-red-600">合同金额需为最多 12 位整数、最多 2 位小数。</div> : null}
</section>
<section className="space-y-4 border-t border-zinc-200 pt-5">
<div>
<h4 className="text-sm font-semibold text-zinc-900">项目关联</h4>
<p className="text-xs text-zinc-500">将合同归入项目,便于项目页汇总合同、费用和交付风险。</p>
</div>
<div className="space-y-2">
<Label>关联项目</Label>
<select
className="select-std"
value={editor.projectId}
onChange={(event) => setEditor((current) => ({ ...current, projectId: event.target.value }))}
>
<option value="">未关联</option>
{projectOptions.map((item) => (
<option key={item.id} value={item.id}>
{item.projectNo} · {item.name}
</option>
))}
</select>
</div>
</section>
<section className="space-y-4 border-t border-zinc-200 pt-5">
<div>
<h4 className="text-sm font-semibold text-zinc-900">日期与续约</h4>
<p className="text-xs text-zinc-500">用于到期预警、续约草稿和归档判断。</p>
</div>
<div className="grid gap-3 md:grid-cols-3">
<div className="space-y-2">
<Label>签署日期</Label>
<DateTextInput
value={editor.signedDate}
onChange={(event) => setEditor((current) => ({ ...current, signedDate: event.target.value }))}
/>
</div>
<div className="space-y-2">
<Label>生效日期</Label>
<DateTextInput
value={editor.effectiveDate}
onChange={(event) => setEditor((current) => ({ ...current, effectiveDate: event.target.value }))}
/>
</div>
<div className="space-y-2">
<Label>到期日期</Label>
<DateTextInput
value={editor.expiryDate}
onChange={(event) => setEditor((current) => ({ ...current, expiryDate: event.target.value }))}
/>
</div>
</div>
</section>
<section className="space-y-4 border-t border-zinc-200 pt-5">
<div>
<h4 className="text-sm font-semibold text-zinc-900">归档与责任</h4>
<p className="text-xs text-zinc-500">登记线下归档位置、密级和责任人,不上传涉密正文。</p>
</div>
<div className="space-y-2">
<Label>归档位置</Label>
<Input
value={editor.archiveLocation}
onChange={(event) => setEditor((current) => ({ ...current, archiveLocation: event.target.value }))}
placeholder="文件柜、档案盒或归档目录摘要"
/>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<Label>离线档案号</Label>
<Input
value={editor.offlineArchiveNo}
onChange={(event) => setEditor((current) => ({ ...current, offlineArchiveNo: event.target.value }))}
placeholder="可选"
/>
</div>
<div className="space-y-2">
<Label>密级</Label>
<select
className="select-std"
value={editor.confidentialityLevel}
onChange={(event) => setEditor((current) => ({ ...current, confidentialityLevel: event.target.value as ConfidentialityLevel }))}
>
{CONFIDENTIALITY_OPTIONS.map((item) => (
<option key={item} value={item}>
{confidentialityLabel(item)}
</option>
))}
</select>
</div>
</div>
<div className="space-y-2">
<Label>责任人</Label>
{canBrowseEmployees ? (
<select
className="select-std"
value={editor.ownerEmployeeId}
onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))}
>
<option value="">默认当前用户</option>
{employeeOptions.map((item) => (
<option key={item.id} value={item.id}>
{item.name} · {item.employeeNo}
</option>
))}
</select>
) : (
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm">
<div className="font-medium text-zinc-900">{readonlyOwnerLabel}</div>
<div className="mt-1 text-xs text-zinc-500">
当前账号无人员目录权限,新建时使用当前用户,编辑时保留原责任人。
</div>
</div>
)}
</div>
</section>
</div>
<Button