mirror of https://github.com/halo-dev/halo-admin
feat: add theme settings support (#593)
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/2299 /hold until https://github.com/halo-dev/halo/pull/2299 merge #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2298 <!-- PR 合并时自动关闭 issue。 Automatically closes linked issue when PR is merged. 用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)` Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> #### Screenshots: |<img width="1564" alt="image" src="https://user-images.githubusercontent.com/21301288/182583412-f14a890e-25ee-418c-b921-0924f94c0d1b.png">|<img width="1564" alt="image" src="https://user-images.githubusercontent.com/21301288/182583525-26fe165c-2dd0-4068-9d52-659dc626367a.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: 测试方式: 1. Halo 后端需要 checkout 到 https://github.com/halo-dev/halo/pull/2299 分支。 2. 将被测主题放置在 `~/halo-next/themes`,可以使用 https://github.com/ruibaby/theme-astro-starter 3. 使用主题内的 `theme.yaml` 和 `settings.yaml` 创建 `Theme` 和 `Setting` 的资源。 4. admin 需要 checkout 到当前 PR 的分支,启动开发服务之后在主题列表即可启用该主题。 5. 测试保存和更新主题配置。 #### 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/594/head
parent
86e86d138b
commit
7bde20a515
|
@ -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.7",
|
"@halo-dev/api-client": "^0.0.8",
|
||||||
"@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",
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
"build": "vite build"
|
"build": "vite build",
|
||||||
|
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -35,7 +36,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.7",
|
"@halo-dev/api-client": "^0.0.8",
|
||||||
"@halo-dev/components": "workspace:*",
|
"@halo-dev/components": "workspace:*",
|
||||||
"axios": "^0.27.2"
|
"axios": "^0.27.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Plugin } from "@/types/plugin";
|
import type { Plugin } from "../types/plugin";
|
||||||
|
|
||||||
export function definePlugin(plugin: Plugin): Plugin {
|
export function definePlugin(plugin: Plugin): Plugin {
|
||||||
return plugin;
|
return plugin;
|
||||||
|
|
|
@ -8,14 +8,14 @@ import {
|
||||||
VRoutesMenu,
|
VRoutesMenu,
|
||||||
VTag,
|
VTag,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import type { MenuGroupType, MenuItemType } from "@/types/menus";
|
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import logo from "@/assets/logo.svg";
|
import logo from "@/assets/logo.svg";
|
||||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
import { RouterView, useRoute, useRouter } from "vue-router";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
|
|
||||||
const menus = inject<MenuGroupType>("menus");
|
const menus = inject<MenuGroupType[]>("menus");
|
||||||
const minimenus = inject<MenuItemType>("minimenus");
|
const minimenus = inject<MenuItemType[]>("minimenus");
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -58,12 +58,12 @@ const currentRole = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
<VRoutesMenu :menus="menus" />
|
<VRoutesMenu :menus="menus" />
|
||||||
<div class="current-profile">
|
<div class="current-profile">
|
||||||
<div v-if="currentUser.spec.avatar" class="profile-avatar">
|
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
||||||
<img :src="currentUser.spec.avatar" class="h-11 w-11 rounded-full" />
|
<img :src="currentUser?.spec.avatar" class="h-11 w-11 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-name">
|
<div class="profile-name">
|
||||||
<div class="flex text-sm font-medium">
|
<div class="flex text-sm font-medium">
|
||||||
{{ currentUser.spec.displayName }}
|
{{ currentUser?.spec.displayName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<VTag>
|
<VTag>
|
||||||
|
@ -89,14 +89,15 @@ const currentRole = computed(() => {
|
||||||
|
|
||||||
<!--bottom nav bar-->
|
<!--bottom nav bar-->
|
||||||
<div
|
<div
|
||||||
|
v-if="minimenus"
|
||||||
class="bottom-nav-bar fixed left-0 bottom-0 right-0 grid grid-cols-6 border-t-2 border-black drop-shadow-2xl mt-safe pb-safe md:hidden bg-secondary"
|
class="bottom-nav-bar fixed left-0 bottom-0 right-0 grid grid-cols-6 border-t-2 border-black drop-shadow-2xl mt-safe pb-safe md:hidden bg-secondary"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(menu, index) in minimenus"
|
v-for="(menu, index) in minimenus"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="{ 'bg-black': route.path === menu.path }"
|
:class="{ 'bg-black': route.path === menu?.path }"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
@click="router.push(menu.path)"
|
@click="router.push(menu?.path)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex w-full cursor-pointer items-center justify-center p-1 text-white"
|
class="flex w-full cursor-pointer items-center justify-center p-1 text-white"
|
||||||
|
@ -105,10 +106,10 @@ const currentRole = computed(() => {
|
||||||
class="is-active is-active0 flex h-10 w-10 flex-col items-center justify-center"
|
class="is-active is-active0 flex h-10 w-10 flex-col items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-base">
|
<div class="text-base">
|
||||||
<Component :is="menu.icon" />
|
<Component :is="menu?.icon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-0.5 text-xs">
|
<div class="mt-0.5 text-xs">
|
||||||
{{ menu.name }}
|
{{ menu?.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { RouterView, useRoute, useRouter } from "vue-router";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { onMounted, provide, ref, watch } from "vue";
|
import { onMounted, provide, ref, watch } from "vue";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import type { Plugin } from "@halo-dev/api-client";
|
||||||
import type { FormKitSetting, FormKitSettingSpec } from "@/types/formkit";
|
import type { FormKitSetting, FormKitSettingSpec } from "../types/formkit";
|
||||||
import { BasicLayout } from "../layouts";
|
import { BasicLayout } from "../layouts";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "../utils/api-client";
|
||||||
|
|
||||||
interface PluginTab {
|
interface PluginTab {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicLayout } from "@/layouts";
|
import { BasicLayout } from "./index";
|
||||||
import {
|
import {
|
||||||
IconSettings,
|
IconSettings,
|
||||||
VButton,
|
VButton,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicLayout } from "@/layouts";
|
import { BasicLayout } from "../layouts";
|
||||||
import { IconUpload, VButton, VTabbar } from "@halo-dev/components";
|
import { IconUpload, VButton, VTabbar } from "@halo-dev/components";
|
||||||
import { onMounted, provide, ref, watch } from "vue";
|
import { onMounted, provide, ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "../utils/api-client";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Component, Ref } from "vue";
|
import type { Component, Ref } from "vue";
|
||||||
import type { RouteRecordRaw } from "vue-router";
|
import type { RouteRecordRaw } from "vue-router";
|
||||||
import type { MenuGroupType } from "./menus";
|
import type { MenuGroupType } from "./menus";
|
||||||
import type { PagesPublicState } from "@/states/pages";
|
import type { PagesPublicState } from "../states/pages";
|
||||||
|
|
||||||
export type ExtensionPointName = "PAGES" | "POSTS";
|
export type ExtensionPointName = "PAGES" | "POSTS";
|
||||||
|
|
||||||
|
|
|
@ -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.7
|
'@halo-dev/api-client': ^0.0.8
|
||||||
'@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.7
|
'@halo-dev/api-client': 0.0.8
|
||||||
'@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.7
|
'@halo-dev/api-client': ^0.0.8
|
||||||
'@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.7
|
'@halo-dev/api-client': 0.0.8
|
||||||
'@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.7:
|
/@halo-dev/api-client/0.0.8:
|
||||||
resolution: {integrity: sha512-UP36IYuSuDh/rRX+fwZAiPE+h/bhBLgh4XVfqVFaynHc3fLQhaUchqzqvep0HYAPHW4E7EDfQaNiFLXHUrH1bA==}
|
resolution: {integrity: sha512-ANCJ9/O++FHyLfiREQQbNWHP0sj721VxoNqPgdG4ctDwbOdXpEyxgXKmOqGPadfThCtMolMS9XKDr6qG3q+xLQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/logger/1.1.0:
|
/@halo-dev/logger/1.1.0:
|
||||||
|
|
|
@ -8,13 +8,15 @@ const textClassification = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const boxClassification = {
|
const boxClassification = {
|
||||||
fieldset: "border border-gray-400 rounded-md px-2 pb-1",
|
fieldset:
|
||||||
|
"border border-gray-300 rounded-base px-2 pb-1 focus-within:border-primary",
|
||||||
legend: "font-bold text-sm",
|
legend: "font-bold text-sm",
|
||||||
wrapper: "flex items-center mb-1 cursor-pointer",
|
wrapper: "flex items-center mb-1 cursor-pointer",
|
||||||
help: "mb-2",
|
help: "mb-2",
|
||||||
input:
|
input:
|
||||||
"form-check-input appearance-none h-5 w-5 mr-2 border border-gray-500 rounded-sm bg-white checked:bg-blue-500 focus:outline-none focus:ring-0 transition duration-200",
|
"form-check-input appearance-none h-4 w-4 mr-2 border border-gray-500 rounded-sm bg-white checked:bg-primary focus:outline-none focus:ring-0 transition duration-200",
|
||||||
label: "text-sm text-gray-700 mt-1",
|
label: "text-sm text-gray-700",
|
||||||
|
inner: "flex items-center",
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonClassification = {
|
const buttonClassification = {
|
||||||
|
|
|
@ -1,239 +1,153 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { VAlert, VSpace, VTag } from "@halo-dev/components";
|
||||||
IconExchange,
|
import type { ComputedRef, Ref } from "vue";
|
||||||
IconEye,
|
import { computed, inject, ref } from "vue";
|
||||||
IconPalette,
|
|
||||||
VAlert,
|
|
||||||
VButton,
|
|
||||||
VCard,
|
|
||||||
VPageHeader,
|
|
||||||
VSpace,
|
|
||||||
VTabbar,
|
|
||||||
VTag,
|
|
||||||
} from "@halo-dev/components";
|
|
||||||
import ThemeListModal from "./components/ThemeListModal.vue";
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { RouterLink } from "vue-router";
|
import { RouterLink } from "vue-router";
|
||||||
import type { Theme } from "@halo-dev/api-client";
|
import type { Theme } from "@halo-dev/api-client";
|
||||||
import { useThemeLifeCycle } from "./composables/use-theme";
|
|
||||||
|
|
||||||
const selectedTheme = ref<Theme>({} as Theme);
|
const selectedTheme = inject<Ref<Theme>>(
|
||||||
const themesModal = ref(false);
|
"selectedTheme",
|
||||||
const tabActiveId = ref("detail");
|
ref<Theme>({} as Theme)
|
||||||
const themeListRef = ref();
|
);
|
||||||
|
const isActivated = inject<ComputedRef<boolean>>(
|
||||||
const { isActivated, activatedTheme, handleActiveTheme } =
|
"isActivated",
|
||||||
useThemeLifeCycle(selectedTheme);
|
computed(() => false)
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ThemeListModal
|
<div class="bg-white px-4 py-4 sm:px-6">
|
||||||
ref="themeListRef"
|
<div class="flex flex-row gap-3">
|
||||||
v-model:activated-theme="activatedTheme"
|
<div v-if="selectedTheme.spec?.logo">
|
||||||
v-model:selected-theme="selectedTheme"
|
<div
|
||||||
v-model:visible="themesModal"
|
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||||
/>
|
|
||||||
<VPageHeader :title="selectedTheme.spec?.displayName">
|
|
||||||
<template #icon>
|
|
||||||
<IconPalette class="mr-2 self-center" />
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<VSpace>
|
|
||||||
<VButton size="sm" type="default" @click="themesModal = true">
|
|
||||||
<template #icon>
|
|
||||||
<IconExchange class="h-full w-full" />
|
|
||||||
</template>
|
|
||||||
切换主题
|
|
||||||
</VButton>
|
|
||||||
<VButton
|
|
||||||
v-if="!isActivated"
|
|
||||||
size="sm"
|
|
||||||
type="primary"
|
|
||||||
@click="handleActiveTheme"
|
|
||||||
>
|
>
|
||||||
启用
|
<img
|
||||||
</VButton>
|
:alt="selectedTheme.spec?.displayName"
|
||||||
<VButton :route="{ name: 'ThemeVisual' }" type="secondary">
|
:src="selectedTheme.spec?.logo"
|
||||||
<template #icon>
|
class="h-full w-full"
|
||||||
<IconEye class="h-full w-full" />
|
/>
|
||||||
</template>
|
</div>
|
||||||
可视化编辑
|
</div>
|
||||||
</VButton>
|
<div>
|
||||||
</VSpace>
|
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
||||||
</template>
|
{{ selectedTheme.spec?.displayName }}
|
||||||
</VPageHeader>
|
</h3>
|
||||||
<div class="m-0 md:m-4">
|
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||||
<VCard :body-class="['!p-0']">
|
<span class="text-sm text-gray-500">
|
||||||
<template #header>
|
{{ selectedTheme.spec?.version }}
|
||||||
<VTabbar
|
</span>
|
||||||
v-model:active-id="tabActiveId"
|
<VTag>
|
||||||
:items="[
|
{{ isActivated ? "当前启用" : "未启用" }}
|
||||||
{ id: 'detail', label: '详情' },
|
</VTag>
|
||||||
{ id: 'settings', label: '基础设置' },
|
</p>
|
||||||
]"
|
</div>
|
||||||
class="w-full !rounded-none"
|
</div>
|
||||||
type="outline"
|
</div>
|
||||||
></VTabbar>
|
<div class="border-t border-gray-200">
|
||||||
</template>
|
<dl class="divide-y divide-gray-100">
|
||||||
|
<div
|
||||||
<div v-if="tabActiveId === 'detail'">
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
<div class="px-4 py-4 sm:px-6">
|
>
|
||||||
<div class="flex flex-row gap-3">
|
<dt class="text-sm font-medium text-gray-900">ID</dt>
|
||||||
<div v-if="selectedTheme.spec?.logo">
|
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||||
|
{{ selectedTheme.metadata?.name }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
{{ selectedTheme.spec?.author?.name }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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="selectedTheme.spec?.website" target="_blank">
|
||||||
|
{{ selectedTheme.spec?.website }}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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="selectedTheme.spec?.repo" target="_blank">
|
||||||
|
{{ selectedTheme.spec?.repo }}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
{{ selectedTheme.spec?.version }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
{{ selectedTheme.spec?.require }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<dt class="text-sm font-medium text-gray-900">插件依赖</dt>
|
||||||
|
<dd class="mt-1 text-sm sm:col-span-3 sm:mt-0">
|
||||||
|
<VAlert description="当前有 1 个插件还未安装" title="提示"></VAlert>
|
||||||
|
<ul class="mt-2 space-y-2">
|
||||||
|
<li>
|
||||||
<div
|
<div
|
||||||
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
|
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||||
>
|
>
|
||||||
<img
|
<RouterLink
|
||||||
:alt="selectedTheme.spec?.displayName"
|
:to="{
|
||||||
:src="selectedTheme.spec?.logo"
|
name: 'PluginDetail',
|
||||||
class="h-full w-full"
|
params: { name: 'PluginLinks' },
|
||||||
/>
|
}"
|
||||||
|
class="font-medium text-gray-900 hover:text-blue-400"
|
||||||
|
>
|
||||||
|
run.halo.plugins.links
|
||||||
|
</RouterLink>
|
||||||
|
<div class="text-xs">
|
||||||
|
<VSpace>
|
||||||
|
<VTag> 已安装</VTag>
|
||||||
|
</VSpace>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
<div>
|
<li>
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
<div
|
||||||
{{ selectedTheme.spec?.displayName }}
|
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||||
</h3>
|
>
|
||||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
<span class="font-medium hover:text-blue-400">
|
||||||
<span class="text-sm text-gray-500">
|
run.halo.plugins.photos
|
||||||
{{ selectedTheme.spec?.version }}
|
|
||||||
</span>
|
</span>
|
||||||
<VTag>
|
<div class="text-xs">
|
||||||
{{ isActivated ? "当前启用" : "未启用" }}
|
<VSpace>
|
||||||
</VTag>
|
<VTag>未安装</VTag>
|
||||||
</p>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
<div class="border-t border-gray-200">
|
</ul>
|
||||||
<dl class="divide-y divide-gray-100">
|
</dd>
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
{{ selectedTheme.metadata?.name }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
{{ selectedTheme.spec?.author?.name }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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="selectedTheme.spec?.website" target="_blank">
|
|
||||||
{{ selectedTheme.spec?.website }}
|
|
||||||
</a>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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="selectedTheme.spec?.repo" target="_blank">
|
|
||||||
{{ selectedTheme.spec?.repo }}
|
|
||||||
</a>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
{{ selectedTheme.spec?.version }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
{{ selectedTheme.spec?.require }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="text-sm font-medium text-gray-900">插件依赖</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:col-span-3 sm:mt-0">
|
|
||||||
<VAlert
|
|
||||||
description="当前有 1 个插件还未安装"
|
|
||||||
title="提示"
|
|
||||||
></VAlert>
|
|
||||||
<ul class="mt-2 space-y-2">
|
|
||||||
<li>
|
|
||||||
<div
|
|
||||||
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
|
||||||
>
|
|
||||||
<RouterLink
|
|
||||||
:to="{
|
|
||||||
name: 'PluginDetail',
|
|
||||||
params: { name: 'PluginLinks' },
|
|
||||||
}"
|
|
||||||
class="font-medium text-gray-900 hover:text-blue-400"
|
|
||||||
>
|
|
||||||
run.halo.plugins.links
|
|
||||||
</RouterLink>
|
|
||||||
<div class="text-xs">
|
|
||||||
<VSpace>
|
|
||||||
<VTag> 已安装</VTag>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div
|
|
||||||
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
|
||||||
>
|
|
||||||
<span class="font-medium hover:text-blue-400">
|
|
||||||
run.halo.plugins.photos
|
|
||||||
</span>
|
|
||||||
<div class="text-xs">
|
|
||||||
<VSpace>
|
|
||||||
<VTag>未安装</VTag>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</dl>
|
||||||
<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>
|
|
||||||
<FormKit label="侧边栏背景图" type="text"></FormKit>
|
|
||||||
<FormKit label="右上角图标" type="text"></FormKit>
|
|
||||||
<FormKit label="文章代码高亮语言" type="text"></FormKit>
|
|
||||||
</FormKit>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pt-5">
|
|
||||||
<div class="flex justify-start">
|
|
||||||
<VButton type="secondary"> 保存</VButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</VCard>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Ref } from "vue";
|
||||||
|
import { computed, inject, ref } from "vue";
|
||||||
|
import { VButton } from "@halo-dev/components";
|
||||||
|
import { apiClient } from "@halo-dev/admin-shared";
|
||||||
|
import type { ConfigMap, Theme } from "@halo-dev/api-client";
|
||||||
|
import type {
|
||||||
|
FormKitSetting,
|
||||||
|
FormKitSettingSpec,
|
||||||
|
} from "@halo-dev/admin-shared/src";
|
||||||
|
|
||||||
|
const settings = inject<Ref<FormKitSetting | undefined>>("settings");
|
||||||
|
const configmapFormData =
|
||||||
|
inject<Ref<Record<string, Record<string, string>> | undefined>>(
|
||||||
|
"configmapFormData"
|
||||||
|
);
|
||||||
|
const configmap = inject<Ref<ConfigMap>>("configmap", {} as Ref<ConfigMap>);
|
||||||
|
const selectedTheme = inject<Ref<Theme>>("selectedTheme", ref({} as Theme));
|
||||||
|
const group = inject<Ref<string | undefined>>("activeTab");
|
||||||
|
|
||||||
|
const saving = ref(false);
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
if (!settings?.value?.spec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return settings.value.spec.find((item) => item.group === group?.value)
|
||||||
|
?.formSchema;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFetchSettings = inject<() => void>("handleFetchSettings");
|
||||||
|
const handleFetchConfigMap = inject<() => void>("handleFetchConfigMap");
|
||||||
|
|
||||||
|
const handleSaveConfigMap = async () => {
|
||||||
|
try {
|
||||||
|
saving.value = true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!configmap.value.metadata.name &&
|
||||||
|
selectedTheme.value.spec.configMapName
|
||||||
|
) {
|
||||||
|
configmap.value.metadata.name = selectedTheme.value.spec.configMapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings?.value?.spec.forEach((item: FormKitSettingSpec) => {
|
||||||
|
// @ts-ignore
|
||||||
|
configmap.value.data[item.group] = JSON.stringify(
|
||||||
|
configmapFormData?.value?.[item.group]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!configmap.value.metadata.creationTimestamp) {
|
||||||
|
await apiClient.extension.configMap.createv1alpha1ConfigMap(
|
||||||
|
configmap.value
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await apiClient.extension.configMap.updatev1alpha1ConfigMap(
|
||||||
|
configmap.value.metadata.name,
|
||||||
|
configmap.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
handleFetchSettings?.();
|
||||||
|
handleFetchConfigMap?.();
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="bg-white p-4 sm:px-6">
|
||||||
|
<div class="w-1/3">
|
||||||
|
<FormKit
|
||||||
|
v-if="group && formSchema && configmapFormData"
|
||||||
|
:id="group"
|
||||||
|
v-model="configmapFormData[group]"
|
||||||
|
:actions="false"
|
||||||
|
:preserve="true"
|
||||||
|
type="form"
|
||||||
|
@submit="handleSaveConfigMap"
|
||||||
|
>
|
||||||
|
<FormKitSchema :schema="formSchema" />
|
||||||
|
</FormKit>
|
||||||
|
</div>
|
||||||
|
<div class="pt-5">
|
||||||
|
<div class="flex justify-start">
|
||||||
|
<VButton
|
||||||
|
:loading="saving"
|
||||||
|
type="secondary"
|
||||||
|
@click="$formkit.submit(group || '')"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</VButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,246 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
IconExchange,
|
||||||
|
IconEye,
|
||||||
|
IconPalette,
|
||||||
|
VButton,
|
||||||
|
VCard,
|
||||||
|
VPageHeader,
|
||||||
|
VSpace,
|
||||||
|
VTabbar,
|
||||||
|
} from "@halo-dev/components";
|
||||||
|
import type {
|
||||||
|
FormKitSetting,
|
||||||
|
FormKitSettingSpec,
|
||||||
|
} from "@halo-dev/admin-shared";
|
||||||
|
import { apiClient, BasicLayout } from "@halo-dev/admin-shared";
|
||||||
|
import ThemeListModal from "../components/ThemeListModal.vue";
|
||||||
|
import type { ComputedRef, Ref } from "vue";
|
||||||
|
import { provide, ref, watch, watchEffect } from "vue";
|
||||||
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import type { ConfigMap, Theme } from "@halo-dev/api-client";
|
||||||
|
import { useThemeLifeCycle } from "../composables/use-theme";
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
|
||||||
|
interface ThemeTab {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
route: RouteLocationRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialTabs: ThemeTab[] = [
|
||||||
|
{
|
||||||
|
id: "detail",
|
||||||
|
label: "详情",
|
||||||
|
route: {
|
||||||
|
name: "ThemeDetail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialConfigMap: ConfigMap = {
|
||||||
|
data: {},
|
||||||
|
apiVersion: "v1alpha1",
|
||||||
|
kind: "ConfigMap",
|
||||||
|
metadata: {
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = ref<ThemeTab[]>(cloneDeep(initialTabs));
|
||||||
|
const selectedTheme = ref<Theme>({} as Theme);
|
||||||
|
const settings = ref<FormKitSetting | undefined>();
|
||||||
|
const configmapFormData = ref<
|
||||||
|
Record<string, Record<string, string>> | undefined
|
||||||
|
>();
|
||||||
|
const configmap = ref<ConfigMap>(cloneDeep(initialConfigMap));
|
||||||
|
const themesModal = ref(false);
|
||||||
|
const activeTab = ref("detail");
|
||||||
|
|
||||||
|
const { isActivated, activatedTheme, handleActiveTheme } =
|
||||||
|
useThemeLifeCycle(selectedTheme);
|
||||||
|
|
||||||
|
provide<Ref<Theme>>("activatedTheme", activatedTheme);
|
||||||
|
provide<Ref<Theme>>("selectedTheme", selectedTheme);
|
||||||
|
provide<Ref<FormKitSetting | undefined>>("settings", settings);
|
||||||
|
provide<Ref<Record<string, Record<string, string>> | undefined>>(
|
||||||
|
"configmapFormData",
|
||||||
|
configmapFormData
|
||||||
|
);
|
||||||
|
provide<Ref<ConfigMap>>("configmap", configmap);
|
||||||
|
provide<ComputedRef<boolean>>("isActivated", isActivated);
|
||||||
|
provide<Ref<string | undefined>>("activeTab", activeTab);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleTabChange = (id: string) => {
|
||||||
|
const tab = tabs.value.find((item) => item.id === id);
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.route);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFetchSettings = async () => {
|
||||||
|
tabs.value = cloneDeep(initialTabs);
|
||||||
|
if (!selectedTheme.value?.spec?.settingName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await apiClient.extension.setting.getv1alpha1Setting(
|
||||||
|
selectedTheme.value.spec.settingName as string
|
||||||
|
);
|
||||||
|
settings.value = response.data as FormKitSetting;
|
||||||
|
|
||||||
|
const { spec } = settings.value;
|
||||||
|
|
||||||
|
if (spec) {
|
||||||
|
tabs.value = [
|
||||||
|
...tabs.value,
|
||||||
|
...spec.map((item: FormKitSettingSpec) => {
|
||||||
|
return {
|
||||||
|
id: item.group,
|
||||||
|
label: item.label || "",
|
||||||
|
route: {
|
||||||
|
name: "ThemeSetting",
|
||||||
|
params: {
|
||||||
|
group: item.group,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
] as ThemeTab[];
|
||||||
|
|
||||||
|
onTabChange(route.name as string);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFetchConfigMap = async () => {
|
||||||
|
if (!selectedTheme.value.spec?.configMapName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await apiClient.extension.configMap.getv1alpha1ConfigMap(
|
||||||
|
selectedTheme.value.spec?.configMapName as string
|
||||||
|
);
|
||||||
|
configmap.value = response.data;
|
||||||
|
|
||||||
|
const { data } = configmap.value;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
configmapFormData.value = Object.keys(data).reduce((acc, key) => {
|
||||||
|
acc[key] = JSON.parse(data[key]);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
if (!configmapFormData.value) {
|
||||||
|
configmapFormData.value = settings.value?.spec.reduce((acc, item) => {
|
||||||
|
acc[item.group] = {};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
provide<() => void>("handleFetchSettings", handleFetchSettings);
|
||||||
|
provide<() => void>("handleFetchConfigMap", handleFetchConfigMap);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (selectedTheme.value) {
|
||||||
|
handleFetchSettings();
|
||||||
|
handleFetchConfigMap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onTabChange = (routeName: string) => {
|
||||||
|
if (routeName === "ThemeSetting") {
|
||||||
|
const tab = tabs.value.find((tab) => {
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
tab.route.name === routeName &&
|
||||||
|
// @ts-ignore
|
||||||
|
tab.route.params.group === route.params.group
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (tab) {
|
||||||
|
activeTab.value = tab.id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push({ name: "ThemeDetail" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const tab = tabs.value.find((tab) => tab.route.name === route.name);
|
||||||
|
activeTab.value = tab ? tab.id : tabs.value[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
async (newRouteName) => {
|
||||||
|
onTabChange(newRouteName as string);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<ThemeListModal
|
||||||
|
v-model:activated-theme="activatedTheme"
|
||||||
|
v-model:selected-theme="selectedTheme"
|
||||||
|
v-model:visible="themesModal"
|
||||||
|
/>
|
||||||
|
<VPageHeader :title="selectedTheme.spec?.displayName">
|
||||||
|
<template #icon>
|
||||||
|
<IconPalette class="mr-2 self-center" />
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<VSpace>
|
||||||
|
<VButton size="sm" type="default" @click="themesModal = true">
|
||||||
|
<template #icon>
|
||||||
|
<IconExchange class="h-full w-full" />
|
||||||
|
</template>
|
||||||
|
切换主题
|
||||||
|
</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" />
|
||||||
|
</template>
|
||||||
|
可视化编辑
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
|
</template>
|
||||||
|
</VPageHeader>
|
||||||
|
|
||||||
|
<div class="m-0 md:m-4">
|
||||||
|
<VCard :body-class="['!p-0']">
|
||||||
|
<template #header>
|
||||||
|
<VTabbar
|
||||||
|
v-model:active-id="activeTab"
|
||||||
|
:items="tabs"
|
||||||
|
class="w-full !rounded-none"
|
||||||
|
type="outline"
|
||||||
|
@change="handleTabChange"
|
||||||
|
></VTabbar>
|
||||||
|
</template>
|
||||||
|
</VCard>
|
||||||
|
<div>
|
||||||
|
<RouterView :key="activeTab" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
|
@ -1,5 +1,7 @@
|
||||||
import { BasicLayout, BlankLayout, definePlugin } from "@halo-dev/admin-shared";
|
import { BlankLayout, definePlugin } from "@halo-dev/admin-shared";
|
||||||
|
import ThemeLayout from "./layouts/ThemeLayout.vue";
|
||||||
import ThemeDetail from "./ThemeDetail.vue";
|
import ThemeDetail from "./ThemeDetail.vue";
|
||||||
|
import ThemeSetting from "./ThemeSetting.vue";
|
||||||
import Visual from "./Visual.vue";
|
import Visual from "./Visual.vue";
|
||||||
import { IconPalette } from "@halo-dev/components";
|
import { IconPalette } from "@halo-dev/components";
|
||||||
|
|
||||||
|
@ -9,13 +11,18 @@ export default definePlugin({
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/theme",
|
path: "/theme",
|
||||||
component: BasicLayout,
|
component: ThemeLayout,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "Theme",
|
name: "ThemeDetail",
|
||||||
component: ThemeDetail,
|
component: ThemeDetail,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "settings/:group",
|
||||||
|
name: "ThemeSetting",
|
||||||
|
component: ThemeSetting,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -115,7 +115,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch([() => plugin.value, () => group?.value], () => {
|
watch([() => plugin.value, () => group?.value], () => {
|
||||||
handleFetchConfigMap();
|
handleFetchSettings();
|
||||||
handleFetchConfigMap();
|
handleFetchConfigMap();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*", "packages/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
|
Loading…
Reference in New Issue