refactor: logic of comment data filtering (#4195)

#### What type of PR is this?

/area console
/kind improvement
/milestone 2.8.x

#### What this PR does / why we need it:

重构评论数据管理的筛选条件逻辑以及 UI。

<img width="1650" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/d89769bb-b8af-427e-a1ec-6ac8674131d3">

Ref https://github.com/halo-dev/halo/pull/4182
Ref https://github.com/halo-dev/halo/issues/4181

#### Special notes for your reviewer:

需要测试:

1. 测试评论的筛选条件包括关键词筛选功能是否正常。

#### Does this PR introduce a user-facing change?

```release-note
重构 Console 端评论数据列表的筛选项 UI 和逻辑。
```
pull/4212/head
Ryan Wang 2023-07-12 09:53:12 +08:00 committed by GitHub
parent 119f352145
commit 197096305b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 198 deletions

View File

@ -424,7 +424,6 @@ core:
filters: filters:
status: status:
items: items:
all: All
approved: Approved approved: Approved
pending_review: Pending Review pending_review: Pending Review
owner: owner:

View File

@ -424,7 +424,6 @@ core:
filters: filters:
status: status:
items: items:
all: 全部
approved: 已审核 approved: 已审核
pending_review: 待审核 pending_review: 待审核
owner: owner:

View File

@ -424,7 +424,6 @@ core:
filters: filters:
status: status:
items: items:
all: 全部
approved: 已審核 approved: 已審核
pending_review: 待審核 pending_review: 待審核
owner: owner:

View File

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
IconArrowDown,
IconMessage, IconMessage,
VButton, VButton,
VCard, VCard,
@ -11,16 +10,12 @@ import {
VEmpty, VEmpty,
Dialog, Dialog,
VLoading, VLoading,
VDropdown,
VDropdownItem,
Toast, Toast,
} from "@halo-dev/components"; } from "@halo-dev/components";
import CommentListItem from "./components/CommentListItem.vue"; import CommentListItem from "./components/CommentListItem.vue";
import type { ListedComment } from "@halo-dev/api-client"; import type { ListedComment } from "@halo-dev/api-client";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import FilterTag from "@/components/filter/FilterTag.vue";
import { getNode } from "@formkit/core";
import { useQuery } from "@tanstack/vue-query"; import { useQuery } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue"; import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
@ -30,108 +25,36 @@ const { t } = useI18n();
const checkAll = ref(false); const checkAll = ref(false);
const selectedComment = ref<ListedComment>(); const selectedComment = ref<ListedComment>();
const selectedCommentNames = ref<string[]>([]); const selectedCommentNames = ref<string[]>([]);
const keyword = ref(""); const keyword = ref("");
const selectedApprovedStatus = ref();
// Filters const selectedSort = ref();
interface SortItem {
label: string;
sort: string;
}
const ApprovedFilterItems: { label: string; value?: boolean }[] = [
{
label: t("core.comment.filters.status.items.all"),
value: undefined,
},
{
label: t("core.comment.filters.status.items.approved"),
value: true,
},
{
label: t("core.comment.filters.status.items.pending_review"),
value: false,
},
];
const SortItems: SortItem[] = [
{
label: t("core.comment.filters.sort.items.last_reply_time_desc"),
sort: "lastReplyTime,desc",
},
{
label: t("core.comment.filters.sort.items.last_reply_time_asc"),
sort: "lastReplyTime,asc",
},
{
label: t("core.comment.filters.sort.items.reply_count_desc"),
sort: "replyCount,desc",
},
{
label: t("core.comment.filters.sort.items.reply_count_asc"),
sort: "replyCount,asc",
},
{
label: t("core.comment.filters.sort.items.create_time_desc"),
sort: "creationTimestamp,desc",
},
{
label: t("core.comment.filters.sort.items.create_time_asc"),
sort: "creationTimestamp,asc",
},
];
const selectedApprovedFilterItem = ref<{ label: string; value?: boolean }>(
ApprovedFilterItems[0]
);
const selectedSortItem = ref<SortItem>();
const selectedUser = ref(); const selectedUser = ref();
const handleApprovedFilterItemChange = (filterItem: { watch(
label: string; () => [
value?: boolean; selectedApprovedStatus.value,
}) => { selectedSort.value,
selectedApprovedFilterItem.value = filterItem; selectedUser.value,
selectedCommentNames.value = []; keyword.value,
],
() => {
page.value = 1; page.value = 1;
};
const handleSortItemChange = (sortItem: SortItem) => {
selectedSortItem.value = sortItem;
selectedCommentNames.value = [];
page.value = 1;
};
function handleKeywordChange() {
const keywordNode = getNode("keywordInput");
if (keywordNode) {
keyword.value = keywordNode._value as string;
} }
page.value = 1; );
}
function handleClearKeyword() {
keyword.value = "";
page.value = 1;
}
const hasFilters = computed(() => { const hasFilters = computed(() => {
return ( return (
selectedApprovedFilterItem.value.value !== undefined || selectedApprovedStatus.value !== undefined ||
selectedSortItem.value || selectedSort.value ||
selectedUser.value || selectedUser.value
keyword.value
); );
}); });
function handleClearFilters() { function handleClearFilters() {
selectedApprovedFilterItem.value = ApprovedFilterItems[0]; selectedApprovedStatus.value = undefined;
selectedSortItem.value = undefined; selectedSort.value = undefined;
selectedUser.value = undefined; selectedUser.value = undefined;
keyword.value = "";
page.value = 1;
} }
const page = ref(1); const page = ref(1);
@ -148,8 +71,8 @@ const {
"comments", "comments",
page, page,
size, size,
selectedApprovedFilterItem, selectedApprovedStatus,
selectedSortItem, selectedSort,
selectedUser, selectedUser,
keyword, keyword,
], ],
@ -157,8 +80,8 @@ const {
const { data } = await apiClient.comment.listComments({ const { data } = await apiClient.comment.listComments({
page: page.value, page: page.value,
size: size.value, size: size.value,
approved: selectedApprovedFilterItem.value.value, approved: selectedApprovedStatus.value,
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[], sort: [selectedSort.value].filter(Boolean) as string[],
keyword: keyword.value, keyword: keyword.value,
ownerName: selectedUser.value, ownerName: selectedUser.value,
}); });
@ -305,57 +228,10 @@ const handleApproveInBatch = async () => {
/> />
</div> </div>
<div class="flex w-full flex-1 items-center sm:w-auto"> <div class="flex w-full flex-1 items-center sm:w-auto">
<div <SearchInput
v-if="!selectedCommentNames.length" v-if="!selectedCommentNames.length"
class="flex items-center gap-2" v-model="keyword"
>
<FormKit
id="keywordInput"
outer-class="!p-0"
:placeholder="$t('core.common.placeholder.search')"
type="text"
name="keyword"
:model-value="keyword"
@keyup.enter="handleKeywordChange"
></FormKit>
<FilterTag v-if="keyword" @close="handleClearKeyword()">
{{
$t("core.common.filters.results.keyword", {
keyword: keyword,
})
}}
</FilterTag>
<FilterTag
v-if="selectedApprovedFilterItem.value != undefined"
@close="
handleApprovedFilterItemChange(ApprovedFilterItems[0])
"
>
{{
$t("core.common.filters.results.status", {
status: selectedApprovedFilterItem.label,
})
}}
</FilterTag>
<FilterTag
v-if="selectedSortItem"
@close="handleSortItemChange(SortItems[0])"
>
{{
$t("core.common.filters.results.sort", {
sort: selectedSortItem.label,
})
}}
</FilterTag>
<FilterCleanButton
v-if="hasFilters"
@click="handleClearFilters"
/> />
</div>
<VSpace v-else> <VSpace v-else>
<VButton type="secondary" @click="handleApproveInBatch"> <VButton type="secondary" @click="handleApproveInBatch">
{{ {{
@ -371,56 +247,78 @@ const handleApproveInBatch = async () => {
</div> </div>
<div class="mt-4 flex sm:mt-0"> <div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg"> <VSpace spacing="lg">
<VDropdown> <FilterCleanButton
<div v-if="hasFilters"
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black" @click="handleClearFilters"
> />
<span class="mr-0.5"> <FilterDropdown
{{ $t("core.common.filters.labels.status") }} v-model="selectedApprovedStatus"
</span> :label="$t('core.common.filters.labels.status')"
<span> :items="[
<IconArrowDown /> {
</span> label: t('core.common.filters.item_labels.all'),
</div> },
<template #popper> {
<VDropdownItem label: t('core.comment.filters.status.items.approved'),
v-for="(filterItem, index) in ApprovedFilterItems" value: true,
:key="index" },
:selected=" {
selectedApprovedFilterItem.value === filterItem.value label: t(
" 'core.comment.filters.status.items.pending_review'
@click="handleApprovedFilterItemChange(filterItem)" ),
> value: false,
{{ filterItem.label }} },
</VDropdownItem> ]"
</template> />
</VDropdown>
<UserFilterDropdown <UserFilterDropdown
v-model="selectedUser" v-model="selectedUser"
:label="$t('core.comment.filters.owner.label')" :label="$t('core.comment.filters.owner.label')"
/> />
<VDropdown> <FilterDropdown
<div v-model="selectedSort"
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black" :label="$t('core.common.filters.labels.sort')"
> :items="[
<span class="mr-0.5"> {
{{ $t("core.common.filters.labels.sort") }} label: t('core.common.filters.item_labels.default'),
</span> },
<span> {
<IconArrowDown /> label: t(
</span> 'core.comment.filters.sort.items.last_reply_time_desc'
</div> ),
<template #popper> value: 'lastReplyTime,desc',
<VDropdownItem },
v-for="(filterItem, index) in SortItems" {
:key="index" label: t(
:selected="selectedSortItem?.sort === filterItem.sort" 'core.comment.filters.sort.items.last_reply_time_asc'
@click="handleSortItemChange(filterItem)" ),
> value: 'lastReplyTime,asc',
{{ filterItem.label }} },
</VDropdownItem> {
</template> label: t(
</VDropdown> 'core.comment.filters.sort.items.reply_count_desc'
),
value: 'replyCount,desc',
},
{
label: t(
'core.comment.filters.sort.items.reply_count_asc'
),
value: 'replyCount,asc',
},
{
label: t(
'core.comment.filters.sort.items.create_time_desc'
),
value: 'creationTimestamp,desc',
},
{
label: t(
'core.comment.filters.sort.items.create_time_asc'
),
value: 'creationTimestamp,asc',
},
]"
/>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<div <div
class="group cursor-pointer rounded p-1 hover:bg-gray-200" class="group cursor-pointer rounded p-1 hover:bg-gray-200"