feat: refine post filtering (#615)

#### What type of PR is this?

/kind feature
/milestone 2.0

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

完善文章的筛选,支持关键词搜索,以及排序。适配 https://github.com/halo-dev/halo/pull/2436

todo list:

- [x] 更新 `@halo-dev/api-client`

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/2424

#### Special notes for your reviewer:

/hold 

等待 https://github.com/halo-dev/halo/pull/2436 合并,以及 https://github.com/halo-dev/halo/issues/2439 被解决。

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

```release-note
完善后台的文章筛选功能
```
pull/620/head
Ryan Wang 2022-09-23 11:18:12 +08:00 committed by GitHub
parent 76345ec231
commit c718094748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 110 deletions

View File

@ -34,7 +34,7 @@
"@formkit/themes": "1.0.0-beta.10", "@formkit/themes": "1.0.0-beta.10",
"@formkit/vue": "1.0.0-beta.10", "@formkit/vue": "1.0.0-beta.10",
"@halo-dev/admin-shared": "workspace:*", "@halo-dev/admin-shared": "workspace:*",
"@halo-dev/api-client": "^0.0.25", "@halo-dev/api-client": "^0.0.26",
"@halo-dev/components": "workspace:*", "@halo-dev/components": "workspace:*",
"@halo-dev/richtext-editor": "^0.0.0-alpha.7", "@halo-dev/richtext-editor": "^0.0.0-alpha.7",
"@tiptap/extension-character-count": "2.0.0-beta.31", "@tiptap/extension-character-count": "2.0.0-beta.31",

View File

@ -14,7 +14,7 @@ importers:
'@formkit/themes': 1.0.0-beta.10 '@formkit/themes': 1.0.0-beta.10
'@formkit/vue': 1.0.0-beta.10 '@formkit/vue': 1.0.0-beta.10
'@halo-dev/admin-shared': workspace:* '@halo-dev/admin-shared': workspace:*
'@halo-dev/api-client': ^0.0.25 '@halo-dev/api-client': ^0.0.26
'@halo-dev/components': workspace:* '@halo-dev/components': workspace:*
'@halo-dev/richtext-editor': ^0.0.0-alpha.7 '@halo-dev/richtext-editor': ^0.0.0-alpha.7
'@iconify-json/mdi': ^1.1.33 '@iconify-json/mdi': ^1.1.33
@ -100,7 +100,7 @@ importers:
'@formkit/themes': 1.0.0-beta.10_tailwindcss@3.1.8 '@formkit/themes': 1.0.0-beta.10_tailwindcss@3.1.8
'@formkit/vue': 1.0.0-beta.10_jhzixbi2r7n2xnmwczrcaimaey '@formkit/vue': 1.0.0-beta.10_jhzixbi2r7n2xnmwczrcaimaey
'@halo-dev/admin-shared': link:packages/shared '@halo-dev/admin-shared': link:packages/shared
'@halo-dev/api-client': 0.0.25 '@halo-dev/api-client': 0.0.26
'@halo-dev/components': link:packages/components '@halo-dev/components': link:packages/components
'@halo-dev/richtext-editor': 0.0.0-alpha.7_vue@3.2.39 '@halo-dev/richtext-editor': 0.0.0-alpha.7_vue@3.2.39
'@tiptap/extension-character-count': 2.0.0-beta.31 '@tiptap/extension-character-count': 2.0.0-beta.31
@ -1892,8 +1892,8 @@ packages:
- windicss - windicss
dev: false dev: false
/@halo-dev/api-client/0.0.25: /@halo-dev/api-client/0.0.26:
resolution: {integrity: sha512-USCCsKam7wH6vExP3SrTBz+vvOndRWPek2CZSfv0FeE8T+TNDR/X/wNRy3BPl//O0wf7TkJUgFyhgbRf+z02YA==} resolution: {integrity: sha512-MSyl2wF3KXSQSYWuqdBcaBgct1RzcQAS9HcLL1oBb0lDLjlUgZv6IEANpPZS80UN0bQjvsGl9JOnqwoIUKUK8A==}
dev: false dev: false
/@halo-dev/richtext-editor/0.0.0-alpha.7_vue@3.2.39: /@halo-dev/richtext-editor/0.0.0-alpha.7_vue@3.2.39:

View File

@ -37,7 +37,6 @@ import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category"; import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag"; import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
import { postLabels } from "@/constants/labels";
enum PostPhase { enum PostPhase {
DRAFT = "未发布", DRAFT = "未发布",
@ -62,51 +61,42 @@ const selectedPostWithContent = ref<PostRequest | null>(null);
const checkedAll = ref(false); const checkedAll = ref(false);
const selectedPostNames = ref<string[]>([]); const selectedPostNames = ref<string[]>([]);
const { categories } = usePostCategory({ fetchOnMounted: true });
const { tags } = usePostTag({ fetchOnMounted: true });
const dialog = useDialog(); const dialog = useDialog();
const handleFetchPosts = async () => { const handleFetchPosts = async () => {
try { try {
loading.value = true; loading.value = true;
const labelSelector: string[] = [];
if (selectedVisibleFilterItem.value.value) {
labelSelector.push(
`${postLabels.VISIBLE}=${selectedVisibleFilterItem.value.value}`
);
}
if (selectedPhaseFilterItem.value.value) {
labelSelector.push(
`${postLabels.PHASE}=${selectedPhaseFilterItem.value.value}`
);
}
let categories: string[] | undefined; let categories: string[] | undefined;
let tags: string[] | undefined; let tags: string[] | undefined;
let contributors: string[] | undefined; let contributors: string[] | undefined;
if (selectedCategoryFilterItem.value) { if (selectedCategory.value) {
categories = [selectedCategoryFilterItem.value.metadata.name]; categories = [
selectedCategory.value.metadata.name,
selectedCategory.value.metadata.name,
];
} }
if (selectedTagFilterItem.value) { if (selectedTag.value) {
tags = [selectedTagFilterItem.value.metadata.name]; tags = [selectedTag.value.metadata.name];
} }
if (selectedContributorItem.value) { if (selectedContributor.value) {
contributors = [selectedContributorItem.value.metadata.name]; contributors = [selectedContributor.value.metadata.name];
} }
const { data } = await apiClient.post.listPosts({ const { data } = await apiClient.post.listPosts({
page: posts.value.page, page: posts.value.page,
size: posts.value.size, size: posts.value.size,
labelSelector, visible: selectedVisibleItem.value?.value,
categories, publishPhase: selectedPublishPhaseItem.value?.value,
tags, sort: selectedSortItem.value?.sort,
contributors, sortOrder: selectedSortItem.value?.sortOrder,
keyword: keyword.value,
category: categories,
tag: tags,
contributor: contributors,
}); });
posts.value = data; posts.value = data;
} catch (e) { } catch (e) {
@ -265,15 +255,28 @@ onMounted(() => {
handleFetchPosts(); handleFetchPosts();
}); });
interface FilterItem { // Filters
interface VisibleItem {
label: string; label: string;
value: string | undefined; value?: "PUBLIC" | "INTERNAL" | "PRIVATE";
} }
const VisibleFilterItems: FilterItem[] = [ interface PublishPhaseItem {
label: string;
value?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED";
}
interface SortItem {
label: string;
sort: "PUBLISH_TIME" | "CREATE_TIME";
sortOrder: boolean;
}
const VisibleItems: VisibleItem[] = [
{ {
label: "全部", label: "全部",
value: "", value: undefined,
}, },
{ {
label: "公开", label: "公开",
@ -289,10 +292,10 @@ const VisibleFilterItems: FilterItem[] = [
}, },
]; ];
const PhaseFilterItems: FilterItem[] = [ const PublishPhaseItems: PublishPhaseItem[] = [
{ {
label: "全部", label: "全部",
value: "", value: undefined,
}, },
{ {
label: "已发布", label: "已发布",
@ -308,34 +311,67 @@ const PhaseFilterItems: FilterItem[] = [
}, },
]; ];
const selectedVisibleFilterItem = ref<FilterItem>(VisibleFilterItems[0]); const SortItems: SortItem[] = [
const selectedPhaseFilterItem = ref<FilterItem>(PhaseFilterItems[0]); {
const selectedCategoryFilterItem = ref<Category>(); label: "较近发布",
const selectedTagFilterItem = ref<Tag>(); sort: "PUBLISH_TIME",
const selectedContributorItem = ref<User>(); sortOrder: false,
},
{
label: "较早发布",
sort: "PUBLISH_TIME",
sortOrder: true,
},
{
label: "较近创建",
sort: "CREATE_TIME",
sortOrder: false,
},
{
label: "较早创建",
sort: "CREATE_TIME",
sortOrder: true,
},
];
function handleVisibleFilterItemChange(filterItem: FilterItem) { const { categories } = usePostCategory({ fetchOnMounted: true });
selectedVisibleFilterItem.value = filterItem; const { tags } = usePostTag({ fetchOnMounted: true });
const selectedVisibleItem = ref<VisibleItem>(VisibleItems[0]);
const selectedPublishPhaseItem = ref<PublishPhaseItem>(PublishPhaseItems[0]);
const selectedSortItem = ref<SortItem>();
const selectedCategory = ref<Category>();
const selectedTag = ref<Tag>();
const selectedContributor = ref<User>();
const keyword = ref("");
function handleVisibleItemChange(visibleItem: VisibleItem) {
selectedVisibleItem.value = visibleItem;
handleFetchPosts(); handleFetchPosts();
} }
function handlePhaseFilterItemChange(filterItem: FilterItem) { function handlePublishPhaseItemChange(publishPhaseItem: PublishPhaseItem) {
selectedPhaseFilterItem.value = filterItem; selectedPublishPhaseItem.value = publishPhaseItem;
handleFetchPosts(); handleFetchPosts();
} }
function handleCategoryFilterItemChange(category?: Category) { function handleSortItemChange(sortItem?: SortItem) {
selectedCategoryFilterItem.value = category; selectedSortItem.value = sortItem;
handleFetchPosts(); handleFetchPosts();
} }
function handleTagFilterItemChange(tag?: Tag) { function handleCategoryChange(category?: Category) {
selectedTagFilterItem.value = tag; selectedCategory.value = category;
handleFetchPosts(); handleFetchPosts();
} }
function handleContributorFilterItemChange(user?: User) { function handleTagChange(tag?: Tag) {
selectedContributorItem.value = user; selectedTag.value = tag;
handleFetchPosts();
}
function handleContributorChange(user?: User) {
selectedContributor.value = user;
handleFetchPosts(); handleFetchPosts();
} }
</script> </script>
@ -392,69 +428,84 @@ function handleContributorFilterItemChange(user?: User) {
v-if="!selectedPostNames.length" v-if="!selectedPostNames.length"
class="flex items-center gap-2" class="flex items-center gap-2"
> >
<FormKit placeholder="输入关键词搜索" type="text"></FormKit> <FormKit
v-model="keyword"
placeholder="输入关键词搜索"
type="text"
@keyup.enter="handleFetchPosts"
></FormKit>
<div <div
v-if="selectedPhaseFilterItem.value" v-if="selectedPublishPhaseItem.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" 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"> <span class="text-xs text-gray-600 group-hover:text-gray-900">
状态{{ selectedPhaseFilterItem.label }} 状态{{ selectedPublishPhaseItem.label }}
</span> </span>
<IconCloseCircle <IconCloseCircle
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@click="handlePhaseFilterItemChange(PhaseFilterItems[0])" @click="handlePublishPhaseItemChange(PublishPhaseItems[0])"
/> />
</div> </div>
<div <div
v-if="selectedVisibleFilterItem.value" 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" 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"> <span class="text-xs text-gray-600 group-hover:text-gray-900">
可见性{{ selectedVisibleFilterItem.label }} 可见性{{ selectedVisibleItem.label }}
</span> </span>
<IconCloseCircle <IconCloseCircle
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@click=" @click="handleVisibleItemChange(VisibleItems[0])"
handleVisibleFilterItemChange(VisibleFilterItems[0])
"
/> />
</div> </div>
<div <div
v-if="selectedCategoryFilterItem" 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" 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"> <span class="text-xs text-gray-600 group-hover:text-gray-900">
分类{{ selectedCategoryFilterItem.spec.displayName }} 分类{{ selectedCategory.spec.displayName }}
</span> </span>
<IconCloseCircle <IconCloseCircle
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@click="handleCategoryFilterItemChange()" @click="handleCategoryChange()"
/> />
</div> </div>
<div <div
v-if="selectedTagFilterItem" 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" 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"> <span class="text-xs text-gray-600 group-hover:text-gray-900">
标签{{ selectedTagFilterItem.spec.displayName }} 标签{{ selectedTag.spec.displayName }}
</span> </span>
<IconCloseCircle <IconCloseCircle
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@click="handleTagFilterItemChange()" @click="handleTagChange()"
/> />
</div> </div>
<div <div
v-if="selectedContributorItem" 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" 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"> <span class="text-xs text-gray-600 group-hover:text-gray-900">
标签{{ selectedContributorItem.spec.displayName }} 作者{{ selectedContributor.spec.displayName }}
</span> </span>
<IconCloseCircle <IconCloseCircle
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@click="handleContributorFilterItemChange()" @click="handleContributorChange()"
/>
</div>
<div
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"
>
<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> </div>
</div> </div>
@ -479,16 +530,16 @@ function handleContributorFilterItemChange(user?: User) {
<div class="w-72 p-4"> <div class="w-72 p-4">
<ul class="space-y-1"> <ul class="space-y-1">
<li <li
v-for="(filterItem, index) in PhaseFilterItems" v-for="(filterItem, index) in PublishPhaseItems"
:key="index" :key="index"
v-close-popper v-close-popper
:class="{ :class="{
'bg-gray-100': 'bg-gray-100':
selectedPhaseFilterItem.value === selectedPublishPhaseItem.value ===
filterItem.value, filterItem.value,
}" }"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900" class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handlePhaseFilterItemChange(filterItem)" @click="handlePublishPhaseItemChange(filterItem)"
> >
<span class="truncate">{{ filterItem.label }}</span> <span class="truncate">{{ filterItem.label }}</span>
</li> </li>
@ -509,16 +560,15 @@ function handleContributorFilterItemChange(user?: User) {
<div class="w-72 p-4"> <div class="w-72 p-4">
<ul class="space-y-1"> <ul class="space-y-1">
<li <li
v-for="(filterItem, index) in VisibleFilterItems" v-for="(filterItem, index) in VisibleItems"
:key="index" :key="index"
v-close-popper v-close-popper
:class="{ :class="{
'bg-gray-100': 'bg-gray-100':
selectedVisibleFilterItem.value === selectedVisibleItem.value === filterItem.value,
filterItem.value,
}" }"
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900" class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleVisibleFilterItemChange(filterItem)" @click="handleVisibleItemChange(filterItem)"
> >
<span class="truncate"> <span class="truncate">
{{ filterItem.label }} {{ filterItem.label }}
@ -553,13 +603,13 @@ function handleContributorFilterItemChange(user?: User) {
v-for="(category, index) in categories" v-for="(category, index) in categories"
:key="index" :key="index"
v-close-popper v-close-popper
@click="handleCategoryFilterItemChange(category)" @click="handleCategoryChange(category)"
> >
<div <div
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
:class="{ :class="{
'bg-gray-100': 'bg-gray-100':
selectedCategoryFilterItem?.metadata.name === selectedCategory?.metadata.name ===
category.metadata.name, category.metadata.name,
}" }"
> >
@ -626,13 +676,13 @@ function handleContributorFilterItemChange(user?: User) {
v-for="(tag, index) in tags" v-for="(tag, index) in tags"
:key="index" :key="index"
v-close-popper v-close-popper
@click="handleTagFilterItemChange(tag)" @click="handleTagChange(tag)"
> >
<div <div
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
:class="{ :class="{
'bg-gray-100': 'bg-gray-100':
selectedTagFilterItem?.metadata.name === selectedTag?.metadata.name ===
tag.metadata.name, tag.metadata.name,
}" }"
> >
@ -668,8 +718,8 @@ function handleContributorFilterItemChange(user?: User) {
</template> </template>
</FloatingDropdown> </FloatingDropdown>
<UserDropdownSelector <UserDropdownSelector
v-model:selected="selectedContributorItem" v-model:selected="selectedContributor"
@select="handleContributorFilterItemChange" @select="handleContributorChange"
> >
<div <div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black" class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
@ -693,34 +743,13 @@ function handleContributorFilterItemChange(user?: User) {
<div class="w-72 p-4"> <div class="w-72 p-4">
<ul class="space-y-1"> <ul class="space-y-1">
<li <li
v-for="(sortItem, index) in SortItems"
:key="index"
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900" class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="handleSortItemChange(sortItem)"
> >
<span class="truncate">较近发布</span> <span class="truncate">{{ sortItem.label }}</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">较晚发布</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">浏览量最多</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">浏览量最少</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">评论量最多</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">评论量最少</span>
</li> </li>
</ul> </ul>
</div> </div>