feat: add plugin filters support (#629)

#### What type of PR is this?

/kind feature
/kind improvement
/milestone 2.0

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

添加插件管理筛选的支持。适配 https://github.com/halo-dev/halo/pull/2489

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

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

#### Special notes for your reviewer:

/cc @halo-dev/sig-halo 
/cc @halo-dev/sig-halo-console 

测试方式:

1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/2489 PR 的分支。
2. Console 需要 `pnpm install`
3. 安装若干插件,测试筛选和排序。

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

```release-note
插件管理支持筛选
```
pull/632/head
Ryan Wang 2022-09-30 11:12:18 +08:00 committed by GitHub
parent 13b4094468
commit bd1472251f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 109 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.33", "@halo-dev/api-client": "^0.0.34",
"@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.33 '@halo-dev/api-client': ^0.0.34
'@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
@ -101,7 +101,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_k5hp3txgeyj6le63abiyc7wx3u '@formkit/vue': 1.0.0-beta.10_k5hp3txgeyj6le63abiyc7wx3u
'@halo-dev/admin-shared': link:packages/shared '@halo-dev/admin-shared': link:packages/shared
'@halo-dev/api-client': 0.0.33 '@halo-dev/api-client': 0.0.34
'@halo-dev/components': link:packages/components '@halo-dev/components': link:packages/components
'@halo-dev/richtext-editor': 0.0.0-alpha.7_vue@3.2.40 '@halo-dev/richtext-editor': 0.0.0-alpha.7_vue@3.2.40
'@tiptap/extension-character-count': 2.0.0-beta.31 '@tiptap/extension-character-count': 2.0.0-beta.31
@ -1886,8 +1886,8 @@ packages:
- windicss - windicss
dev: false dev: false
/@halo-dev/api-client/0.0.33: /@halo-dev/api-client/0.0.34:
resolution: {integrity: sha512-L0U11BO0rInJ49JjH/aynFFj28+vnBDXtDKDMNCnaZNo4ijweqdU8q4S6lUo6okGbznYlZ9fWDPDHvS7EKqHKA==} resolution: {integrity: sha512-WOEbyRjPSASH1tyQjQQU/RjXKtRPSD+L6erAWLUfy7BIsyCAMSBdkF3auBWaevUt08uyCBeI3ln+sP+VPyFM0A==}
dev: false dev: false
/@halo-dev/richtext-editor/0.0.0-alpha.7_vue@3.2.40: /@halo-dev/richtext-editor/0.0.0-alpha.7_vue@3.2.40:

View File

@ -2,6 +2,7 @@
import { import {
IconAddCircle, IconAddCircle,
IconArrowDown, IconArrowDown,
IconCloseCircle,
IconPlug, IconPlug,
VButton, VButton,
VCard, VCard,
@ -9,15 +10,23 @@ import {
VPageHeader, VPageHeader,
VPagination, VPagination,
VSpace, VSpace,
VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import PluginListItem from "./components/PluginListItem.vue"; import PluginListItem from "./components/PluginListItem.vue";
import PluginInstallModal from "./components/PluginInstallModal.vue"; import PluginInstallModal from "./components/PluginInstallModal.vue";
import { onMounted, ref, watch } from "vue"; import { onMounted, ref } from "vue";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import type { Plugin } from "@halo-dev/api-client"; import type { PluginList } from "@halo-dev/api-client";
const plugins = ref<Plugin[]>([] as Plugin[]); const plugins = ref<PluginList>({
page: 1,
size: 20,
total: 0,
items: [],
first: true,
last: false,
hasNext: false,
hasPrevious: false,
});
const loading = ref(false); const loading = ref(false);
const pluginInstall = ref(false); const pluginInstall = ref(false);
const keyword = ref(""); const keyword = ref("");
@ -26,34 +35,87 @@ const handleFetchPlugins = async () => {
try { try {
loading.value = true; loading.value = true;
const fieldSelector: Array<string> = []; const { data } = await apiClient.plugin.listPlugins({
page: plugins.value.page,
size: plugins.value.size,
keyword: keyword.value,
enabled: selectedEnabledItem.value?.value,
sort: [selectedSortItem.value?.value].filter(
(item) => !!item
) as string[],
});
if (keyword.value) { plugins.value = data;
fieldSelector.push(`name=${keyword.value}`);
}
const { data } =
await apiClient.extension.plugin.listpluginHaloRunV1alpha1Plugin({
page: 0,
size: 0,
fieldSelector,
});
plugins.value = data.items;
} catch (e) { } catch (e) {
console.error("Fail to fetch plugins", e); console.error("Failed to fetch plugins", e);
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
watch( const handlePaginationChange = ({
() => keyword.value, page,
() => { size,
handleFetchPlugins(); }: {
} page: number;
); size: number;
}) => {
plugins.value.page = page;
plugins.value.size = size;
handleFetchPlugins();
};
onMounted(handleFetchPlugins); onMounted(handleFetchPlugins);
// Filters
interface EnabledItem {
label: string;
value?: boolean;
}
interface SortItem {
label: string;
value: string;
}
const EnabledItems: EnabledItem[] = [
{
label: "全部",
value: undefined,
},
{
label: "已启用",
value: true,
},
{
label: "未启用",
value: false,
},
];
const SortItems: SortItem[] = [
{
label: "较近安装",
value: "creationTimestamp,desc",
},
{
label: "较早安装",
value: "creationTimestamp,asc",
},
];
const selectedEnabledItem = ref<EnabledItem>();
const selectedSortItem = ref<SortItem>();
function handleEnabledItemChange(enabledItem: EnabledItem) {
selectedEnabledItem.value = enabledItem;
handleFetchPlugins();
}
function handleSortItemChange(sortItem?: SortItem) {
selectedSortItem.value = sortItem;
handleFetchPlugins();
}
</script> </script>
<template> <template>
<PluginInstallModal <PluginInstallModal
@ -87,12 +149,39 @@ onMounted(handleFetchPlugins);
<div <div
class="relative flex flex-col items-start sm:flex-row sm:items-center" class="relative flex flex-col items-start sm:flex-row sm:items-center"
> >
<div class="flex w-full flex-1 sm:w-auto"> <div class="flex w-full flex-1 items-center gap-2 sm:w-auto">
<FormKit <FormKit
v-model="keyword" v-model="keyword"
placeholder="输入关键词搜索" placeholder="输入关键词搜索"
type="text" type="text"
@keyup.enter="
handlePaginationChange({ page: 1, size: plugins.size })
"
></FormKit> ></FormKit>
<div
v-if="selectedEnabledItem?.value !== undefined"
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">
启用状态{{ selectedEnabledItem.label }}
</span>
<IconCloseCircle
class="h-4 w-4 text-gray-600"
@click="handleEnabledItemChange(EnabledItems[0])"
/>
</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 class="mt-4 flex sm:mt-0"> <div class="mt-4 flex sm:mt-0">
<VSpace spacing="lg"> <VSpace spacing="lg">
@ -109,84 +198,22 @@ onMounted(handleFetchPlugins);
<div class="w-52 p-4"> <div class="w-52 p-4">
<ul class="space-y-1"> <ul class="space-y-1">
<li <li
v-for="(enabledItem, index) in EnabledItems"
:key="index"
v-close-popper 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"
:class="{
'bg-gray-100':
selectedEnabledItem?.value === enabledItem.value,
}"
@click="handleEnabledItemChange(enabledItem)"
> >
<span class="truncate">已启用</span> <span class="truncate">{{ enabledItem.label }}</span>
</li>
<li
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"
>
<span class="truncate">未启用</span>
</li> </li>
</ul> </ul>
</div> </div>
</template> </template>
</FloatingDropdown> </FloatingDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
<span class="mr-0.5">类别</span>
<span>
<IconArrowDown />
</span>
</div>
<FloatingDropdown>
<div
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
>
<span class="mr-0.5">提供方</span>
<span>
<IconArrowDown />
</span>
</div>
<template #popper>
<div class="h-96 w-80 p-4">
<div class="bg-white">
<!--TODO: Auto Focus-->
<FormKit
placeholder="输入关键词搜索"
type="text"
></FormKit>
</div>
<div class="mt-2">
<ul class="divide-y divide-gray-200" role="list">
<li class="cursor-pointer py-4 hover:bg-gray-50">
<div class="flex items-center space-x-4">
<div class="flex items-center">
<input
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox"
/>
</div>
<div class="flex-shrink-0">
<img
alt="halo-dev"
class="h-10 w-10 rounded"
src="https://halo.run/logo"
/>
</div>
<div class="min-w-0 flex-1">
<p
class="truncate text-sm font-medium text-gray-900"
>
Halo
</p>
<p class="truncate text-sm text-gray-500">
https://halo.run
</p>
</div>
<div>
<VTag>2 </VTag>
</div>
</div>
</li>
</ul>
</div>
</div>
</template>
</FloatingDropdown>
<FloatingDropdown> <FloatingDropdown>
<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"
@ -200,16 +227,13 @@ onMounted(handleFetchPlugins);
<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 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
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"
>
<span class="truncate">较晚安装</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -222,7 +246,7 @@ onMounted(handleFetchPlugins);
</template> </template>
<VEmpty <VEmpty
v-if="!plugins.length && !loading" v-if="!plugins.total && !loading"
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件" message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
title="当前没有已安装的插件" title="当前没有已安装的插件"
> >
@ -248,14 +272,20 @@ onMounted(handleFetchPlugins);
class="box-border h-full w-full divide-y divide-gray-100" class="box-border h-full w-full divide-y divide-gray-100"
role="list" role="list"
> >
<li v-for="(plugin, index) in plugins" :key="index"> <li v-for="(plugin, index) in plugins.items" :key="index">
<PluginListItem :plugin="plugin" /> <PluginListItem :plugin="plugin" />
</li> </li>
</ul> </ul>
<template #footer> <template #footer>
<div class="bg-white sm:flex sm:items-center sm:justify-end"> <div class="bg-white sm:flex sm:items-center sm:justify-end">
<VPagination :page="1" :size="10" :total="20" /> <VPagination
:page="plugins.page"
:size="plugins.size"
:total="plugins.total"
:size-options="[20, 30, 50, 100]"
@change="handlePaginationChange"
/>
</div> </div>
</template> </template>
</VCard> </VCard>