mirror of https://github.com/halo-dev/halo-admin
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
parent
13b4094468
commit
bd1472251f
|
@ -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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue