mirror of https://github.com/halo-dev/halo-admin
perf: improve the list filter style and support clear all (#702)
#### What type of PR is this? /kind improvement /milestone 2.0 #### What this PR does / why we need it: 优化 Console 数据列表的筛选标签样式,以及支持清空所有筛选条件。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2657 #### Screenshots: <img width="1663" alt="image" src="https://user-images.githubusercontent.com/21301288/203353043-b5e7631f-cc02-4368-b770-42b53e1dbf78.png"> #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. 测试文章、自定义页面、附件、插件、评论管理中的筛选功能。 #### Does this PR introduce a user-facing change? ```release-note 优化 Console 数据列表的筛选标签样式,以及支持清空所有筛选条件。 ```pull/699/head^2
parent
2350f541d7
commit
b79eccb6a2
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts" setup>
|
||||
import { IconDeleteBin } from "@halo-dev/components";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group inline-flex cursor-pointer items-center justify-center rounded-full bg-primary px-1 py-1 opacity-80 ring-1 ring-primary transition-all duration-300 hover:opacity-100 hover:shadow-sm hover:ring-opacity-50"
|
||||
>
|
||||
<div class="h-4 w-4 rounded-full transition-all">
|
||||
<IconDeleteBin
|
||||
class="h-full w-full text-gray-200 transition-all group-hover:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,25 @@
|
|||
<script lang="ts" setup>
|
||||
import { IconCloseCircle } from "@halo-dev/components";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-full bg-primary px-2 py-1 ring-1 ring-primary transition-all duration-300 hover:shadow-sm hover:ring-opacity-50"
|
||||
>
|
||||
<span v-if="$slots.default" class="text-xs text-white transition-all">
|
||||
<slot />
|
||||
</span>
|
||||
<div
|
||||
class="h-4 w-4 rounded-full ring-white transition-all group-hover:ring-1"
|
||||
@click="emit('close')"
|
||||
>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-200 transition-all group-hover:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -15,7 +15,6 @@ import {
|
|||
VPagination,
|
||||
VSpace,
|
||||
VEmpty,
|
||||
IconCloseCircle,
|
||||
IconFolder,
|
||||
VStatusDot,
|
||||
VEntity,
|
||||
|
@ -40,6 +39,9 @@ import { isImage } from "@/utils/image";
|
|||
import { useRouteQuery } from "@vueuse/router";
|
||||
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import FilteCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -90,17 +92,47 @@ const selectedSortItemValue = computed(() => {
|
|||
|
||||
function handleSelectPolicy(policy: Policy | undefined) {
|
||||
selectedPolicy.value = policy;
|
||||
handleFetchAttachments();
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
function handleSelectUser(user: User | undefined) {
|
||||
selectedUser.value = user;
|
||||
handleFetchAttachments();
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
handleFetchAttachments();
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedPolicy.value ||
|
||||
selectedUser.value ||
|
||||
selectedSortItem.value ||
|
||||
keyword.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedPolicy.value = undefined;
|
||||
selectedUser.value = undefined;
|
||||
selectedSortItem.value = undefined;
|
||||
keyword.value = "";
|
||||
handleFetchAttachments(1);
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -322,57 +354,44 @@ onMounted(() => {
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchAttachments()"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<div
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedPolicy"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSelectPolicy(undefined)"
|
||||
>
|
||||
<span
|
||||
class="text-xs text-gray-600 group-hover:text-gray-900"
|
||||
>
|
||||
存储策略:{{ selectedPolicy?.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSelectPolicy(undefined)"
|
||||
/>
|
||||
</div>
|
||||
存储策略:{{ selectedPolicy?.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<div
|
||||
<FilterTag
|
||||
v-if="selectedUser"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSelectUser(undefined)"
|
||||
>
|
||||
<span
|
||||
class="text-xs text-gray-600 group-hover:text-gray-900"
|
||||
>
|
||||
上传者:{{ selectedUser?.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSelectUser(undefined)"
|
||||
/>
|
||||
</div>
|
||||
上传者:{{ selectedUser?.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<div
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@click="handleSortItemChange()"
|
||||
>
|
||||
<span
|
||||
class="text-xs text-gray-600 group-hover:text-gray-900"
|
||||
>
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSortItemChange()"
|
||||
/>
|
||||
</div>
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilteCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">
|
||||
|
@ -521,7 +540,7 @@ onMounted(() => {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchAttachments"
|
||||
@click="handleFetchAttachments()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -19,7 +19,7 @@ interface useAttachmentControlReturn {
|
|||
selectedAttachment: Ref<Attachment | undefined>;
|
||||
selectedAttachments: Ref<Set<Attachment>>;
|
||||
checkedAll: Ref<boolean>;
|
||||
handleFetchAttachments: () => void;
|
||||
handleFetchAttachments: (page?: number) => void;
|
||||
handlePaginationChange: ({
|
||||
page,
|
||||
size,
|
||||
|
@ -68,11 +68,16 @@ export function useAttachmentControl(filterOptions?: {
|
|||
const checkedAll = ref(false);
|
||||
const refreshInterval = ref();
|
||||
|
||||
const handleFetchAttachments = async () => {
|
||||
const handleFetchAttachments = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (page) {
|
||||
attachments.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.attachment.searchAttachments({
|
||||
policy: policy?.value?.metadata.name,
|
||||
displayName: keyword?.value,
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
IconCloseCircle,
|
||||
IconRefreshLine,
|
||||
VEmpty,
|
||||
Dialog,
|
||||
|
@ -19,9 +18,12 @@ import type {
|
|||
ListedCommentList,
|
||||
User,
|
||||
} from "@halo-dev/api-client";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import FilterCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
|
||||
const comments = ref<ListedCommentList>({
|
||||
page: 1,
|
||||
|
@ -41,11 +43,16 @@ const selectedCommentNames = ref<string[]>([]);
|
|||
const keyword = ref("");
|
||||
const refreshInterval = ref();
|
||||
|
||||
const handleFetchComments = async () => {
|
||||
const handleFetchComments = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (page) {
|
||||
comments.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.comment.listComments({
|
||||
page: comments.value.page,
|
||||
size: comments.value.size,
|
||||
|
@ -212,13 +219,16 @@ const SortFilterItems: {
|
|||
value: "CREATE_TIME",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedApprovedFilterItem = ref<{ label: string; value?: boolean }>(
|
||||
ApprovedFilterItems[0]
|
||||
);
|
||||
|
||||
const selectedSortFilterItem = ref<{
|
||||
label: string;
|
||||
value?: Sort;
|
||||
}>(SortFilterItems[0]);
|
||||
|
||||
const selectedUser = ref<User>();
|
||||
|
||||
const handleApprovedFilterItemChange = (filterItem: {
|
||||
|
@ -227,7 +237,7 @@ const handleApprovedFilterItemChange = (filterItem: {
|
|||
}) => {
|
||||
selectedApprovedFilterItem.value = filterItem;
|
||||
selectedCommentNames.value = [];
|
||||
handlePaginationChange({ page: 1, size: 20 });
|
||||
handleFetchComments(1);
|
||||
};
|
||||
|
||||
const handleSortFilterItemChange = (filterItem: {
|
||||
|
@ -236,12 +246,42 @@ const handleSortFilterItemChange = (filterItem: {
|
|||
}) => {
|
||||
selectedSortFilterItem.value = filterItem;
|
||||
selectedCommentNames.value = [];
|
||||
handlePaginationChange({ page: 1, size: 20 });
|
||||
handleFetchComments(1);
|
||||
};
|
||||
|
||||
function handleSelectUser(user: User | undefined) {
|
||||
selectedUser.value = user;
|
||||
handlePaginationChange({ page: 1, size: 20 });
|
||||
handleFetchComments(1);
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchComments(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchComments(1);
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedApprovedFilterItem.value.value !== undefined ||
|
||||
selectedSortFilterItem.value.value !== "LAST_REPLY_TIME" ||
|
||||
selectedUser.value ||
|
||||
keyword.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedApprovedFilterItem.value = ApprovedFilterItems[0];
|
||||
selectedSortFilterItem.value = SortFilterItems[0];
|
||||
selectedUser.value = undefined;
|
||||
keyword.value = "";
|
||||
handleFetchComments(1);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -275,49 +315,46 @@ function handleSelectUser(user: User | undefined) {
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchComments"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
<div
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedApprovedFilterItem.value != undefined"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="
|
||||
handleApprovedFilterItemChange(ApprovedFilterItems[0])
|
||||
"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
状态:{{ selectedApprovedFilterItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="
|
||||
handleApprovedFilterItemChange(ApprovedFilterItems[0])
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
状态:{{ selectedApprovedFilterItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedUser"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSelectUser(undefined)"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
评论者:{{ selectedUser?.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSelectUser(undefined)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
评论者:{{ selectedUser?.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortFilterItem.value != 'LAST_REPLY_TIME'"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSortFilterItemChange(SortFilterItems[0])"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
排序:{{ selectedSortFilterItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSortFilterItemChange(SortFilterItems[0])"
|
||||
/>
|
||||
</div>
|
||||
排序:{{ selectedSortFilterItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="secondary" @click="handleApproveInBatch">
|
||||
|
@ -409,7 +446,7 @@ function handleSelectUser(user: User | undefined) {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchComments"
|
||||
@click="handleFetchComments()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -290,12 +290,6 @@ const subjectRefResult = computed(() => {
|
|||
class="w-28 min-w-[7rem]"
|
||||
:title="comment?.owner?.displayName"
|
||||
:description="comment?.owner?.email"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: {
|
||||
name: comment?.owner?.name,
|
||||
},
|
||||
}"
|
||||
></VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
|
|
|
@ -22,6 +22,8 @@ import { formatDatetime } from "@/utils/date";
|
|||
import { onBeforeRouteLeave, RouterLink } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { getNode } from "@formkit/core";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -42,12 +44,16 @@ const checkedAll = ref(false);
|
|||
const refreshInterval = ref();
|
||||
const keyword = ref("");
|
||||
|
||||
const handleFetchSinglePages = async () => {
|
||||
const handleFetchSinglePages = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (page) {
|
||||
singlePages.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.singlePage.listSinglePages({
|
||||
labelSelector: [`content.halo.run/deleted=true`],
|
||||
page: singlePages.value.page,
|
||||
|
@ -198,6 +204,20 @@ watch(selectedPageNames, (newValue) => {
|
|||
});
|
||||
|
||||
onMounted(handleFetchSinglePages);
|
||||
|
||||
// Filters
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -245,11 +265,18 @@ onMounted(handleFetchSinglePages);
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchSinglePages"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeletePermanentlyInBatch">
|
||||
|
@ -265,7 +292,7 @@ onMounted(handleFetchSinglePages);
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchSinglePages"
|
||||
@click="handleFetchSinglePages()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
IconEye,
|
||||
IconEyeOff,
|
||||
IconTeam,
|
||||
IconCloseCircle,
|
||||
IconAddCircle,
|
||||
IconRefreshLine,
|
||||
VButton,
|
||||
|
@ -22,7 +21,7 @@ import {
|
|||
} from "@halo-dev/components";
|
||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import type {
|
||||
ListedSinglePageList,
|
||||
SinglePage,
|
||||
|
@ -34,6 +33,9 @@ import { onBeforeRouteLeave, RouterLink } from "vue-router";
|
|||
import cloneDeep from "lodash.clonedeep";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { singlePageLabels } from "@/constants/labels";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import FilterCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -55,7 +57,7 @@ const selectedPageNames = ref<string[]>([]);
|
|||
const checkedAll = ref(false);
|
||||
const refreshInterval = ref();
|
||||
|
||||
const handleFetchSinglePages = async () => {
|
||||
const handleFetchSinglePages = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
|
@ -74,6 +76,10 @@ const handleFetchSinglePages = async () => {
|
|||
);
|
||||
}
|
||||
|
||||
if (page) {
|
||||
singlePages.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.singlePage.listSinglePages({
|
||||
labelSelector,
|
||||
page: singlePages.value.page,
|
||||
|
@ -354,22 +360,54 @@ const keyword = ref("");
|
|||
|
||||
function handleVisibleItemChange(visibleItem: VisibleItem) {
|
||||
selectedVisibleItem.value = visibleItem;
|
||||
handleFetchSinglePages();
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
const handleSelectUser = (user?: User) => {
|
||||
selectedContributor.value = user;
|
||||
handleFetchSinglePages();
|
||||
handleFetchSinglePages(1);
|
||||
};
|
||||
|
||||
function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) {
|
||||
selectedPublishStatusItem.value = publishStatusItem;
|
||||
handleFetchSinglePages();
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
handleFetchSinglePages();
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedContributor.value ||
|
||||
selectedVisibleItem.value.value ||
|
||||
selectedPublishStatusItem.value.value !== undefined ||
|
||||
selectedSortItem.value ||
|
||||
keyword.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedContributor.value = undefined;
|
||||
selectedVisibleItem.value = VisibleItems[0];
|
||||
selectedPublishStatusItem.value = PublishStatusItems[0];
|
||||
selectedSortItem.value = undefined;
|
||||
keyword.value = "";
|
||||
handleFetchSinglePages(1);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -411,60 +449,48 @@ function handleSortItemChange(sortItem?: SortItem) {
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchSinglePages"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
<div
|
||||
v-if="selectedPublishStatusItem.value"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedPublishStatusItem.value !== undefined"
|
||||
@close="handlePublishStatusItemChange(PublishStatusItems[0])"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
状态:{{ selectedPublishStatusItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handlePublishStatusItemChange(PublishStatusItems[0])"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
状态:{{ selectedPublishStatusItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedVisibleItem.value"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleVisibleItemChange(VisibleItems[0])"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
可见性:{{ selectedVisibleItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleVisibleItemChange(VisibleItems[0])"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedContributor"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
作者:{{ selectedContributor?.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSelectUser(undefined)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
可见性:{{ selectedVisibleItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag v-if="selectedContributor" @close="handleSelectUser()">
|
||||
作者:{{ selectedContributor?.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSortItemChange()"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSortItemChange()"
|
||||
/>
|
||||
</div>
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">删除</VButton>
|
||||
|
@ -574,7 +600,7 @@ function handleSortItemChange(sortItem?: SortItem) {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchSinglePages"
|
||||
@click="handleFetchSinglePages()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -23,6 +23,8 @@ import { formatDatetime } from "@/utils/date";
|
|||
import { usePermission } from "@/utils/permission";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { getNode } from "@formkit/core";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -43,12 +45,16 @@ const selectedPostNames = ref<string[]>([]);
|
|||
const refreshInterval = ref();
|
||||
const keyword = ref("");
|
||||
|
||||
const handleFetchPosts = async () => {
|
||||
const handleFetchPosts = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (page) {
|
||||
posts.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post.listPosts({
|
||||
labelSelector: [`content.halo.run/deleted=true`],
|
||||
page: posts.value.page,
|
||||
|
@ -191,6 +197,19 @@ watch(selectedPostNames, (newValue) => {
|
|||
onMounted(() => {
|
||||
handleFetchPosts();
|
||||
});
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<VPageHeader title="文章回收站">
|
||||
|
@ -238,12 +257,18 @@ onMounted(() => {
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchPosts"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeletePermanentlyInBatch">
|
||||
|
@ -259,7 +284,7 @@ onMounted(() => {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchPosts"
|
||||
@click="handleFetchPosts()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
IconEye,
|
||||
IconEyeOff,
|
||||
IconTeam,
|
||||
IconCloseCircle,
|
||||
IconRefreshLine,
|
||||
Dialog,
|
||||
VButton,
|
||||
|
@ -25,7 +24,7 @@ import {
|
|||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||
import PostTag from "../posts/tags/components/PostTag.vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import type {
|
||||
User,
|
||||
Category,
|
||||
|
@ -40,6 +39,9 @@ import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-t
|
|||
import { usePermission } from "@/utils/permission";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import { postLabels } from "@/constants/labels";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import FilteCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -61,7 +63,7 @@ const checkedAll = ref(false);
|
|||
const selectedPostNames = ref<string[]>([]);
|
||||
const refreshInterval = ref();
|
||||
|
||||
const handleFetchPosts = async () => {
|
||||
const handleFetchPosts = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
|
@ -93,6 +95,10 @@ const handleFetchPosts = async () => {
|
|||
);
|
||||
}
|
||||
|
||||
if (page) {
|
||||
posts.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post.listPosts({
|
||||
labelSelector,
|
||||
page: posts.value.page,
|
||||
|
@ -372,33 +378,69 @@ const keyword = ref("");
|
|||
|
||||
function handleVisibleItemChange(visibleItem: VisibleItem) {
|
||||
selectedVisibleItem.value = visibleItem;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handlePublishStatusItemChange(publishStatusItem: PublishStatuItem) {
|
||||
selectedPublishStatusItem.value = publishStatusItem;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleCategoryChange(category?: Category) {
|
||||
selectedCategory.value = category;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleTagChange(tag?: Tag) {
|
||||
selectedTag.value = tag;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleContributorChange(user?: User) {
|
||||
selectedContributor.value = user;
|
||||
handleFetchPosts();
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedVisibleItem.value = VisibleItems[0];
|
||||
selectedPublishStatusItem.value = PublishStatuItems[0];
|
||||
selectedSortItem.value = undefined;
|
||||
selectedCategory.value = undefined;
|
||||
selectedTag.value = undefined;
|
||||
selectedContributor.value = undefined;
|
||||
keyword.value = "";
|
||||
handleFetchPosts(1);
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedVisibleItem.value.value ||
|
||||
selectedPublishStatusItem.value.value !== undefined ||
|
||||
selectedSortItem.value ||
|
||||
selectedCategory.value ||
|
||||
selectedTag.value ||
|
||||
selectedContributor.value ||
|
||||
keyword.value
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<PostSettingModal
|
||||
|
@ -462,86 +504,62 @@ function handleContributorChange(user?: User) {
|
|||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="handleFetchPosts"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<div
|
||||
v-if="selectedPublishStatusItem.value"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
状态:{{ selectedPublishStatusItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handlePublishStatusItemChange(PublishStatuItems[0])"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedVisibleItem.value"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
可见性:{{ selectedVisibleItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleVisibleItemChange(VisibleItems[0])"
|
||||
/>
|
||||
</div>
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
|
||||
<div
|
||||
<FilterTag
|
||||
v-if="selectedPublishStatusItem.value !== undefined"
|
||||
@close="handlePublishStatusItemChange(PublishStatuItems[0])"
|
||||
>
|
||||
状态:{{ selectedPublishStatusItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedVisibleItem.value"
|
||||
@close="handleVisibleItemChange(VisibleItems[0])"
|
||||
>
|
||||
可见性:{{ selectedVisibleItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedCategory"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleCategoryChange()"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
分类:{{ selectedCategory.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleCategoryChange()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedTag"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
标签:{{ selectedTag.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleTagChange()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
分类:{{ selectedCategory.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag v-if="selectedTag" @click="handleTagChange()">
|
||||
标签:{{ selectedTag.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedContributor"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleContributorChange()"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
作者:{{ selectedContributor.spec.displayName }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleContributorChange()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
作者:{{ selectedContributor.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSortItemChange()"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSortItemChange()"
|
||||
/>
|
||||
</div>
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilteCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">
|
||||
|
@ -792,7 +810,7 @@ function handleContributorChange(user?: User) {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchPosts"
|
||||
@click="handleFetchPosts()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import {
|
||||
IconAddCircle,
|
||||
IconArrowDown,
|
||||
IconCloseCircle,
|
||||
IconPlug,
|
||||
IconRefreshLine,
|
||||
VButton,
|
||||
|
@ -14,11 +13,14 @@ import {
|
|||
} from "@halo-dev/components";
|
||||
import PluginListItem from "./components/PluginListItem.vue";
|
||||
import PluginUploadModal from "./components/PluginUploadModal.vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import type { PluginList } from "@halo-dev/api-client";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import FilteCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
|
@ -38,12 +40,16 @@ const pluginInstall = ref(false);
|
|||
const keyword = ref("");
|
||||
const refreshInterval = ref();
|
||||
|
||||
const handleFetchPlugins = async () => {
|
||||
const handleFetchPlugins = async (page?: number) => {
|
||||
try {
|
||||
clearInterval(refreshInterval.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
if (page) {
|
||||
plugins.value.page = page;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.plugin.listPlugins({
|
||||
page: plugins.value.page,
|
||||
size: plugins.value.size,
|
||||
|
@ -132,12 +138,40 @@ const selectedSortItem = ref<SortItem>();
|
|||
|
||||
function handleEnabledItemChange(enabledItem: EnabledItem) {
|
||||
selectedEnabledItem.value = enabledItem;
|
||||
handleFetchPlugins();
|
||||
handleFetchPlugins(1);
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
handleFetchPlugins();
|
||||
handleFetchPlugins(1);
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
}
|
||||
handleFetchPlugins(1);
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
handleFetchPlugins(1);
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedEnabledItem.value?.value !== undefined ||
|
||||
selectedSortItem.value?.value ||
|
||||
keyword.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedEnabledItem.value = undefined;
|
||||
selectedSortItem.value = undefined;
|
||||
keyword.value = "";
|
||||
handleFetchPlugins(1);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -174,37 +208,34 @@ function handleSortItemChange(sortItem?: SortItem) {
|
|||
>
|
||||
<div class="flex w-full flex-1 items-center gap-2 sm:w-auto">
|
||||
<FormKit
|
||||
v-model="keyword"
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
placeholder="输入关键词搜索"
|
||||
type="text"
|
||||
@keyup.enter="
|
||||
handlePaginationChange({ page: 1, size: plugins.size })
|
||||
"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
<div
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
关键词:{{ keyword }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedEnabledItem?.value !== undefined"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleEnabledItemChange(EnabledItems[0])"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
启用状态:{{ selectedEnabledItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleEnabledItemChange(EnabledItems[0])"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
启用状态:{{ selectedEnabledItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
class="group flex cursor-pointer items-center justify-center gap-1 rounded-full bg-gray-200 px-2 py-1 hover:bg-gray-300"
|
||||
@close="handleSortItemChange()"
|
||||
>
|
||||
<span class="text-xs text-gray-600 group-hover:text-gray-900">
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</span>
|
||||
<IconCloseCircle
|
||||
class="h-4 w-4 text-gray-600"
|
||||
@click="handleSortItemChange()"
|
||||
/>
|
||||
</div>
|
||||
排序:{{ selectedSortItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilteCleanButton v-if="hasFilters" @click="handleClearFilters" />
|
||||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
<VSpace spacing="lg">
|
||||
|
@ -265,7 +296,7 @@ function handleSortItemChange(sortItem?: SortItem) {
|
|||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="handleFetchPlugins"
|
||||
@click="handleFetchPlugins()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
:class="{ 'animate-spin text-gray-900': loading }"
|
||||
|
|
Loading…
Reference in New Issue