mirror of https://github.com/halo-dev/halo
feat: add support for displaying and installing uninstalled themes (halo-dev/console#648)
#### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 支持显示和安装**未安装**的主题,以方便主题开发的时候,创建主题资源。适配 https://github.com/halo-dev/halo/pull/2586 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2554 #### Screenshots: <img width="1663" alt="image" src="https://user-images.githubusercontent.com/21301288/196148567-f43b1bf3-e745-4c1a-950d-65899c1ae73c.png"> #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. 需要 `pnpm install` 2. Halo 需要切换到 https://github.com/halo-dev/halo/pull/2586 PR 的分支。 3. 在本地的 `~/halo-dev/themes` 创建新的主题,需要包含 `themes.yaml`,或者将现有的主题直接下载到 `~/halo-dev/themes` 4. 检查后台主题管理的主题列表中是否显示了未安装的主题,以及测试是否可以安装成功。 #### Does this PR introduce a user-facing change? ```release-note 支持显示和安装未安装的主题,以方便主题开发的时候,创建主题资源。 ```pull/3445/head
parent
a0512e43bc
commit
97f0d99538
|
@ -11,10 +11,12 @@ import {
|
|||
VEntity,
|
||||
VEntityField,
|
||||
VStatusDot,
|
||||
VTabItem,
|
||||
VTabs,
|
||||
} from "@halo-dev/components";
|
||||
import LazyImage from "@/components/image/LazyImage.vue";
|
||||
import ThemeInstallModal from "./ThemeInstallModal.vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
|
@ -41,15 +43,22 @@ const emit = defineEmits<{
|
|||
(event: "select", theme: Theme | null): void;
|
||||
}>();
|
||||
|
||||
const themes = ref<Theme[]>([]);
|
||||
const activeTab = ref("installed");
|
||||
const themes = ref<Theme[]>([] as Theme[]);
|
||||
const loading = ref(false);
|
||||
const themeInstall = ref(false);
|
||||
const creating = ref(false);
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题";
|
||||
});
|
||||
|
||||
const handleFetchThemes = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } =
|
||||
await apiClient.extension.theme.listthemeHaloRunV1alpha1Theme();
|
||||
const { data } = await apiClient.theme.listThemes({
|
||||
uninstalled: activeTab.value !== "installed",
|
||||
});
|
||||
themes.value = data.items;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch themes", e);
|
||||
|
@ -58,6 +67,13 @@ const handleFetchThemes = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => activeTab.value,
|
||||
() => {
|
||||
handleFetchThemes();
|
||||
}
|
||||
);
|
||||
|
||||
const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
|
||||
Dialog.warning({
|
||||
title: `${
|
||||
|
@ -99,6 +115,27 @@ const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleCreateTheme = async (theme: Theme) => {
|
||||
try {
|
||||
creating.value = true;
|
||||
|
||||
const { data } =
|
||||
await apiClient.extension.theme.createthemeHaloRunV1alpha1Theme({
|
||||
theme,
|
||||
});
|
||||
|
||||
// create theme settings
|
||||
apiClient.theme.reloadThemeSetting({ name: data.metadata.name });
|
||||
|
||||
activeTab.value = "installed";
|
||||
} catch (error) {
|
||||
console.error("Failed to create theme", error);
|
||||
} finally {
|
||||
creating.value = false;
|
||||
handleFetchThemes();
|
||||
}
|
||||
};
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
|
@ -131,9 +168,15 @@ defineExpose({
|
|||
:visible="visible"
|
||||
:width="888"
|
||||
height="calc(100vh - 20px)"
|
||||
title="已安装的主题"
|
||||
:title="modalTitle"
|
||||
@update:visible="onVisibleChange"
|
||||
>
|
||||
<VTabs
|
||||
v-model:active-id="activeTab"
|
||||
type="outline"
|
||||
class="my-[12px] mx-[16px]"
|
||||
>
|
||||
<VTabItem id="installed" label="已安装" class="-mx-[16px]">
|
||||
<VEmpty
|
||||
v-if="!themes.length && !loading"
|
||||
message="当前没有已安装的主题,你可以尝试刷新或者安装新主题"
|
||||
|
@ -141,7 +184,9 @@ defineExpose({
|
|||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchThemes"> 刷新</VButton>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:themes:manage']"
|
||||
type="primary"
|
||||
|
@ -167,7 +212,9 @@ defineExpose({
|
|||
@click="handleSelectTheme(theme)"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="theme.metadata.name === selectedTheme?.metadata?.name"
|
||||
:is-selected="
|
||||
theme.metadata.name === selectedTheme?.metadata?.name
|
||||
"
|
||||
>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
|
@ -177,6 +224,7 @@ defineExpose({
|
|||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
|
@ -185,7 +233,9 @@ defineExpose({
|
|||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-gray-400">加载中...</span>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
|
@ -206,7 +256,9 @@ defineExpose({
|
|||
>
|
||||
<template #extra>
|
||||
<VTag
|
||||
v-if="theme.metadata.name === activatedTheme?.metadata?.name"
|
||||
v-if="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
>
|
||||
当前启用
|
||||
</VTag>
|
||||
|
@ -257,7 +309,9 @@ defineExpose({
|
|||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
:disabled="theme.metadata.name === activatedTheme?.metadata?.name"
|
||||
:disabled="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
block
|
||||
type="danger"
|
||||
@click="handleUninstall(theme, true)"
|
||||
|
@ -268,6 +322,107 @@ defineExpose({
|
|||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</VTabItem>
|
||||
<VTabItem id="uninstalled" label="未安装" class="-mx-[16px]">
|
||||
<VEmpty v-if="!themes.length && !loading" title="当前没有未安装的主题">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(theme, index) in themes" :key="index">
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div class="w-32">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
>
|
||||
<template #loading>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-red-400">加载异常</span>
|
||||
</div>
|
||||
</template>
|
||||
</LazyImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="theme.spec.displayName"
|
||||
:description="theme.spec.version"
|
||||
>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
class="text-sm text-gray-400 hover:text-blue-600"
|
||||
:href="theme.spec.author.website"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
{{ theme.spec.author.name }}
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-permission="['system:themes:manage']">
|
||||
<template #description>
|
||||
<VButton
|
||||
size="sm"
|
||||
:disabled="creating"
|
||||
@click="handleCreateTheme(theme)"
|
||||
>
|
||||
安装
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</VTabItem>
|
||||
</VTabs>
|
||||
|
||||
<template #footer>
|
||||
<VSpace>
|
||||
<VButton
|
||||
|
|
Loading…
Reference in New Issue