mirror of https://github.com/halo-dev/halo
feat: make theme list item operations extendable (#4523)
#### What type of PR is this? /area console /kind feature /milestone 2.9.x #### What this PR does / why we need it: 主题管理列表项的操作按钮支持被插件扩展。  #### Which issue(s) this PR fixes: Fixes #4522 #### Special notes for your reviewer: 测试已有的操作按钮功能正常即可。 #### Does this PR introduce a user-facing change? ```release-note Console 主题管理列表项的操作按钮支持被插件扩展。 ```pull/4530/head
parent
58eac2e30b
commit
8eaedd6ee8
|
@ -11,6 +11,7 @@
|
|||
- 文章:`"post:list-item:operation:create"?: (post: Ref<ListedPost>) => | EntityDropdownItem<ListedPost>[] | Promise<EntityDropdownItem<ListedPost>[]>`
|
||||
- 插件:`"plugin:list-item:operation:create"?: (plugin: Ref<Plugin>) => | EntityDropdownItem<Plugin>[] | Promise<EntityDropdownItem<Plugin>[]>`
|
||||
- 备份:`"backup:list-item:operation:create"?: (backup: Ref<Backup>) => | EntityDropdownItem<Backup>[] | Promise<EntityDropdownItem<Backup>[]>`
|
||||
- 主题:`"theme:list-item:operation:create"?: (theme: Ref<Theme>) => | EntityDropdownItem<Theme>[] | Promise<EntityDropdownItem<Theme>[]>`
|
||||
|
||||
示例:
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { BackupTab } from "@/states/backup";
|
|||
import type { PluginInstallationTab } from "@/states/plugin-installation-tabs";
|
||||
import type { EntityDropdownItem, EntityFieldItem } from "@/states/entity";
|
||||
import type { ThemeListTab } from "@/states/theme-list-tabs";
|
||||
import type { Backup, ListedPost, Plugin } from "@halo-dev/api-client";
|
||||
import type { Backup, ListedPost, Plugin, Theme } from "@halo-dev/api-client";
|
||||
|
||||
export interface RouteRecordAppend {
|
||||
parentName: RouteRecordName;
|
||||
|
@ -59,6 +59,10 @@ export interface ExtensionPoint {
|
|||
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
|
||||
|
||||
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
||||
|
||||
"theme:list-item:operation:create"?: (
|
||||
theme: Ref<Theme>
|
||||
) => EntityDropdownItem<Theme>[] | Promise<EntityDropdownItem<Theme>[]>;
|
||||
}
|
||||
|
||||
export interface PluginModule {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import {
|
||||
VTag,
|
||||
VStatusDot,
|
||||
Dialog,
|
||||
Toast,
|
||||
VDropdownItem,
|
||||
VDropdown,
|
||||
|
@ -18,6 +17,11 @@ import { useThemeLifeCycle } from "../composables/use-theme";
|
|||
import { usePermission } from "@/utils/permission";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
import { useEntityDropdownItemExtensionPoint } from "@/composables/use-entity-extension-points";
|
||||
import { markRaw } from "vue";
|
||||
import { defineComponent } from "vue";
|
||||
import UninstallOperationItem from "./operation/UninstallOperationItem.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -52,59 +56,6 @@ const {
|
|||
handleResetSettingConfig,
|
||||
} = useThemeLifeCycle(theme);
|
||||
|
||||
const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
|
||||
Dialog.warning({
|
||||
title: `${
|
||||
deleteExtensions
|
||||
? t("core.theme.operations.uninstall_and_delete_config.title")
|
||||
: t("core.theme.operations.uninstall.title")
|
||||
}`,
|
||||
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
||||
confirmText: t("core.common.buttons.confirm"),
|
||||
cancelText: t("core.common.buttons.cancel"),
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await apiClient.extension.theme.deletethemeHaloRunV1alpha1Theme({
|
||||
name: theme.metadata.name,
|
||||
});
|
||||
|
||||
// delete theme setting and configMap
|
||||
if (deleteExtensions) {
|
||||
const { settingName, configMapName } = theme.spec;
|
||||
|
||||
if (settingName) {
|
||||
await apiClient.extension.setting.deletev1alpha1Setting(
|
||||
{
|
||||
name: settingName,
|
||||
},
|
||||
{
|
||||
mute: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (configMapName) {
|
||||
await apiClient.extension.configMap.deletev1alpha1ConfigMap(
|
||||
{
|
||||
name: configMapName,
|
||||
},
|
||||
{
|
||||
mute: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.success(t("core.common.toast.uninstall_success"));
|
||||
} catch (e) {
|
||||
console.error("Failed to uninstall theme", e);
|
||||
} finally {
|
||||
queryClient.invalidateQueries({ queryKey: ["installed-themes"] });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Creating theme
|
||||
const creating = ref(false);
|
||||
|
||||
|
@ -131,6 +82,79 @@ const handleCreateTheme = async () => {
|
|||
queryClient.invalidateQueries({ queryKey: ["not-installed-themes"] });
|
||||
}
|
||||
};
|
||||
|
||||
const { dropdownItems } = useEntityDropdownItemExtensionPoint<Theme>(
|
||||
"theme:list-item:operation:create",
|
||||
theme,
|
||||
computed(() => [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VButton),
|
||||
props: {
|
||||
size: "sm",
|
||||
},
|
||||
action: () => handleActiveTheme(true),
|
||||
label: t("core.common.buttons.activate"),
|
||||
visible:
|
||||
!isActivated.value &&
|
||||
currentUserHasPermission(["system:themes:manage"]),
|
||||
},
|
||||
{
|
||||
priority: 20,
|
||||
component: markRaw(VButton),
|
||||
props: {
|
||||
size: "sm",
|
||||
},
|
||||
action: () => {
|
||||
emit("select", props.theme);
|
||||
},
|
||||
label: t("core.common.buttons.select"),
|
||||
},
|
||||
{
|
||||
priority: 30,
|
||||
component: markRaw(
|
||||
defineComponent({
|
||||
components: {
|
||||
VButton,
|
||||
IconMore,
|
||||
},
|
||||
template: `<VButton size="sm"><IconMore /></VButton>`,
|
||||
})
|
||||
),
|
||||
visible: currentUserHasPermission(["system:themes:manage"]),
|
||||
children: [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
action: () => {
|
||||
emit("preview");
|
||||
},
|
||||
label: t("core.common.buttons.preview"),
|
||||
},
|
||||
{
|
||||
priority: 20,
|
||||
component: markRaw(VDropdownDivider),
|
||||
},
|
||||
{
|
||||
priority: 30,
|
||||
component: markRaw(UninstallOperationItem),
|
||||
props: {
|
||||
theme: props.theme,
|
||||
},
|
||||
},
|
||||
{
|
||||
priority: 40,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {
|
||||
type: "danger",
|
||||
},
|
||||
action: () => handleResetSettingConfig(),
|
||||
label: t("core.common.buttons.reset"),
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -214,58 +238,49 @@ const handleCreateTheme = async () => {
|
|||
</div>
|
||||
<div>
|
||||
<VSpace v-if="installed">
|
||||
<VButton
|
||||
v-if="
|
||||
!isActivated &&
|
||||
currentUserHasPermission(['system:themes:manage'])
|
||||
"
|
||||
size="sm"
|
||||
@click="handleActiveTheme(true)"
|
||||
>
|
||||
{{ $t("core.common.buttons.activate") }}
|
||||
</VButton>
|
||||
<VButton size="sm" @click="emit('select', theme)">
|
||||
{{ $t("core.common.buttons.select") }}
|
||||
</VButton>
|
||||
<VDropdown
|
||||
v-if="currentUserHasPermission(['system:themes:manage'])"
|
||||
>
|
||||
<VButton size="sm">
|
||||
<IconMore />
|
||||
</VButton>
|
||||
<template #popper>
|
||||
<VDropdownItem @click="emit('preview')">
|
||||
{{ $t("core.common.buttons.preview") }}
|
||||
</VDropdownItem>
|
||||
<VDropdownDivider />
|
||||
<VDropdown placement="right" :triggers="['click']">
|
||||
<VDropdownItem type="danger">
|
||||
{{ $t("core.common.buttons.uninstall") }}
|
||||
</VDropdownItem>
|
||||
<template v-for="(item, index) in dropdownItems" :key="index">
|
||||
<template v-if="!item.children?.length">
|
||||
<component
|
||||
:is="item.component"
|
||||
v-if="item.visible !== false"
|
||||
v-permission="item.permissions"
|
||||
v-bind="item.props"
|
||||
@click="item.action?.(theme)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<VDropdown
|
||||
v-if="item.visible !== false"
|
||||
v-permission="item.permissions"
|
||||
>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-bind="item.props"
|
||||
@click="item.action?.(theme)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</component>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
type="danger"
|
||||
@click="handleUninstall(theme)"
|
||||
<template
|
||||
v-for="(childItem, childIndex) in item.children"
|
||||
:key="`child-${childIndex}`"
|
||||
>
|
||||
{{ $t("core.common.buttons.uninstall") }}
|
||||
</VDropdownItem>
|
||||
<VDropdownItem
|
||||
type="danger"
|
||||
@click="handleUninstall(theme, true)"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"core.theme.operations.uninstall_and_delete_config.button"
|
||||
)
|
||||
}}
|
||||
</VDropdownItem>
|
||||
<component
|
||||
:is="childItem.component"
|
||||
v-if="childItem.visible !== false"
|
||||
v-permission="childItem.permissions"
|
||||
v-bind="childItem.props"
|
||||
@click="childItem.action?.(theme)"
|
||||
>
|
||||
{{ childItem.label }}
|
||||
</component>
|
||||
</template>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<VDropdownItem type="danger" @click="handleResetSettingConfig">
|
||||
{{ $t("core.common.buttons.reset") }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
</template>
|
||||
</VSpace>
|
||||
<VButton
|
||||
v-if="
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<script lang="ts" setup>
|
||||
import { Dialog, Toast, VDropdown, VDropdownItem } from "@halo-dev/components";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
theme: Theme;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const handleUninstall = async (deleteExtensions?: boolean) => {
|
||||
Dialog.warning({
|
||||
title: `${
|
||||
deleteExtensions
|
||||
? t("core.theme.operations.uninstall_and_delete_config.title")
|
||||
: t("core.theme.operations.uninstall.title")
|
||||
}`,
|
||||
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
||||
confirmText: t("core.common.buttons.confirm"),
|
||||
cancelText: t("core.common.buttons.cancel"),
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await apiClient.extension.theme.deletethemeHaloRunV1alpha1Theme({
|
||||
name: props.theme.metadata.name,
|
||||
});
|
||||
|
||||
// delete theme setting and configMap
|
||||
if (deleteExtensions) {
|
||||
const { settingName, configMapName } = props.theme.spec;
|
||||
|
||||
if (settingName) {
|
||||
await apiClient.extension.setting.deletev1alpha1Setting(
|
||||
{
|
||||
name: settingName,
|
||||
},
|
||||
{
|
||||
mute: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (configMapName) {
|
||||
await apiClient.extension.configMap.deletev1alpha1ConfigMap(
|
||||
{
|
||||
name: configMapName,
|
||||
},
|
||||
{
|
||||
mute: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.success(t("core.common.toast.uninstall_success"));
|
||||
} catch (e) {
|
||||
console.error("Failed to uninstall theme", e);
|
||||
} finally {
|
||||
queryClient.invalidateQueries({ queryKey: ["installed-themes"] });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDropdown placement="right" :triggers="['click']">
|
||||
<VDropdownItem type="danger">
|
||||
{{ $t("core.common.buttons.uninstall") }}
|
||||
</VDropdownItem>
|
||||
<template #popper>
|
||||
<VDropdownItem type="danger" @click="handleUninstall()">
|
||||
{{ $t("core.common.buttons.uninstall") }}
|
||||
</VDropdownItem>
|
||||
<VDropdownItem type="danger" @click="handleUninstall(true)">
|
||||
{{ $t("core.theme.operations.uninstall_and_delete_config.button") }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
</template>
|
Loading…
Reference in New Issue