修改完成待测试
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
<template>
|
||||
<view v-if="visible" class="member-date-picker" role="dialog" :aria-label="title">
|
||||
<view class="member-date-picker__mask" @click="$emit('cancel')" />
|
||||
<view class="member-date-picker__panel">
|
||||
<view class="member-date-picker__heading">
|
||||
<text class="member-date-picker__title">{{ title }}</text>
|
||||
<text class="member-date-picker__hint">点击年份可直接切换,左右按钮切换月份</text>
|
||||
</view>
|
||||
|
||||
<view class="member-date-picker__toolbar">
|
||||
<button
|
||||
class="member-date-picker__month-button"
|
||||
aria-label="上个月"
|
||||
:disabled="!canSelectPreviousMonth"
|
||||
@click="changeMonth(-1)"
|
||||
>
|
||||
<image
|
||||
class="member-date-picker__month-icon member-date-picker__month-icon--previous"
|
||||
src="/static/assets/foundation/transparent/chevron-right.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</button>
|
||||
<view class="member-date-picker__current-month">
|
||||
<picker
|
||||
:range="yearLabels"
|
||||
:value="selectedYearIndex"
|
||||
@change="selectYear"
|
||||
>
|
||||
<button class="member-date-picker__year-button">
|
||||
<text>选择年份</text>
|
||||
<text>{{ selectedYear }}年</text>
|
||||
</button>
|
||||
</picker>
|
||||
<picker
|
||||
:range="monthLabels"
|
||||
:value="selectedMonthIndex"
|
||||
@change="selectMonth"
|
||||
>
|
||||
<button class="member-date-picker__month-button-direct" aria-label="选择月份">
|
||||
<text>选择月份</text>
|
||||
<text>{{ selectedMonth }}月</text>
|
||||
</button>
|
||||
</picker>
|
||||
</view>
|
||||
<button
|
||||
class="member-date-picker__month-button"
|
||||
aria-label="下个月"
|
||||
:disabled="!canSelectNextMonth"
|
||||
@click="changeMonth(1)"
|
||||
>
|
||||
<image
|
||||
class="member-date-picker__month-icon"
|
||||
src="/static/assets/foundation/transparent/chevron-right.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="member-date-picker__weekdays">
|
||||
<text v-for="weekday in weekdays" :key="weekday">{{ weekday }}</text>
|
||||
</view>
|
||||
<view class="member-date-picker__calendar">
|
||||
<view
|
||||
v-for="calendarDay in calendarDays"
|
||||
:key="calendarDay.key"
|
||||
class="member-date-picker__day-slot"
|
||||
>
|
||||
<button
|
||||
v-if="calendarDay.day"
|
||||
class="member-date-picker__day"
|
||||
:class="{
|
||||
'member-date-picker__day--selected': calendarDay.day === selectedDay,
|
||||
}"
|
||||
:disabled="calendarDay.disabled"
|
||||
:aria-label="`${selectedYear}年${selectedMonth}月${calendarDay.day}日`"
|
||||
@click="selectedDay = calendarDay.day"
|
||||
>{{ calendarDay.day }}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="member-date-picker__footer">
|
||||
<button class="member-date-picker__cancel" @click="$emit('cancel')">取消</button>
|
||||
<button class="member-date-picker__confirm" @click="confirmSelection">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
title: { type: String, default: "选择日期" },
|
||||
value: { type: String, default: "" },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["cancel", "confirm"]);
|
||||
const MIN_YEAR = 1900;
|
||||
const today = new Date();
|
||||
const CURRENT_YEAR = today.getFullYear();
|
||||
const CURRENT_MONTH = today.getMonth() + 1;
|
||||
const CURRENT_DAY = today.getDate();
|
||||
const years = Array.from(
|
||||
{ length: CURRENT_YEAR - MIN_YEAR + 1 },
|
||||
(_, index) => MIN_YEAR + index,
|
||||
);
|
||||
const yearLabels = years.map((year) => `${year}年`);
|
||||
const weekdays = Object.freeze(["日", "一", "二", "三", "四", "五", "六"]);
|
||||
const selectedYear = ref(new Date().getFullYear());
|
||||
const selectedMonth = ref(new Date().getMonth() + 1);
|
||||
const selectedDay = ref(new Date().getDate());
|
||||
|
||||
const selectedYearIndex = computed(() => selectedYear.value - MIN_YEAR);
|
||||
const availableMonths = computed(() =>
|
||||
Array.from(
|
||||
{ length: selectedYear.value === CURRENT_YEAR ? CURRENT_MONTH : 12 },
|
||||
(_, index) => index + 1,
|
||||
),
|
||||
);
|
||||
const monthLabels = computed(() =>
|
||||
availableMonths.value.map((month) => `${month}月`),
|
||||
);
|
||||
const selectedMonthIndex = computed(() =>
|
||||
Math.max(0, availableMonths.value.indexOf(selectedMonth.value)),
|
||||
);
|
||||
const daysInSelectedMonth = computed(() =>
|
||||
new Date(selectedYear.value, selectedMonth.value, 0).getDate(),
|
||||
);
|
||||
const selectedMonthKey = computed(
|
||||
() => selectedYear.value * 12 + selectedMonth.value - 1,
|
||||
);
|
||||
const firstMonthKey = MIN_YEAR * 12;
|
||||
const currentMonthKey = CURRENT_YEAR * 12 + CURRENT_MONTH - 1;
|
||||
const canSelectPreviousMonth = computed(() => selectedMonthKey.value > firstMonthKey);
|
||||
const canSelectNextMonth = computed(() => selectedMonthKey.value < currentMonthKey);
|
||||
const calendarDays = computed(() => {
|
||||
const leadingDays = new Date(selectedYear.value, selectedMonth.value - 1, 1).getDay();
|
||||
const slots = Array.from({ length: leadingDays }, (_, index) => ({
|
||||
key: `empty-${index}`,
|
||||
day: 0,
|
||||
}));
|
||||
for (let day = 1; day <= daysInSelectedMonth.value; day += 1) {
|
||||
const disabled =
|
||||
selectedYear.value === CURRENT_YEAR &&
|
||||
selectedMonth.value === CURRENT_MONTH &&
|
||||
day > CURRENT_DAY;
|
||||
slots.push({ key: `day-${day}`, day, disabled });
|
||||
}
|
||||
return slots;
|
||||
});
|
||||
|
||||
const clampSelectedDay = () => {
|
||||
const latestDay =
|
||||
selectedYear.value === CURRENT_YEAR && selectedMonth.value === CURRENT_MONTH
|
||||
? CURRENT_DAY
|
||||
: daysInSelectedMonth.value;
|
||||
selectedDay.value = Math.min(selectedDay.value, latestDay);
|
||||
};
|
||||
const parseInitialDate = () => {
|
||||
const matched = /^(\d{4})-(\d{2})-(\d{2})$/.exec(props.value);
|
||||
const fallback = new Date();
|
||||
const isFutureDate = matched
|
||||
? new Date(Number(matched[1]), Number(matched[2]) - 1, Number(matched[3])) > today
|
||||
: false;
|
||||
if (isFutureDate) {
|
||||
selectedYear.value = CURRENT_YEAR;
|
||||
selectedMonth.value = CURRENT_MONTH;
|
||||
selectedDay.value = CURRENT_DAY;
|
||||
return;
|
||||
}
|
||||
selectedYear.value = matched
|
||||
? Math.max(MIN_YEAR, Math.min(CURRENT_YEAR, Number(matched[1])))
|
||||
: fallback.getFullYear();
|
||||
selectedMonth.value = matched
|
||||
? Math.max(1, Math.min(12, Number(matched[2])))
|
||||
: fallback.getMonth() + 1;
|
||||
selectedDay.value = matched ? Math.max(1, Number(matched[3])) : fallback.getDate();
|
||||
if (selectedMonthKey.value > currentMonthKey) {
|
||||
selectedMonth.value = CURRENT_MONTH;
|
||||
}
|
||||
clampSelectedDay();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) parseInitialDate();
|
||||
},
|
||||
);
|
||||
|
||||
const selectYear = (event) => {
|
||||
selectedYear.value = years[Number(event.detail.value)] || selectedYear.value;
|
||||
if (selectedMonthKey.value > currentMonthKey) {
|
||||
selectedMonth.value = CURRENT_MONTH;
|
||||
}
|
||||
clampSelectedDay();
|
||||
};
|
||||
const selectMonth = (event) => {
|
||||
const nextMonth = availableMonths.value[Number(event.detail.value)];
|
||||
if (!nextMonth) return;
|
||||
selectedMonth.value = nextMonth;
|
||||
clampSelectedDay();
|
||||
};
|
||||
const changeMonth = (offset) => {
|
||||
const nextDate = new Date(selectedYear.value, selectedMonth.value - 1 + offset, 1);
|
||||
const nextYear = nextDate.getFullYear();
|
||||
const nextMonth = nextDate.getMonth() + 1;
|
||||
const nextMonthKey = nextYear * 12 + nextMonth - 1;
|
||||
if (nextMonthKey < firstMonthKey || nextMonthKey > currentMonthKey) return;
|
||||
selectedYear.value = nextYear;
|
||||
selectedMonth.value = nextMonth;
|
||||
clampSelectedDay();
|
||||
};
|
||||
const padDatePart = (value) => String(value).padStart(2, "0");
|
||||
const confirmSelection = () => {
|
||||
if (selectedMonthKey.value > currentMonthKey) return;
|
||||
clampSelectedDay();
|
||||
emit(
|
||||
"confirm",
|
||||
`${selectedYear.value}-${padDatePart(selectedMonth.value)}-${padDatePart(
|
||||
selectedDay.value,
|
||||
)}`,
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.member-date-picker {
|
||||
position: fixed;
|
||||
z-index: 90;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.member-date-picker__mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(43, 30, 20, 0.5);
|
||||
}
|
||||
.member-date-picker__panel {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 28rpx 30rpx calc(28rpx + env(safe-area-inset-bottom));
|
||||
border-radius: 28rpx 28rpx 0 0;
|
||||
background: #fdf8ed;
|
||||
box-shadow: 0 -12rpx 36rpx rgba(43, 30, 20, 0.2);
|
||||
}
|
||||
.member-date-picker__heading {
|
||||
text-align: center;
|
||||
}
|
||||
.member-date-picker__title,
|
||||
.member-date-picker__hint {
|
||||
display: block;
|
||||
}
|
||||
.member-date-picker__title {
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(20px, 36rpx, 25px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-date-picker__hint {
|
||||
margin-top: 6rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
}
|
||||
.member-date-picker__toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 76rpx minmax(0, 1fr) 76rpx;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.member-date-picker__month-button {
|
||||
display: flex;
|
||||
width: 76rpx;
|
||||
min-height: 76rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1rpx solid rgba(181, 137, 63, 0.34);
|
||||
border-radius: 10rpx;
|
||||
background: rgba(247, 237, 218, 0.7);
|
||||
}
|
||||
.member-date-picker__month-button[disabled],
|
||||
.member-date-picker__day[disabled] {
|
||||
opacity: 0.32;
|
||||
}
|
||||
.member-date-picker__month-button::after,
|
||||
.member-date-picker__year-button::after,
|
||||
.member-date-picker__month-button-direct::after,
|
||||
.member-date-picker__day::after,
|
||||
.member-date-picker__cancel::after,
|
||||
.member-date-picker__confirm::after {
|
||||
display: none;
|
||||
}
|
||||
.member-date-picker__month-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
.member-date-picker__month-icon--previous {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.member-date-picker__current-month {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
.member-date-picker__year-button {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 76rpx;
|
||||
align-content: center;
|
||||
margin: 0;
|
||||
padding: 6rpx 18rpx;
|
||||
border: 1rpx solid rgba(159, 23, 15, 0.38);
|
||||
border-radius: 10rpx;
|
||||
background: rgba(255, 250, 240, 0.92);
|
||||
color: $brand-red;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.member-date-picker__year-button text:first-child {
|
||||
font-size: clamp(12px, 19rpx, 15px);
|
||||
}
|
||||
.member-date-picker__year-button text:last-child {
|
||||
font-size: clamp(17px, 29rpx, 21px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-date-picker__month-button-direct {
|
||||
display: grid;
|
||||
min-width: 116rpx;
|
||||
min-height: 76rpx;
|
||||
align-content: center;
|
||||
margin: 0;
|
||||
padding: 6rpx 12rpx;
|
||||
border: 1rpx solid rgba(181, 137, 63, 0.34);
|
||||
border-radius: 10rpx;
|
||||
background: rgba(247, 237, 218, 0.7);
|
||||
color: $ink;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.member-date-picker__month-button-direct text:first-child {
|
||||
color: $ink-muted;
|
||||
font-size: clamp(12px, 19rpx, 15px);
|
||||
}
|
||||
.member-date-picker__month-button-direct text:last-child {
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(17px, 29rpx, 21px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-date-picker__weekdays,
|
||||
.member-date-picker__calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
.member-date-picker__weekdays {
|
||||
margin-top: 20rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
text-align: center;
|
||||
}
|
||||
.member-date-picker__calendar {
|
||||
margin-top: 8rpx;
|
||||
padding: 8rpx;
|
||||
border: 1rpx solid rgba(181, 137, 63, 0.3);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 252, 245, 0.76);
|
||||
}
|
||||
.member-date-picker__day-slot {
|
||||
display: flex;
|
||||
min-height: 70rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.member-date-picker__day {
|
||||
display: flex;
|
||||
width: 58rpx;
|
||||
min-height: 58rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: $ink;
|
||||
font-size: clamp(14px, 23rpx, 18px);
|
||||
line-height: 1;
|
||||
}
|
||||
.member-date-picker__day--selected {
|
||||
background: $brand-red;
|
||||
color: #fff9ed;
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-date-picker__footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.member-date-picker__cancel,
|
||||
.member-date-picker__confirm {
|
||||
min-height: 82rpx;
|
||||
margin: 0;
|
||||
border-radius: 10rpx;
|
||||
font-size: clamp(16px, 28rpx, 20px);
|
||||
font-weight: 700;
|
||||
}
|
||||
.member-date-picker__cancel {
|
||||
border: 1rpx solid rgba(159, 23, 15, 0.34);
|
||||
background: transparent;
|
||||
color: $brand-red;
|
||||
}
|
||||
.member-date-picker__confirm {
|
||||
border: 0;
|
||||
background: $brand-red;
|
||||
color: #fff9ed;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user