mirror of https://github.com/halo-dev/halo
feat: refine post filtering
parent
ea10523dd5
commit
bcd997231a
|
@ -9,6 +9,7 @@ import {
|
||||||
IconEyeOff,
|
IconEyeOff,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconTeam,
|
IconTeam,
|
||||||
|
IconCloseCircle,
|
||||||
useDialog,
|
useDialog,
|
||||||
VButton,
|
VButton,
|
||||||
VCard,
|
VCard,
|
||||||
|
@ -21,7 +22,14 @@ import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSel
|
||||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||||
import PostTag from "../posts/tags/components/PostTag.vue";
|
import PostTag from "../posts/tags/components/PostTag.vue";
|
||||||
import { onMounted, ref, watch, watchEffect } from "vue";
|
import { onMounted, ref, watch, watchEffect } from "vue";
|
||||||
import type { ListedPostList, Post, PostRequest } from "@halo-dev/api-client";
|
import type {
|
||||||
|
User,
|
||||||
|
Category,
|
||||||
|
ListedPostList,
|
||||||
|
Post,
|
||||||
|
PostRequest,
|
||||||
|
Tag,
|
||||||
|
} from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@halo-dev/admin-shared";
|
import { apiClient } from "@halo-dev/admin-shared";
|
||||||
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";
|
||||||
|
@ -74,10 +82,29 @@ const handleFetchPosts = async () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let categories: string[] | undefined;
|
||||||
|
let tags: string[] | undefined;
|
||||||
|
let contributors: string[] | undefined;
|
||||||
|
|
||||||
|
if (selectedCategoryFilterItem.value) {
|
||||||
|
categories = [selectedCategoryFilterItem.value.metadata.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTagFilterItem.value) {
|
||||||
|
tags = [selectedTagFilterItem.value.metadata.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedContributorItem.value) {
|
||||||
|
contributors = [selectedContributorItem.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,
|
labelSelector,
|
||||||
|
categories,
|
||||||
|
tags,
|
||||||
|
contributors,
|
||||||
});
|
});
|
||||||
posts.value = data;
|
posts.value = data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -266,6 +293,9 @@ const PhaseFilterItems: FilterItem[] = [
|
||||||
|
|
||||||
const selectedVisibleFilterItem = ref<FilterItem>(VisibleFilterItems[0]);
|
const selectedVisibleFilterItem = ref<FilterItem>(VisibleFilterItems[0]);
|
||||||
const selectedPhaseFilterItem = ref<FilterItem>(PhaseFilterItems[0]);
|
const selectedPhaseFilterItem = ref<FilterItem>(PhaseFilterItems[0]);
|
||||||
|
const selectedCategoryFilterItem = ref<Category>();
|
||||||
|
const selectedTagFilterItem = ref<Tag>();
|
||||||
|
const selectedContributorItem = ref<User>();
|
||||||
|
|
||||||
function handleVisibleFilterItemChange(filterItem: FilterItem) {
|
function handleVisibleFilterItemChange(filterItem: FilterItem) {
|
||||||
selectedVisibleFilterItem.value = filterItem;
|
selectedVisibleFilterItem.value = filterItem;
|
||||||
|
@ -276,6 +306,21 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
selectedPhaseFilterItem.value = filterItem;
|
selectedPhaseFilterItem.value = filterItem;
|
||||||
handleFetchPosts();
|
handleFetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCategoryFilterItemChange(category?: Category) {
|
||||||
|
selectedCategoryFilterItem.value = category;
|
||||||
|
handleFetchPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTagFilterItemChange(tag?: Tag) {
|
||||||
|
selectedTagFilterItem.value = tag;
|
||||||
|
handleFetchPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleContributorFilterItemChange(user?: User) {
|
||||||
|
selectedContributorItem.value = user;
|
||||||
|
handleFetchPosts();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PostSettingModal
|
<PostSettingModal
|
||||||
|
@ -325,9 +370,76 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
@change="handleCheckAllChange"
|
@change="handleCheckAllChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-1 sm:w-auto">
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||||
<div v-if="!selectedPostNames.length">
|
<div
|
||||||
|
v-if="!selectedPostNames.length"
|
||||||
|
class="flex items-center gap-2"
|
||||||
|
>
|
||||||
<FormKit placeholder="输入关键词搜索" type="text"></FormKit>
|
<FormKit placeholder="输入关键词搜索" type="text"></FormKit>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedPhaseFilterItem.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">
|
||||||
|
状态:{{ selectedPhaseFilterItem.label }}
|
||||||
|
</span>
|
||||||
|
<IconCloseCircle
|
||||||
|
class="h-4 w-4 text-gray-600"
|
||||||
|
@click="handlePhaseFilterItemChange(PhaseFilterItems[0])"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedVisibleFilterItem.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">
|
||||||
|
可见性:{{ selectedVisibleFilterItem.label }}
|
||||||
|
</span>
|
||||||
|
<IconCloseCircle
|
||||||
|
class="h-4 w-4 text-gray-600"
|
||||||
|
@click="
|
||||||
|
handleVisibleFilterItemChange(VisibleFilterItems[0])
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedCategoryFilterItem"
|
||||||
|
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">
|
||||||
|
分类:{{ selectedCategoryFilterItem.spec.displayName }}
|
||||||
|
</span>
|
||||||
|
<IconCloseCircle
|
||||||
|
class="h-4 w-4 text-gray-600"
|
||||||
|
@click="handleCategoryFilterItemChange()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTagFilterItem"
|
||||||
|
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">
|
||||||
|
标签:{{ selectedTagFilterItem.spec.displayName }}
|
||||||
|
</span>
|
||||||
|
<IconCloseCircle
|
||||||
|
class="h-4 w-4 text-gray-600"
|
||||||
|
@click="handleTagFilterItemChange()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedContributorItem"
|
||||||
|
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">
|
||||||
|
标签:{{ selectedContributorItem.spec.displayName }}
|
||||||
|
</span>
|
||||||
|
<IconCloseCircle
|
||||||
|
class="h-4 w-4 text-gray-600"
|
||||||
|
@click="handleContributorFilterItemChange()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VSpace v-else>
|
<VSpace v-else>
|
||||||
<VButton type="default">设置</VButton>
|
<VButton type="default">设置</VButton>
|
||||||
|
@ -423,9 +535,15 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
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)"
|
||||||
>
|
>
|
||||||
<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="{
|
||||||
|
'bg-gray-100':
|
||||||
|
selectedCategoryFilterItem?.metadata.name ===
|
||||||
|
category.metadata.name,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div class="relative flex flex-row items-center">
|
<div class="relative flex flex-row items-center">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
@ -439,7 +557,7 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex">
|
<div class="mt-1 flex">
|
||||||
<span class="text-xs text-gray-500">
|
<span class="text-xs text-gray-500">
|
||||||
/categories/{{ category.spec.slug }}
|
{{ category.status?.permalink }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -450,7 +568,8 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||||
>
|
>
|
||||||
20 篇文章
|
{{ category.status?.posts?.length || 0 }}
|
||||||
|
篇文章
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -489,9 +608,15 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
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)"
|
||||||
>
|
>
|
||||||
<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="{
|
||||||
|
'bg-gray-100':
|
||||||
|
selectedTagFilterItem?.metadata.name ===
|
||||||
|
tag.metadata.name,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div class="relative flex flex-row items-center">
|
<div class="relative flex flex-row items-center">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
@ -500,7 +625,7 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex">
|
<div class="mt-1 flex">
|
||||||
<span class="text-xs text-gray-500">
|
<span class="text-xs text-gray-500">
|
||||||
/tags/{{ tag.spec.slug }}
|
{{ tag.status?.permalink }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -511,7 +636,8 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||||
>
|
>
|
||||||
20 篇文章
|
{{ tag.status?.posts?.length || 0 }}
|
||||||
|
篇文章
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -523,7 +649,10 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</FloatingDropdown>
|
</FloatingDropdown>
|
||||||
<UserDropdownSelector>
|
<UserDropdownSelector
|
||||||
|
v-model:selected="selectedContributorItem"
|
||||||
|
@select="handleContributorFilterItemChange"
|
||||||
|
>
|
||||||
<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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
|
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
|
||||||
import { computed, ref, watchEffect } from "vue";
|
import { computed, ref, watch, watchEffect } from "vue";
|
||||||
import type { PostRequest } from "@halo-dev/api-client";
|
import type { PostRequest } from "@halo-dev/api-client";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
||||||
|
@ -67,7 +67,7 @@ const saving = ref(false);
|
||||||
const publishing = ref(false);
|
const publishing = ref(false);
|
||||||
const publishCanceling = ref(false);
|
const publishCanceling = ref(false);
|
||||||
|
|
||||||
const { categories } = usePostCategory({ fetchOnMounted: true });
|
const { categories, handleFetchCategories } = usePostCategory();
|
||||||
const categoriesMap = computed(() => {
|
const categoriesMap = computed(() => {
|
||||||
return categories.value.map((category) => {
|
return categories.value.map((category) => {
|
||||||
return {
|
return {
|
||||||
|
@ -77,7 +77,7 @@ const categoriesMap = computed(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const { tags } = usePostTag({ fetchOnMounted: true });
|
const { tags, handleFetchTags } = usePostTag();
|
||||||
const tagsMap = computed(() => {
|
const tagsMap = computed(() => {
|
||||||
return tags.value.map((tag) => {
|
return tags.value.map((tag) => {
|
||||||
return {
|
return {
|
||||||
|
@ -180,6 +180,16 @@ const handlePublishCanceling = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(visible) => {
|
||||||
|
if (visible) {
|
||||||
|
handleFetchCategories();
|
||||||
|
handleFetchTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.post) {
|
if (props.post) {
|
||||||
formState.value = cloneDeep(props.post);
|
formState.value = cloneDeep(props.post);
|
||||||
|
|
Loading…
Reference in New Issue