mirror of https://github.com/halo-dev/halo
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
parent
119f352145
commit
197096305b
|
@ -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:
|
||||||
|
|
|
@ -424,7 +424,6 @@ core:
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
all: 全部
|
|
||||||
approved: 已审核
|
approved: 已审核
|
||||||
pending_review: 待审核
|
pending_review: 待审核
|
||||||
owner:
|
owner:
|
||||||
|
|
|
@ -424,7 +424,6 @@ core:
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
all: 全部
|
|
||||||
approved: 已審核
|
approved: 已審核
|
||||||
pending_review: 待審核
|
pending_review: 待審核
|
||||||
owner:
|
owner:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue