R108/A1-A7 固化企业微信移动端视觉门禁

这个提交包含在:
2026-05-07 21:23:03 +08:00
父节点 230d8f53db
当前提交 ee4aebd237
共修改 11 个文件,包含 722 行新增和 24 行删除
+25 -17
查看文件
@@ -99,6 +99,17 @@ function workflowStatusLabel(status: string) {
return status;
}
function isInternalId(value: string | null | undefined) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value ?? "");
}
function publicReference(value: string | null | undefined) {
if (!value || isInternalId(value)) {
return null;
}
return value;
}
function resolveErrorMessage(error: unknown) {
if (isApiError(error)) {
return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
@@ -170,18 +181,17 @@ function matchesEntityFilter(entityType: string | null, filter: EntityFilter) {
function toVisibleRemoteTask(task: WorkflowTaskSummary): VisibleTaskCard {
const entityType = task.workflowInstance?.entityType ?? null;
const entityId = task.workflowInstance?.entityId ?? null;
return {
id: task.id,
title: task.stepName,
subtitle:
task.status === "completed"
? `业务:${entityLabel(entityType)} · 完成于 ${formatDateTime(task.completedAt)}`
: `业务:${entityLabel(entityType)} · 实体ID:${entityId ?? "-"}`,
: `业务:${entityLabel(entityType)}`,
status: task.status,
entityType,
entityId,
reference: entityId ?? task.id,
entityId: task.workflowInstance?.entityId ?? null,
reference: publicReference(task.workflowInstance?.entityId),
allowedActions: task.allowedActions,
completedAt: task.completedAt,
isCached: false,
@@ -351,7 +361,7 @@ export function MobileApprovalsPage() {
status: item.detail.task.status,
entityType: item.taskSnapshot.entityType ?? item.detail.workflowInstance?.entityType ?? null,
entityId: item.taskSnapshot.entityId,
reference: item.taskSnapshot.reference ?? item.detail.task.id,
reference: publicReference(item.taskSnapshot.reference),
allowedActions: item.detail.allowedActions,
completedAt: item.detail.task.completedAt,
isCached: true,
@@ -379,6 +389,7 @@ export function MobileApprovalsPage() {
const selectedTask = detailQuery.data ?? cachedDetailQuery.data?.detail ?? selectedCachedTaskDetail;
const selectedReference = selectedTask ? resolveWorkflowTaskReferenceSnapshot(selectedTask) : null;
const selectedDisplayReference = publicReference(selectedReference?.reference);
const selectedTaskAllowsActions = Boolean(taskTab === "pending" && selectedTask?.task.status === "pending");
const isOfflineUncachedDeepLink = Boolean(selectedTaskId) && !selectedTask && !canUseRemote;
@@ -738,7 +749,7 @@ export function MobileApprovalsPage() {
<PageScaffold className="space-y-4">
<PageHeader
title="移动审批"
subtitle={deepLinkTaskId ? `待办:${deepLinkTaskId}` : "处理待办。"}
subtitle={deepLinkTaskId ? "已定位指定待办。" : "处理待办。"}
/>
{!isOnline ? (
@@ -915,7 +926,7 @@ export function MobileApprovalsPage() {
className="mt-1 h-4 w-4 rounded border-zinc-300"
checked={isBatchSelected}
onChange={() => toggleBatchTask(task.id)}
aria-label={`选择任务 ${task.id}`}
aria-label={`选择${entityLabel(task.entityType)}待办`}
/>
<button
type="button"
@@ -927,8 +938,8 @@ export function MobileApprovalsPage() {
<div className="font-medium text-zinc-900">{task.title}</div>
<StatusBadge status={task.status} label={workflowStatusLabel(task.status)} />
</div>
<div className="mt-1 text-xs text-zinc-500">任务:{task.id}</div>
<div className="mt-1 text-xs text-zinc-500">{task.subtitle}</div>
{task.reference ? <div className="mt-1 text-xs text-zinc-500">业务单号:{task.reference}</div> : null}
{task.isCached ? <div className="mt-1 text-xs text-zinc-500">来源:离线缓存</div> : null}
</button>
</div>
@@ -949,11 +960,8 @@ export function MobileApprovalsPage() {
<div className="font-medium text-zinc-900">{task.title}</div>
<StatusBadge status={task.status} label={workflowStatusLabel(task.status)} />
</div>
<div className="mt-1 text-xs text-zinc-500">任务:{task.id}</div>
<div className="mt-1 text-xs text-zinc-500">{task.subtitle}</div>
{task.reference && task.reference !== task.id ? (
<div className="mt-1 text-xs text-zinc-500">业务参考:{task.reference}</div>
) : null}
{task.reference ? <div className="mt-1 text-xs text-zinc-500">业务单号:{task.reference}</div> : null}
{task.isCached ? <div className="mt-1 text-xs text-zinc-500">来源:离线缓存</div> : null}
</button>
);
@@ -978,7 +986,7 @@ export function MobileApprovalsPage() {
{isOfflineUncachedDeepLink ? (
<p className="rounded-lg border border-orange-200 bg-orange-50 px-3 py-2 text-sm text-orange-700">
离线未缓存该待办,请联网后重试。任务号:{selectedTaskId}
离线未缓存该待办,请联网后重试。
</p>
) : null}
@@ -988,7 +996,7 @@ export function MobileApprovalsPage() {
<div>任务状态:{workflowStatusLabel(selectedTask.task.status)}</div>
<div>步骤:{selectedTask.task.stepName}</div>
<div>对象类型:{entityLabel(selectedTask.workflowInstance?.entityType)}</div>
<div>业务参考:{selectedReference?.reference ?? "-"}</div>
<div>业务单号:{selectedDisplayReference ?? "-"}</div>
<div>创建时间:{formatDateTime(selectedTask.task.createdAt)}</div>
<div>完成时间:{formatDateTime(selectedTask.task.completedAt)}</div>
{selectedTask.reimbursement ? (
@@ -1007,7 +1015,7 @@ export function MobileApprovalsPage() {
) : null}
{selectedTask.purchaseRequest ? (
<>
<div>采购单号:{selectedTask.purchaseRequest.requestNo ?? selectedTask.purchaseRequest.id}</div>
<div>采购单号:{selectedTask.purchaseRequest.requestNo ?? "-"}</div>
<div>申请人:{selectedTask.purchaseRequest.applicant?.name ?? "-"}</div>
<div>供应商:{selectedTask.purchaseRequest.supplier?.name ?? "-"}</div>
<div>金额:{selectedTask.purchaseRequest.totalAmount ?? "-"}</div>
@@ -1015,7 +1023,7 @@ export function MobileApprovalsPage() {
) : null}
{selectedTask.travelRequest ? (
<>
<div>差旅单号:{selectedTask.travelRequest.requestNo ?? selectedTask.travelRequest.id}</div>
<div>差旅单号:{selectedTask.travelRequest.requestNo ?? "-"}</div>
<div>申请人:{selectedTask.travelRequest.applicant?.name ?? "-"}</div>
<div>出差类型:{selectedTask.travelRequest.tripType === "international" ? "国际" : "国内"}</div>
<div>预估金额:{selectedTask.travelRequest.estimatedAmount ?? "-"}</div>
@@ -1154,7 +1162,7 @@ export function MobileApprovalsPage() {
{currentQueueItems.map((item) => (
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-3 text-sm">
<div className="font-medium text-zinc-900">
{item.taskSnapshot.reference ?? item.taskId} · {item.action === "approve" ? "通过" : "驳回"}
{publicReference(item.taskSnapshot.reference) ?? entityLabel(item.taskSnapshot.entityType)} · {item.action === "approve" ? "通过" : "驳回"}
</div>
<div className="text-zinc-500">业务:{entityLabel(item.taskSnapshot.entityType)} · 步骤:{item.taskSnapshot.stepName ?? "-"}</div>
<div className="text-zinc-500">状态:{queueStatusLabel(item.status)} · 更新时间:{formatDateTime(item.updatedAt)}</div>
+8 -3
查看文件
@@ -243,10 +243,13 @@ export function MobileCapturePage() {
setError("");
if (!isOnline) {
const enqueuedItems: MobileUploadQueueItem[] = [];
for (const file of selectedFiles) {
await enqueueInvoiceUpload(file);
enqueuedItems.push(await enqueueInvoiceUpload(file));
}
await queryClient.invalidateQueries({ queryKey: ["mobile", "invoice-upload-queue"] });
queryClient.setQueryData<MobileUploadQueueItem[]>(["mobile", "invoice-upload-queue"], (current) =>
[...(current ?? []), ...enqueuedItems].sort((a, b) => a.createdAt.localeCompare(b.createdAt)),
);
setSelectedFiles([]);
setMessage(`当前离线,已加入待上传队列 ${selectedFiles.length} 项`);
return;
@@ -296,7 +299,9 @@ export function MobileCapturePage() {
data-testid={`mobile-capture-remove-${item.id}`}
onClick={async () => {
await removeQueuedInvoiceUpload(item.id);
await queryClient.invalidateQueries({ queryKey: ["mobile", "invoice-upload-queue"] });
queryClient.setQueryData<MobileUploadQueueItem[]>(["mobile", "invoice-upload-queue"], (current) =>
(current ?? []).filter((queuedItem) => queuedItem.id !== item.id),
);
}}
size="sm"
type="button"