mirror of https://github.com/halo-dev/halo-admin
Browse Source
#### What type of PR is this? /kind improvement /milestone 2.0 #### What this PR does / why we need it: 优化文章管理的分类/标签/作者的筛选下拉框样式,以及支持搜索。 #### Screenshots: <img width="525" alt="image" src="https://user-images.githubusercontent.com/21301288/203499340-b7c59b2f-9be8-4804-9e21-9c5bfbca99bc.png"> #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式:检查文章管理的分类 / 标签 / 作者筛选功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 优化文章管理的分类/标签/作者的筛选下拉框样式,以及支持搜索。 ```pull/706/head
Ryan Wang
2 years ago
committed by
GitHub
4 changed files with 307 additions and 159 deletions
@ -0,0 +1,119 @@
|
||||
<script lang="ts" setup> |
||||
import type { Category } from "@halo-dev/api-client"; |
||||
import { VEntity, VEntityField } from "@halo-dev/components"; |
||||
import { setFocus } from "@/formkit/utils/focus"; |
||||
import { computed, ref, watch } from "vue"; |
||||
import Fuse from "fuse.js"; |
||||
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category"; |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
selected?: Category; |
||||
}>(), |
||||
{ |
||||
selected: undefined, |
||||
} |
||||
); |
||||
|
||||
const emit = defineEmits<{ |
||||
(event: "update:selected", category?: Category): void; |
||||
(event: "select", category?: Category): void; |
||||
}>(); |
||||
|
||||
const { categories, handleFetchCategories } = usePostCategory({ |
||||
fetchOnMounted: false, |
||||
}); |
||||
|
||||
const handleSelect = (category: Category) => { |
||||
if ( |
||||
props.selected && |
||||
category.metadata.name === props.selected.metadata.name |
||||
) { |
||||
emit("update:selected", undefined); |
||||
emit("select", undefined); |
||||
return; |
||||
} |
||||
|
||||
emit("update:selected", category); |
||||
emit("select", category); |
||||
}; |
||||
|
||||
function onDropdownShow() { |
||||
handleFetchCategories(); |
||||
setTimeout(() => { |
||||
setFocus("categoryDropdownSelectorInput"); |
||||
}, 200); |
||||
} |
||||
|
||||
// search |
||||
const keyword = ref(""); |
||||
|
||||
let fuse: Fuse<Category> | undefined = undefined; |
||||
|
||||
watch( |
||||
() => categories.value, |
||||
() => { |
||||
fuse = new Fuse(categories.value, { |
||||
keys: ["spec.displayName", "metadata.name"], |
||||
useExtendedSearch: true, |
||||
}); |
||||
} |
||||
); |
||||
|
||||
const searchResults = computed(() => { |
||||
if (!fuse || !keyword.value) { |
||||
return categories.value; |
||||
} |
||||
|
||||
return fuse?.search(keyword.value).map((item) => item.item); |
||||
}); |
||||
</script> |
||||
|
||||
<template> |
||||
<FloatingDropdown @show="onDropdownShow"> |
||||
<slot /> |
||||
<template #popper> |
||||
<div class="h-96 w-80"> |
||||
<div class="border-b border-b-gray-100 bg-white p-4"> |
||||
<FormKit |
||||
id="categoryDropdownSelectorInput" |
||||
v-model="keyword" |
||||
placeholder="输入关键词搜索" |
||||
type="text" |
||||
></FormKit> |
||||
</div> |
||||
<div> |
||||
<ul |
||||
class="box-border h-full w-full divide-y divide-gray-100" |
||||
role="list" |
||||
> |
||||
<li |
||||
v-for="(category, index) in searchResults" |
||||
:key="index" |
||||
v-close-popper |
||||
@click="handleSelect(category)" |
||||
> |
||||
<VEntity |
||||
:is-selected=" |
||||
selected?.metadata.name === category.metadata.name |
||||
" |
||||
> |
||||
<template #start> |
||||
<VEntityField |
||||
:title="category.spec.displayName" |
||||
:description="category.status?.permalink" |
||||
/> |
||||
</template> |
||||
<template #end> |
||||
<VEntityField |
||||
:description="`${category.status?.postCount || 0} 篇文章`" |
||||
/> |
||||
</template> |
||||
</VEntity> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</FloatingDropdown> |
||||
</template> |
@ -0,0 +1,114 @@
|
||||
<script lang="ts" setup> |
||||
import type { Tag } from "@halo-dev/api-client"; |
||||
import { VEntity, VEntityField } from "@halo-dev/components"; |
||||
import { setFocus } from "@/formkit/utils/focus"; |
||||
import { computed, ref, watch } from "vue"; |
||||
import Fuse from "fuse.js"; |
||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag"; |
||||
import PostTag from "@/modules/contents/posts/tags/components/PostTag.vue"; |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
selected?: Tag; |
||||
}>(), |
||||
{ |
||||
selected: undefined, |
||||
} |
||||
); |
||||
|
||||
const emit = defineEmits<{ |
||||
(event: "update:selected", tag?: Tag): void; |
||||
(event: "select", tag?: Tag): void; |
||||
}>(); |
||||
|
||||
const { tags, handleFetchTags } = usePostTag({ fetchOnMounted: false }); |
||||
|
||||
const handleSelect = (tag: Tag) => { |
||||
if (props.selected && tag.metadata.name === props.selected.metadata.name) { |
||||
emit("update:selected", undefined); |
||||
emit("select", undefined); |
||||
return; |
||||
} |
||||
|
||||
emit("update:selected", tag); |
||||
emit("select", tag); |
||||
}; |
||||
|
||||
function onDropdownShow() { |
||||
handleFetchTags(); |
||||
setTimeout(() => { |
||||
setFocus("tagDropdownSelectorInput"); |
||||
}, 200); |
||||
} |
||||
|
||||
// search |
||||
const keyword = ref(""); |
||||
|
||||
let fuse: Fuse<Tag> | undefined = undefined; |
||||
|
||||
watch( |
||||
() => tags.value, |
||||
() => { |
||||
fuse = new Fuse(tags.value, { |
||||
keys: ["spec.displayName", "metadata.name", "spec.email"], |
||||
useExtendedSearch: true, |
||||
}); |
||||
} |
||||
); |
||||
|
||||
const searchResults = computed(() => { |
||||
if (!fuse || !keyword.value) { |
||||
return tags.value; |
||||
} |
||||
|
||||
return fuse?.search(keyword.value).map((item) => item.item); |
||||
}); |
||||
</script> |
||||
|
||||
<template> |
||||
<FloatingDropdown @show="onDropdownShow"> |
||||
<slot /> |
||||
<template #popper> |
||||
<div class="h-96 w-80"> |
||||
<div class="border-b border-b-gray-100 bg-white p-4"> |
||||
<FormKit |
||||
id="tagDropdownSelectorInput" |
||||
v-model="keyword" |
||||
placeholder="输入关键词搜索" |
||||
type="text" |
||||
></FormKit> |
||||
</div> |
||||
<div> |
||||
<ul |
||||
class="box-border h-full w-full divide-y divide-gray-100" |
||||
role="list" |
||||
> |
||||
<li |
||||
v-for="(tag, index) in searchResults" |
||||
:key="index" |
||||
v-close-popper |
||||
@click="handleSelect(tag)" |
||||
> |
||||
<VEntity |
||||
:is-selected="selected?.metadata.name === tag.metadata.name" |
||||
> |
||||
<template #start> |
||||
<VEntityField :description="tag.status?.permalink"> |
||||
<template #title> |
||||
<PostTag :tag="tag" /> |
||||
</template> |
||||
</VEntityField> |
||||
</template> |
||||
<template #end> |
||||
<VEntityField |
||||
:description="`${tag.status?.postCount || 0} 篇文章`" |
||||
/> |
||||
</template> |
||||
</VEntity> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</FloatingDropdown> |
||||
</template> |
Loading…
Reference in new issue