mirror of https://github.com/halo-dev/halo
				
				
				
			feat: add theme settings support (halo-dev/console#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:  After:  --> #### 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/3445/head
							parent
							
								
									a580d89e39
								
							
						
					
					
						commit
						c55449c858
					
				| 
						 | 
				
			
			@ -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.7",
 | 
			
		||||
    "@halo-dev/api-client": "^0.0.8",
 | 
			
		||||
    "@halo-dev/components": "workspace:*",
 | 
			
		||||
    "@vueuse/components": "^8.9.4",
 | 
			
		||||
    "@vueuse/core": "^8.9.4",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,8 @@
 | 
			
		|||
  ],
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite build --watch",
 | 
			
		||||
    "build": "vite build"
 | 
			
		||||
    "build": "vite build",
 | 
			
		||||
    "typecheck": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [],
 | 
			
		||||
  "author": {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +36,7 @@
 | 
			
		|||
  "homepage": "https://github.com/halo-dev/halo-admin/tree/next/shared/components#readme",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@halo-dev/api-client": "^0.0.7",
 | 
			
		||||
    "@halo-dev/api-client": "^0.0.8",
 | 
			
		||||
    "@halo-dev/components": "workspace:*",
 | 
			
		||||
    "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 {
 | 
			
		||||
  return plugin;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,14 +8,14 @@ import {
 | 
			
		|||
  VRoutesMenu,
 | 
			
		||||
  VTag,
 | 
			
		||||
} 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 logo from "@/assets/logo.svg";
 | 
			
		||||
import { RouterView, useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { computed, inject, ref } from "vue";
 | 
			
		||||
 | 
			
		||||
const menus = inject<MenuGroupType>("menus");
 | 
			
		||||
const minimenus = inject<MenuItemType>("minimenus");
 | 
			
		||||
const menus = inject<MenuGroupType[]>("menus");
 | 
			
		||||
const minimenus = inject<MenuItemType[]>("minimenus");
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,12 +58,12 @@ const currentRole = computed(() => {
 | 
			
		|||
      </div>
 | 
			
		||||
      <VRoutesMenu :menus="menus" />
 | 
			
		||||
      <div class="current-profile">
 | 
			
		||||
        <div v-if="currentUser.spec.avatar" class="profile-avatar">
 | 
			
		||||
          <img :src="currentUser.spec.avatar" class="h-11 w-11 rounded-full" />
 | 
			
		||||
        <div v-if="currentUser?.spec.avatar" class="profile-avatar">
 | 
			
		||||
          <img :src="currentUser?.spec.avatar" class="h-11 w-11 rounded-full" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="profile-name">
 | 
			
		||||
          <div class="flex text-sm font-medium">
 | 
			
		||||
            {{ currentUser.spec.displayName }}
 | 
			
		||||
            {{ currentUser?.spec.displayName }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex">
 | 
			
		||||
            <VTag>
 | 
			
		||||
| 
						 | 
				
			
			@ -89,14 +89,15 @@ const currentRole = computed(() => {
 | 
			
		|||
 | 
			
		||||
    <!--bottom nav bar-->
 | 
			
		||||
    <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"
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        v-for="(menu, index) in minimenus"
 | 
			
		||||
        :key="index"
 | 
			
		||||
        :class="{ 'bg-black': route.path === menu.path }"
 | 
			
		||||
        :class="{ 'bg-black': route.path === menu?.path }"
 | 
			
		||||
        class="nav-item"
 | 
			
		||||
        @click="router.push(menu.path)"
 | 
			
		||||
        @click="router.push(menu?.path)"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          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"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="text-base">
 | 
			
		||||
              <Component :is="menu.icon" />
 | 
			
		||||
              <Component :is="menu?.icon" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mt-0.5 text-xs">
 | 
			
		||||
              {{ menu.name }}
 | 
			
		||||
              {{ menu?.name }}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,9 +5,9 @@ import { RouterView, useRoute, useRouter } from "vue-router";
 | 
			
		|||
import type { Ref } from "vue";
 | 
			
		||||
import { onMounted, provide, ref, watch } from "vue";
 | 
			
		||||
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 { apiClient } from "@/utils/api-client";
 | 
			
		||||
import { apiClient } from "../utils/api-client";
 | 
			
		||||
 | 
			
		||||
interface PluginTab {
 | 
			
		||||
  id: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import { BasicLayout } from "@/layouts";
 | 
			
		||||
import { BasicLayout } from "./index";
 | 
			
		||||
import {
 | 
			
		||||
  IconSettings,
 | 
			
		||||
  VButton,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import { BasicLayout } from "@/layouts";
 | 
			
		||||
import { BasicLayout } from "../layouts";
 | 
			
		||||
import { IconUpload, VButton, VTabbar } from "@halo-dev/components";
 | 
			
		||||
import { onMounted, provide, ref, watch } from "vue";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
const tabs = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import type { Component, Ref } from "vue";
 | 
			
		||||
import type { RouteRecordRaw } from "vue-router";
 | 
			
		||||
import type { MenuGroupType } from "./menus";
 | 
			
		||||
import type { PagesPublicState } from "@/states/pages";
 | 
			
		||||
import type { PagesPublicState } from "../states/pages";
 | 
			
		||||
 | 
			
		||||
export type ExtensionPointName = "PAGES" | "POSTS";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.7
 | 
			
		||||
      '@halo-dev/api-client': ^0.0.8
 | 
			
		||||
      '@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.7
 | 
			
		||||
      '@halo-dev/api-client': 0.0.8
 | 
			
		||||
      '@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.7
 | 
			
		||||
      '@halo-dev/api-client': ^0.0.8
 | 
			
		||||
      '@halo-dev/components': workspace:*
 | 
			
		||||
      axios: ^0.27.2
 | 
			
		||||
      vite-plugin-dts: ^1.4.0
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@halo-dev/api-client': 0.0.7
 | 
			
		||||
      '@halo-dev/api-client': 0.0.8
 | 
			
		||||
      '@halo-dev/components': link:../components
 | 
			
		||||
      axios: 0.27.2
 | 
			
		||||
    devDependencies:
 | 
			
		||||
| 
						 | 
				
			
			@ -1873,8 +1873,8 @@ packages:
 | 
			
		|||
      - debug
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@halo-dev/api-client/0.0.7:
 | 
			
		||||
    resolution: {integrity: sha512-UP36IYuSuDh/rRX+fwZAiPE+h/bhBLgh4XVfqVFaynHc3fLQhaUchqzqvep0HYAPHW4E7EDfQaNiFLXHUrH1bA==}
 | 
			
		||||
  /@halo-dev/api-client/0.0.8:
 | 
			
		||||
    resolution: {integrity: sha512-ANCJ9/O++FHyLfiREQQbNWHP0sj721VxoNqPgdG4ctDwbOdXpEyxgXKmOqGPadfThCtMolMS9XKDr6qG3q+xLQ==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@halo-dev/logger/1.1.0:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,13 +8,15 @@ const textClassification = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
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",
 | 
			
		||||
  wrapper: "flex items-center mb-1 cursor-pointer",
 | 
			
		||||
  help: "mb-2",
 | 
			
		||||
  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",
 | 
			
		||||
  label: "text-sm text-gray-700 mt-1",
 | 
			
		||||
    "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",
 | 
			
		||||
  inner: "flex items-center",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buttonClassification = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,239 +1,153 @@
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import {
 | 
			
		||||
  IconExchange,
 | 
			
		||||
  IconEye,
 | 
			
		||||
  IconPalette,
 | 
			
		||||
  VAlert,
 | 
			
		||||
  VButton,
 | 
			
		||||
  VCard,
 | 
			
		||||
  VPageHeader,
 | 
			
		||||
  VSpace,
 | 
			
		||||
  VTabbar,
 | 
			
		||||
  VTag,
 | 
			
		||||
} from "@halo-dev/components";
 | 
			
		||||
import ThemeListModal from "./components/ThemeListModal.vue";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { VAlert, VSpace, VTag } from "@halo-dev/components";
 | 
			
		||||
import type { ComputedRef, Ref } from "vue";
 | 
			
		||||
import { computed, inject, ref } from "vue";
 | 
			
		||||
import { RouterLink } from "vue-router";
 | 
			
		||||
import type { Theme } from "@halo-dev/api-client";
 | 
			
		||||
import { useThemeLifeCycle } from "./composables/use-theme";
 | 
			
		||||
 | 
			
		||||
const selectedTheme = ref<Theme>({} as Theme);
 | 
			
		||||
const themesModal = ref(false);
 | 
			
		||||
const tabActiveId = ref("detail");
 | 
			
		||||
const themeListRef = ref();
 | 
			
		||||
 | 
			
		||||
const { isActivated, activatedTheme, handleActiveTheme } =
 | 
			
		||||
  useThemeLifeCycle(selectedTheme);
 | 
			
		||||
const selectedTheme = inject<Ref<Theme>>(
 | 
			
		||||
  "selectedTheme",
 | 
			
		||||
  ref<Theme>({} as Theme)
 | 
			
		||||
);
 | 
			
		||||
const isActivated = inject<ComputedRef<boolean>>(
 | 
			
		||||
  "isActivated",
 | 
			
		||||
  computed(() => false)
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <ThemeListModal
 | 
			
		||||
    ref="themeListRef"
 | 
			
		||||
    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"
 | 
			
		||||
  <div class="bg-white px-4 py-4 sm:px-6">
 | 
			
		||||
    <div class="flex flex-row gap-3">
 | 
			
		||||
      <div v-if="selectedTheme.spec?.logo">
 | 
			
		||||
        <div
 | 
			
		||||
          class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
 | 
			
		||||
        >
 | 
			
		||||
          启用
 | 
			
		||||
        </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="tabActiveId"
 | 
			
		||||
          :items="[
 | 
			
		||||
            { id: 'detail', label: '详情' },
 | 
			
		||||
            { id: 'settings', label: '基础设置' },
 | 
			
		||||
          ]"
 | 
			
		||||
          class="w-full !rounded-none"
 | 
			
		||||
          type="outline"
 | 
			
		||||
        ></VTabbar>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <div v-if="tabActiveId === 'detail'">
 | 
			
		||||
        <div class="px-4 py-4 sm:px-6">
 | 
			
		||||
          <div class="flex flex-row gap-3">
 | 
			
		||||
            <div v-if="selectedTheme.spec?.logo">
 | 
			
		||||
          <img
 | 
			
		||||
            :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">
 | 
			
		||||
          {{ selectedTheme.spec?.displayName }}
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p class="mt-1 flex max-w-2xl items-center gap-2">
 | 
			
		||||
          <span class="text-sm text-gray-500">
 | 
			
		||||
            {{ selectedTheme.spec?.version }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <VTag>
 | 
			
		||||
            {{ isActivated ? "当前启用" : "未启用" }}
 | 
			
		||||
          </VTag>
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="border-t border-gray-200">
 | 
			
		||||
    <dl class="divide-y divide-gray-100">
 | 
			
		||||
      <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="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
 | 
			
		||||
                  :alt="selectedTheme.spec?.displayName"
 | 
			
		||||
                  :src="selectedTheme.spec?.logo"
 | 
			
		||||
                  class="h-full w-full"
 | 
			
		||||
                />
 | 
			
		||||
                <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>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
              <h3 class="text-lg font-medium leading-6 text-gray-900">
 | 
			
		||||
                {{ selectedTheme.spec?.displayName }}
 | 
			
		||||
              </h3>
 | 
			
		||||
              <p class="mt-1 flex max-w-2xl items-center gap-2">
 | 
			
		||||
                <span class="text-sm text-gray-500">
 | 
			
		||||
                  {{ selectedTheme.spec?.version }}
 | 
			
		||||
            </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>
 | 
			
		||||
                <VTag>
 | 
			
		||||
                  {{ isActivated ? "当前启用" : "未启用" }}
 | 
			
		||||
                </VTag>
 | 
			
		||||
              </p>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="border-t border-gray-200">
 | 
			
		||||
          <dl class="divide-y divide-gray-100">
 | 
			
		||||
            <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 class="text-xs">
 | 
			
		||||
                  <VSpace>
 | 
			
		||||
                    <VTag>未安装</VTag>
 | 
			
		||||
                  </VSpace>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </li>
 | 
			
		||||
          </ul>
 | 
			
		||||
        </dd>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <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>
 | 
			
		||||
    </dl>
 | 
			
		||||
  </div>
 | 
			
		||||
</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 ThemeSetting from "./ThemeSetting.vue";
 | 
			
		||||
import Visual from "./Visual.vue";
 | 
			
		||||
import { IconPalette } from "@halo-dev/components";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,13 +11,18 @@ export default definePlugin({
 | 
			
		|||
  routes: [
 | 
			
		||||
    {
 | 
			
		||||
      path: "/theme",
 | 
			
		||||
      component: BasicLayout,
 | 
			
		||||
      component: ThemeLayout,
 | 
			
		||||
      children: [
 | 
			
		||||
        {
 | 
			
		||||
          path: "",
 | 
			
		||||
          name: "Theme",
 | 
			
		||||
          name: "ThemeDetail",
 | 
			
		||||
          component: ThemeDetail,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: "settings/:group",
 | 
			
		||||
          name: "ThemeSetting",
 | 
			
		||||
          component: ThemeSetting,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ onMounted(() => {
 | 
			
		|||
});
 | 
			
		||||
 | 
			
		||||
watch([() => plugin.value, () => group?.value], () => {
 | 
			
		||||
  handleFetchConfigMap();
 | 
			
		||||
  handleFetchSettings();
 | 
			
		||||
  handleFetchConfigMap();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
{
 | 
			
		||||
  "extends": "@vue/tsconfig/tsconfig.web.json",
 | 
			
		||||
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
 | 
			
		||||
  "exclude": ["src/**/__tests__/*"],
 | 
			
		||||
  "exclude": ["src/**/__tests__/*", "packages/**/*"],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "composite": true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue