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>[]>`
|
- 文章:`"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>[]>`
|
- 插件:`"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>[]>`
|
- 备份:`"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 { PluginInstallationTab } from "@/states/plugin-installation-tabs";
|
||||||
import type { EntityDropdownItem, EntityFieldItem } from "@/states/entity";
|
import type { EntityDropdownItem, EntityFieldItem } from "@/states/entity";
|
||||||
import type { ThemeListTab } from "@/states/theme-list-tabs";
|
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 {
|
export interface RouteRecordAppend {
|
||||||
parentName: RouteRecordName;
|
parentName: RouteRecordName;
|
||||||
|
@ -59,6 +59,10 @@ export interface ExtensionPoint {
|
||||||
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
|
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
|
||||||
|
|
||||||
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
||||||
|
|
||||||
|
"theme:list-item:operation:create"?: (
|
||||||
|
theme: Ref<Theme>
|
||||||
|
) => EntityDropdownItem<Theme>[] | Promise<EntityDropdownItem<Theme>[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginModule {
|
export interface PluginModule {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import {
|
import {
|
||||||
VTag,
|
VTag,
|
||||||
VStatusDot,
|
VStatusDot,
|
||||||
Dialog,
|
|
||||||
Toast,
|
Toast,
|
||||||
VDropdownItem,
|
VDropdownItem,
|
||||||
VDropdown,
|
VDropdown,
|
||||||
|
@ -18,6 +17,11 @@ import { useThemeLifeCycle } from "../composables/use-theme";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useQueryClient } from "@tanstack/vue-query";
|
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 { currentUserHasPermission } = usePermission();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -52,59 +56,6 @@ const {
|
||||||
handleResetSettingConfig,
|
handleResetSettingConfig,
|
||||||
} = useThemeLifeCycle(theme);
|
} = 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
|
// Creating theme
|
||||||
const creating = ref(false);
|
const creating = ref(false);
|
||||||
|
|
||||||
|
@ -131,6 +82,79 @@ const handleCreateTheme = async () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["not-installed-themes"] });
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -214,58 +238,49 @@ const handleCreateTheme = async () => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<VSpace v-if="installed">
|
<VSpace v-if="installed">
|
||||||
<VButton
|
<template v-for="(item, index) in dropdownItems" :key="index">
|
||||||
v-if="
|
<template v-if="!item.children?.length">
|
||||||
!isActivated &&
|
<component
|
||||||
currentUserHasPermission(['system:themes:manage'])
|
:is="item.component"
|
||||||
"
|
v-if="item.visible !== false"
|
||||||
size="sm"
|
v-permission="item.permissions"
|
||||||
@click="handleActiveTheme(true)"
|
v-bind="item.props"
|
||||||
>
|
@click="item.action?.(theme)"
|
||||||
{{ $t("core.common.buttons.activate") }}
|
>
|
||||||
</VButton>
|
{{ item.label }}
|
||||||
<VButton size="sm" @click="emit('select', theme)">
|
</component>
|
||||||
{{ $t("core.common.buttons.select") }}
|
</template>
|
||||||
</VButton>
|
<template v-else>
|
||||||
<VDropdown
|
<VDropdown
|
||||||
v-if="currentUserHasPermission(['system:themes:manage'])"
|
v-if="item.visible !== false"
|
||||||
>
|
v-permission="item.permissions"
|
||||||
<VButton size="sm">
|
>
|
||||||
<IconMore />
|
<component
|
||||||
</VButton>
|
:is="item.component"
|
||||||
<template #popper>
|
v-bind="item.props"
|
||||||
<VDropdownItem @click="emit('preview')">
|
@click="item.action?.(theme)"
|
||||||
{{ $t("core.common.buttons.preview") }}
|
>
|
||||||
</VDropdownItem>
|
{{ item.label }}
|
||||||
<VDropdownDivider />
|
</component>
|
||||||
<VDropdown placement="right" :triggers="['click']">
|
|
||||||
<VDropdownItem type="danger">
|
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<VDropdownItem
|
<template
|
||||||
type="danger"
|
v-for="(childItem, childIndex) in item.children"
|
||||||
@click="handleUninstall(theme)"
|
:key="`child-${childIndex}`"
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
<component
|
||||||
</VDropdownItem>
|
:is="childItem.component"
|
||||||
<VDropdownItem
|
v-if="childItem.visible !== false"
|
||||||
type="danger"
|
v-permission="childItem.permissions"
|
||||||
@click="handleUninstall(theme, true)"
|
v-bind="childItem.props"
|
||||||
>
|
@click="childItem.action?.(theme)"
|
||||||
{{
|
>
|
||||||
$t(
|
{{ childItem.label }}
|
||||||
"core.theme.operations.uninstall_and_delete_config.button"
|
</component>
|
||||||
)
|
</template>
|
||||||
}}
|
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VDropdown>
|
</VDropdown>
|
||||||
<VDropdownItem type="danger" @click="handleResetSettingConfig">
|
|
||||||
{{ $t("core.common.buttons.reset") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VDropdown>
|
</template>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
<VButton
|
<VButton
|
||||||
v-if="
|
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