mirror of https://github.com/halo-dev/halo
refactor: post tag management page (#5593)
#### What type of PR is this? /kind improvement /area ui #### What this PR does / why we need it: 使用分页列表的形式重新展示文章标签页,移除原有的 grid 形式。 #### How to test it? 查看文章标签页面的分页列表功能显示是否正常。 完成增、删、改之后是否能够正常回显。 #### Which issue(s) this PR fixes: Fixes #5415 #### Does this PR introduce a user-facing change? ```release-note 使用分页列表的形式重构文章标签页 UI ```pull/5685/head^2
parent
95ec1c1cce
commit
26db6036a7
|
@ -1,63 +1,75 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// core libs
|
import type { Tag } from "@halo-dev/api-client";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
// components
|
|
||||||
import {
|
import {
|
||||||
IconAddCircle,
|
IconAddCircle,
|
||||||
IconBookRead,
|
IconBookRead,
|
||||||
IconGrid,
|
|
||||||
IconList,
|
|
||||||
VButton,
|
VButton,
|
||||||
VCard,
|
VCard,
|
||||||
VEmpty,
|
VEmpty,
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VSpace,
|
VSpace,
|
||||||
VStatusDot,
|
|
||||||
VEntity,
|
|
||||||
VEntityField,
|
|
||||||
VLoading,
|
VLoading,
|
||||||
VDropdownItem,
|
VPagination,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
|
import HasPermission from "@/components/permission/HasPermission.vue";
|
||||||
import TagEditingModal from "./components/TagEditingModal.vue";
|
import TagEditingModal from "./components/TagEditingModal.vue";
|
||||||
import PostTag from "./components/PostTag.vue";
|
|
||||||
|
|
||||||
// types
|
|
||||||
import type { Tag } from "@halo-dev/api-client";
|
|
||||||
import { usePostTag } from "./composables/use-post-tag";
|
|
||||||
|
|
||||||
import { formatDatetime } from "@/utils/date";
|
|
||||||
|
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePostTag } from "./composables/use-post-tag";
|
||||||
|
import TagListItem from "./components/TagListItem.vue";
|
||||||
const { currentUserHasPermission } = usePermission();
|
|
||||||
|
|
||||||
const viewTypes = [
|
|
||||||
{
|
|
||||||
name: "list",
|
|
||||||
icon: IconList,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "grid",
|
|
||||||
icon: IconGrid,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const viewType = ref("list");
|
|
||||||
|
|
||||||
const { tags, isLoading, handleFetchTags, handleDelete } = usePostTag();
|
|
||||||
|
|
||||||
const editingModal = ref(false);
|
const editingModal = ref(false);
|
||||||
const selectedTag = ref<Tag | null>(null);
|
const selectedTag = ref<Tag | null>(null);
|
||||||
|
|
||||||
|
const selectedTagNames = ref<string[]>([]);
|
||||||
|
const checkedAll = ref(false);
|
||||||
|
|
||||||
|
const page = useRouteQuery<number>("page", 1, {
|
||||||
|
transform: Number,
|
||||||
|
});
|
||||||
|
const size = useRouteQuery<number>("size", 20, {
|
||||||
|
transform: Number,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
tags,
|
||||||
|
total,
|
||||||
|
hasNext,
|
||||||
|
hasPrevious,
|
||||||
|
isLoading,
|
||||||
|
handleFetchTags,
|
||||||
|
handleDelete,
|
||||||
|
handleDeleteInBatch,
|
||||||
|
} = usePostTag({
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
|
||||||
const handleOpenEditingModal = (tag: Tag | null) => {
|
const handleOpenEditingModal = (tag: Tag | null) => {
|
||||||
selectedTag.value = tag;
|
selectedTag.value = tag;
|
||||||
editingModal.value = true;
|
editingModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectPrevious = () => {
|
const handleDeleteTagInBatch = () => {
|
||||||
|
handleDeleteInBatch(selectedTagNames.value).then(() => {
|
||||||
|
selectedTagNames.value = [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckAllChange = () => {
|
||||||
|
if (checkedAll.value) {
|
||||||
|
selectedTagNames.value = tags.value?.map((tag) => tag.metadata.name) || [];
|
||||||
|
} else {
|
||||||
|
selectedTagNames.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectPrevious = async () => {
|
||||||
|
if (!hasPrevious.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tags.value) return;
|
if (!tags.value) return;
|
||||||
|
|
||||||
const currentIndex = tags.value.findIndex(
|
const currentIndex = tags.value.findIndex(
|
||||||
|
@ -69,12 +81,18 @@ const handleSelectPrevious = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentIndex <= 0) {
|
if (currentIndex === 0 && hasPrevious.value) {
|
||||||
selectedTag.value = null;
|
page.value--;
|
||||||
|
await handleFetchTags();
|
||||||
|
selectedTag.value = tags.value[tags.value.length - 1];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectNext = () => {
|
const handleSelectNext = async () => {
|
||||||
|
if (!hasNext.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tags.value) return;
|
if (!tags.value) return;
|
||||||
|
|
||||||
if (!selectedTag.value) {
|
if (!selectedTag.value) {
|
||||||
|
@ -87,6 +105,12 @@ const handleSelectNext = () => {
|
||||||
if (currentIndex !== tags.value.length - 1) {
|
if (currentIndex !== tags.value.length - 1) {
|
||||||
selectedTag.value = tags.value[currentIndex + 1];
|
selectedTag.value = tags.value[currentIndex + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentIndex === tags.value.length - 1 && hasNext.value) {
|
||||||
|
page.value++;
|
||||||
|
await handleFetchTags();
|
||||||
|
selectedTag.value = tags.value[0];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEditingModalClose = () => {
|
const onEditingModalClose = () => {
|
||||||
|
@ -108,6 +132,10 @@ onMounted(async () => {
|
||||||
editingModal.value = true;
|
editingModal.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(selectedTagNames, (newVal) => {
|
||||||
|
checkedAll.value = newVal.length === tags.value?.length;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<TagEditingModal
|
<TagEditingModal
|
||||||
|
@ -139,27 +167,23 @@ onMounted(async () => {
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="block w-full bg-gray-50 px-4 py-3">
|
<div class="block w-full bg-gray-50 px-4 py-3">
|
||||||
<div
|
<div
|
||||||
class="relative flex flex-col items-start sm:flex-row sm:items-center"
|
class="relative flex h-9 flex-col flex-wrap items-start gap-4 sm:flex-row sm:items-center"
|
||||||
>
|
>
|
||||||
<div class="flex w-full flex-1 sm:w-auto">
|
<HasPermission :permissions="['system:posts:manage']">
|
||||||
<span class="text-base font-medium">
|
<div class="hidden items-center sm:flex">
|
||||||
{{
|
<input
|
||||||
$t("core.post_tag.header.title", { count: tags?.length || 0 })
|
v-model="checkedAll"
|
||||||
}}
|
type="checkbox"
|
||||||
</span>
|
@change="handleCheckAllChange"
|
||||||
</div>
|
/>
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in viewTypes"
|
|
||||||
:key="index"
|
|
||||||
:class="{
|
|
||||||
'bg-gray-200 font-bold text-black': viewType === item.name,
|
|
||||||
}"
|
|
||||||
class="cursor-pointer rounded p-1 hover:bg-gray-200"
|
|
||||||
@click="viewType = item.name"
|
|
||||||
>
|
|
||||||
<component :is="item.icon" />
|
|
||||||
</div>
|
</div>
|
||||||
|
</HasPermission>
|
||||||
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||||
|
<VSpace v-if="selectedTagNames.length > 0">
|
||||||
|
<VButton type="danger" @click="handleDeleteTagInBatch">
|
||||||
|
{{ $t("core.common.buttons.delete") }}
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,7 +196,7 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<VButton @click="handleFetchTags">
|
<VButton @click="() => handleFetchTags">
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="primary" @click="editingModal = true">
|
<VButton type="primary" @click="editingModal = true">
|
||||||
|
@ -186,93 +210,42 @@ onMounted(async () => {
|
||||||
</VEmpty>
|
</VEmpty>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<div v-else>
|
<Transition appear name="fade">
|
||||||
<Transition v-if="viewType === 'list'" appear name="fade">
|
|
||||||
<ul
|
<ul
|
||||||
class="box-border h-full w-full divide-y divide-gray-100"
|
class="box-border h-full w-full divide-y divide-gray-100"
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<li v-for="(tag, index) in tags" :key="index">
|
<li v-for="(tag, index) in tags" :key="index">
|
||||||
<VEntity
|
<TagListItem
|
||||||
|
:tag="tag"
|
||||||
:is-selected="selectedTag?.metadata.name === tag.metadata.name"
|
:is-selected="selectedTag?.metadata.name === tag.metadata.name"
|
||||||
|
@editing="handleOpenEditingModal"
|
||||||
|
@delete="handleDelete"
|
||||||
>
|
>
|
||||||
<template #start>
|
<template #checkbox>
|
||||||
<VEntityField>
|
<input
|
||||||
<template #title>
|
v-model="selectedTagNames"
|
||||||
<PostTag :tag="tag" />
|
:value="tag.metadata.name"
|
||||||
</template>
|
type="checkbox"
|
||||||
<template #description>
|
|
||||||
<a
|
|
||||||
v-if="tag.status?.permalink"
|
|
||||||
:href="tag.status?.permalink"
|
|
||||||
:title="tag.status?.permalink"
|
|
||||||
target="_blank"
|
|
||||||
class="truncate text-xs text-gray-500 group-hover:text-gray-900"
|
|
||||||
>
|
|
||||||
{{ tag.status.permalink }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
</template>
|
|
||||||
<template #end>
|
|
||||||
<VEntityField v-if="tag.metadata.deletionTimestamp">
|
|
||||||
<template #description>
|
|
||||||
<VStatusDot
|
|
||||||
v-tooltip="$t('core.common.status.deleting')"
|
|
||||||
state="warning"
|
|
||||||
animate
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VEntityField>
|
</TagListItem>
|
||||||
<VEntityField
|
|
||||||
:description="
|
|
||||||
$t('core.common.fields.post_count', {
|
|
||||||
count: tag.status?.postCount || 0,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<VEntityField>
|
|
||||||
<template #description>
|
|
||||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
|
||||||
{{ formatDatetime(tag.metadata.creationTimestamp) }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
|
||||||
#dropdownItems
|
|
||||||
>
|
|
||||||
<VDropdownItem
|
|
||||||
v-permission="['system:posts:manage']"
|
|
||||||
@click="handleOpenEditingModal(tag)"
|
|
||||||
>
|
|
||||||
{{ $t("core.common.buttons.edit") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
<VDropdownItem
|
|
||||||
v-permission="['system:posts:manage']"
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(tag)"
|
|
||||||
>
|
|
||||||
{{ $t("core.common.buttons.delete") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
|
||||||
</VEntity>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<template #footer>
|
||||||
<Transition v-else appear name="fade">
|
<VPagination
|
||||||
<div class="flex flex-wrap gap-3 p-4" role="list">
|
v-model:page="page"
|
||||||
<PostTag
|
v-model:size="size"
|
||||||
v-for="(tag, index) in tags"
|
:page-label="$t('core.components.pagination.page_label')"
|
||||||
:key="index"
|
:size-label="$t('core.components.pagination.size_label')"
|
||||||
:tag="tag"
|
:total-label="
|
||||||
@click="handleOpenEditingModal(tag)"
|
$t('core.components.pagination.total_label', { total: total })
|
||||||
|
"
|
||||||
|
:total="total"
|
||||||
|
:size-options="[20, 30, 50, 100]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import HasPermission from "@/components/permission/HasPermission.vue";
|
||||||
|
import { formatDatetime } from "@/utils/date";
|
||||||
|
import type { Tag } from "@halo-dev/api-client";
|
||||||
|
import {
|
||||||
|
VStatusDot,
|
||||||
|
VEntity,
|
||||||
|
VEntityField,
|
||||||
|
VDropdownItem,
|
||||||
|
IconExternalLinkLine,
|
||||||
|
VSpace,
|
||||||
|
} from "@halo-dev/components";
|
||||||
|
import PostTag from "./PostTag.vue";
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
tag: Tag;
|
||||||
|
isSelected?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ isSelected: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "editing", tag: Tag): void;
|
||||||
|
(event: "delete", tag: Tag): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VEntity :is-selected="isSelected">
|
||||||
|
<template #checkbox>
|
||||||
|
<HasPermission :permissions="['system:posts:manage']">
|
||||||
|
<slot name="checkbox" />
|
||||||
|
</HasPermission>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #start>
|
||||||
|
<VEntityField>
|
||||||
|
<template #title>
|
||||||
|
<PostTag :tag="tag" />
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<VSpace>
|
||||||
|
<div
|
||||||
|
v-if="tag.status?.permalink"
|
||||||
|
:title="tag.status?.permalink"
|
||||||
|
target="_blank"
|
||||||
|
class="truncate text-xs text-gray-500 group-hover:text-gray-900"
|
||||||
|
>
|
||||||
|
{{ tag.status.permalink }}
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="tag.status?.permalink"
|
||||||
|
class="hidden text-gray-600 transition-all hover:text-gray-900 group-hover:inline-block"
|
||||||
|
>
|
||||||
|
<IconExternalLinkLine class="h-3.5 w-3.5" />
|
||||||
|
</a>
|
||||||
|
</VSpace>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
||||||
|
<template #end>
|
||||||
|
<VEntityField v-if="tag.metadata.deletionTimestamp">
|
||||||
|
<template #description>
|
||||||
|
<VStatusDot
|
||||||
|
v-tooltip="$t('core.common.status.deleting')"
|
||||||
|
state="warning"
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
<VEntityField
|
||||||
|
:description="
|
||||||
|
$t('core.common.fields.post_count', {
|
||||||
|
count: tag.status?.postCount || 0,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<VEntityField>
|
||||||
|
<template #description>
|
||||||
|
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||||
|
{{ formatDatetime(tag.metadata.creationTimestamp) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
||||||
|
<template #dropdownItems>
|
||||||
|
<HasPermission :permissions="['system:posts:manage']">
|
||||||
|
<VDropdownItem @click="emit('editing', tag)">
|
||||||
|
{{ $t("core.common.buttons.edit") }}
|
||||||
|
</VDropdownItem>
|
||||||
|
<VDropdownItem type="danger" @click="emit('delete', tag)">
|
||||||
|
{{ $t("core.common.buttons.delete") }}
|
||||||
|
</VDropdownItem>
|
||||||
|
</HasPermission>
|
||||||
|
</template>
|
||||||
|
</VEntity>
|
||||||
|
</template>
|
|
@ -1,34 +1,52 @@
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Tag } from "@halo-dev/api-client";
|
import type { Tag } from "@halo-dev/api-client";
|
||||||
import type { Ref } from "vue";
|
import { ref, type Ref } from "vue";
|
||||||
import { Dialog, Toast } from "@halo-dev/components";
|
import { Dialog, Toast } from "@halo-dev/components";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery, type QueryObserverResult } from "@tanstack/vue-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
interface usePostTagReturn {
|
interface usePostTagReturn {
|
||||||
tags: Ref<Tag[] | undefined>;
|
tags: Ref<Tag[] | undefined>;
|
||||||
|
total: Ref<number>;
|
||||||
|
hasPrevious: Ref<boolean>;
|
||||||
|
hasNext: Ref<boolean>;
|
||||||
isLoading: Ref<boolean>;
|
isLoading: Ref<boolean>;
|
||||||
handleFetchTags: () => void;
|
handleFetchTags: () => Promise<QueryObserverResult<Tag[], unknown>>;
|
||||||
handleDelete: (tag: Tag) => void;
|
handleDelete: (tag: Tag) => void;
|
||||||
|
handleDeleteInBatch: (tagNames: string[]) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePostTag(): usePostTagReturn {
|
export function usePostTag(filterOptions?: {
|
||||||
|
sort?: Ref<string[]>;
|
||||||
|
page?: Ref<number>;
|
||||||
|
size?: Ref<number>;
|
||||||
|
}): usePostTagReturn {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { sort, page, size } = filterOptions || {};
|
||||||
|
|
||||||
|
const total = ref(0);
|
||||||
|
const hasPrevious = ref(false);
|
||||||
|
const hasNext = ref(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: tags,
|
data: tags,
|
||||||
isLoading,
|
isLoading,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["post-tags"],
|
queryKey: ["post-tags", sort, page, size],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } =
|
const { data } =
|
||||||
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
||||||
page: 0,
|
page: page?.value || 0,
|
||||||
size: 0,
|
size: size?.value || 0,
|
||||||
sort: ["metadata.creationTimestamp,desc"],
|
sort: sort?.value || ["metadata.creationTimestamp,desc"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
total.value = data.total;
|
||||||
|
hasPrevious.value = data.hasPrevious;
|
||||||
|
hasNext.value = data.hasNext;
|
||||||
|
|
||||||
return data.items;
|
return data.items;
|
||||||
},
|
},
|
||||||
refetchInterval(data) {
|
refetchInterval(data) {
|
||||||
|
@ -62,10 +80,44 @@ export function usePostTag(): usePostTagReturn {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteInBatch = (tagNames: string[]) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
Dialog.warning({
|
||||||
|
title: t("core.post_tag.operations.delete_in_batch.title"),
|
||||||
|
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
||||||
|
confirmType: "danger",
|
||||||
|
confirmText: t("core.common.buttons.confirm"),
|
||||||
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
tagNames.map((tagName) => {
|
||||||
|
apiClient.extension.tag.deletecontentHaloRunV1alpha1Tag({
|
||||||
|
name: tagName,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.delete_success"));
|
||||||
|
resolve();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to delete tags in batch", e);
|
||||||
|
} finally {
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tags,
|
tags,
|
||||||
|
total,
|
||||||
|
hasPrevious,
|
||||||
|
hasNext,
|
||||||
isLoading,
|
isLoading,
|
||||||
handleFetchTags: refetch,
|
handleFetchTags: refetch,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
|
handleDeleteInBatch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,6 +290,8 @@ core:
|
||||||
description: >-
|
description: >-
|
||||||
After deleting this tag, the association with the corresponding
|
After deleting this tag, the association with the corresponding
|
||||||
article will be removed. This operation cannot be undone.
|
article will be removed. This operation cannot be undone.
|
||||||
|
delete_in_batch:
|
||||||
|
title: Delete the selected tags
|
||||||
editing_modal:
|
editing_modal:
|
||||||
titles:
|
titles:
|
||||||
update: Update post tag
|
update: Update post tag
|
||||||
|
|
|
@ -292,6 +292,8 @@ core:
|
||||||
delete:
|
delete:
|
||||||
title: 删除标签
|
title: 删除标签
|
||||||
description: 删除此标签之后,对应文章的关联将被解除。该操作不可恢复。
|
description: 删除此标签之后,对应文章的关联将被解除。该操作不可恢复。
|
||||||
|
delete_in_batch:
|
||||||
|
title: 删除所选标签
|
||||||
editing_modal:
|
editing_modal:
|
||||||
titles:
|
titles:
|
||||||
update: 编辑文章标签
|
update: 编辑文章标签
|
||||||
|
|
|
@ -280,6 +280,8 @@ core:
|
||||||
delete:
|
delete:
|
||||||
title: 刪除標籤
|
title: 刪除標籤
|
||||||
description: 刪除此標籤之後,對應文章的關聯將被解除。該操作不可恢復。
|
description: 刪除此標籤之後,對應文章的關聯將被解除。該操作不可恢復。
|
||||||
|
delete_in_batch:
|
||||||
|
title: 刪除所選標籤
|
||||||
editing_modal:
|
editing_modal:
|
||||||
titles:
|
titles:
|
||||||
update: 編輯文章標籤
|
update: 編輯文章標籤
|
||||||
|
|
Loading…
Reference in New Issue