mirror of https://github.com/halo-dev/halo-admin
feat: add support for theme management (#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:  After:  --> #### 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/593/head
parent
edfa8f6a90
commit
86e86d138b
|
@ -34,7 +34,7 @@
|
||||||
"@formkit/vue": "1.0.0-beta.10",
|
"@formkit/vue": "1.0.0-beta.10",
|
||||||
"@halo-dev/admin-api": "^1.1.0",
|
"@halo-dev/admin-api": "^1.1.0",
|
||||||
"@halo-dev/admin-shared": "workspace:*",
|
"@halo-dev/admin-shared": "workspace:*",
|
||||||
"@halo-dev/api-client": "^0.0.6",
|
"@halo-dev/api-client": "^0.0.7",
|
||||||
"@halo-dev/components": "workspace:*",
|
"@halo-dev/components": "workspace:*",
|
||||||
"@vueuse/components": "^8.9.4",
|
"@vueuse/components": "^8.9.4",
|
||||||
"@vueuse/core": "^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",
|
"homepage": "https://github.com/halo-dev/halo-admin/tree/next/shared/components#readme",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@halo-dev/api-client": "^0.0.6",
|
"@halo-dev/api-client": "^0.0.7",
|
||||||
"@halo-dev/components": "workspace:*",
|
"@halo-dev/components": "workspace:*",
|
||||||
"axios": "^0.27.2"
|
"axios": "^0.27.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
V1alpha1RoleBindingApi,
|
V1alpha1RoleBindingApi,
|
||||||
V1alpha1SettingApi,
|
V1alpha1SettingApi,
|
||||||
V1alpha1UserApi,
|
V1alpha1UserApi,
|
||||||
|
ThemeHaloRunV1alpha1ThemeApi,
|
||||||
} from "@halo-dev/api-client";
|
} from "@halo-dev/api-client";
|
||||||
import type { AxiosInstance } from "axios";
|
import type { AxiosInstance } from "axios";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
@ -58,6 +59,7 @@ function setupApiClient(axios: AxiosInstance) {
|
||||||
),
|
),
|
||||||
plugin: new PluginHaloRunV1alpha1PluginApi(undefined, apiUrl, axios),
|
plugin: new PluginHaloRunV1alpha1PluginApi(undefined, apiUrl, axios),
|
||||||
user: new V1alpha1UserApi(undefined, apiUrl, axios),
|
user: new V1alpha1UserApi(undefined, apiUrl, axios),
|
||||||
|
theme: new ThemeHaloRunV1alpha1ThemeApi(undefined, apiUrl, axios),
|
||||||
|
|
||||||
// TODO optional
|
// TODO optional
|
||||||
// link: new CoreHaloRunV1alpha1LinkApi(undefined, apiUrl, axios),
|
// link: new CoreHaloRunV1alpha1LinkApi(undefined, apiUrl, axios),
|
||||||
|
|
|
@ -14,7 +14,7 @@ importers:
|
||||||
'@formkit/vue': 1.0.0-beta.10
|
'@formkit/vue': 1.0.0-beta.10
|
||||||
'@halo-dev/admin-api': ^1.1.0
|
'@halo-dev/admin-api': ^1.1.0
|
||||||
'@halo-dev/admin-shared': workspace:*
|
'@halo-dev/admin-shared': workspace:*
|
||||||
'@halo-dev/api-client': ^0.0.6
|
'@halo-dev/api-client': ^0.0.7
|
||||||
'@halo-dev/components': workspace:*
|
'@halo-dev/components': workspace:*
|
||||||
'@rushstack/eslint-patch': ^1.1.4
|
'@rushstack/eslint-patch': ^1.1.4
|
||||||
'@tailwindcss/aspect-ratio': ^0.4.0
|
'@tailwindcss/aspect-ratio': ^0.4.0
|
||||||
|
@ -84,7 +84,7 @@ importers:
|
||||||
'@formkit/vue': 1.0.0-beta.10_h3koegrx2cxvlj7ceqfsnru344
|
'@formkit/vue': 1.0.0-beta.10_h3koegrx2cxvlj7ceqfsnru344
|
||||||
'@halo-dev/admin-api': 1.1.0
|
'@halo-dev/admin-api': 1.1.0
|
||||||
'@halo-dev/admin-shared': link:packages/shared
|
'@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
|
'@halo-dev/components': link:packages/components
|
||||||
'@vueuse/components': 8.9.4_vue@3.2.37
|
'@vueuse/components': 8.9.4_vue@3.2.37
|
||||||
'@vueuse/core': 8.9.4_vue@3.2.37
|
'@vueuse/core': 8.9.4_vue@3.2.37
|
||||||
|
@ -174,12 +174,12 @@ importers:
|
||||||
|
|
||||||
packages/shared:
|
packages/shared:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@halo-dev/api-client': ^0.0.6
|
'@halo-dev/api-client': ^0.0.7
|
||||||
'@halo-dev/components': workspace:*
|
'@halo-dev/components': workspace:*
|
||||||
axios: ^0.27.2
|
axios: ^0.27.2
|
||||||
vite-plugin-dts: ^1.4.0
|
vite-plugin-dts: ^1.4.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@halo-dev/api-client': 0.0.6
|
'@halo-dev/api-client': 0.0.7
|
||||||
'@halo-dev/components': link:../components
|
'@halo-dev/components': link:../components
|
||||||
axios: 0.27.2
|
axios: 0.27.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
@ -1873,8 +1873,8 @@ packages:
|
||||||
- debug
|
- debug
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/api-client/0.0.6:
|
/@halo-dev/api-client/0.0.7:
|
||||||
resolution: {integrity: sha512-JDWGlTq+pHVrZsmqDCXAowZQFcNL3M6+guL37yrKbhylUgIutYpCMU/d2Qogc+c43FzFBq84igP84T5XtuknjQ==}
|
resolution: {integrity: sha512-UP36IYuSuDh/rRX+fwZAiPE+h/bhBLgh4XVfqVFaynHc3fLQhaUchqzqvep0HYAPHW4E7EDfQaNiFLXHUrH1bA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/logger/1.1.0:
|
/@halo-dev/logger/1.1.0:
|
||||||
|
|
|
@ -1,184 +1,39 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
IconArrowRight,
|
|
||||||
IconExchange,
|
IconExchange,
|
||||||
IconEye,
|
IconEye,
|
||||||
IconGitHub,
|
|
||||||
IconPalette,
|
IconPalette,
|
||||||
VAlert,
|
VAlert,
|
||||||
VButton,
|
VButton,
|
||||||
VCard,
|
VCard,
|
||||||
VModal,
|
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VSpace,
|
VSpace,
|
||||||
VTabbar,
|
VTabbar,
|
||||||
VTag,
|
VTag,
|
||||||
} from "@halo-dev/components";
|
} 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 { 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 {
|
const selectedTheme = ref<Theme>({} as Theme);
|
||||||
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 themesModal = ref(false);
|
const themesModal = ref(false);
|
||||||
const themeActiveId = ref("detail");
|
const tabActiveId = ref("detail");
|
||||||
|
const themeListRef = ref();
|
||||||
|
|
||||||
const handleChangeTheme = (theme: Theme) => {
|
const { isActivated, activatedTheme, handleActiveTheme } =
|
||||||
currentTheme.value = theme;
|
useThemeLifeCycle(selectedTheme);
|
||||||
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);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<ThemeListModal
|
||||||
|
ref="themeListRef"
|
||||||
|
v-model:activated-theme="activatedTheme"
|
||||||
|
v-model:selected-theme="selectedTheme"
|
||||||
v-model:visible="themesModal"
|
v-model:visible="themesModal"
|
||||||
:body-class="['!p-0']"
|
/>
|
||||||
:width="888"
|
<VPageHeader :title="selectedTheme.spec?.displayName">
|
||||||
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">
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconPalette class="mr-2 self-center" />
|
<IconPalette class="mr-2 self-center" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -190,7 +45,14 @@ onMounted(handleFetchThemes);
|
||||||
</template>
|
</template>
|
||||||
切换主题
|
切换主题
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton size="sm" type="primary"> 启用</VButton>
|
<VButton
|
||||||
|
v-if="!isActivated"
|
||||||
|
size="sm"
|
||||||
|
type="primary"
|
||||||
|
@click="handleActiveTheme"
|
||||||
|
>
|
||||||
|
启用
|
||||||
|
</VButton>
|
||||||
<VButton :route="{ name: 'ThemeVisual' }" type="secondary">
|
<VButton :route="{ name: 'ThemeVisual' }" type="secondary">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconEye class="h-full w-full" />
|
<IconEye class="h-full w-full" />
|
||||||
|
@ -204,7 +66,7 @@ onMounted(handleFetchThemes);
|
||||||
<VCard :body-class="['!p-0']">
|
<VCard :body-class="['!p-0']">
|
||||||
<template #header>
|
<template #header>
|
||||||
<VTabbar
|
<VTabbar
|
||||||
v-model:active-id="themeActiveId"
|
v-model:active-id="tabActiveId"
|
||||||
:items="[
|
:items="[
|
||||||
{ id: 'detail', label: '详情' },
|
{ id: 'detail', label: '详情' },
|
||||||
{ id: 'settings', label: '基础设置' },
|
{ id: 'settings', label: '基础设置' },
|
||||||
|
@ -214,29 +76,31 @@ onMounted(handleFetchThemes);
|
||||||
></VTabbar>
|
></VTabbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="themeActiveId === 'detail'">
|
<div v-if="tabActiveId === 'detail'">
|
||||||
<div class="px-4 py-4 sm:px-6">
|
<div class="px-4 py-4 sm:px-6">
|
||||||
<div class="flex flex-row gap-3">
|
<div class="flex flex-row gap-3">
|
||||||
<div v-if="currentTheme.spec?.logo">
|
<div v-if="selectedTheme.spec?.logo">
|
||||||
<div
|
<div
|
||||||
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
|
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:alt="currentTheme.spec?.displayName"
|
:alt="selectedTheme.spec?.displayName"
|
||||||
:src="currentTheme.spec?.logo"
|
:src="selectedTheme.spec?.logo"
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
||||||
{{ currentTheme.spec?.displayName }}
|
{{ selectedTheme.spec?.displayName }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
{{ currentTheme.spec?.version }}
|
{{ selectedTheme.spec?.version }}
|
||||||
</span>
|
</span>
|
||||||
<VTag> 当前启用</VTag>
|
<VTag>
|
||||||
|
{{ isActivated ? "当前启用" : "未启用" }}
|
||||||
|
</VTag>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -248,7 +112,7 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">ID</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
{{ currentTheme.metadata?.name }}
|
{{ selectedTheme.metadata?.name }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -256,7 +120,7 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">作者</dt>
|
<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">
|
<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>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -264,8 +128,8 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">网站</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
<a :href="currentTheme.spec?.website" target="_blank">
|
<a :href="selectedTheme.spec?.website" target="_blank">
|
||||||
{{ currentTheme.spec?.website }}
|
{{ selectedTheme.spec?.website }}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
@ -274,8 +138,8 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">源码仓库</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
<a :href="currentTheme.spec?.website" target="_blank">
|
<a :href="selectedTheme.spec?.repo" target="_blank">
|
||||||
{{ currentTheme.spec?.website }}
|
{{ selectedTheme.spec?.repo }}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
@ -284,7 +148,7 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">当前版本</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
{{ currentTheme.spec?.version }}
|
{{ selectedTheme.spec?.version }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -292,7 +156,7 @@ onMounted(handleFetchThemes);
|
||||||
>
|
>
|
||||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
{{ currentTheme.spec?.require }}
|
{{ selectedTheme.spec?.require }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -354,7 +218,7 @@ onMounted(handleFetchThemes);
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="w-1/3">
|
||||||
<FormKit id="theme-setting-form" :actions="false" type="form">
|
<FormKit id="theme-setting-form" :actions="false" type="form">
|
||||||
<FormKit label="侧边栏宽度" type="text"></FormKit>
|
<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