mirror of https://github.com/halo-dev/halo
feat: add support for theme management (halo-dev/console#592)
Signed-off-by: Ryan Wang <i@ryanc.cc> <!-- Thanks for sending a pull request! Here are some tips for you: 1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。 1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>. 2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。 2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. 3. 请确保你已经添加并运行了适当的测试。 3. Ensure you have added or ran the appropriate tests for your PR. --> #### What type of PR is this? /kind feature /milestone 2.0 <!-- 添加其中一个类别: Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature /kind optimization 适当添加其中一个或多个类别(可选): Optionally add one or more of the following kinds if applicable: /kind api-change /kind deprecation /kind failing-test /kind flake /kind regression --> #### What this PR does / why we need it: 添加基础的主题管理支持,适配 https://github.com/halo-dev/halo/pull/2280 /hold until https://github.com/halo-dev/halo/pull/2280 merge #### Which issue(s) this PR fixes: <!-- PR 合并时自动关闭 issue。 Automatically closes linked issue when PR is merged. 用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)` Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> None #### Screenshots: |<img width="1564" alt="image" src="https://user-images.githubusercontent.com/21301288/182314023-546d5fe1-03bb-4c7e-af36-6730ab051931.png">|<img width="1564" alt="image" src="https://user-images.githubusercontent.com/21301288/182314339-6c13b60a-cddc-449d-a06f-faaa53f76335.png">| | ---- | ---- | |<img width="1564" alt="image" src="https://user-images.githubusercontent.com/21301288/182314487-4c551208-938c-4582-bce6-7f589f278e8f.png">| | <!-- 如果此 PR 有 UI 的改动,最好截图说明这个 PR 的改动。 If there are UI changes to this PR, it is best to take a screenshot to illustrate the changes to this PR. eg. Before: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> #### Special notes for your reviewer: None #### Does this PR introduce a user-facing change? <!-- 如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。 否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change), Release Note 需要以 `action required` 开头。 If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". --> ```release-note None ```pull/3445/head
parent
75cb96a24e
commit
a580d89e39
|
@ -34,7 +34,7 @@
|
|||
"@formkit/vue": "1.0.0-beta.10",
|
||||
"@halo-dev/admin-api": "^1.1.0",
|
||||
"@halo-dev/admin-shared": "workspace:*",
|
||||
"@halo-dev/api-client": "^0.0.6",
|
||||
"@halo-dev/api-client": "^0.0.7",
|
||||
"@halo-dev/components": "workspace:*",
|
||||
"@vueuse/components": "^8.9.4",
|
||||
"@vueuse/core": "^8.9.4",
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"homepage": "https://github.com/halo-dev/halo-admin/tree/next/shared/components#readme",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@halo-dev/api-client": "^0.0.6",
|
||||
"@halo-dev/api-client": "^0.0.7",
|
||||
"@halo-dev/components": "workspace:*",
|
||||
"axios": "^0.27.2"
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
V1alpha1RoleBindingApi,
|
||||
V1alpha1SettingApi,
|
||||
V1alpha1UserApi,
|
||||
ThemeHaloRunV1alpha1ThemeApi,
|
||||
} from "@halo-dev/api-client";
|
||||
import type { AxiosInstance } from "axios";
|
||||
import axios from "axios";
|
||||
|
@ -58,6 +59,7 @@ function setupApiClient(axios: AxiosInstance) {
|
|||
),
|
||||
plugin: new PluginHaloRunV1alpha1PluginApi(undefined, apiUrl, axios),
|
||||
user: new V1alpha1UserApi(undefined, apiUrl, axios),
|
||||
theme: new ThemeHaloRunV1alpha1ThemeApi(undefined, apiUrl, axios),
|
||||
|
||||
// TODO optional
|
||||
// link: new CoreHaloRunV1alpha1LinkApi(undefined, apiUrl, axios),
|
||||
|
|
|
@ -14,7 +14,7 @@ importers:
|
|||
'@formkit/vue': 1.0.0-beta.10
|
||||
'@halo-dev/admin-api': ^1.1.0
|
||||
'@halo-dev/admin-shared': workspace:*
|
||||
'@halo-dev/api-client': ^0.0.6
|
||||
'@halo-dev/api-client': ^0.0.7
|
||||
'@halo-dev/components': workspace:*
|
||||
'@rushstack/eslint-patch': ^1.1.4
|
||||
'@tailwindcss/aspect-ratio': ^0.4.0
|
||||
|
@ -84,7 +84,7 @@ importers:
|
|||
'@formkit/vue': 1.0.0-beta.10_h3koegrx2cxvlj7ceqfsnru344
|
||||
'@halo-dev/admin-api': 1.1.0
|
||||
'@halo-dev/admin-shared': link:packages/shared
|
||||
'@halo-dev/api-client': 0.0.6
|
||||
'@halo-dev/api-client': 0.0.7
|
||||
'@halo-dev/components': link:packages/components
|
||||
'@vueuse/components': 8.9.4_vue@3.2.37
|
||||
'@vueuse/core': 8.9.4_vue@3.2.37
|
||||
|
@ -174,12 +174,12 @@ importers:
|
|||
|
||||
packages/shared:
|
||||
specifiers:
|
||||
'@halo-dev/api-client': ^0.0.6
|
||||
'@halo-dev/api-client': ^0.0.7
|
||||
'@halo-dev/components': workspace:*
|
||||
axios: ^0.27.2
|
||||
vite-plugin-dts: ^1.4.0
|
||||
dependencies:
|
||||
'@halo-dev/api-client': 0.0.6
|
||||
'@halo-dev/api-client': 0.0.7
|
||||
'@halo-dev/components': link:../components
|
||||
axios: 0.27.2
|
||||
devDependencies:
|
||||
|
@ -1873,8 +1873,8 @@ packages:
|
|||
- debug
|
||||
dev: false
|
||||
|
||||
/@halo-dev/api-client/0.0.6:
|
||||
resolution: {integrity: sha512-JDWGlTq+pHVrZsmqDCXAowZQFcNL3M6+guL37yrKbhylUgIutYpCMU/d2Qogc+c43FzFBq84igP84T5XtuknjQ==}
|
||||
/@halo-dev/api-client/0.0.7:
|
||||
resolution: {integrity: sha512-UP36IYuSuDh/rRX+fwZAiPE+h/bhBLgh4XVfqVFaynHc3fLQhaUchqzqvep0HYAPHW4E7EDfQaNiFLXHUrH1bA==}
|
||||
dev: false
|
||||
|
||||
/@halo-dev/logger/1.1.0:
|
||||
|
|
|
@ -1,184 +1,39 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconExchange,
|
||||
IconEye,
|
||||
IconGitHub,
|
||||
IconPalette,
|
||||
VAlert,
|
||||
VButton,
|
||||
VCard,
|
||||
VModal,
|
||||
VPageHeader,
|
||||
VSpace,
|
||||
VTabbar,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { onMounted, ref } from "vue";
|
||||
import ThemeListModal from "./components/ThemeListModal.vue";
|
||||
import { ref } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
import type { Metadata } from "@halo-dev/api-client";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
import { useThemeLifeCycle } from "./composables/use-theme";
|
||||
|
||||
interface ThemeAuthor {
|
||||
name: string;
|
||||
website: string;
|
||||
}
|
||||
|
||||
interface ThemeSpec {
|
||||
displayName: string;
|
||||
author: ThemeAuthor;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
website?: string;
|
||||
repo?: string;
|
||||
version: string;
|
||||
require: string;
|
||||
}
|
||||
|
||||
interface Theme {
|
||||
metadata: Metadata;
|
||||
spec: ThemeSpec;
|
||||
kind: string;
|
||||
apiVersion: string;
|
||||
}
|
||||
|
||||
const themes = ref<Theme[]>([]);
|
||||
const currentTheme = ref<Theme>({} as Theme);
|
||||
const selectedTheme = ref<Theme>({} as Theme);
|
||||
const themesModal = ref(false);
|
||||
const themeActiveId = ref("detail");
|
||||
const tabActiveId = ref("detail");
|
||||
const themeListRef = ref();
|
||||
|
||||
const handleChangeTheme = (theme: Theme) => {
|
||||
currentTheme.value = theme;
|
||||
themesModal.value = false;
|
||||
};
|
||||
|
||||
const handleFetchThemes = async () => {
|
||||
themes.value = await new Promise((resolve) => {
|
||||
resolve([
|
||||
{
|
||||
apiVersion: "theme.halo.run/v1alpha1",
|
||||
kind: "Theme",
|
||||
metadata: {
|
||||
name: "default",
|
||||
},
|
||||
spec: {
|
||||
displayName: "Default",
|
||||
author: {
|
||||
name: "halo-dev",
|
||||
website: "https://halo.run",
|
||||
},
|
||||
description: "Halo 2.0 的默认主题",
|
||||
logo: "https://halo.run/logo",
|
||||
website: "https://github.com/halo-sigs/theme-default.git",
|
||||
repo: "https://github.com/halo-sigs/theme-default.git",
|
||||
version: "1.0.0",
|
||||
require: "2.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: "theme.halo.run/v1alpha1",
|
||||
kind: "Theme",
|
||||
metadata: {
|
||||
name: "gtvg",
|
||||
},
|
||||
spec: {
|
||||
displayName: "GTVG",
|
||||
author: {
|
||||
name: "guqing",
|
||||
website: "https://guqing.xyz",
|
||||
},
|
||||
description: "测试主题",
|
||||
logo: "https://guqing.xyz/logo.png",
|
||||
website: "https://github.com/guqing/halo-theme-test.git",
|
||||
repo: "https://github.com/guqing/halo-theme-test.git",
|
||||
version: "1.0.0",
|
||||
require: "2.0.0",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
currentTheme.value = themes.value[0];
|
||||
};
|
||||
|
||||
onMounted(handleFetchThemes);
|
||||
const { isActivated, activatedTheme, handleActiveTheme } =
|
||||
useThemeLifeCycle(selectedTheme);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VModal
|
||||
<ThemeListModal
|
||||
ref="themeListRef"
|
||||
v-model:activated-theme="activatedTheme"
|
||||
v-model:selected-theme="selectedTheme"
|
||||
v-model:visible="themesModal"
|
||||
:body-class="['!p-0']"
|
||||
:width="888"
|
||||
title="已安装的主题"
|
||||
>
|
||||
<ul class="flex flex-col divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(theme, index) in themes"
|
||||
:key="index"
|
||||
:class="{
|
||||
'bg-gray-50': theme.metadata.name === currentTheme.metadata?.name,
|
||||
}"
|
||||
class="relative cursor-pointer py-4 transition-all hover:bg-gray-100"
|
||||
@click="handleChangeTheme(theme)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-show="theme.metadata.name === currentTheme.metadata?.name"
|
||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
||||
></div>
|
||||
<div class="w-40 px-4">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<img
|
||||
:src="theme.spec.logo"
|
||||
alt=""
|
||||
class="pointer-events-none object-cover group-hover:opacity-75"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<VSpace align="start" direction="column" spacing="xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg font-medium text-gray-900">
|
||||
{{ theme.spec.displayName }}
|
||||
</span>
|
||||
<VTag>当前启用</VTag>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-400">
|
||||
{{ theme.spec.version }}
|
||||
</span>
|
||||
</div>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="px-4">
|
||||
<VSpace spacing="lg">
|
||||
<div>
|
||||
<span class="text-sm text-gray-400 hover:text-blue-600">
|
||||
{{ theme.spec.author.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="theme.spec.website">
|
||||
<a
|
||||
:href="theme.spec.website"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<IconArrowRight class="text-gray-900" />
|
||||
</div>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<template #footer>
|
||||
<VButton @click="themesModal = false">关闭</VButton>
|
||||
</template>
|
||||
</VModal>
|
||||
<VPageHeader :title="currentTheme.spec?.displayName">
|
||||
<VPageHeader :title="selectedTheme.spec?.displayName">
|
||||
<template #icon>
|
||||
<IconPalette class="mr-2 self-center" />
|
||||
</template>
|
||||
|
@ -190,7 +45,14 @@ onMounted(handleFetchThemes);
|
|||
</template>
|
||||
切换主题
|
||||
</VButton>
|
||||
<VButton size="sm" type="primary"> 启用</VButton>
|
||||
<VButton
|
||||
v-if="!isActivated"
|
||||
size="sm"
|
||||
type="primary"
|
||||
@click="handleActiveTheme"
|
||||
>
|
||||
启用
|
||||
</VButton>
|
||||
<VButton :route="{ name: 'ThemeVisual' }" type="secondary">
|
||||
<template #icon>
|
||||
<IconEye class="h-full w-full" />
|
||||
|
@ -204,7 +66,7 @@ onMounted(handleFetchThemes);
|
|||
<VCard :body-class="['!p-0']">
|
||||
<template #header>
|
||||
<VTabbar
|
||||
v-model:active-id="themeActiveId"
|
||||
v-model:active-id="tabActiveId"
|
||||
:items="[
|
||||
{ id: 'detail', label: '详情' },
|
||||
{ id: 'settings', label: '基础设置' },
|
||||
|
@ -214,29 +76,31 @@ onMounted(handleFetchThemes);
|
|||
></VTabbar>
|
||||
</template>
|
||||
|
||||
<div v-if="themeActiveId === 'detail'">
|
||||
<div v-if="tabActiveId === 'detail'">
|
||||
<div class="px-4 py-4 sm:px-6">
|
||||
<div class="flex flex-row gap-3">
|
||||
<div v-if="currentTheme.spec?.logo">
|
||||
<div v-if="selectedTheme.spec?.logo">
|
||||
<div
|
||||
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||
>
|
||||
<img
|
||||
:alt="currentTheme.spec?.displayName"
|
||||
:src="currentTheme.spec?.logo"
|
||||
:alt="selectedTheme.spec?.displayName"
|
||||
:src="selectedTheme.spec?.logo"
|
||||
class="h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
||||
{{ currentTheme.spec?.displayName }}
|
||||
{{ selectedTheme.spec?.displayName }}
|
||||
</h3>
|
||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ currentTheme.spec?.version }}
|
||||
{{ selectedTheme.spec?.version }}
|
||||
</span>
|
||||
<VTag> 当前启用</VTag>
|
||||
<VTag>
|
||||
{{ isActivated ? "当前启用" : "未启用" }}
|
||||
</VTag>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -248,7 +112,7 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ currentTheme.metadata?.name }}
|
||||
{{ selectedTheme.metadata?.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
|
@ -256,7 +120,7 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">作者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ currentTheme.spec?.author?.name }}
|
||||
{{ selectedTheme.spec?.author?.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
|
@ -264,8 +128,8 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">网站</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="currentTheme.spec?.website" target="_blank">
|
||||
{{ currentTheme.spec?.website }}
|
||||
<a :href="selectedTheme.spec?.website" target="_blank">
|
||||
{{ selectedTheme.spec?.website }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -274,8 +138,8 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">源码仓库</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="currentTheme.spec?.website" target="_blank">
|
||||
{{ currentTheme.spec?.website }}
|
||||
<a :href="selectedTheme.spec?.repo" target="_blank">
|
||||
{{ selectedTheme.spec?.repo }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -284,7 +148,7 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">当前版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ currentTheme.spec?.version }}
|
||||
{{ selectedTheme.spec?.version }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
|
@ -292,7 +156,7 @@ onMounted(handleFetchThemes);
|
|||
>
|
||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ currentTheme.spec?.require }}
|
||||
{{ selectedTheme.spec?.require }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
|
@ -354,7 +218,7 @@ onMounted(handleFetchThemes);
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="themeActiveId === 'settings'" class="p-4 sm:px-6">
|
||||
<div v-if="tabActiveId === 'settings'" class="p-4 sm:px-6">
|
||||
<div class="w-1/3">
|
||||
<FormKit id="theme-setting-form" :actions="false" type="form">
|
||||
<FormKit label="侧边栏宽度" type="text"></FormKit>
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconGitHub,
|
||||
VButton,
|
||||
VModal,
|
||||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import type { PropType } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
|
||||
defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectedTheme: {
|
||||
type: Object as PropType<Theme | null>,
|
||||
default: null,
|
||||
},
|
||||
activatedTheme: {
|
||||
type: Object as PropType<Theme | null>,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:visible", "close", "update:selectedTheme"]);
|
||||
|
||||
const themes = ref<Theme[]>([]);
|
||||
|
||||
const handleFetchThemes = async () => {
|
||||
try {
|
||||
const { data } =
|
||||
await apiClient.extension.theme.listthemeHaloRunV1alpha1Theme();
|
||||
themes.value = data.items;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch themes", e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectTheme = (theme: Theme) => {
|
||||
emit("update:selectedTheme", theme);
|
||||
handleVisibleChange(false);
|
||||
};
|
||||
|
||||
onMounted(handleFetchThemes);
|
||||
|
||||
defineExpose({
|
||||
handleFetchThemes,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:body-class="['!p-0']"
|
||||
:visible="visible"
|
||||
:width="888"
|
||||
title="已安装的主题"
|
||||
@update:visible="handleVisibleChange"
|
||||
>
|
||||
<ul class="flex flex-col divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(theme, index) in themes"
|
||||
:key="index"
|
||||
:class="{
|
||||
'bg-gray-50': theme.metadata.name === selectedTheme?.metadata?.name,
|
||||
}"
|
||||
class="relative cursor-pointer py-4 transition-all hover:bg-gray-100"
|
||||
@click="handleSelectTheme(theme)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-show="theme.metadata.name === selectedTheme?.metadata?.name"
|
||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
||||
></div>
|
||||
<div class="w-40 px-4">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<img
|
||||
:src="theme.spec.logo"
|
||||
alt=""
|
||||
class="pointer-events-none object-cover group-hover:opacity-75"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<VSpace align="start" direction="column" spacing="xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg font-medium text-gray-900">
|
||||
{{ theme.spec.displayName }}
|
||||
</span>
|
||||
<VTag
|
||||
v-if="theme.metadata.name === activatedTheme?.metadata?.name"
|
||||
>
|
||||
当前启用
|
||||
</VTag>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-400">
|
||||
{{ theme.spec.version }}
|
||||
</span>
|
||||
</div>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="px-4">
|
||||
<VSpace spacing="lg">
|
||||
<div>
|
||||
<span class="text-sm text-gray-400 hover:text-blue-600">
|
||||
{{ theme.spec.author.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="theme.spec.repo">
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<IconArrowRight class="text-gray-900" />
|
||||
</div>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<template #footer>
|
||||
<VButton @click="handleVisibleChange(false)">关闭</VButton>
|
||||
</template>
|
||||
</VModal>
|
||||
</template>
|
|
@ -0,0 +1,83 @@
|
|||
import type { ComputedRef, Ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
import { useDialog } from "@halo-dev/components";
|
||||
|
||||
interface useThemeLifeCycleReturn {
|
||||
activatedTheme: Ref<Theme>;
|
||||
isActivated: ComputedRef<boolean>;
|
||||
handleActiveTheme: () => void;
|
||||
}
|
||||
|
||||
export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
|
||||
const activatedTheme = ref<Theme>({} as Theme);
|
||||
|
||||
const isActivated = computed(() => {
|
||||
return activatedTheme.value?.metadata?.name === theme.value?.metadata?.name;
|
||||
});
|
||||
|
||||
const dialog = useDialog();
|
||||
|
||||
const handleFetchActivatedTheme = async () => {
|
||||
try {
|
||||
const { data } = await apiClient.extension.configMap.getv1alpha1ConfigMap(
|
||||
"system"
|
||||
);
|
||||
|
||||
if (!data.data?.theme) {
|
||||
// Todo: show error
|
||||
return;
|
||||
}
|
||||
const themeConfig = JSON.parse(data.data.theme);
|
||||
|
||||
const { data: themeData } =
|
||||
await apiClient.extension.theme.getthemeHaloRunV1alpha1Theme(
|
||||
themeConfig.active
|
||||
);
|
||||
|
||||
theme.value = themeData;
|
||||
activatedTheme.value = themeData;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch active theme", e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActiveTheme = async () => {
|
||||
dialog.info({
|
||||
title: "是否确认启用当前主题",
|
||||
description: theme.value.spec.displayName,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const { data: systemConfigMap } =
|
||||
await apiClient.extension.configMap.getv1alpha1ConfigMap("system");
|
||||
|
||||
if (systemConfigMap.data) {
|
||||
const themeConfigToUpdate = JSON.parse(
|
||||
systemConfigMap.data?.theme || "{}"
|
||||
);
|
||||
themeConfigToUpdate.active = theme.value?.metadata?.name;
|
||||
systemConfigMap.data["theme"] = JSON.stringify(themeConfigToUpdate);
|
||||
|
||||
await apiClient.extension.configMap.updatev1alpha1ConfigMap(
|
||||
"system",
|
||||
systemConfigMap
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to active theme", e);
|
||||
} finally {
|
||||
await handleFetchActivatedTheme();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(handleFetchActivatedTheme);
|
||||
|
||||
return {
|
||||
activatedTheme,
|
||||
isActivated,
|
||||
handleActiveTheme,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue