feat(P15B-1,P15B-6): 完成请假V1闭环

这个提交包含在:
2026-03-07 08:11:24 +08:00
父节点 03c5a78485
当前提交 4ba311199d
共修改 23 个文件,包含 3202 行新增和 308 行删除
+61 -36
查看文件
@@ -19,6 +19,16 @@ import { Textarea } from "../components/ui/textarea";
type MobileAction = "approve" | "reject" | "return" | "transfer";
function entityLabel(entityType: string | undefined) {
if (entityType === "leave") {
return "请假";
}
if (entityType === "reimbursement") {
return "报销";
}
return entityType || "未知";
}
export function MobileApprovalsPage() {
const queryClient = useQueryClient();
const [selectedTaskId, setSelectedTaskId] = useState("");
@@ -97,6 +107,7 @@ export function MobileApprovalsPage() {
const pendingTasks = tasksQuery.data?.items ?? [];
const selectedTask = detailQuery.data;
const isLeaveTask = selectedTask?.workflowInstance?.entityType === "leave";
return (
<div className="space-y-4">
@@ -130,7 +141,7 @@ export function MobileApprovalsPage() {
>
<div className="font-medium">{task.stepName}</div>
<div className="text-xs text-zinc-500">任务ID: {task.id}</div>
<div className="text-xs text-zinc-500">状态: {task.status}</div>
<div className="text-xs text-zinc-500">业务: {entityLabel(task.workflowInstance?.entityType)}</div>
</button>
))}
</div>
@@ -150,9 +161,21 @@ export function MobileApprovalsPage() {
<div className="rounded-lg border border-zinc-200 p-3 text-sm">
<div>任务状态:{selectedTask.task.status}</div>
<div>步骤:{selectedTask.task.stepName}</div>
<div>报销单号:{selectedTask.reimbursement?.formNumber ?? "-"}</div>
<div>金额:{selectedTask.reimbursement?.totalAmount ?? "-"}</div>
<div>提交人:{selectedTask.reimbursement?.submitter?.name ?? "-"}</div>
<div>对象类型:{entityLabel(selectedTask.workflowInstance?.entityType)}</div>
{selectedTask.reimbursement ? (
<>
<div>报销单号:{selectedTask.reimbursement.formNumber ?? "-"}</div>
<div>金额:{selectedTask.reimbursement.totalAmount ?? "-"}</div>
<div>提交人:{selectedTask.reimbursement.submitter?.name ?? "-"}</div>
</>
) : null}
{selectedTask.leaveRequest ? (
<>
<div>假期类型:{selectedTask.leaveRequest.leaveType?.name ?? "-"}</div>
<div>申请人:{selectedTask.leaveRequest.owner?.name ?? "-"}</div>
<div>请假天数:{selectedTask.leaveRequest.requestedDays}</div>
</>
) : null}
</div>
<Textarea
@@ -192,40 +215,42 @@ export function MobileApprovalsPage() {
</Button>
</div>
<div className="space-y-2 rounded-lg border border-zinc-200 p-3 text-sm">
<p className="font-medium text-zinc-800">转审</p>
<select
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
data-testid="mobile-approvals-transfer-to"
onChange={(event) => setTransferToApproverId(event.target.value)}
value={transferToApproverId}
>
<option value="">请选择目标审批人</option>
{(approversQuery.data?.items ?? []).map((approver) => (
<option key={approver.id} value={approver.id}>
{approver.name} ({approver.employeeNo})
</option>
))}
</select>
{!isLeaveTask && selectedTask.allowedActions.includes("transfer") ? (
<div className="space-y-2 rounded-lg border border-zinc-200 p-3 text-sm">
<p className="font-medium text-zinc-800">转审</p>
<select
className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm"
data-testid="mobile-approvals-transfer-to"
onChange={(event) => setTransferToApproverId(event.target.value)}
value={transferToApproverId}
>
<option value="">请选择目标审批人</option>
{(approversQuery.data?.items ?? []).map((approver) => (
<option key={approver.id} value={approver.id}>
{approver.name} ({approver.employeeNo})
</option>
))}
</select>
<Textarea
data-testid="mobile-approvals-transfer-reason"
onChange={(event) => setTransferReason(event.target.value)}
placeholder="转审理由(至少10字)"
rows={2}
value={transferReason}
/>
<Textarea
data-testid="mobile-approvals-transfer-reason"
onChange={(event) => setTransferReason(event.target.value)}
placeholder="转审理由(至少10字)"
rows={2}
value={transferReason}
/>
<Button
data-testid="mobile-approvals-transfer"
disabled={actionMutation.isPending || !selectedTask.allowedActions.includes("transfer")}
onClick={() => actionMutation.mutate("transfer")}
type="button"
variant="secondary"
>
提交转审
</Button>
</div>
<Button
data-testid="mobile-approvals-transfer"
disabled={actionMutation.isPending || !selectedTask.allowedActions.includes("transfer")}
onClick={() => actionMutation.mutate("transfer")}
type="button"
variant="secondary"
>
提交转审
</Button>
</div>
) : null}
</>
)}
</div>