修改完成待测试

This commit is contained in:
2026-09-13 17:45:47 +08:00
parent 9ad572907b
commit bbc024f7f2
80 changed files with 3844 additions and 1672 deletions
+99 -6
View File
@@ -22,15 +22,35 @@
label="新建证件档案"
@click="openDocumentCreate"
/>
<BatchManagementBar
v-if="deletableDocuments.length"
resource-name="证件档案"
:active="documentBatch.selectionMode.value"
:selected-count="documentBatch.selectedCount.value"
:all-selected="documentBatch.allSelected.value"
:busy="documentBatch.deleting.value"
@start="documentBatch.enterSelectionMode"
@finish="documentBatch.exitSelectionMode"
@toggle-all="documentBatch.toggleAll"
@delete="documentBatch.requestDelete"
/>
<text v-if="documentBatch.notice.value" class="document-batch-notice" role="status">{{ documentBatch.notice.value }}</text>
<text v-if="documentBatch.error.value" class="document-dialog-error" role="alert">{{ documentBatch.error.value }}</text>
<text v-if="!documents.length" class="document-dialog-empty">{{ personId ? "这位成员还没有可查看的重要证件" : "当前家谱还没有可查看的重要证件" }}</text>
<view
v-for="documentSummary in documents"
:key="documentSummary.documentId"
class="document-list-item"
role="button"
:aria-label="`查看${documentSummary.documentTitle}`"
@click="openDocument(documentSummary)"
:aria-label="documentBatch.selectionMode.value && documentSummary.canDelete ? `${documentBatch.isSelected(documentSummary) ? '取消选择' : '选择'}${documentSummary.documentTitle}` : `查看${documentSummary.documentTitle}`"
@click="handleDocumentSummaryClick(documentSummary)"
>
<BatchSelectionMark
v-if="documentBatch.selectionMode.value && documentSummary.canDelete"
:selected="documentBatch.isSelected(documentSummary)"
:label="`证件档案:${documentSummary.documentTitle}`"
@toggle="documentBatch.toggleSelection(documentSummary)"
/>
<view>
<text>{{ documentSummary.documentTitle }}</text>
<text
@@ -113,6 +133,11 @@
<button v-if="documentDialogMode === 'create'" class="document-upload-button" :disabled="documentState === 'submitting'" @click="uploadDocumentImage">
{{ documentUploadReceipt ? `已选择${documentUploadReceipt.fileName || '证件图片'}` : '添加证件图片选填' }}
</button>
<template v-if="documentDialogMode === 'create'">
<text class="document-form-hint">内容密码选填填写后会先创建档案再立即启用密码保护</text>
<input v-model="documentPassword" class="document-form-field" password maxlength="128" placeholder="请输入8至128位内容密码" />
<input v-model="documentPasswordConfirm" class="document-form-field" password maxlength="128" placeholder="请再次输入内容密码" />
</template>
<text v-if="documentError" class="document-dialog-error">{{ documentError }}</text>
<AppButton block :disabled="documentState === 'submitting'" :label="documentState === 'submitting' ? '正在保存' : '保存证件档案'" @click="saveDocument" />
</template>
@@ -129,6 +154,18 @@
</template>
</view>
</AppDialog>
<AppDialog
:visible="documentBatch.confirmationVisible.value"
eyebrow="批量删除"
title="将选中的证件档案删除?"
message="删除后,档案和已关联的证件文件都无法恢复。"
:confirm-text="documentBatch.deleting.value ? '正在删除' : '确认删除'"
cancel-text="继续选择"
show-cancel
:close-on-mask="false"
@confirm="documentBatch.confirmDelete"
@cancel="documentBatch.cancelDelete"
/>
<AppDialog
:visible="documentDeleteVisible"
eyebrow="重要证件"
@@ -182,6 +219,8 @@ import { computed, onUnmounted, reactive, ref, watch } from "vue";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
import AppLoading from "@/components/AppLoading.vue";
import BatchManagementBar from "@/components/BatchManagementBar.vue";
import BatchSelectionMark from "@/components/BatchSelectionMark.vue";
import ContentPasswordRecoveryDialog from "@/components/ContentPasswordRecoveryDialog.vue";
import {
PERSON_DOCUMENT_RESOURCE_USAGE,
@@ -194,6 +233,7 @@ import {
} from "@/services/api/request-controller.js";
import { personDocumentApi } from "@/services/api/person-document-service.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
import { useBatchDeletion } from "@/composables/use-batch-deletion.js";
import { isImagePickCancelled, pickAndUploadImage } from "@/utils/media-upload.js";
import { createNonIdempotentWriteGuard } from "@/utils/non-idempotent-write-guard.js";
@@ -240,6 +280,17 @@ let documentCreateGuard = createNonIdempotentWriteGuard();
let documentResourceCreateGuard = createNonIdempotentWriteGuard();
let isActive = true;
let workflowVersion = 0;
const documentBatch = useBatchDeletion({
items: documents,
getId: (document) => document?.documentId,
deleteOne: (document) =>
personDocumentApi.deletePersonDocument(props.genealogyId, document.documentId, {
requestController: documentDeletionRequestController,
}),
resourceName: "证件档案",
isActive: () => isActive && documentDialogVisible.value,
});
const deletableDocuments = documentBatch.deletableItems;
const documentDialogTitle = computed(() => {
switch (documentDialogMode.value) {
@@ -340,6 +391,7 @@ const resetDocumentWorkflow = () => {
documentAccessToken.value = "";
documentError.value = "";
documentUploadReceipt.value = null;
documentBatch.exitSelectionMode();
};
const open = async () => {
@@ -367,6 +419,7 @@ const open = async () => {
]);
if (!isCurrentPersonContext(documentPersonId, activeWorkflow)) return;
documents.value = documentRows;
documentBatch.exitSelectionMode();
documentTypeOptions.value = typeOptions;
documentState.value = "ready";
} catch (error) {
@@ -396,6 +449,8 @@ const openDocumentCreate = () => {
description: "",
});
documentUploadReceipt.value = null;
documentPassword.value = "";
documentPasswordConfirm.value = "";
documentDialogMode.value = "create";
documentState.value = "ready";
documentError.value = "";
@@ -454,6 +509,16 @@ const createDocument = async () => {
documentError.value = "请填写证件名称。";
return;
}
if (documentPassword.value || documentPasswordConfirm.value) {
if (documentPassword.value.length < 8 || documentPassword.value.length > 128) {
documentError.value = "内容密码必须为8至128位。";
return;
}
if (documentPassword.value !== documentPasswordConfirm.value) {
documentError.value = "两次输入的内容密码不一致。";
return;
}
}
const documentPersonId = props.personId;
const createPayload = { lineagePersonId: documentPersonId, ...documentForm };
const createAttempt = documentCreateGuard.begin(createPayload);
@@ -467,6 +532,7 @@ const createDocument = async () => {
const activeWorkflow = workflowVersion;
const uploadReceipt = documentUploadReceipt.value;
let createdDocumentId = "";
let postCreateStage = "creating";
try {
const createdDocument = await personDocumentApi.createPersonDocument(
props.genealogyId,
@@ -475,6 +541,7 @@ const createDocument = async () => {
);
if (!isCurrentPersonContext(documentPersonId, activeWorkflow)) return;
createdDocumentId = createdDocument.documentId;
postCreateStage = "linking-file";
if (uploadReceipt?.ossId) {
await personDocumentApi.addPersonDocumentResource(
props.genealogyId,
@@ -487,6 +554,19 @@ const createDocument = async () => {
);
if (!isCurrentPersonContext(documentPersonId, activeWorkflow)) return;
}
postCreateStage = "setting-password";
if (documentPassword.value) {
await personDocumentApi.setPersonDocumentPassword(
props.genealogyId,
createdDocument.documentId,
documentPassword.value,
{ requestController: documentSaveRequestController },
);
if (!isCurrentPersonContext(documentPersonId, activeWorkflow)) return;
}
postCreateStage = "complete";
documentPassword.value = "";
documentPasswordConfirm.value = "";
await open();
} catch (error) {
if (
@@ -494,6 +574,8 @@ const createDocument = async () => {
isCurrentPersonContext(documentPersonId, activeWorkflow) &&
!isRequestCancelled(error)
) {
documentPassword.value = "";
documentPasswordConfirm.value = "";
await loadDocumentDetail(createdDocumentId, "");
if (
isCurrentDocumentContext(
@@ -502,12 +584,14 @@ const createDocument = async () => {
activeWorkflow,
)
) {
documentError.value =
"证件档案已创建,图片关联结果暂时无法确认。请检查文件列表后再添加,避免重复创建档案。";
documentError.value = postCreateStage === "setting-password"
? "证件档案已创建,图片也已处理,但内容密码设置结果暂时无法确认。请在详情中重新设置,当前档案可能尚未受到密码保护。"
: "证件档案已创建,但图片关联结果暂时无法确认。请检查文件列表后再添加,避免重复创建档案。";
} else if (isCurrentPersonContext(documentPersonId, activeWorkflow)) {
documentState.value = "error";
documentError.value =
"证件档案已创建,但暂时无法确认图片是否关联。请关闭后重新打开证件列表,避免重复创建档案。";
documentError.value = postCreateStage === "setting-password"
? "证件档案已创建,但内容密码设置结果暂时无法确认。请重新打开详情检查,避免重复创建档案。"
: "证件档案已创建,但暂时无法确认图片是否关联。请关闭后重新打开证件列表,避免重复创建档案。";
}
return;
}
@@ -600,6 +684,13 @@ const openDocument = async (documentSummary) => {
}
await loadDocumentDetail(documentSummary.documentId, "");
};
const handleDocumentSummaryClick = (documentSummary) => {
if (documentBatch.selectionMode.value && documentSummary?.canDelete) {
documentBatch.toggleSelection(documentSummary);
return;
}
openDocument(documentSummary);
};
const unlockDocument = async () => {
if (documentState.value === "submitting" || !selectedDocument.value) return;
documentError.value = "";
@@ -1001,6 +1092,7 @@ onUnmounted(() => {
font-size: clamp(14px, 22rpx, 17px);
}
.document-form-field--textarea { min-height: 132rpx; }
.document-form-hint { display: block; margin-top: 16rpx; color: $ink-muted; font-size: clamp(13px, 21rpx, 16px); line-height: 1.5; }
.document-upload-button {
width: 100%;
min-height: 76rpx;
@@ -1068,4 +1160,5 @@ onUnmounted(() => {
.document-dialog-empty,
.document-dialog-error { padding: 24rpx 10rpx; text-align: center; }
.document-dialog-error { color: $brand-red; }
.document-batch-notice { display: block; padding: 14rpx 10rpx; color: #426538; font-size: clamp(13px, 21rpx, 16px); line-height: 1.55; }
</style>