Files
jiapuapp/composables/use-batch-deletion.js
2026-09-13 17:45:52 +08:00

136 lines
4.2 KiB
JavaScript

import { computed, ref } from "vue";
import { isRequestCancelled } from "@/services/api/request-controller.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
export const useBatchDeletion = ({
items,
visibleItems = items,
getId = (item) => item?.id,
canDelete = (item) => item?.canDelete === true,
deleteOne,
resourceName,
isActive = () => true,
onEmpty = () => {},
}) => {
const selectionMode = ref(false);
const selectedIds = ref([]);
const confirmationVisible = ref(false);
const deleting = ref(false);
const notice = ref("");
const error = ref("");
const deletableItems = computed(() => visibleItems.value.filter(canDelete));
const allSelected = computed(
() =>
deletableItems.value.length > 0 &&
deletableItems.value.every((item) => selectedIds.value.includes(getId(item))),
);
const selectedCount = computed(() => selectedIds.value.length);
const confirmationMessage = computed(
() =>
`将选中的 ${selectedCount.value}${resourceName}移入回收站;管理员可在保留期内恢复。`,
);
const clearFeedback = () => {
notice.value = "";
error.value = "";
};
const exitSelectionMode = () => {
selectionMode.value = false;
selectedIds.value = [];
confirmationVisible.value = false;
};
const enterSelectionMode = () => {
if (!deletableItems.value.length || deleting.value) return;
clearFeedback();
selectedIds.value = [];
selectionMode.value = true;
};
const isSelected = (item) => selectedIds.value.includes(getId(item));
const toggleSelection = (item) => {
if (!selectionMode.value || !canDelete(item) || deleting.value) return;
const id = getId(item);
selectedIds.value = isSelected(item)
? selectedIds.value.filter((selectedId) => selectedId !== id)
: [...selectedIds.value, id];
clearFeedback();
};
const toggleAll = () => {
if (!selectionMode.value || deleting.value) return;
selectedIds.value = allSelected.value ? [] : deletableItems.value.map(getId);
clearFeedback();
};
const requestDelete = () => {
if (!selectionMode.value || !selectedIds.value.length || deleting.value) return;
clearFeedback();
confirmationVisible.value = true;
};
const cancelDelete = () => {
if (!deleting.value) confirmationVisible.value = false;
};
const confirmDelete = async () => {
if (!selectionMode.value || !selectedIds.value.length || deleting.value) return;
const pendingIds = selectedIds.value.slice();
deleting.value = true;
clearFeedback();
let deletedCount = 0;
let failedRequest = null;
try {
for (const id of pendingIds) {
const target = items.value.find((item) => getId(item) === id);
if (!target || !canDelete(target)) continue;
try {
await deleteOne(target);
} catch (cause) {
failedRequest = cause;
break;
}
if (!isActive()) return;
deletedCount += 1;
items.value = items.value.filter((item) => getId(item) !== id);
selectedIds.value = selectedIds.value.filter((selectedId) => selectedId !== id);
}
if (!isActive()) return;
confirmationVisible.value = false;
if (failedRequest && !isRequestCancelled(failedRequest)) {
const failureCopy = getRequestErrorMessage(
failedRequest,
`剩余${resourceName}删除失败,请稍后重试。`,
);
error.value = deletedCount ? `已删除 ${deletedCount} 条;${failureCopy}` : failureCopy;
} else if (deletedCount) {
notice.value = `已删除 ${deletedCount}${resourceName}。`;
}
if (!items.value.length) {
exitSelectionMode();
onEmpty();
} else if (!selectedIds.value.length) {
selectionMode.value = false;
}
} finally {
if (isActive()) deleting.value = false;
}
};
return {
selectionMode,
selectedIds,
selectedCount,
deletableItems,
allSelected,
confirmationVisible,
confirmationMessage,
deleting,
notice,
error,
enterSelectionMode,
exitSelectionMode,
isSelected,
toggleSelection,
toggleAll,
requestDelete,
cancelDelete,
confirmDelete,
};
};