136 lines
4.2 KiB
JavaScript
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,
|
|
};
|
|
};
|